Skip to main content

GraphQL to Python code generator - generates typed Pydantic models and sync/async clients

Project description

gql-pygen

GraphQL to Python Code Generator — Generate typed Pydantic models and async clients from GraphQL schemas.

Features

  • 🎯 Typed Pydantic Models — All GraphQL types, inputs, and enums become Pydantic models with full IDE autocomplete
  • 🔗 Nested Client — Access operations via intuitive paths like client.policy.firewall.add_rule(...)
  • Async & Sync Support — Generate async clients (default) or sync clients with --sync flag
  • Response Parsing — Responses are automatically validated and converted to typed models
  • 🎛️ Field Selection — Request ALL fields, MINIMAL fields, or custom field sets
  • 📦 Archive Support — Works with .graphqls files, directories, .zip, .tar.gz, and .tgz archives
  • 🔌 Extensible — Custom auth, templates, scalar handlers, and generation hooks

Installation

pip install gql-pygen

Or with uv:

uv add gql-pygen

Quick Start

1. Generate Pydantic Models

gql-pygen generate -s ./schema.graphqls -o ./generated

This creates:

  • generated/models/ — Pydantic models for all types
  • generated/enums.py — All GraphQL enums
  • generated/scalars.py — Custom scalar definitions

2. Generate Typed Client

gql-pygen client -s ./schema.graphqls -o ./client.py --client-name MyAPIClient

This creates a single file with:

  • Nested client classes matching your schema structure
  • Typed async methods for all queries and mutations
  • Automatic response parsing with model_validate()

3. Use the Client

import asyncio
from generated.client import MyAPIClient
from generated.models import CreateUserInput

async def main():
    async with MyAPIClient(url="https://api.example.com/graphql", api_key="...") as client:
        # Full IDE autocomplete on nested paths
        result = await client.users.create_user(
            input=CreateUserInput(name="Alice", email="alice@example.com")
        )

        # Response is already typed — no manual parsing needed
        print(f"Created user: {result.user.id}")

asyncio.run(main())

CLI Reference

gql-pygen generate

Generate Pydantic models from a GraphQL schema.

gql-pygen generate [OPTIONS]

Options:
  -s, --schema PATH        Path to schema file, directory, or archive [required]
  -o, --output PATH        Output directory for generated code [required]
  -t, --templates PATH     Custom template directory (overrides built-in templates)
  --async                  Generate async clients (async def + await). Default: sync
  -v, --verbose            Enable verbose output

Examples:

# From a directory of .graphqls files (sync mode, default)
gql-pygen generate -s ./schema -o ./generated

# Generate async clients
gql-pygen generate -s ./schema -o ./generated --async

# From an archive
gql-pygen generate -s ./schema-bundle.tgz -o ./generated

# With custom templates
gql-pygen generate -s ./schema -o ./generated --templates ./my_templates

gql-pygen client

Generate a typed client with all operations. By default generates async clients.

gql-pygen client [OPTIONS]

Options:
  -s, --schema PATH        Path to schema file, directory, or archive [required]
  -o, --output PATH        Output file for generated client [required]
  -n, --client-name TEXT   Client class name (default: GraphQLClient)
  --async                  Generate async clients (default: True)
  --sync                   Generate sync clients instead of async
  -v, --verbose            Enable verbose output

Examples:

# Generate async client (default)
gql-pygen client -s ./schema.tgz -o ./client.py

# Generate sync client
gql-pygen client -s ./schema.tgz -o ./client.py --sync

# Generate with custom class name
gql-pygen client -s ./schema.tgz -o ./client.py --client-name CatoClient

Generated Client Usage

Async Context Manager

async with MyAPIClient(url=API_URL, api_key=API_KEY) as client:
    result = await client.namespace.operation(...)

Field Selection

Control which fields are requested:

from generated.client import FieldSelection

# Request all fields (default)
result = await client.users.get_user(id="123", fields=FieldSelection.ALL)

# Request minimal fields (just IDs and __typename)
result = await client.users.get_user(id="123", fields=FieldSelection.MINIMAL)

# Request specific fields
result = await client.users.get_user(
    id="123",
    fields=FieldSelection.custom(["id", "name", "email"])
)

Error Handling

from generated.client import GraphQLError

try:
    result = await client.users.create_user(input=user_input)
except GraphQLError as e:
    print(f"GraphQL error: {e.message}")
    for error in e.errors:
        print(f"  - {error}")

Extensibility

gql-pygen is designed to be extensible. You can customize authentication, templates, scalar handling, and code generation without modifying the package.

Custom Authentication

The generated client supports pluggable authentication:

from gql_pygen.core import BearerAuth, BasicAuth, HeaderAuth, ApiKeyAuth

# Bearer token (OAuth, JWT)
async with MyClient(url=URL, auth=BearerAuth("your-token")) as client:
    result = await client.users.get_user(id="123")

# Basic auth
async with MyClient(url=URL, auth=BasicAuth("user", "pass")) as client:
    ...

# Custom headers
async with MyClient(url=URL, auth=HeaderAuth({"X-Custom": "value"})) as client:
    ...

# API key (default, backward compatible)
async with MyClient(url=URL, auth=ApiKeyAuth("key", header_name="x-api-key")) as client:
    ...

You can also implement your own auth by following the Auth protocol:

class MyCustomAuth:
    def get_headers(self) -> dict[str, str]:
        return {"Authorization": f"Custom {self.token}"}

Custom Templates

Override built-in Jinja2 templates to customize generated code:

gql-pygen generate -s ./schema -o ./generated --templates ./my_templates

Templates in your directory take precedence. Available templates to override:

  • models.py.j2 — Pydantic model generation
  • enums.py.j2 — Enum generation
  • scalars.py.j2 — Scalar type definitions

Custom Scalar Handlers

Define how GraphQL custom scalars map to Python types:

from gql_pygen.core import ScalarHandler, ScalarRegistry

class MoneyHandler:
    python_type = "Decimal"
    import_statement = "from decimal import Decimal"

    def serialize(self, value):
        return str(value)

    def deserialize(self, value):
        from decimal import Decimal
        return Decimal(value)

# Register custom scalars
registry = ScalarRegistry()
registry.register("Money", MoneyHandler())

Built-in handlers: DateTimeHandler, DateHandler, UUIDHandler, JSONHandler

Generation Hooks

Transform the IR before generation or modify generated code after:

from gql_pygen.core import HookRunner, FilterTypesHook, AddHeaderHook

runner = HookRunner()

# Pre-generation: filter out internal types
runner.add_pre_hook(FilterTypesHook(exclude_prefix="_"))

# Post-generation: add license header
runner.add_post_hook(AddHeaderHook("# Copyright 2024 My Company"))

Custom hooks follow the PreGenerateHook and PostGenerateHook protocols:

class MyPreHook:
    def pre_generate(self, ir):
        # Modify IR
        return ir

class MyPostHook:
    def post_generate(self, filename: str, content: str) -> str:
        # Transform generated code
        return content

How It Works

  1. Parse — Reads GraphQL schema files using graphql-core
  2. Transform — Converts to an intermediate representation (IR)
  3. Generate — Renders Pydantic models and client code via Jinja2 templates

The generated client:

  • Uses httpx for async HTTP requests
  • Validates responses with Pydantic's model_validate()
  • Handles lists, optionals, and nested types correctly

Development

# Clone the repository
git clone https://github.com/your-org/gql-pygen.git
cd gql-pygen

# Install with dev dependencies
uv sync

# Run tests
uv run pytest tests/ -v

# Run a specific test file
uv run pytest tests/test_client_generator.py -v

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

gql_pygen-0.2.0.tar.gz (41.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

gql_pygen-0.2.0-py3-none-any.whl (40.0 kB view details)

Uploaded Python 3

File details

Details for the file gql_pygen-0.2.0.tar.gz.

File metadata

  • Download URL: gql_pygen-0.2.0.tar.gz
  • Upload date:
  • Size: 41.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.3

File hashes

Hashes for gql_pygen-0.2.0.tar.gz
Algorithm Hash digest
SHA256 8fea654dfbebec4f4421325e189af7af59dae7f59d9b64dd10c19e1b85a27825
MD5 76f6679cbf318d09f5020bab1439c488
BLAKE2b-256 dff4fbf2b36da5a1c07d43ed3810cc253355126552df90f18eb76e1d4edfe843

See more details on using hashes here.

File details

Details for the file gql_pygen-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: gql_pygen-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 40.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.3

File hashes

Hashes for gql_pygen-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7ac729bbb34b509fed99479bfaeba3914e6b49a8e7fb5fab344e423b5fbe6e69
MD5 ce2554c3674c18885e1d43368bfe94de
BLAKE2b-256 4dbb6fb8cb7218fa5a01942ac7ed8603d5e6aa32cecdb7fd19f6e88751dbdf08

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page