Skip to main content

GraphQL to Python code generator - generates typed Pydantic models and 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 Async Client — Access operations via intuitive paths like client.policy.firewall.add_rule(...)
  • 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)
  -v, --verbose            Enable verbose output

Examples:

# From a directory of .graphqls files
gql-pygen generate -s ./schema -o ./generated

# 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 async client with all operations.

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)
  -v, --verbose            Enable verbose output

Examples:

# Generate with default class name (GraphQLClient)
gql-pygen client -s ./schema.tgz -o ./client.py

# 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.1.1.tar.gz (41.6 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.1.1-py3-none-any.whl (40.0 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for gql_pygen-0.1.1.tar.gz
Algorithm Hash digest
SHA256 baeba1612735cdd19b093c7fb01900c7d627265c0c01572d5cefd4d749304092
MD5 c96cc1751ea605f56ef3398f89939d20
BLAKE2b-256 9a396edc836aece272d47308f5da3c04a838dede7cf7e80b0daa0c82f2b5cba0

See more details on using hashes here.

File details

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

File metadata

  • Download URL: gql_pygen-0.1.1-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.14.0

File hashes

Hashes for gql_pygen-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 1b836b4f8887a780d2e38af76c2f34dcdfae9e5f6f2c4bd6c7e7dc0ec4567072
MD5 68c98e706235f9a07cefd9b1982f5cc7
BLAKE2b-256 b6d0ae7766f22d8a198e3ef5978d06af31f4cd8791e963dd359cb36e31ea05c1

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