Ariadne

repository·main·Indexed 25 days ago

https://github.com/mirumee/ariadne

A schema-first Python library for implementing GraphQL servers. Ariadne focuses on simplicity and modularity, allowing developers to define API structures using Schema Definition Language (SDL) and map them to Python logic via resolvers. It supports ASGI and WSGI applications, providing tools for QueryType, MutationType, ObjectType, and various custom GraphQL types including ScalarType, EnumType, and UnionType. It includes utilities for schema validation, error formatting, and handling GraphQL subscriptions.

Tokens
89.3K
Snippets
193
Records
340
Agent score
79%

What's inside ariadne

  1. What is a resolver in Ariadne

    main

    In Ariadne, a resolver is any Python callable (synchronous or asynchronous) that accepts two positional arguments:

    1. obj: The value returned by the parent resolver. For root resolvers (on Query, Mutation, or Subscription), this is None if the server doesn't explicitly define a value.
    2. info: An instance of GraphQLResolveInfo. The most commonly used attribute on this object is info.context, which contains application-specific data like authentication state or HTTP requests.

    Resolvers can be standard functions or classes implementing __call__.

    def example_resolver(obj: Any, info: GraphQLResolveInfo):
        return obj.do_something()
    
    class FormResolver:
        def __call__(self, obj: Any, info: GraphQLResolveInfo, **data):
            ...
  2. Overview of Pluggable Subscription Handlers

    main

    Ariadne's subscription handler system is transport-agnostic, allowing you to implement custom delivery mechanisms such as Server-Sent Events (SSE) for browsers or HTTP callbacks for gateway architectures.

    Key benefits include:

    • Flexibility: Implement any transport protocol.
    • Reusability: Share subscription execution logic via generate_events().
    • Composability: Combine multiple handlers for different clients.
    • Clean separation: Transport concerns are separated from GraphQL execution logic.
  3. What is the N+1 problem in GraphQL?

    main

    The N+1 problem occurs when a GraphQL query retrieves a list of $N$ items, and for each item, a separate resolver is triggered to fetch a related object. This results in $1$ initial query for the list plus $N$ additional queries for the related data, leading to significant performance degradation.

    For example, if you fetch 20 messages and each message has a poster field that triggers a database lookup, you will execute 21 database queries instead of one efficient join or batch request.

  4. What is GraphQL middleware and how to use it

    main

    GraphQL middleware are Python functions or callable objects used to inject custom logic into the query executor. They are executed for every resolver call in a query.

    Signature

    Middleware functions share the same arguments as standard resolvers, but include one additional argument: resolver. The resolver argument is a callable representing the resolver associated with the currently resolved field. If multiple middlewares are enabled, resolver will be a partial function that chains to the next middleware in the execution chain.

    def middleware_function(resolver, obj, info, **args)

    Implementation Note

    Middleware is not supported by subscriptions. For subscription logic (like auth or logging), use Python decorators applied directly to your subscription source and resolver functions instead.

    Usage

    To apply middleware, pass a list of middleware functions to the middleware option of the GraphQLHTTPHandler when initializing your GraphQL ASGI app.

    from ariadne.asgi import GraphQL
    from ariadne.asgi.handlers import GraphQLHTTPHandler
    
    def my_middleware(resolver, obj, info, **args):
        value = resolver(obj, info, **args)
        return value
    
    app = GraphQL(
        schema,
        http_handler=GraphQLHTTPHandler(
            middleware=[my_middleware],
        ),
    )
  5. How Ariadne's schema-first workflow works

    main

    Ariadne follows a schema-first approach, meaning you define your API structure using Schema Definition Language (SDL) before implementing logic.

    The workflow consists of three main steps:

    1. Define Schema: Write your types, queries, and mutations in SDL. Use the gql() function to wrap these strings; this provides syntax validation and better error tracebacks during development.
    2. Bind Resolvers:
      • Use QueryType to bind Python functions to top-level fields in the Query type.
      • Use ObjectType to bind Python functions to specific fields of a custom type (e.g., adding a fullName field to a Person type).
    3. Execute: Use make_executable_schema() to combine your SDL and your type objects into a single executable schema, which can then be passed to an ASGI or WSGI application.
  6. Handle request data in Ariadne ASGI middleware

    main

    The Ariadne GraphQL application uses Starlette's Request class. When writing ASGI middleware, do not mutate the request object directly. Instead, store additional data within the request.scope dictionary to ensure compatibility with the ASGI specification.

    # This is wrong
    request.app_data
    
    # This is correct
    request.scope["app_data"]
  7. Access the Flask request in Ariadne resolvers

    main

    To access Flask-specific information (like headers or session data) inside an Ariadne resolver, you must pass the Flask request object into the context_value argument of graphql_sync during the POST request handling.

    In your resolver function, the request object will then be available via info.context.

    # In the Flask route:
    success, result = graphql_sync(
        schema,
        data,
        context_value={"request": request},
        debug=app.debug
    )
    
    # In the resolver:
    @query.field("hello")
    def resolve_hello(_, info):
        request = info.context
        user_agent = request.headers.get("User-Agent", "Guest")
        return "Hello, %s!" % user_agent
  8. Use result types for mutation feedback

    main

    Instead of returning simple scalars (like Boolean), it is a best practice to return specialized result types. These types can contain metadata about the operation, such as a success status, error messages, or the updated state of the object being modified.

    In Python, these result types can be returned as simple dict objects where the keys match the field names in the GraphQL type definition.

    Returning the updated object (e.g., the User object after a name change) allows modern GraphQL clients like Apollo Client to perform automatic cache updates and handle optimistic UI updates effectively.

    # Schema definition for a result type
    type_def = """
        type Mutation {
            login(username: String!, password: String!): LoginResult
        }
    
        type LoginResult {
            status: Boolean!
            error: Error
            user: User
        }
    """
    
    # Resolver returning a dictionary representing the result type
    def resolve_login(_, info, username, password):
        # ... logic ...
        if user:
            return {"status": True, "user": user}
        return {"status": False, "error": "Invalid username or password"}
  9. Choosing between Async and Sync Generators for Subscriptions

    main

    Deciding whether to use an async def or a standard def for your subscription source depends on your library ecosystem and performance requirements.

    Use Async Generators when:

    • You have access to async-native libraries (e.g., aiohttp, asyncpg).
    • You require maximum concurrency.
    • You are already working within an async context.

    Use Sync Generators when:

    • You must use legacy or synchronous-only libraries.
    • Blocking I/O is unavoidable.
    • You want to simplify your codebase by avoiding async/await complexity.
    • You are migrating existing synchronous code to GraphQL subscriptions.
  10. Implement GraphQL Unions with `UnionType`

    main

    To handle GraphQL unions in Python, use the UnionType class. Because a union field can return different Python types, you must provide a Type Resolver.

    Type Resolver Requirements:

    • It is a function that receives the object returned by the field's resolver.
    • It must return a str representing the name of the GraphQL type.
    • Optimization: If the returned Python object is a dict containing a "__typename" key, or a class with a __typename attribute matching the GraphQL type name, a manual type resolver is not required.

    Use the @union_type.type_resolver decorator to define the logic for mapping Python objects to GraphQL type names.

    from ariadne import UnionType
    
    result_type = UnionType("Result")
    
    @result_type.type_resolver
    def resolve_result_type(obj, *_) -> str:
        if isinstance(obj, UserModel):
            return "User"
        if isinstance(obj, PostModel):
            return "Post"
        raise ValueError(f"Unknown type: {obj}")
  11. How InterfaceType works

    main

    InterfaceType is a specialized class used to handle GraphQL interfaces. It serves two primary purposes:

    1. Type Resolution: It uses a type_resolver function to determine which concrete type an object belongs to at runtime. This resolver receives the object (obj) and the context (*_).
    2. Shared Field Resolvers: It allows you to define resolvers for fields that are common to all implementing types.

    Key Behavior Note: Unlike ObjectType, an InterfaceType will only assign a resolver to a field if that field does not already have a resolver set on the concrete type. This allows concrete types to override interface-level field resolvers.

  12. Implement GraphQL Subscriptions with `SubscriptionType`

    main

    To implement GraphQL subscriptions, use the SubscriptionType class. A subscription requires two distinct parts for each field:

    1. Subscription Source (Subscriber): A generator function (sync or async) that yields events or messages. This function is responsible for listening to an event stream (e.g., via a message broker) and yielding data when events occur. Sync generators are automatically wrapped to run in worker threads.
    2. Subscription Resolver: A function called with the message yielded by the source. Its role is to transform that message into the Python representation of the GraphQL type.

    Arguments defined on the GraphQL subscription field are passed to both the source and the resolver functions.

    Use the @subscription_type.source("field_name") decorator to bind a source to a field, and @subscription_type.field("field_name") to bind the resolver.