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

The following code:

    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:

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

## How to use

First generate the root using the `DSLSchema`:

    ds = DSLSchema(client.schema)

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

    ds.Query

From this root type, you use auto-generated attributes to get a field. This will generate a `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 `select` method.

To generate the children fields, we use the same method as above to auto-generate the fields from the `ds` instance (ie `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 `DSLQuery`, `DSLMutation` or `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 `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 `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 `from=5`, you can use `**{"from":5}`

### Aliases

You can set an alias of a field using the `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 `select` method:

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

### Mutations

For the mutations, you need to start from root fields starting from `ds.Mutation` then you need to create the GraphQL operation using the class `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 `DSLVariableDefinitions`:

      var = DSLVariableDefinitions()

- From this instance you can generate `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:

    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 `default` method on a variable.

The following code:

    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 `ds.Subscription` then you need to create the GraphQL operation using the class `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 `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 `DSLFragment` with a name:

      name_and_appearances = DSLFragment("NameAndAppearances")

- Provide the GraphQL type of the fragment with the `on` method:

      name_and_appearances.on(ds.Character)

- Add children fields using the `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 `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 `DSLInlineFragment`:

      human_fragment = DSLInlineFragment()

- Provide the GraphQL type of the fragment with the `on` method:

      human_fragment.on(ds.Human)

- Add children fields using the `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 `on` and `select` methods return `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 (`__typename`, `__schema` and `__type`), you can use the `DSLMetaField` class:

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

## Executable examples

### Async example

    import asyncio

    from gql import Client
    from gql.dsl import DSLQuery, 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:

            assert client.schema is not None

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

            # Create the query using dynamically generated attributes from ds
            query = dsl_gql(
                DSLQuery(
                    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(DSLQuery(query_continents))

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

    asyncio.run(main())

### Sync example

    from gql import Client
    from gql.dsl import DSLQuery, 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

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

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

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