From 69fbcbd17e5556124d93e767286bbb90e312b80f Mon Sep 17 00:00:00 2001 From: esfomeado Date: Fri, 15 May 2026 15:00:58 +0100 Subject: [PATCH] Support `schema_path` as both a single path and a list of paths --- CHANGELOG.md | 2 ++ ariadne_codegen/schema.py | 11 +++++-- ariadne_codegen/settings.py | 28 +++++++++++----- docs/02-configuration.md | 2 +- tests/test_schema.py | 22 +++++++++++++ tests/test_settings.py | 66 +++++++++++++++++++++++++++++++++++++ 6 files changed, 118 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 500a2793..569c9397 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ For released versions, see the [Releases](https://github.com/mirumee/ariadne-cod ## Unreleased +- Allow `schema_path` to accept a list of file/directory paths in addition to a single path string. + ### ⚠️ Breaking Changes ### 📚 Documentation diff --git a/ariadne_codegen/schema.py b/ariadne_codegen/schema.py index f66723b9..ba2c2b0a 100644 --- a/ariadne_codegen/schema.py +++ b/ariadne_codegen/schema.py @@ -133,9 +133,14 @@ def introspect_remote_schema( return cast(IntrospectionQuery, data) -def get_graphql_schema_from_path(schema_path: str) -> GraphQLSchema: - """Get graphql schema build from provided path.""" - schema_str = load_graphql_files_from_path(Path(schema_path)) +def get_graphql_schema_from_path(schema_path: str | list[str]) -> GraphQLSchema: + """Get graphql schema build from the provided path or list of paths.""" + if isinstance(schema_path, list): + schema_str = "\n".join( + load_graphql_files_from_path(Path(p)) for p in schema_path + ) + else: + schema_str = load_graphql_files_from_path(Path(schema_path)) graphql_ast = parse(schema_str) schema: GraphQLSchema = build_ast_schema(graphql_ast, assume_valid=True) return schema diff --git a/ariadne_codegen/settings.py b/ariadne_codegen/settings.py index 709bfa2b..a6ba6ca7 100644 --- a/ariadne_codegen/settings.py +++ b/ariadne_codegen/settings.py @@ -65,7 +65,7 @@ class IntrospectionSettings: @dataclass class BaseSettings: - schema_path: str = "" + schema_path: str | list[str] = "" remote_schema_url: str = "" remote_schema_headers: dict = field(default_factory=dict) remote_schema_verify_ssl: bool = True @@ -86,7 +86,11 @@ def __post_init__(self): ) if self.schema_path: - assert_path_exists(self.schema_path) + if isinstance(self.schema_path, list): + for path in self.schema_path: + assert_path_exists(path) + else: + assert_path_exists(self.schema_path) self.remote_schema_headers = resolve_headers(self.remote_schema_headers) if self.remote_schema_url: @@ -99,6 +103,16 @@ def using_remote_schema(self) -> bool: """ return bool(self.remote_schema_url) and not bool(self.schema_path) + @property + def schema_source(self) -> str: + if isinstance(self.schema_path, list): + return ( + ", ".join(self.schema_path) + if self.schema_path + else self.remote_schema_url + ) + return self.schema_path if self.schema_path else self.remote_schema_url + @property def introspection_settings(self) -> IntrospectionSettings: """ @@ -218,10 +232,6 @@ def _set_default_base_client_data(self): self.base_client_name = name self.base_client_file_path = path.as_posix() - @property - def schema_source(self) -> str: - return self.schema_path if self.schema_path else self.remote_schema_url - @property def used_settings_message(self) -> str: snake_case_msg = ( @@ -257,7 +267,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. @@ -307,7 +317,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 {self.schema_source} {introspection_msg} Saving graphql schema to: {self.target_file_path} Using {self.schema_variable_name} as variable name for schema. @@ -319,7 +329,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 {self.schema_source} {introspection_msg} Saving graphql schema to: {self.target_file_path} {plugins_msg} diff --git a/docs/02-configuration.md b/docs/02-configuration.md index f0f21c00..0f253580 100644 --- a/docs/02-configuration.md +++ b/docs/02-configuration.md @@ -20,7 +20,7 @@ queries_path = "queries.graphql" One of the following 2 parameters is required, in case of providing both of them `schema_path` is prioritized: -- `schema_path` - path to file/directory with graphql schema +- `schema_path` - path to file/directory with graphql schema, or a list of paths whose contents will be concatenated - `remote_schema_url` - url to graphql server, where introspection query can be perfomed ## Optional settings: diff --git a/tests/test_schema.py b/tests/test_schema.py index fd130e0f..631e7f3e 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -332,6 +332,28 @@ def test_get_graphql_schema_from_path_with_invalid_syntax_raises_invalid_graphql get_graphql_schema_from_path(invalid_syntax_schema_file.as_posix()) +def test_get_graphql_schema_from_path_accepts_list_of_paths( + single_file_schema, schemas_directory, schema_str, extra_type_str +): + schema = get_graphql_schema_from_path( + [single_file_schema.as_posix(), schemas_directory.as_posix()] + ) + + assert isinstance(schema, GraphQLSchema) + assert schema.get_type("Custom") is not None + assert schema.get_type("User") is not None + + +def test_get_graphql_schema_from_path_with_single_element_list_matches_single_path( + single_file_schema, +): + list_schema = get_graphql_schema_from_path([single_file_schema.as_posix()]) + str_schema = get_graphql_schema_from_path(single_file_schema.as_posix()) + + assert isinstance(list_schema, GraphQLSchema) + assert list_schema.type_map.keys() == str_schema.type_map.keys() + + def test_introspect_remote_schema_called_with_invalid_url_raises_introspection_error( mocker, ): diff --git a/tests/test_settings.py b/tests/test_settings.py index 5f36d347..5c1fe488 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -133,6 +133,48 @@ def test_client_settings_without_schema_path_or_remote_schema_url_raises_excepti ClientSettings(queries_path=queries_path) +def test_client_settings_accepts_list_for_schema_path(tmp_path): + schema_path_a = tmp_path / "schema_a.graphql" + schema_path_a.touch() + schema_path_b = tmp_path / "schema_b.graphql" + schema_path_b.touch() + queries_path = tmp_path / "queries.graphql" + queries_path.touch() + + settings = ClientSettings( + schema_path=[schema_path_a.as_posix(), schema_path_b.as_posix()], + queries_path=queries_path.as_posix(), + ) + + assert settings.schema_path == [ + schema_path_a.as_posix(), + schema_path_b.as_posix(), + ] + + +def test_client_settings_raises_invalid_configuration_for_nonexistent_path_in_list( + tmp_path, +): + schema_path = tmp_path / "schema.graphql" + schema_path.touch() + queries_path = tmp_path / "queries.graphql" + queries_path.touch() + + with pytest.raises(InvalidConfiguration): + ClientSettings( + schema_path=[schema_path.as_posix(), (tmp_path / "missing.graphql").as_posix()], + queries_path=queries_path.as_posix(), + ) + + +def test_client_settings_with_empty_list_schema_path_and_no_remote_url_raises(tmp_path): + queries_path = tmp_path / "queries.graphql" + queries_path.touch() + + with pytest.raises(InvalidConfiguration): + ClientSettings(schema_path=[], queries_path=queries_path.as_posix()) + + @pytest.mark.parametrize( "configured_header, expected_header", [ @@ -550,6 +592,30 @@ class BaseClient: assert settings.using_remote_schema is False +def test_using_remote_schema_false_when_schema_path_is_non_empty_list(tmp_path): + """ + Test that using_remote_schema is False when schema_path is a non-empty list, + even if remote_schema_url is also provided. + """ + schema_path_a = tmp_path / "schema_a.graphql" + schema_path_a.touch() + schema_path_b = tmp_path / "schema_b.graphql" + schema_path_b.touch() + queries_path = tmp_path / "queries.graphql" + queries_path.touch() + + settings = ClientSettings( + schema_path=[schema_path_a.as_posix(), schema_path_b.as_posix()], + remote_schema_url="http://testserver/graphql/", + queries_path=queries_path.as_posix(), + ) + + assert settings.using_remote_schema is False + assert settings.schema_source == ", ".join( + [schema_path_a.as_posix(), schema_path_b.as_posix()] + ) + + def test_introspection_settings_defaults(tmp_path): """ Test that introspection settings have the correct default values.