From 53c7667cfbf1a0d2026f5064a262282b3500cff2 Mon Sep 17 00:00:00 2001 From: Minister944 Date: Mon, 13 Jul 2026 18:36:53 +0200 Subject: [PATCH 1/4] docs: restructure documentation into Docusaurus site AR-8 Split the flat docs/ files into guides/reference/plugins/community sections with proper sidebar categories, add missing guide pages (schema sources, subscriptions, file uploads, custom scalars, extending types, multiple clients, OpenTelemetry, async vs sync, schema generation), and slim README.md down to a feature overview that links out to the new docs site instead of duplicating content. --- README.md | 547 ++---------------- docs/01-intro.md | 35 -- docs/01-introduction.md | 102 ++++ .../01-step-by-step-example.md} | 2 + docs/02-guides/02-schema-sources.md | 95 +++ docs/02-guides/03-using-generated-client.md | 119 ++++ docs/02-guides/04-subscriptions.md | 102 ++++ docs/02-guides/05-file-uploads.md | 89 +++ docs/02-guides/06-custom-scalars.md | 93 +++ docs/02-guides/07-extending-types.md | 97 ++++ docs/02-guides/08-multiple-clients.md | 13 + docs/02-guides/09-opentelemetry.md | 63 ++ docs/02-guides/10-async-vs-sync.md | 94 +++ docs/02-guides/11-schema-generation.md | 74 +++ .../12-custom-operation-builder.md} | 48 ++ docs/02-guides/_category_.yml | 2 + .../01-configuration.md} | 87 +-- .../02-generated-code-dependencies.md | 40 ++ docs/03-reference/_category_.yml | 2 + docs/03-using-generated-client.md | 81 --- docs/{06-plugins => 04-plugins}/01-intro.md | 0 .../02-custom-plugins.md | 0 docs/{06-plugins => 04-plugins}/03-hooks.md | 0 .../04-standard-plugins.md | 0 .../{06-plugins => 04-plugins}/_category_.yml | 0 docs/05-community/01-contributing.md | 20 + docs/05-community/02-versioning-policy.md | 20 + docs/05-community/_category_.yml | 2 + 28 files changed, 1152 insertions(+), 675 deletions(-) delete mode 100644 docs/01-intro.md create mode 100644 docs/01-introduction.md rename docs/{04-step-by-step-example.md => 02-guides/01-step-by-step-example.md} (99%) create mode 100644 docs/02-guides/02-schema-sources.md create mode 100644 docs/02-guides/03-using-generated-client.md create mode 100644 docs/02-guides/04-subscriptions.md create mode 100644 docs/02-guides/05-file-uploads.md create mode 100644 docs/02-guides/06-custom-scalars.md create mode 100644 docs/02-guides/07-extending-types.md create mode 100644 docs/02-guides/08-multiple-clients.md create mode 100644 docs/02-guides/09-opentelemetry.md create mode 100644 docs/02-guides/10-async-vs-sync.md create mode 100644 docs/02-guides/11-schema-generation.md rename docs/{05-custom-operation-builder.md => 02-guides/12-custom-operation-builder.md} (62%) create mode 100644 docs/02-guides/_category_.yml rename docs/{02-configuration.md => 03-reference/01-configuration.md} (59%) create mode 100644 docs/03-reference/02-generated-code-dependencies.md create mode 100644 docs/03-reference/_category_.yml delete mode 100644 docs/03-using-generated-client.md rename docs/{06-plugins => 04-plugins}/01-intro.md (100%) rename docs/{06-plugins => 04-plugins}/02-custom-plugins.md (100%) rename docs/{06-plugins => 04-plugins}/03-hooks.md (100%) rename docs/{06-plugins => 04-plugins}/04-standard-plugins.md (100%) rename docs/{06-plugins => 04-plugins}/_category_.yml (100%) create mode 100644 docs/05-community/01-contributing.md create mode 100644 docs/05-community/02-versioning-policy.md create mode 100644 docs/05-community/_category_.yml diff --git a/README.md b/README.md index cd523cf9..289960d1 100644 --- a/README.md +++ b/README.md @@ -4,555 +4,94 @@ [![Build Status](https://github.com/mirumee/ariadne-codegen/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/mirumee/ariadne-codegen/actions) -Python code generator that takes graphql schema, queries, mutations and subscriptions and generates Python package with fully typed and asynchronous GraphQL client. +Python code generator that turns a GraphQL schema and your operations into a fully typed, async (or sync) Python client built on Pydantic. You write your queries, mutations and subscriptions in GraphQL, and ariadne-codegen generates a typed Python method for each one, returning Pydantic models β€” so you get autocompletion and static type checking instead of hand-writing query strings and parsing raw JSON. -It's available as `ariadne-codegen` command and reads configuration from the `pyproject.toml` file: - -``` -ariadne-codegen -``` - -It can also be run as `python -m ariadne_codegen`. +πŸ“– **[Documentation](docs/01-introduction.md)** Β· [Step-by-step example](docs/02-guides/01-step-by-step-example.md) Β· [Configuration reference](docs/03-reference/01-configuration.md) ## Features -- Generate pydantic models from schema types, inputs and enums. -- Generate pydantic models for GraphQL results. -- Generate client package with each GraphQL operation available as async method. +- **Fully typed models** β€” Pydantic models for schema types, inputs, enums, fragments, and every operation's result. +- **Typed client methods** β€” each query, mutation, and subscription becomes a method with typed arguments and a typed return value. +- **[Async or sync](docs/02-guides/10-async-vs-sync.md)** β€” generate an async client (default) or a synchronous one. +- **[Subscriptions](docs/02-guides/04-subscriptions.md)** β€” real-time updates over WebSockets (`graphql-transport-ws`). +- **[File uploads](docs/02-guides/05-file-uploads.md)** β€” multipart requests via the GraphQL multipart request spec. +- **[Custom scalars](docs/02-guides/06-custom-scalars.md)** β€” map GraphQL scalars to your own Python types with `serialize`/`parse` hooks. +- **[Extensible output](docs/02-guides/07-extending-types.md)** β€” inject mixins into generated models, copy in your own files, or swap the [base client](docs/03-reference/02-generated-code-dependencies.md) (custom auth, or to drop the `httpx`/`websockets` deps). +- **[Flexible schema sources](docs/02-guides/02-schema-sources.md)** β€” a local file, installed Python packages, or remote introspection. +- **[Plugin system](docs/04-plugins/01-intro.md)** β€” customize generation through hooks, plus ready-made plugins (shorter results, extracted operation strings, forward refs, …). +- **More** β€” [programmatic query building](docs/02-guides/12-custom-operation-builder.md), [OpenTelemetry tracing](docs/02-guides/09-opentelemetry.md), [multiple clients per project](docs/02-guides/08-multiple-clients.md), and a [schema-copy mode](docs/02-guides/11-schema-generation.md). ## Installation -Ariadne Code Generator can be installed with pip: - ``` pip install ariadne-codegen ``` -To support subscriptions, default base client requires `websockets` package: +Add subscription (WebSocket) support with: ``` pip install ariadne-codegen[subscriptions] ``` -## Configuration +## Quickstart -`ariadne-codegen` reads configuration from `[tool.ariadne-codegen]` section in your `pyproject.toml`. You can use other configuration file with `--config` option, eg. `ariadne-codegen --config custom_file.toml` - -Minimal configuration for client generation: - -```toml -[tool.ariadne-codegen] -schema_path = "schema.graphql" -queries_path = "queries.graphql" -``` +Generate a typed client from three files. -Required settings: - -- `queries_path` - path to file/directory with queries (Can be optional if `enable_custom_operations` is used) - -Exactly one of the following 3 parameters is required. They are mutually exclusive - providing more than one raises a configuration error: - -- `schema_path` - path to file/directory with graphql schema -- `schema_paths` - list of schema sources; each entry can be a local path (file or directory) or a dotted Python attribute path (resolved at codegen time). See [Loading schema from installed packages](#loading-schema-from-installed-packages). -- `remote_schema_url` - url to graphql server, where introspection query can be perfomed - -Optional settings: - -- `remote_schema_headers` - extra headers that are passed along with introspection query, eg. `{"Authorization" = "Bearer token"}`. To include an environment variable in a header value, prefix the variable with `$`, eg. `{"Authorization" = "$AUTH_TOKEN"}` -- `remote_schema_verify_ssl` (defaults to `true`) - a flag that specifies wheter to verify ssl while introspecting remote schema -- `remote_schema_timeout` (defaults to `5`) - timeout in seconds while introspecting remote schema -- `remote_schema_http_client_path` - absolute import path to the HTTP client class used to introspect remote schema. If not provided, default `httpx` client is used. -- `target_package_name` (defaults to `"graphql_client"`) - name of generated package -- `target_package_path` (defaults to cwd) - path where to generate package -- `client_name` (defaults to `"Client"`) - name of generated client class -- `client_file_name` (defaults to `"client"`) - name of file with generated client class -- `base_client_name` (defaults to `"AsyncBaseClient"`) - name of base client class -- `base_client_file_path` (defaults to `.../ariadne_codegen/client_generators/dependencies/async_base_client.py`) - path to file where `base_client_name` is defined -- `enums_module_name` (defaults to `"enums"`) - name of file with generated enums models -- `input_types_module_name` (defaults to `"input_types"`) - name of file with generated input types models -- `fragments_module_name` (defaults to `"fragments"`) - name of file with generated fragments models -- `include_comments` (defaults to `"stable"`) - option which sets content of comments included at the top of every generated file. Valid choices are: `"none"` (no comments), `"timestamp"` (comment with generation timestamp), `"stable"` (comment contains a message that this is a generated file) -- `convert_to_snake_case` (defaults to `true`) - a flag that specifies whether to convert fields and arguments names to snake case -- `include_all_inputs` (defaults to `true`) - a flag specifying whether to include all inputs defined in the schema, or only those used in supplied operations -- `include_all_enums` (defaults to `true`) - a flag specifying whether to include all enums defined in the schema, or only those used in supplied operations -- `async_client` (defaults to `true`) - default generated client is `async`, change this to option `false` to generate synchronous client instead -- `opentelemetry_client` (defaults to `false`) - default base clients don't support any performance tracing. Change this option to `true` to use the base client with Open Telemetry support. -- `multipart_uploads` (defaults to `true`) - when set to `false`, a lighter base client variant is generated that omits multipart file upload support. -- `files_to_include` (defaults to `[]`) - list of files which will be copied into generated package -- `plugins` (defaults to `[]`) - list of plugins to use during generation -- `enable_custom_operations` (defaults to `false`) - enables building custom operations. Generates additional files that contains all the classes and methods for generation. - -These options control which fields are included in the GraphQL introspection query when using `remote_schema_url`. - -- `introspection_descriptions` (defaults to `false`) – include descriptions in the introspection result -- `introspection_input_value_deprecation` (defaults to `false`) – include deprecation information for input values -- `introspection_specified_by_url` (defaults to `false`) – include `specifiedByUrl` for custom scalars -- `introspection_schema_description` (defaults to `false`) – include schema description -- `introspection_directive_is_repeatable` (defaults to `false`) – include `isRepeatable` information for directives -- `introspection_input_object_one_of` (defaults to `false`) – include `oneOf` information for input objects - -## Loading schema from installed packages - -`schema_paths` lets you pull type definitions from installed Python packages alongside your local schema files, so codegen can resolve types that live in a shared library without copying them manually. - -Each entry in `schema_paths` must be one of the following: - -- **an absolute import path to a callable** that returns a `list[str]` of file paths, eg. `some_package.get_schema_files` -- **an absolute import path to a variable** holding the path to a single schema file, eg. `some_package.SCHEMA_FILE` -- **an absolute import path to a variable** holding the path to a directory β€” all `.graphql`, `.graphqls` and `.gql` files from it are included, eg. `some_package.SCHEMA_DIR` -- **a path to a directory** β€” all `.graphql`, `.graphqls` and `.gql` files from it are included, eg. `./my_schemas/` -- **a path to a specific file** to be used, eg. `./shared/types.graphql` +**1. Describe your schema** in `schema.graphql`: -```toml -[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" +```graphql +type Query { + hello(name: String!): String! +} ``` -`schema_path`, `schema_paths` and `remote_schema_url` are mutually exclusive - only one schema source may be used at a time. +**2. Write the operations you need** in `queries.graphql`: -## Custom operation builder - -The custom operation builder allows you to create complex GraphQL queries in a structured and intuitive way. - -### Example Code - -```python -import asyncio -from graphql_client import Client -from graphql_client.custom_fields import ( - ProductFields, - ProductTranslatableContentFields, - ProductTranslationFields, - TranslatableItemConnectionFields, - TranslatableItemEdgeFields, -) -from graphql_client.custom_queries import Query -from graphql_client.enums import LanguageCodeEnum, TranslatableKinds - - -async def get_products(): - # Create a client instance with the specified URL and headers - client = Client( - url="https://saleor.cloud/graphql/", - headers={"authorization": "bearer ..."}, - ) - - # Build the queries - product_query = Query.product(id="...", channel="channel-uk").fields( - ProductFields.id, - ProductFields.name, - ) - - translation_query = Query.translations(kind=TranslatableKinds.PRODUCT, first=10).fields( - TranslatableItemConnectionFields.edges().alias("aliased_edges").fields( - TranslatableItemEdgeFields.node.on( - "ProductTranslatableContent", - ProductTranslatableContentFields.id, - ProductTranslatableContentFields.product_id, - ProductTranslatableContentFields.name, - ) - ) - ) - - # Execute the queries with an operation name - response = await client.query( - product_query, - translation_query, - operation_name="get_products", - ) - - print(response) - -# Run the async function -asyncio.run(get_products()) +```graphql +query Greet($name: String!) { + hello(name: $name) +} ``` -### Explanation - -1. Building the Product Query: - 1. The Query.product(id="...", channel="channel-uk") initiates a query for a product with the specified ID and channel. - 2. .fields(ProductFields.id, ProductFields.name) specifies the fields to retrieve for the product: id and name. -2. Building the Translation Query: - 1. The Query.translations(kind=TranslatableKinds.PRODUCT, first=10) initiates a query for product translations. - 2. .fields(...) specifies the fields to retrieve for the translations. - 3. .alias("aliased_edges") renames the edges field to aliased_edges. - 4. .on("ProductTranslatableContent", ...) specifies the fields to retrieve if the node is of type ProductTranslatableContent: id, product_id, and name. -3. Executing the Queries: - 1. The client.query(...) method is called with the built queries and an operation name "get_products". - 2. This method sends the queries to the server and retrieves the response. - -### Example pyproject.toml configuration - -`Note: queries_path is optional when enable_custom_operations is set to true` +**3. Point `ariadne-codegen` at them** in `pyproject.toml`: ```toml [tool.ariadne-codegen] schema_path = "schema.graphql" -include_comments = "none" -target_package_name = "example_client" -enable_custom_operations = true -``` - -## Plugins - -Ariadne Codegen implements a plugin system that enables further customization and fine-tuning of generated Python code. It’s documentation is available separately in the [PLUGINS.md](https://github.com/mirumee/ariadne-codegen/blob/main/PLUGINS.md) file. - -### Standard plugins - -Ariadne Codegen ships with optional plugins importable from the `ariadne_codegen.contrib` package: - -- [`ariadne_codegen.contrib.shorter_results.ShorterResultsPlugin`](ariadne_codegen/contrib/shorter_results.py) - This plugin processes generated client methods for operations where only single top field is requested, so they return this field's value directly instead of operation's result type. For example get_user method generated for query `GetUser() { user(...) { ... }}` will return value of user field directly instead of `GetUserResult`. - -- [`ariadne_codegen.contrib.extract_operations.ExtractOperationsPlugin`](ariadne_codegen/contrib/extract_operations.py) - This extracts query strings from generated client's methods into separate `operations.py` module. It also modifies the generated client to import these definitions. Generated module name can be customized by adding `operations_module_name="custom_name"` to the `[tool.ariadne-codegen.operations]` section in config. Eg.: - - ```toml - [tool.ariadne-codegen] - ... - plugins = ["ariadne_codegen.contrib.extract_operations.ExtractOperationsPlugin"] - - [tool.ariadne-codegen.extract_operations] - operations_module_name = "custom_operations_module_name" - ``` - -- [`ariadne_codegen.contrib.client_forward_refs.ClientForwardRefsPlugin`](ariadne_codegen/contrib/client_forward_refs.py) - This plugin changes generated client module moving all Pydantic models imports under the `TYPE_CHECKING` condition, making them forward references. This greatly improves the import performance of the `client` module. - -- [`ariadne_codegen.contrib.no_reimports.NoReimportsPlugin`](ariadne_codegen/contrib/no_reimports.py) - This plugin removes content of generated `__init__.py`. This is useful in scenarios where generated plugins contain so many Pydantic models that client's eager initialization of entire package on first import is very slow. - -## Using generated client - -Generated client can be imported from package: - -```py -from {target_package_name}.{client_file_name} import {client_name} -``` - -Example with default settings: - -```py -from graphql_client.client import Client -``` - -### Passing headers to client - -Client (with default base client), takes passed headers and attaches them to every sent request. - -```py -client = Client("https://example.com/graphql", {"Authorization": "Bearer token"}) -``` - -### Using custom http client - -The default base class http client can be replaced with another client: - -```py -client = Client(http_client=CustomComplexHttpClient()) -``` - -`CustomComplexHttpClient` needs to fulfill the following protocol for async client: - -```py -class Response(Protocol): - status_code: int - - def json(self, **kwargs: Any) -> Any: ... - - -class HttpClient(Protocol): - async def post( - self, - url: Any | str, - json: Any | None = None, - data: Any | None = None, - files: Any | None = None, - headers: Any | None = None, - **kwargs: Any, - ) -> Response: ... - - async def aclose(self) -> None: ... -``` - -Protocol for sync client: - -```py -class Response(Protocol): - status_code: int - - def json(self, **kwargs: Any) -> Any: ... - - -class HttpClient(Protocol): - def post( - self, - url: Any | str, - json: Any | None = None, - data: Any | None = None, - files: Any | None = None, - headers: Any | None = None, - **kwargs: Any, - ) -> Response: ... - - def close(self) -> None: ... -``` - -The protocol for sync client is also fulfilled by some commonly known classes, like `requests.Session`. - -### Websockets - -To handle subscriptions, default `AsyncBaseClient` uses [websockets](https://github.com/python-websockets/websockets) and implements [graphql-transport-ws](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md) subprotocol. Arguments `ws_origin` and `ws_headers` are added as headers to the handshake request and `ws_connection_init_payload` is used as payload of [ConnectionInit](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md#connectioninit) message. - -### File upload - -Default base client (`AsyncBaseClient` or `BaseClient`) checks if any part of `variables` dictionary is an instance of `Upload`. If at least one instance is found then client sends multipart request according to [GraphQL multipart request specification](https://github.com/jaydenseric/graphql-multipart-request-spec). - -Class `Upload` is included in generated client and can be imported from it: - -```py -from {target_package_name} import Upload -``` - -By default we use this class to represent graphql scalar `Upload`. For schema with different name for this scalar, you can still use `Upload` and default client for file uploads: - -```toml -[tool.ariadne-codegen.scalars.OTHERSCALAR] -type = "Upload" -``` - -If your schema does not use file uploads, you can set `multipart_uploads = false` in your config to generate a lighter client that omits multipart handling entirely: - -```toml -[tool.ariadne-codegen] -multipart_uploads = false -``` - -### Open Telemetry - -When config option `opentelemetry_client` is set to `true` then default, included base client is replaced with one that implements the opt-in Open Telemetry support. By default this support does nothing but when the `opentelemetry-api` package is installed and the `tracer` argument is provided then the client will create spans with data about performed requests. - -Tracing arguments handled by `BaseClientOpenTelemetry`: - -- `tracer`: `Optional[Union[str, Tracer]] = None` - tracer object or name which will be passed to the `get_tracer` method -- `root_context`: `Optional[Context] = None` - optional context added to root span -- `root_span_name`: `str = "GraphQL Operation"` - name of root span - -`AsyncBaseClientOpenTelemetry` supports all arguments which `BaseClientOpenTelemetry` does, but also exposes additional arguments regarding websockets: - -- `ws_root_context`: `Optional[Context] = None` - optional context added to root span for websocket connection -- `ws_root_span_name`: `str = "GraphQL Subscription"` - name of root span for websocket connection - -## Custom scalars - -By default, not built-in scalars are represented as `typing.Any` in generated client. -You can provide information about specific scalar by adding section to `pyproject.toml`: - -```toml -[tool.ariadne-codegen.scalars.{graphql scalar name}] -type = "(required) python type name" -serialize = "function used to serialize scalar" -parse = "function used to create scalar instance from serialized form" -``` - -For each custom scalar client will use given `type` in all occurrences of `{graphql scalar name}`. If provided, `serialize` and `parse` will be used for serialization and deserialization. In result models `type` will be annotated with `BeforeValidator`, eg. `Annotated[type, BeforeValidator(parse)]`. In inputs annotation will use `PlainSerializer`, eg. `Annotated[type, PlainSerializer(serialize)]`. -If `type`/`serialize`/`parse` contains at least one `.` then string will be split by it's last occurrence. First part will be used as module to import from, and second part as type/method name. For example, `type = "custom_scalars.a.ScalarA"` will produce `from custom_scalars.a import ScalarA`. - -### Example with scalar mapped to built-in type - -In this case scalar is mapped to built-in `str` which doesn't require custom `serialize` and `parse` methods. - -```toml -[tool.ariadne-codegen.scalars.SCALARA] -type = "str" -``` - -### Example with type supported by pydantic - -In this scenario scalar is represented as `datetime`, so it needs to be imported. Pydantic handles serialization and deserialization so custom `parse` and `serialize` is not necessary. - -```toml -[tool.ariadne-codegen.scalars.DATETIME] -type = "datetime.datetime" -``` - -### Example with fully custom type - -In this example scalar is represented as class `TypeB`. Pydantic can't handle serialization and deserialization so custom `parse` and `serialize` is necessary. To provide `type`, `parse` and `serialize` implementation we can use `files_to_include` to copy `type_b.py` file. - -```toml -[tool.ariadne-codegen] -... -files_to_include = [".../type_b.py"] - -[tool.ariadne-codegen.scalars.SCALARB] -type = ".type_b.TypeB" -parse = ".type_b.parse_b" -serialize = ".type_b.serialize_b" -``` - -```py -# inputs.py - -class TestInput(BaseModel): - value_b: Annotated[TypeB, PlainSerializer(serialize_b)] -``` - -```py -# get_b.py - -class GetB(BaseModel): - query_b: Annotated[TypeB, BeforeValidator(parse_b)] -``` - -```py -# client.py - -class Client(AsyncBaseClient): - async def test_mutation(self, value: TypeB) -> TestMutation: - ... - variables: dict[str, object] = { - "value": serialize_b(value), - } - ... -``` - -## Extending generated types - -### Extending models with custom mixins - -`mixin` directive allows to extend class generated for query/mutation field with custom logic. -`mixin` takes two required arguments: - -- `from` - name of a module to import from -- `import` - name of a parent class - -Generated class will use `import` as extra base class, and import will be added to the file. - -```py -from {from} import {import} -... -class OperationNameField(BaseModel, {import}): - ... -``` - -This directive can be used along with `files_to_include` option to extend functionality of generated classes. - -#### Example of usage of `mixin` and `files_to_include` - -Query with `mixin` directive: - -```gql -query listUsers { - users @mixin(from: ".mixins", import: "UsersMixin") { - id - } -} -``` - -Part of `pyproject.toml` with `files_to_include` (`mixins.py` contains `UsersMixin` implementation) - -```toml -files_to_include = [".../mixins.py"] -``` - -Part of generated `list_users.py` file: - -```py -... -from .mixins import UsersMixin -... -class ListUsersUsers(BaseModel, UsersMixin): - ... +queries_path = "queries.graphql" ``` -## Multiple clients - -To generate multiple different clients you can store config for each in different file, then provide path to config file by `--config` option, eg. +Then generate the client: ``` -ariadne-codegen --config clientA.toml -ariadne-codegen --config clientB.toml +ariadne-codegen ``` -## Generated code dependencies - -Generated code requires: +This creates a `graphql_client` package. Use it: -- [pydantic](https://github.com/pydantic/pydantic) -- [httpx](https://github.com/encode/httpx) -- [websockets](https://github.com/python-websockets/websockets) (only for default async base client) - -Both `httpx` and `websockets` dependencies can be avoided by providing another base client class with `base_client_file_path` and `base_client_name` options. - -## Example +```python +import asyncio +from graphql_client import Client -Example with simple schema and few queries and mutations is available [here](https://github.com/mirumee/ariadne-codegen/blob/main/EXAMPLE.md). -## Generating a copy of GraphSQL schema +async def main(): + async with Client(url="https://example.com/graphql") as client: + result = await client.greet(name="World") + print(result.hello) -Instead of generating a client, you can generate a file with a copy of a GraphQL schema. To do this call `ariadne-codegen` with `graphqlschema` argument: +asyncio.run(main()) ``` -ariadne-codegen graphqlschema -``` - -`graphqlschema` mode reads configuration from the same place as [`client`](#configuration) but uses only `schema_path`, `schema_paths`, `remote_schema_url`, `remote_schema_headers`, `remote_schema_verify_ssl`, `remote_schema_timeout`, `remote_schema_http_client_path` options to retrieve the schema and `plugins` option to load plugins. - -In addition to the above, `graphqlschema` mode also accepts additional settings specific to it: - -### `target_file_path` - -A string with destination path for generated file. Must be either a Python (`.py`), or GraphQL (`.graphql` or `.gql`) file. - -Defaults to `schema.py`. - -Generated Python file will contain: - -- Necessary imports -- Type map declaration `{type_map_variable_name}: TypeMap = {...}` -- Schema declaration `{schema_variable_name}: GraphQLSchema = GraphQLSchema(...)` - -Generated GraphQL file will contain a formatted output of the `print_schema` function from the `graphql-core` package. - -### `schema_variable_name` - -A string with a name for schema variable, must be valid python identifier. -Defaults to `"schema"`. Used only if target is a Python file. +`greet` is a typed method generated from your `Greet` operation, and `result` is a +validated Pydantic model. See the [step-by-step example](docs/02-guides/01-step-by-step-example.md) +for a walk-through of everything that gets generated. -### `type_map_variable_name` - -A string with a name for type map variable, must be valid python identifier. - -Defaults to `"type_map"`. Used only if target is a Python file. - ---- - -## Versioning policy ## - -`ariadne-codegen` follows a custom versioning scheme where the minor version increases for breaking changes, while the patch version increments for bug fixes, enhancements, and other non-breaking updates. - -Since `ariadne-codegen` has not yet reached a stable API, this approach is in place until version 1.0.0. Once the API stabilizes, the project will adopt [Semantic Versioning](https://semver.org/).. - -## Development - -Formatting and linting use [ruff](https://github.com/astral-sh/ruff). We use [Hatch](https://github.com/pypa/hatch) for local tasks: - -- `hatch run lint` – lint (formatting check + typecheck) -- `hatch fmt` – auto-format code -- `hatch test` – tests with coverage (default Python 3.10) -- `hatch test -a -p` – tests across all supported Python versions -- `hatch run check` – full check (format, typecheck, tests) ## Contributing -We welcome all contributions to Ariadne! If you've found a bug or issue, feel free to use [GitHub issues](https://github.com/mirumee/ariadne-codegen/issues). If you have any questions or feedback, don't hesitate to catch us on [GitHub discussions](https://github.com/mirumee/ariadne/discussions/). - -For guidance and instructions, please see [CONTRIBUTING.md](CONTRIBUTING.md). +Contributions are welcome! See [Contributing](docs/05-community/01-contributing.md) for how to report bugs, work on issues, and open pull requests. Also make sure you follow [@AriadneGraphQL](https://twitter.com/AriadneGraphQL) on Twitter for latest updates, news and random musings! -## **Crafted with ❀️ by [Mirumee Software](http://mirumee.com)** +# Crafted with ❀️ by [Mirumee Labs](http://mirumee.com) diff --git a/docs/01-intro.md b/docs/01-intro.md deleted file mode 100644 index 30418384..00000000 --- a/docs/01-intro.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: Introduction ---- - -# Ariadne Client based on code generator - -Python code generator that takes graphql schema, queries, mutations and subscriptions and generates Python package with fully typed and asynchronous GraphQL client. - -It's available as `ariadne-codegen` command and reads configuration from the `pyproject.toml` file: - -``` -$ ariadne-codegen -``` - -It can also be run as `python -m ariadne_codegen`. - -## Features - -- Generate pydantic models from schema types, inputs and enums. -- Generate pydantic models for GraphQL results. -- Generate client package with each GraphQL operation available as async method. - -## Installation - -Ariadne Code Generator can be installed with pip: - -``` -$ pip install ariadne-codegen -``` - -To support subscriptions, default base client requires `websockets` package: - -``` -$ pip install ariadne-codegen[subscriptions] -``` diff --git a/docs/01-introduction.md b/docs/01-introduction.md new file mode 100644 index 00000000..b47e56ac --- /dev/null +++ b/docs/01-introduction.md @@ -0,0 +1,102 @@ +--- +title: Introduction +slug: / +--- + +# Ariadne Code Generator + +[![Ariadne](https://ariadnegraphql.org/img/logo-horizontal-sm.png)](https://ariadnegraphql.org) + +[![Build Status](https://github.com/mirumee/ariadne-codegen/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/mirumee/ariadne-codegen/actions) + +Python code generator that turns a GraphQL schema and your operations into a fully typed, async (or sync) Python client built on Pydantic. You write your queries, mutations and subscriptions in GraphQL, and ariadne-codegen generates a typed Python method for each one, returning Pydantic models β€” so you get autocompletion and static type checking instead of hand-writing query strings and parsing raw JSON. + +πŸ“– **[Documentation](docs/01-introduction.md)** Β· [Step-by-step example](docs/02-guides/01-step-by-step-example.md) Β· [Configuration reference](docs/03-reference/01-configuration.md) + +## Features + +- **Fully typed models** β€” Pydantic models for schema types, inputs, enums, fragments, and every operation's result. +- **Typed client methods** β€” each query, mutation, and subscription becomes a method with typed arguments and a typed return value. +- **[Async or sync](docs/02-guides/10-async-vs-sync.md)** β€” generate an async client (default) or a synchronous one. +- **[Subscriptions](docs/02-guides/04-subscriptions.md)** β€” real-time updates over WebSockets (`graphql-transport-ws`). +- **[File uploads](docs/02-guides/05-file-uploads.md)** β€” multipart requests via the GraphQL multipart request spec. +- **[Custom scalars](docs/02-guides/06-custom-scalars.md)** β€” map GraphQL scalars to your own Python types with `serialize`/`parse` hooks. +- **[Extensible output](docs/02-guides/07-extending-types.md)** β€” inject mixins into generated models, copy in your own files, or swap the [base client](docs/03-reference/02-generated-code-dependencies.md) (custom auth, or to drop the `httpx`/`websockets` deps). +- **[Flexible schema sources](docs/02-guides/02-schema-sources.md)** β€” a local file, installed Python packages, or remote introspection. +- **[Plugin system](docs/04-plugins/01-intro.md)** β€” customize generation through hooks, plus ready-made plugins (shorter results, extracted operation strings, forward refs, …). +- **More** β€” [programmatic query building](docs/02-guides/12-custom-operation-builder.md), [OpenTelemetry tracing](docs/02-guides/09-opentelemetry.md), [multiple clients per project](docs/02-guides/08-multiple-clients.md), and a [schema-copy mode](docs/02-guides/11-schema-generation.md). + +## Installation + +``` +pip install ariadne-codegen +``` + +Add subscription (WebSocket) support with: + +``` +pip install ariadne-codegen[subscriptions] +``` + +## Quickstart + +Generate a typed client from three files. + +**1. Describe your schema** in `schema.graphql`: + +```graphql +type Query { + hello(name: String!): String! +} +``` + +**2. Write the operations you need** in `queries.graphql`: + +```graphql +query Greet($name: String!) { + hello(name: $name) +} +``` + +**3. Point `ariadne-codegen` at them** in `pyproject.toml`: + +```toml +[tool.ariadne-codegen] +schema_path = "schema.graphql" +queries_path = "queries.graphql" +``` + +Then generate the client: + +``` +ariadne-codegen +``` + +This creates a `graphql_client` package. Use it: + +```python +import asyncio +from graphql_client import Client + + +async def main(): + async with Client(url="https://example.com/graphql") as client: + result = await client.greet(name="World") + print(result.hello) + + +asyncio.run(main()) +``` + +`greet` is a typed method generated from your `Greet` operation, and `result` is a +validated Pydantic model. See the [step-by-step example](docs/02-guides/01-step-by-step-example.md) +for a walk-through of everything that gets generated. + + +## Contributing + +Contributions are welcome! See [Contributing](docs/05-community/01-contributing.md) for how to report bugs, work on issues, and open pull requests. + +Also make sure you follow [@AriadneGraphQL](https://twitter.com/AriadneGraphQL) on Twitter for latest updates, news and random musings! + +# Crafted with ❀️ by [Mirumee Labs](http://mirumee.com) diff --git a/docs/04-step-by-step-example.md b/docs/02-guides/01-step-by-step-example.md similarity index 99% rename from docs/04-step-by-step-example.md rename to docs/02-guides/01-step-by-step-example.md index b0264fe1..7b8f9c4d 100644 --- a/docs/04-step-by-step-example.md +++ b/docs/02-guides/01-step-by-step-example.md @@ -12,6 +12,7 @@ We start with a GraphQL schema that defines queries, mutations, and subscription This schema includes `User` types, input objects for creating users and setting preferences, and a `Color` enum. It also shows how to use default values in input objects. ```graphql +# /schema.graphql schema { query: Query mutation: Mutation @@ -97,6 +98,7 @@ Next we define the operations we want to use: a mutation to create a user, queri Notice that we also define fragments (`BasicUser` and `UserPersonalData`) to reuse fields across queries. ```graphql +# /queries.graphql mutation CreateUser($userData: UserCreateInput!) { userCreate(userData: $userData) { id diff --git a/docs/02-guides/02-schema-sources.md b/docs/02-guides/02-schema-sources.md new file mode 100644 index 00000000..066d4a76 --- /dev/null +++ b/docs/02-guides/02-schema-sources.md @@ -0,0 +1,95 @@ +--- +title: Schema sources +--- + +# Schema sources + +`ariadne-codegen` needs a GraphQL schema to generate a client. You can provide it +from a local file/directory, from installed Python packages, or by introspecting a +remote GraphQL server. + +Exactly one of the following settings is required β€” `schema_path`, `schema_paths` +and `remote_schema_url` are **mutually exclusive**, so only one schema source may be +used at a time: + +- `schema_path` - path to file/directory with GraphQL schema +- `schema_paths` - list of local paths and/or installed-package sources to build the schema from +- `remote_schema_url` - url to GraphQL server, where introspection query can be performed + +## Local schema file + +Point `schema_path` at a `.graphql` file or a directory containing schema files: + +```toml +[tool.ariadne-codegen] +schema_path = "schema.graphql" +queries_path = "queries.graphql" +``` + +## Loading schema from installed packages + +`schema_paths` lets you pull type definitions from installed Python packages +alongside your local schema files, so codegen can resolve types that live in a +shared library without copying them manually. + +Each entry in `schema_paths` is first tried as a local filesystem path, and if it +is neither an existing file nor directory it is treated as a dotted Python import +path. An entry must be one of the following: + +- a path to a directory β€” all `.graphql`, `.graphqls` and `.gql` files from it are included (searched recursively), eg. `./my_schemas/` +- a path to a specific file to be used, eg. `./shared/types.graphql` +- an absolute import path to a callable that returns a `list[str]` of file paths, eg. `some_package.get_schema_files` +- an absolute import path to a variable holding the path to a single schema file, eg. `some_package.SCHEMA_FILE` +- an absolute import path to a variable holding the path to a directory β€” all `.graphql`, `.graphqls` and `.gql` files from it are included, eg. `some_package.SCHEMA_DIR` + +```toml +[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" +``` + +## Remote schema introspection + +Set `remote_schema_url` to a GraphQL endpoint and `ariadne-codegen` will run an +introspection query to retrieve the schema: + +```toml +[tool.ariadne-codegen] +remote_schema_url = "https://example.com/graphql/" +queries_path = "queries.graphql" +``` + +The following options control how the introspection request is performed: + +- `remote_schema_headers` - extra headers that are passed along with introspection query, eg. `{"Authorization" = "Bearer token"}`. To include an environment variable in a header value, prefix the variable with `$`, eg. `{"Authorization" = "$AUTH_TOKEN"}` +- `remote_schema_verify_ssl` (defaults to `true`) - a flag that specifies whether to verify ssl while introspecting remote schema +- `remote_schema_timeout` (defaults to `5`) - timeout in seconds while introspecting remote schema + +## Introspection query options + +These options only apply when the schema is fetched via `remote_schema_url`. They +map directly to the arguments of `graphql-core`'s +[`get_introspection_query()`](https://graphql-core-3.readthedocs.io/en/latest/modules/utilities.html#graphql.utilities.get_introspection_query) +(the config key `introspection_` becomes the `` argument), and they +control which optional fields the generated introspection query asks the server +for. They have no effect when using a local `schema_path` / `schema_paths`, because +a local SDL already contains everything. + +```toml +[tool.ariadne-codegen] +remote_schema_url = "https://example.com/graphql/" +queries_path = "queries.graphql" +introspection_descriptions = true +``` + +- `introspection_descriptions` (defaults to `false`) – include descriptions in the introspection result +- `introspection_input_value_deprecation` (defaults to `false`) – include deprecation information for input values +- `introspection_specified_by_url` (defaults to `false`) – include `specifiedByUrl` for custom scalars +- `introspection_schema_description` (defaults to `false`) – include schema description +- `introspection_directive_is_repeatable` (defaults to `false`) – include `isRepeatable` information for directives +- `introspection_input_object_one_of` (defaults to `false`) – include `oneOf` information for input objects diff --git a/docs/02-guides/03-using-generated-client.md b/docs/02-guides/03-using-generated-client.md new file mode 100644 index 00000000..3c2fca37 --- /dev/null +++ b/docs/02-guides/03-using-generated-client.md @@ -0,0 +1,119 @@ +--- +title: Using generated client +--- + +# Using generated client + +Generated client can be imported from package: + +```py +from {target_package_name}.{client_file_name} import {client_name} +``` + +Example with default settings: + +```py +from graphql_client.client import Client +``` + +### Passing headers to client + +Client (with default base client), takes passed headers and attaches them to every sent request. + +```py +client = Client("https://example.com/graphql", {"Authorization": "Bearer token"}) +``` + +For more complex scenarios, you can pass your own http client: + +```py +client = Client(http_client=CustomComplexHttpClient()) +``` + +`CustomComplexHttpClient` needs to be an instance of `httpx.AsyncClient` for async client, or `httpx.Client` for sync. + +## Client configuration + +The generated client inherits its constructor from the base client. With the default +async base client (`AsyncBaseClient`), all constructor arguments are optional: + +```py +client = Client( + url="https://example.com/graphql", + headers={"Authorization": "Bearer token"}, + http_client=None, # supply your own httpx.AsyncClient if needed + ws_url="wss://example.com/graphql", + ws_headers={"Authorization": "Bearer token"}, + ws_origin="https://example.com", + ws_connection_init_payload={"Authorization": "Bearer token"}, +) +``` + +- `url` (defaults to `""`) - HTTP endpoint the client sends operations to. +- `headers` (defaults to `None`) - headers attached to every HTTP request. When no `http_client` is supplied, they are also used to build the default `httpx.AsyncClient`. +- `http_client` (defaults to `None`) - your own HTTP client instance. Must be an `httpx.AsyncClient` for the async client, or `httpx.Client` for the sync client. When provided, `url`/`headers` handling for the transport is up to you. +- `ws_url` (defaults to `""`) - WebSocket endpoint used for subscriptions. +- `ws_headers` (defaults to `None`) - headers added to the WebSocket handshake request. +- `ws_origin` (defaults to `None`) - value of the `Origin` header for the WebSocket handshake. +- `ws_connection_init_payload` (defaults to `None`) - payload sent in the `ConnectionInit` message. + +The `ws_*` arguments only exist on the async base client (subscriptions are not +supported by the synchronous client). See [Subscriptions](./04-subscriptions.md) for +details. The synchronous base client (`async_client = false`) accepts only `url`, +`headers` and `http_client` β€” see [Async vs sync client](./10-async-vs-sync.md). + +If you enable the OpenTelemetry base client (`opentelemetry_client = true`), the +constructor also accepts tracing arguments β€” see [Open Telemetry](./09-opentelemetry.md). + +## Using the client as a context manager + +Both base clients support the context manager protocol, which closes the underlying +HTTP client on exit. Use `async with` for the async client: + +```py +async with Client(url="https://example.com/graphql") as client: + result = await client.list_all_users() +``` + +and `with` for the synchronous client: + +```py +with Client(url="https://example.com/graphql") as client: + result = client.list_all_users() +``` + +## Advanced HTTP configuration + +The base client is a thin wrapper around [httpx](https://www.python-httpx.org/). When +you don't supply an `http_client`, it builds a default `httpx.AsyncClient`/`httpx.Client` +passing only `headers` to it; `url` is stored separately and used as the target of each +request. Any HTTP behaviour beyond that (authentication, timeouts, proxies, HTTP/2, SSL +verification, cookies, connection limits, custom transports/retries, event hooks, +etc.) is configured on the httpx client itself. To use it, build your own httpx +client and pass it as `http_client`: + +```py +import httpx +from graphql_client.client import Client + +http_client = httpx.AsyncClient( + base_url="https://example.com/graphql", + auth=("user", "pass"), + timeout=httpx.Timeout(10.0), + http2=True, + verify=False, + proxy="http://localhost:8080", + limits=httpx.Limits(max_connections=100), + headers={"Authorization": "Bearer token"}, +) + +client = Client(http_client=http_client) +``` + +When you pass your own `http_client`, the client sends every request through it as-is. +The `headers` you would otherwise pass to `Client` are *not* merged into requests (they +only build the default client), so set them directly on the httpx client. Configure the +endpoint either by passing `url` to `Client` or by setting `base_url` on the httpx client +(as above). Refer to the +[httpx `Client` / `AsyncClient` API](https://www.python-httpx.org/api/#client) +for the full list of options. \ No newline at end of file diff --git a/docs/02-guides/04-subscriptions.md b/docs/02-guides/04-subscriptions.md new file mode 100644 index 00000000..137763b2 --- /dev/null +++ b/docs/02-guides/04-subscriptions.md @@ -0,0 +1,102 @@ +--- +title: Subscriptions +--- + +# Subscriptions + +To handle subscriptions, the default `AsyncBaseClient` uses +[websockets](https://github.com/python-websockets/websockets) and implements the +[graphql-transport-ws](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md) +subprotocol. + +## Installation + +Subscription support requires the `websockets` package, which is installed with the +`subscriptions` extra: + +``` +pip install ariadne-codegen[subscriptions] +``` + +Subscriptions are only available on the async client. The synchronous base client +(`async_client = false`) has no subscription support β€” see +[Async vs sync client](./10-async-vs-sync.md). + +## Connecting + +The WebSocket endpoint is separate from the HTTP `url` and is configured with +`ws_url`. Set it when constructing the client (the HTTP `url` is still used for +queries and mutations): + +```py +from graphql_client.client import Client + +client = Client( + url="https://example.com/graphql", + ws_url="wss://example.com/graphql", +) +``` + +## Consuming a subscription + +Generated subscription methods return an `AsyncIterator` of the result model and +yield a new value for every message the server pushes, so iterate over them with +`async for`: + +```py +async for result in client.get_users_counter(): + print(result.users_counter) +``` + +Here `get_users_counter` is the method generated from your `subscription` operation +(`subscription GetUsersCounter { usersCounter }`) and `result` is the typed response +model for that operation. + +## Full example + +Given a schema with a subscription field: + +```graphql +type Subscription { + usersCounter: Int! +} +``` + +and a subscription operation in your queries file: + +```graphql +subscription GetUsersCounter { + usersCounter +} +``` + +`ariadne-codegen` generates a `get_users_counter` method. Connect over WebSockets and +consume the stream with `async for`: + +```py +import asyncio +from graphql_client.client import Client + + +async def main(): + client = Client( + url="https://example.com/graphql", + ws_url="wss://example.com/graphql", + ) + async for result in client.get_users_counter(): + print("users online:", result.users_counter) + + +asyncio.run(main()) +``` + +Each server push yields a new `GetUsersCounter` instance; the loop runs until the +server closes the subscription (or you `break` out of it). + +## Connection arguments + +Arguments `ws_origin` and `ws_headers` are added as headers to the handshake +request, and `ws_connection_init_payload` is used as the payload of the +[ConnectionInit](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md#connectioninit) +message. See [Using generated client](./03-using-generated-client.md#client-configuration) +for the full list of constructor arguments. diff --git a/docs/02-guides/05-file-uploads.md b/docs/02-guides/05-file-uploads.md new file mode 100644 index 00000000..5e539b4b --- /dev/null +++ b/docs/02-guides/05-file-uploads.md @@ -0,0 +1,89 @@ +--- +title: File uploads +--- + +# File uploads + +The default base client (`AsyncBaseClient` or `BaseClient`) checks if any part of +the `variables` dictionary is an instance of `Upload`. If at least one instance is +found then the client sends a multipart request according to the +[GraphQL multipart request specification](https://github.com/jaydenseric/graphql-multipart-request-spec). + +The `Upload` class is included in the generated client and can be imported from it: + +```py +from {target_package_name} import Upload +``` + +By default this class is used to represent the GraphQL scalar `Upload`. For a schema +with a different name for this scalar, you can still use `Upload` and the default +client for file uploads: + +```toml +[tool.ariadne-codegen.scalars.OTHERSCALAR] +type = "Upload" +``` + +## Constructing an `Upload` + +`Upload` takes three required arguments: + +- `filename` (`str`) - name reported to the server for the file. +- `content` (a file-like object / `IOBase`, e.g. the result of `open(...)` or an `io.BytesIO`) - the file contents. It is streamed as-is, so open binary files in binary mode (`"rb"`). +- `content_type` (`str`) - MIME type of the file, e.g. `"image/png"`. + +```py +from graphql_client import Upload + +upload = Upload( + filename="avatar.png", + content=open("avatar.png", "rb"), + content_type="image/png", +) +``` + +## Full example + +Given a mutation that accepts the `Upload` scalar: + +```graphql +mutation uploadFile($file: Upload!) { + fileUpload(file: $file) +} +``` + +`ariadne-codegen` generates an `upload_file` method that takes a typed `Upload` +argument. Pass an `Upload` instance and the client automatically sends a multipart +request: + +```py +import asyncio +from graphql_client.client import Client +from graphql_client import Upload + + +async def main(): + client = Client(url="https://example.com/graphql") + with open("avatar.png", "rb") as f: + result = await client.upload_file( + file=Upload(filename="avatar.png", content=f, content_type="image/png") + ) + print(result) + + +asyncio.run(main()) +``` + +Opening the file inside a `with` block ensures it is closed after the request +completes. Any `Upload` found anywhere in the operation's variables triggers the +multipart request, so this also works for lists of files or nested input objects. + +## Disabling multipart uploads + +If your schema does not use file uploads, you can set `multipart_uploads = false` +in your config to generate a lighter client that omits multipart handling entirely: + +```toml +[tool.ariadne-codegen] +multipart_uploads = false +``` diff --git a/docs/02-guides/06-custom-scalars.md b/docs/02-guides/06-custom-scalars.md new file mode 100644 index 00000000..682dc9dc --- /dev/null +++ b/docs/02-guides/06-custom-scalars.md @@ -0,0 +1,93 @@ +--- +title: Custom scalars +--- + +# Custom scalars + +By default, non built-in scalars are represented as `typing.Any` in the generated +client. You can provide information about a specific scalar by adding a section to +`pyproject.toml`: + +```toml +[tool.ariadne-codegen.scalars.{graphql scalar name}] +type = "(required) python type name" +serialize = "function used to serialize scalar" +parse = "function used to create scalar instance from serialized form" +``` + +For each custom scalar the client will use the given `type` in all occurrences of +`{graphql scalar name}`. If provided, `serialize` and `parse` will be used for +serialization and deserialization. In result models `type` will be annotated with +`BeforeValidator`, eg. `Annotated[type, BeforeValidator(parse)]`. In inputs the +annotation will use `PlainSerializer`, eg. `Annotated[type, PlainSerializer(serialize)]`. + +If `type`/`serialize`/`parse` contains at least one `.` then the string will be +split by its last occurrence. The first part will be used as the module to import +from, and the second part as the type/method name. For example, +`type = "custom_scalars.a.ScalarA"` will produce +`from custom_scalars.a import ScalarA`. + +## Example with scalar mapped to built-in type + +In this case the scalar is mapped to the built-in `str` which doesn't require custom +`serialize` and `parse` methods. + +```toml +[tool.ariadne-codegen.scalars.SCALARA] +type = "str" +``` + +## Example with type supported by pydantic + +In this scenario the scalar is represented as `datetime`, so it needs to be imported. +Pydantic handles serialization and deserialization so custom `parse` and `serialize` +is not necessary. + +```toml +[tool.ariadne-codegen.scalars.DATETIME] +type = "datetime.datetime" +``` + +## Example with fully custom type + +In this example the scalar is represented as class `TypeB`. Pydantic can't handle +serialization and deserialization so custom `parse` and `serialize` is necessary. +To provide `type`, `parse` and `serialize` implementation we can use +`files_to_include` to copy the `type_b.py` file. + +```toml +[tool.ariadne-codegen] +... +files_to_include = [".../type_b.py"] + +[tool.ariadne-codegen.scalars.SCALARB] +type = ".type_b.TypeB" +parse = ".type_b.parse_b" +serialize = ".type_b.serialize_b" +``` + +```py +# inputs.py + +class TestInput(BaseModel): + value_b: Annotated[TypeB, PlainSerializer(serialize_b)] +``` + +```py +# get_b.py + +class GetB(BaseModel): + query_b: Annotated[TypeB, BeforeValidator(parse_b)] +``` + +```py +# client.py + +class Client(AsyncBaseClient): + async def test_mutation(self, value: TypeB) -> TestMutation: + ... + variables: dict[str, object] = { + "value": serialize_b(value), + } + ... +``` diff --git a/docs/02-guides/07-extending-types.md b/docs/02-guides/07-extending-types.md new file mode 100644 index 00000000..5c6b3dac --- /dev/null +++ b/docs/02-guides/07-extending-types.md @@ -0,0 +1,97 @@ +--- +title: Extending generated types +--- + +# Extending generated types + +## Extending models with custom mixins + +The `mixin` directive allows you to extend a generated class with custom logic. +`mixin` takes two required arguments: + +- `from` - name of a module to import from +- `import` - name of a parent class + +The generated class will use `import` as an extra base class, and the import will be +added to the file. + +```py +from {from} import {import} +... +class OperationNameField(BaseModel, {import}): + ... +``` + +You don't need to declare the directive in your schema β€” `ariadne-codegen` registers +it automatically before processing your operations, so you can use `@mixin` right +away. + +Both arguments must be string literals; if either `from` or `import` is missing (or +a non-string value is passed), generation fails with a `ParsingError`. + +This directive can be used along with the `files_to_include` option to extend the +functionality of generated classes. + +### Where `@mixin` can be used + +The directive is valid in two locations: + +- on a **field** β€” extends the model generated for that field's selection set. +- on a **fragment definition** β€” extends the model generated for that fragment. + +```gql +fragment UserData on User @mixin(from: ".mixins", import: "UsersMixin") { + id +} +``` + +### Applying multiple mixins + +`@mixin` is repeatable, so you can apply it several times to the same field or +fragment to add multiple base classes (they are added in the order they appear): + +```gql +query listUsers { + users + @mixin(from: ".mixins", import: "UsersMixin") + @mixin(from: ".mixins", import: "AuditMixin") { + id + } +} +``` + +```py +from .mixins import AuditMixin, UsersMixin +... +class ListUsersUsers(BaseModel, UsersMixin, AuditMixin): + ... +``` + +### Example of usage of `mixin` and `files_to_include` + +Query with `mixin` directive: + +```gql +query listUsers { + users @mixin(from: ".mixins", import: "UsersMixin") { + id + } +} +``` + +Part of `pyproject.toml` with `files_to_include` (`mixins.py` contains the +`UsersMixin` implementation): + +```toml +files_to_include = [".../mixins.py"] +``` + +Part of generated `list_users.py` file: + +```py +... +from .mixins import UsersMixin +... +class ListUsersUsers(BaseModel, UsersMixin): + ... +``` diff --git a/docs/02-guides/08-multiple-clients.md b/docs/02-guides/08-multiple-clients.md new file mode 100644 index 00000000..b1686d8c --- /dev/null +++ b/docs/02-guides/08-multiple-clients.md @@ -0,0 +1,13 @@ +--- +title: Multiple clients +--- + +# Multiple clients + +To generate multiple different clients you can store the config for each in a +different file, then provide the path to the config file with the `--config` option, eg. + +``` +ariadne-codegen --config clientA.toml +ariadne-codegen --config clientB.toml +``` diff --git a/docs/02-guides/09-opentelemetry.md b/docs/02-guides/09-opentelemetry.md new file mode 100644 index 00000000..cd49b113 --- /dev/null +++ b/docs/02-guides/09-opentelemetry.md @@ -0,0 +1,63 @@ +--- +title: Open Telemetry +--- + +# Open Telemetry + +When the config option `opentelemetry_client` is set to `true` then the default, +included base client is replaced with one that implements opt-in Open Telemetry +support. By default this support does nothing, but when the `opentelemetry-api` +package is installed and the `tracer` argument is provided then the client will +create spans with data about performed requests. + +## Enabling + +Turn it on in your config: + +```toml +[tool.ariadne-codegen] +opentelemetry_client = true +``` + +The exact base client that gets generated depends on this option together with +`async_client` and `multipart_uploads` β€” you get an async or sync variant, with or +without multipart upload support. Regardless of the variant, the tracing arguments +below work the same way. + +The `opentelemetry` import is optional: if the package is not installed the tracing +code is a no-op, so the generated client still works β€” it just doesn't produce spans. +To actually export spans, install and configure OpenTelemetry (`opentelemetry-api` +plus an SDK/exporter). + +## Usage + +Pass a `tracer` (an OpenTelemetry `Tracer`, or a string name that is resolved via +`get_tracer`) when constructing the client: + +```py +from opentelemetry import trace +from graphql_client.client import Client + +# A configured TracerProvider/exporter is assumed to be set up elsewhere. +tracer = trace.get_tracer("my-app") + +client = Client( + url="https://example.com/graphql", + tracer=tracer, # or tracer="my-app" + root_span_name="MyGraphQLCall", +) +``` + +## Tracing arguments + +Tracing arguments handled by `BaseClientOpenTelemetry`: + +- `tracer`: `Optional[Union[str, Tracer]] = None` - tracer object or name which will be passed to the `get_tracer` method +- `root_context`: `Optional[Context] = None` - optional context added to root span +- `root_span_name` - name of root span; defaults to `"GraphQL Operation"` + +`AsyncBaseClientOpenTelemetry` supports all arguments which `BaseClientOpenTelemetry` +does, but also exposes additional arguments regarding websockets: + +- `ws_root_context`: `Optional[Context] = None` - optional context added to root span for websocket connection +- `ws_root_span_name`: `str = "GraphQL Subscription"` - name of root span for websocket connection diff --git a/docs/02-guides/10-async-vs-sync.md b/docs/02-guides/10-async-vs-sync.md new file mode 100644 index 00000000..6260ff36 --- /dev/null +++ b/docs/02-guides/10-async-vs-sync.md @@ -0,0 +1,94 @@ +--- +title: Async vs sync client +--- + +# Async vs sync client + +By default the generated client is asynchronous β€” every operation is available as an +`async` method and the client inherits from `AsyncBaseClient`. + +To generate a synchronous client instead, set `async_client` to `false`: + +```toml +[tool.ariadne-codegen] +async_client = false +``` + +- `async_client` (defaults to `true`) - default generated client is `async`, change this option to `false` to generate a synchronous client instead + +## What changes + +| | Async (default) | Sync (`async_client = false`) | +| ---------------------- | ----------------------------------- | --------------------------------- | +| Base class | `AsyncBaseClient` | `BaseClient` | +| Operation methods | `async def` (must be `await`ed) | plain `def` | +| Context manager | `async with` | `with` | +| Custom `http_client` | `httpx.AsyncClient` | `httpx.Client` | +| Subscriptions | supported | **not supported** | + +The generated method bodies mirror this β€” the async client does +`response = await self.execute(...)`, the sync client does `response = self.execute(...)`: + +```py +# async +async def list_all_users(self, **kwargs: Any) -> ListAllUsers: + ... + response = await self.execute(query=query, ...) + data = self.get_data(response) + return ListAllUsers.model_validate(data) +``` + +```py +# sync +def list_all_users(self, **kwargs: Any) -> ListAllUsers: + ... + response = self.execute(query=query, ...) + data = self.get_data(response) + return ListAllUsers.model_validate(data) +``` + +## Subscriptions require the async client + +Subscriptions are only available on the async client. If your operations include a +`subscription` while `async_client = false`, generation fails with: + +``` +Subscriptions are only available when using async client. +``` + +See [Subscriptions](./04-subscriptions.md). + +## Passing a custom http client + +The choice of async vs sync also determines which `httpx` client you can supply when +passing your own http client: + +```py +client = Client(http_client=CustomComplexHttpClient()) +``` + +`CustomComplexHttpClient` needs to be an instance of `httpx.AsyncClient` for the +async client, or `httpx.Client` for the sync client. See +[Using generated client](./03-using-generated-client.md#advanced-http-configuration). + +## Calling the client + +```py +# async +import asyncio +from graphql_client.client import Client + +async def main(): + async with Client(url="https://example.com/graphql") as client: + users = await client.list_all_users() + +asyncio.run(main()) +``` + +```py +# sync +from graphql_client.client import Client + +with Client(url="https://example.com/graphql") as client: + users = client.list_all_users() +``` diff --git a/docs/02-guides/11-schema-generation.md b/docs/02-guides/11-schema-generation.md new file mode 100644 index 00000000..3a3a8464 --- /dev/null +++ b/docs/02-guides/11-schema-generation.md @@ -0,0 +1,74 @@ +--- +title: Schema generation +--- + +# Generating a copy of the GraphQL schema + +Instead of generating a client, you can generate a file with a copy of a GraphQL +schema. To do this call `ariadne-codegen` with the `graphqlschema` argument: + +``` +ariadne-codegen graphqlschema +``` + +`graphqlschema` mode reads configuration from the same place as the client, but uses +only the `schema_path`, `schema_paths`, `remote_schema_url`, `remote_schema_headers`, +`remote_schema_verify_ssl`, `remote_schema_timeout` options to retrieve the schema +(see [Schema sources](./02-schema-sources.md)) and the `plugins` option to load +plugins. + +Before writing the file, the loaded schema is passed through each plugin's +`process_schema` hook and then validated, so this mode can also be used to apply +plugin transformations to a schema or to fail early on an invalid one. + +In addition to the above, `graphqlschema` mode also accepts additional settings +specific to it: + +## `target_file_path` + +A string with the destination path for the generated file. Must be either a Python +(`.py`), or GraphQL (`.graphql` or `.gql`) file. + +Defaults to `schema.py`. + +A generated Python file will contain: + +- Necessary imports +- Type map declaration `{type_map_variable_name}: TypeMap = {...}` +- Schema declaration `{schema_variable_name}: GraphQLSchema = GraphQLSchema(...)` + +A generated GraphQL file will contain a formatted output of the `print_schema` +function from the `graphql-core` package. + +## `schema_variable_name` + +A string with a name for the schema variable, must be a valid python identifier. + +Defaults to `"schema"`. Used only if the target is a Python file. + +## `type_map_variable_name` + +A string with a name for the type map variable, must be a valid python identifier. + +Defaults to `"type_map"`. Used only if the target is a Python file. + +## Example + +The output format is chosen from the `target_file_path` extension. To emit a GraphQL +SDL file: + +```toml +[tool.ariadne-codegen] +remote_schema_url = "https://example.com/graphql/" +target_file_path = "schema.graphql" +``` + +These settings live in the same `[tool.ariadne-codegen]` section as the rest of the +configuration β€” there is no separate subsection for `graphqlschema` mode. + +``` +ariadne-codegen graphqlschema +``` + +Point `target_file_path` at a `.py` file instead to emit an importable Python module +with the `schema` / `type_map` variables described above. diff --git a/docs/05-custom-operation-builder.md b/docs/02-guides/12-custom-operation-builder.md similarity index 62% rename from docs/05-custom-operation-builder.md rename to docs/02-guides/12-custom-operation-builder.md index 163ac2f0..735d03dc 100644 --- a/docs/05-custom-operation-builder.md +++ b/docs/02-guides/12-custom-operation-builder.md @@ -6,6 +6,28 @@ title: Custom operation builder The custom operation builder allows you to create complex GraphQL queries in a structured and intuitive way. +## Enabling + +Set `enable_custom_operations = true` in your config. This mode generates additional +helper modules instead of (or alongside) the per-operation client methods, so +`queries_path` becomes optional: + +```toml +[tool.ariadne-codegen] +schema_path = "schema.graphql" +enable_custom_operations = true +``` + +With it enabled, the following modules are generated in your package: + +- `custom_fields.py` - a `…Fields` class per object type, used to select fields. +- `custom_typing_fields.py` - supporting types for field selection. +- `custom_queries.py` - a `Query` class with a builder method per root query field (generated only if the schema has a `Query` type). +- `custom_mutations.py` - a `Mutation` class with a builder method per root mutation field (generated only if the schema has a `Mutation` type). + +The client also gains `query(...)`, `mutation(...)` and `execute_custom_operation(...)` +methods for executing the built operations. + ## Example Code ```python @@ -73,6 +95,32 @@ asyncio.run(get_products()) 1. The client.query(...) method is called with the built queries and an operation name "get_products". 2. This method sends the queries to the server and retrieves the response. +Unlike the per-operation client methods, `client.query(...)` and +`client.mutation(...)` return the raw response as a `dict[str, Any]` β€” the result is +not parsed into a generated Pydantic model. + +## Building mutations + +Mutations are built the same way, using the generated `Mutation` class from +`custom_mutations` and executed with `client.mutation(...)`: + +```python +from graphql_client import Client +from graphql_client.custom_fields import ProductFields +from graphql_client.custom_mutations import Mutation + + +async def update_product(): + client = Client(url="https://saleor.cloud/graphql/") + + build = Mutation.product_update(id="...").fields( + ProductFields.id, + ProductFields.name, + ) + response = await client.mutation(build, operation_name="update_product") + print(response) +``` + ## Example pyproject.toml configuration. `Note: queries_path is optional when enable_custom_operations is set to true` diff --git a/docs/02-guides/_category_.yml b/docs/02-guides/_category_.yml new file mode 100644 index 00000000..f1646d8f --- /dev/null +++ b/docs/02-guides/_category_.yml @@ -0,0 +1,2 @@ +label: 'Guides' +collapsible: false diff --git a/docs/02-configuration.md b/docs/03-reference/01-configuration.md similarity index 59% rename from docs/02-configuration.md rename to docs/03-reference/01-configuration.md index a3ada0df..b42bef5a 100644 --- a/docs/02-configuration.md +++ b/docs/03-reference/01-configuration.md @@ -18,45 +18,25 @@ queries_path = "queries.graphql" - `queries_path` - path to file/directory with queries (Can be optional if `enable_custom_operations` is used) -Exactly one of the following 3 parameters is required. They are mutually exclusive - providing more than one raises a configuration error: +Exactly one of the following parameters is required β€” they are mutually exclusive, so only one schema source may be used at a time (see [Schema sources](../02-guides/02-schema-sources.md)): - `schema_path` - path to file/directory with graphql schema -- `schema_paths` - list of schema sources resolved at codegen time; each entry may be a local path (file or directory) or a dotted Python attribute path (`pkg.ATTR` or `pkg.callable`). See details below. +- `schema_paths` - list of local paths and/or installed-package sources to build the schema from - `remote_schema_url` - url to graphql server, where introspection query can be perfomed -### `schema_paths` entries - -Each entry in `schema_paths` must be one of the following: - -- **an absolute import path to a callable** that returns a `list[str]` of file paths, eg. `some_pkg.get_schema_files` -- **an absolute import path to a variable** holding the path to a single schema file, eg. `some_pkg.SCHEMA_FILE` -- **an absolute import path to a variable** holding the path to a directory - all `.graphql`, `.graphqls` and `.gql` files from it are included, eg. `some_pkg.SCHEMA_DIR` -- **a path to a directory** - all `.graphql`, `.graphqls` and `.gql` files from it are included, eg. `./schemas/` -- **a path to a specific file** to be used, eg. `./foo/bar.graphql` - -```toml -[tool.ariadne-codegen] -schema_paths = [ - "some_gql_commontypes.get_schema_files", - "other_pkg.SCHEMA_DIR", - "./my_other_packages/", - "./foo/bar.graphql", -] -queries_path = "queries.graphql" -``` - ## Optional settings: -- `remote_schema_headers` - extra headers that are passed along with introspection query, eg. `{"Authorization" = "Bearer: token"}`. To include an environment variable in a header value, prefix the variable with `$`, eg. `{"Authorization" = "$AUTH_TOKEN"}` +- `remote_schema_headers` - extra headers that are passed along with introspection query, eg. `{"Authorization" = "Bearer token"}`. To include an environment variable in a header value, prefix the variable with `$`, eg. `{"Authorization" = "$AUTH_TOKEN"}` - `remote_schema_verify_ssl` (defaults to `true`) - a flag that specifies wheter to verify ssl while introspecting remote schema - `remote_schema_timeout` (defaults to `5`) - timeout in seconds while introspecting remote schema -- `remote_schema_http_client_path` - absolute import path to the HTTP client class used to introspect remote schema. If not provided, default `httpx` client is used. +- `remote_schema_http_client_path` (defaults to `None`) - dotted import path to a custom HTTP client (a class/callable that returns one, or a ready client object) providing an `httpx`-compatible `post` interface, used only for the remote schema introspection request. If unset, the default `httpx` client is used. - `target_package_name` (defaults to `"graphql_client"`) - name of generated package - `target_package_path` (defaults to cwd) - path where to generate package - `client_name` (defaults to `"Client"`) - name of generated client class - `client_file_name` (defaults to `"client"`) - name of file with generated client class - `base_client_name` (defaults to `"AsyncBaseClient"`) - name of base client class - `base_client_file_path` (defaults to `.../ariadne_codegen/client_generators/dependencies/async_base_client.py`) - path to file where `base_client_name` is defined +- `base_client_module_name` (defaults to the file name of `base_client_file_path`) - name of the module the base client is copied to and imported from in the generated package - `enums_module_name` (defaults to `"enums"`) - name of file with generated enums models - `input_types_module_name` (defaults to `"input_types"`) - name of file with generated input types models - `fragments_module_name` (defaults to `"fragments"`) - name of file with generated fragments models @@ -70,8 +50,25 @@ queries_path = "queries.graphql" - `files_to_include` (defaults to `[]`) - list of files which will be copied into generated package - `plugins` (defaults to `[]`) - list of plugins to use during generation - `enable_custom_operations` (defaults to `false`) - enables building custom operations. Generates additional files that contains all the classes and methods for generation. +- `include_typename` (defaults to `true`) - a flag that specifies whether to include the `__typename` field in generated models +- `ignore_extra_fields` (defaults to `true`) - when `true`, generated models ignore extra fields returned by the server; set to `false` to add `extra="forbid"` to the base model so unexpected fields raise a validation error +- `default_optional_fields_to_none` (defaults to `false`) - when `true`, optional fields in generated models default to `None` instead of being required keyword arguments +- `skip_validation_rules` (defaults to `["NoUnusedFragments"]`) - list of [graphql-core validation rule](https://github.com/graphql-python/graphql-core) names to skip when validating operations against the schema + +### Scalars -These options control which fields are included in the GraphQL introspection query when using `remote_schema_url`. +Custom scalar mappings are configured in per-scalar subsections rather than a single key: + +```toml +[tool.ariadne-codegen.scalars.{graphql scalar name}] +type = "..." +``` + +See [Custom scalars](../02-guides/06-custom-scalars.md) for the full syntax. + +## Introspection query settings: + +These options control which fields are included in the GraphQL introspection query when using `remote_schema_url`. See [Schema sources](../02-guides/02-schema-sources.md) for more details. - `introspection_descriptions` (defaults to `false`) – include descriptions in the introspection result - `introspection_input_value_deprecation` (defaults to `false`) – include deprecation information for input values @@ -80,34 +77,14 @@ These options control which fields are included in the GraphQL introspection que - `introspection_directive_is_repeatable` (defaults to `false`) – include `isRepeatable` information for directives - `introspection_input_object_one_of` (defaults to `false`) – include `oneOf` information for input objects -## Base Client customization - -The `base_client_file_path` and `base_client_name` can be used to provide a custom base client implementation. It should implement the following protocol for async client: +## Related guides -```py -class AsyncBaseClient(Protocol): - async def execute( - self, - query: str, - operation_name: Optional[str] = None, - variables: Optional[dict[str, Any]] = None, - **kwargs: Any, - ) -> Response: ... +Several settings have dedicated guides that explain them in context: - def get_data(self, response: Response) -> dict[str, Any]: ... -``` - -Protocol for sync client: - -```py -class BaseClient(Protocol): - def execute( - self, - query: str, - operation_name: Optional[str] = None, - variables: Optional[dict[str, Any]] = None, - **kwargs: Any, - ) -> Response: ... - - def get_data(self, response: Response) -> dict[str, Any]: ... -``` +- [Schema sources](../02-guides/02-schema-sources.md) β€” `schema_path`, `remote_schema_url`, `remote_schema_*`, and `introspection_*` +- [File uploads](../02-guides/05-file-uploads.md) β€” `multipart_uploads` +- [Custom scalars](../02-guides/06-custom-scalars.md) β€” scalar sections and `files_to_include` +- [Async vs sync client](../02-guides/10-async-vs-sync.md) β€” `async_client` +- [Open Telemetry](../02-guides/09-opentelemetry.md) β€” `opentelemetry_client` +- [Plugins](../04-plugins/01-intro.md) β€” `plugins` +- [Custom operation builder](../02-guides/12-custom-operation-builder.md) β€” `enable_custom_operations` diff --git a/docs/03-reference/02-generated-code-dependencies.md b/docs/03-reference/02-generated-code-dependencies.md new file mode 100644 index 00000000..1ab79bb6 --- /dev/null +++ b/docs/03-reference/02-generated-code-dependencies.md @@ -0,0 +1,40 @@ +--- +title: Generated code dependencies +--- + +# Generated code dependencies + +Generated code requires: + +- [pydantic](https://github.com/pydantic/pydantic) +- [httpx](https://github.com/encode/httpx) +- [websockets](https://github.com/python-websockets/websockets) (only for the default async base client) +- [graphql-core](https://github.com/graphql-python/graphql-core) (only when `enable_custom_operations` is used) + +## When each dependency is required + +- **pydantic** is always required β€” all generated models (inputs, enums, results, + fragments) inherit from a pydantic `BaseModel`. +- **httpx** is required by the default base clients (`AsyncBaseClient` and + `BaseClient`), which use it to perform HTTP requests. +- **websockets** is required only by the default async base client + (`AsyncBaseClient`), and only when handling subscriptions over WebSockets. See + [Subscriptions](../02-guides/04-subscriptions.md). +- **graphql-core** is required only when the custom operation builder is enabled + (`enable_custom_operations = true`); the generated `client.py` and `base_operation.py` + then import GraphQL AST nodes from it. Regular generated clients do not import it. + See [Custom operation builder](../02-guides/12-custom-operation-builder.md). + +## Avoiding httpx and websockets + +Both the `httpx` and `websockets` dependencies can be avoided by providing another +base client class with the `base_client_file_path` and `base_client_name` options. +When you supply your own base client, `ariadne-codegen` copies it into the generated +package instead of the default one, so the generated code only depends on whatever +your base client imports. + +```toml +[tool.ariadne-codegen] +base_client_file_path = "path/to/custom_base_client.py" +base_client_name = "CustomBaseClient" +``` diff --git a/docs/03-reference/_category_.yml b/docs/03-reference/_category_.yml new file mode 100644 index 00000000..360f10f1 --- /dev/null +++ b/docs/03-reference/_category_.yml @@ -0,0 +1,2 @@ +label: 'Reference' +collapsible: false diff --git a/docs/03-using-generated-client.md b/docs/03-using-generated-client.md deleted file mode 100644 index fd7ffbeb..00000000 --- a/docs/03-using-generated-client.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -title: Using generated client ---- - -# Using generated client - -Generated client can be imported from package: - -```py -from {target_package_name}.{client_file_name} import {client_name} -``` - -Example with default settings: - -```py -from graphql_client.client import Client -``` - -### Passing headers to client - -Client (with default base client), takes passed headers and attaches them to every sent request. - -```py -client = Client("https://example.com/graphql", {"Authorization": "Bearer token"}) -``` - -### Using custom http client - -The default base class http client can be replaced with another client: - -```py -client = Client(http_client=CustomComplexHttpClient()) -``` - -`CustomComplexHttpClient` needs to fulfill the following protocol for async client: - -```py -class Response(Protocol): - status_code: int - - def json(self, **kwargs: Any) -> Any: ... - - -class HttpClient(Protocol): - async def post( - self, - url: Any | str, - json: Any | None = None, - data: Any | None = None, - files: Any | None = None, - headers: Any | None = None, - **kwargs: Any, - ) -> Response: ... - - async def aclose(self) -> None: ... -``` - -Protocol for sync client: - -```py -class Response(Protocol): - status_code: int - - def json(self, **kwargs: Any) -> Any: ... - - -class HttpClient(Protocol): - def post( - self, - url: Any | str, - json: Any | None = None, - data: Any | None = None, - files: Any | None = None, - headers: Any | None = None, - **kwargs: Any, - ) -> Response: ... - - def close(self) -> None: ... -``` - -The protocol for sync client is also fulfilled by some commonly known classes, like `requests.Session`. diff --git a/docs/06-plugins/01-intro.md b/docs/04-plugins/01-intro.md similarity index 100% rename from docs/06-plugins/01-intro.md rename to docs/04-plugins/01-intro.md diff --git a/docs/06-plugins/02-custom-plugins.md b/docs/04-plugins/02-custom-plugins.md similarity index 100% rename from docs/06-plugins/02-custom-plugins.md rename to docs/04-plugins/02-custom-plugins.md diff --git a/docs/06-plugins/03-hooks.md b/docs/04-plugins/03-hooks.md similarity index 100% rename from docs/06-plugins/03-hooks.md rename to docs/04-plugins/03-hooks.md diff --git a/docs/06-plugins/04-standard-plugins.md b/docs/04-plugins/04-standard-plugins.md similarity index 100% rename from docs/06-plugins/04-standard-plugins.md rename to docs/04-plugins/04-standard-plugins.md diff --git a/docs/06-plugins/_category_.yml b/docs/04-plugins/_category_.yml similarity index 100% rename from docs/06-plugins/_category_.yml rename to docs/04-plugins/_category_.yml diff --git a/docs/05-community/01-contributing.md b/docs/05-community/01-contributing.md new file mode 100644 index 00000000..a0640bc1 --- /dev/null +++ b/docs/05-community/01-contributing.md @@ -0,0 +1,20 @@ +--- +title: Contributing +--- + +# Contributing + +Ariadne Codegen is developed on GitHub, and the canonical contributor documents live +in the repository. This page just points to them so they stay in a single place: + +- [CONTRIBUTING.md](https://github.com/mirumee/ariadne-codegen/blob/main/CONTRIBUTING.md) β€” how to report bugs, work on issues, and open pull requests (also shown by GitHub on issues/PRs) +- [EXAMPLE.md](https://github.com/mirumee/ariadne-codegen/blob/main/EXAMPLE.md) β€” full example project referenced by the [step-by-step guide](../02-guides/01-step-by-step-example.md) +- [LICENSE](https://github.com/mirumee/ariadne-codegen/blob/main/LICENSE) β€” project license + +Local development tasks (formatting, linting, type checking, tests) are covered in +the [Development setup](https://github.com/mirumee/ariadne-codegen/blob/main/CONTRIBUTING.md#development-setup) +section of `CONTRIBUTING.md`. + +To report a bug, ask a question, or share feedback, use +[GitHub issues](https://github.com/mirumee/ariadne-codegen/issues) or +[GitHub discussions](https://github.com/mirumee/ariadne/discussions/). diff --git a/docs/05-community/02-versioning-policy.md b/docs/05-community/02-versioning-policy.md new file mode 100644 index 00000000..cc9e2551 --- /dev/null +++ b/docs/05-community/02-versioning-policy.md @@ -0,0 +1,20 @@ +--- +title: Versioning policy +--- + +# Versioning policy + +`ariadne-codegen` follows a custom versioning scheme where the minor version +increases for breaking changes, while the patch version increments for bug fixes, +enhancements, and other non-breaking updates. + +Since `ariadne-codegen` has not yet reached a stable API, this approach is in place +until version 1.0.0. Once the API stabilizes, the project will adopt +[Semantic Versioning](https://semver.org/). + +## Release history + +Per-version changes are published on the +[Releases](https://github.com/mirumee/ariadne-codegen/releases) page, and +unreleased changes (including any breaking changes) are tracked in +[CHANGELOG.md](https://github.com/mirumee/ariadne-codegen/blob/main/CHANGELOG.md). diff --git a/docs/05-community/_category_.yml b/docs/05-community/_category_.yml new file mode 100644 index 00000000..d2810dc0 --- /dev/null +++ b/docs/05-community/_category_.yml @@ -0,0 +1,2 @@ +label: 'Community' +collapsible: false From 33f951c31424e1207d4dd60da58f7712d8acc75f Mon Sep 17 00:00:00 2001 From: Minister944 Date: Mon, 13 Jul 2026 19:35:48 +0200 Subject: [PATCH 2/4] docs: fix broken doc links, typos, and stale content in Docusaurus migration Relative links inside docs/ still pointed at the old docs/... paths, em dashes rendered inconsistently, and a few pages were missing recently added settings (remote_schema_http_client_path, introspection_*, opentelemetry-api dependency). --- EXAMPLE.md | 3 +- LICENSE | 2 +- README.md | 24 +++++----- docs/01-introduction.md | 47 +++++++++++++------ docs/02-guides/01-step-by-step-example.md | 5 +- docs/02-guides/02-schema-sources.md | 11 +++-- docs/02-guides/03-using-generated-client.md | 4 +- docs/02-guides/04-subscriptions.md | 2 +- docs/02-guides/07-extending-types.md | 6 +-- docs/02-guides/09-opentelemetry.md | 4 +- docs/02-guides/10-async-vs-sync.md | 4 +- docs/02-guides/11-schema-generation.md | 8 ++-- docs/02-guides/12-custom-operation-builder.md | 2 +- docs/03-reference/01-configuration.md | 21 +++++---- .../02-generated-code-dependencies.md | 6 ++- docs/04-plugins/04-standard-plugins.md | 4 +- docs/05-community/01-contributing.md | 6 +-- 17 files changed, 92 insertions(+), 67 deletions(-) diff --git a/EXAMPLE.md b/EXAMPLE.md index 04c3131a..15dbbc35 100644 --- a/EXAMPLE.md +++ b/EXAMPLE.md @@ -156,7 +156,7 @@ $ ariadne-codegen Structure of generated package: ``` -grapql_client/ +graphql_client/ __init__.py async_base_client.py base_model.py @@ -169,7 +169,6 @@ grapql_client/ input_types.py list_all_users.py list_users_by_country.py - scalars.py upload_file.py ``` diff --git a/LICENSE b/LICENSE index 74b19090..ecf37548 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2025, Mirumee Labs +Copyright (c) 2026, Mirumee Labs All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/README.md b/README.md index 289960d1..77fb05d1 100644 --- a/README.md +++ b/README.md @@ -4,25 +4,27 @@ [![Build Status](https://github.com/mirumee/ariadne-codegen/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/mirumee/ariadne-codegen/actions) -Python code generator that turns a GraphQL schema and your operations into a fully typed, async (or sync) Python client built on Pydantic. You write your queries, mutations and subscriptions in GraphQL, and ariadne-codegen generates a typed Python method for each one, returning Pydantic models β€” so you get autocompletion and static type checking instead of hand-writing query strings and parsing raw JSON. +Python code generator that turns a GraphQL schema and your operations into a fully typed, async (or sync) Python client built on Pydantic. You write your queries, mutations and subscriptions in GraphQL, and ariadne-codegen generates a typed Python method for each one, returning Pydantic models - so you get autocompletion and static type checking instead of hand-writing query strings and parsing raw JSON. πŸ“– **[Documentation](docs/01-introduction.md)** Β· [Step-by-step example](docs/02-guides/01-step-by-step-example.md) Β· [Configuration reference](docs/03-reference/01-configuration.md) ## Features -- **Fully typed models** β€” Pydantic models for schema types, inputs, enums, fragments, and every operation's result. -- **Typed client methods** β€” each query, mutation, and subscription becomes a method with typed arguments and a typed return value. -- **[Async or sync](docs/02-guides/10-async-vs-sync.md)** β€” generate an async client (default) or a synchronous one. -- **[Subscriptions](docs/02-guides/04-subscriptions.md)** β€” real-time updates over WebSockets (`graphql-transport-ws`). -- **[File uploads](docs/02-guides/05-file-uploads.md)** β€” multipart requests via the GraphQL multipart request spec. -- **[Custom scalars](docs/02-guides/06-custom-scalars.md)** β€” map GraphQL scalars to your own Python types with `serialize`/`parse` hooks. -- **[Extensible output](docs/02-guides/07-extending-types.md)** β€” inject mixins into generated models, copy in your own files, or swap the [base client](docs/03-reference/02-generated-code-dependencies.md) (custom auth, or to drop the `httpx`/`websockets` deps). -- **[Flexible schema sources](docs/02-guides/02-schema-sources.md)** β€” a local file, installed Python packages, or remote introspection. -- **[Plugin system](docs/04-plugins/01-intro.md)** β€” customize generation through hooks, plus ready-made plugins (shorter results, extracted operation strings, forward refs, …). -- **More** β€” [programmatic query building](docs/02-guides/12-custom-operation-builder.md), [OpenTelemetry tracing](docs/02-guides/09-opentelemetry.md), [multiple clients per project](docs/02-guides/08-multiple-clients.md), and a [schema-copy mode](docs/02-guides/11-schema-generation.md). +- **Fully typed models** - Pydantic models for schema types, inputs, enums, fragments, and every operation's result. +- **Typed client methods** - each query, mutation, and subscription becomes a method with typed arguments and a typed return value. +- **[Async or sync](docs/02-guides/10-async-vs-sync.md)** - generate an async client (default) or a synchronous one. +- **[Subscriptions](docs/02-guides/04-subscriptions.md)** - real-time updates over WebSockets (`graphql-transport-ws`). +- **[File uploads](docs/02-guides/05-file-uploads.md)** - multipart requests via the GraphQL multipart request spec. +- **[Custom scalars](docs/02-guides/06-custom-scalars.md)** - map GraphQL scalars to your own Python types with `serialize`/`parse` hooks. +- **[Extensible output](docs/02-guides/07-extending-types.md)** - inject mixins into generated models, copy in your own files, or swap the [base client](docs/03-reference/02-generated-code-dependencies.md) (custom auth, or to drop the `httpx`/`websockets` deps). +- **[Flexible schema sources](docs/02-guides/02-schema-sources.md)** - a local file, installed Python packages, or remote introspection. +- **[Plugin system](docs/04-plugins/01-intro.md)** - customize generation through hooks, plus ready-made plugins (shorter results, extracted operation strings, forward refs, …). +- **More** - [programmatic query building](docs/02-guides/12-custom-operation-builder.md), [OpenTelemetry tracing](docs/02-guides/09-opentelemetry.md), [multiple clients per project](docs/02-guides/08-multiple-clients.md), and a [schema-copy mode](docs/02-guides/11-schema-generation.md). ## Installation +Requires Python 3.10 or newer. + ``` pip install ariadne-codegen ``` diff --git a/docs/01-introduction.md b/docs/01-introduction.md index b47e56ac..b6ba0bc1 100644 --- a/docs/01-introduction.md +++ b/docs/01-introduction.md @@ -9,22 +9,22 @@ slug: / [![Build Status](https://github.com/mirumee/ariadne-codegen/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/mirumee/ariadne-codegen/actions) -Python code generator that turns a GraphQL schema and your operations into a fully typed, async (or sync) Python client built on Pydantic. You write your queries, mutations and subscriptions in GraphQL, and ariadne-codegen generates a typed Python method for each one, returning Pydantic models β€” so you get autocompletion and static type checking instead of hand-writing query strings and parsing raw JSON. +Python code generator that turns a GraphQL schema and your operations into a fully typed, async (or sync) Python client built on Pydantic. You write your queries, mutations and subscriptions in GraphQL, and ariadne-codegen generates a typed Python method for each one, returning Pydantic models - so you get autocompletion and static type checking instead of hand-writing query strings and parsing raw JSON. -πŸ“– **[Documentation](docs/01-introduction.md)** Β· [Step-by-step example](docs/02-guides/01-step-by-step-example.md) Β· [Configuration reference](docs/03-reference/01-configuration.md) +πŸ“– **[Documentation](./01-introduction.md)** Β· [Step-by-step example](./02-guides/01-step-by-step-example.md) Β· [Configuration reference](./03-reference/01-configuration.md) ## Features -- **Fully typed models** β€” Pydantic models for schema types, inputs, enums, fragments, and every operation's result. -- **Typed client methods** β€” each query, mutation, and subscription becomes a method with typed arguments and a typed return value. -- **[Async or sync](docs/02-guides/10-async-vs-sync.md)** β€” generate an async client (default) or a synchronous one. -- **[Subscriptions](docs/02-guides/04-subscriptions.md)** β€” real-time updates over WebSockets (`graphql-transport-ws`). -- **[File uploads](docs/02-guides/05-file-uploads.md)** β€” multipart requests via the GraphQL multipart request spec. -- **[Custom scalars](docs/02-guides/06-custom-scalars.md)** β€” map GraphQL scalars to your own Python types with `serialize`/`parse` hooks. -- **[Extensible output](docs/02-guides/07-extending-types.md)** β€” inject mixins into generated models, copy in your own files, or swap the [base client](docs/03-reference/02-generated-code-dependencies.md) (custom auth, or to drop the `httpx`/`websockets` deps). -- **[Flexible schema sources](docs/02-guides/02-schema-sources.md)** β€” a local file, installed Python packages, or remote introspection. -- **[Plugin system](docs/04-plugins/01-intro.md)** β€” customize generation through hooks, plus ready-made plugins (shorter results, extracted operation strings, forward refs, …). -- **More** β€” [programmatic query building](docs/02-guides/12-custom-operation-builder.md), [OpenTelemetry tracing](docs/02-guides/09-opentelemetry.md), [multiple clients per project](docs/02-guides/08-multiple-clients.md), and a [schema-copy mode](docs/02-guides/11-schema-generation.md). +- **Fully typed models** - Pydantic models for schema types, inputs, enums, fragments, and every operation's result. +- **Typed client methods** - each query, mutation, and subscription becomes a method with typed arguments and a typed return value. +- **[Async or sync](./02-guides/10-async-vs-sync.md)** - generate an async client (default) or a synchronous one. +- **[Subscriptions](./02-guides/04-subscriptions.md)** - real-time updates over WebSockets (`graphql-transport-ws`). +- **[File uploads](./02-guides/05-file-uploads.md)** - multipart requests via the GraphQL multipart request spec. +- **[Custom scalars](./02-guides/06-custom-scalars.md)** - map GraphQL scalars to your own Python types with `serialize`/`parse` hooks. +- **[Extensible output](./02-guides/07-extending-types.md)** - inject mixins into generated models, copy in your own files, or swap the [base client](./03-reference/02-generated-code-dependencies.md) (custom auth, or to drop the `httpx`/`websockets` deps). +- **[Flexible schema sources](./02-guides/02-schema-sources.md)** - a local file, installed Python packages, or remote introspection. +- **[Plugin system](./04-plugins/01-intro.md)** - customize generation through hooks, plus ready-made plugins (shorter results, extracted operation strings, forward refs, …). +- **More** - [programmatic query building](./02-guides/12-custom-operation-builder.md), [OpenTelemetry tracing](./02-guides/09-opentelemetry.md), [multiple clients per project](./02-guides/08-multiple-clients.md), and a [schema-copy mode](./02-guides/11-schema-generation.md). ## Installation @@ -72,7 +72,24 @@ Then generate the client: ariadne-codegen ``` -This creates a `graphql_client` package. Use it: +This creates a Python package (named `graphql_client` by default) next to your +config, with the typed client and Pydantic models generated from your schema and +operations: + +``` +graphql_client/ + __init__.py + async_base_client.py # networking base client + base_model.py + client.py # the typed Client, with a greet() method + enums.py + exceptions.py + greet.py # result model for the Greet operation + input_types.py +``` + +You get one result module per operation plus shared modules for enums, input types, +fragments and scalars. Import the client and use it right away: ```python import asyncio @@ -89,13 +106,13 @@ asyncio.run(main()) ``` `greet` is a typed method generated from your `Greet` operation, and `result` is a -validated Pydantic model. See the [step-by-step example](docs/02-guides/01-step-by-step-example.md) +validated Pydantic model. See the [step-by-step example](./02-guides/01-step-by-step-example.md) for a walk-through of everything that gets generated. ## Contributing -Contributions are welcome! See [Contributing](docs/05-community/01-contributing.md) for how to report bugs, work on issues, and open pull requests. +Contributions are welcome! See [Contributing](./05-community/01-contributing.md) for how to report bugs, work on issues, and open pull requests. Also make sure you follow [@AriadneGraphQL](https://twitter.com/AriadneGraphQL) on Twitter for latest updates, news and random musings! diff --git a/docs/02-guides/01-step-by-step-example.md b/docs/02-guides/01-step-by-step-example.md index 7b8f9c4d..ea890f73 100644 --- a/docs/02-guides/01-step-by-step-example.md +++ b/docs/02-guides/01-step-by-step-example.md @@ -6,6 +6,8 @@ title: Step-by-step example This example shows how **ariadne-codegen** can take a GraphQL schema and a set of queries, generate a fully typed Python client, and produce ready-to-use models for queries, mutations, and subscriptions. +The full source for this example (schema, queries, and generated client) is available in [EXAMPLE.md](https://github.com/mirumee/ariadne-codegen/blob/main/EXAMPLE.md). + ## Schema file We start with a GraphQL schema that defines queries, mutations, and subscriptions. @@ -182,7 +184,6 @@ graphql_client/ input_types.py list_all_users.py list_users_by_country.py - scalars.py upload_file.py ``` @@ -202,7 +203,7 @@ class Client(AsyncBaseClient): async def upload_file(...): ... ``` -Each method executes the corresponding GraphQL operation and returns a typed Pydantic model. +Each method executes the corresponding GraphQL operation and returns a typed Pydantic model, except `get_users_counter`, which is a subscription method - it returns an `AsyncIterator` and yields a new model for every message. See [Subscriptions](04-subscriptions.md) for details. ### Base client diff --git a/docs/02-guides/02-schema-sources.md b/docs/02-guides/02-schema-sources.md index 066d4a76..65d0a682 100644 --- a/docs/02-guides/02-schema-sources.md +++ b/docs/02-guides/02-schema-sources.md @@ -8,7 +8,7 @@ title: Schema sources from a local file/directory, from installed Python packages, or by introspecting a remote GraphQL server. -Exactly one of the following settings is required β€” `schema_path`, `schema_paths` +Exactly one of the following settings is required - `schema_path`, `schema_paths` and `remote_schema_url` are **mutually exclusive**, so only one schema source may be used at a time: @@ -36,17 +36,17 @@ Each entry in `schema_paths` is first tried as a local filesystem path, and if i is neither an existing file nor directory it is treated as a dotted Python import path. An entry must be one of the following: -- a path to a directory β€” all `.graphql`, `.graphqls` and `.gql` files from it are included (searched recursively), eg. `./my_schemas/` +- a path to a directory - all `.graphql`, `.graphqls` and `.gql` files from it are included (searched recursively), eg. `./my_schemas/` - a path to a specific file to be used, eg. `./shared/types.graphql` - an absolute import path to a callable that returns a `list[str]` of file paths, eg. `some_package.get_schema_files` - an absolute import path to a variable holding the path to a single schema file, eg. `some_package.SCHEMA_FILE` -- an absolute import path to a variable holding the path to a directory β€” all `.graphql`, `.graphqls` and `.gql` files from it are included, eg. `some_package.SCHEMA_DIR` +- an absolute import path to a variable holding the path to a directory - all `.graphql`, `.graphqls` and `.gql` files from it are included, eg. `some_package.SCHEMA_DIR` ```toml [tool.ariadne-codegen] schema_paths = [ - "some_gql_commontypes.get_schema_files", # callable β†’ returns list of paths - "other_pkg.SCHEMA_DIR", # variable β†’ directory + "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 ] @@ -69,6 +69,7 @@ The following options control how the introspection request is performed: - `remote_schema_headers` - extra headers that are passed along with introspection query, eg. `{"Authorization" = "Bearer token"}`. To include an environment variable in a header value, prefix the variable with `$`, eg. `{"Authorization" = "$AUTH_TOKEN"}` - `remote_schema_verify_ssl` (defaults to `true`) - a flag that specifies whether to verify ssl while introspecting remote schema - `remote_schema_timeout` (defaults to `5`) - timeout in seconds while introspecting remote schema +- `remote_schema_http_client_path` (defaults to `None`) - dotted import path to a custom HTTP client used only for the introspection request, instead of the default `httpx` client ## Introspection query options diff --git a/docs/02-guides/03-using-generated-client.md b/docs/02-guides/03-using-generated-client.md index 3c2fca37..1d93cc67 100644 --- a/docs/02-guides/03-using-generated-client.md +++ b/docs/02-guides/03-using-generated-client.md @@ -60,10 +60,10 @@ client = Client( The `ws_*` arguments only exist on the async base client (subscriptions are not supported by the synchronous client). See [Subscriptions](./04-subscriptions.md) for details. The synchronous base client (`async_client = false`) accepts only `url`, -`headers` and `http_client` β€” see [Async vs sync client](./10-async-vs-sync.md). +`headers` and `http_client` - see [Async vs sync client](./10-async-vs-sync.md). If you enable the OpenTelemetry base client (`opentelemetry_client = true`), the -constructor also accepts tracing arguments β€” see [Open Telemetry](./09-opentelemetry.md). +constructor also accepts tracing arguments - see [Open Telemetry](./09-opentelemetry.md). ## Using the client as a context manager diff --git a/docs/02-guides/04-subscriptions.md b/docs/02-guides/04-subscriptions.md index 137763b2..4dbe690d 100644 --- a/docs/02-guides/04-subscriptions.md +++ b/docs/02-guides/04-subscriptions.md @@ -19,7 +19,7 @@ pip install ariadne-codegen[subscriptions] ``` Subscriptions are only available on the async client. The synchronous base client -(`async_client = false`) has no subscription support β€” see +(`async_client = false`) has no subscription support - see [Async vs sync client](./10-async-vs-sync.md). ## Connecting diff --git a/docs/02-guides/07-extending-types.md b/docs/02-guides/07-extending-types.md index 5c6b3dac..99757ce4 100644 --- a/docs/02-guides/07-extending-types.md +++ b/docs/02-guides/07-extending-types.md @@ -22,7 +22,7 @@ class OperationNameField(BaseModel, {import}): ... ``` -You don't need to declare the directive in your schema β€” `ariadne-codegen` registers +You don't need to declare the directive in your schema - `ariadne-codegen` registers it automatically before processing your operations, so you can use `@mixin` right away. @@ -36,8 +36,8 @@ functionality of generated classes. The directive is valid in two locations: -- on a **field** β€” extends the model generated for that field's selection set. -- on a **fragment definition** β€” extends the model generated for that fragment. +- on a **field** - extends the model generated for that field's selection set. +- on a **fragment definition** - extends the model generated for that fragment. ```gql fragment UserData on User @mixin(from: ".mixins", import: "UsersMixin") { diff --git a/docs/02-guides/09-opentelemetry.md b/docs/02-guides/09-opentelemetry.md index cd49b113..cb1e19f7 100644 --- a/docs/02-guides/09-opentelemetry.md +++ b/docs/02-guides/09-opentelemetry.md @@ -20,12 +20,12 @@ opentelemetry_client = true ``` The exact base client that gets generated depends on this option together with -`async_client` and `multipart_uploads` β€” you get an async or sync variant, with or +`async_client` and `multipart_uploads` - you get an async or sync variant, with or without multipart upload support. Regardless of the variant, the tracing arguments below work the same way. The `opentelemetry` import is optional: if the package is not installed the tracing -code is a no-op, so the generated client still works β€” it just doesn't produce spans. +code is a no-op, so the generated client still works - it just doesn't produce spans. To actually export spans, install and configure OpenTelemetry (`opentelemetry-api` plus an SDK/exporter). diff --git a/docs/02-guides/10-async-vs-sync.md b/docs/02-guides/10-async-vs-sync.md index 6260ff36..26f8bd25 100644 --- a/docs/02-guides/10-async-vs-sync.md +++ b/docs/02-guides/10-async-vs-sync.md @@ -4,7 +4,7 @@ title: Async vs sync client # Async vs sync client -By default the generated client is asynchronous β€” every operation is available as an +By default the generated client is asynchronous - every operation is available as an `async` method and the client inherits from `AsyncBaseClient`. To generate a synchronous client instead, set `async_client` to `false`: @@ -26,7 +26,7 @@ async_client = false | Custom `http_client` | `httpx.AsyncClient` | `httpx.Client` | | Subscriptions | supported | **not supported** | -The generated method bodies mirror this β€” the async client does +The generated method bodies mirror this - the async client does `response = await self.execute(...)`, the sync client does `response = self.execute(...)`: ```py diff --git a/docs/02-guides/11-schema-generation.md b/docs/02-guides/11-schema-generation.md index 3a3a8464..db958919 100644 --- a/docs/02-guides/11-schema-generation.md +++ b/docs/02-guides/11-schema-generation.md @@ -13,9 +13,9 @@ ariadne-codegen graphqlschema `graphqlschema` mode reads configuration from the same place as the client, but uses only the `schema_path`, `schema_paths`, `remote_schema_url`, `remote_schema_headers`, -`remote_schema_verify_ssl`, `remote_schema_timeout` options to retrieve the schema -(see [Schema sources](./02-schema-sources.md)) and the `plugins` option to load -plugins. +`remote_schema_verify_ssl`, `remote_schema_timeout`, `remote_schema_http_client_path`, +and `introspection_*` options to retrieve the schema (see +[Schema sources](./02-schema-sources.md)) and the `plugins` option to load plugins. Before writing the file, the loaded schema is passed through each plugin's `process_schema` hook and then validated, so this mode can also be used to apply @@ -64,7 +64,7 @@ target_file_path = "schema.graphql" ``` These settings live in the same `[tool.ariadne-codegen]` section as the rest of the -configuration β€” there is no separate subsection for `graphqlschema` mode. +configuration - there is no separate subsection for `graphqlschema` mode. ``` ariadne-codegen graphqlschema diff --git a/docs/02-guides/12-custom-operation-builder.md b/docs/02-guides/12-custom-operation-builder.md index 735d03dc..61215200 100644 --- a/docs/02-guides/12-custom-operation-builder.md +++ b/docs/02-guides/12-custom-operation-builder.md @@ -96,7 +96,7 @@ asyncio.run(get_products()) 2. This method sends the queries to the server and retrieves the response. Unlike the per-operation client methods, `client.query(...)` and -`client.mutation(...)` return the raw response as a `dict[str, Any]` β€” the result is +`client.mutation(...)` return the raw response as a `dict[str, Any]` - the result is not parsed into a generated Pydantic model. ## Building mutations diff --git a/docs/03-reference/01-configuration.md b/docs/03-reference/01-configuration.md index b42bef5a..d3f2457a 100644 --- a/docs/03-reference/01-configuration.md +++ b/docs/03-reference/01-configuration.md @@ -18,16 +18,16 @@ queries_path = "queries.graphql" - `queries_path` - path to file/directory with queries (Can be optional if `enable_custom_operations` is used) -Exactly one of the following parameters is required β€” they are mutually exclusive, so only one schema source may be used at a time (see [Schema sources](../02-guides/02-schema-sources.md)): +Exactly one of the following parameters is required - they are mutually exclusive, so only one schema source may be used at a time (see [Schema sources](../02-guides/02-schema-sources.md)): - `schema_path` - path to file/directory with graphql schema - `schema_paths` - list of local paths and/or installed-package sources to build the schema from -- `remote_schema_url` - url to graphql server, where introspection query can be perfomed +- `remote_schema_url` - url to graphql server, where introspection query can be performed ## Optional settings: - `remote_schema_headers` - extra headers that are passed along with introspection query, eg. `{"Authorization" = "Bearer token"}`. To include an environment variable in a header value, prefix the variable with `$`, eg. `{"Authorization" = "$AUTH_TOKEN"}` -- `remote_schema_verify_ssl` (defaults to `true`) - a flag that specifies wheter to verify ssl while introspecting remote schema +- `remote_schema_verify_ssl` (defaults to `true`) - a flag that specifies whether to verify ssl while introspecting remote schema - `remote_schema_timeout` (defaults to `5`) - timeout in seconds while introspecting remote schema - `remote_schema_http_client_path` (defaults to `None`) - dotted import path to a custom HTTP client (a class/callable that returns one, or a ready client object) providing an `httpx`-compatible `post` interface, used only for the remote schema introspection request. If unset, the default `httpx` client is used. - `target_package_name` (defaults to `"graphql_client"`) - name of generated package @@ -81,10 +81,11 @@ These options control which fields are included in the GraphQL introspection que Several settings have dedicated guides that explain them in context: -- [Schema sources](../02-guides/02-schema-sources.md) β€” `schema_path`, `remote_schema_url`, `remote_schema_*`, and `introspection_*` -- [File uploads](../02-guides/05-file-uploads.md) β€” `multipart_uploads` -- [Custom scalars](../02-guides/06-custom-scalars.md) β€” scalar sections and `files_to_include` -- [Async vs sync client](../02-guides/10-async-vs-sync.md) β€” `async_client` -- [Open Telemetry](../02-guides/09-opentelemetry.md) β€” `opentelemetry_client` -- [Plugins](../04-plugins/01-intro.md) β€” `plugins` -- [Custom operation builder](../02-guides/12-custom-operation-builder.md) β€” `enable_custom_operations` +- [Schema sources](../02-guides/02-schema-sources.md) - `schema_path`, `remote_schema_url`, `remote_schema_*`, and `introspection_*` +- [Schema generation](../02-guides/11-schema-generation.md) - `graphqlschema` mode and its settings +- [File uploads](../02-guides/05-file-uploads.md) - `multipart_uploads` +- [Custom scalars](../02-guides/06-custom-scalars.md) - scalar sections and `files_to_include` +- [Async vs sync client](../02-guides/10-async-vs-sync.md) - `async_client` +- [Open Telemetry](../02-guides/09-opentelemetry.md) - `opentelemetry_client` +- [Plugins](../04-plugins/01-intro.md) - `plugins` +- [Custom operation builder](../02-guides/12-custom-operation-builder.md) - `enable_custom_operations` diff --git a/docs/03-reference/02-generated-code-dependencies.md b/docs/03-reference/02-generated-code-dependencies.md index 1ab79bb6..09913578 100644 --- a/docs/03-reference/02-generated-code-dependencies.md +++ b/docs/03-reference/02-generated-code-dependencies.md @@ -10,10 +10,11 @@ Generated code requires: - [httpx](https://github.com/encode/httpx) - [websockets](https://github.com/python-websockets/websockets) (only for the default async base client) - [graphql-core](https://github.com/graphql-python/graphql-core) (only when `enable_custom_operations` is used) +- [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python) (only when `opentelemetry_client` is enabled) ## When each dependency is required -- **pydantic** is always required β€” all generated models (inputs, enums, results, +- **pydantic** is always required - all generated models (inputs, enums, results, fragments) inherit from a pydantic `BaseModel`. - **httpx** is required by the default base clients (`AsyncBaseClient` and `BaseClient`), which use it to perform HTTP requests. @@ -24,6 +25,9 @@ Generated code requires: (`enable_custom_operations = true`); the generated `client.py` and `base_operation.py` then import GraphQL AST nodes from it. Regular generated clients do not import it. See [Custom operation builder](../02-guides/12-custom-operation-builder.md). +- **opentelemetry-api** is required only when `opentelemetry_client = true`, which + swaps in an OpenTelemetry-instrumented base client that imports `opentelemetry.trace` + and `opentelemetry.context`. See [OpenTelemetry](../02-guides/09-opentelemetry.md). ## Avoiding httpx and websockets diff --git a/docs/04-plugins/04-standard-plugins.md b/docs/04-plugins/04-standard-plugins.md index 62e93f4d..a19aeb1a 100644 --- a/docs/04-plugins/04-standard-plugins.md +++ b/docs/04-plugins/04-standard-plugins.md @@ -6,9 +6,9 @@ title: Standard plugins Ariadne Codegen ships with optional plugins importable from the `ariadne_codegen.contrib` package: -- [`ariadne_codegen.contrib.shorter_results.ShorterResultsPlugin`](https://github.com/mirumee/ariadne-codegen/blob/main/ariadne_codegen/contrib/shorter_results.py#L61) - This plugin processes generated client methods for operations where only single top field is requested, so they return this field's value directly instead of operation's result type. For example get_user method generated for query `GetUser() { user(...) { ... }}` will return value of user field directly instead of `GetUserResult`. +- [`ariadne_codegen.contrib.shorter_results.ShorterResultsPlugin`](https://github.com/mirumee/ariadne-codegen/blob/main/ariadne_codegen/contrib/shorter_results.py#L62) - This plugin processes generated client methods for operations where only single top field is requested, so they return this field's value directly instead of operation's result type. For example get_user method generated for query `GetUser() { user(...) { ... }}` will return value of user field directly instead of `GetUserResult`. -- [`ariadne_codegen.contrib.extract_operations.ExtractOperationsPlugin`](https://github.com/mirumee/ariadne-codegen/blob/main/ariadne_codegen/contrib/extract_operations.py#L29) - This extracts query strings from generated client's methods into separate `operations.py` module. It also modifies the generated client to import these definitions. Generated module name can be customized by adding `operations_module_name="custom_name"` to the `[tool.ariadne-codegen.extract-operations]` section in config. Eg.: +- [`ariadne_codegen.contrib.extract_operations.ExtractOperationsPlugin`](https://github.com/mirumee/ariadne-codegen/blob/main/ariadne_codegen/contrib/extract_operations.py#L31) - This extracts query strings from generated client's methods into separate `operations.py` module. It also modifies the generated client to import these definitions. Generated module name can be customized by adding `operations_module_name="custom_name"` to the `[tool.ariadne-codegen.extract-operations]` section in config. Eg.: ```toml [tool.ariadne-codegen] diff --git a/docs/05-community/01-contributing.md b/docs/05-community/01-contributing.md index a0640bc1..2c38c249 100644 --- a/docs/05-community/01-contributing.md +++ b/docs/05-community/01-contributing.md @@ -7,9 +7,9 @@ title: Contributing Ariadne Codegen is developed on GitHub, and the canonical contributor documents live in the repository. This page just points to them so they stay in a single place: -- [CONTRIBUTING.md](https://github.com/mirumee/ariadne-codegen/blob/main/CONTRIBUTING.md) β€” how to report bugs, work on issues, and open pull requests (also shown by GitHub on issues/PRs) -- [EXAMPLE.md](https://github.com/mirumee/ariadne-codegen/blob/main/EXAMPLE.md) β€” full example project referenced by the [step-by-step guide](../02-guides/01-step-by-step-example.md) -- [LICENSE](https://github.com/mirumee/ariadne-codegen/blob/main/LICENSE) β€” project license +- [CONTRIBUTING.md](https://github.com/mirumee/ariadne-codegen/blob/main/CONTRIBUTING.md) - how to report bugs, work on issues, and open pull requests (also shown by GitHub on issues/PRs) +- [EXAMPLE.md](https://github.com/mirumee/ariadne-codegen/blob/main/EXAMPLE.md) - full example project referenced by the [step-by-step guide](../02-guides/01-step-by-step-example.md) +- [LICENSE](https://github.com/mirumee/ariadne-codegen/blob/main/LICENSE) - project license Local development tasks (formatting, linting, type checking, tests) are covered in the [Development setup](https://github.com/mirumee/ariadne-codegen/blob/main/CONTRIBUTING.md#development-setup) From a7b33581c0d64b6b4fc0b8276cac8baafe2c6500 Mon Sep 17 00:00:00 2001 From: Kacper Machel <47012829+Minister944@users.noreply.github.com> Date: Tue, 14 Jul 2026 16:06:33 +0200 Subject: [PATCH 3/4] Update docs/02-guides/02-schema-sources.md Co-authored-by: Aleksander Spyra --- docs/02-guides/02-schema-sources.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/02-guides/02-schema-sources.md b/docs/02-guides/02-schema-sources.md index 65d0a682..49744bd8 100644 --- a/docs/02-guides/02-schema-sources.md +++ b/docs/02-guides/02-schema-sources.md @@ -71,7 +71,7 @@ The following options control how the introspection request is performed: - `remote_schema_timeout` (defaults to `5`) - timeout in seconds while introspecting remote schema - `remote_schema_http_client_path` (defaults to `None`) - dotted import path to a custom HTTP client used only for the introspection request, instead of the default `httpx` client -## Introspection query options +### Introspection query options These options only apply when the schema is fetched via `remote_schema_url`. They map directly to the arguments of `graphql-core`'s From 725a8a8046478341cec51a3ca4d6623bd1ac4335 Mon Sep 17 00:00:00 2001 From: Minister944 Date: Tue, 14 Jul 2026 17:26:36 +0200 Subject: [PATCH 4/4] docs: address PR review feedback AR-8 - schema-sources: document directory extension behavior; reframe schema_paths around combining multiple files (installed packages as secondary use case) - using-generated-client: add basic init example, drop redundant headers heading, move context-manager section before configuration, split advanced HTTP config into httpx vs custom-client sections, fix non-httpx client claims - subscriptions: drop redundant client-configuration link - extending-types: clarify files_to_include is required for relative mixin imports - opentelemetry: link opentelemetry-api to PyPI - async-vs-sync: drop duplicated setting bullet and sections repeating the client guide - schema-generation: bulletize schema-source options, link process_schema hook - custom-operation-builder: fold Enabling into intro, rename example sections - configuration: restore original remote_schema_http_client_path description - README: link Contributing directly to CONTRIBUTING.md --- README.md | 2 +- docs/02-guides/02-schema-sources.md | 20 ++- docs/02-guides/03-using-generated-client.md | 117 +++++++++++++----- docs/02-guides/04-subscriptions.md | 3 +- docs/02-guides/07-extending-types.md | 10 +- docs/02-guides/09-opentelemetry.md | 7 +- docs/02-guides/10-async-vs-sync.md | 39 +----- docs/02-guides/11-schema-generation.md | 20 ++- docs/02-guides/12-custom-operation-builder.md | 12 +- docs/03-reference/01-configuration.md | 2 +- 10 files changed, 134 insertions(+), 98 deletions(-) diff --git a/README.md b/README.md index 77fb05d1..10883222 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ for a walk-through of everything that gets generated. ## Contributing -Contributions are welcome! See [Contributing](docs/05-community/01-contributing.md) for how to report bugs, work on issues, and open pull requests. +Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for how to report bugs, work on issues, and open pull requests. Also make sure you follow [@AriadneGraphQL](https://twitter.com/AriadneGraphQL) on Twitter for latest updates, news and random musings! diff --git a/docs/02-guides/02-schema-sources.md b/docs/02-guides/02-schema-sources.md index 49744bd8..a32584fa 100644 --- a/docs/02-guides/02-schema-sources.md +++ b/docs/02-guides/02-schema-sources.md @@ -14,11 +14,11 @@ used at a time: - `schema_path` - path to file/directory with GraphQL schema - `schema_paths` - list of local paths and/or installed-package sources to build the schema from -- `remote_schema_url` - url to GraphQL server, where introspection query can be performed +- `remote_schema_url` - URL to GraphQL server, where introspection query can be performed ## Local schema file -Point `schema_path` at a `.graphql` file or a directory containing schema files: +Point `schema_path` at a single schema file or at a directory: ```toml [tool.ariadne-codegen] @@ -26,11 +26,19 @@ schema_path = "schema.graphql" queries_path = "queries.graphql" ``` -## Loading schema from installed packages +When `schema_path` points to a directory, `ariadne-codegen` reads every file with a +`.graphql`, `.graphqls` or `.gql` extension in it (searched recursively) and combines +them into a single schema. -`schema_paths` lets you pull type definitions from installed Python packages -alongside your local schema files, so codegen can resolve types that live in a -shared library without copying them manually. +## Combining multiple schema files + +`schema_paths` lets you build a single schema from several sources in different +locations - multiple files and/or directories - instead of a single `schema_path`. +Each source is resolved and all the collected files are combined into one schema. + +It additionally supports importing schema files from installed Python packages, so +codegen can resolve types that live in a shared library without copying them +manually. Each entry in `schema_paths` is first tried as a local filesystem path, and if it is neither an existing file nor directory it is treated as a dotted Python import diff --git a/docs/02-guides/03-using-generated-client.md b/docs/02-guides/03-using-generated-client.md index 1d93cc67..88d31dc9 100644 --- a/docs/02-guides/03-using-generated-client.md +++ b/docs/02-guides/03-using-generated-client.md @@ -16,21 +16,36 @@ Example with default settings: from graphql_client.client import Client ``` -### Passing headers to client +Initialize the imported client with the server URL: -Client (with default base client), takes passed headers and attaches them to every sent request. +```py +client = Client("https://example.com/graphql") +``` + +The client (with the default base client) takes passed headers and attaches them to every sent request. ```py client = Client("https://example.com/graphql", {"Authorization": "Bearer token"}) ``` -For more complex scenarios, you can pass your own http client: +For more complex scenarios, you can pass your own HTTP client - see [Advanced HTTP configuration with httpx clients](#advanced-http-configuration-with-httpx-clients) or [with a custom client](#advanced-http-configuration-with-a-custom-client). + +## Using the client as a context manager + +Both base clients support the context manager protocol, which closes the underlying +HTTP client on exit. Use `async with` for the async client: ```py -client = Client(http_client=CustomComplexHttpClient()) +async with Client(url="https://example.com/graphql") as client: + result = await client.list_all_users() ``` -`CustomComplexHttpClient` needs to be an instance of `httpx.AsyncClient` for async client, or `httpx.Client` for sync. +and `with` for the synchronous client: + +```py +with Client(url="https://example.com/graphql") as client: + result = client.list_all_users() +``` ## Client configuration @@ -41,7 +56,7 @@ async base client (`AsyncBaseClient`), all constructor arguments are optional: client = Client( url="https://example.com/graphql", headers={"Authorization": "Bearer token"}, - http_client=None, # supply your own httpx.AsyncClient if needed + http_client=None, # supply your own HTTP client if needed ws_url="wss://example.com/graphql", ws_headers={"Authorization": "Bearer token"}, ws_origin="https://example.com", @@ -50,8 +65,8 @@ client = Client( ``` - `url` (defaults to `""`) - HTTP endpoint the client sends operations to. -- `headers` (defaults to `None`) - headers attached to every HTTP request. When no `http_client` is supplied, they are also used to build the default `httpx.AsyncClient`. -- `http_client` (defaults to `None`) - your own HTTP client instance. Must be an `httpx.AsyncClient` for the async client, or `httpx.Client` for the sync client. When provided, `url`/`headers` handling for the transport is up to you. +- `headers` (defaults to `None`) - headers attached to every HTTP request. When no `http_client` is supplied, they are also used to build the default `httpx` client. +- `http_client` (defaults to `None`) - your own HTTP client instance; when none is provided a default `httpx` client is used. See [Advanced HTTP configuration with httpx clients](#advanced-http-configuration-with-httpx-clients) and [with a custom client](#advanced-http-configuration-with-a-custom-client) for details. - `ws_url` (defaults to `""`) - WebSocket endpoint used for subscriptions. - `ws_headers` (defaults to `None`) - headers added to the WebSocket handshake request. - `ws_origin` (defaults to `None`) - value of the `Origin` header for the WebSocket handshake. @@ -65,32 +80,15 @@ details. The synchronous base client (`async_client = false`) accepts only `url` If you enable the OpenTelemetry base client (`opentelemetry_client = true`), the constructor also accepts tracing arguments - see [Open Telemetry](./09-opentelemetry.md). -## Using the client as a context manager - -Both base clients support the context manager protocol, which closes the underlying -HTTP client on exit. Use `async with` for the async client: - -```py -async with Client(url="https://example.com/graphql") as client: - result = await client.list_all_users() -``` +## Advanced HTTP configuration with httpx clients -and `with` for the synchronous client: - -```py -with Client(url="https://example.com/graphql") as client: - result = client.list_all_users() -``` - -## Advanced HTTP configuration - -The base client is a thin wrapper around [httpx](https://www.python-httpx.org/). When -you don't supply an `http_client`, it builds a default `httpx.AsyncClient`/`httpx.Client` +When you don't supply an `http_client`, the base client builds a default +[httpx](https://www.python-httpx.org/) client (`httpx.AsyncClient`/`httpx.Client`), passing only `headers` to it; `url` is stored separately and used as the target of each request. Any HTTP behaviour beyond that (authentication, timeouts, proxies, HTTP/2, SSL verification, cookies, connection limits, custom transports/retries, event hooks, -etc.) is configured on the httpx client itself. To use it, build your own httpx -client and pass it as `http_client`: +etc.) is configured on the httpx client itself. Build a configured httpx client and +pass it as `http_client`: ```py import httpx @@ -116,4 +114,61 @@ only build the default client), so set them directly on the httpx client. Config endpoint either by passing `url` to `Client` or by setting `base_url` on the httpx client (as above). Refer to the [httpx `Client` / `AsyncClient` API](https://www.python-httpx.org/api/#client) -for the full list of options. \ No newline at end of file +for the full list of options. + +## Advanced HTTP configuration with a custom client + +The default base class HTTP client can be replaced with any client - it doesn't have to +be httpx, it only needs to fulfill the expected protocol: + +```py +client = Client(http_client=CustomComplexHttpClient()) +``` + +`CustomComplexHttpClient` needs to fulfill the following protocol for the async client: + +```py +class Response(Protocol): + status_code: int + + def json(self, **kwargs: Any) -> Any: ... + + +class HttpClient(Protocol): + async def post( + self, + url: Any | str, + json: Any | None = None, + data: Any | None = None, + files: Any | None = None, + headers: Any | None = None, + **kwargs: Any, + ) -> Response: ... + + async def aclose(self) -> None: ... +``` + +Protocol for the sync client: + +```py +class Response(Protocol): + status_code: int + + def json(self, **kwargs: Any) -> Any: ... + + +class HttpClient(Protocol): + def post( + self, + url: Any | str, + json: Any | None = None, + data: Any | None = None, + files: Any | None = None, + headers: Any | None = None, + **kwargs: Any, + ) -> Response: ... + + def close(self) -> None: ... +``` + +The protocol for the sync client is also fulfilled by some commonly known classes, like `requests.Session`. \ No newline at end of file diff --git a/docs/02-guides/04-subscriptions.md b/docs/02-guides/04-subscriptions.md index 4dbe690d..16957f95 100644 --- a/docs/02-guides/04-subscriptions.md +++ b/docs/02-guides/04-subscriptions.md @@ -98,5 +98,4 @@ server closes the subscription (or you `break` out of it). Arguments `ws_origin` and `ws_headers` are added as headers to the handshake request, and `ws_connection_init_payload` is used as the payload of the [ConnectionInit](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md#connectioninit) -message. See [Using generated client](./03-using-generated-client.md#client-configuration) -for the full list of constructor arguments. +message. diff --git a/docs/02-guides/07-extending-types.md b/docs/02-guides/07-extending-types.md index 99757ce4..235189cd 100644 --- a/docs/02-guides/07-extending-types.md +++ b/docs/02-guides/07-extending-types.md @@ -29,8 +29,14 @@ away. Both arguments must be string literals; if either `from` or `import` is missing (or a non-string value is passed), generation fails with a `ParsingError`. -This directive can be used along with the `files_to_include` option to extend the -functionality of generated classes. +The directive only emits the `from {from} import {import}` line - it does not copy any +code - so the imported module must be reachable from the generated package. This +depends on what `from` points to: + +- a **relative import** (e.g. `from: ".mixins"`) refers to a module inside the generated + package, so you must copy that module in with the [`files_to_include`](../03-reference/01-configuration.md) option. +- an **absolute import** (e.g. `from: "my_package.mixins"`) just has to be importable in + the environment where the generated code runs; `files_to_include` is not needed. ### Where `@mixin` can be used diff --git a/docs/02-guides/09-opentelemetry.md b/docs/02-guides/09-opentelemetry.md index cb1e19f7..1371e779 100644 --- a/docs/02-guides/09-opentelemetry.md +++ b/docs/02-guides/09-opentelemetry.md @@ -6,9 +6,10 @@ title: Open Telemetry When the config option `opentelemetry_client` is set to `true` then the default, included base client is replaced with one that implements opt-in Open Telemetry -support. By default this support does nothing, but when the `opentelemetry-api` -package is installed and the `tracer` argument is provided then the client will -create spans with data about performed requests. +support. By default this support does nothing, but when the +[`opentelemetry-api`](https://pypi.org/project/opentelemetry-api/) package is installed +and the `tracer` argument is provided then the client will create spans with data about +performed requests. ## Enabling diff --git a/docs/02-guides/10-async-vs-sync.md b/docs/02-guides/10-async-vs-sync.md index 26f8bd25..37b4b206 100644 --- a/docs/02-guides/10-async-vs-sync.md +++ b/docs/02-guides/10-async-vs-sync.md @@ -14,8 +14,6 @@ To generate a synchronous client instead, set `async_client` to `false`: async_client = false ``` -- `async_client` (defaults to `true`) - default generated client is `async`, change this option to `false` to generate a synchronous client instead - ## What changes | | Async (default) | Sync (`async_client = false`) | @@ -23,7 +21,7 @@ async_client = false | Base class | `AsyncBaseClient` | `BaseClient` | | Operation methods | `async def` (must be `await`ed) | plain `def` | | Context manager | `async with` | `with` | -| Custom `http_client` | `httpx.AsyncClient` | `httpx.Client` | +| Custom `http_client` | async client (default `httpx.AsyncClient`) | sync client (default `httpx.Client`) | | Subscriptions | supported | **not supported** | The generated method bodies mirror this - the async client does @@ -57,38 +55,3 @@ Subscriptions are only available when using async client. ``` See [Subscriptions](./04-subscriptions.md). - -## Passing a custom http client - -The choice of async vs sync also determines which `httpx` client you can supply when -passing your own http client: - -```py -client = Client(http_client=CustomComplexHttpClient()) -``` - -`CustomComplexHttpClient` needs to be an instance of `httpx.AsyncClient` for the -async client, or `httpx.Client` for the sync client. See -[Using generated client](./03-using-generated-client.md#advanced-http-configuration). - -## Calling the client - -```py -# async -import asyncio -from graphql_client.client import Client - -async def main(): - async with Client(url="https://example.com/graphql") as client: - users = await client.list_all_users() - -asyncio.run(main()) -``` - -```py -# sync -from graphql_client.client import Client - -with Client(url="https://example.com/graphql") as client: - users = client.list_all_users() -``` diff --git a/docs/02-guides/11-schema-generation.md b/docs/02-guides/11-schema-generation.md index db958919..4e3a18ee 100644 --- a/docs/02-guides/11-schema-generation.md +++ b/docs/02-guides/11-schema-generation.md @@ -12,14 +12,24 @@ ariadne-codegen graphqlschema ``` `graphqlschema` mode reads configuration from the same place as the client, but uses -only the `schema_path`, `schema_paths`, `remote_schema_url`, `remote_schema_headers`, -`remote_schema_verify_ssl`, `remote_schema_timeout`, `remote_schema_http_client_path`, -and `introspection_*` options to retrieve the schema (see -[Schema sources](./02-schema-sources.md)) and the `plugins` option to load plugins. +only a subset of the options: + +- to retrieve the schema (see [Schema sources](./02-schema-sources.md)): + - `schema_path` + - `schema_paths` + - `remote_schema_url` + - `remote_schema_headers` + - `remote_schema_verify_ssl` + - `remote_schema_timeout` + - `remote_schema_http_client_path` + - `introspection_*` +- `plugins` - to load plugins Before writing the file, the loaded schema is passed through each plugin's `process_schema` hook and then validated, so this mode can also be used to apply -plugin transformations to a schema or to fail early on an invalid one. +plugin transformations to a schema or to fail early on an invalid one. See +[Plugins](../04-plugins/01-intro.md) (and the [`process_schema` hook](../04-plugins/03-hooks.md#process_schema)) +for details. In addition to the above, `graphqlschema` mode also accepts additional settings specific to it: diff --git a/docs/02-guides/12-custom-operation-builder.md b/docs/02-guides/12-custom-operation-builder.md index 61215200..60b4bcdc 100644 --- a/docs/02-guides/12-custom-operation-builder.md +++ b/docs/02-guides/12-custom-operation-builder.md @@ -4,13 +4,7 @@ title: Custom operation builder # Custom operation builder -The custom operation builder allows you to create complex GraphQL queries in a structured and intuitive way. - -## Enabling - -Set `enable_custom_operations = true` in your config. This mode generates additional -helper modules instead of (or alongside) the per-operation client methods, so -`queries_path` becomes optional: +The custom operation builder allows you to create complex GraphQL queries in a structured and intuitive way. This feature is disabled by default. To enable it, set `enable_custom_operations = true` in your config. It generates additional helper modules instead of (or alongside) the per-operation client methods, so `queries_path` becomes optional: ```toml [tool.ariadne-codegen] @@ -28,7 +22,7 @@ With it enabled, the following modules are generated in your package: The client also gains `query(...)`, `mutation(...)` and `execute_custom_operation(...)` methods for executing the built operations. -## Example Code +## Example: custom queries ```python import asyncio @@ -99,7 +93,7 @@ Unlike the per-operation client methods, `client.query(...)` and `client.mutation(...)` return the raw response as a `dict[str, Any]` - the result is not parsed into a generated Pydantic model. -## Building mutations +## Example: custom mutations Mutations are built the same way, using the generated `Mutation` class from `custom_mutations` and executed with `client.mutation(...)`: diff --git a/docs/03-reference/01-configuration.md b/docs/03-reference/01-configuration.md index d3f2457a..69666c8b 100644 --- a/docs/03-reference/01-configuration.md +++ b/docs/03-reference/01-configuration.md @@ -29,7 +29,7 @@ Exactly one of the following parameters is required - they are mutually exclusiv - `remote_schema_headers` - extra headers that are passed along with introspection query, eg. `{"Authorization" = "Bearer token"}`. To include an environment variable in a header value, prefix the variable with `$`, eg. `{"Authorization" = "$AUTH_TOKEN"}` - `remote_schema_verify_ssl` (defaults to `true`) - a flag that specifies whether to verify ssl while introspecting remote schema - `remote_schema_timeout` (defaults to `5`) - timeout in seconds while introspecting remote schema -- `remote_schema_http_client_path` (defaults to `None`) - dotted import path to a custom HTTP client (a class/callable that returns one, or a ready client object) providing an `httpx`-compatible `post` interface, used only for the remote schema introspection request. If unset, the default `httpx` client is used. +- `remote_schema_http_client_path` - absolute import path to the HTTP client class used to introspect remote schema. If not provided, default `httpx` client is used. - `target_package_name` (defaults to `"graphql_client"`) - name of generated package - `target_package_path` (defaults to cwd) - path where to generate package - `client_name` (defaults to `"Client"`) - name of generated client class