mirror of
https://github.com/graphql-python/graphene.git
synced 2025-09-21 19:32:33 +03:00
Revise documentation
- Add missing reference to `flask-graphql` in integrations - align documentation for resolver arguments (use root for 1st argument instead of self) - explore use of `parent` instead of `root` for first argument - clarify resolvers and object type documentation - add documentation for Meta class options for ObjectType - expand quickstart documentation for first time users - streamline order of documentation for first time users (broad -> specific) - document resolver quirks
This commit is contained in:
parent
40229b8a73
commit
abe58cc40e
|
@ -2,30 +2,29 @@
|
||||||
|
|
||||||
Big changes from v0.10.x to 1.0. While on the surface a lot of this just looks like shuffling around API, the entire codebase has been rewritten to handle some really great use cases and improved performance.
|
Big changes from v0.10.x to 1.0. While on the surface a lot of this just looks like shuffling around API, the entire codebase has been rewritten to handle some really great use cases and improved performance.
|
||||||
|
|
||||||
|
|
||||||
## Backwards Compatibility and Deprecation Warnings
|
## Backwards Compatibility and Deprecation Warnings
|
||||||
|
|
||||||
This has been a community project from the start, we need your help making the upgrade as smooth as possible for everybody!
|
This has been a community project from the start, we need your help making the upgrade as smooth as possible for everybody!
|
||||||
We have done our best to provide backwards compatibility with deprecated APIs.
|
We have done our best to provide backwards compatibility with deprecated APIs.
|
||||||
|
|
||||||
|
|
||||||
## Deprecations
|
## Deprecations
|
||||||
|
|
||||||
* `with_context` is no longer needed. Resolvers now always take the context argument.
|
- `with_context` is no longer needed. Resolvers now always take the context argument.
|
||||||
Before:
|
Before:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def resolve_xxx(self, args, info):
|
def resolve_xxx(root, args, info):
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
With 1.0:
|
With 1.0:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def resolve_xxx(self, args, context, info):
|
def resolve_xxx(root, args, context, info):
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
* `ObjectType` and `Interface` no longer accept the `abstract` option in the `Meta`.
|
- `ObjectType` and `Interface` no longer accept the `abstract` option in the `Meta`.
|
||||||
Inheriting fields should be now achieved using `AbstractType` inheritance.
|
Inheriting fields should be now achieved using `AbstractType` inheritance.
|
||||||
|
|
||||||
Before:
|
Before:
|
||||||
|
@ -42,6 +41,7 @@ We have done our best to provide backwards compatibility with deprecated APIs.
|
||||||
```
|
```
|
||||||
|
|
||||||
With 1.0:
|
With 1.0:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class MyBaseQuery(graphene.AbstractType):
|
class MyBaseQuery(graphene.AbstractType):
|
||||||
my_field = String()
|
my_field = String()
|
||||||
|
@ -50,9 +50,9 @@ We have done our best to provide backwards compatibility with deprecated APIs.
|
||||||
pass
|
pass
|
||||||
```
|
```
|
||||||
|
|
||||||
* The `type_name` option in the Meta in types is now `name`
|
- The `type_name` option in the Meta in types is now `name`
|
||||||
|
|
||||||
* Type references no longer work with strings, but with functions.
|
- Type references no longer work with strings, but with functions.
|
||||||
|
|
||||||
Before:
|
Before:
|
||||||
|
|
||||||
|
@ -70,7 +70,6 @@ We have done our best to provide backwards compatibility with deprecated APIs.
|
||||||
users = graphene.List(lambda: User)
|
users = graphene.List(lambda: User)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Schema
|
## Schema
|
||||||
|
|
||||||
Schemas in graphene `1.0` are `Immutable`, that means that once you create a `graphene.Schema` any
|
Schemas in graphene `1.0` are `Immutable`, that means that once you create a `graphene.Schema` any
|
||||||
|
@ -80,7 +79,6 @@ The `name` argument is removed from the Schema.
|
||||||
The arguments `executor` and `middlewares` are also removed from the `Schema` definition.
|
The arguments `executor` and `middlewares` are also removed from the `Schema` definition.
|
||||||
You can still use them, but by calling explicitly in the `execute` method in `graphql`.
|
You can still use them, but by calling explicitly in the `execute` method in `graphql`.
|
||||||
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# Old way
|
# Old way
|
||||||
schema = graphene.Schema(name='My Schema')
|
schema = graphene.Schema(name='My Schema')
|
||||||
|
@ -94,7 +92,6 @@ schema = graphene.Schema(
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Interfaces
|
## Interfaces
|
||||||
|
|
||||||
For implementing an Interface in an ObjectType, you have to add it onto `Meta.interfaces`.
|
For implementing an Interface in an ObjectType, you have to add it onto `Meta.interfaces`.
|
||||||
|
@ -131,7 +128,7 @@ class ReverseString(Mutation):
|
||||||
|
|
||||||
reversed = String()
|
reversed = String()
|
||||||
|
|
||||||
def mutate(self, args, context, info):
|
def mutate(root, args, context, info):
|
||||||
reversed = args.get('input')[::-1]
|
reversed = args.get('input')[::-1]
|
||||||
return ReverseString(reversed=reversed)
|
return ReverseString(reversed=reversed)
|
||||||
|
|
||||||
|
@ -158,14 +155,13 @@ class Query(ObjectType):
|
||||||
Also, if you wanted to create an `ObjectType` that implements `Node`, you have to do it
|
Also, if you wanted to create an `ObjectType` that implements `Node`, you have to do it
|
||||||
explicity.
|
explicity.
|
||||||
|
|
||||||
|
|
||||||
## Django
|
## Django
|
||||||
|
|
||||||
The Django integration with Graphene now has an independent package: `graphene-django`.
|
The Django integration with Graphene now has an independent package: `graphene-django`.
|
||||||
For installing, you have to replace the old `graphene[django]` with `graphene-django`.
|
For installing, you have to replace the old `graphene[django]` with `graphene-django`.
|
||||||
|
|
||||||
* As the package is now independent, you now have to import from `graphene_django`.
|
- As the package is now independent, you now have to import from `graphene_django`.
|
||||||
* **DjangoNode no longer exists**, please use `relay.Node` instead:
|
- **DjangoNode no longer exists**, please use `relay.Node` instead:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from graphene.relay import Node
|
from graphene.relay import Node
|
||||||
|
@ -181,8 +177,8 @@ For installing, you have to replace the old `graphene[django]` with `graphene-dj
|
||||||
The SQLAlchemy integration with Graphene now has an independent package: `graphene-sqlalchemy`.
|
The SQLAlchemy integration with Graphene now has an independent package: `graphene-sqlalchemy`.
|
||||||
For installing, you have to replace the old `graphene[sqlalchemy]` with `graphene-sqlalchemy`.
|
For installing, you have to replace the old `graphene[sqlalchemy]` with `graphene-sqlalchemy`.
|
||||||
|
|
||||||
* As the package is now independent, you have to import now from `graphene_sqlalchemy`.
|
- As the package is now independent, you have to import now from `graphene_sqlalchemy`.
|
||||||
* **SQLAlchemyNode no longer exists**, please use `relay.Node` instead:
|
- **SQLAlchemyNode no longer exists**, please use `relay.Node` instead:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from graphene.relay import Node
|
from graphene.relay import Node
|
||||||
|
|
|
@ -7,20 +7,22 @@ It also improves the field resolvers, [simplifying the code](#simpler-resolvers)
|
||||||
developer has to write to use them.
|
developer has to write to use them.
|
||||||
|
|
||||||
**Deprecations:**
|
**Deprecations:**
|
||||||
* [`AbstractType`](#abstracttype-deprecated)
|
|
||||||
* [`resolve_only_args`](#resolve_only_args)
|
- [`AbstractType`](#abstracttype-deprecated)
|
||||||
* [`Mutation.Input`](#mutationinput)
|
- [`resolve_only_args`](#resolve_only_args)
|
||||||
|
- [`Mutation.Input`](#mutationinput)
|
||||||
|
|
||||||
**Breaking changes:**
|
**Breaking changes:**
|
||||||
* [`Simpler Resolvers`](#simpler-resolvers)
|
|
||||||
* [`Node Connections`](#node-connections)
|
- [`Simpler Resolvers`](#simpler-resolvers)
|
||||||
|
- [`Node Connections`](#node-connections)
|
||||||
|
|
||||||
**New Features!**
|
**New Features!**
|
||||||
* [`InputObjectType`](#inputobjecttype)
|
|
||||||
* [`Meta as Class arguments`](#meta-as-class-arguments) (_only available for Python 3_)
|
|
||||||
|
|
||||||
|
- [`InputObjectType`](#inputobjecttype)
|
||||||
|
- [`Meta as Class arguments`](#meta-as-class-arguments) (_only available for Python 3_)
|
||||||
|
|
||||||
> The type metaclasses are now deleted as they are no longer necessary. If your code was depending
|
> The type metaclasses are now deleted as they are no longer necessary. If your code was depending
|
||||||
> on this strategy for creating custom attrs, see an [example on how to do it in 2.0](https://github.com/graphql-python/graphene/blob/v2.0.0/graphene/tests/issues/test_425.py).
|
> on this strategy for creating custom attrs, see an [example on how to do it in 2.0](https://github.com/graphql-python/graphene/blob/v2.0.0/graphene/tests/issues/test_425.py).
|
||||||
|
|
||||||
## Deprecations
|
## Deprecations
|
||||||
|
@ -49,7 +51,7 @@ class Pet(CommonFields, Interface):
|
||||||
pass
|
pass
|
||||||
```
|
```
|
||||||
|
|
||||||
### resolve\_only\_args
|
### resolve_only_args
|
||||||
|
|
||||||
`resolve_only_args` is now deprecated as the resolver API has been simplified.
|
`resolve_only_args` is now deprecated as the resolver API has been simplified.
|
||||||
|
|
||||||
|
@ -60,8 +62,8 @@ class User(ObjectType):
|
||||||
name = String()
|
name = String()
|
||||||
|
|
||||||
@resolve_only_args
|
@resolve_only_args
|
||||||
def resolve_name(self):
|
def resolve_name(root):
|
||||||
return self.name
|
return root.name
|
||||||
```
|
```
|
||||||
|
|
||||||
With 2.0:
|
With 2.0:
|
||||||
|
@ -70,8 +72,8 @@ With 2.0:
|
||||||
class User(ObjectType):
|
class User(ObjectType):
|
||||||
name = String()
|
name = String()
|
||||||
|
|
||||||
def resolve_name(self, info):
|
def resolve_name(root, info):
|
||||||
return self.name
|
return root.name
|
||||||
```
|
```
|
||||||
|
|
||||||
### Mutation.Input
|
### Mutation.Input
|
||||||
|
@ -94,7 +96,6 @@ class User(Mutation):
|
||||||
name = String()
|
name = String()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Breaking Changes
|
## Breaking Changes
|
||||||
|
|
||||||
### Simpler resolvers
|
### Simpler resolvers
|
||||||
|
@ -108,7 +109,7 @@ Before:
|
||||||
```python
|
```python
|
||||||
my_field = graphene.String(my_arg=graphene.String())
|
my_field = graphene.String(my_arg=graphene.String())
|
||||||
|
|
||||||
def resolve_my_field(self, args, context, info):
|
def resolve_my_field(root, args, context, info):
|
||||||
my_arg = args.get('my_arg')
|
my_arg = args.get('my_arg')
|
||||||
return ...
|
return ...
|
||||||
```
|
```
|
||||||
|
@ -118,7 +119,7 @@ With 2.0:
|
||||||
```python
|
```python
|
||||||
my_field = graphene.String(my_arg=graphene.String())
|
my_field = graphene.String(my_arg=graphene.String())
|
||||||
|
|
||||||
def resolve_my_field(self, info, my_arg):
|
def resolve_my_field(root, info, my_arg):
|
||||||
return ...
|
return ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -126,7 +127,7 @@ def resolve_my_field(self, info, my_arg):
|
||||||
You may need something like this:
|
You may need something like this:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def resolve_my_field(self, info, known_field1, known_field2, **args): ## get other args with: args.get('arg_key')
|
def resolve_my_field(root, info, known_field1, known_field2, **args): ## get other args with: args.get('arg_key')
|
||||||
```
|
```
|
||||||
|
|
||||||
And, if you need the context in the resolver, you can use `info.context`:
|
And, if you need the context in the resolver, you can use `info.context`:
|
||||||
|
@ -134,7 +135,7 @@ And, if you need the context in the resolver, you can use `info.context`:
|
||||||
```python
|
```python
|
||||||
my_field = graphene.String(my_arg=graphene.String())
|
my_field = graphene.String(my_arg=graphene.String())
|
||||||
|
|
||||||
def resolve_my_field(self, info, my_arg):
|
def resolve_my_field(root, info, my_arg):
|
||||||
context = info.context
|
context = info.context
|
||||||
return ...
|
return ...
|
||||||
```
|
```
|
||||||
|
@ -188,6 +189,7 @@ class MyObject(ObjectType):
|
||||||
```
|
```
|
||||||
|
|
||||||
To:
|
To:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class MyObject(ObjectType):
|
class MyObject(ObjectType):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -203,30 +205,32 @@ class MyObject(ObjectType):
|
||||||
The parameters' order of `get_node_from_global_id` method has changed. You may need to adjust your [Node Root Field](http://docs.graphene-python.org/en/latest/relay/nodes/#node-root-field) and maybe other places that uses this method to obtain an object.
|
The parameters' order of `get_node_from_global_id` method has changed. You may need to adjust your [Node Root Field](http://docs.graphene-python.org/en/latest/relay/nodes/#node-root-field) and maybe other places that uses this method to obtain an object.
|
||||||
|
|
||||||
Before:
|
Before:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class RootQuery(object):
|
class RootQuery(object):
|
||||||
...
|
...
|
||||||
node = Field(relay.Node, id=ID(required=True))
|
node = Field(relay.Node, id=ID(required=True))
|
||||||
|
|
||||||
def resolve_node(self, args, context, info):
|
def resolve_node(root, args, context, info):
|
||||||
node = relay.Node.get_node_from_global_id(args['id'], context, info)
|
node = relay.Node.get_node_from_global_id(args['id'], context, info)
|
||||||
return node
|
return node
|
||||||
```
|
```
|
||||||
|
|
||||||
Now:
|
Now:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class RootQuery(object):
|
class RootQuery(object):
|
||||||
...
|
...
|
||||||
node = Field(relay.Node, id=ID(required=True))
|
node = Field(relay.Node, id=ID(required=True))
|
||||||
|
|
||||||
def resolve_node(self, info, id):
|
def resolve_node(root, info, id):
|
||||||
node = relay.Node.get_node_from_global_id(info, id)
|
node = relay.Node.get_node_from_global_id(info, id)
|
||||||
return node
|
return node
|
||||||
```
|
```
|
||||||
|
|
||||||
## Mutation.mutate
|
## Mutation.mutate
|
||||||
|
|
||||||
Now only receives (`self`, `info`, `**args`) and is not a @classmethod
|
Now only receives (`root`, `info`, `**kwargs`) and is not a @classmethod
|
||||||
|
|
||||||
Before:
|
Before:
|
||||||
|
|
||||||
|
@ -245,7 +249,7 @@ With 2.0:
|
||||||
class SomeMutation(Mutation):
|
class SomeMutation(Mutation):
|
||||||
...
|
...
|
||||||
|
|
||||||
def mutate(self, info, **args):
|
def mutate(root, info, **args):
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -258,17 +262,14 @@ class SomeMutation(Mutation):
|
||||||
last_name = String(required=True)
|
last_name = String(required=True)
|
||||||
...
|
...
|
||||||
|
|
||||||
def mutate(self, info, first_name, last_name):
|
def mutate(root, info, first_name, last_name):
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## ClientIDMutation.mutate_and_get_payload
|
## ClientIDMutation.mutate_and_get_payload
|
||||||
|
|
||||||
Now only receives (`root`, `info`, `**input`)
|
Now only receives (`root`, `info`, `**input`)
|
||||||
|
|
||||||
|
|
||||||
### Middlewares
|
### Middlewares
|
||||||
|
|
||||||
If you are using Middelwares, you need to some adjustments:
|
If you are using Middelwares, you need to some adjustments:
|
||||||
|
@ -294,10 +295,9 @@ class MyGrapheneMiddleware(object):
|
||||||
## Middleware code
|
## Middleware code
|
||||||
|
|
||||||
info.context = context
|
info.context = context
|
||||||
return next_mw(root, info, **args)```
|
return next_mw(root, info, **args)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## New Features
|
## New Features
|
||||||
|
|
||||||
### InputObjectType
|
### InputObjectType
|
||||||
|
@ -321,7 +321,7 @@ class Query(ObjectType):
|
||||||
user = graphene.Field(User, input=UserInput())
|
user = graphene.Field(User, input=UserInput())
|
||||||
|
|
||||||
@resolve_only_args
|
@resolve_only_args
|
||||||
def resolve_user(self, input):
|
def resolve_user(root, input):
|
||||||
user_id = input.get('id')
|
user_id = input.get('id')
|
||||||
if is_valid_input(user_id):
|
if is_valid_input(user_id):
|
||||||
return get_user(user_id)
|
return get_user(user_id)
|
||||||
|
@ -334,18 +334,17 @@ class UserInput(InputObjectType):
|
||||||
id = ID(required=True)
|
id = ID(required=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_valid(self):
|
def is_valid(root):
|
||||||
return self.id.startswith('userid_')
|
return root.id.startswith('userid_')
|
||||||
|
|
||||||
class Query(ObjectType):
|
class Query(ObjectType):
|
||||||
user = graphene.Field(User, input=UserInput())
|
user = graphene.Field(User, input=UserInput())
|
||||||
|
|
||||||
def resolve_user(self, info, input):
|
def resolve_user(root, info, input):
|
||||||
if input.is_valid:
|
if input.is_valid:
|
||||||
return get_user(input.id)
|
return get_user(input.id)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Meta as Class arguments
|
### Meta as Class arguments
|
||||||
|
|
||||||
Now you can use the meta options as class arguments (**ONLY PYTHON 3**).
|
Now you can use the meta options as class arguments (**ONLY PYTHON 3**).
|
||||||
|
@ -366,7 +365,6 @@ class Dog(ObjectType, interfaces=[Pet]):
|
||||||
name = String()
|
name = String()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Abstract types
|
### Abstract types
|
||||||
|
|
||||||
Now you can create abstact types super easily, without the need of subclassing the meta.
|
Now you can create abstact types super easily, without the need of subclassing the meta.
|
||||||
|
@ -378,10 +376,10 @@ class Base(ObjectType):
|
||||||
|
|
||||||
id = ID()
|
id = ID()
|
||||||
|
|
||||||
def resolve_id(self, info):
|
def resolve_id(root, info):
|
||||||
return "{type}_{id}".format(
|
return "{type}_{id}".format(
|
||||||
type=self.__class__.__name__,
|
type=root.__class__.__name__,
|
||||||
id=self.id
|
id=root.id
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -111,8 +111,8 @@ leaner code and at most 4 database requests, and possibly fewer if there are cac
|
||||||
best_friend = graphene.Field(lambda: User)
|
best_friend = graphene.Field(lambda: User)
|
||||||
friends = graphene.List(lambda: User)
|
friends = graphene.List(lambda: User)
|
||||||
|
|
||||||
def resolve_best_friend(self, info):
|
def resolve_best_friend(root, info):
|
||||||
return user_loader.load(self.best_friend_id)
|
return user_loader.load(root.best_friend_id)
|
||||||
|
|
||||||
def resolve_friends(self, info):
|
def resolve_friends(root, info):
|
||||||
return user_loader.load_many(self.friend_ids)
|
return user_loader.load_many(root.friend_ids)
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
.. _SchemaExecute:
|
||||||
|
|
||||||
Executing a query
|
Executing a query
|
||||||
=================
|
=================
|
||||||
|
|
||||||
|
@ -13,6 +15,8 @@ For executing a query a schema, you can directly call the ``execute`` method on
|
||||||
``result`` represents the result of execution. ``result.data`` is the result of executing the query, ``result.errors`` is ``None`` if no errors occurred, and is a non-empty list if an error occurred.
|
``result`` represents the result of execution. ``result.data`` is the result of executing the query, ``result.errors`` is ``None`` if no errors occurred, and is a non-empty list if an error occurred.
|
||||||
|
|
||||||
|
|
||||||
|
.. _SchemaExecuteContext:
|
||||||
|
|
||||||
Context
|
Context
|
||||||
_______
|
_______
|
||||||
|
|
||||||
|
@ -59,3 +63,66 @@ You can pass variables to a query via ``variables``.
|
||||||
''',
|
''',
|
||||||
variables={'id': 12},
|
variables={'id': 12},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Root Value
|
||||||
|
__________
|
||||||
|
|
||||||
|
Value used for :ref:`ResolverRootArgument` in root queries and mutations can be overridden using ``root`` parameter.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
class Query(graphene.ObjectType):
|
||||||
|
me = graphene.Field(User)
|
||||||
|
|
||||||
|
def resolve_user(root, info):
|
||||||
|
return get_user_by_id(root.id)
|
||||||
|
|
||||||
|
schema = graphene.Schema(Query)
|
||||||
|
user_root = User(id=12, name='bob'}
|
||||||
|
result = schema.execute(
|
||||||
|
'''
|
||||||
|
query getUser {
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''',
|
||||||
|
root=user_root
|
||||||
|
)
|
||||||
|
|
||||||
|
Operation Name
|
||||||
|
______________
|
||||||
|
|
||||||
|
If there are multiple operations defined in a query string, ``operation_name`` should be used to indicate which should be executed.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
class Query(graphene.ObjectType):
|
||||||
|
me = graphene.Field(User)
|
||||||
|
|
||||||
|
def resolve_user(root, info):
|
||||||
|
return get_user_by_id(12)
|
||||||
|
|
||||||
|
schema = graphene.Schema(Query)
|
||||||
|
query_string = '''
|
||||||
|
query getUserWithFirstName {
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
query getUserWithFullName {
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
result = schema.execute(
|
||||||
|
query_string,
|
||||||
|
operation_name='getUserWithFullName'
|
||||||
|
)
|
||||||
|
|
|
@ -12,10 +12,13 @@ Contents:
|
||||||
relay/index
|
relay/index
|
||||||
testing/index
|
testing/index
|
||||||
|
|
||||||
|
.. _Integrations:
|
||||||
|
|
||||||
Integrations
|
Integrations
|
||||||
-----
|
------------
|
||||||
|
|
||||||
* `Graphene-Django <http://docs.graphene-python.org/projects/django/en/latest/>`_ (`source <https://github.com/graphql-python/graphene-django/>`_)
|
* `Graphene-Django <http://docs.graphene-python.org/projects/django/en/latest/>`_ (`source <https://github.com/graphql-python/graphene-django/>`_)
|
||||||
|
* Flask-Graphql (`source <https://github.com/graphql-python/flask-graphql>`_)
|
||||||
* `Graphene-SQLAlchemy <http://docs.graphene-python.org/projects/sqlalchemy/en/latest/>`_ (`source <https://github.com/graphql-python/graphene-sqlalchemy/>`_)
|
* `Graphene-SQLAlchemy <http://docs.graphene-python.org/projects/sqlalchemy/en/latest/>`_ (`source <https://github.com/graphql-python/graphene-sqlalchemy/>`_)
|
||||||
* `Graphene-GAE <http://docs.graphene-python.org/projects/gae/en/latest/>`_ (`source <https://github.com/graphql-python/graphene-gae/>`_)
|
* `Graphene-GAE <http://docs.graphene-python.org/projects/gae/en/latest/>`_ (`source <https://github.com/graphql-python/graphene-gae/>`_)
|
||||||
* `Graphene-Mongo <http://graphene-mongo.readthedocs.io/en/latest/>`_ (`source <https://github.com/graphql-python/graphene-mongo>`_)
|
* `Graphene-Mongo <http://graphene-mongo.readthedocs.io/en/latest/>`_ (`source <https://github.com/graphql-python/graphene-mongo>`_)
|
||||||
|
|
|
@ -1,62 +1,143 @@
|
||||||
Getting started
|
Getting started
|
||||||
===============
|
===============
|
||||||
|
|
||||||
|
Introduction
|
||||||
|
------------
|
||||||
|
|
||||||
What is GraphQL?
|
What is GraphQL?
|
||||||
----------------
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
For an introduction to GraphQL and an overview of its concepts, please refer
|
GraphQL is a query language for your API.
|
||||||
to `the official introduction <http://graphql.org/learn/>`_.
|
|
||||||
|
It provides a standard way to:
|
||||||
|
|
||||||
|
* *describe data provided by a server* in a statically typed **Schema**
|
||||||
|
* *request data* in a **Query** which exactly describes your data requirements and
|
||||||
|
* *receive data* in a **Response** containing only the data you requested.
|
||||||
|
|
||||||
|
For an introduction to GraphQL and an overview of its concepts, please refer to `the official GraphQL documentation`_.
|
||||||
|
|
||||||
|
.. _the official GraphQL documentation: http://graphql.org/learn/
|
||||||
|
|
||||||
|
What is Graphene?
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Graphene is a library that provides tools to implement a GraphQL API in Python using a *code-first* approach.
|
||||||
|
|
||||||
|
Compare Graphene's *code-first* approach to building a GraphQL API with *schema-first* approaches like `Apollo Server`_ (JavaScript) or Ariadne_ (Python). Instead of writing GraphQL **Schema Definition Langauge (SDL)**, we write Python code to describe the data provided by your server.
|
||||||
|
|
||||||
|
.. _Apollo Server: https://www.apollographql.com/docs/apollo-server/
|
||||||
|
|
||||||
|
.. _Ariadne: https://ariadne.readthedocs.io
|
||||||
|
|
||||||
|
Graphene is fully featured with integrations for the most popular web frameworks and ORM and easy Relay Compliance.
|
||||||
|
|
||||||
|
An example in Graphene
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
Let’s build a basic GraphQL schema to say "hello" and "goodbye" in Graphene.
|
||||||
|
|
||||||
|
When we send a **Query** requesting only one **Field**, ``hello``, and specify a value for the ``name`` **Argument**...
|
||||||
|
|
||||||
|
.. code::
|
||||||
|
|
||||||
|
{
|
||||||
|
hello(name: "friend")
|
||||||
|
}
|
||||||
|
|
||||||
|
...we would expect the following Response containing only the data requested (the ``goodbye`` field is not resolved).
|
||||||
|
|
||||||
|
.. code::
|
||||||
|
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"hello": "Hello friend!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Let’s build a basic GraphQL schema from scratch.
|
|
||||||
|
|
||||||
Requirements
|
Requirements
|
||||||
------------
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
- Python (2.7, 3.4, 3.5, 3.6, pypy)
|
- Python (2.7, 3.4, 3.5, 3.6, pypy)
|
||||||
- Graphene (2.0)
|
- Graphene (2.0)
|
||||||
|
|
||||||
Project setup
|
Project setup
|
||||||
-------------
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
pip install "graphene>=2.0"
|
pip install "graphene>=2.0"
|
||||||
|
|
||||||
Creating a basic Schema
|
Creating a basic Schema
|
||||||
-----------------------
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
A GraphQL schema describes your data model, and provides a GraphQL
|
In Graphene, we can define a simple schema using the following code:
|
||||||
server with an associated set of resolve methods that know how to fetch
|
|
||||||
data.
|
|
||||||
|
|
||||||
We are going to create a very simple schema, with a ``Query`` with only
|
|
||||||
one field: ``hello`` and an input name. And when we query it, it should return ``"Hello
|
|
||||||
{argument}"``.
|
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
import graphene
|
import graphene
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
class Query(graphene.ObjectType):
|
||||||
hello = graphene.String(argument=graphene.String(default_value="stranger"))
|
# this defines a Field `hello` in our Schema with a single Argument `name`
|
||||||
|
hello = graphene.String(name=graphene.String(default_value="stranger"))
|
||||||
|
goodbye = graphene.String()
|
||||||
|
|
||||||
def resolve_hello(self, info, argument):
|
# our Resolver method takes the GraphQL context (root, info) as well as
|
||||||
return 'Hello ' + argument
|
# Argument (name) for the Field and returns data for the query Response
|
||||||
|
def resolve_hello(root, info, name):
|
||||||
|
return f'Hello {name}!'
|
||||||
|
|
||||||
|
def resolve_goodbye(root, info):
|
||||||
|
return 'See ya!'
|
||||||
|
|
||||||
schema = graphene.Schema(query=Query)
|
schema = graphene.Schema(query=Query)
|
||||||
|
|
||||||
Querying
|
|
||||||
--------
|
|
||||||
|
|
||||||
Then we can start querying our schema:
|
A GraphQL **Schema** describes each **Field** in the data model provided by the server using scalar types like *String*, *Int* and *Enum* and compound types like *List* and *Object*. For more details refer to the Graphene :ref:`TypesReference`.
|
||||||
|
|
||||||
|
Our schema can also define any number of **Arguments** for our **Fields**. This is a powerful way for a **Query** to describe the exact data requirements for each **Field**.
|
||||||
|
|
||||||
|
For each **Field** in our **Schema**, we write a **Resolver** method to fetch data requested by a client's **Query** using the current context and **Arguments**. For more details, refer to this section on :ref:`Resolvers`.
|
||||||
|
|
||||||
|
Schema Definition Language (SDL)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
In the `GraphQL Schema Definition Language`_, we could describe the feilds defined by our example code as show below.
|
||||||
|
|
||||||
|
.. _GraphQL Schema Definition Language: https://graphql.org/learn/schema/
|
||||||
|
|
||||||
|
.. code::
|
||||||
|
|
||||||
|
type Query {
|
||||||
|
hello(name: String = "stranger"): String
|
||||||
|
goodbye: String
|
||||||
|
}
|
||||||
|
|
||||||
|
Further examples in this documentation will use SDL to describe schema created by ObjectTypes and other fields.
|
||||||
|
|
||||||
|
Querying
|
||||||
|
~~~~~~~~
|
||||||
|
|
||||||
|
Then we can start querying our **Schema** by passing a GraphQL query string to ``execute``:
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
result = schema.execute('{ hello }')
|
# we can query for our field (with the default argument)
|
||||||
print(result.data['hello']) # "Hello stranger"
|
query_string = '{ hello }'
|
||||||
|
result = schema.execute(query_string)
|
||||||
|
print(result.data['hello'])
|
||||||
|
# "Hello stranger"
|
||||||
|
|
||||||
# or passing the argument in the query
|
# or passing the argument in the query
|
||||||
result = schema.execute('{ hello (argument: "graph") }')
|
query_string_with_argument = '{ hello (name: "GraphQL") }'
|
||||||
print(result.data['hello']) # "Hello graph"
|
result = schema.execute(query_with_argument)
|
||||||
|
print(result.data['hello'])
|
||||||
|
# "Hello GraphQL!"
|
||||||
|
|
||||||
Congrats! You got your first graphene schema working!
|
Next steps
|
||||||
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
Congrats! You got your first Graphene schema working!
|
||||||
|
|
||||||
|
Normally, we don't need to directly execute a query string against our schema as Graphene provides many useful Integrations with popular web frameworks like Flask and Django. Check out :ref:`Integrations` for more information on how to get started serving your GraphQL API.
|
||||||
|
|
|
@ -41,5 +41,5 @@ that implements ``Node`` will have a default Connection.
|
||||||
name = graphene.String()
|
name = graphene.String()
|
||||||
ships = relay.ConnectionField(ShipConnection)
|
ships = relay.ConnectionField(ShipConnection)
|
||||||
|
|
||||||
def resolve_ships(self, info):
|
def resolve_ships(root, info):
|
||||||
return []
|
return []
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
.. _TypesReference:
|
||||||
|
|
||||||
===============
|
===============
|
||||||
Types Reference
|
Types Reference
|
||||||
===============
|
===============
|
||||||
|
@ -5,12 +7,12 @@ Types Reference
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
enums
|
schema
|
||||||
scalars
|
scalars
|
||||||
list-and-nonnull
|
list-and-nonnull
|
||||||
objecttypes
|
objecttypes
|
||||||
|
enums
|
||||||
interfaces
|
interfaces
|
||||||
unions
|
unions
|
||||||
schema
|
|
||||||
mutations
|
mutations
|
||||||
abstracttypes
|
abstracttypes
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
.. _Interfaces:
|
||||||
|
|
||||||
Interfaces
|
Interfaces
|
||||||
==========
|
==========
|
||||||
|
|
||||||
|
@ -82,7 +84,7 @@ For example, you can define a field ``hero`` that resolves to any
|
||||||
episode=graphene.Int(required=True)
|
episode=graphene.Int(required=True)
|
||||||
)
|
)
|
||||||
|
|
||||||
def resolve_hero(_, info, episode):
|
def resolve_hero(root, info, episode):
|
||||||
# Luke is the hero of Episode V
|
# Luke is the hero of Episode V
|
||||||
if episode == 5:
|
if episode == 5:
|
||||||
return get_human(name='Luke Skywalker')
|
return get_human(name='Luke Skywalker')
|
||||||
|
|
|
@ -19,7 +19,7 @@ This example defines a Mutation:
|
||||||
ok = graphene.Boolean()
|
ok = graphene.Boolean()
|
||||||
person = graphene.Field(lambda: Person)
|
person = graphene.Field(lambda: Person)
|
||||||
|
|
||||||
def mutate(self, info, name):
|
def mutate(root, info, name):
|
||||||
person = Person(name=name)
|
person = Person(name=name)
|
||||||
ok = True
|
ok = True
|
||||||
return CreatePerson(person=person, ok=ok)
|
return CreatePerson(person=person, ok=ok)
|
||||||
|
@ -32,7 +32,8 @@ resolved.
|
||||||
only argument for the mutation.
|
only argument for the mutation.
|
||||||
|
|
||||||
**mutate** is the function that will be applied once the mutation is
|
**mutate** is the function that will be applied once the mutation is
|
||||||
called.
|
called. This method is just a special resolver that we can change
|
||||||
|
data within. It takes the same arguments as the standard query :ref:`ResolverArguments`.
|
||||||
|
|
||||||
So, we can finish our schema like this:
|
So, we can finish our schema like this:
|
||||||
|
|
||||||
|
@ -157,7 +158,7 @@ To return an existing ObjectType instead of a mutation-specific type, set the **
|
||||||
|
|
||||||
Output = Person
|
Output = Person
|
||||||
|
|
||||||
def mutate(self, info, name):
|
def mutate(root, info, name):
|
||||||
return Person(name=name)
|
return Person(name=name)
|
||||||
|
|
||||||
Then, if we query (``schema.execute(query_str)``) the following:
|
Then, if we query (``schema.execute(query_str)``) the following:
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
ObjectTypes
|
.. _ObjectType:
|
||||||
===========
|
|
||||||
|
|
||||||
An ObjectType is the single, definitive source of information about your
|
ObjectType
|
||||||
data. It contains the essential fields and behaviors of the data you’re
|
==========
|
||||||
querying.
|
|
||||||
|
A Graphene *ObjectType* is the building block used to define the relationship between **Fields** in your **Schema** and how their data is retrieved.
|
||||||
|
|
||||||
The basics:
|
The basics:
|
||||||
|
|
||||||
- Each ObjectType is a Python class that inherits from
|
- Each ObjectType is a Python class that inherits from ``graphene.ObjectType``.
|
||||||
``graphene.ObjectType``.
|
|
||||||
- Each attribute of the ObjectType represents a ``Field``.
|
- Each attribute of the ObjectType represents a ``Field``.
|
||||||
|
- Each ``Field`` has a :ref:`resolver method<Resolvers>` to fetch data (or :ref:`DefaultResolver`).
|
||||||
|
|
||||||
Quick example
|
Quick example
|
||||||
-------------
|
-------------
|
||||||
|
@ -18,19 +18,15 @@ This example model defines a Person, with a first and a last name:
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
import graphene
|
|
||||||
|
|
||||||
class Person(graphene.ObjectType):
|
class Person(graphene.ObjectType):
|
||||||
first_name = graphene.String()
|
first_name = graphene.String()
|
||||||
last_name = graphene.String()
|
last_name = graphene.String()
|
||||||
full_name = graphene.String()
|
full_name = graphene.String()
|
||||||
|
|
||||||
def resolve_full_name(root, info):
|
def resolve_full_name(parent, info):
|
||||||
return '{} {}'.format(root.first_name, root.last_name)
|
return f'{parent.first_name} {parent.last_name}'
|
||||||
|
|
||||||
**first\_name** and **last\_name** are fields of the ObjectType. Each
|
This *ObjectType* defines the feild **first\_name**, **last\_name**, and **full\_name**. Each field is specified as a class attribute, and each attribute maps to a Field. Data is fetched by our ``resolve_full_name`` :ref:`resolver method<Resolvers>` for ``full_name`` field and the :ref:`DefaultResolver` for other fields.
|
||||||
field is specified as a class attribute, and each attribute maps to a
|
|
||||||
Field.
|
|
||||||
|
|
||||||
The above ``Person`` ObjectType has the following schema representation:
|
The above ``Person`` ObjectType has the following schema representation:
|
||||||
|
|
||||||
|
@ -42,52 +38,102 @@ The above ``Person`` ObjectType has the following schema representation:
|
||||||
fullName: String
|
fullName: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.. _Resolvers:
|
||||||
|
|
||||||
Resolvers
|
Resolvers
|
||||||
---------
|
---------
|
||||||
|
|
||||||
A resolver is a method that resolves certain fields within an
|
A **Resolver** is a method that helps us answer **Queries** by fetching data for a **Field** in our **Schema**.
|
||||||
``ObjectType``. If not specified otherwise, the resolver of a
|
|
||||||
field is the ``resolve_{field_name}`` method on the ``ObjectType``.
|
|
||||||
|
|
||||||
By default resolvers take the arguments ``info`` and ``*args``.
|
Resolvers are lazily executed, so if a field is not included in a query, its resolver will not be executed.
|
||||||
|
|
||||||
NOTE: The resolvers on an ``ObjectType`` are always treated as ``staticmethod``\ s,
|
Each field on an *ObjectType* in Graphene should have a corresponding resolver method to fetch data. This resolver method should match the field name. For example, in the ``Person`` type above, the ``full_name`` field is resolved by the method ``resolve_full_name``.
|
||||||
so the first argument to the resolver method ``self`` (or ``root``) need
|
|
||||||
not be an actual instance of the ``ObjectType``.
|
|
||||||
|
|
||||||
If an explicit resolver is not defined on the ``ObjectType`` then Graphene will
|
Each resolver method takes the parameters:
|
||||||
attempt to use a property with the same name on the object or dict that is
|
* :ref:`ResolverRootArgument` for the value object use to resolve most fields
|
||||||
passed to the ``ObjectType``.
|
* :ref:`ResolverInfoArgument` for query and schema meta information and per-request context
|
||||||
|
* :ref:`ResolverGraphQLArguments` as defined on the **Field**.
|
||||||
|
|
||||||
|
.. _ResolverArguments:
|
||||||
|
|
||||||
|
Resolver Arguments
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. _ResolverRootArgument:
|
||||||
|
|
||||||
|
Parent Value Object (*parent*)
|
||||||
|
******************************
|
||||||
|
|
||||||
|
This parameter is typically used to derive the values for most fields on an *ObjectType*.
|
||||||
|
|
||||||
|
The first parameter of a resolver method (*parent*) is the value object returned from the resolver of the parent field. If there is no parent field, such as a root Query field, then the value for *parent* is set to the ``root_value`` configured while executing the query (default ``None``). See :ref:`SchemaExecute` for more details on executing queries.
|
||||||
|
|
||||||
|
Resolver example
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
If we have a schema with Person type and one field on the root query.
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
import graphene
|
import graphene
|
||||||
|
|
||||||
class Person(graphene.ObjectType):
|
class Person(graphene.ObjectType):
|
||||||
first_name = graphene.String()
|
full_name = graphene.String()
|
||||||
last_name = graphene.String()
|
|
||||||
|
def resolve_full_name(parent, info):
|
||||||
|
return f'{parent.first_name} {parent.last_name}'
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
class Query(graphene.ObjectType):
|
||||||
me = graphene.Field(Person)
|
me = graphene.Field(Person)
|
||||||
best_friend = graphene.Field(Person)
|
|
||||||
|
|
||||||
def resolve_me(_, info):
|
def resolve_me(parent, info):
|
||||||
# returns an object that represents a Person
|
# returns an object that represents a Person
|
||||||
return get_human(name='Luke Skywalker')
|
return get_human(name='Luke Skywalker')
|
||||||
|
|
||||||
def resolve_best_friend(_, info):
|
When we execute a query against that schema.
|
||||||
return {
|
|
||||||
"first_name": "R2",
|
|
||||||
"last_name": "D2",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
Resolvers with arguments
|
schema = Schema(query=Query)
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
query_string = "{ me { fullName } }"
|
||||||
|
result = schema.execute(query_string)
|
||||||
|
|
||||||
|
assert result["data"]["me"] == {"fullName": "Luke Skywalker")
|
||||||
|
|
||||||
|
Then we go through the following steps to resolve this query:
|
||||||
|
|
||||||
|
* ``parent`` is set with the root_value from query execution (None).
|
||||||
|
* ``Query.resolve_me`` called with ``parent`` None which returns a value object ``Person("Luke", "Skywalker")``.
|
||||||
|
* This value object is then used as ``parent`` while calling ``Person.resolve_full_name`` to resolve the scalar String value "Luke Skywalker".
|
||||||
|
* The scalar value is serialized and sent back in the query response.
|
||||||
|
|
||||||
|
Each resolver returns the next :ref:`ResolverRootArgument` to be used in executing the following resolver in the chain. If the Field is a Scalar type, that value will be serialized and sent in the **Response**. Otherwise, while resolving Compound types like *ObjectType*, the value be passed forward as the next :ref:`ResolverRootArgument`.
|
||||||
|
|
||||||
|
Naming convention
|
||||||
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
This :ref:`ResolverRootArgument` is sometimes named ``obj``, ``parent``, or ``source`` in other GraphQL documentation. It can also be named after the value object being resolved (ex. ``root`` for a root Query or Mutation, and ``person`` for a Person value object). Sometimes this argument will be named ``self`` in Graphene code, but this can be misleading due to :ref:`ResolverImplicitStaticMethod` while executing queries in Graphene.
|
||||||
|
|
||||||
|
.. _ResolverInfoArgument:
|
||||||
|
|
||||||
|
GraphQL Execution Info (*info*)
|
||||||
|
*******************************
|
||||||
|
|
||||||
|
The second parameter provides two things:
|
||||||
|
|
||||||
|
* reference to meta information about the execution of the current GraphQL Query (fields, schema, parsed query, etc.)
|
||||||
|
* access to per-request ``context`` which can be used to store user authentication, data loader instances or anything else useful for resolving the query.
|
||||||
|
|
||||||
|
Only context will be required for most applications. See :ref:`SchemaExecuteContext` for more information about setting context.
|
||||||
|
|
||||||
|
.. _ResolverGraphQLArguments:
|
||||||
|
|
||||||
|
GraphQL Arguments (*\*\*kwargs*)
|
||||||
|
********************************
|
||||||
|
|
||||||
Any arguments that a field defines gets passed to the resolver function as
|
Any arguments that a field defines gets passed to the resolver function as
|
||||||
kwargs. For example:
|
keyword arguments. For example:
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
|
@ -96,7 +142,7 @@ kwargs. For example:
|
||||||
class Query(graphene.ObjectType):
|
class Query(graphene.ObjectType):
|
||||||
human_by_name = graphene.Field(Human, name=graphene.String(required=True))
|
human_by_name = graphene.Field(Human, name=graphene.String(required=True))
|
||||||
|
|
||||||
def resolve_human_by_name(_, info, name):
|
def resolve_human_by_name(parent, info, name):
|
||||||
return get_human(name=name)
|
return get_human(name=name)
|
||||||
|
|
||||||
You can then execute the following query:
|
You can then execute the following query:
|
||||||
|
@ -110,7 +156,94 @@ You can then execute the following query:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NOTE: if you define an argument for a field that is not required (and in a query
|
Convenience Features of Graphene Resolvers
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. _ResolverImplicitStaticMethod:
|
||||||
|
|
||||||
|
Implicit staticmethod
|
||||||
|
*********************
|
||||||
|
|
||||||
|
One surprising feature of Graphene is that all resolver methods are treated implicitly as staticmethods. This means that, unlike other methods in Python, the first argument of a resolver is *never* ``self`` while it is being executed by Graphene. Instead, the first argument is always :ref:`ResolverRootArgument`. In practice, this is very convenient as, in GraphQL, we are almost always more concerned with the using the parent value object to resolve queries than attributes on the Python object itself.
|
||||||
|
|
||||||
|
The two resolvers in this example are effectively the same.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
class Person(graphene.ObjectType):
|
||||||
|
first_name = graphene.String()
|
||||||
|
last_name = graphene.String()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_first_name(parent, info):
|
||||||
|
'''
|
||||||
|
Decorating a Python method with `staticmethod` ensures that `self` will not be provided as an
|
||||||
|
argument. However, Graphene does not need this decorator for this behavior.
|
||||||
|
'''
|
||||||
|
return parent.first_name
|
||||||
|
|
||||||
|
def resolve_last_name(parent, info):
|
||||||
|
'''
|
||||||
|
Normally the first argument for this method would be `self`, but Graphene executes this as
|
||||||
|
a staticmethod implicitly.
|
||||||
|
'''
|
||||||
|
return parent.last_name
|
||||||
|
|
||||||
|
# ...
|
||||||
|
|
||||||
|
If you prefer your code to be more explict, feel free to use ``@staticmethod`` decorators. Otherwise, your code may be cleaner without them!
|
||||||
|
|
||||||
|
.. _DefaultResolver:
|
||||||
|
|
||||||
|
Default Resolver
|
||||||
|
****************
|
||||||
|
|
||||||
|
If a resolver method is not defined for a **Field** attribute on our *ObjectType*, Graphene supplies a default resolver.
|
||||||
|
|
||||||
|
If the :ref:`ResolverRootArgument` is a dictionary, the resolver will look for a dictionary key matching the field name. Otherwise, the resolver will get the attribute from the parent value object matching the field name.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
PersonValueObject = namedtuple('Person', 'first_name', 'last_name')
|
||||||
|
|
||||||
|
class Person(graphene.ObjectType):
|
||||||
|
first_name = graphene.String()
|
||||||
|
last_name = graphene.String()
|
||||||
|
|
||||||
|
class Query(graphene.Object):
|
||||||
|
me = graphene.Field(Person)
|
||||||
|
my_best_friend = graphene.Field(Person)
|
||||||
|
|
||||||
|
def resolve_me(parent, info):
|
||||||
|
# always pass an object for `me` field
|
||||||
|
return PersonValueObject(first_name='Luke', last_name='Skywalker')
|
||||||
|
|
||||||
|
def resolve_my_best_friend(parent, info):
|
||||||
|
# always pass a dictionary for `my_best_fiend_field`
|
||||||
|
return {"first_name": "R2", "last_name": "D2"}
|
||||||
|
|
||||||
|
schema = graphene.Schema(query=Query)
|
||||||
|
result = schema.execute('''
|
||||||
|
{
|
||||||
|
me { firstName lastName }
|
||||||
|
myBestFriend { firstName lastName }
|
||||||
|
}
|
||||||
|
''')
|
||||||
|
# With default resolvers we can resolve attributes from an object..
|
||||||
|
assert result['data']['me'] == {"firstName": "Luke", "lastName": "Skywalker"}
|
||||||
|
|
||||||
|
# With default resolvers, we can also resolve keys from a dictionary..
|
||||||
|
assert result['data']['my_best_friend'] == {"firstName": "R2", "lastName": "D2"}
|
||||||
|
|
||||||
|
Advanced
|
||||||
|
~~~~~~~~
|
||||||
|
|
||||||
|
GraphQL Argument defaults
|
||||||
|
*************************
|
||||||
|
|
||||||
|
If you define an argument for a field that is not required (and in a query
|
||||||
execution it is not provided as an argument) it will not be passed to the
|
execution it is not provided as an argument) it will not be passed to the
|
||||||
resolver function at all. This is so that the developer can differenciate
|
resolver function at all. This is so that the developer can differenciate
|
||||||
between a ``undefined`` value for an argument and an explicit ``null`` value.
|
between a ``undefined`` value for an argument and an explicit ``null`` value.
|
||||||
|
@ -124,7 +257,7 @@ For example, given this schema:
|
||||||
class Query(graphene.ObjectType):
|
class Query(graphene.ObjectType):
|
||||||
hello = graphene.String(required=True, name=graphene.String())
|
hello = graphene.String(required=True, name=graphene.String())
|
||||||
|
|
||||||
def resolve_hello(_, info, name):
|
def resolve_hello(parent, info, name):
|
||||||
return name if name else 'World'
|
return name if name else 'World'
|
||||||
|
|
||||||
And this query:
|
And this query:
|
||||||
|
@ -141,7 +274,7 @@ An error will be thrown:
|
||||||
|
|
||||||
TypeError: resolve_hello() missing 1 required positional argument: 'name'
|
TypeError: resolve_hello() missing 1 required positional argument: 'name'
|
||||||
|
|
||||||
You can fix this error in 2 ways. Either by combining all keyword arguments
|
You can fix this error in serveral ways. Either by combining all keyword arguments
|
||||||
into a dict:
|
into a dict:
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
@ -149,8 +282,9 @@ into a dict:
|
||||||
class Query(graphene.ObjectType):
|
class Query(graphene.ObjectType):
|
||||||
hello = graphene.String(required=True, name=graphene.String())
|
hello = graphene.String(required=True, name=graphene.String())
|
||||||
|
|
||||||
def resolve_hello(_, info, **args):
|
def resolve_hello(parent, info, **kwargs):
|
||||||
return args.get('name', 'World')
|
name = kwargs.get('name', 'World')
|
||||||
|
return f'Hello, {name}!'
|
||||||
|
|
||||||
Or by setting a default value for the keyword argument:
|
Or by setting a default value for the keyword argument:
|
||||||
|
|
||||||
|
@ -159,12 +293,24 @@ Or by setting a default value for the keyword argument:
|
||||||
class Query(graphene.ObjectType):
|
class Query(graphene.ObjectType):
|
||||||
hello = graphene.String(required=True, name=graphene.String())
|
hello = graphene.String(required=True, name=graphene.String())
|
||||||
|
|
||||||
def resolve_hello(_, info, name='World'):
|
def resolve_hello(parent, info, name='World'):
|
||||||
return name
|
return f'Hello, {name}!'
|
||||||
|
|
||||||
|
One can also set a default value for an Argument in the GraphQL schema itself using Graphene!
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
class Query(graphene.ObjectType):
|
||||||
|
hello = graphene.String(
|
||||||
|
required=True,
|
||||||
|
name=graphene.Argument(graphene.String, default_value='World')
|
||||||
|
)
|
||||||
|
|
||||||
|
def resolve_hello(parent, info, name):
|
||||||
|
return f'Hello, {name}!'
|
||||||
|
|
||||||
Resolvers outside the class
|
Resolvers outside the class
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
***************************
|
||||||
|
|
||||||
A field can use a custom resolver from outside the class:
|
A field can use a custom resolver from outside the class:
|
||||||
|
|
||||||
|
@ -181,11 +327,11 @@ A field can use a custom resolver from outside the class:
|
||||||
full_name = graphene.String(resolver=resolve_full_name)
|
full_name = graphene.String(resolver=resolve_full_name)
|
||||||
|
|
||||||
|
|
||||||
Instances as data containers
|
Instances as value objects
|
||||||
----------------------------
|
**************************
|
||||||
|
|
||||||
Graphene ``ObjectType``\ s can act as containers too. So with the
|
Graphene ``ObjectType``\ s can act as value objects too. So with the
|
||||||
previous example you could do:
|
previous example you could use ``Person`` to capture data for each of the *ObjectType*'s fields.
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
|
@ -194,8 +340,18 @@ previous example you could do:
|
||||||
peter.first_name # prints "Peter"
|
peter.first_name # prints "Peter"
|
||||||
peter.last_name # prints "Griffin"
|
peter.last_name # prints "Griffin"
|
||||||
|
|
||||||
Changing the name
|
Field camelcasing
|
||||||
-----------------
|
*****************
|
||||||
|
|
||||||
|
Graphene automatically camelcases fields on *ObjectType* from ``field_name`` to ``fieldName`` to conform with GraphQL standards. See :ref:`SchemaAutoCamelCase` for more information.
|
||||||
|
|
||||||
|
*ObjectType* Configuration - Meta class
|
||||||
|
---------------------------------------
|
||||||
|
|
||||||
|
Graphene uses a Meta inner class on *ObjectType* to set different options.
|
||||||
|
|
||||||
|
GraphQL type name
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
By default the type name in the GraphQL schema will be the same as the class name
|
By default the type name in the GraphQL schema will be the same as the class name
|
||||||
that defines the ``ObjectType``. This can be changed by setting the ``name``
|
that defines the ``ObjectType``. This can be changed by setting the ``name``
|
||||||
|
@ -207,4 +363,34 @@ property on the ``Meta`` class:
|
||||||
class Meta:
|
class Meta:
|
||||||
name = 'Song'
|
name = 'Song'
|
||||||
|
|
||||||
|
GraphQL Description
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The schema description of an *ObjectType* can be set as a docstring on the Python object or on the Meta inner class.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
class MyGraphQlSong(graphene.ObjectType):
|
||||||
|
''' We can set the schema description for an Object Type here on a docstring '''
|
||||||
|
class Meta:
|
||||||
|
description = 'But if we set the description in Meta, this value is used instead'
|
||||||
|
|
||||||
|
Interfaces & Possible Types
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Setting ``interfaces`` in Meta inner class specifies the GraphQL Interfaces that this Object implements.
|
||||||
|
|
||||||
|
Providing ``possible_types`` helps Graphene resolve ambiguous types such as interfaces or Unions.
|
||||||
|
|
||||||
|
See :ref:`Interfaces` for more information.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
Song = namedtuple('Song', ('title', 'artist'))
|
||||||
|
|
||||||
|
class MyGraphQlSong(graphene.ObjectType):
|
||||||
|
class Meta:
|
||||||
|
interfaces = (graphene.Node, )
|
||||||
|
possible_types = (Song, )
|
||||||
|
|
||||||
.. _Interface: /docs/interfaces/
|
.. _Interface: /docs/interfaces/
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
.. _Scalars:
|
||||||
|
|
||||||
Scalars
|
Scalars
|
||||||
=======
|
=======
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,42 @@
|
||||||
Schema
|
Schema
|
||||||
======
|
======
|
||||||
|
|
||||||
A Schema is created by supplying the root types of each type of operation, query and mutation (optional).
|
A GraphQL **Schema** defines the types and relationship between **Fields** in your API.
|
||||||
A schema definition is then supplied to the validator and executor.
|
|
||||||
|
A Schema is created by supplying the root :ref:`ObjectType` of each operation, query (mandatory), mutation and subscription.
|
||||||
|
|
||||||
|
Schema will collect all type definitions related to the root operations and then supplied to the validator and executor.
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
my_schema = Schema(
|
my_schema = Schema(
|
||||||
query=MyRootQuery,
|
query=MyRootQuery,
|
||||||
mutation=MyRootMutation,
|
mutation=MyRootMutation,
|
||||||
|
subscription=MyRootSubscription
|
||||||
)
|
)
|
||||||
|
|
||||||
|
A Root Query is just a special :ref:`ObjectType` that :ref:`defines the fields <Scalars>` that are the entrypoint for your API. Root Mutation and Root Subscription are similar to Root Query, but for different operation types:
|
||||||
|
|
||||||
|
* Query fetches data
|
||||||
|
* Mutation to changes data and retrieve the changes
|
||||||
|
* Subscription to sends changes to clients in real time
|
||||||
|
|
||||||
|
Review the `GraphQL documentation on Schema`_ for a brief overview of fields, schema and operations.
|
||||||
|
|
||||||
|
.. _GraphQL documentation on Schema: https://graphql.org/learn/schema/
|
||||||
|
|
||||||
|
|
||||||
|
Querying
|
||||||
|
--------
|
||||||
|
|
||||||
|
To query a schema, call the ``execute`` method on it. See :ref:`SchemaExecute` for more details.
|
||||||
|
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
query_string = 'query whoIsMyBestFriend { myBestFriend { lastName } }'
|
||||||
|
my_schema.execute(query_string)
|
||||||
|
|
||||||
Types
|
Types
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
@ -28,17 +54,7 @@ In this case, we need to use the ``types`` argument when creating the Schema.
|
||||||
types=[SomeExtraObjectType, ]
|
types=[SomeExtraObjectType, ]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
.. _SchemaAutoCamelCase:
|
||||||
Querying
|
|
||||||
--------
|
|
||||||
|
|
||||||
To query a schema, call the ``execute`` method on it.
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
my_schema.execute('{ lastName }')
|
|
||||||
|
|
||||||
|
|
||||||
Auto CamelCase field names
|
Auto CamelCase field names
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
Loading…
Reference in New Issue
Block a user