From 4e4848fb9f03989599b2c8cc957dc30b4496afa3 Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Wed, 18 Nov 2020 01:35:07 +0900 Subject: [PATCH] fix external definitions (#265) --- datamodel_code_generator/parser/jsonschema.py | 5 ++++ datamodel_code_generator/reference.py | 9 ++++++- .../main/main_external_definitions/output.py | 15 +++++++++++ .../data/jsonschema/external_definitions.json | 7 ++++++ .../jsonschema/external_definitions_root.json | 14 +++++++++++ tests/test_main.py | 25 +++++++++++++++++++ 6 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 tests/data/expected/main/main_external_definitions/output.py create mode 100644 tests/data/jsonschema/external_definitions.json create mode 100644 tests/data/jsonschema/external_definitions_root.json diff --git a/datamodel_code_generator/parser/jsonschema.py b/datamodel_code_generator/parser/jsonschema.py index 7d670db85..beee1d487 100644 --- a/datamodel_code_generator/parser/jsonschema.py +++ b/datamodel_code_generator/parser/jsonschema.py @@ -708,6 +708,8 @@ def parse_ref(self, obj: JsonSchemaObject, path: List[str]) -> None: if obj.ref.startswith('#'): # Local Reference – $ref: '#/definitions/myElement' pass + elif self.model_resolver.is_after_load(obj.ref): + pass else: if ( not obj.ref.startswith(('https://', 'http://')) @@ -811,6 +813,9 @@ def parse_raw(self) -> None: isinstance(self.source, Path) and self.source.is_dir() ): self.current_source_path = Path() + self.model_resolver.after_load_files = [ + str(s.path) for s in self.iter_source + ] for source in self.iter_source: if self.current_source_path is not None: self.current_source_path = source.path diff --git a/datamodel_code_generator/reference.py b/datamodel_code_generator/reference.py index 4bbf099bc..c7244eb43 100644 --- a/datamodel_code_generator/reference.py +++ b/datamodel_code_generator/reference.py @@ -37,6 +37,7 @@ def __init__(self, aliases: Optional[Mapping[str, str]] = None) -> None: self._current_root: List[str] = [] self._root_id_base_path: Optional[str] = None self.ids: DefaultDict[str, Dict[str, str]] = defaultdict(dict) + self.after_load_files: List[str] = [] @property def current_root(self) -> List[str]: @@ -67,6 +68,9 @@ def _get_path(self, path: List[str]) -> str: return f'{self.root_id_base_path}/{joined_path}#/' return f'{joined_path}#/' + def is_after_load(self, ref: str) -> bool: + return '#/' in ref and ref.split('#/', 1)[0] in self.after_load_files + def add_ref(self, ref: str, actual_module_name: Optional[str] = None) -> Reference: path = self._get_path(ref.split('/')) reference = self.references.get(path) @@ -78,7 +82,10 @@ def add_ref(self, ref: str, actual_module_name: Optional[str] = None) -> Referen parents, original_name = self.root_id_base_path, Path(split_ref[0]).stem else: parents, original_name = split_ref - loaded: bool = not ref.startswith(('https://', 'http://')) + if self.is_after_load(ref): + loaded: bool = False + else: + loaded = '#/' not in ref if not original_name: original_name = Path(parents).stem # type: ignore loaded = False diff --git a/tests/data/expected/main/main_external_definitions/output.py b/tests/data/expected/main/main_external_definitions/output.py new file mode 100644 index 000000000..76e8f099e --- /dev/null +++ b/tests/data/expected/main/main_external_definitions/output.py @@ -0,0 +1,15 @@ +# generated by datamodel-codegen: +# filename: external_definitions_root.json +# timestamp: 2019-07-26T00:00:00+00:00 + +from __future__ import annotations + +from pydantic import BaseModel, constr + + +class ElegantName(BaseModel): + __root__: constr(min_length=3) + + +class Person(BaseModel): + name: ElegantName diff --git a/tests/data/jsonschema/external_definitions.json b/tests/data/jsonschema/external_definitions.json new file mode 100644 index 000000000..ebbdec761 --- /dev/null +++ b/tests/data/jsonschema/external_definitions.json @@ -0,0 +1,7 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "elegantName": { + "type": "string", + "minLength": 3 + } +} \ No newline at end of file diff --git a/tests/data/jsonschema/external_definitions_root.json b/tests/data/jsonschema/external_definitions_root.json new file mode 100644 index 000000000..c7b7f74cc --- /dev/null +++ b/tests/data/jsonschema/external_definitions_root.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Person", + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "$ref": "external_definitions.json#/elegantName" + } + }, + "required": [ + "name" + ] +} \ No newline at end of file diff --git a/tests/test_main.py b/tests/test_main.py index 954a298d0..49cdcc24f 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1064,3 +1064,28 @@ def test_main_use_standard_collections(tmpdir_factory: TempdirFactory) -> None: path.relative_to(main_use_standard_collections_dir) ).read_text() assert result == path.read_text() + + +@freeze_time('2019-07-26') +def test_main_external_definitions(): + with TemporaryDirectory() as output_dir: + output_file: Path = Path(output_dir) / 'output.py' + return_code: Exit = main( + [ + '--input', + str(JSON_SCHEMA_DATA_PATH / 'external_definitions_root.json'), + '--output', + str(output_file), + '--input-file-type', + 'jsonschema', + ] + ) + assert return_code == Exit.OK + assert ( + output_file.read_text() + == ( + EXPECTED_MAIN_PATH / 'main_external_definitions' / 'output.py' + ).read_text() + ) + with pytest.raises(SystemExit): + main()