ariadne-codegen

repository·main·Indexed 19 days ago

https://github.com/mirumee/ariadne-codegen

A Python tool that generates a type-safe, Pydantic-based GraphQL client from schemas and operations. It provides autocompletion and static type checking for queries, mutations, and subscriptions, eliminating manual JSON parsing. Key features include support for WebSockets (graphql-transport-ws), multipart file uploads, custom scalars, and a plugin system for extending the generation lifecycle.

Tokens
27.2K
Snippets
89
Records
134
Agent score
58%

What's inside ariadne-codegen

  1. Overview of ariadne-codegen features

    main

    ariadne-codegen is a Python code generator that converts GraphQL schemas and operations into a fully typed, async (or sync) Python client built on Pydantic.

    Key capabilities include:

    • Fully typed models: Pydantic models for schema types, inputs, enums, fragments, and operation results.
    • Typed client methods: Each operation becomes a method with typed arguments and return values.
    • Async or sync: Generate an async client (default) or a synchronous one.
    • Subscriptions: Real-time updates via WebSockets (graphql-transport-ws).
    • File uploads: Support for multipart requests via the GraphQL multipart request spec.
    • Custom scalars: Map GraphQL scalars to Python types using serialize/parse hooks.
    • Extensible output: Inject mixins, copy files, or swap the base client (e.g., for custom auth or removing httpx/websockets dependencies).
    • Flexible schema sources: Load schemas from local files, installed Python packages, or remote introspection.
    • Plugin system: Customize generation via hooks.
    • Advanced features: Programmatic query building, OpenTelemetry tracing, multiple clients per project, and schema-copy mode.
  2. What is ariadne-codegen?

    main

    ariadne-codegen is a Python code generator that transforms a GraphQL schema and your defined operations into a fully typed, async (or sync) Python client built on Pydantic.

    Instead of manually writing query strings and parsing raw JSON, the tool generates:

    • Pydantic models for schema types, inputs, enums, fragments, and operation results.
    • Typed client methods for every query, mutation, and subscription, providing autocompletion and static type checking.
  3. Extend generated models with custom mixins

    main

    Use the @mixin directive in your GraphQL schema to extend generated classes with custom logic by adding extra base classes. This allows you to inject custom methods or properties into the generated models.

    The directive requires two string literal arguments:

    • from: The module path to import from.
    • import: The name of the parent class to inherit from.

    Important details:

    • You do not need to declare the @mixin directive in your schema definition; ariadne-codegen registers it automatically.
    • The directive only adds the import statement and the base class to the generated class; it does not copy any code. The imported module must be reachable in the runtime environment.
    • If using a relative import (e.g., from: ".mixins"), you must include the corresponding file in your configuration using the files_to_include option.
    • If using an absolute import (e.g., from: "my_package.mixins"), the module must be installable in your environment.
    • If arguments are missing or are not string literals, generation will fail with a ParsingError.
    fragment UserData on User @mixin(from: ".mixins", import: "UsersMixin") {
        id
    }
  4. How the Custom Operation Builder works

    main

    When enable_custom_operations is enabled, ariadne-codegen generates several specialized modules to facilitate programmatic query building:

    • custom_fields.py: Contains ...Fields classes for every object type in your schema, used to select specific fields.
    • custom_typing_fields.py: Provides supporting types for field selection.
    • custom_queries.py: Contains a Query class with builder methods for every root query field (if a Query type exists in the schema).
    • custom_mutations.py: Contains a Mutation class with builder methods for every root mutation field (if a Mutation type exists in the schema).

    To execute these built operations, the Client gains three new methods:

    • query(...)
    • mutation(...)
    • execute_custom_operation(...)

    Important Note on Return Types: Unlike the standard per-operation client methods which return parsed Pydantic models, client.query(...) and client.mutation(...) return the raw response as a dict[str, Any].

  5. Understand the structure of the generated GraphQL client package

    main

    When you run the code generator, it produces a Python package (e.g., graphql_client/) containing several specialized files. The package structure typically includes:

    • __init__.py: Contains re-imports and exports all generated classes for easy access.
    • client.py: The main Client class containing async methods for every query, mutation, and subscription.
    • async_base_client.py: The base class that the generated Client inherits from.
    • base_model.py: Contains the BaseModel (extending Pydantic) used by all generated models.
    • input_types.py: Models generated from GraphQL input types, used as arguments in client methods.
    • enums.py: Python Enum classes generated from GraphQL enums.
    • fragments.py: Classes generated from GraphQL fragments used in operations.
    • exceptions.py: Custom exception classes for error handling.
    • Operation-specific files (e.g., create_user.py, list_all_users.py): Each file contains the model corresponding to the return type of a specific operation. The filename is the snake_case version of the operation name.
  6. How the plugin execution model works

    main
    Plugins are instantiated once at the start of the ariadne-codegen command. They operate as a sequential pipeline: each plugin receives the output produced by the previous plugin in the sequence. This allows you to chain transformations, such as rewriting the schema in one plugin and then transforming the resulting source code in another.
  7. How hooks work in custom plugins

    main

    Hooks are methods provided by the base Plugin class (importable as ariadne_codegen.plugins.base.Plugin) that allow custom plugins to intercept and modify different stages of the code generation process.

    Every hook has a default implementation that returns its input unchanged, meaning a plugin only needs to override the specific hooks it intends to use. Hooks are executed in a specific order during a single ariadne-codegen run, ranging from schema loading to final file writing.

    from ariadne_codegen.plugins.base import Plugin
    
    class MyCustomPlugin(Plugin):
        def process_name(self, name: str, node=None) -> str:
            # Override only what you need
            return name.upper()
  8. Combine multiple schema files and Python package sources

    main

    Use schema_paths to build a single schema from multiple sources. Each entry is first treated as a local filesystem path; if not found, it is treated as a dotted Python import path.

    Supported entry types:

    • Local directory: All .graphql, .graphqls, and .gql files are included recursively.
    • Local file: A specific file to be used.
    • Python callable: An absolute import path to a callable that returns a list[str] of file paths.
    • Python variable (file): An absolute import path to a variable holding a single schema file path.
    • Python variable (directory): An absolute import path to a variable holding a directory path (all .graphql, .graphqls, and .gql files are included).

    Note: schema_path, schema_paths, and remote_schema_url are mutually exclusive; you can only use one of these settings at a time.

    [tool.ariadne-codegen]
    schema_paths = [
      "some_gql_commontypes.get_schema_files",   # callable -> returns list of paths
      "other_pkg.SCHEMA_DIR",                     # variable -> directory
      "./my_other_packages/",                     # local directory
      "./foo/bar.graphql",                        # local file
    ]
    queries_path = "queries.graphql"
  9. How file uploads work with Ariadne Code Generator

    main

    The default base clients (AsyncBaseClient or BaseClient) automatically detect file uploads. If any part of the variables dictionary passed to a mutation is an instance of the Upload class, the client switches from a standard JSON request to a multipart request following the GraphQL multipart request specification.

    This detection works recursively: if an Upload instance is found within a list or a nested input object, the entire operation is sent as a multipart request.

  10. Understand the ariadne-codegen versioning scheme

    main

    Until version 1.0.0, ariadne-codegen uses a custom versioning scheme because the API is not yet stable.

    • Minor version increases: Occur when there are breaking changes.
    • Patch version increases: Occurs for bug fixes, enhancements, and other non-breaking updates.

    Once the project reaches version 1.0.0, it will transition to standard Semantic Versioning (SemVer).

  11. How fragments and forward references work in generated code

    main

    When a GraphQL operation spreads a fragment, the generated Python class for that operation's result will subclass the fragment's class.

    Because Pydantic resolves inherited type annotations against the subclass's own module, any types referred to by forward references within that fragment must be importable in the operation's module. To ensure this, ariadne-codegen automatically emits imports for all fragment-related types in every generated client.

    Note that some imports may include # noqa: F401 because the names are only used within inherited annotations, which might otherwise trigger linter warnings for unused imports.

    # Example of generated code where the operation class subclasses a fragment class
    from .fragments import (
        ProductListItem,
        ProductListItemThumbnail,  # noqa: F401
    )
    
    class GetProductsProductsEdgesNode(ProductListItem):
        pass
  12. Optimize import performance and model building

    main

    For large schemas, use these settings to improve application startup time:

    • defer_model_build: (default false) Defers building Pydantic models until first use by setting defer_build=True and skipping eager model_rebuild() calls.
    • use_alias_generator: (default false) Sets alias_generator=to_camel on BaseModel to allow deriving aliases from Python names. Requires pydantic >= 2.8.
    • lazy_imports: (default false) Generates an __init__.py that performs lazy imports of modules when names are first used. This also enables the ClientForwardRefsPlugin to maintain type annotations without circular imports.