Skip to main content

iron_gql generates typed async GraphQL clients and runtime helpers from schemas for Python services

Project description

iron_gql

License main PyPI - Version

iron_gql is a lightweight GraphQL code generator and runtime that turns schema SDL and real query documents into typed Python clients powered by Pydantic models. Use it to wire GraphQL APIs into services, CLIs, background jobs, or tests without hand-writing boilerplate.

Installation

pip install iron-gql            # runtime only (httpx + pydantic)
pip install iron-gql[codegen]   # + graphql-core for code generation

Key Features

  • Query discovery. generate_gql_package scans your codebase for calls that look like <package>_gql("""..."""), validates each statement, and emits a module with strongly typed helpers.
  • Typed inputs and results. Generated Pydantic models mirror every selection set, enum, and input object referenced by the discovered queries.
  • Async runtime. runtime.GQLClient speaks to GraphQL endpoints over httpx and can shortcut network hops when pointed at an ASGI app.
  • Deterministic validation. graphql-core (codegen dependency) enforces schema compatibility and rejects duplicate operation names with incompatible bodies.

Package Layout

  • runtime.py – provides the async GQLClient, the reusable GQLOperation base class, and value serialization helpers.
  • codegen/generator.py – orchestrates query discovery, validation, and module rendering.
  • codegen/parser.py – converts GraphQL AST into typed helper structures consumed by the renderer.

Getting Started

  1. Describe your schema. Point generate_gql_package at an SDL file (schema.graphql). Include whichever root types you rely on (query, mutation, subscription).
  2. Author queries where they live. Import the future helper and wrap your GraphQL statement:
    from myapp.gql.client import client_gql
    
    get_user = client_gql(
        """
        query GetUser($id: ID!) {
            user(id: $id) {
                id
                name
            }
        }
        """
    )
    
    The generator infers the helper name (client_gql) from the package path you ask it to build.
  3. Generate the client module.
    from pathlib import Path
    
    from iron_gql.codegen import generate_gql_package
    
    generate_gql_package(
        schema_path=Path("schema.graphql"),
        package_full_name="myapp.gql.client",
        base_url_import="myapp.config:GRAPHQL_URL",
        scalars={"ID": "builtins:str"},
        to_camel_fn_full_name="myapp.inflection:to_camel",
        to_snake_fn=my_project_to_snake,
        debug_path=Path("iron_gql/debug/myapp.gql.client"),
        src_path=Path("."),
    )
    
    The call writes myapp/gql/client.py containing:
    • an async client singleton,
    • Pydantic result and input models,
    • a query class per operation with typed execute methods,
    • overloads for the helper function so editors can infer return types.
  4. Call your API.
    async def fetch_user(user_id: str):
        query = get_user.with_headers({"Authorization": "Bearer token"})
        result = await query.execute(id=user_id)
        return result.user
    

Custom Scalars

The generator maps GraphQL scalars to Python types in two layers:

Built-in scalars are mapped automatically:

GraphQL Python
String, Int, Float, Boolean str, int, float, bool
Date datetime.date
DateTime datetime.datetime
JSON object
Upload iron_gql.FileVar

Custom scalars are configured via the scalars parameter in "module:type" format:

generate_gql_package(
    ...,
    scalars={
        "ID": "builtins:str",
        "Money": "decimal:Decimal",
        "ULID": "ulid:ULID",
    },
)

Custom scalar types must be Pydantic-compatible — i.e. Pydantic should know how to parse them from JSON (deserialization) and serialize them to JSON. This works out of the box for standard library types (datetime, Decimal, UUID, Enum) and for any type that implements __get_pydantic_core_schema__. Unknown scalars fall back to object with a log warning.

Customization Hooks

  • Naming conventions. Supply to_camel_fn_full_name (module:path string) and a to_snake_fn callable to align casing with your own alias_generator.
  • Endpoint configuration. base_url_import is written verbatim into the generated module; point it at a global string, config object, or helper that returns the GraphQL endpoint.

Runtime Highlights

  • GQLClient accepts ASGI target_app so you can reuse the runtime for production HTTP calls or in-process ASGI execution.
  • GQLOperation.with_headers clones the operation object, making per-call customization trivial.
  • Upload scalars map to iron_gql.FileVar; multipart upload per the GraphQL multipart request spec is triggered automatically when variables contain FileVar instances.
  • serialize_var converts variables to JSON-friendly structures via Pydantic's TypeAdapter, supporting custom scalar types alongside nested models, dicts, and lists.

Example

The example/ directory contains a complete working setup: a GraphQL schema with queries, mutations, enums, interfaces, unions, and fragments, plus the generation script and sample query definitions. See example/generate.py for the codegen call and example/myapp/main.py for query usage.

Testing

Override the generated client via monkeypatch (or any other module attribute patching) to point queries at a test server or an ASGI app:

from iron_gql import runtime
from myapp.gql import api

async def test_get_user(monkeypatch):
    test_client = runtime.GQLClient(
        base_url="http://testserver",
        target_app=my_asgi_app,
    )
    monkeypatch.setattr(api, "API_CLIENT", test_client)

    result = await get_user.execute(id="1")
    assert result.user.name == "Alice"

The generated query classes resolve the client by module attribute name at call time, so replacing it is sufficient. The attribute is always named {PACKAGE}_CLIENT — for a package myapp.gql.api it is API_CLIENT.

Validation and Troubleshooting

  • Errors identify the file and line where the problematic statement lives.
  • Duplicate operation names must share identical bodies; rename or consolidate to resolve the conflict.

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

iron_gql-0.6.0.tar.gz (23.4 kB view details)

Uploaded Source

Built Distribution

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

iron_gql-0.6.0-py3-none-any.whl (29.2 kB view details)

Uploaded Python 3

File details

Details for the file iron_gql-0.6.0.tar.gz.

File metadata

  • Download URL: iron_gql-0.6.0.tar.gz
  • Upload date:
  • Size: 23.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for iron_gql-0.6.0.tar.gz
Algorithm Hash digest
SHA256 99ac202c95d1da3b9cae757d0e7e14288464df3d0c6e4b5df44c48e8590cc5c5
MD5 ab1e4df0453ea9d7c5a77f451df61bdc
BLAKE2b-256 5607733979ef6302d79ef66933eee813648f4ede8dd9b32b0d91494744cd3003

See more details on using hashes here.

File details

Details for the file iron_gql-0.6.0-py3-none-any.whl.

File metadata

  • Download URL: iron_gql-0.6.0-py3-none-any.whl
  • Upload date:
  • Size: 29.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for iron_gql-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e4b761bb130764c93b2e3c4a1169bdf3a12bdfa5353d3371bc5ad7707989ec00
MD5 e0841a7f961268cad6ad1f5d9ba293b5
BLAKE2b-256 046b583804358e27f64cd551db300b232549bbc40b55ee35c9ac893a78a854e4

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