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:
David Anderson 2019-06-09 19:49:56 -04:00 committed by Jonathan Kim
parent da1359ecca
commit 5cb7d91aaa
13 changed files with 573 additions and 190 deletions

View File

@ -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.
## 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!
We have done our best to provide backwards compatibility with deprecated APIs.
## 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:
```python
def resolve_xxx(self, args, info):
def resolve_xxx(root, args, info):
# ...
```
With 1.0:
```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.
Before:
@ -42,6 +41,7 @@ We have done our best to provide backwards compatibility with deprecated APIs.
```
With 1.0:
```python
class MyBaseQuery(graphene.AbstractType):
my_field = String()
@ -50,9 +50,9 @@ We have done our best to provide backwards compatibility with deprecated APIs.
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:
@ -70,7 +70,6 @@ We have done our best to provide backwards compatibility with deprecated APIs.
users = graphene.List(lambda: User)
```
## Schema
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.
You can still use them, but by calling explicitly in the `execute` method in `graphql`.
```python
# Old way
schema = graphene.Schema(name='My Schema')
@ -94,7 +92,6 @@ schema = graphene.Schema(
)
```
## 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()
def mutate(self, args, context, info):
def mutate(root, args, context, info):
reversed = args.get('input')[::-1]
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
explicity.
## 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`.
* As the package is now independent, you now have to import from `graphene_django`.
* **DjangoNode no longer exists**, please use `relay.Node` instead:
- As the package is now independent, you now have to import from `graphene_django`.
- **DjangoNode no longer exists**, please use `relay.Node` instead:
```python
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`.
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`.
* **SQLAlchemyNode no longer exists**, please use `relay.Node` instead:
- As the package is now independent, you have to import now from `graphene_sqlalchemy`.
- **SQLAlchemyNode no longer exists**, please use `relay.Node` instead:
```python
from graphene.relay import Node

View File

@ -7,18 +7,20 @@ It also improves the field resolvers, [simplifying the code](#simpler-resolvers)
developer has to write to use them.
**Deprecations:**
* [`AbstractType`](#abstracttype-deprecated)
* [`resolve_only_args`](#resolve_only_args)
* [`Mutation.Input`](#mutationinput)
- [`AbstractType`](#abstracttype-deprecated)
- [`resolve_only_args`](#resolve_only_args)
- [`Mutation.Input`](#mutationinput)
**Breaking changes:**
* [`Simpler Resolvers`](#simpler-resolvers)
* [`Node Connections`](#node-connections)
- [`Simpler Resolvers`](#simpler-resolvers)
- [`Node Connections`](#node-connections)
**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
> 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).
@ -49,7 +51,7 @@ class Pet(CommonFields, Interface):
pass
```
### resolve\_only\_args
### resolve_only_args
`resolve_only_args` is now deprecated as the resolver API has been simplified.
@ -60,8 +62,8 @@ class User(ObjectType):
name = String()
@resolve_only_args
def resolve_name(self):
return self.name
def resolve_name(root):
return root.name
```
With 2.0:
@ -70,8 +72,8 @@ With 2.0:
class User(ObjectType):
name = String()
def resolve_name(self, info):
return self.name
def resolve_name(root, info):
return root.name
```
### Mutation.Input
@ -94,7 +96,6 @@ class User(Mutation):
name = String()
```
## Breaking Changes
### Simpler resolvers
@ -108,7 +109,7 @@ Before:
```python
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')
return ...
```
@ -118,7 +119,7 @@ With 2.0:
```python
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 ...
```
@ -126,7 +127,7 @@ def resolve_my_field(self, info, my_arg):
You may need something like this:
```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`:
@ -134,7 +135,7 @@ And, if you need the context in the resolver, you can use `info.context`:
```python
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
return ...
```
@ -188,6 +189,7 @@ class MyObject(ObjectType):
```
To:
```python
class MyObject(ObjectType):
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.
Before:
```python
class RootQuery(object):
...
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)
return node
```
Now:
```python
class RootQuery(object):
...
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)
return node
```
## 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:
@ -245,7 +249,7 @@ With 2.0:
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)
...
def mutate(self, info, first_name, last_name):
def mutate(root, info, first_name, last_name):
...
```
## ClientIDMutation.mutate_and_get_payload
Now only receives (`root`, `info`, `**input`)
### Middlewares
If you are using Middelwares, you need to some adjustments:
@ -294,10 +295,9 @@ class MyGrapheneMiddleware(object):
## Middleware code
info.context = context
       return next_mw(root, info, **args)```
       return next_mw(root, info, **args)
```
## New Features
### InputObjectType
@ -321,7 +321,7 @@ class Query(ObjectType):
user = graphene.Field(User, input=UserInput())
@resolve_only_args
def resolve_user(self, input):
def resolve_user(root, input):
user_id = input.get('id')
if is_valid_input(user_id):
return get_user(user_id)
@ -334,18 +334,17 @@ class UserInput(InputObjectType):
id = ID(required=True)
@property
def is_valid(self):
return self.id.startswith('userid_')
def is_valid(root):
return root.id.startswith('userid_')
class Query(ObjectType):
user = graphene.Field(User, input=UserInput())
def resolve_user(self, info, input):
def resolve_user(root, info, input):
if input.is_valid:
return get_user(input.id)
```
### Meta as Class arguments
Now you can use the meta options as class arguments (**ONLY PYTHON 3**).
@ -366,7 +365,6 @@ class Dog(ObjectType, interfaces=[Pet]):
name = String()
```
### Abstract types
Now you can create abstact types super easily, without the need of subclassing the meta.
@ -378,10 +376,10 @@ class Base(ObjectType):
id = ID()
def resolve_id(self, info):
def resolve_id(root, info):
return "{type}_{id}".format(
type=self.__class__.__name__,
id=self.id
type=root.__class__.__name__,
id=root.id
)
```

View File

@ -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)
friends = graphene.List(lambda: User)
def resolve_best_friend(self, info):
return user_loader.load(self.best_friend_id)
def resolve_best_friend(root, info):
return user_loader.load(root.best_friend_id)
def resolve_friends(self, info):
return user_loader.load_many(self.friend_ids)
def resolve_friends(root, info):
return user_loader.load_many(root.friend_ids)

View File

@ -1,3 +1,5 @@
.. _SchemaExecute:
Executing a query
=================
@ -7,12 +9,16 @@ For executing a query a schema, you can directly call the ``execute`` method on
.. code:: python
schema = graphene.Schema(...)
from graphene import Schema
schema = Schema(...)
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.
.. _SchemaExecuteContext:
Context
_______
@ -21,15 +27,17 @@ You can pass context to a query via ``context``.
.. code:: python
class Query(graphene.ObjectType):
name = graphene.String()
from graphene import ObjectType, String, Schema
class Query(ObjectType):
name = String()
def resolve_name(root, info):
return info.context.get('name')
schema = graphene.Schema(Query)
schema = Schema(Query)
result = schema.execute('{ name }', context={'name': 'Syrus'})
assert result.data['name'] == 'Syrus'
Variables
@ -40,13 +48,15 @@ You can pass variables to a query via ``variables``.
.. code:: python
class Query(graphene.ObjectType):
user = graphene.Field(User, id=graphene.ID(required=True))
from graphene import ObjectType, Field, ID, Schema
class Query(ObjectType):
user = Field(User, id=ID(required=True))
def resolve_user(root, info, id):
return get_user_by_id(id)
schema = graphene.Schema(Query)
schema = Schema(Query)
result = schema.execute(
'''
query getUser($id: ID) {
@ -59,3 +69,71 @@ You can pass variables to a query via ``variables``.
''',
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']

View File

@ -13,10 +13,13 @@ Contents:
testing/index
api/index
.. _Integrations:
Integrations
------------
* `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-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>`_)

View File

@ -1,62 +1,143 @@
Getting started
===============
Introduction
------------
What is GraphQL?
----------------
~~~~~~~~~~~~~~~~
For an introduction to GraphQL and an overview of its concepts, please refer
to `the official introduction <http://graphql.org/learn/>`_.
GraphQL is a query language for your API.
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
----------------------
Lets 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!"
}
}
Lets build a basic GraphQL schema from scratch.
Requirements
------------
~~~~~~~~~~~~
- Python (2.7, 3.4, 3.5, 3.6, pypy)
- Graphene (2.0)
Project setup
-------------
~~~~~~~~~~~~~
.. code:: bash
pip install "graphene>=2.0"
Creating a basic Schema
-----------------------
~~~~~~~~~~~~~~~~~~~~~~~
A GraphQL schema describes your data model, and provides a GraphQL
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}"``.
In Graphene, we can define a simple schema using the following code:
.. code:: python
import graphene
from graphene import ObjectType, String, Schema
class Query(graphene.ObjectType):
hello = graphene.String(argument=graphene.String(default_value="stranger"))
class Query(ObjectType):
# 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):
return 'Hello ' + argument
# our Resolver method takes the GraphQL context (root, info) as well as
# 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
--------
~~~~~~~~
Then we can start querying our schema:
Then we can start querying our **Schema** by passing a GraphQL query string to ``execute``:
.. code:: python
result = schema.execute('{ hello }')
print(result.data['hello']) # "Hello stranger"
# we can query for our field (with the default argument)
query_string = '{ hello }'
result = schema.execute(query_string)
print(result.data['hello'])
# "Hello stranger"
# or passing the argument in the query
result = schema.execute('{ hello (argument: "graph") }')
print(result.data['hello']) # "Hello graph"
query_string_with_argument = '{ hello (name: "GraphQL") }'
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.

View File

@ -41,5 +41,5 @@ that implements ``Node`` will have a default Connection.
name = graphene.String()
ships = relay.ConnectionField(ShipConnection)
def resolve_ships(self, info):
def resolve_ships(root, info):
return []

View File

@ -1,3 +1,5 @@
.. _TypesReference:
===============
Types Reference
===============
@ -5,12 +7,12 @@ Types Reference
.. toctree::
:maxdepth: 1
enums
schema
scalars
list-and-nonnull
objecttypes
enums
interfaces
unions
schema
mutations
abstracttypes

View File

@ -1,3 +1,5 @@
.. _Interfaces:
Interfaces
==========
@ -82,7 +84,7 @@ For example, you can define a field ``hero`` that resolves to any
episode=graphene.Int(required=True)
)
def resolve_hero(_, info, episode):
def resolve_hero(root, info, episode):
# Luke is the hero of Episode V
if episode == 5:
return get_human(name='Luke Skywalker')

View File

@ -19,7 +19,7 @@ This example defines a Mutation:
ok = graphene.Boolean()
person = graphene.Field(lambda: Person)
def mutate(self, info, name):
def mutate(root, info, name):
person = Person(name=name)
ok = True
return CreatePerson(person=person, ok=ok)
@ -32,7 +32,8 @@ resolved.
only argument for the mutation.
**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:
@ -157,7 +158,7 @@ To return an existing ObjectType instead of a mutation-specific type, set the **
Output = Person
def mutate(self, info, name):
def mutate(root, info, name):
return Person(name=name)
Then, if we query (``schema.execute(query_str)``) the following:

View File

@ -1,15 +1,15 @@
ObjectTypes
===========
.. _ObjectType:
An ObjectType is the single, definitive source of information about your
data. It contains the essential fields and behaviors of the data youre
querying.
ObjectType
==========
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:
- Each ObjectType is a Python class that inherits from
``graphene.ObjectType``.
- Each ObjectType is a Python class that inherits from ``graphene.ObjectType``.
- Each attribute of the ObjectType represents a ``Field``.
- Each ``Field`` has a :ref:`resolver method<Resolvers>` to fetch data (or :ref:`DefaultResolver`).
Quick example
-------------
@ -18,19 +18,17 @@ This example model defines a Person, with a first and a last name:
.. code:: python
import graphene
from graphene import ObjectType, String
class Person(graphene.ObjectType):
first_name = graphene.String()
last_name = graphene.String()
full_name = graphene.String()
class Person(ObjectType):
first_name = String()
last_name = String()
full_name = String()
def resolve_full_name(root, info):
return '{} {}'.format(root.first_name, root.last_name)
def resolve_full_name(parent, info):
return f"{parent.first_name} {parent.last_name}"
**first\_name** and **last\_name** are fields of the ObjectType. Each
field is specified as a class attribute, and each attribute maps to a
Field.
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.
The above ``Person`` ObjectType has the following schema representation:
@ -42,61 +40,111 @@ The above ``Person`` ObjectType has the following schema representation:
fullName: String
}
.. _Resolvers:
Resolvers
---------
A resolver is a method that resolves certain fields within an
``ObjectType``. If not specified otherwise, the resolver of a
field is the ``resolve_{field_name}`` method on the ``ObjectType``.
A **Resolver** is a method that helps us answer **Queries** by fetching data for a **Field** in our **Schema**.
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,
so the first argument to the resolver method ``self`` (or ``root``) need
not be an actual instance of the ``ObjectType``.
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``.
If an explicit resolver is not defined on the ``ObjectType`` then Graphene will
attempt to use a property with the same name on the object or dict that is
passed to the ``ObjectType``.
Each resolver method takes the parameters:
* :ref:`ResolverParamParent` for the value object use to resolve most fields
* :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
import graphene
from graphene import ObjectType, String, Field
class Person(graphene.ObjectType):
first_name = graphene.String()
last_name = graphene.String()
class Person(ObjectType):
full_name = String()
class Query(graphene.ObjectType):
me = graphene.Field(Person)
best_friend = graphene.Field(Person)
def resolve_full_name(parent, info):
return f"{parent.first_name} {parent.last_name}"
def resolve_me(_, info):
class Query(ObjectType):
me = Field(Person)
def resolve_me(parent, info):
# returns an object that represents a Person
return get_human(name='Luke Skywalker')
return get_human(name="Luke Skywalker")
def resolve_best_friend(_, info):
return {
"first_name": "R2",
"last_name": "D2",
}
When we execute a query against that schema.
.. 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
kwargs. For example:
keyword arguments. For example:
.. code:: python
import graphene
from graphene import ObjectType, Field, String
class Query(graphene.ObjectType):
human_by_name = graphene.Field(Human, name=graphene.String(required=True))
class Query(ObjectType):
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)
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
resolver function at all. This is so that the developer can differenciate
between a ``undefined`` value for an argument and an explicit ``null`` value.
@ -119,12 +258,12 @@ For example, given this schema:
.. code:: python
import graphene
from graphene import ObjectType, String
class Query(graphene.ObjectType):
hello = graphene.String(required=True, name=graphene.String())
class Query(ObjectType):
hello = String(required=True, name=String())
def resolve_hello(_, info, name):
def resolve_hello(parent, info, name):
return name if name else 'World'
And this query:
@ -141,51 +280,70 @@ An error will be thrown:
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:
.. code:: python
class Query(graphene.ObjectType):
hello = graphene.String(required=True, name=graphene.String())
from graphene import ObjectType, String
def resolve_hello(_, info, **args):
return args.get('name', 'World')
class Query(ObjectType):
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:
.. code:: python
class Query(graphene.ObjectType):
hello = graphene.String(required=True, name=graphene.String())
from graphene import ObjectType, String
def resolve_hello(_, info, name='World'):
return name
class Query(ObjectType):
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
~~~~~~~~~~~~~~~~~~~~~~~~~~~
***************************
A field can use a custom resolver from outside the class:
.. code:: python
import graphene
from graphene import ObjectType, String
def resolve_full_name(person, info):
return '{} {}'.format(person.first_name, person.last_name)
class Person(graphene.ObjectType):
first_name = graphene.String()
last_name = graphene.String()
full_name = graphene.String(resolver=resolve_full_name)
class Person(ObjectType):
first_name = String()
last_name = String()
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
previous example you could do:
Graphene ``ObjectType``\ s can act as value objects too. So with the
previous example you could use ``Person`` to capture data for each of the *ObjectType*'s fields.
.. code:: python
@ -194,8 +352,18 @@ previous example you could do:
peter.first_name # prints "Peter"
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
that defines the ``ObjectType``. This can be changed by setting the ``name``
@ -203,8 +371,44 @@ property on the ``Meta`` class:
.. code:: python
class MyGraphQlSong(graphene.ObjectType):
from graphene import ObjectType
class MyGraphQlSong(ObjectType):
class Meta:
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/

View File

@ -1,3 +1,5 @@
.. _Scalars:
Scalars
=======

View File

@ -1,16 +1,42 @@
Schema
======
A Schema is created by supplying the root types of each type of operation, query and mutation (optional).
A schema definition is then supplied to the validator and executor.
A GraphQL **Schema** defines the types and relationship between **Fields** in your API.
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
my_schema = Schema(
query=MyRootQuery,
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
-----
@ -28,17 +54,7 @@ In this case, we need to use the ``types`` argument when creating the Schema.
types=[SomeExtraObjectType, ]
)
Querying
--------
To query a schema, call the ``execute`` method on it.
.. code:: python
my_schema.execute('{ lastName }')
.. _SchemaAutoCamelCase:
Auto CamelCase field names
--------------------------