File: dsl_module.rst

package info (click to toggle)
python-gql 3.6.0~b4-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 1,824 kB
  • sloc: python: 20,567; makefile: 52
file content (402 lines) | stat: -rw-r--r-- 11,000 bytes parent folder | download | duplicates (2)
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
Compose queries dynamically
===========================

Instead of providing the GraphQL queries as a Python String, it is also possible to create GraphQL queries dynamically.
Using the :mod:`DSL module <gql.dsl>`, we can create a query using a Domain Specific Language which is created from the schema.

The following code:

.. code-block:: python

    ds = DSLSchema(StarWarsSchema)

    query = dsl_gql(
        DSLQuery(
            ds.Query.hero.select(
                ds.Character.id,
                ds.Character.name,
                ds.Character.friends.select(ds.Character.name),
            )
        )
    )

will generate a query equivalent to:

.. code-block:: python

    query = gql("""
        query {
          hero {
            id
            name
            friends {
              name
            }
          }
        }
    """)

How to use
----------

First generate the root using the :class:`DSLSchema <gql.dsl.DSLSchema>`::

    ds = DSLSchema(client.schema)

Then use auto-generated attributes of the :code:`ds` instance
to get a root type (Query, Mutation or Subscription).
This will generate a :class:`DSLType <gql.dsl.DSLType>` instance::

    ds.Query

From this root type, you use auto-generated attributes to get a field.
This will generate a :class:`DSLField <gql.dsl.DSLField>` instance::

    ds.Query.hero

hero is a GraphQL object type and needs children fields. By default,
there is no children fields selected. To select the fields that you want
in your query, you use the :meth:`select <gql.dsl.DSLField.select>` method.

To generate the children fields, we use the same method as above to auto-generate the fields
from the :code:`ds` instance
(ie :code:`ds.Character.name` is the field `name` of the type `Character`)::

    ds.Query.hero.select(ds.Character.name)

The select method return the same instance, so it is possible to chain the calls::

    ds.Query.hero.select(ds.Character.name).select(ds.Character.id)

Or do it sequencially::

    hero_query = ds.Query.hero

    hero_query.select(ds.Character.name)
    hero_query.select(ds.Character.id)

As you can select children fields of any object type, you can construct your complete query tree::

    ds.Query.hero.select(
        ds.Character.id,
        ds.Character.name,
        ds.Character.friends.select(ds.Character.name),
    )

Once your root query fields are defined, you can put them in an operation using
:class:`DSLQuery <gql.dsl.DSLQuery>`,
:class:`DSLMutation <gql.dsl.DSLMutation>` or
:class:`DSLSubscription <gql.dsl.DSLSubscription>`::

    DSLQuery(
        ds.Query.hero.select(
            ds.Character.id,
            ds.Character.name,
            ds.Character.friends.select(ds.Character.name),
        )
    )


Once your operations are defined,
use the :func:`dsl_gql <gql.dsl.dsl_gql>` function to convert your operations into
a document which will be able to get executed in the client or a session::

    query = dsl_gql(
        DSLQuery(
            ds.Query.hero.select(
                ds.Character.id,
                ds.Character.name,
                ds.Character.friends.select(ds.Character.name),
            )
        )
    )

    result = client.execute(query)

Arguments
^^^^^^^^^

It is possible to add arguments to any field simply by calling it
with the required arguments::

    ds.Query.human(id="1000").select(ds.Human.name)

It can also be done using the :meth:`args <gql.dsl.DSLField.args>` method::

    ds.Query.human.args(id="1000").select(ds.Human.name)

.. note::
    If your argument name is a Python keyword (for, in, from, ...), you will receive a
    SyntaxError (See `issue #308`_). To fix this, you can provide the arguments by unpacking a dictionary.

    For example, instead of using :code:`from=5`, you can use :code:`**{"from":5}`

Aliases
^^^^^^^

You can set an alias of a field using the :meth:`alias <gql.dsl.DSLField.alias>` method::

    ds.Query.human.args(id=1000).alias("luke").select(ds.Character.name)

It is also possible to set the alias directly using keyword arguments of an operation::

    DSLQuery(
        luke=ds.Query.human.args(id=1000).select(ds.Character.name)
    )

Or using keyword arguments in the :meth:`select <gql.dsl.DSLField.select>` method::

    ds.Query.hero.select(
        my_name=ds.Character.name
    )

Mutations
^^^^^^^^^

For the mutations, you need to start from root fields starting from :code:`ds.Mutation`
then you need to create the GraphQL operation using the class
:class:`DSLMutation <gql.dsl.DSLMutation>`. Example::

    query = dsl_gql(
        DSLMutation(
            ds.Mutation.createReview.args(
                episode=6, review={"stars": 5, "commentary": "This is a great movie!"}
            ).select(ds.Review.stars, ds.Review.commentary)
        )
    )

Variable arguments
^^^^^^^^^^^^^^^^^^

To provide variables instead of argument values directly for an operation, you have to:

* Instantiate a :class:`DSLVariableDefinitions <gql.dsl.DSLVariableDefinitions>`::

    var = DSLVariableDefinitions()

* From this instance you can generate :class:`DSLVariable <gql.dsl.DSLVariable>` instances
  and provide them as the value of the arguments::

    ds.Mutation.createReview.args(review=var.review, episode=var.episode)

* Once the operation has been defined, you have to save the variable definitions used
  in it::

    operation.variable_definitions = var

The following code:

.. code-block:: python

    var = DSLVariableDefinitions()
    op = DSLMutation(
        ds.Mutation.createReview.args(review=var.review, episode=var.episode).select(
            ds.Review.stars, ds.Review.commentary
        )
    )
    op.variable_definitions = var
    query = dsl_gql(op)

will generate a query equivalent to::

    mutation ($review: ReviewInput, $episode: Episode) {
      createReview(review: $review, episode: $episode) {
        stars
        commentary
      }
    }

Variable arguments with a default value
"""""""""""""""""""""""""""""""""""""""

If you want to provide a **default value** for your variable, you can use
the :code:`default` method on a variable.

The following code:

.. code-block:: python

    var = DSLVariableDefinitions()
    op = DSLMutation(
        ds.Mutation.createReview.args(
            review=var.review.default({"stars": 5, "commentary": "Wow!"}),
            episode=var.episode,
        ).select(ds.Review.stars, ds.Review.commentary)
    )
    op.variable_definitions = var
    query = dsl_gql(op)

will generate a query equivalent to::

    mutation ($review: ReviewInput = {stars: 5, commentary: "Wow!"}, $episode: Episode) {
      createReview(review: $review, episode: $episode) {
        stars
        commentary
      }
    }

Subscriptions
^^^^^^^^^^^^^

For the subscriptions, you need to start from root fields starting from :code:`ds.Subscription`
then you need to create the GraphQL operation using the class
:class:`DSLSubscription <gql.dsl.DSLSubscription>`. Example::

    query = dsl_gql(
        DSLSubscription(
            ds.Subscription.reviewAdded(episode=6).select(ds.Review.stars, ds.Review.commentary)
        )
    )

Multiple fields in an operation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

It is possible to create an operation with multiple fields::

    DSLQuery(
        ds.Query.hero.select(ds.Character.name),
        hero_of_episode_5=ds.Query.hero(episode=5).select(ds.Character.name),
    )

Operation name
^^^^^^^^^^^^^^

You can set the operation name of an operation using a keyword argument
to :func:`dsl_gql <gql.dsl.dsl_gql>`::

    query = dsl_gql(
        GetHeroName=DSLQuery(ds.Query.hero.select(ds.Character.name))
    )

will generate the request::

    query GetHeroName {
        hero {
            name
        }
    }

Multiple operations in a document
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

It is possible to create an Document with multiple operations::

    query = dsl_gql(
        operation_name_1=DSLQuery( ... ),
        operation_name_2=DSLQuery( ... ),
        operation_name_3=DSLMutation( ... ),
    )

Fragments
^^^^^^^^^

To define a `Fragment`_, you have to:

* Instantiate a :class:`DSLFragment <gql.dsl.DSLFragment>` with a name::

    name_and_appearances = DSLFragment("NameAndAppearances")

* Provide the GraphQL type of the fragment with the
  :meth:`on <gql.dsl.DSLFragment.on>` method::

    name_and_appearances.on(ds.Character)

* Add children fields using the :meth:`select <gql.dsl.DSLFragment.select>` method::

    name_and_appearances.select(ds.Character.name, ds.Character.appearsIn)

Once your fragment is defined, to use it you should:

* select it as a field somewhere in your query::

    query_with_fragment = DSLQuery(ds.Query.hero.select(name_and_appearances))

* add it as an argument of :func:`dsl_gql <gql.dsl.dsl_gql>` with your query::

    query = dsl_gql(name_and_appearances, query_with_fragment)

The above example will generate the following request::

    fragment NameAndAppearances on Character {
        name
        appearsIn
    }

    {
        hero {
            ...NameAndAppearances
        }
    }

Inline Fragments
^^^^^^^^^^^^^^^^

To define an `Inline Fragment`_, you have to:

* Instantiate a :class:`DSLInlineFragment <gql.dsl.DSLInlineFragment>`::

    human_fragment = DSLInlineFragment()

* Provide the GraphQL type of the fragment with the
  :meth:`on <gql.dsl.DSLInlineFragment.on>` method::

    human_fragment.on(ds.Human)

* Add children fields using the :meth:`select <gql.dsl.DSLInlineFragment.select>` method::

    human_fragment.select(ds.Human.homePlanet)

Once your inline fragment is defined, to use it you should:

* select it as a field somewhere in your query::

    query_with_inline_fragment = ds.Query.hero.args(episode=6).select(
        ds.Character.name,
        human_fragment
    )

The above example will generate the following request::

    hero(episode: JEDI) {
        name
        ... on Human {
          homePlanet
        }
    }

Note: because the :meth:`on <gql.dsl.DSLInlineFragment.on>` and
:meth:`select <gql.dsl.DSLInlineFragment.select>` methods return :code:`self`,
this can be written in a concise manner::

    query_with_inline_fragment = ds.Query.hero.args(episode=6).select(
        ds.Character.name,
        DSLInlineFragment().on(ds.Human).select(ds.Human.homePlanet)
    )

Meta-fields
^^^^^^^^^^^

To define meta-fields (:code:`__typename`, :code:`__schema` and :code:`__type`),
you can use the :class:`DSLMetaField <gql.dsl.DSLMetaField>` class::

    query = ds.Query.hero.select(
        ds.Character.name,
        DSLMetaField("__typename")
    )

Executable examples
-------------------

Async example
^^^^^^^^^^^^^

.. literalinclude:: ../code_examples/aiohttp_async_dsl.py

Sync example
^^^^^^^^^^^^^

.. literalinclude:: ../code_examples/requests_sync_dsl.py

.. _Fragment: https://graphql.org/learn/queries/#fragments
.. _Inline Fragment: https://graphql.org/learn/queries/#inline-fragments
.. _issue #308: https://github.com/graphql-python/gql/issues/308