From a520ebc02b3b6e40496f8a673188f578fa8e8d30 Mon Sep 17 00:00:00 2001 From: Ernesto Ruge Date: Mon, 6 Apr 2026 09:10:04 +0200 Subject: [PATCH] filters and improvements --- .gitignore | 1 + src/schema2validataclass/_version.py | 24 ---- src/schema2validataclass/app.py | 120 ++++++++++++++---- src/schema2validataclass/config.py | 9 +- .../output/base_outputs.py | 3 - src/schema2validataclass/schema/models.py | 35 +++-- .../dataclass/chained_schemas_ignore_test.py | 94 ++++++++++++++ .../chained_schemas_list_ignore_test.py | 56 ++++++++ .../dataclass/chained_schemas_unused_test.py | 36 ++++++ tests/integration/dataclass/helpers.py | 2 +- .../pydantic/chained_schemas_ignore_test.py | 94 ++++++++++++++ .../chained_schemas_list_ignore_test.py | 56 ++++++++ .../pydantic/chained_schemas_unused_test.py | 36 ++++++ tests/integration/pydantic/helpers.py | 2 +- .../chained_schemas_ignore_test.py | 94 ++++++++++++++ .../chained_schemas_list_ignore_test.py | 56 ++++++++ .../chained_schemas_unused_test.py | 36 ++++++ tests/integration/validataclass/helpers.py | 4 +- .../chained_schemas_ignore/main_schema.json | 10 ++ .../chained_schemas_ignore/second_schema.json | 16 +++ .../chained_schemas_ignore/third_schema.json | 21 +++ .../main_schema.json | 10 ++ .../second_schema.json | 19 +++ .../third_schema.json | 21 +++ .../chained_schemas_unused/main_schema.json | 10 ++ .../chained_schemas_unused/second_schema.json | 13 ++ .../chained_schemas_unused/third_schema.json | 21 +++ .../exclusive_child_schema.json | 13 ++ .../ignored_classnames/ignored_schema.json | 19 +++ .../input/ignored_classnames/kept_schema.json | 16 +++ .../input/ignored_classnames/main_schema.json | 22 ++++ .../shared_child_schema.json | 13 ++ tests/unit/get_reference_base_uris_test.py | 31 +++-- .../object_get_reference_base_uris_test.py | 33 +++-- .../schema_get_reference_base_uris_test.py | 10 +- 35 files changed, 960 insertions(+), 96 deletions(-) delete mode 100644 src/schema2validataclass/_version.py create mode 100644 tests/integration/dataclass/chained_schemas_ignore_test.py create mode 100644 tests/integration/dataclass/chained_schemas_list_ignore_test.py create mode 100644 tests/integration/dataclass/chained_schemas_unused_test.py create mode 100644 tests/integration/pydantic/chained_schemas_ignore_test.py create mode 100644 tests/integration/pydantic/chained_schemas_list_ignore_test.py create mode 100644 tests/integration/pydantic/chained_schemas_unused_test.py create mode 100644 tests/integration/validataclass/chained_schemas_ignore_test.py create mode 100644 tests/integration/validataclass/chained_schemas_list_ignore_test.py create mode 100644 tests/integration/validataclass/chained_schemas_unused_test.py create mode 100644 tests/test_schema/input/chained_schemas_ignore/main_schema.json create mode 100644 tests/test_schema/input/chained_schemas_ignore/second_schema.json create mode 100644 tests/test_schema/input/chained_schemas_ignore/third_schema.json create mode 100644 tests/test_schema/input/chained_schemas_list_ignore/main_schema.json create mode 100644 tests/test_schema/input/chained_schemas_list_ignore/second_schema.json create mode 100644 tests/test_schema/input/chained_schemas_list_ignore/third_schema.json create mode 100644 tests/test_schema/input/chained_schemas_unused/main_schema.json create mode 100644 tests/test_schema/input/chained_schemas_unused/second_schema.json create mode 100644 tests/test_schema/input/chained_schemas_unused/third_schema.json create mode 100644 tests/test_schema/input/ignored_classnames/exclusive_child_schema.json create mode 100644 tests/test_schema/input/ignored_classnames/ignored_schema.json create mode 100644 tests/test_schema/input/ignored_classnames/kept_schema.json create mode 100644 tests/test_schema/input/ignored_classnames/main_schema.json create mode 100644 tests/test_schema/input/ignored_classnames/shared_child_schema.json diff --git a/.gitignore b/.gitignore index 12e8376..84fdbe9 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ /output /input +/src/schema2validataclass/_version.py /tests/test_schema/output diff --git a/src/schema2validataclass/_version.py b/src/schema2validataclass/_version.py deleted file mode 100644 index 0b25865..0000000 --- a/src/schema2validataclass/_version.py +++ /dev/null @@ -1,24 +0,0 @@ -# file generated by vcs-versioning -# don't change, don't track in version control -from __future__ import annotations - -__all__ = [ - '__version__', - '__version_tuple__', - 'version', - 'version_tuple', - '__commit_id__', - 'commit_id', -] - -version: str -__version__: str -__version_tuple__: tuple[int | str, ...] -version_tuple: tuple[int | str, ...] -commit_id: str | None -__commit_id__: str | None - -__version__ = version = '0.0.post8+g5df0c6455.d20260404' -__version_tuple__ = version_tuple = (0, 0, 'post8', 'g5df0c6455.d20260404') - -__commit_id__ = commit_id = 'g5df0c6455' diff --git a/src/schema2validataclass/app.py b/src/schema2validataclass/app.py index 5aaf62f..f7d8f94 100644 --- a/src/schema2validataclass/app.py +++ b/src/schema2validataclass/app.py @@ -7,6 +7,7 @@ import logging import subprocess # noqa: S404 from pathlib import Path +from typing import Callable from urllib.request import urlopen from schema2validataclass.common.helper import to_snake_case @@ -23,7 +24,7 @@ from schema2validataclass.output.dataclass_outputs import DATACLASS_OUTPUT_CLASSES, DataclassObjectOutput from schema2validataclass.output.pydantic_outputs import PYDANTIC_OUTPUT_CLASSES, PydanticObjectOutput from schema2validataclass.output.validataclass_outputs import VALIDATACLASS_OUTPUT_CLASSES, ValidataclassObjectOutput -from schema2validataclass.schema.models import BaseField, Object, Schema +from schema2validataclass.schema.models import Array, BaseField, Object, Reference, Schema, get_reference_uris logger = logging.getLogger(__name__) @@ -44,29 +45,43 @@ def generate(self, schema_uri: URI, output_path: Path): } object_output_class, output_classes = output_format_map[self.config.output_format] - main_schema_dict = self.read_schema(schema_uri) - - main_schema = Schema(main_schema_dict, uri=schema_uri) - schema_objects: dict[URI, Schema] = {schema_uri: main_schema} - schemas_to_load: list[URI] = main_schema.get_reference_base_uris() - while len(schemas_to_load): - child_schema = schemas_to_load.pop() - logger.info(f'parsing {child_schema} ...') - child_schema_dict = self.read_schema(child_schema) - child_schema_object = Schema(child_schema_dict, uri=child_schema) - schema_objects[child_schema] = child_schema_object - for reference_uri in child_schema_object.get_reference_base_uris(): - if reference_uri not in schema_objects: - schemas_to_load.append(reference_uri) - - # Check Reference Uniqueness and generate referencable fields + # Schema file cache: each file is read and parsed only once + schema_cache: dict[URI, Schema] = {} + + main_schema = self.get_or_load_schema(schema_cache, schema_uri) + + # Build referencable_fields by walking the reference tree from the main schema referencable_fields: dict[URI, BaseField] = {} - for schema_object in list(schema_objects.values()): - for field in schema_object.properties + schema_object.definitions: - if field.uri in referencable_fields: - logger.warning(f'Duplicate field: {field.uri}') - continue - referencable_fields[field.uri] = field + + # Main schema's contained_object properties are always included + for field in main_schema.properties: + referencable_fields[field.uri] = field + + # Tree traversal: follow references starting from the main schema's properties + refs_to_process: list[URI] = get_reference_uris(main_schema.properties) + processed_refs: set[URI] = set() + + while refs_to_process: + ref_uri = refs_to_process.pop() + if ref_uri in processed_refs: + continue + processed_refs.add(ref_uri) + + base_uri = URI.from_uri_without_json_path(ref_uri) + schema = self.get_or_load_schema(schema_cache, base_uri) + + field = schema.get_field_by_uri(ref_uri) + if field is None: + logger.warning(f'Referenced field not found: {ref_uri}') + continue + + if ref_uri in referencable_fields: + logger.warning(f'Duplicate field: {ref_uri}') + continue + referencable_fields[ref_uri] = field + + # Discover further references from this field + refs_to_process.extend(get_reference_uris([field])) main_object_output = object_output_class( main_schema.contained_object, @@ -109,6 +124,65 @@ def generate(self, schema_uri: URI, output_path: Path): self._run_post_processing(output_path) + def _is_ignored_reference(self, ref_uri: URI) -> bool: + ref_str = str(ref_uri) + for pattern in self.config.ignore_references: + if ref_str.endswith(pattern): + return True + return False + + def get_or_load_schema(self, schema_cache: dict[URI, Schema], base_uri: URI) -> Schema: + if base_uri not in schema_cache: + logger.info(f'parsing {base_uri} ...') + schema_dict = self.read_schema(base_uri) + schema = Schema(schema_dict, uri=base_uri) + self._apply_ignore_paths(schema) + self._apply_ignore_references(schema) + schema_cache[base_uri] = schema + return schema_cache[base_uri] + + def _apply_ignore_paths(self, schema: Schema) -> None: + if not self.config.ignore_paths: + return + if schema.contained_object: + self._filter_object_properties(schema.contained_object, self._is_ignored_path) + for definition in schema.definitions: + if isinstance(definition, Object): + self._filter_object_properties(definition, self._is_ignored_path) + + def _apply_ignore_references(self, schema: Schema) -> None: + if not self.config.ignore_references: + return + if schema.contained_object: + self._filter_object_properties(schema.contained_object, self._is_ignored_reference_property) + for definition in schema.definitions: + if isinstance(definition, Object): + self._filter_object_properties(definition, self._is_ignored_reference_property) + + def _filter_object_properties(self, obj: Object, predicate: Callable) -> None: + obj.properties = [prop for prop in obj.properties if not predicate(prop)] + for prop in obj.properties: + if isinstance(prop, Object): + self._filter_object_properties(prop, predicate) + if isinstance(prop, Array) and isinstance(prop.items, Object): + self._filter_object_properties(prop.items, predicate) + + def _is_ignored_path(self, field: BaseField) -> bool: + uri_str = str(field.uri) + for pattern in self.config.ignore_paths: + if uri_str.endswith(pattern): + logger.info(f'skipping ignored path {field.uri}') + return True + return False + + def _is_ignored_reference_property(self, field: BaseField) -> bool: + ref = field + if isinstance(ref, Array): + ref = ref.items + if not isinstance(ref, Reference): + return False + return self._is_ignored_reference(ref.to) + @staticmethod def _get_referenced_object_name(output: BaseOutput) -> str | None: if isinstance(output, NestedObjectBaseOutput): diff --git a/src/schema2validataclass/config.py b/src/schema2validataclass/config.py index 0635a2f..9285d90 100644 --- a/src/schema2validataclass/config.py +++ b/src/schema2validataclass/config.py @@ -37,14 +37,17 @@ class PostProcessing(Enum): class Config: unset_value_output: UnsetValueOutput = UnsetValueOutput.UNSET_VALUE object_postfix: str = 'Input' - ignored_uris: list[str] = field( - default_factory=list, - ) output_format: OutputFormat = OutputFormat.VALIDATACLASS set_validataclass_mixin: bool = True renamed_properties: list[str] = field( default_factory=lambda: keyword.kwlist, ) + ignore_references: list[str] = field( + default_factory=list, + ) + ignore_paths: list[str] = field( + default_factory=list, + ) detect_looping_references: bool = True post_processing: list[PostProcessing] = field( default_factory=lambda: [PostProcessing.RUFF_FORMAT, PostProcessing.RUFF_CHECK], diff --git a/src/schema2validataclass/output/base_outputs.py b/src/schema2validataclass/output/base_outputs.py index 422cb86..aa20f66 100644 --- a/src/schema2validataclass/output/base_outputs.py +++ b/src/schema2validataclass/output/base_outputs.py @@ -291,9 +291,6 @@ def __init__(self, field: Object, config: Config, referencable_fields: dict[URI, references.append(field) field = follow_reference(field, referencable_fields=referencable_fields) - if str(field.uri.json_path) in self.config.ignored_uris: - continue - if field is None: continue diff --git a/src/schema2validataclass/schema/models.py b/src/schema2validataclass/schema/models.py index e2b3ce0..e92ac4d 100644 --- a/src/schema2validataclass/schema/models.py +++ b/src/schema2validataclass/schema/models.py @@ -185,8 +185,8 @@ def get_objects(self) -> list['Object']: result.extend(field.items.get_objects()) return result - def get_reference_base_uris(self) -> list[URI]: - return get_reference_base_uris(self.properties) + def get_reference_uris(self) -> list[URI]: + return get_reference_uris(self.properties) @dataclass(kw_only=True, init=False) @@ -212,12 +212,22 @@ def __init__(self, schema: dict, uri: URI): def properties(self) -> list[BaseField]: return self.contained_object.properties if self.contained_object else [] - def get_reference_base_uris(self) -> list[URI]: - reference_uris = get_reference_base_uris(self.definitions) + def get_reference_uris(self) -> list[URI]: + reference_uris = get_reference_uris(self.definitions) if self.contained_object: - reference_uris.extend(self.contained_object.get_reference_base_uris()) + reference_uris.extend(self.contained_object.get_reference_uris()) return reference_uris + def get_field_by_uri(self, uri: URI) -> BaseField | None: + for field in self.definitions: + if field.uri == uri: + return field + if self.contained_object: + for field in self.contained_object.properties: + if field.uri == uri: + return field + return None + def parse_schema(schema: dict, **kwargs) -> BaseField: # Special cases without type @@ -243,17 +253,14 @@ def parse_schema(schema: dict, **kwargs) -> BaseField: raise ValueError(f'Unsupported type: {schema.get("type")}') -def get_reference_base_uris(fields: list[BaseField]) -> list[URI]: +def get_reference_uris(fields: list[BaseField]) -> list[URI]: result: list[URI] = [] for field in fields: - # Incrementally look in children if isinstance(field, Object): - result.extend(field.get_reference_base_uris()) - - # Get References - if isinstance(field, Reference) and field.uri is not None: - result.append(URI.from_uri_without_json_path(field.to)) - if isinstance(field, Array) and isinstance(field.items, Reference) and field.items.uri is not None: - result.append(URI.from_uri_without_json_path(field.items.to)) + result.extend(field.get_reference_uris()) + if isinstance(field, Reference): + result.append(field.to) + if isinstance(field, Array) and isinstance(field.items, Reference): + result.append(field.items.to) return list(set(result)) diff --git a/tests/integration/dataclass/chained_schemas_ignore_test.py b/tests/integration/dataclass/chained_schemas_ignore_test.py new file mode 100644 index 0000000..33c5c03 --- /dev/null +++ b/tests/integration/dataclass/chained_schemas_ignore_test.py @@ -0,0 +1,94 @@ +""" +Copyright 2026 binary butterfly GmbH +Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt. +""" + +from pathlib import Path + +from schema2validataclass import App +from schema2validataclass.common.uri import URI +from schema2validataclass.config import Config, OutputFormat +from tests.integration.dataclass.helpers import INPUT_DIR, generated_files + +SCHEMA_PATH = INPUT_DIR / 'chained_schemas_ignore' / 'main_schema.json' + + +def run_generate(schema_path: Path, output_path: Path, **config_kwargs): + config = Config(output_format=OutputFormat.DATACLASS, **config_kwargs) + app = App(config=config) + app.generate(URI(file_path=schema_path), output_path) + + +def test_without_ignoring_generates_all_files(tmp_path: Path): + run_generate(SCHEMA_PATH, tmp_path) + assert generated_files(tmp_path) == { + '__init__.py', + 'simple_schema_input.py', + 'second_object_input.py', + 'third_object_input.py', + 'ignored_object_input.py', + } + + +def test_ignored_reference_not_loaded(tmp_path: Path): + run_generate(SCHEMA_PATH, tmp_path, ignore_references=['third_schema.json#/definitions/IgnoredObject']) + assert generated_files(tmp_path) == { + '__init__.py', + 'simple_schema_input.py', + 'second_object_input.py', + 'third_object_input.py', + } + + +def test_ignored_reference_property_removed_from_parent(tmp_path: Path): + run_generate(SCHEMA_PATH, tmp_path, ignore_references=['third_schema.json#/definitions/IgnoredObject']) + content = (tmp_path / 'second_object_input.py').read_text() + + assert 'IgnoredObject' not in content + assert 'ThirdObject' in content + + +def test_third_object_still_works(tmp_path: Path): + run_generate(SCHEMA_PATH, tmp_path, ignore_references=['third_schema.json#/definitions/IgnoredObject']) + content = (tmp_path / 'third_object_input.py').read_text() + + assert 'class ThirdObjectInput:' in content + assert 'third_string' in content + + +def test_ignored_path_not_loaded(tmp_path: Path): + run_generate( + SCHEMA_PATH, + tmp_path, + ignore_paths=['second_schema.json#/definitions/SecondObject/properties/IgnoredObject'], + ) + assert generated_files(tmp_path) == { + '__init__.py', + 'simple_schema_input.py', + 'second_object_input.py', + 'third_object_input.py', + } + + +def test_ignored_path_property_removed_from_parent(tmp_path: Path): + run_generate( + SCHEMA_PATH, + tmp_path, + ignore_paths=['second_schema.json#/definitions/SecondObject/properties/IgnoredObject'], + ) + content = (tmp_path / 'second_object_input.py').read_text() + + assert 'IgnoredObject' not in content + assert 'ThirdObject' in content + + +def test_ignored_path_third_object_still_works(tmp_path: Path): + run_generate( + SCHEMA_PATH, + tmp_path, + ignore_paths=['second_schema.json#/definitions/SecondObject/properties/IgnoredObject'], + ) + content = (tmp_path / 'third_object_input.py').read_text() + + assert 'class ThirdObjectInput:' in content + assert 'third_string' in content diff --git a/tests/integration/dataclass/chained_schemas_list_ignore_test.py b/tests/integration/dataclass/chained_schemas_list_ignore_test.py new file mode 100644 index 0000000..76b25a3 --- /dev/null +++ b/tests/integration/dataclass/chained_schemas_list_ignore_test.py @@ -0,0 +1,56 @@ +""" +Copyright 2026 binary butterfly GmbH +Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt. +""" + +from pathlib import Path + +from schema2validataclass import App +from schema2validataclass.common.uri import URI +from schema2validataclass.config import Config, OutputFormat +from tests.integration.dataclass.helpers import INPUT_DIR, generated_files + +SCHEMA_PATH = INPUT_DIR / 'chained_schemas_list_ignore' / 'main_schema.json' + + +def run_generate(schema_path: Path, output_path: Path, **config_kwargs): + config = Config(output_format=OutputFormat.DATACLASS, **config_kwargs) + app = App(config=config) + app.generate(URI(file_path=schema_path), output_path) + + +def test_without_ignoring_generates_all_files(tmp_path: Path): + run_generate(SCHEMA_PATH, tmp_path) + assert generated_files(tmp_path) == { + '__init__.py', + 'simple_schema_input.py', + 'second_object_input.py', + 'third_object_input.py', + 'ignored_object_input.py', + } + + +def test_ignored_reference_not_loaded(tmp_path: Path): + run_generate(SCHEMA_PATH, tmp_path, ignore_references=['third_schema.json#/definitions/IgnoredObject']) + assert generated_files(tmp_path) == { + '__init__.py', + 'simple_schema_input.py', + 'second_object_input.py', + 'third_object_input.py', + } + + +def test_ignored_reference_property_removed_from_parent(tmp_path: Path): + run_generate(SCHEMA_PATH, tmp_path, ignore_references=['third_schema.json#/definitions/IgnoredObject']) + content = (tmp_path / 'second_object_input.py').read_text() + + assert 'IgnoredObject' not in content + assert 'ThirdObject' in content + + +def test_third_object_still_works(tmp_path: Path): + run_generate(SCHEMA_PATH, tmp_path, ignore_references=['third_schema.json#/definitions/IgnoredObject']) + content = (tmp_path / 'third_object_input.py').read_text() + + assert 'class ThirdObjectInput:' in content + assert 'third_string' in content diff --git a/tests/integration/dataclass/chained_schemas_unused_test.py b/tests/integration/dataclass/chained_schemas_unused_test.py new file mode 100644 index 0000000..6c6d6b0 --- /dev/null +++ b/tests/integration/dataclass/chained_schemas_unused_test.py @@ -0,0 +1,36 @@ +""" +Copyright 2026 binary butterfly GmbH +Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt. +""" + +from pathlib import Path + +from tests.integration.dataclass.helpers import INPUT_DIR, generated_files, run_generate + +SCHEMA_PATH = INPUT_DIR / 'chained_schemas_unused' / 'main_schema.json' + + +def test_unused_object_not_generated(tmp_path: Path): + run_generate(SCHEMA_PATH, tmp_path) + assert generated_files(tmp_path) == { + '__init__.py', + 'simple_schema_input.py', + 'second_object_input.py', + 'third_object_input.py', + } + + +def test_main_class_references_second(tmp_path: Path): + run_generate(SCHEMA_PATH, tmp_path) + content = (tmp_path / 'simple_schema_input.py').read_text() + + assert 'class SimpleSchemaInput:' in content + + +def test_third_class_has_no_unused_object(tmp_path: Path): + run_generate(SCHEMA_PATH, tmp_path) + content = (tmp_path / 'third_object_input.py').read_text() + + assert 'class ThirdObjectInput:' in content + assert 'third_string' in content + assert 'UnusedObject' not in content diff --git a/tests/integration/dataclass/helpers.py b/tests/integration/dataclass/helpers.py index 6c1976c..131bb70 100644 --- a/tests/integration/dataclass/helpers.py +++ b/tests/integration/dataclass/helpers.py @@ -17,6 +17,6 @@ def generated_files(output_path: Path) -> set[str]: def run_generate(schema_path: Path, output_path: Path): - config = Config(output_format=OutputFormat.DATACLASS) + config = Config(output_format=OutputFormat.DATACLASS, post_processing=[]) app = App(config=config) app.generate(URI(file_path=schema_path), output_path) diff --git a/tests/integration/pydantic/chained_schemas_ignore_test.py b/tests/integration/pydantic/chained_schemas_ignore_test.py new file mode 100644 index 0000000..7e256e1 --- /dev/null +++ b/tests/integration/pydantic/chained_schemas_ignore_test.py @@ -0,0 +1,94 @@ +""" +Copyright 2026 binary butterfly GmbH +Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt. +""" + +from pathlib import Path + +from schema2validataclass import App +from schema2validataclass.common.uri import URI +from schema2validataclass.config import Config, OutputFormat +from tests.integration.pydantic.helpers import INPUT_DIR, generated_files + +SCHEMA_PATH = INPUT_DIR / 'chained_schemas_ignore' / 'main_schema.json' + + +def run_generate(schema_path: Path, output_path: Path, **config_kwargs): + config = Config(output_format=OutputFormat.PYDANTIC, **config_kwargs) + app = App(config=config) + app.generate(URI(file_path=schema_path), output_path) + + +def test_without_ignoring_generates_all_files(tmp_path: Path): + run_generate(SCHEMA_PATH, tmp_path) + assert generated_files(tmp_path) == { + '__init__.py', + 'simple_schema_input.py', + 'second_object_input.py', + 'third_object_input.py', + 'ignored_object_input.py', + } + + +def test_ignored_reference_not_loaded(tmp_path: Path): + run_generate(SCHEMA_PATH, tmp_path, ignore_references=['third_schema.json#/definitions/IgnoredObject']) + assert generated_files(tmp_path) == { + '__init__.py', + 'simple_schema_input.py', + 'second_object_input.py', + 'third_object_input.py', + } + + +def test_ignored_reference_property_removed_from_parent(tmp_path: Path): + run_generate(SCHEMA_PATH, tmp_path, ignore_references=['third_schema.json#/definitions/IgnoredObject']) + content = (tmp_path / 'second_object_input.py').read_text() + + assert 'IgnoredObject' not in content + assert 'ThirdObject' in content + + +def test_third_object_still_works(tmp_path: Path): + run_generate(SCHEMA_PATH, tmp_path, ignore_references=['third_schema.json#/definitions/IgnoredObject']) + content = (tmp_path / 'third_object_input.py').read_text() + + assert 'class ThirdObjectInput(BaseModel):' in content + assert 'third_string' in content + + +def test_ignored_path_not_loaded(tmp_path: Path): + run_generate( + SCHEMA_PATH, + tmp_path, + ignore_paths=['second_schema.json#/definitions/SecondObject/properties/IgnoredObject'], + ) + assert generated_files(tmp_path) == { + '__init__.py', + 'simple_schema_input.py', + 'second_object_input.py', + 'third_object_input.py', + } + + +def test_ignored_path_property_removed_from_parent(tmp_path: Path): + run_generate( + SCHEMA_PATH, + tmp_path, + ignore_paths=['second_schema.json#/definitions/SecondObject/properties/IgnoredObject'], + ) + content = (tmp_path / 'second_object_input.py').read_text() + + assert 'IgnoredObject' not in content + assert 'ThirdObject' in content + + +def test_ignored_path_third_object_still_works(tmp_path: Path): + run_generate( + SCHEMA_PATH, + tmp_path, + ignore_paths=['second_schema.json#/definitions/SecondObject/properties/IgnoredObject'], + ) + content = (tmp_path / 'third_object_input.py').read_text() + + assert 'class ThirdObjectInput(BaseModel):' in content + assert 'third_string' in content diff --git a/tests/integration/pydantic/chained_schemas_list_ignore_test.py b/tests/integration/pydantic/chained_schemas_list_ignore_test.py new file mode 100644 index 0000000..f261aa4 --- /dev/null +++ b/tests/integration/pydantic/chained_schemas_list_ignore_test.py @@ -0,0 +1,56 @@ +""" +Copyright 2026 binary butterfly GmbH +Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt. +""" + +from pathlib import Path + +from schema2validataclass import App +from schema2validataclass.common.uri import URI +from schema2validataclass.config import Config, OutputFormat +from tests.integration.pydantic.helpers import INPUT_DIR, generated_files + +SCHEMA_PATH = INPUT_DIR / 'chained_schemas_list_ignore' / 'main_schema.json' + + +def run_generate(schema_path: Path, output_path: Path, **config_kwargs): + config = Config(output_format=OutputFormat.PYDANTIC, **config_kwargs) + app = App(config=config) + app.generate(URI(file_path=schema_path), output_path) + + +def test_without_ignoring_generates_all_files(tmp_path: Path): + run_generate(SCHEMA_PATH, tmp_path) + assert generated_files(tmp_path) == { + '__init__.py', + 'simple_schema_input.py', + 'second_object_input.py', + 'third_object_input.py', + 'ignored_object_input.py', + } + + +def test_ignored_reference_not_loaded(tmp_path: Path): + run_generate(SCHEMA_PATH, tmp_path, ignore_references=['third_schema.json#/definitions/IgnoredObject']) + assert generated_files(tmp_path) == { + '__init__.py', + 'simple_schema_input.py', + 'second_object_input.py', + 'third_object_input.py', + } + + +def test_ignored_reference_property_removed_from_parent(tmp_path: Path): + run_generate(SCHEMA_PATH, tmp_path, ignore_references=['third_schema.json#/definitions/IgnoredObject']) + content = (tmp_path / 'second_object_input.py').read_text() + + assert 'IgnoredObject' not in content + assert 'ThirdObject' in content + + +def test_third_object_still_works(tmp_path: Path): + run_generate(SCHEMA_PATH, tmp_path, ignore_references=['third_schema.json#/definitions/IgnoredObject']) + content = (tmp_path / 'third_object_input.py').read_text() + + assert 'class ThirdObjectInput(BaseModel):' in content + assert 'third_string' in content diff --git a/tests/integration/pydantic/chained_schemas_unused_test.py b/tests/integration/pydantic/chained_schemas_unused_test.py new file mode 100644 index 0000000..612d715 --- /dev/null +++ b/tests/integration/pydantic/chained_schemas_unused_test.py @@ -0,0 +1,36 @@ +""" +Copyright 2026 binary butterfly GmbH +Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt. +""" + +from pathlib import Path + +from tests.integration.pydantic.helpers import INPUT_DIR, generated_files, run_generate + +SCHEMA_PATH = INPUT_DIR / 'chained_schemas_unused' / 'main_schema.json' + + +def test_unused_object_not_generated(tmp_path: Path): + run_generate(SCHEMA_PATH, tmp_path) + assert generated_files(tmp_path) == { + '__init__.py', + 'simple_schema_input.py', + 'second_object_input.py', + 'third_object_input.py', + } + + +def test_main_class_references_second(tmp_path: Path): + run_generate(SCHEMA_PATH, tmp_path) + content = (tmp_path / 'simple_schema_input.py').read_text() + + assert 'class SimpleSchemaInput(BaseModel):' in content + + +def test_third_class_has_no_unused_object(tmp_path: Path): + run_generate(SCHEMA_PATH, tmp_path) + content = (tmp_path / 'third_object_input.py').read_text() + + assert 'class ThirdObjectInput(BaseModel):' in content + assert 'third_string' in content + assert 'UnusedObject' not in content diff --git a/tests/integration/pydantic/helpers.py b/tests/integration/pydantic/helpers.py index dcf3516..54a8144 100644 --- a/tests/integration/pydantic/helpers.py +++ b/tests/integration/pydantic/helpers.py @@ -17,6 +17,6 @@ def generated_files(output_path: Path) -> set[str]: def run_generate(schema_path: Path, output_path: Path): - config = Config(output_format=OutputFormat.PYDANTIC) + config = Config(output_format=OutputFormat.PYDANTIC, post_processing=[]) app = App(config=config) app.generate(URI(file_path=schema_path), output_path) diff --git a/tests/integration/validataclass/chained_schemas_ignore_test.py b/tests/integration/validataclass/chained_schemas_ignore_test.py new file mode 100644 index 0000000..83431bf --- /dev/null +++ b/tests/integration/validataclass/chained_schemas_ignore_test.py @@ -0,0 +1,94 @@ +""" +Copyright 2026 binary butterfly GmbH +Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt. +""" + +from pathlib import Path + +from schema2validataclass import App +from schema2validataclass.common.uri import URI +from schema2validataclass.config import Config +from tests.integration.validataclass.helpers import INPUT_DIR, generated_files + +SCHEMA_PATH = INPUT_DIR / 'chained_schemas_ignore' / 'main_schema.json' + + +def run_generate(schema_path: Path, output_path: Path, **config_kwargs): + config = Config(**config_kwargs) + app = App(config=config) + app.generate(URI(file_path=schema_path), output_path) + + +def test_without_ignoring_generates_all_files(tmp_path: Path): + run_generate(SCHEMA_PATH, tmp_path) + assert generated_files(tmp_path) == { + '__init__.py', + 'simple_schema_input.py', + 'second_object_input.py', + 'third_object_input.py', + 'ignored_object_input.py', + } + + +def test_ignored_reference_not_loaded(tmp_path: Path): + run_generate(SCHEMA_PATH, tmp_path, ignore_references=['third_schema.json#/definitions/IgnoredObject']) + assert generated_files(tmp_path) == { + '__init__.py', + 'simple_schema_input.py', + 'second_object_input.py', + 'third_object_input.py', + } + + +def test_ignored_reference_property_removed_from_parent(tmp_path: Path): + run_generate(SCHEMA_PATH, tmp_path, ignore_references=['third_schema.json#/definitions/IgnoredObject']) + content = (tmp_path / 'second_object_input.py').read_text() + + assert 'IgnoredObject' not in content + assert 'ThirdObject' in content + + +def test_third_object_still_works(tmp_path: Path): + run_generate(SCHEMA_PATH, tmp_path, ignore_references=['third_schema.json#/definitions/IgnoredObject']) + content = (tmp_path / 'third_object_input.py').read_text() + + assert 'class ThirdObjectInput(ValidataclassMixin):' in content + assert 'third_string' in content + + +def test_ignored_path_not_loaded(tmp_path: Path): + run_generate( + SCHEMA_PATH, + tmp_path, + ignore_paths=['second_schema.json#/definitions/SecondObject/properties/IgnoredObject'], + ) + assert generated_files(tmp_path) == { + '__init__.py', + 'simple_schema_input.py', + 'second_object_input.py', + 'third_object_input.py', + } + + +def test_ignored_path_property_removed_from_parent(tmp_path: Path): + run_generate( + SCHEMA_PATH, + tmp_path, + ignore_paths=['second_schema.json#/definitions/SecondObject/properties/IgnoredObject'], + ) + content = (tmp_path / 'second_object_input.py').read_text() + + assert 'IgnoredObject' not in content + assert 'ThirdObject' in content + + +def test_ignored_path_third_object_still_works(tmp_path: Path): + run_generate( + SCHEMA_PATH, + tmp_path, + ignore_paths=['second_schema.json#/definitions/SecondObject/properties/IgnoredObject'], + ) + content = (tmp_path / 'third_object_input.py').read_text() + + assert 'class ThirdObjectInput(ValidataclassMixin):' in content + assert 'third_string' in content diff --git a/tests/integration/validataclass/chained_schemas_list_ignore_test.py b/tests/integration/validataclass/chained_schemas_list_ignore_test.py new file mode 100644 index 0000000..eb5f017 --- /dev/null +++ b/tests/integration/validataclass/chained_schemas_list_ignore_test.py @@ -0,0 +1,56 @@ +""" +Copyright 2026 binary butterfly GmbH +Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt. +""" + +from pathlib import Path + +from schema2validataclass import App +from schema2validataclass.common.uri import URI +from schema2validataclass.config import Config +from tests.integration.validataclass.helpers import INPUT_DIR, generated_files + +SCHEMA_PATH = INPUT_DIR / 'chained_schemas_list_ignore' / 'main_schema.json' + + +def run_generate(schema_path: Path, output_path: Path, **config_kwargs): + config = Config(**config_kwargs) + app = App(config=config) + app.generate(URI(file_path=schema_path), output_path) + + +def test_without_ignoring_generates_all_files(tmp_path: Path): + run_generate(SCHEMA_PATH, tmp_path) + assert generated_files(tmp_path) == { + '__init__.py', + 'simple_schema_input.py', + 'second_object_input.py', + 'third_object_input.py', + 'ignored_object_input.py', + } + + +def test_ignored_reference_not_loaded(tmp_path: Path): + run_generate(SCHEMA_PATH, tmp_path, ignore_references=['third_schema.json#/definitions/IgnoredObject']) + assert generated_files(tmp_path) == { + '__init__.py', + 'simple_schema_input.py', + 'second_object_input.py', + 'third_object_input.py', + } + + +def test_ignored_reference_property_removed_from_parent(tmp_path: Path): + run_generate(SCHEMA_PATH, tmp_path, ignore_references=['third_schema.json#/definitions/IgnoredObject']) + content = (tmp_path / 'second_object_input.py').read_text() + + assert 'IgnoredObject' not in content + assert 'ThirdObject' in content + + +def test_third_object_still_works(tmp_path: Path): + run_generate(SCHEMA_PATH, tmp_path, ignore_references=['third_schema.json#/definitions/IgnoredObject']) + content = (tmp_path / 'third_object_input.py').read_text() + + assert 'class ThirdObjectInput(ValidataclassMixin):' in content + assert 'third_string' in content diff --git a/tests/integration/validataclass/chained_schemas_unused_test.py b/tests/integration/validataclass/chained_schemas_unused_test.py new file mode 100644 index 0000000..ac8b611 --- /dev/null +++ b/tests/integration/validataclass/chained_schemas_unused_test.py @@ -0,0 +1,36 @@ +""" +Copyright 2026 binary butterfly GmbH +Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt. +""" + +from pathlib import Path + +from tests.integration.validataclass.helpers import INPUT_DIR, generated_files, run_generate + +SCHEMA_PATH = INPUT_DIR / 'chained_schemas_unused' / 'main_schema.json' + + +def test_unused_object_not_generated(tmp_path: Path): + run_generate(SCHEMA_PATH, tmp_path) + assert generated_files(tmp_path) == { + '__init__.py', + 'simple_schema_input.py', + 'second_object_input.py', + 'third_object_input.py', + } + + +def test_main_class_references_second(tmp_path: Path): + run_generate(SCHEMA_PATH, tmp_path) + content = (tmp_path / 'simple_schema_input.py').read_text() + + assert 'class SimpleSchemaInput(ValidataclassMixin):' in content + + +def test_third_class_has_no_unused_object(tmp_path: Path): + run_generate(SCHEMA_PATH, tmp_path) + content = (tmp_path / 'third_object_input.py').read_text() + + assert 'class ThirdObjectInput(ValidataclassMixin):' in content + assert 'third_string' in content + assert 'UnusedObject' not in content diff --git a/tests/integration/validataclass/helpers.py b/tests/integration/validataclass/helpers.py index 8f0817a..ce37a9e 100644 --- a/tests/integration/validataclass/helpers.py +++ b/tests/integration/validataclass/helpers.py @@ -7,6 +7,7 @@ from schema2validataclass import App from schema2validataclass.common.uri import URI +from schema2validataclass.config import Config INPUT_DIR = Path(__file__).resolve().parent.parent.parent / 'test_schema' / 'input' @@ -16,5 +17,6 @@ def generated_files(output_path: Path) -> set[str]: def run_generate(schema_path: Path, output_path: Path): - app = App() + config = Config(post_processing=[]) + app = App(config=config) app.generate(URI(file_path=schema_path), output_path) diff --git a/tests/test_schema/input/chained_schemas_ignore/main_schema.json b/tests/test_schema/input/chained_schemas_ignore/main_schema.json new file mode 100644 index 0000000..50fbb21 --- /dev/null +++ b/tests/test_schema/input/chained_schemas_ignore/main_schema.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "title": "Simple Schema", + "properties": { + "SecondObject": { + "$ref": "second_schema.json#/definitions/SecondObject" + } + } +} diff --git a/tests/test_schema/input/chained_schemas_ignore/second_schema.json b/tests/test_schema/input/chained_schemas_ignore/second_schema.json new file mode 100644 index 0000000..ffea0ef --- /dev/null +++ b/tests/test_schema/input/chained_schemas_ignore/second_schema.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "definitions": { + "SecondObject": { + "type": "object", + "properties": { + "ThirdObject": { + "$ref": "third_schema.json#/definitions/ThirdObject" + }, + "IgnoredObject": { + "$ref": "third_schema.json#/definitions/IgnoredObject" + } + } + } + } +} diff --git a/tests/test_schema/input/chained_schemas_ignore/third_schema.json b/tests/test_schema/input/chained_schemas_ignore/third_schema.json new file mode 100644 index 0000000..4e40fa1 --- /dev/null +++ b/tests/test_schema/input/chained_schemas_ignore/third_schema.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "definitions": { + "ThirdObject": { + "type": "object", + "properties": { + "third_string": { + "type": "string" + } + } + }, + "IgnoredObject": { + "type": "object", + "properties": { + "ignored_string": { + "type": "string" + } + } + } + } +} diff --git a/tests/test_schema/input/chained_schemas_list_ignore/main_schema.json b/tests/test_schema/input/chained_schemas_list_ignore/main_schema.json new file mode 100644 index 0000000..50fbb21 --- /dev/null +++ b/tests/test_schema/input/chained_schemas_list_ignore/main_schema.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "title": "Simple Schema", + "properties": { + "SecondObject": { + "$ref": "second_schema.json#/definitions/SecondObject" + } + } +} diff --git a/tests/test_schema/input/chained_schemas_list_ignore/second_schema.json b/tests/test_schema/input/chained_schemas_list_ignore/second_schema.json new file mode 100644 index 0000000..57c8fed --- /dev/null +++ b/tests/test_schema/input/chained_schemas_list_ignore/second_schema.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "definitions": { + "SecondObject": { + "type": "object", + "properties": { + "ThirdObject": { + "$ref": "third_schema.json#/definitions/ThirdObject" + }, + "IgnoredObject": { + "type": "array", + "items": { + "$ref": "third_schema.json#/definitions/IgnoredObject" + } + } + } + } + } +} diff --git a/tests/test_schema/input/chained_schemas_list_ignore/third_schema.json b/tests/test_schema/input/chained_schemas_list_ignore/third_schema.json new file mode 100644 index 0000000..4e40fa1 --- /dev/null +++ b/tests/test_schema/input/chained_schemas_list_ignore/third_schema.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "definitions": { + "ThirdObject": { + "type": "object", + "properties": { + "third_string": { + "type": "string" + } + } + }, + "IgnoredObject": { + "type": "object", + "properties": { + "ignored_string": { + "type": "string" + } + } + } + } +} diff --git a/tests/test_schema/input/chained_schemas_unused/main_schema.json b/tests/test_schema/input/chained_schemas_unused/main_schema.json new file mode 100644 index 0000000..50fbb21 --- /dev/null +++ b/tests/test_schema/input/chained_schemas_unused/main_schema.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "title": "Simple Schema", + "properties": { + "SecondObject": { + "$ref": "second_schema.json#/definitions/SecondObject" + } + } +} diff --git a/tests/test_schema/input/chained_schemas_unused/second_schema.json b/tests/test_schema/input/chained_schemas_unused/second_schema.json new file mode 100644 index 0000000..c382912 --- /dev/null +++ b/tests/test_schema/input/chained_schemas_unused/second_schema.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "definitions": { + "SecondObject": { + "type": "object", + "properties": { + "ThirdObject": { + "$ref": "third_schema.json#/definitions/ThirdObject" + } + } + } + } +} diff --git a/tests/test_schema/input/chained_schemas_unused/third_schema.json b/tests/test_schema/input/chained_schemas_unused/third_schema.json new file mode 100644 index 0000000..5cf89ce --- /dev/null +++ b/tests/test_schema/input/chained_schemas_unused/third_schema.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "definitions": { + "ThirdObject": { + "type": "object", + "properties": { + "third_string": { + "type": "string" + } + } + }, + "UnusedObject": { + "type": "object", + "properties": { + "ignored_string": { + "type": "string" + } + } + } + } +} diff --git a/tests/test_schema/input/ignored_classnames/exclusive_child_schema.json b/tests/test_schema/input/ignored_classnames/exclusive_child_schema.json new file mode 100644 index 0000000..f1f7f4c --- /dev/null +++ b/tests/test_schema/input/ignored_classnames/exclusive_child_schema.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "definitions": { + "ExclusiveChild": { + "type": "object", + "properties": { + "exclusive_value": { + "type": "string" + } + } + } + } +} diff --git a/tests/test_schema/input/ignored_classnames/ignored_schema.json b/tests/test_schema/input/ignored_classnames/ignored_schema.json new file mode 100644 index 0000000..3c34396 --- /dev/null +++ b/tests/test_schema/input/ignored_classnames/ignored_schema.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "definitions": { + "IgnoredObject": { + "type": "object", + "properties": { + "shared_child": { + "$ref": "shared_child_schema.json#/definitions/SharedChild" + }, + "exclusive_child": { + "$ref": "exclusive_child_schema.json#/definitions/ExclusiveChild" + }, + "ignored_string": { + "type": "string" + } + } + } + } +} diff --git a/tests/test_schema/input/ignored_classnames/kept_schema.json b/tests/test_schema/input/ignored_classnames/kept_schema.json new file mode 100644 index 0000000..5a32484 --- /dev/null +++ b/tests/test_schema/input/ignored_classnames/kept_schema.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "definitions": { + "KeptObject": { + "type": "object", + "properties": { + "shared_child": { + "$ref": "shared_child_schema.json#/definitions/SharedChild" + }, + "kept_string": { + "type": "string" + } + } + } + } +} diff --git a/tests/test_schema/input/ignored_classnames/main_schema.json b/tests/test_schema/input/ignored_classnames/main_schema.json new file mode 100644 index 0000000..3f5f945 --- /dev/null +++ b/tests/test_schema/input/ignored_classnames/main_schema.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "title": "Main Schema", + "properties": { + "kept_object": { + "$ref": "kept_schema.json#/definitions/KeptObject" + }, + "ignored_object": { + "$ref": "ignored_schema.json#/definitions/IgnoredObject" + }, + "ignored_list": { + "type": "array", + "items": { + "$ref": "ignored_schema.json#/definitions/IgnoredObject" + } + }, + "normal_field": { + "type": "string" + } + } +} diff --git a/tests/test_schema/input/ignored_classnames/shared_child_schema.json b/tests/test_schema/input/ignored_classnames/shared_child_schema.json new file mode 100644 index 0000000..b5d4227 --- /dev/null +++ b/tests/test_schema/input/ignored_classnames/shared_child_schema.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "definitions": { + "SharedChild": { + "type": "object", + "properties": { + "shared_value": { + "type": "string" + } + } + } + } +} diff --git a/tests/unit/get_reference_base_uris_test.py b/tests/unit/get_reference_base_uris_test.py index f6d888d..c6db9f2 100644 --- a/tests/unit/get_reference_base_uris_test.py +++ b/tests/unit/get_reference_base_uris_test.py @@ -3,12 +3,12 @@ Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt. """ -from schema2validataclass.schema.models import Array, Integer, Object, Reference, String, get_reference_base_uris +from schema2validataclass.schema.models import Array, Integer, Object, Reference, String, get_reference_uris from tests.unit.helpers import make_uri def test_empty_list(): - assert get_reference_base_uris([]) == [] + assert get_reference_uris([]) == [] def test_non_reference_fields_ignored(): @@ -16,14 +16,14 @@ def test_non_reference_fields_ignored(): String({'type': 'string'}, uri=make_uri()), Integer({'type': 'integer'}, uri=make_uri()), ] - assert get_reference_base_uris(fields) == [] + assert get_reference_uris(fields) == [] def test_reference_field(): ref = Reference({'$ref': 'other.json#/definitions/Foo'}, uri=make_uri()) - result = get_reference_base_uris([ref]) + result = get_reference_uris([ref]) assert len(result) == 1 - assert result[0].json_path == '' + assert result[0].json_path == '/definitions/Foo' def test_array_with_reference_items(): @@ -34,7 +34,7 @@ def test_array_with_reference_items(): }, uri=make_uri(), ) - result = get_reference_base_uris([arr]) + result = get_reference_uris([arr]) assert len(result) == 1 @@ -46,7 +46,7 @@ def test_array_with_non_reference_items(): }, uri=make_uri(), ) - assert get_reference_base_uris([arr]) == [] + assert get_reference_uris([arr]) == [] def test_object_with_reference_property(): @@ -59,21 +59,28 @@ def test_object_with_reference_property(): }, uri=make_uri(), ) - result = get_reference_base_uris([obj]) + result = get_reference_uris([obj]) assert len(result) == 1 def test_duplicate_references_deduplicated(): ref1 = Reference({'$ref': 'other.json#/definitions/Foo'}, uri=make_uri()) - ref2 = Reference({'$ref': 'other.json#/definitions/Bar'}, uri=make_uri()) - result = get_reference_base_uris([ref1, ref2]) + ref2 = Reference({'$ref': 'other.json#/definitions/Foo'}, uri=make_uri()) + result = get_reference_uris([ref1, ref2]) assert len(result) == 1 +def test_same_file_different_definitions_not_deduplicated(): + ref1 = Reference({'$ref': 'other.json#/definitions/Foo'}, uri=make_uri()) + ref2 = Reference({'$ref': 'other.json#/definitions/Bar'}, uri=make_uri()) + result = get_reference_uris([ref1, ref2]) + assert len(result) == 2 + + def test_different_file_references_not_deduplicated(): ref1 = Reference({'$ref': 'one.json#/definitions/A'}, uri=make_uri()) ref2 = Reference({'$ref': 'two.json#/definitions/B'}, uri=make_uri()) - result = get_reference_base_uris([ref1, ref2]) + result = get_reference_uris([ref1, ref2]) assert len(result) == 2 @@ -83,5 +90,5 @@ def test_mixed_field_types(): Reference({'$ref': 'other.json#/definitions/Foo'}, uri=make_uri()), Integer({'type': 'integer'}, uri=make_uri()), ] - result = get_reference_base_uris(fields) + result = get_reference_uris(fields) assert len(result) == 1 diff --git a/tests/unit/object_get_reference_base_uris_test.py b/tests/unit/object_get_reference_base_uris_test.py index 1ef4a14..b31338c 100644 --- a/tests/unit/object_get_reference_base_uris_test.py +++ b/tests/unit/object_get_reference_base_uris_test.py @@ -15,7 +15,7 @@ def test_no_references(): }, uri=make_uri(), ) - assert obj.get_reference_base_uris() == [] + assert obj.get_reference_uris() == [] def test_direct_reference(): @@ -28,9 +28,9 @@ def test_direct_reference(): }, uri=make_uri(), ) - uris = obj.get_reference_base_uris() + uris = obj.get_reference_uris() assert len(uris) == 1 - assert uris[0].json_path == '' + assert uris[0].json_path == '/definitions/Foo' def test_reference_in_array_items(): @@ -46,25 +46,40 @@ def test_reference_in_array_items(): }, uri=make_uri(), ) - uris = obj.get_reference_base_uris() + uris = obj.get_reference_uris() assert len(uris) == 1 -def test_multiple_references_to_same_file_deduplicated(): +def test_multiple_references_to_same_definition_deduplicated(): obj = Object( { 'type': 'object', 'properties': { 'a': {'$ref': 'other.json#/definitions/Foo'}, - 'b': {'$ref': 'other.json#/definitions/Bar'}, + 'b': {'$ref': 'other.json#/definitions/Foo'}, }, }, uri=make_uri(), ) - uris = obj.get_reference_base_uris() + uris = obj.get_reference_uris() assert len(uris) == 1 +def test_same_file_different_definitions_not_deduplicated(): + obj = Object( + { + 'type': 'object', + 'properties': { + 'a': {'$ref': 'other.json#/definitions/Foo'}, + 'b': {'$ref': 'other.json#/definitions/Bar'}, + }, + }, + uri=make_uri(), + ) + uris = obj.get_reference_uris() + assert len(uris) == 2 + + def test_references_to_different_files(): obj = Object( { @@ -76,7 +91,7 @@ def test_references_to_different_files(): }, uri=make_uri(), ) - uris = obj.get_reference_base_uris() + uris = obj.get_reference_uris() assert len(uris) == 2 @@ -95,5 +110,5 @@ def test_reference_in_nested_object(): }, uri=make_uri(), ) - uris = obj.get_reference_base_uris() + uris = obj.get_reference_uris() assert len(uris) == 1 diff --git a/tests/unit/schema_get_reference_base_uris_test.py b/tests/unit/schema_get_reference_base_uris_test.py index dba196e..c6519d7 100644 --- a/tests/unit/schema_get_reference_base_uris_test.py +++ b/tests/unit/schema_get_reference_base_uris_test.py @@ -17,7 +17,7 @@ def test_from_object_properties(): }, uri=make_uri(), ) - uris = schema.get_reference_base_uris() + uris = schema.get_reference_uris() assert len(uris) == 1 @@ -30,7 +30,7 @@ def test_from_definitions(): }, uri=make_uri(), ) - uris = schema.get_reference_base_uris() + uris = schema.get_reference_uris() assert len(uris) == 1 @@ -47,7 +47,7 @@ def test_from_both(): }, uri=make_uri(), ) - uris = schema.get_reference_base_uris() + uris = schema.get_reference_uris() assert len(uris) == 2 @@ -59,7 +59,7 @@ def test_no_references(): }, uri=make_uri(), ) - assert schema.get_reference_base_uris() == [] + assert schema.get_reference_uris() == [] def test_definitions_only_no_object(): @@ -71,5 +71,5 @@ def test_definitions_only_no_object(): }, uri=make_uri(), ) - uris = schema.get_reference_base_uris() + uris = schema.get_reference_uris() assert len(uris) == 1