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
--syncflag - ✅ 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
.graphqlsfiles, directories,.zip,.tar.gz, and.tgzarchives - 🔌 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 typesgenerated/enums.py— All GraphQL enumsgenerated/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 generationenums.py.j2— Enum generationscalars.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
- Parse — Reads GraphQL schema files using
graphql-core - Transform — Converts to an intermediate representation (IR)
- Generate — Renders Pydantic models and client code via Jinja2 templates
The generated client:
- Uses
httpxfor 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8fea654dfbebec4f4421325e189af7af59dae7f59d9b64dd10c19e1b85a27825
|
|
| MD5 |
76f6679cbf318d09f5020bab1439c488
|
|
| BLAKE2b-256 |
dff4fbf2b36da5a1c07d43ed3810cc253355126552df90f18eb76e1d4edfe843
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7ac729bbb34b509fed99479bfaeba3914e6b49a8e7fb5fab344e423b5fbe6e69
|
|
| MD5 |
ce2554c3674c18885e1d43368bfe94de
|
|
| BLAKE2b-256 |
4dbb6fb8cb7218fa5a01942ac7ed8603d5e6aa32cecdb7fd19f6e88751dbdf08
|