# GraphQL in Python

Query and mutate data on any GraphQL server from Python services.

Python services query and mutate data on any system that exposes a GraphQL API - Microsoft 365, GitHub, Shopify, Hasura or a custom backend - through outgoing connections. The connection handles transport, timeouts and authentication.

You can write queries in two ways:

- **String queries:** pass a query or mutation as a string to `.execute()` - use them when the query is known up front
- **DSL queries:** build the query from Python objects with the gql DSL - use them when the query's structure depends on runtime conditions

## Create a connection {#create-a-connection}

To create a connection, go to **Connections** > **Outgoing** > **GraphQL** in the Dashboard, click **Create a new connection** and fill in the form:

1. **Name:** Airport GraphQL
2. **Address:** your GraphQL server's address, e.g. `https://graphql.example.com`
3. Click **OK**

[Video: New GraphQL connection](https://zatosource-production.b-cdn.net/docs/gfx/outgoing/graphql-create.webm?v=1788800742)

Dashboard menu: Connections > Outgoing > GraphQL

The connection becomes available when you click OK and every service can use it through `self.out.graphql`, passing the connection's name.

## Query a server {#query-a-server}

To run a query, pass it as a string to `.execute()` - the connection parses the string before sending it to the server. The service below queries flights that are currently airborne:

```python
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class GetActiveFlights(Service):

    def handle(self):

        # Get the connection by the name configured in Dashboard
        conn = self.out.graphql['Airport GraphQL']

        # Query all flights that are currently airborne
        query = """
            {
                flights(status: "airborne") {
                    id
                    flight_number
                    origin
                    destination
                    status
                }
            }
        """

        result = conn.execute(query)

        self.logger.info('Flights: %s', result)
```

`.invoke()` is an alias for `.execute()`.

## Query with variables {#query-with-variables}

To provide GraphQL variables, pass a params dict to `.execute()`:

```python
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class GetFlightById(Service):

    input = 'flight_id'

    def handle(self):
        conn = self.out.graphql['Airport GraphQL']

        query = """
            query GetFlight($flight_id: ID!) {
                flight(id: $flight_id) {
                    id
                    flight_number
                    origin
                    destination
                    departure_time
                    gate {
                        name
                        terminal
                    }
                }
            }
        """

        params = {'flight_id': self.request.input.flight_id}
        result = conn.execute(query, params=params)

        self.response.payload = result
```

## Run mutations {#run-mutations}

Mutations also use `.execute()` - call it with a mutation string and optional variables:

```python
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class AssignFlightGate(Service):

    input = 'flight_id', 'gate_id'

    def handle(self):
        conn = self.out.graphql['Airport GraphQL']

        mutation = """
            mutation AssignGate($flight_id: ID!, $gate_id: ID!) {
                assignGate(flightId: $flight_id, gateId: $gate_id) {
                    id
                    flight_number
                    gate {
                        name
                        terminal
                    }
                }
            }
        """

        params = {
            'flight_id': self.request.input.flight_id,
            'gate_id': self.request.input.gate_id,
        }

        result = conn.execute(mutation, params=params)

        self.logger.info('Gate assigned: %s', result)
```

## Build queries with the DSL {#build-queries-with-the-dsl}

The gql library's DSL module lets you build queries from Python objects instead of strings, with auto-completion in your IDE and field validation against the server's schema.

To use it, call `.session()` to open a connection that fetches the schema, then build the query with the DSLSchema object:

```python
# -*- coding: utf-8 -*-

# gql
from gql.dsl import DSLQuery, dsl_gql

# Zato
from zato.server.service import Service

class GetFlightsDSL(Service):

    def handle(self):
        conn = self.out.graphql['Airport GraphQL']

        with conn.session() as (session, schema):

            query = dsl_gql(
                DSLQuery(
                    schema.Query.flights.select(
                        schema.Flight.id,
                        schema.Flight.flight_number,
                        schema.Flight.origin,
                        schema.Flight.destination,
                    )
                )
            )

            result = session.execute(query)

        self.logger.info('Flights: %s', result)
```

Use the DSL when the query's structure depends on runtime conditions:

```python
# -*- coding: utf-8 -*-

# gql
from gql.dsl import DSLQuery, dsl_gql

# Zato
from zato.server.service import Service

class GetFlightsConditional(Service):

    def handle(self):
        conn = self.out.graphql['Airport GraphQL']

        with conn.session() as (session, schema):

            # Start with core fields
            fields = [schema.Flight.id, schema.Flight.flight_number, schema.Flight.status]

            # Add optional fields based on the caller's request
            if self.request.input.include_gate:
                fields.append(schema.Flight.gate)

            if self.request.input.include_crew:
                fields.append(schema.Flight.crew)

            query = dsl_gql(
                DSLQuery(
                    schema.Query.flights.select(*fields)
                )
            )

            result = session.execute(query)

        self.logger.info('Flights: %s', result)
```

## Ping the server {#ping-the-server}

To verify that the connection works, call `.ping()` - it sends a schema introspection query and returns True when the server responds:

```python
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class PingGraphQL(Service):

    def handle(self):
        conn = self.out.graphql['Airport GraphQL']

        is_alive = conn.ping()
        self.logger.info('Server alive: %s', is_alive)
```

## Security {#security}

Like all other outgoing connections, GraphQL connections are secured by attaching a security definition. The definition is created separately in **Security** in the Dashboard and assigned to the connection when you create or edit it.

The following security types are supported:

- **Basic Auth** - sends a username and password as HTTP Basic Authentication headers with every request to the GraphQL server
- **API key** - sends a token in a configurable HTTP header, commonly used with services like GitHub's GraphQL API where the header is `Authorization: Bearer <token>`
- **OAuth** - obtains a [Bearer token](https://zato.io/docs/security/bearer-tokens/index.html) from an OAuth endpoint and attaches it to each request, commonly used with Microsoft 365 and other enterprise GraphQL APIs

The connection attaches the credentials to every request - your service code stays the same regardless of the security mechanism.

Security definitions can also be managed through [enmasse](https://zato.io/docs/admin/enmasse.html) for automated deployments.

## Custom headers {#custom-headers}

To send additional HTTP headers with every request to the GraphQL server, use the **Extra headers** field on the connection. It accepts a JSON object where each key is a header name and each value is the header value.

For example, to send a tenant identifier and a custom tracing header:

```json
{"X-Tenant": "acme", "X-Request-Source": "zato"}
```

The connection merges these headers into every request, whether made with `.execute()` or `.session()`.

## See also {#see-also}

- [Shopify GraphQL Admin API](https://zato.io/docs/dev/shopify/graphql-admin-api-python.html) - A production GraphQL API used from Python services
- [REST](https://zato.io/docs/dev/examples/rest.html) - Call external REST APIs when a system offers no GraphQL
- [gRPC](https://zato.io/docs/dev/examples/grpc.html) - Call gRPC endpoints with Protocol Buffers
- [Enmasse](https://zato.io/docs/admin/enmasse.html) - Define connections and security in YAML for automated deployments

## Learn more {#learn-more}

- [Development documentation](https://zato.io/docs/dev/) - The complete reference for writing services
- [Requests and responses](https://zato.io/docs/dev/request-response/) - What a service receives, what it returns and how both are structured
- [Integration examples](https://zato.io/docs/dev/examples/) - Ready-made code for the systems you are likely to connect to
- [IDE and debugging](https://zato.io/docs/dev/ide/) - Write services in the Dashboard or in your own editor
- [Data models](https://zato.io/docs/dev/model/) - Declare inputs and outputs and have them validated for you
- [In-depth API tutorial](https://zato.io/tutorials/main/01.html) - The full platform tutorial, from installation to production patterns
