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
Release files for gql-pygen 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| gql_pygen-0.2.0.tar.gz | 41.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| gql_pygen-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 81.2 kB
Release files / gql_pygen-0.2.0.tar.gz
| Download URL | gql_pygen-0.2.0.tar.gz |
|---|---|
| Size | 41.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8fea654dfbebec4f4421325e189af7af59dae7f59d9b64dd10c19e1b85a27825
|
|
BLAKE2b-256 checksum How to use checksums |
dff4fbf2b36da5a1c07d43ed3810cc253355126552df90f18eb76e1d4edfe843
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.3
|
Release files / gql_pygen-0.2.0-py3-none-any.whl
| Download URL | gql_pygen-0.2.0-py3-none-any.whl |
|---|---|
| Size | 40.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7ac729bbb34b509fed99479bfaeba3914e6b49a8e7fb5fab344e423b5fbe6e69
|
|
BLAKE2b-256 checksum How to use checksums |
4dbb6fb8cb7218fa5a01942ac7ed8603d5e6aa32cecdb7fd19f6e88751dbdf08
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.3
|