Skip to content

Commit f2c5666

Browse files
committed
Merge branch 'master' into fix_aiohttp_wait_for_closed_connections
2 parents 6f327a9 + 75a771d commit f2c5666

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+8614
-543
lines changed

.github/workflows/tests.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88
strategy:
99
max-parallel: 4
1010
matrix:
11-
python-version: ["3.6", "3.7", "3.8", "3.9", "pypy3"]
11+
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "pypy3"]
1212
os: [ubuntu-latest, windows-latest]
1313
exclude:
1414
- os: windows-latest
@@ -17,6 +17,8 @@ jobs:
1717
python-version: "3.7"
1818
- os: windows-latest
1919
python-version: "3.9"
20+
- os: windows-latest
21+
python-version: "3.10"
2022
- os: windows-latest
2123
python-version: "pypy3"
2224

.readthedocs.yaml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# .readthedocs.yaml
2+
# Read the Docs configuration file
3+
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
4+
5+
# Required
6+
version: 2
7+
8+
# Set the version of Python and other tools you might need
9+
build:
10+
os: ubuntu-20.04
11+
tools:
12+
python: "3.9"
13+
14+
# Build documentation in the docs/ directory with Sphinx
15+
sphinx:
16+
configuration: docs/conf.py
17+
18+
# Optionally build your docs in additional formats such as PDF
19+
formats:
20+
- pdf
21+
22+
python:
23+
install:
24+
- requirements: docs/requirements.txt
25+
- method: pip
26+
path: .
27+
extra_requirements:
28+
- all
29+
system_packages: true

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ virtualenv gql-dev
3131
Activate the virtualenv and install dependencies by running:
3232

3333
```console
34-
python pip install -e.[dev]
34+
python -m pip install -e.[dev]
3535
```
3636

3737
If you are using Linux or MacOS, you can make use of Makefile command

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ include CODEOWNERS
44
include LICENSE
55
include README.md
66
include CONTRIBUTING.md
7+
include .readthedocs.yaml
78

89
include dev_requirements.txt
910
include Makefile

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ The main features of GQL are:
3939
* Supports GraphQL queries, mutations and [subscriptions](https://gql.readthedocs.io/en/latest/usage/subscriptions.html)
4040
* Supports [sync or async usage](https://gql.readthedocs.io/en/latest/async/index.html), [allowing concurrent requests](https://gql.readthedocs.io/en/latest/advanced/async_advanced_usage.html#async-advanced-usage)
4141
* Supports [File uploads](https://gql.readthedocs.io/en/latest/usage/file_upload.html)
42-
* [gql-cli script](https://gql.readthedocs.io/en/latest/gql-cli/intro.html) to execute GraphQL queries from the command line
42+
* Supports [Custom scalars / Enums](https://gql.readthedocs.io/en/latest/usage/custom_scalars_and_enums.html)
43+
* [gql-cli script](https://gql.readthedocs.io/en/latest/gql-cli/intro.html) to execute GraphQL queries or download schemas from the command line
4344
* [DSL module](https://gql.readthedocs.io/en/latest/advanced/dsl_module.html) to compose GraphQL queries dynamically
4445

4546
## Installation

docs/advanced/dsl_module.rst

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,47 @@ then you need to create the GraphQL operation using the class
159159
)
160160
)
161161

162+
Variable arguments
163+
^^^^^^^^^^^^^^^^^^
164+
165+
To provide variables instead of argument values directly for an operation, you have to:
166+
167+
* Instantiate a :class:`DSLVariableDefinitions <gql.dsl.DSLVariableDefinitions>`::
168+
169+
var = DSLVariableDefinitions()
170+
171+
* From this instance you can generate :class:`DSLVariable <gql.dsl.DSLVariable>` instances
172+
and provide them as the value of the arguments::
173+
174+
ds.Mutation.createReview.args(review=var.review, episode=var.episode)
175+
176+
* Once the operation has been defined, you have to save the variable definitions used
177+
in it::
178+
179+
operation.variable_definitions = var
180+
181+
The following code:
182+
183+
.. code-block:: python
184+
185+
var = DSLVariableDefinitions()
186+
op = DSLMutation(
187+
ds.Mutation.createReview.args(review=var.review, episode=var.episode).select(
188+
ds.Review.stars, ds.Review.commentary
189+
)
190+
)
191+
op.variable_definitions = var
192+
query = dsl_gql(op)
193+
194+
will generate a query equivalent to::
195+
196+
mutation ($review: ReviewInput, $episode: Episode) {
197+
createReview(review: $review, episode: $episode) {
198+
stars
199+
commentary
200+
}
201+
}
202+
162203
Subscriptions
163204
^^^^^^^^^^^^^
164205

@@ -211,6 +252,103 @@ It is possible to create an Document with multiple operations::
211252
operation_name_3=DSLMutation( ... ),
212253
)
213254

255+
Fragments
256+
^^^^^^^^^
257+
258+
To define a `Fragment`_, you have to:
259+
260+
* Instantiate a :class:`DSLFragment <gql.dsl.DSLFragment>` with a name::
261+
262+
name_and_appearances = DSLFragment("NameAndAppearances")
263+
264+
* Provide the GraphQL type of the fragment with the
265+
:meth:`on <gql.dsl.DSLFragment.on>` method::
266+
267+
name_and_appearances.on(ds.Character)
268+
269+
* Add children fields using the :meth:`select <gql.dsl.DSLFragment.select>` method::
270+
271+
name_and_appearances.select(ds.Character.name, ds.Character.appearsIn)
272+
273+
Once your fragment is defined, to use it you should:
274+
275+
* select it as a field somewhere in your query::
276+
277+
query_with_fragment = DSLQuery(ds.Query.hero.select(name_and_appearances))
278+
279+
* add it as an argument of :func:`dsl_gql <gql.dsl.dsl_gql>` with your query::
280+
281+
query = dsl_gql(name_and_appearances, query_with_fragment)
282+
283+
The above example will generate the following request::
284+
285+
fragment NameAndAppearances on Character {
286+
name
287+
appearsIn
288+
}
289+
290+
{
291+
hero {
292+
...NameAndAppearances
293+
}
294+
}
295+
296+
Inline Fragments
297+
^^^^^^^^^^^^^^^^
298+
299+
To define an `Inline Fragment`_, you have to:
300+
301+
* Instantiate a :class:`DSLInlineFragment <gql.dsl.DSLInlineFragment>`::
302+
303+
human_fragment = DSLInlineFragment()
304+
305+
* Provide the GraphQL type of the fragment with the
306+
:meth:`on <gql.dsl.DSLInlineFragment.on>` method::
307+
308+
human_fragment.on(ds.Human)
309+
310+
* Add children fields using the :meth:`select <gql.dsl.DSLInlineFragment.select>` method::
311+
312+
human_fragment.select(ds.Human.homePlanet)
313+
314+
Once your inline fragment is defined, to use it you should:
315+
316+
* select it as a field somewhere in your query::
317+
318+
query_with_inline_fragment = ds.Query.hero.args(episode=6).select(
319+
ds.Character.name,
320+
human_fragment
321+
)
322+
323+
The above example will generate the following request::
324+
325+
hero(episode: JEDI) {
326+
name
327+
... on Human {
328+
homePlanet
329+
}
330+
}
331+
332+
Note: because the :meth:`on <gql.dsl.DSLInlineFragment.on>` and
333+
:meth:`select <gql.dsl.DSLInlineFragment.select>` methods return :code:`self`,
334+
this can be written in a concise manner::
335+
336+
query_with_inline_fragment = ds.Query.hero.args(episode=6).select(
337+
ds.Character.name,
338+
DSLInlineFragment().on(ds.Human).select(ds.Human.homePlanet)
339+
)
340+
341+
Meta-fields
342+
^^^^^^^^^^^
343+
344+
To define meta-fields (:code:`__typename`, :code:`__schema` and :code:`__type`),
345+
you can use the :class:`DSLMetaField <gql.dsl.DSLMetaField>` class::
346+
347+
query = ds.Query.hero.select(
348+
ds.Character.name,
349+
DSLMetaField("__typename")
350+
)
351+
214352
Executable examples
215353
-------------------
216354

@@ -224,3 +362,5 @@ Sync example
224362

225363
.. literalinclude:: ../code_examples/requests_sync_dsl.py
226364

365+
.. _Fragment: https://graphql.org/learn/queries/#fragments
366+
.. _Inline Fragment: https://graphql.org/learn/queries/#inline-fragments

docs/advanced/logging.rst

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,31 @@ For even more logs, you can set the loglevel at **DEBUG**:
1818
import logging
1919
logging.basicConfig(level=logging.DEBUG)
2020
21+
Disabling logs
22+
--------------
23+
24+
By default, the logs for the transports are quite verbose.
25+
26+
On the **INFO** level, all the messages between the frontend and the backend are logged which can
27+
be difficult to read especially when it fetches the schema from the transport.
28+
29+
It is possible to disable the logs only for a specific gql transport by setting a higher
30+
log level for this transport (**WARNING** for example) so that the other logs of your program are not affected.
31+
32+
For this, you should import the logger from the transport file and set the level on this logger.
33+
34+
For the RequestsHTTPTransport:
35+
36+
.. code-block:: python
37+
38+
from gql.transport.requests import log as requests_logger
39+
requests_logger.setLevel(logging.WARNING)
40+
41+
For the WebsocketsTransport:
42+
43+
.. code-block:: python
44+
45+
from gql.transport.websockets import log as websockets_logger
46+
websockets_logger.setLevel(logging.WARNING)
47+
2148
.. _logging: https://docs.python.org/3/howto/logging.html

docs/code_examples/aiohttp_async_dsl.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ async def main():
1717
# GQL will fetch the schema just after the establishment of the first session
1818
async with client as session:
1919

20-
# Instanciate the root of the DSL Schema as ds
20+
# Instantiate the root of the DSL Schema as ds
2121
ds = DSLSchema(client.schema)
2222

2323
# Create the query using dynamically generated attributes from ds

docs/code_examples/requests_sync_dsl.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
# We should have received the schema now that the session is established
1818
assert client.schema is not None
1919

20-
# Instanciate the root of the DSL Schema as ds
20+
# Instantiate the root of the DSL Schema as ds
2121
ds = DSLSchema(client.schema)
2222

2323
# Create the query using dynamically generated attributes from ds

docs/gql-cli/intro.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
.. _gql_cli:
2+
13
gql-cli
24
=======
35

@@ -69,3 +71,10 @@ Then execute query from the file:
6971
7072
$ cat query.gql | gql-cli wss://countries.trevorblades.com/graphql
7173
{"continent": {"name": "Africa"}}
74+
75+
Print the GraphQL schema in a file
76+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
77+
78+
.. code-block:: shell
79+
80+
$ gql-cli https://countries.trevorblades.com/graphql --print-schema > schema.graphql

docs/intro.rst

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,9 @@ Less dependencies
2626
^^^^^^^^^^^^^^^^^
2727

2828
GQL supports multiple :ref:`transports <transports>` to communicate with the backend.
29-
Each transport can each necessitate specific dependencies.
30-
If you only need one transport, instead of using the "`all`" extra dependency
31-
as described above which installs everything,
32-
you might want to install only the dependency needed for your transport.
29+
Each transport can necessitate specific dependencies.
30+
If you only need one transport you might want to install only the dependency needed for your transport,
31+
instead of using the "`all`" extra dependency as described above, which installs everything.
3332

3433
If for example you only need the :ref:`AIOHTTPTransport <aiohttp_transport>`,
3534
which needs the :code:`aiohttp` dependency, then you can install GQL with::

docs/modules/gql.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ Sub-Packages
2121
client
2222
transport
2323
dsl
24+
utilities

docs/modules/transport.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,5 @@ gql.transport
1414
.. autoclass:: gql.transport.aiohttp.AIOHTTPTransport
1515

1616
.. autoclass:: gql.transport.websockets.WebsocketsTransport
17+
18+
.. autoclass:: gql.transport.phoenix_channel_websockets.PhoenixChannelWebsocketsTransport

docs/modules/utilities.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
gql.utilities
2+
=============
3+
4+
.. currentmodule:: gql.utilities
5+
6+
.. automodule:: gql.utilities

docs/transports/aiohttp.rst

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,48 @@ AIOHTTPTransport
55

66
This transport uses the `aiohttp`_ library and allows you to send GraphQL queries using the HTTP protocol.
77

8+
Reference: :class:`gql.transport.aiohttp.AIOHTTPTransport`
9+
810
.. note::
911

1012
GraphQL subscriptions are not supported on the HTTP transport.
1113
For subscriptions you should use the :ref:`websockets transport <websockets_transport>`.
1214

1315
.. literalinclude:: ../code_examples/aiohttp_async.py
1416

17+
Authentication
18+
--------------
19+
20+
There are multiple ways to authenticate depending on the server configuration.
21+
22+
1. Using HTTP Headers
23+
24+
.. code-block:: python
25+
26+
transport = AIOHTTPTransport(
27+
url='https://SERVER_URL:SERVER_PORT/graphql',
28+
headers={'Authorization': 'token'}
29+
)
30+
31+
2. Using HTTP Cookies
32+
33+
You can manually set the cookies which will be sent with each connection:
34+
35+
.. code-block:: python
36+
37+
transport = AIOHTTPTransport(url=url, cookies={"cookie1": "val1"})
38+
39+
Or you can use a cookie jar to save cookies set from the backend and reuse them later.
40+
41+
In some cases, the server will set some connection cookies after a successful login mutation
42+
and you can save these cookies in a cookie jar to reuse them in a following connection
43+
(See `issue 197`_):
44+
45+
.. code-block:: python
46+
47+
jar = aiohttp.CookieJar()
48+
transport = AIOHTTPTransport(url=url, client_session_args={'cookie_jar': jar})
49+
50+
1551
.. _aiohttp: https://docs.aiohttp.org
52+
.. _issue 197: https://github.com/graphql-python/gql/issues/197

docs/transports/phoenix.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
PhoenixChannelWebsocketsTransport
44
=================================
55

6-
The PhoenixChannelWebsocketsTransport is an **EXPERIMENTAL** async transport which allows you
6+
The PhoenixChannelWebsocketsTransport is an async transport which allows you
77
to execute queries and subscriptions against an `Absinthe`_ backend using the `Phoenix`_
88
framework `channels`_.
99

10+
Reference:
11+
:class:`gql.transport.phoenix_channel_websockets.PhoenixChannelWebsocketsTransport`
12+
1013
.. _Absinthe: http://absinthe-graphql.org
1114
.. _Phoenix: https://www.phoenixframework.org
1215
.. _channels: https://hexdocs.pm/phoenix/Phoenix.Channel.html#content

0 commit comments

Comments
 (0)