Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor DSL code #169

Merged
merged 23 commits into from
Nov 21, 2020
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
246df96
DSL Refactor 1
leszekhanusz Nov 6, 2020
9d501a3
DSL Refactor 2
leszekhanusz Nov 6, 2020
921ea69
DSL Refactor 3
leszekhanusz Nov 7, 2020
8d34d31
DSL Refactor 4
leszekhanusz Nov 7, 2020
2b80179
Fix annotations for python 3.6
leszekhanusz Nov 7, 2020
79b082b
DSL Refactor 5
leszekhanusz Nov 7, 2020
c926a3d
DSL Refactor 6
leszekhanusz Nov 7, 2020
e0c7e34
DSL Refactor 7
leszekhanusz Nov 7, 2020
5e130cf
DOC document the dsl code
leszekhanusz Nov 8, 2020
0ffe5b9
DSL Refactor 8
leszekhanusz Nov 8, 2020
7406c7a
Merge branch 'master' into refactor_dsl
leszekhanusz Nov 8, 2020
dca2832
DOCS adding sphinx docs for the DSL module
leszekhanusz Nov 8, 2020
7501c17
Fix typo and modify doc following @KingDarBoja advice
leszekhanusz Nov 8, 2020
9974899
Add alias to selection as keyword argument at DSL
KingDarBoja Nov 8, 2020
db654f9
small cleaning
leszekhanusz Nov 11, 2020
fa8e0fe
Remove GraphQLTypeWithFields alias
leszekhanusz Nov 12, 2020
ec57e46
Rephrase DSLType comment
leszekhanusz Nov 12, 2020
2b8f985
fix _get_arg_serializer typing
leszekhanusz Nov 12, 2020
49eecd3
Really Fix nested input arguments this time
leszekhanusz Nov 12, 2020
dbfc221
The _get_arg_serializer method is not needed...
leszekhanusz Nov 13, 2020
3201c05
Allow aliases as keyword arguments for dsl_gql + new operation_name a…
leszekhanusz Nov 14, 2020
27e0eb7
Another refactor to allow multiple operations in documents
leszekhanusz Nov 14, 2020
a2d56d9
Merge branch 'master' into refactor_dsl
leszekhanusz Nov 21, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ The main features of GQL are:
* 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)
* Supports [File uploads](https://gql.readthedocs.io/en/latest/usage/file_upload.html)
* [gql-cli script](https://gql.readthedocs.io/en/latest/gql-cli/intro.html) to execute GraphQL queries from the command line
* [DSL module](https://gql.readthedocs.io/en/latest/advanced/dsl_module.html) to compose GraphQL queries dynamically

## Installation

Expand Down
153 changes: 136 additions & 17 deletions docs/advanced/dsl_module.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,152 @@ Compose queries dynamically
===========================

Instead of providing the GraphQL queries as a Python String, it is also possible to create GraphQL queries dynamically.
Using the DSL module, we can create a query using a Domain Specific Language which is created from the schema.
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(
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

from gql.dsl import DSLSchema
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

client = Client(schema=StarWarsSchema)
ds = DSLSchema(client)
From this root type, you use auto-generated attributes to get a field.
This will generate a :class:`DSLField <gql.dsl.DSLField>` instance::

query_dsl = ds.Query.hero.select(
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,),
ds.Character.friends.select(ds.Character.name),
)

will create a query equivalent to:
Once your query is completed and you have selected all the fields you want,
use the :func:`dsl_gql <gql.dsl.dsl_gql>` function to convert your query into
a document which will be able to get executed in the client or a session::

.. code-block:: python
query = dsl_gql(
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)

Alias
^^^^^

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)

Mutations
^^^^^^^^^

It works the same way for mutations. Example::

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

Multiple requests
^^^^^^^^^^^^^^^^^

It is possible to create a document with multiple requests::

query = dsl_gql(
ds.Query.hero.select(ds.Character.name),
ds.Query.hero(episode=5).alias("hero_of_episode_5").select(ds.Character.name),
)

But you have to take care that the root type is always the same. It is not possible
to mix queries and mutations for example.

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

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

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

hero {
id
name
friends {
name
}
}
Sync example
^^^^^^^^^^^^^

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

Please note that the DSL module is still considered experimental in GQL 3 and is subject to changes
53 changes: 53 additions & 0 deletions docs/code_examples/aiohttp_async_dsl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import asyncio

from gql import Client
from gql.dsl import DSLSchema, dsl_gql
from gql.transport.aiohttp import AIOHTTPTransport


async def main():

transport = AIOHTTPTransport(url="https://countries.trevorblades.com/graphql")

client = Client(transport=transport, fetch_schema_from_transport=True)

# Using `async with` on the client will start a connection on the transport
# and provide a `session` variable to execute queries on this connection.
# Because we requested to fetch the schema from the transport,
# GQL will fetch the schema just after the establishment of the first session
async with client as session:

# Instanciate the root of the DSL Schema as ds
ds = DSLSchema(client.schema)

# Create the query using dynamically generated attributes from ds
query = dsl_gql(
ds.Query.continents(filter={"code": {"eq": "EU"}}).select(
ds.Continent.code, ds.Continent.name
)
)

result = await session.execute(query)
print(result)

# This can also be written as:

# I want to query the continents
query_continents = ds.Query.continents

# I want to get only the continents with code equal to "EU"
query_continents(filter={"code": {"eq": "EU"}})

# I want this query to return the code and name fields
query_continents.select(ds.Continent.code)
query_continents.select(ds.Continent.name)

# I generate a document from my query to be able to execute it
query = dsl_gql(query_continents)

# Execute the query
result = await session.execute(query)
print(result)


asyncio.run(main())
4 changes: 2 additions & 2 deletions docs/code_examples/requests_sync.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from gql import Client, gql
from gql.transport.requests import RequestsHTTPTransport

sample_transport = RequestsHTTPTransport(
transport = RequestsHTTPTransport(
url="https://countries.trevorblades.com/", verify=True, retries=3,
)

client = Client(transport=sample_transport, fetch_schema_from_transport=True,)
client = Client(transport=transport, fetch_schema_from_transport=True)

query = gql(
"""
Expand Down
27 changes: 27 additions & 0 deletions docs/code_examples/requests_sync_dsl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from gql import Client
from gql.dsl import DSLSchema, dsl_gql
from gql.transport.requests import RequestsHTTPTransport

transport = RequestsHTTPTransport(
url="https://countries.trevorblades.com/", verify=True, retries=3,
)

client = Client(transport=transport, fetch_schema_from_transport=True)

# Using `with` on the sync client will start a connection on the transport
# and provide a `session` variable to execute queries on this connection.
# Because we requested to fetch the schema from the transport,
# GQL will fetch the schema just after the establishment of the first session
with client as session:

# We should have received the schema now that the session is established
assert client.schema is not None

# Instanciate the root of the DSL Schema as ds
ds = DSLSchema(client.schema)

# Create the query using dynamically generated attributes from ds
query = dsl_gql(ds.Query.continents.select(ds.Continent.code, ds.Continent.name))

result = session.execute(query)
print(result)
4 changes: 2 additions & 2 deletions docs/modules/client.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Client
======
gql.client
==========

.. currentmodule:: gql.client

Expand Down
7 changes: 7 additions & 0 deletions docs/modules/dsl.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
gql.dsl
=======

.. currentmodule:: gql.dsl

.. automodule:: gql.dsl
:member-order: bysource
1 change: 1 addition & 0 deletions docs/modules/gql.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ Sub-Packages

client
transport
dsl
4 changes: 2 additions & 2 deletions docs/modules/transport.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Transport
=========
gql.transport
=============

.. currentmodule:: gql.transport

Expand Down
Loading