Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,10 @@ Required settings:

- `queries_path` - path to file/directory with queries (Can be optional if `enable_custom_operations` is used)

One of the following 2 parameters is required, in case of providing both of them `schema_path` is prioritized:
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:
Expand Down Expand Up @@ -88,6 +89,30 @@ 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

## 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 resolved in order and can be one of:

- **Local path** — a file (`./shared/types.graphql`) or a directory (`./my_schemas/`). Directories are searched recursively for `.graphql`, `.graphqls`, and `.gql` files.
- **Dotted Python attribute** — `some_package.SCHEMA_DIR` or `some_package.get_schema_files`. The attribute is looked up via `importlib` at codegen time:
- If it is **callable**, it is called and expected to return a list of file paths.
- If it is a **string or `Path`**, it is treated as a directory and searched recursively.

```toml
[tool.ariadne-codegen]
schema_paths = [
"some_gql_commontypes.get_schema_files", # callable → returns list of paths
"other_pkg.SCHEMA_DIR", # Path attribute → directory
"./my_other_packages/", # local directory
"./foo/bar.graphql", # local file
]
queries_path = "queries.graphql"
```

`schema_path`, `schema_paths` and `remote_schema_url` are mutually exclusive - only one schema source may be used at a time.

## Custom operation builder

The custom operation builder allows you to create complex GraphQL queries in a structured and intuitive way.
Expand Down Expand Up @@ -416,7 +441,7 @@ Instead of generating a client, you can generate a file with a copy of a GraphQL
ariadne-codegen graphqlschema
```

`graphqlschema` mode reads configuration from the same place as [`client`](#configuration) but uses only `schema_path`, `remote_schema_url`, `remote_schema_headers`, `remote_schema_verify_ssl`, `remote_schema_timeout` options to retrieve the schema and `plugins` option to load plugins.
`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` 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:

Expand Down
14 changes: 9 additions & 5 deletions ariadne_codegen/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
filter_operations_definitions,
get_graphql_queries,
get_graphql_schema_from_path,
get_graphql_schema_from_paths,
get_graphql_schema_from_url,
)
from .settings import Strategy, get_validation_rule
Expand Down Expand Up @@ -45,6 +46,8 @@ def client(config_dict):

if settings.schema_path:
schema = get_graphql_schema_from_path(settings.schema_path)
elif settings.schema_paths:
schema = get_graphql_schema_from_paths(settings.schema_paths)
else:
schema = get_graphql_schema_from_url(
url=settings.remote_schema_url,
Expand Down Expand Up @@ -92,17 +95,18 @@ def client(config_dict):
def graphql_schema(config_dict):
settings = get_graphql_schema_settings(config_dict)

schema = (
get_graphql_schema_from_path(settings.schema_path)
if settings.schema_path
else get_graphql_schema_from_url(
if settings.schema_path:
schema = get_graphql_schema_from_path(settings.schema_path)
elif settings.schema_paths:
schema = get_graphql_schema_from_paths(settings.schema_paths)
else:
schema = get_graphql_schema_from_url(
url=settings.remote_schema_url,
headers=settings.remote_schema_headers,
verify_ssl=settings.remote_schema_verify_ssl,
timeout=settings.remote_schema_timeout,
introspection_settings=settings.introspection_settings,
)
)
plugin_manager = PluginManager(
schema=schema,
config_dict=config_dict,
Expand Down
48 changes: 48 additions & 0 deletions ariadne_codegen/schema.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import importlib
from collections.abc import Generator, Sequence
from dataclasses import asdict
from pathlib import Path
Expand Down Expand Up @@ -141,6 +142,53 @@ def get_graphql_schema_from_path(schema_path: str) -> GraphQLSchema:
return schema


def resolve_schema_paths(sources: list[str]) -> list[Path]:
"""Resolve a list of schema sources to concrete file paths.

Each entry is tried as a dotted Python import path first (e.g.
``pkg.SCHEMA_DIR`` or ``pkg.get_schema_files``). If the import fails the
entry is treated as a local filesystem path instead.
"""
result: list[Path] = []
for source in sources:
if (
"." in source
and "/" not in source
and not source.endswith((".graphql", ".graphqls", ".gql"))
):
try:
module_path, attr = source.rsplit(".", 1)
module = importlib.import_module(module_path)
obj = getattr(module, attr)
if callable(obj):
result.extend(Path(f) for f in obj())
elif isinstance(obj, (str, Path)):
dir_path = Path(obj)
if dir_path.is_dir():
result.extend(sorted(walk_graphql_files(dir_path)))
else:
result.append(dir_path)
continue
except (ImportError, ModuleNotFoundError):
pass

local_path = Path(source)
if local_path.is_dir():
result.extend(sorted(walk_graphql_files(local_path)))
else:
result.append(local_path)
return result


def get_graphql_schema_from_paths(schema_paths: list[str]) -> GraphQLSchema:
"""Get graphql schema built from multiple path sources."""
resolved = resolve_schema_paths(schema_paths)
schema_str = "\n".join(read_graphql_file(p) for p in resolved)
graphql_ast = parse(schema_str)
schema: GraphQLSchema = build_ast_schema(graphql_ast, assume_valid=True)
return schema


def load_graphql_files_from_path(path: Path) -> str:
"""
Get schema from given path.
Expand Down
40 changes: 33 additions & 7 deletions ariadne_codegen/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class IntrospectionSettings:
@dataclass
class BaseSettings:
schema_path: str = ""
schema_paths: list[str] = field(default_factory=list)
remote_schema_url: str = ""
remote_schema_headers: dict = field(default_factory=dict)
remote_schema_verify_ssl: bool = True
Expand All @@ -80,9 +81,25 @@ class BaseSettings:
introspection_input_object_one_of: bool = False

def __post_init__(self):
if not self.schema_path and not self.remote_schema_url:
provided_sources = [
name
for name, value in (
("schema_path", self.schema_path),
("schema_paths", self.schema_paths),
("remote_schema_url", self.remote_schema_url),
)
if value
]
if not provided_sources:
raise InvalidConfiguration(
"Schema source not provided. Use one of: schema_path, schema_paths,"
" or remote_schema_url."
)
if len(provided_sources) > 1:
raise InvalidConfiguration(
"Schema source not provided. Use schema_path or remote_schema_url"
"Cannot use more than one schema source at the same time. "
f"Provided: {', '.join(provided_sources)}. Use only one of: "
"schema_path, schema_paths, or remote_schema_url."
)

if self.schema_path:
Expand All @@ -97,7 +114,7 @@ def using_remote_schema(self) -> bool:
"""
Return true if remote schema is used as source, false otherwise.
"""
return bool(self.remote_schema_url) and not bool(self.schema_path)
return bool(self.remote_schema_url)

@property
def introspection_settings(self) -> IntrospectionSettings:
Expand Down Expand Up @@ -220,7 +237,11 @@ def _set_default_base_client_data(self):

@property
def schema_source(self) -> str:
return self.schema_path if self.schema_path else self.remote_schema_url
if self.schema_path:
return self.schema_path
if self.schema_paths:
return ", ".join(self.schema_paths)
return self.remote_schema_url

@property
def used_settings_message(self) -> str:
Expand Down Expand Up @@ -257,7 +278,7 @@ def used_settings_message(self) -> str:
return dedent(
f"""\
Selected strategy: {Strategy.CLIENT}
Using schema from '{self.schema_path or self.remote_schema_url}'.
Using schema from '{self.schema_source}'.
{introspection_msg}
Reading queries from '{self.queries_path}'.
Using '{self.target_package_name}' as package name.
Expand Down Expand Up @@ -303,11 +324,16 @@ def used_settings_message(self):
self._introspection_settings_message() if self.using_remote_schema else ""
)

schema_source = self.schema_path or (
", ".join(self.schema_paths)
if self.schema_paths
else self.remote_schema_url
)
if self.target_file_format == "py":
return dedent(
f"""\
Selected strategy: {Strategy.GRAPHQL_SCHEMA}
Using schema from {self.schema_path or self.remote_schema_url}
Using schema from {schema_source}
{introspection_msg}
Saving graphql schema to: {self.target_file_path}
Using {self.schema_variable_name} as variable name for schema.
Expand All @@ -319,7 +345,7 @@ def used_settings_message(self):
return dedent(
f"""\
Selected strategy: {Strategy.GRAPHQL_SCHEMA}
Using schema from {self.schema_path or self.remote_schema_url}
Using schema from {schema_source}
{introspection_msg}
Saving graphql schema to: {self.target_file_path}
{plugins_msg}
Expand Down
23 changes: 22 additions & 1 deletion docs/02-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,32 @@ queries_path = "queries.graphql"

- `queries_path` - path to file/directory with queries (Can be optional if `enable_custom_operations` is used)

One of the following 2 parameters is required, in case of providing both of them `schema_path` is prioritized:
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 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.
- `remote_schema_url` - url to graphql server, where introspection query can be perfomed

### `schema_paths` resolution

Each entry is resolved in order:

1. **Dotted Python attribute** (no `/` in the string, not ending with a graphql extension) — the attribute is imported via `importlib`:
- **callable** → called, expected to return a list of file paths
- **string / `Path`** → treated as a directory and searched recursively for `.graphql`, `.graphqls`, `.gql` files
2. **Local path** (fallback when import fails, or when the entry contains `/` or has a graphql extension) — a file or directory searched recursively.

```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"}`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ def test_get_package_generator_without_default_settings(tmp_path: Path):

settings_without_defaults = ClientSettings(
schema_path=schema_path.as_posix(),
remote_schema_url="remote_schema_url",
remote_schema_headers={"header": "header"},
remote_schema_verify_ssl=False,
remote_schema_timeout=5,
Expand Down
Loading
Loading