Skip to content
Merged
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
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,12 @@ Template customization:
Custom template directory
--encoding ENCODING The encoding of input and output (default: utf-8)
--extra-template-data EXTRA_TEMPLATE_DATA
Extra template data
Extra template data for output models. Input is supposed to be a
json/yaml file. For OpenAPI and Jsonschema the keys are the spec
path of the object, or the name of the object if you want to apply
the template data to multiple objects with the same name. If you are
using another input file type (e.g. GraphQL), the key is the name of
the object. The value is a dictionary of the template data to add.
--use-double-quotes Model generated with double quotes. Single quotes or your black
config skip_string_normalization value will be used without this
option.
Expand Down
7 changes: 6 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,12 @@ Template customization:
Custom template directory
--encoding ENCODING The encoding of input and output (default: utf-8)
--extra-template-data EXTRA_TEMPLATE_DATA
Extra template data
Extra template data for output models. Input is supposed to be a
json/yaml file. For OpenAPI and Jsonschema the keys are the spec
path of the object, or the name of the object if you want to apply
the template data to multiple objects with the same name. If you are
using another input file type (e.g. GraphQL), the key is the name of
the object. The value is a dictionary of the template data to add.
--use-double-quotes Model generated with double quotes. Single quotes or your black
config skip_string_normalization value will be used without this
option.
Expand Down
6 changes: 5 additions & 1 deletion src/datamodel_code_generator/arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,11 @@ def start_section(self, heading: str | None) -> None:
)
template_options.add_argument(
"--extra-template-data",
help="Extra template data",
help="Extra template data for output models. Input is supposed to be a json/yaml file. "
"For OpenAPI and Jsonschema the keys are the spec path of the object, or the name of the object if you want to "
"apply the template data to multiple objects with the same name. "
"If you are using another input file type (e.g. GraphQL), the key is the name of the object. "
"The value is a dictionary of the template data to add.",
type=FileType("rt"),
)
template_options.add_argument(
Expand Down
13 changes: 10 additions & 3 deletions src/datamodel_code_generator/model/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,11 +298,18 @@ def __init__( # noqa: PLR0913

self.reference.source = self

self.extra_template_data = (
if extra_template_data is not None:
# The supplied defaultdict will either create a new entry,
# or already contain a predefined entry for this type
extra_template_data[self.name] if extra_template_data is not None else defaultdict(dict)
)
self.extra_template_data = extra_template_data[self.reference.path]

# We use the full object reference path as dictionary key, but
# we still support `name` as key because it was used for
# `--extra-template-data` input file and we don't want to break the
# existing behavior.
self.extra_template_data.update(extra_template_data[self.name])
else:
self.extra_template_data = defaultdict(dict)

self.fields = self._validate_fields(fields) if fields else []

Expand Down
18 changes: 9 additions & 9 deletions src/datamodel_code_generator/parser/jsonschema.py
Original file line number Diff line number Diff line change
Expand Up @@ -654,13 +654,13 @@ def get_ref_data_type(self, ref: str) -> DataType:
reference = self.model_resolver.add_ref(ref)
return self.data_type(reference=reference)

def set_additional_properties(self, name: str, obj: JsonSchemaObject) -> None:
def set_additional_properties(self, path: str, obj: JsonSchemaObject) -> None:
if isinstance(obj.additionalProperties, bool):
self.extra_template_data[name]["additionalProperties"] = obj.additionalProperties
self.extra_template_data[path]["additionalProperties"] = obj.additionalProperties

def set_title(self, name: str, obj: JsonSchemaObject) -> None:
def set_title(self, path: str, obj: JsonSchemaObject) -> None:
if obj.title:
self.extra_template_data[name]["title"] = obj.title
self.extra_template_data[path]["title"] = obj.title

def _deep_merge(self, dict1: dict[Any, Any], dict2: dict[Any, Any]) -> dict[Any, Any]:
result = dict1.copy()
Expand Down Expand Up @@ -782,7 +782,7 @@ def _parse_object_common_part( # noqa: PLR0913, PLR0917
if self.use_title_as_name and obj.title: # pragma: no cover
name = obj.title
reference = self.model_resolver.add(path, name, class_name=True, loaded=True)
self.set_additional_properties(reference.name, obj)
self.set_additional_properties(reference.path, obj)

data_model_type = self._create_data_model(
reference=reference,
Expand Down Expand Up @@ -993,7 +993,7 @@ def parse_object(
loaded=True,
)
class_name = reference.name
self.set_title(class_name, obj)
self.set_title(reference.path, obj)
fields = self.parse_object_fields(
obj, path, get_module_name(class_name, None, treat_dot_as_module=self.treat_dot_as_module)
)
Expand Down Expand Up @@ -1022,7 +1022,7 @@ def parse_object(
)
data_model_type_class = self.data_model_root_type

self.set_additional_properties(class_name, obj)
self.set_additional_properties(reference.path, obj)

data_model_type = self._create_data_model(
model_type=data_model_type_class,
Expand Down Expand Up @@ -1305,8 +1305,8 @@ def parse_root_type( # noqa: PLR0912
name = obj.title
if not reference:
reference = self.model_resolver.add(path, name, loaded=True, class_name=True)
self.set_title(name, obj)
self.set_additional_properties(name, obj)
self.set_title(reference.path, obj)
self.set_additional_properties(reference.path, obj)
data_model_root_type = self.data_model_root_type(
reference=reference,
fields=[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ class Config:


class TextResponse(BaseModel):
class Config:
extra = Extra.forbid

__root__: Dict[constr(regex=r'^[a-z]{1}[0-9]{1}$'), Any]


Expand Down
32 changes: 32 additions & 0 deletions tests/data/expected/main/jsonschema/same_name_objects.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# generated by datamodel-codegen:
# filename: same_name_objects.json
# timestamp: 2019-07-26T00:00:00+00:00

from __future__ import annotations

from typing import Any, List

from pydantic import BaseModel, Extra


class Model(BaseModel):
__root__: Any


class Friends(BaseModel):
pass

Check warning

Code scanning / CodeQL

Unnecessary pass Warning test

Unnecessary 'pass' statement.

class Config:
extra = Extra.forbid


class FriendsModel(BaseModel):
__root__: List


class Tst2(BaseModel):
__root__: FriendsModel


class Tst1(BaseModel):
__root__: FriendsModel
43 changes: 43 additions & 0 deletions tests/data/expected/main/openapi/same_name_objects.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# generated by datamodel-codegen:
# filename: same_name_objects.yaml
# timestamp: 2019-07-26T00:00:00+00:00

from __future__ import annotations

from typing import List, Optional

from pydantic import BaseModel, Extra


class Pets(BaseModel):
pass

Check warning

Code scanning / CodeQL

Unnecessary pass Warning test

Unnecessary 'pass' statement.

class Config:
extra = Extra.forbid


class Pet(BaseModel):
id: int
name: str
tag: Optional[str] = None


class Error(BaseModel):
code: int
message: str


class Resolved(BaseModel):
resolved: Optional[List[str]] = None


class PetsModel(BaseModel):
__root__: List[Pet]


class Friends2(BaseModel):
__root__: PetsModel


class Friends1(BaseModel):
__root__: PetsModel
16 changes: 16 additions & 0 deletions tests/data/jsonschema/same_name_objects.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$id": "https://example.com/same_name_objects.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"friends": {
"type": "object",
"additionalProperties": false
},
"tst1": {
"$ref": "person.json#/properties/friends"
},
"tst2": {
"$ref": "person.json#/properties/friends"
}
}
}
15 changes: 15 additions & 0 deletions tests/data/openapi/same_name_objects.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
openapi: "3.0.0"
info:
version: 1.0.0
title: Swagger Petstore
license:
name: MIT
components:
schemas:
Pets:
type: object
additionalProperties: false
Friends1:
$ref: "resolved_models.yaml#/components/schemas/Pets"
Friends2:
$ref: "resolved_models.yaml#/components/schemas/Pets"
18 changes: 18 additions & 0 deletions tests/main/jsonschema/test_main_jsonschema.py
Original file line number Diff line number Diff line change
Expand Up @@ -3125,3 +3125,21 @@ def test_main_extra_fields(extra_fields: str, output_model: str, expected_output
])
assert return_code == Exit.OK
assert output_file.read_text() == (EXPECTED_JSON_SCHEMA_PATH / expected_output).read_text()


@freeze_time("2019-07-26")
def test_main_jsonschema_same_name_objects(tmp_path: Path) -> None:
"""
See: https://github.com/koxudaxi/datamodel-code-generator/issues/2460
"""
output_file: Path = tmp_path / "output.py"
return_code: Exit = main([
"--input",
str(JSON_SCHEMA_DATA_PATH / "same_name_objects.json"),
"--output",
str(output_file),
"--input-file-type",
"jsonschema",
])
assert return_code == Exit.OK
assert output_file.read_text() == (EXPECTED_JSON_SCHEMA_PATH / "same_name_objects.py").read_text()
15 changes: 15 additions & 0 deletions tests/main/openapi/test_main_openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -2502,3 +2502,18 @@ def test_main_openapi_extra_fields_forbid(tmp_path: Path) -> None:
])
assert return_code == Exit.OK
assert output_file.read_text() == (EXPECTED_OPENAPI_PATH / "additional_properties.py").read_text()


@freeze_time("2019-07-26")
def test_main_openapi_same_name_objects(tmp_path: Path) -> None:
output_file: Path = tmp_path / "output.py"
return_code: Exit = main([
"--input",
str(OPEN_API_DATA_PATH / "same_name_objects.yaml"),
"--output",
str(output_file),
"--input-file-type",
"openapi",
])
assert return_code == Exit.OK
assert output_file.read_text() == (EXPECTED_OPENAPI_PATH / "same_name_objects.py").read_text()
Loading