File: objecttypes.rst

package info (click to toggle)
python-graphene 3.4.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,120 kB
  • sloc: python: 8,935; makefile: 214; sh: 18
file content (435 lines) | stat: -rw-r--r-- 14,286 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
.. _ObjectType:

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 attribute of the ObjectType represents a ``Field``.
- Each ``Field`` has a :ref:`resolver method<Resolvers>` to fetch data (or :ref:`DefaultResolver`).

Quick example
-------------

This example model defines a Person, with a first and a last name:

.. code:: python

    from graphene import ObjectType, String

    class Person(ObjectType):
        first_name = String()
        last_name = String()
        full_name = String()

        def resolve_full_name(parent, info):
            return f"{parent.first_name} {parent.last_name}"

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:

.. code::

    type Person {
      firstName: String
      lastName: String
      fullName: String
    }

.. _Resolvers:

Resolvers
---------

A **Resolver** is a method that helps us answer **Queries** by fetching data for a **Field** in our **Schema**.

Resolvers are lazily executed, so if a field is not included in a query, its resolver will not be executed.

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``.

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

    from graphene import ObjectType, String, Field

    def get_human(name):
        first_name, last_name = name.split()
        return Person(first_name, last_name)

    class Person(ObjectType):
        full_name = String()

        def resolve_full_name(parent, info):
            return f"{parent.first_name} {parent.last_name}"

    class Query(ObjectType):
        me = Field(Person)

        def resolve_me(parent, info):
            # returns an object that represents a Person
            return get_human(name="Luke Skywalker")

When we execute a query against that schema.

.. code:: python

    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
keyword arguments. For example:

.. code:: python

    from graphene import ObjectType, Field, String

    class Query(ObjectType):
        human_by_name = Field(Human, name=String(required=True))

        def resolve_human_by_name(parent, info, name):
            return get_human(name=name)

You can then execute the following query:

.. code::

    query {
        humanByName(name: "Luke Skywalker") {
            firstName
            lastName
        }
    }

*Note:* There are several arguments to a field that are "reserved" by Graphene
(see :ref:`fields-mounted-types`).
You can still define an argument that clashes with one of these fields by using
the ``args`` parameter like so:

.. code:: python

    from graphene import ObjectType, Field, String

    class Query(ObjectType):
        answer = String(args={'description': String()})

        def resolve_answer(parent, info, description):
            return description


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 explicit, 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["myBestFriend"] == {"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 differentiate
between a ``undefined`` value for an argument and an explicit ``null`` value.

For example, given this schema:

.. code:: python

    from graphene import ObjectType, String

    class Query(ObjectType):
        hello = String(required=True, name=String())

        def resolve_hello(parent, info, name):
            return name if name else 'World'

And this query:

.. code::

    query {
        hello
    }

An error will be thrown:

.. code::

    TypeError: resolve_hello() missing 1 required positional argument: 'name'

You can fix this error in several ways. Either by combining all keyword arguments
into a dict:

.. code:: python

    from graphene import ObjectType, String

    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

    from graphene import ObjectType, String

    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

    from graphene import ObjectType, String

    def resolve_full_name(person, info):
        return f"{person.first_name} {person.last_name}"

    class Person(ObjectType):
        first_name = String()
        last_name = String()
        full_name = String(resolver=resolve_full_name)


Instances as value objects
**************************

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

    peter = Person(first_name='Peter', last_name='Griffin')

    peter.first_name  # prints "Peter"
    peter.last_name  # prints "Griffin"

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``
property on the ``Meta`` class:

.. code:: python

    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/