# GraphQL : Ariadne \[ [PyPi](https://pypi.org/project/ariadne/) | [Source](https://github.com/mirumee/ariadne/) | [Docs](https://ariadnegraphql.org/docs/intro) ] ## Overview - async & schema-first - uses graphql-core + starlette - subscriptions - custom scalars - file uploads - extensions: Apollo Tracing, OpenTracing - automatic resolvers mapping between camelCase and snake_case - `@convert_kwargs_to_snake_case` function decorator for converting camelCase kwargs to snake_case - simple sync dev server w/GraphiQL - support for multiple GraphQL APIs in same codebase with explicit type reuse ```bash $ pip install ariadne, uvicorn $ uvicorn example:app ``` #### Imports ```python ariadne MutationType, ObjectType, QueryType gql, load_schema_from_path, make_executable_schema ariadne.asgi GraphQL ariadne.asgi.handlers GraphQLHTTPHandler, GraphQLTransportWSHandler ariadne.types Extension ariadne.wsgi GraphQL ``` #### Example ```python # define types using SDL (wrapping string in gql function provides validation and better error traceback) type_defs = gql(''' # supports single-line comments type Query { people : [ Person! ]! } """ also, triple-quoted strings can be used for type descriptions """ type Person { firstName : String lastName : String age : Int fullName : String } ''') # map resolvers to Query fields using QueryType query = QueryType() # shortcut for ObjectType( "Query" ) # map resolvers to fields using decorator syntax @query.field( "people" ) [async] def resolve_people( *_ ): return [ { "firstName": "John", "lastName": "Doe", "age": 21 }, { "firstName": "Bob", "lastName": "Boberson", "age": 24 }, ] # map resolvers to fields using setters query.set_field( "people", resolve_people ) # map resolvers to custom type fields using ObjectType person = ObjectType( "Person" ) @person.field( "fullName" ) def resolve_person_fullname( person, *_ ): return "%s %s" % ( person[ "firstName" ], person[ "lastName" ] ) # create executable GraphQL schema schema = make_executable_schema( type_defs, query, person ) # create an ASGI or WSGI app using the schema, running in debug mode app = GraphQL( schema, debug=True ) ``` > [!NOTE] > In Ariadne, the process of adding the Python logic to GraphQL schema is called *binding to schema*, and special types that can be passed to `make_executable_schema`'s second argument are called *bindables*. The ASGI application: - Creates its own request object, an instance of the `Request` class from Starlette. Its scope and receive attributes are populated from the received request. - Uses Starlette's `JSONResponse` for its JSON responses. ## Usage #### Schemas ```python type_defs = load_schema_from_path( "schema.graphql" ) # load from file type_defs = load_schema_from_path( "schema" ) # load from directory containing graphql files type_defs = gql( "..." ) # load from string ``` ```python schema = make_executable_schema( type_defs, Query, ...other_schema_bindables..., convert_names_case=True ) # convert applies to resolver inputs and outputs, but not to field setters ``` #### Type Classes ```python SchemaBindable EnumType # optional — values are strings by default; EnumType( "Name", dict( A=1, ... ) ) InputType # InputType( "Name", out_type=func( dict ), out_names=dict( isGood="is_good" ) ) ObjectType InterfaceType MutationType QueryType SubscriptionType ScalarType # see Custom Types UnionType # only used to bind an explicit type resolver; simpler to just include "__typename" in returned object ``` #### Custom Types ```graphql scalar DateTime scalar Point ``` ```python DateTimeType = ScalarType( "DateTime", serializer = lambda value: value.isoformat(), value_parser = lambda value: dateutil.parser.parse( value ), # from pkg: python-dateutil ) PointType = ScalarType( "Point", serializer = lambda value: dict( lat=value.lat.value, lng=value.lng.value ), value_parser = lambda value: Point( value[ "lat" ], value[ "lng" ] ), ) ## Alternative methods: DateTimeType.[set_]serializer( func ) @DateTimeType.[set_]serializer def my_serializer( value ): ... ``` #### Resolvers Default resolver is provided by graphql-core: 1. uses `obj.get( "field" )` if dict, else `getattr( obj, "field" )` 2. if value is callable, calls it as a resolver — but WITHOUT the `obj` param ```python resolver( obj, info, **kwargs ) # if registered with a setter resolver( info, **kwargs ) # if called by the default resolver # Can specify the GraphQL args individually, providing defaults for optional args: resolver( *_, foo, bar=None ) request = info.context[ "request" ] request.get( "HTTP_USER_AGENT", "guest" ) request.headers.get( "user-agent", "guest" ) ``` ## Middleware When writing the ASGI middleware, remember to rely on the `request.scope` dict for storing additional data on the request object, instead of mutating the request object directly (like it's done in Django). ``` request.app_data # bad request.scope[ "app_data" ] # good ``` ## Starlette Integration ```python # without WebSockets gql_app = GraphQL( schema, debug=True ) app = Starlette( debug=True ) app.mount( "/graphql/", gql_app ) # with WebSockets gql_app = GraphQL( schema, debug=True, websocket_handler=GraphQLTransportWSHandler() ) app = Starlette( debug = True routes = [ Route( "/graphql/", gql_app.handle_request, methods=[ "GET", "POST", "OPTIONS" ] ) WebSocketRoute( "/graphql/", gql_app.handle_websocket ) ] ) ```