diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index db0e625ab..9d27d4358 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: 'v0.4.7'
+ rev: 'v0.4.8'
hooks:
- id: ruff
files: "^datamodel_code_generator|^tests"
diff --git a/README.md b/README.md
index 5129c61d6..7bbb143c9 100644
--- a/README.md
+++ b/README.md
@@ -16,9 +16,6 @@ and [msgspec.Struct](https://github.com/jcrist/msgspec) from an openapi file and
## Help
See [documentation](https://koxudaxi.github.io/datamodel-code-generator) for more details.
-## Sponsors
-[![JetBrains](https://avatars.githubusercontent.com/u/60931315?s=200&v=4)](https://github.com/JetBrainsOfficial)
-
## Quick Installation
To install `datamodel-code-generator`:
@@ -236,6 +233,45 @@ class Apis(BaseModel):
```
+## Supported input types
+- OpenAPI 3 (YAML/JSON, [OpenAPI Data Type](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#data-types));
+- JSON Schema ([JSON Schema Core](http://json-schema.org/draft/2019-09/json-schema-validation.html)/[JSON Schema Validation](http://json-schema.org/draft/2019-09/json-schema-validation.html));
+- JSON/YAML/CSV Data (it will be converted to JSON Schema);
+- Python dictionary (it will be converted to JSON Schema);
+- GraphQL schema ([GraphQL Schemas and Types](https://graphql.org/learn/schema/));
+
+## Supported output types
+- [pydantic](https://docs.pydantic.dev/1.10/).BaseModel;
+- [pydantic_v2](https://docs.pydantic.dev/2.0/).BaseModel;
+- [dataclasses.dataclass](https://docs.python.org/3/library/dataclasses.html);
+- [typing.TypedDict](https://docs.python.org/3/library/typing.html#typing.TypedDict);
+- [msgspec.Struct](https://github.com/jcrist/msgspec);
+- Custom type from your [jinja2](https://jinja.palletsprojects.com/en/3.1.x/) template;
+
+## Sponsors
+
+
## Projects that use datamodel-code-generator
These OSS projects use datamodel-code-generator to generate many models.
@@ -267,21 +303,6 @@ See the following linked projects for real world examples and inspiration.
- [SeldonIO/MLServer](https://github.com/SeldonIO/MLServer)
- *[generate-types.sh](https://github.com/SeldonIO/MLServer/blob/master/hack/generate-types.sh)*
-## Supported input types
-- OpenAPI 3 (YAML/JSON, [OpenAPI Data Type](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#data-types));
-- JSON Schema ([JSON Schema Core](http://json-schema.org/draft/2019-09/json-schema-validation.html)/[JSON Schema Validation](http://json-schema.org/draft/2019-09/json-schema-validation.html));
-- JSON/YAML/CSV Data (it will be converted to JSON Schema);
-- Python dictionary (it will be converted to JSON Schema);
-- GraphQL schema ([GraphQL Schemas and Types](https://graphql.org/learn/schema/));
-
-## Supported output types
-- [pydantic](https://docs.pydantic.dev/1.10/).BaseModel;
-- [pydantic_v2](https://docs.pydantic.dev/2.0/).BaseModel;
-- [dataclasses.dataclass](https://docs.python.org/3/library/dataclasses.html);
-- [typing.TypedDict](https://docs.python.org/3/library/typing.html#typing.TypedDict);
-- [msgspec.Struct](https://github.com/jcrist/msgspec);
-- Custom type from your [jinja2](https://jinja.palletsprojects.com/en/3.1.x/) template;
-
## Installation
To install `datamodel-code-generator`:
@@ -319,6 +340,7 @@ This method needs the [http extra option](#http-extra-option)
## All Command Options
The `datamodel-codegen` command:
+
```bash
usage:
datamodel-codegen [options]
diff --git a/datamodel_code_generator/parser/base.py b/datamodel_code_generator/parser/base.py
index 3961208b6..8a8ea898c 100644
--- a/datamodel_code_generator/parser/base.py
+++ b/datamodel_code_generator/parser/base.py
@@ -700,6 +700,7 @@ def __change_from_import(
from_, import_ = full_path = relative(
model.module_name, data_type.full_name
)
+ import_ = import_.replace('-', '_')
alias = scoped_model_resolver.add(full_path, import_).name
@@ -778,8 +779,18 @@ def __apply_discriminator_type(
discriminator_model.path.split('#/')[-1]
!= path.split('#/')[-1]
):
- # TODO: support external reference
- continue
+ if (
+ path.startswith('#/')
+ or discriminator_model.path[:-1]
+ != path.split('/')[-1]
+ ):
+ t_path = path[str(path).find('/') + 1 :]
+ t_disc = discriminator_model.path[
+ : str(discriminator_model.path).find('#')
+ ].lstrip('../')
+ t_disc_2 = '/'.join(t_disc.split('/')[1:])
+ if t_path != t_disc and t_path != t_disc_2:
+ continue
type_names.append(name)
else:
type_names = [discriminator_model.path.split('/')[-1]]
@@ -1250,6 +1261,7 @@ class Processed(NamedTuple):
init = True
else:
module = (*module[:-1], f'{module[-1]}.py')
+ module = tuple(part.replace('-', '_') for part in module)
else:
module = ('__init__.py',)
diff --git a/docs/index.md b/docs/index.md
index 071a6a9f5..6291035b2 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -12,9 +12,6 @@ This code generator creates [pydantic v1 and v2](https://docs.pydantic.dev/) mod
[![Pydantic v1](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/pydantic/pydantic/main/docs/badge/v1.json)](https://pydantic.dev)
[![Pydantic v2](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/pydantic/pydantic/main/docs/badge/v2.json)](https://pydantic.dev)
-## Sponsors
-[![JetBrains](https://avatars.githubusercontent.com/u/60931315?s=200&v=4)](https://github.com/JetBrainsOfficial)
-
## Quick Installation
To install `datamodel-code-generator`:
@@ -232,6 +229,44 @@ class Apis(BaseModel):
```
+## Supported input types
+- OpenAPI 3 (YAML/JSON, [OpenAPI Data Type](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#data-types));
+- JSON Schema ([JSON Schema Core](http://json-schema.org/draft/2019-09/json-schema-validation.html)/[JSON Schema Validation](http://json-schema.org/draft/2019-09/json-schema-validation.html));
+- JSON/YAML/CSV Data (it will be converted to JSON Schema);
+- Python dictionary (it will be converted to JSON Schema);
+- GraphQL schema ([GraphQL Schemas and Types](https://graphql.org/learn/schema/));
+
+## Supported output types
+- [pydantic](https://docs.pydantic.dev/1.10/).BaseModel;
+- [pydantic_v2](https://docs.pydantic.dev/2.0/).BaseModel;
+- [dataclasses.dataclass](https://docs.python.org/3/library/dataclasses.html);
+- [typing.TypedDict](https://docs.python.org/3/library/typing.html#typing.TypedDict);
+- [msgspec.Struct](https://github.com/jcrist/msgspec);
+- Custom type from your [jinja2](https://jinja.palletsprojects.com/en/3.1.x) template;
+
+## Sponsors
+
## Projects that use datamodel-code-generator
These OSS projects use datamodel-code-generator to generate many models.
@@ -263,21 +298,6 @@ See the following linked projects for real world examples and inspiration.
- [SeldonIO/MLServer](https://github.com/SeldonIO/MLServer)
- *[generate-types.sh](https://github.com/SeldonIO/MLServer/blob/master/hack/generate-types.sh)*
-## Supported input types
-- OpenAPI 3 (YAML/JSON, [OpenAPI Data Type](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#data-types));
-- JSON Schema ([JSON Schema Core](http://json-schema.org/draft/2019-09/json-schema-validation.html)/[JSON Schema Validation](http://json-schema.org/draft/2019-09/json-schema-validation.html));
-- JSON/YAML/CSV Data (it will be converted to JSON Schema);
-- Python dictionary (it will be converted to JSON Schema);
-- GraphQL schema ([GraphQL Schemas and Types](https://graphql.org/learn/schema/));
-
-## Supported output types
-- [pydantic](https://docs.pydantic.dev/1.10/).BaseModel;
-- [pydantic_v2](https://docs.pydantic.dev/2.0/).BaseModel;
-- [dataclasses.dataclass](https://docs.python.org/3/library/dataclasses.html);
-- [typing.TypedDict](https://docs.python.org/3/library/typing.html#typing.TypedDict);
-- [msgspec.Struct](https://github.com/jcrist/msgspec);
-- Custom type from your [jinja2](https://jinja.palletsprojects.com/en/3.1.x) template;
-
## Installation
To install `datamodel-code-generator`:
diff --git a/tests/data/expected/main/discriminator_with_external_reference/output.py b/tests/data/expected/main/discriminator_with_external_reference/output.py
new file mode 100644
index 000000000..8d87f2bba
--- /dev/null
+++ b/tests/data/expected/main/discriminator_with_external_reference/output.py
@@ -0,0 +1,37 @@
+# generated by datamodel-codegen:
+# filename: schema.json
+# timestamp: 2019-07-26T00:00:00+00:00
+
+from __future__ import annotations
+
+from typing import Optional, Union
+
+from pydantic import BaseModel, Field
+from typing_extensions import Literal
+
+
+class Type1(BaseModel):
+ type_: Literal['a'] = Field('a', title='Type ')
+
+
+class Type2(BaseModel):
+ type_: Literal['b'] = Field('b', title='Type ')
+ ref_type: Optional[Type1] = Field(None, description='A referenced type.')
+
+
+class Type4(BaseModel):
+ type_: Literal['d'] = Field('d', title='Type ')
+
+
+class Type5(BaseModel):
+ type_: Literal['e'] = Field('e', title='Type ')
+
+
+class Type3(BaseModel):
+ type_: Literal['c'] = Field('c', title='Type ')
+
+
+class Response(BaseModel):
+ inner: Union[Type1, Type2, Type3, Type4, Type5] = Field(
+ ..., discriminator='type_', title='Inner'
+ )
diff --git a/tests/data/expected/main/discriminator_with_external_references_folder/__init__.py b/tests/data/expected/main/discriminator_with_external_references_folder/__init__.py
new file mode 100644
index 000000000..abc855210
--- /dev/null
+++ b/tests/data/expected/main/discriminator_with_external_references_folder/__init__.py
@@ -0,0 +1,3 @@
+# generated by datamodel-codegen:
+# filename: discriminator_with_external_reference
+# timestamp: 2019-07-26T00:00:00+00:00
diff --git a/tests/data/expected/main/discriminator_with_external_references_folder/inner_folder/__init__.py b/tests/data/expected/main/discriminator_with_external_references_folder/inner_folder/__init__.py
new file mode 100644
index 000000000..abc855210
--- /dev/null
+++ b/tests/data/expected/main/discriminator_with_external_references_folder/inner_folder/__init__.py
@@ -0,0 +1,3 @@
+# generated by datamodel-codegen:
+# filename: discriminator_with_external_reference
+# timestamp: 2019-07-26T00:00:00+00:00
diff --git a/tests/data/expected/main/discriminator_with_external_references_folder/inner_folder/artificial_folder/__init__.py b/tests/data/expected/main/discriminator_with_external_references_folder/inner_folder/artificial_folder/__init__.py
new file mode 100644
index 000000000..abc855210
--- /dev/null
+++ b/tests/data/expected/main/discriminator_with_external_references_folder/inner_folder/artificial_folder/__init__.py
@@ -0,0 +1,3 @@
+# generated by datamodel-codegen:
+# filename: discriminator_with_external_reference
+# timestamp: 2019-07-26T00:00:00+00:00
diff --git a/tests/data/expected/main/discriminator_with_external_references_folder/inner_folder/artificial_folder/type_1.py b/tests/data/expected/main/discriminator_with_external_references_folder/inner_folder/artificial_folder/type_1.py
new file mode 100644
index 000000000..8df555769
--- /dev/null
+++ b/tests/data/expected/main/discriminator_with_external_references_folder/inner_folder/artificial_folder/type_1.py
@@ -0,0 +1,12 @@
+# generated by datamodel-codegen:
+# filename: inner_folder/artificial_folder/type-1.json
+# timestamp: 2019-07-26T00:00:00+00:00
+
+from __future__ import annotations
+
+from pydantic import BaseModel, Field
+from typing_extensions import Literal
+
+
+class Type1(BaseModel):
+ type_: Literal['a'] = Field(..., const=True, title='Type ')
diff --git a/tests/data/expected/main/discriminator_with_external_references_folder/inner_folder/schema.py b/tests/data/expected/main/discriminator_with_external_references_folder/inner_folder/schema.py
new file mode 100644
index 000000000..21806d292
--- /dev/null
+++ b/tests/data/expected/main/discriminator_with_external_references_folder/inner_folder/schema.py
@@ -0,0 +1,25 @@
+# generated by datamodel-codegen:
+# filename: inner_folder/schema.json
+# timestamp: 2019-07-26T00:00:00+00:00
+
+from __future__ import annotations
+
+from typing import Union
+
+from pydantic import BaseModel, Field
+from typing_extensions import Literal
+
+from .. import type_4
+from ..subfolder import type_5
+from . import type_2
+from .artificial_folder import type_1
+
+
+class Type3(BaseModel):
+ type_: Literal['c'] = Field(..., const=True, title='Type ')
+
+
+class Response(BaseModel):
+ inner: Union[type_1.Type1, type_2.Type2, Type3, type_4.Type4, type_5.Type5] = Field(
+ ..., discriminator='type_', title='Inner'
+ )
diff --git a/tests/data/expected/main/discriminator_with_external_references_folder/inner_folder/type_2.py b/tests/data/expected/main/discriminator_with_external_references_folder/inner_folder/type_2.py
new file mode 100644
index 000000000..95342e63d
--- /dev/null
+++ b/tests/data/expected/main/discriminator_with_external_references_folder/inner_folder/type_2.py
@@ -0,0 +1,16 @@
+# generated by datamodel-codegen:
+# filename: inner_folder/type-2.json
+# timestamp: 2019-07-26T00:00:00+00:00
+
+from __future__ import annotations
+
+from typing import Optional
+
+from pydantic import BaseModel, Field
+
+from .artificial_folder import type_1
+
+
+class Type2(BaseModel):
+ type_: Literal['b'] = Field(..., const=True, title='Type ')
+ ref_type: Optional[type_1.Type1] = Field(None, description='A referenced type.')
diff --git a/tests/data/expected/main/discriminator_with_external_references_folder/subfolder/__init__.py b/tests/data/expected/main/discriminator_with_external_references_folder/subfolder/__init__.py
new file mode 100644
index 000000000..abc855210
--- /dev/null
+++ b/tests/data/expected/main/discriminator_with_external_references_folder/subfolder/__init__.py
@@ -0,0 +1,3 @@
+# generated by datamodel-codegen:
+# filename: discriminator_with_external_reference
+# timestamp: 2019-07-26T00:00:00+00:00
diff --git a/tests/data/expected/main/discriminator_with_external_references_folder/subfolder/type_5.py b/tests/data/expected/main/discriminator_with_external_references_folder/subfolder/type_5.py
new file mode 100644
index 000000000..2a1361794
--- /dev/null
+++ b/tests/data/expected/main/discriminator_with_external_references_folder/subfolder/type_5.py
@@ -0,0 +1,11 @@
+# generated by datamodel-codegen:
+# filename: subfolder/type-5.json
+# timestamp: 2019-07-26T00:00:00+00:00
+
+from __future__ import annotations
+
+from pydantic import BaseModel, Field
+
+
+class Type5(BaseModel):
+ type_: Literal['e'] = Field(..., const=True, title='Type ')
diff --git a/tests/data/expected/main/discriminator_with_external_references_folder/type_4.py b/tests/data/expected/main/discriminator_with_external_references_folder/type_4.py
new file mode 100644
index 000000000..abad814bf
--- /dev/null
+++ b/tests/data/expected/main/discriminator_with_external_references_folder/type_4.py
@@ -0,0 +1,11 @@
+# generated by datamodel-codegen:
+# filename: type-4.json
+# timestamp: 2019-07-26T00:00:00+00:00
+
+from __future__ import annotations
+
+from pydantic import BaseModel, Field
+
+
+class Type4(BaseModel):
+ type_: Literal['d'] = Field(..., const=True, title='Type ')
diff --git a/tests/data/jsonschema/discriminator_with_external_reference/inner_folder/artificial_folder/type-1.json b/tests/data/jsonschema/discriminator_with_external_reference/inner_folder/artificial_folder/type-1.json
new file mode 100644
index 000000000..e7da9f40c
--- /dev/null
+++ b/tests/data/jsonschema/discriminator_with_external_reference/inner_folder/artificial_folder/type-1.json
@@ -0,0 +1,11 @@
+{
+ "properties": {
+ "type_": {
+ "const": "a",
+ "default": "a",
+ "title": "Type "
+ }
+ },
+ "title": "Type1",
+ "type": "object"
+}
\ No newline at end of file
diff --git a/tests/data/jsonschema/discriminator_with_external_reference/inner_folder/schema.json b/tests/data/jsonschema/discriminator_with_external_reference/inner_folder/schema.json
new file mode 100644
index 000000000..0fce2310c
--- /dev/null
+++ b/tests/data/jsonschema/discriminator_with_external_reference/inner_folder/schema.json
@@ -0,0 +1,52 @@
+{
+ "$def": {
+ "Type3": {
+ "properties": {
+ "type_": {
+ "const": "c",
+ "default": "c",
+ "title": "Type "
+ }
+ },
+ "title": "Type3",
+ "type": "object"
+ }
+ },
+ "properties": {
+ "inner": {
+ "discriminator": {
+ "mapping": {
+ "a": "./artificial_folder/type-1.json",
+ "b": "./type-2.json",
+ "c": "#/$def/Type3",
+ "d": "../type-4.json",
+ "e": "../subfolder/type-5.json"
+ },
+ "propertyName": "type_"
+ },
+ "oneOf": [
+ {
+ "$ref": "./artificial_folder/type-1.json"
+ },
+ {
+ "$ref": "./type-2.json"
+ },
+ {
+ "$ref": "#/$def/Type3"
+ },
+ {
+ "$ref": "../type-4.json"
+ },
+ {
+ "$ref": "../subfolder/type-5.json"
+ }
+ ],
+ "title": "Inner"
+ }
+ },
+ "required": [
+ "inner"
+ ],
+ "title": "Response",
+ "type": "object"
+}
diff --git a/tests/data/jsonschema/discriminator_with_external_reference/inner_folder/type-2.json b/tests/data/jsonschema/discriminator_with_external_reference/inner_folder/type-2.json
new file mode 100644
index 000000000..b76c25b14
--- /dev/null
+++ b/tests/data/jsonschema/discriminator_with_external_reference/inner_folder/type-2.json
@@ -0,0 +1,15 @@
+{
+ "properties": {
+ "type_": {
+ "const": "b",
+ "default": "b",
+ "title": "Type "
+ },
+ "ref_type": {
+ "$ref": "./artificial_folder/type-1.json",
+ "description": "A referenced type."
+ }
+ },
+ "title": "Type2",
+ "type": "object"
+}
\ No newline at end of file
diff --git a/tests/data/jsonschema/discriminator_with_external_reference/subfolder/type-5.json b/tests/data/jsonschema/discriminator_with_external_reference/subfolder/type-5.json
new file mode 100644
index 000000000..ada842093
--- /dev/null
+++ b/tests/data/jsonschema/discriminator_with_external_reference/subfolder/type-5.json
@@ -0,0 +1,11 @@
+{
+ "properties": {
+ "type_": {
+ "const": "e",
+ "default": "e",
+ "title": "Type "
+ }
+ },
+ "title": "Type5",
+ "type": "object"
+}
\ No newline at end of file
diff --git a/tests/data/jsonschema/discriminator_with_external_reference/type-4.json b/tests/data/jsonschema/discriminator_with_external_reference/type-4.json
new file mode 100644
index 000000000..4c357a275
--- /dev/null
+++ b/tests/data/jsonschema/discriminator_with_external_reference/type-4.json
@@ -0,0 +1,11 @@
+{
+ "properties": {
+ "type_": {
+ "const": "d",
+ "default": "d",
+ "title": "Type "
+ }
+ },
+ "title": "Type4",
+ "type": "object"
+}
\ No newline at end of file
diff --git a/tests/test_main.py b/tests/test_main.py
index 3854bc591..49bc9e0bd 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -6123,6 +6123,59 @@ def test_main_jsonschema_discriminator_literals():
)
+@freeze_time('2019-07-26')
+def test_main_jsonschema_external_discriminator():
+ with TemporaryDirectory() as output_dir:
+ output_file: Path = Path(output_dir) / 'output.py'
+ return_code: Exit = main(
+ [
+ '--input',
+ str(
+ JSON_SCHEMA_DATA_PATH
+ / 'discriminator_with_external_reference'
+ / 'inner_folder'
+ / 'schema.json'
+ ),
+ '--output',
+ str(output_file),
+ '--output-model-type',
+ 'pydantic_v2.BaseModel',
+ ]
+ )
+ assert return_code == Exit.OK
+ assert (
+ output_file.read_text()
+ == (
+ EXPECTED_MAIN_PATH
+ / 'discriminator_with_external_reference'
+ / 'output.py'
+ ).read_text()
+ )
+
+
+@freeze_time('2019-07-26')
+def test_main_jsonschema_external_discriminator_folder():
+ with TemporaryDirectory() as output_dir:
+ output_path: Path = Path(output_dir)
+ return_code: Exit = main(
+ [
+ '--input',
+ str(JSON_SCHEMA_DATA_PATH / 'discriminator_with_external_reference'),
+ '--output',
+ str(output_path),
+ ]
+ )
+ assert return_code == Exit.OK
+ main_modular_dir = (
+ EXPECTED_MAIN_PATH / 'discriminator_with_external_references_folder'
+ )
+ for path in main_modular_dir.rglob('*.py'):
+ result = output_path.joinpath(
+ path.relative_to(main_modular_dir)
+ ).read_text()
+ assert result == path.read_text()
+
+
@freeze_time('2019-07-26')
@pytest.mark.skipif(
black.__version__.split('.')[0] == '19',