mirror of
https://github.com/graphql-python/graphene.git
synced 2024-11-22 09:36:44 +03:00
Revise documentation (#969)
* 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 * explict imports from graphene * rename doc refs for resolvers * suggestions typos and graphene import
This commit is contained in:
parent
da1359ecca
commit
5cb7d91aaa
|
@ -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
|
||||||
=================
|
=================
|
||||||
|
|
||||||
|
@ -7,12 +9,16 @@ For executing a query a schema, you can directly call the ``execute`` method on
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
schema = graphene.Schema(...)
|
from graphene import Schema
|
||||||
|
|
||||||
|
schema = Schema(...)
|
||||||
result = schema.execute('{ name }')
|
result = schema.execute('{ name }')
|
||||||
|
|
||||||
``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
|
||||||
_______
|
_______
|
||||||
|
|
||||||
|
@ -21,15 +27,17 @@ You can pass context to a query via ``context``.
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
from graphene import ObjectType, String, Schema
|
||||||
name = graphene.String()
|
|
||||||
|
class Query(ObjectType):
|
||||||
|
name = String()
|
||||||
|
|
||||||
def resolve_name(root, info):
|
def resolve_name(root, info):
|
||||||
return info.context.get('name')
|
return info.context.get('name')
|
||||||
|
|
||||||
schema = graphene.Schema(Query)
|
schema = Schema(Query)
|
||||||
result = schema.execute('{ name }', context={'name': 'Syrus'})
|
result = schema.execute('{ name }', context={'name': 'Syrus'})
|
||||||
|
assert result.data['name'] == 'Syrus'
|
||||||
|
|
||||||
|
|
||||||
Variables
|
Variables
|
||||||
|
@ -40,13 +48,15 @@ You can pass variables to a query via ``variables``.
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
from graphene import ObjectType, Field, ID, Schema
|
||||||
user = graphene.Field(User, id=graphene.ID(required=True))
|
|
||||||
|
class Query(ObjectType):
|
||||||
|
user = Field(User, id=ID(required=True))
|
||||||
|
|
||||||
def resolve_user(root, info, id):
|
def resolve_user(root, info, id):
|
||||||
return get_user_by_id(id)
|
return get_user_by_id(id)
|
||||||
|
|
||||||
schema = graphene.Schema(Query)
|
schema = Schema(Query)
|
||||||
result = schema.execute(
|
result = schema.execute(
|
||||||
'''
|
'''
|
||||||
query getUser($id: ID) {
|
query getUser($id: ID) {
|
||||||
|
@ -59,3 +69,71 @@ You can pass variables to a query via ``variables``.
|
||||||
''',
|
''',
|
||||||
variables={'id': 12},
|
variables={'id': 12},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Root Value
|
||||||
|
__________
|
||||||
|
|
||||||
|
Value used for :ref:`ResolverParamParent` in root queries and mutations can be overridden using ``root`` parameter.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
from graphene import ObjectType, Field, Schema
|
||||||
|
|
||||||
|
class Query(ObjectType):
|
||||||
|
me = Field(User)
|
||||||
|
|
||||||
|
def resolve_user(root, info):
|
||||||
|
return {'id': root.id, 'firstName': root.name}
|
||||||
|
|
||||||
|
schema = Schema(Query)
|
||||||
|
user_root = User(id=12, name='bob'}
|
||||||
|
result = schema.execute(
|
||||||
|
'''
|
||||||
|
query getUser {
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''',
|
||||||
|
root=user_root
|
||||||
|
)
|
||||||
|
assert result.data['user']['id'] == user_root.id
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
from graphene import ObjectType, Field, Schema
|
||||||
|
|
||||||
|
class Query(ObjectType):
|
||||||
|
me = Field(User)
|
||||||
|
|
||||||
|
def resolve_user(root, info):
|
||||||
|
return get_user_by_id(12)
|
||||||
|
|
||||||
|
schema = Schema(Query)
|
||||||
|
query_string = '''
|
||||||
|
query getUserWithFirstName {
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
query getUserWithFullName {
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
fullName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
result = schema.execute(
|
||||||
|
query_string,
|
||||||
|
operation_name='getUserWithFullName'
|
||||||
|
)
|
||||||
|
assert result.data['user']['fullName']
|
||||||
|
|
|
@ -13,10 +13,13 @@ Contents:
|
||||||
testing/index
|
testing/index
|
||||||
api/index
|
api/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 ORMs. Graphene produces schemas tha are fully compliant with the GraphQL spec and provides tools and patterns for building a Relay-Compliant API as well.
|
||||||
|
|
||||||
|
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
|
from graphene import ObjectType, String, Schema
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
class Query(ObjectType):
|
||||||
hello = graphene.String(argument=graphene.String(default_value="stranger"))
|
# this defines a Field `hello` in our Schema with a single Argument `name`
|
||||||
|
hello = String(name=String(default_value="stranger"))
|
||||||
|
goodbye = 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}!'
|
||||||
|
|
||||||
schema = graphene.Schema(query=Query)
|
def resolve_goodbye(root, info):
|
||||||
|
return 'See ya!'
|
||||||
|
|
||||||
|
schema = Schema(query=Query)
|
||||||
|
|
||||||
|
|
||||||
|
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
|
Querying
|
||||||
--------
|
~~~~~~~~
|
||||||
|
|
||||||
Then we can start querying our schema:
|
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,17 @@ This example model defines a Person, with a first and a last name:
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
import graphene
|
from graphene import ObjectType, String
|
||||||
|
|
||||||
class Person(graphene.ObjectType):
|
class Person(ObjectType):
|
||||||
first_name = graphene.String()
|
first_name = String()
|
||||||
last_name = graphene.String()
|
last_name = String()
|
||||||
full_name = graphene.String()
|
full_name = 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 field **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,61 +40,111 @@ 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:`ResolverParamParent` for the value object use to resolve most fields
|
||||||
passed to the ``ObjectType``.
|
* :ref:`ResolverParamInfo` for query and schema meta information and per-request context
|
||||||
|
* :ref:`ResolverParamGraphQLArguments` as defined on the **Field**.
|
||||||
|
|
||||||
|
.. _ResolverArguments:
|
||||||
|
|
||||||
|
Resolver Parameters
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. _ResolverParamParent:
|
||||||
|
|
||||||
|
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
|
from graphene import ObjectType, String, Field
|
||||||
|
|
||||||
class Person(graphene.ObjectType):
|
class Person(ObjectType):
|
||||||
first_name = graphene.String()
|
full_name = String()
|
||||||
last_name = graphene.String()
|
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
def resolve_full_name(parent, info):
|
||||||
me = graphene.Field(Person)
|
return f"{parent.first_name} {parent.last_name}"
|
||||||
best_friend = graphene.Field(Person)
|
|
||||||
|
|
||||||
def resolve_me(_, info):
|
class Query(ObjectType):
|
||||||
|
me = Field(Person)
|
||||||
|
|
||||||
|
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:`ResolverParamParent` 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:`ResolverParamParent`.
|
||||||
|
|
||||||
|
Naming convention
|
||||||
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
This :ref:`ResolverParamParent` 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.
|
||||||
|
|
||||||
|
.. _ResolverParamInfo:
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
.. _ResolverParamGraphQLArguments:
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
import graphene
|
from graphene import ObjectType, Field, String
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
class Query(ObjectType):
|
||||||
human_by_name = graphene.Field(Human, name=graphene.String(required=True))
|
human_by_name = Field(Human, name=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 +158,98 @@ 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:`ResolverParamParent`. 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
|
||||||
|
|
||||||
|
from graphene import ObjectType, String
|
||||||
|
|
||||||
|
class Person(ObjectType):
|
||||||
|
first_name = String()
|
||||||
|
last_name = 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:`ResolverParamParent` 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
|
||||||
|
|
||||||
|
from graphene import ObjectType, String, Field, Schema
|
||||||
|
|
||||||
|
PersonValueObject = namedtuple('Person', 'first_name', 'last_name')
|
||||||
|
|
||||||
|
class Person(ObjectType):
|
||||||
|
first_name = String()
|
||||||
|
last_name = String()
|
||||||
|
|
||||||
|
class Query(ObjectType):
|
||||||
|
me = Field(Person)
|
||||||
|
my_best_friend = 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 = 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.
|
||||||
|
@ -119,12 +258,12 @@ For example, given this schema:
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
import graphene
|
from graphene import ObjectType, String
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
class Query(ObjectType):
|
||||||
hello = graphene.String(required=True, name=graphene.String())
|
hello = String(required=True, name=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,61 +280,90 @@ 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
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
from graphene import ObjectType, String
|
||||||
hello = graphene.String(required=True, name=graphene.String())
|
|
||||||
|
|
||||||
def resolve_hello(_, info, **args):
|
class Query(ObjectType):
|
||||||
return args.get('name', 'World')
|
hello = String(required=True, name=String())
|
||||||
|
|
||||||
|
def resolve_hello(parent, info, **kwargs):
|
||||||
|
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:
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
from graphene import ObjectType, String
|
||||||
hello = graphene.String(required=True, name=graphene.String())
|
|
||||||
|
|
||||||
def resolve_hello(_, info, name='World'):
|
class Query(ObjectType):
|
||||||
return name
|
hello = String(required=True, name=String())
|
||||||
|
|
||||||
|
def resolve_hello(parent, info, name='World'):
|
||||||
|
return f'Hello, {name}!'
|
||||||
|
|
||||||
|
One can also set a default value for an Argument in the GraphQL schema itself using Graphene!
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
from graphene import ObjectType, String
|
||||||
|
|
||||||
|
class Query(ObjectType):
|
||||||
|
hello = String(
|
||||||
|
required=True,
|
||||||
|
name=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:
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
import graphene
|
from graphene import ObjectType, String
|
||||||
|
|
||||||
def resolve_full_name(person, info):
|
def resolve_full_name(person, info):
|
||||||
return '{} {}'.format(person.first_name, person.last_name)
|
return '{} {}'.format(person.first_name, person.last_name)
|
||||||
|
|
||||||
class Person(graphene.ObjectType):
|
class Person(ObjectType):
|
||||||
first_name = graphene.String()
|
first_name = String()
|
||||||
last_name = graphene.String()
|
last_name = String()
|
||||||
full_name = graphene.String(resolver=resolve_full_name)
|
full_name = 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
|
||||||
|
|
||||||
peter = Person(first_name='Peter', last_name='Griffin')
|
peter = Person(first_name='Peter', last_name='Griffin')
|
||||||
|
|
||||||
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``
|
||||||
|
@ -203,8 +371,44 @@ property on the ``Meta`` class:
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
class MyGraphQlSong(graphene.ObjectType):
|
from graphene import ObjectType
|
||||||
|
|
||||||
|
class MyGraphQlSong(ObjectType):
|
||||||
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
|
||||||
|
|
||||||
|
from graphene import ObjectType
|
||||||
|
|
||||||
|
class MyGraphQlSong(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
|
||||||
|
|
||||||
|
from graphene import ObjectType, Node
|
||||||
|
|
||||||
|
Song = namedtuple('Song', ('title', 'artist'))
|
||||||
|
|
||||||
|
class MyGraphQlSong(ObjectType):
|
||||||
|
class Meta:
|
||||||
|
interfaces = (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