diff --git a/UPGRADE-v1.0.md b/UPGRADE-v1.0.md
index a1ab5bd4..8ace8756 100644
--- a/UPGRADE-v1.0.md
+++ b/UPGRADE-v1.0.md
@@ -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
diff --git a/UPGRADE-v2.0.md b/UPGRADE-v2.0.md
index d9d48005..fed15923 100644
--- a/UPGRADE-v2.0.md
+++ b/UPGRADE-v2.0.md
@@ -7,20 +7,22 @@ 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
+> 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).
## Deprecations
@@ -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
)
```
diff --git a/docs/execution/dataloader.rst b/docs/execution/dataloader.rst
index 3cd36fcc..3f693075 100644
--- a/docs/execution/dataloader.rst
+++ b/docs/execution/dataloader.rst
@@ -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)
diff --git a/docs/execution/execute.rst b/docs/execution/execute.rst
index f4a1e759..20d4b07a 100644
--- a/docs/execution/execute.rst
+++ b/docs/execution/execute.rst
@@ -1,3 +1,5 @@
+.. _SchemaExecute:
+
Executing a query
=================
@@ -13,6 +15,8 @@ For executing a query a schema, you can directly call the ``execute`` method on
``result`` represents the result of execution. ``result.data`` is the result of executing the query, ``result.errors`` is ``None`` if no errors occurred, and is a non-empty list if an error occurred.
+.. _SchemaExecuteContext:
+
Context
_______
@@ -59,3 +63,66 @@ You can pass variables to a query via ``variables``.
''',
variables={'id': 12},
)
+
+Root Value
+__________
+
+Value used for :ref:`ResolverRootArgument` in root queries and mutations can be overridden using ``root`` parameter.
+
+.. code:: python
+
+ class Query(graphene.ObjectType):
+ me = graphene.Field(User)
+
+ def resolve_user(root, info):
+ return get_user_by_id(root.id)
+
+ schema = graphene.Schema(Query)
+ user_root = User(id=12, name='bob'}
+ result = schema.execute(
+ '''
+ query getUser {
+ user {
+ id
+ firstName
+ lastName
+ }
+ }
+ ''',
+ root=user_root
+ )
+
+Operation Name
+______________
+
+If there are multiple operations defined in a query string, ``operation_name`` should be used to indicate which should be executed.
+
+.. code:: python
+
+ class Query(graphene.ObjectType):
+ me = graphene.Field(User)
+
+ def resolve_user(root, info):
+ return get_user_by_id(12)
+
+ schema = graphene.Schema(Query)
+ query_string = '''
+ query getUserWithFirstName {
+ user {
+ id
+ firstName
+ lastName
+ }
+ }
+ query getUserWithFullName {
+ user {
+ id
+ firstName
+ lastName
+ }
+ }
+ '''
+ result = schema.execute(
+ query_string,
+ operation_name='getUserWithFullName'
+ )
diff --git a/docs/index.rst b/docs/index.rst
index aff3960f..4d624777 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -12,10 +12,13 @@ Contents:
relay/index
testing/index
+.. _Integrations:
+
Integrations
------
+------------
* `Graphene-Django `_ (`source `_)
+* Flask-Graphql (`source `_)
* `Graphene-SQLAlchemy `_ (`source `_)
* `Graphene-GAE `_ (`source `_)
* `Graphene-Mongo `_ (`source `_)
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index fc333fae..c23f09f9 100644
--- a/docs/quickstart.rst
+++ b/docs/quickstart.rst
@@ -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 `_.
+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 ORM and easy Relay Compliance.
+
+An example in Graphene
+----------------------
+
+Let’s build a basic GraphQL schema to say "hello" and "goodbye" in Graphene.
+
+When we send a **Query** requesting only one **Field**, ``hello``, and specify a value for the ``name`` **Argument**...
+
+.. code::
+
+ {
+ hello(name: "friend")
+ }
+
+...we would expect the following Response containing only the data requested (the ``goodbye`` field is not resolved).
+
+.. code::
+
+ {
+ "data": {
+ "hello": "Hello friend!"
+ }
+ }
-Let’s build a basic GraphQL schema from scratch.
Requirements
-------------
+~~~~~~~~~~~~
- 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
class Query(graphene.ObjectType):
- hello = graphene.String(argument=graphene.String(default_value="stranger"))
+ # this defines a Field `hello` in our Schema with a single Argument `name`
+ hello = graphene.String(name=graphene.String(default_value="stranger"))
+ goodbye = graphene.String()
- def resolve_hello(self, info, argument):
- 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}!'
+
+ def resolve_goodbye(root, info):
+ return 'See ya!'
schema = graphene.Schema(query=Query)
-Querying
---------
-Then we can start querying our schema:
+A GraphQL **Schema** describes each **Field** in the data model provided by the server using scalar types like *String*, *Int* and *Enum* and compound types like *List* and *Object*. For more details refer to the Graphene :ref:`TypesReference`.
+
+Our schema can also define any number of **Arguments** for our **Fields**. This is a powerful way for a **Query** to describe the exact data requirements for each **Field**.
+
+For each **Field** in our **Schema**, we write a **Resolver** method to fetch data requested by a client's **Query** using the current context and **Arguments**. For more details, refer to this section on :ref:`Resolvers`.
+
+Schema Definition Language (SDL)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In the `GraphQL Schema Definition Language`_, we could describe the feilds defined by our example code as show below.
+
+.. _GraphQL Schema Definition Language: https://graphql.org/learn/schema/
+
+.. code::
+
+ type Query {
+ hello(name: String = "stranger"): String
+ goodbye: String
+ }
+
+Further examples in this documentation will use SDL to describe schema created by ObjectTypes and other fields.
+
+Querying
+~~~~~~~~
+
+Then we can start querying our **Schema** by passing a GraphQL query string to ``execute``:
.. code:: python
- 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.
diff --git a/docs/relay/connection.rst b/docs/relay/connection.rst
index c2379cbc..07d81ada 100644
--- a/docs/relay/connection.rst
+++ b/docs/relay/connection.rst
@@ -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 []
diff --git a/docs/types/index.rst b/docs/types/index.rst
index acbdb8e8..d44894af 100644
--- a/docs/types/index.rst
+++ b/docs/types/index.rst
@@ -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
diff --git a/docs/types/interfaces.rst b/docs/types/interfaces.rst
index f2073d6a..eb7172e9 100644
--- a/docs/types/interfaces.rst
+++ b/docs/types/interfaces.rst
@@ -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')
diff --git a/docs/types/mutations.rst b/docs/types/mutations.rst
index 8532595e..d63ada3e 100644
--- a/docs/types/mutations.rst
+++ b/docs/types/mutations.rst
@@ -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:
diff --git a/docs/types/objecttypes.rst b/docs/types/objecttypes.rst
index 1579258d..30d29307 100644
--- a/docs/types/objecttypes.rst
+++ b/docs/types/objecttypes.rst
@@ -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 you’re
-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` to fetch data (or :ref:`DefaultResolver`).
Quick example
-------------
@@ -18,19 +18,15 @@ This example model defines a Person, with a first and a last name:
.. code:: python
- import graphene
-
class Person(graphene.ObjectType):
first_name = graphene.String()
last_name = graphene.String()
full_name = graphene.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 feild **first\_name**, **last\_name**, and **full\_name**. Each field is specified as a class attribute, and each attribute maps to a Field. Data is fetched by our ``resolve_full_name`` :ref:`resolver method` for ``full_name`` field and the :ref:`DefaultResolver` for other fields.
The above ``Person`` ObjectType has the following schema representation:
@@ -42,52 +38,102 @@ 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:`ResolverRootArgument` for the value object use to resolve most fields
+* :ref:`ResolverInfoArgument` for query and schema meta information and per-request context
+* :ref:`ResolverGraphQLArguments` as defined on the **Field**.
+
+.. _ResolverArguments:
+
+Resolver Arguments
+~~~~~~~~~~~~~~~~~~
+
+.. _ResolverRootArgument:
+
+Parent Value Object (*parent*)
+******************************
+
+This parameter is typically used to derive the values for most fields on an *ObjectType*.
+
+The first parameter of a resolver method (*parent*) is the value object returned from the resolver of the parent field. If there is no parent field, such as a root Query field, then the value for *parent* is set to the ``root_value`` configured while executing the query (default ``None``). See :ref:`SchemaExecute` for more details on executing queries.
+
+Resolver example
+^^^^^^^^^^^^^^^^
+
+If we have a schema with Person type and one field on the root query.
.. code:: python
import graphene
class Person(graphene.ObjectType):
- first_name = graphene.String()
- last_name = graphene.String()
+ full_name = graphene.String()
+
+ def resolve_full_name(parent, info):
+ return f'{parent.first_name} {parent.last_name}'
class Query(graphene.ObjectType):
me = graphene.Field(Person)
- best_friend = graphene.Field(Person)
- def resolve_me(_, info):
+ def resolve_me(parent, info):
# returns an object that represents a Person
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:`ResolverRootArgument` to be used in executing the following resolver in the chain. If the Field is a Scalar type, that value will be serialized and sent in the **Response**. Otherwise, while resolving Compound types like *ObjectType*, the value be passed forward as the next :ref:`ResolverRootArgument`.
+
+Naming convention
+^^^^^^^^^^^^^^^^^
+
+This :ref:`ResolverRootArgument` is sometimes named ``obj``, ``parent``, or ``source`` in other GraphQL documentation. It can also be named after the value object being resolved (ex. ``root`` for a root Query or Mutation, and ``person`` for a Person value object). Sometimes this argument will be named ``self`` in Graphene code, but this can be misleading due to :ref:`ResolverImplicitStaticMethod` while executing queries in Graphene.
+
+.. _ResolverInfoArgument:
+
+GraphQL Execution Info (*info*)
+*******************************
+
+The second parameter provides two things:
+
+* reference to meta information about the execution of the current GraphQL Query (fields, schema, parsed query, etc.)
+* access to per-request ``context`` which can be used to store user authentication, data loader instances or anything else useful for resolving the query.
+
+Only context will be required for most applications. See :ref:`SchemaExecuteContext` for more information about setting context.
+
+.. _ResolverGraphQLArguments:
+
+GraphQL Arguments (*\*\*kwargs*)
+********************************
Any arguments that a field defines gets passed to the resolver function as
-kwargs. For example:
+keyword arguments. For example:
.. code:: python
@@ -96,7 +142,7 @@ kwargs. For example:
class Query(graphene.ObjectType):
human_by_name = graphene.Field(Human, name=graphene.String(required=True))
- def resolve_human_by_name(_, info, name):
+ def resolve_human_by_name(parent, info, name):
return get_human(name=name)
You can then execute the following query:
@@ -110,7 +156,94 @@ You can then execute the following query:
}
}
-NOTE: if you define an argument for a field that is not required (and in a query
+Convenience Features of Graphene Resolvers
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. _ResolverImplicitStaticMethod:
+
+Implicit staticmethod
+*********************
+
+One surprising feature of Graphene is that all resolver methods are treated implicitly as staticmethods. This means that, unlike other methods in Python, the first argument of a resolver is *never* ``self`` while it is being executed by Graphene. Instead, the first argument is always :ref:`ResolverRootArgument`. In practice, this is very convenient as, in GraphQL, we are almost always more concerned with the using the parent value object to resolve queries than attributes on the Python object itself.
+
+The two resolvers in this example are effectively the same.
+
+.. code:: python
+
+ class Person(graphene.ObjectType):
+ first_name = graphene.String()
+ last_name = graphene.String()
+
+ @staticmethod
+ def resolve_first_name(parent, info):
+ '''
+ Decorating a Python method with `staticmethod` ensures that `self` will not be provided as an
+ argument. However, Graphene does not need this decorator for this behavior.
+ '''
+ return parent.first_name
+
+ def resolve_last_name(parent, info):
+ '''
+ Normally the first argument for this method would be `self`, but Graphene executes this as
+ a staticmethod implicitly.
+ '''
+ return parent.last_name
+
+ # ...
+
+If you prefer your code to be more explict, feel free to use ``@staticmethod`` decorators. Otherwise, your code may be cleaner without them!
+
+.. _DefaultResolver:
+
+Default Resolver
+****************
+
+If a resolver method is not defined for a **Field** attribute on our *ObjectType*, Graphene supplies a default resolver.
+
+If the :ref:`ResolverRootArgument` is a dictionary, the resolver will look for a dictionary key matching the field name. Otherwise, the resolver will get the attribute from the parent value object matching the field name.
+
+.. code:: python
+
+ from collections import namedtuple
+
+ PersonValueObject = namedtuple('Person', 'first_name', 'last_name')
+
+ class Person(graphene.ObjectType):
+ first_name = graphene.String()
+ last_name = graphene.String()
+
+ class Query(graphene.Object):
+ me = graphene.Field(Person)
+ my_best_friend = graphene.Field(Person)
+
+ def resolve_me(parent, info):
+ # always pass an object for `me` field
+ return PersonValueObject(first_name='Luke', last_name='Skywalker')
+
+ def resolve_my_best_friend(parent, info):
+ # always pass a dictionary for `my_best_fiend_field`
+ return {"first_name": "R2", "last_name": "D2"}
+
+ schema = graphene.Schema(query=Query)
+ result = schema.execute('''
+ {
+ me { firstName lastName }
+ myBestFriend { firstName lastName }
+ }
+ ''')
+ # With default resolvers we can resolve attributes from an object..
+ assert result['data']['me'] == {"firstName": "Luke", "lastName": "Skywalker"}
+
+ # With default resolvers, we can also resolve keys from a dictionary..
+ assert result['data']['my_best_friend'] == {"firstName": "R2", "lastName": "D2"}
+
+Advanced
+~~~~~~~~
+
+GraphQL Argument defaults
+*************************
+
+If you define an argument for a field that is not required (and in a query
execution it is not provided as an argument) it will not be passed to the
resolver function at all. This is so that the developer can differenciate
between a ``undefined`` value for an argument and an explicit ``null`` value.
@@ -124,7 +257,7 @@ For example, given this schema:
class Query(graphene.ObjectType):
hello = graphene.String(required=True, name=graphene.String())
- def resolve_hello(_, info, name):
+ def resolve_hello(parent, info, name):
return name if name else 'World'
And this query:
@@ -141,7 +274,7 @@ 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
@@ -149,8 +282,9 @@ into a dict:
class Query(graphene.ObjectType):
hello = graphene.String(required=True, name=graphene.String())
- def resolve_hello(_, info, **args):
- return args.get('name', 'World')
+ 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:
@@ -159,12 +293,24 @@ Or by setting a default value for the keyword argument:
class Query(graphene.ObjectType):
hello = graphene.String(required=True, name=graphene.String())
- def resolve_hello(_, info, name='World'):
- return name
+ 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
+
+ class Query(graphene.ObjectType):
+ hello = graphene.String(
+ required=True,
+ name=graphene.Argument(graphene.String, default_value='World')
+ )
+
+ def resolve_hello(parent, info, name):
+ return f'Hello, {name}!'
Resolvers outside the class
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
+***************************
A field can use a custom resolver from outside the class:
@@ -181,11 +327,11 @@ A field can use a custom resolver from outside the class:
full_name = graphene.String(resolver=resolve_full_name)
-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 +340,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``
@@ -207,4 +363,34 @@ property on the ``Meta`` class:
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
+
+ class MyGraphQlSong(graphene.ObjectType):
+ ''' We can set the schema description for an Object Type here on a docstring '''
+ class Meta:
+ description = 'But if we set the description in Meta, this value is used instead'
+
+Interfaces & Possible Types
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Setting ``interfaces`` in Meta inner class specifies the GraphQL Interfaces that this Object implements.
+
+Providing ``possible_types`` helps Graphene resolve ambiguous types such as interfaces or Unions.
+
+See :ref:`Interfaces` for more information.
+
+.. code:: python
+
+ Song = namedtuple('Song', ('title', 'artist'))
+
+ class MyGraphQlSong(graphene.ObjectType):
+ class Meta:
+ interfaces = (graphene.Node, )
+ possible_types = (Song, )
+
.. _Interface: /docs/interfaces/
diff --git a/docs/types/scalars.rst b/docs/types/scalars.rst
index acbd534b..74002483 100644
--- a/docs/types/scalars.rst
+++ b/docs/types/scalars.rst
@@ -1,3 +1,5 @@
+.. _Scalars:
+
Scalars
=======
diff --git a/docs/types/schema.rst b/docs/types/schema.rst
index 3832b6f9..1af5c294 100644
--- a/docs/types/schema.rst
+++ b/docs/types/schema.rst
@@ -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 ` 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
--------------------------