# 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 )
]
)
```