datamodel-code-generator 0.30.0__py3-none-any.whl → 0.30.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of datamodel-code-generator might be problematic. Click here for more details.
- datamodel_code_generator/__init__.py +2 -0
- datamodel_code_generator/__main__.py +2 -0
- datamodel_code_generator/arguments.py +6 -0
- datamodel_code_generator/format.py +2 -2
- datamodel_code_generator/model/base.py +3 -0
- datamodel_code_generator/model/dataclass.py +2 -0
- datamodel_code_generator/model/pydantic_v2/__init__.py +1 -0
- datamodel_code_generator/model/template/dataclass.jinja2 +8 -1
- datamodel_code_generator/parser/base.py +32 -10
- datamodel_code_generator/parser/graphql.py +11 -1
- datamodel_code_generator/parser/jsonschema.py +33 -10
- datamodel_code_generator/parser/openapi.py +6 -1
- datamodel_code_generator/types.py +48 -27
- {datamodel_code_generator-0.30.0.dist-info → datamodel_code_generator-0.30.2.dist-info}/METADATA +3 -1
- {datamodel_code_generator-0.30.0.dist-info → datamodel_code_generator-0.30.2.dist-info}/RECORD +18 -18
- {datamodel_code_generator-0.30.0.dist-info → datamodel_code_generator-0.30.2.dist-info}/WHEEL +0 -0
- {datamodel_code_generator-0.30.0.dist-info → datamodel_code_generator-0.30.2.dist-info}/entry_points.txt +0 -0
- {datamodel_code_generator-0.30.0.dist-info → datamodel_code_generator-0.30.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -285,6 +285,7 @@ def generate( # noqa: PLR0912, PLR0913, PLR0914, PLR0915
|
|
|
285
285
|
union_mode: UnionMode | None = None,
|
|
286
286
|
output_datetime_class: DatetimeClassType | None = None,
|
|
287
287
|
keyword_only: bool = False,
|
|
288
|
+
frozen_dataclasses: bool = False,
|
|
288
289
|
no_alias: bool = False,
|
|
289
290
|
formatters: list[Formatter] = DEFAULT_FORMATTERS,
|
|
290
291
|
parent_scoped_naming: bool = False,
|
|
@@ -481,6 +482,7 @@ def generate( # noqa: PLR0912, PLR0913, PLR0914, PLR0915
|
|
|
481
482
|
default_field_extras=default_field_extras,
|
|
482
483
|
target_datetime_class=output_datetime_class,
|
|
483
484
|
keyword_only=keyword_only,
|
|
485
|
+
frozen_dataclasses=frozen_dataclasses,
|
|
484
486
|
no_alias=no_alias,
|
|
485
487
|
formatters=formatters,
|
|
486
488
|
encoding=encoding,
|
|
@@ -312,6 +312,7 @@ class Config(BaseModel):
|
|
|
312
312
|
union_mode: Optional[UnionMode] = None # noqa: UP045
|
|
313
313
|
output_datetime_class: Optional[DatetimeClassType] = None # noqa: UP045
|
|
314
314
|
keyword_only: bool = False
|
|
315
|
+
frozen_dataclasses: bool = False
|
|
315
316
|
no_alias: bool = False
|
|
316
317
|
formatters: list[Formatter] = DEFAULT_FORMATTERS
|
|
317
318
|
parent_scoped_naming: bool = False
|
|
@@ -524,6 +525,7 @@ def main(args: Sequence[str] | None = None) -> Exit: # noqa: PLR0911, PLR0912,
|
|
|
524
525
|
union_mode=config.union_mode,
|
|
525
526
|
output_datetime_class=config.output_datetime_class,
|
|
526
527
|
keyword_only=config.keyword_only,
|
|
528
|
+
frozen_dataclasses=config.frozen_dataclasses,
|
|
527
529
|
no_alias=config.no_alias,
|
|
528
530
|
formatters=config.formatters,
|
|
529
531
|
parent_scoped_naming=config.parent_scoped_naming,
|
|
@@ -153,6 +153,12 @@ model_options.add_argument(
|
|
|
153
153
|
action="store_true",
|
|
154
154
|
default=None,
|
|
155
155
|
)
|
|
156
|
+
model_options.add_argument(
|
|
157
|
+
"--frozen-dataclasses",
|
|
158
|
+
help="Generate frozen dataclasses (dataclass(frozen=True)). Only applies to dataclass output.",
|
|
159
|
+
action="store_true",
|
|
160
|
+
default=None,
|
|
161
|
+
)
|
|
156
162
|
model_options.add_argument(
|
|
157
163
|
"--reuse-model",
|
|
158
164
|
help="Reuse models on the field when a module has the model with the same content",
|
|
@@ -222,7 +222,7 @@ class CodeFormatter:
|
|
|
222
222
|
)
|
|
223
223
|
|
|
224
224
|
def apply_ruff_lint(self, code: str) -> str:
|
|
225
|
-
result = subprocess.run(
|
|
225
|
+
result = subprocess.run(
|
|
226
226
|
("ruff", "check", "--fix", "-"),
|
|
227
227
|
input=code.encode(self.encoding),
|
|
228
228
|
capture_output=True,
|
|
@@ -231,7 +231,7 @@ class CodeFormatter:
|
|
|
231
231
|
return result.stdout.decode(self.encoding)
|
|
232
232
|
|
|
233
233
|
def apply_ruff_formatter(self, code: str) -> str:
|
|
234
|
-
result = subprocess.run(
|
|
234
|
+
result = subprocess.run(
|
|
235
235
|
("ruff", "format", "-"),
|
|
236
236
|
input=code.encode(self.encoding),
|
|
237
237
|
capture_output=True,
|
|
@@ -275,9 +275,11 @@ class DataModel(TemplateBase, Nullable, ABC):
|
|
|
275
275
|
default: Any = UNDEFINED,
|
|
276
276
|
nullable: bool = False,
|
|
277
277
|
keyword_only: bool = False,
|
|
278
|
+
frozen: bool = False,
|
|
278
279
|
treat_dot_as_module: bool = False,
|
|
279
280
|
) -> None:
|
|
280
281
|
self.keyword_only = keyword_only
|
|
282
|
+
self.frozen = frozen
|
|
281
283
|
if not self.TEMPLATE_FILE_PATH:
|
|
282
284
|
msg = "TEMPLATE_FILE_PATH is undefined"
|
|
283
285
|
raise Exception(msg) # noqa: TRY002
|
|
@@ -434,5 +436,6 @@ class DataModel(TemplateBase, Nullable, ABC):
|
|
|
434
436
|
methods=self.methods,
|
|
435
437
|
description=self.description,
|
|
436
438
|
keyword_only=self.keyword_only,
|
|
439
|
+
frozen=self.frozen,
|
|
437
440
|
**self.extra_template_data,
|
|
438
441
|
)
|
|
@@ -53,6 +53,7 @@ class DataClass(DataModel):
|
|
|
53
53
|
default: Any = UNDEFINED,
|
|
54
54
|
nullable: bool = False,
|
|
55
55
|
keyword_only: bool = False,
|
|
56
|
+
frozen: bool = False,
|
|
56
57
|
treat_dot_as_module: bool = False,
|
|
57
58
|
) -> None:
|
|
58
59
|
super().__init__(
|
|
@@ -69,6 +70,7 @@ class DataClass(DataModel):
|
|
|
69
70
|
default=default,
|
|
70
71
|
nullable=nullable,
|
|
71
72
|
keyword_only=keyword_only,
|
|
73
|
+
frozen=frozen,
|
|
72
74
|
treat_dot_as_module=treat_dot_as_module,
|
|
73
75
|
)
|
|
74
76
|
|
|
@@ -27,6 +27,7 @@ class ConfigDict(_BaseModel):
|
|
|
27
27
|
protected_namespaces: Optional[tuple[str, ...]] = None # noqa: UP045
|
|
28
28
|
regex_engine: Optional[str] = None # noqa: UP045
|
|
29
29
|
use_enum_values: Optional[bool] = None # noqa: UP045
|
|
30
|
+
coerce_numbers_to_str: Optional[bool] = None # noqa: UP045
|
|
30
31
|
|
|
31
32
|
|
|
32
33
|
__all__ = [
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
{% for decorator in decorators -%}
|
|
2
2
|
{{ decorator }}
|
|
3
3
|
{% endfor -%}
|
|
4
|
-
@dataclass
|
|
4
|
+
@dataclass
|
|
5
|
+
{%- if keyword_only or frozen -%}
|
|
6
|
+
(
|
|
7
|
+
{%- if keyword_only -%}kw_only=True{%- endif -%}
|
|
8
|
+
{%- if keyword_only and frozen -%}, {% endif -%}
|
|
9
|
+
{%- if frozen -%}frozen=True{%- endif -%}
|
|
10
|
+
)
|
|
11
|
+
{%- endif %}
|
|
5
12
|
{%- if base_class %}
|
|
6
13
|
class {{ class_name }}({{ base_class }}):
|
|
7
14
|
{%- else %}
|
|
@@ -88,6 +88,8 @@ def to_hashable(item: Any) -> Any:
|
|
|
88
88
|
return frozenset(to_hashable(i) for i in item)
|
|
89
89
|
if isinstance(item, BaseModel):
|
|
90
90
|
return to_hashable(item.dict())
|
|
91
|
+
if item is None:
|
|
92
|
+
return ""
|
|
91
93
|
return item
|
|
92
94
|
|
|
93
95
|
|
|
@@ -375,11 +377,13 @@ class Parser(ABC):
|
|
|
375
377
|
default_field_extras: dict[str, Any] | None = None,
|
|
376
378
|
target_datetime_class: DatetimeClassType | None = DatetimeClassType.Datetime,
|
|
377
379
|
keyword_only: bool = False,
|
|
380
|
+
frozen_dataclasses: bool = False,
|
|
378
381
|
no_alias: bool = False,
|
|
379
382
|
formatters: list[Formatter] = DEFAULT_FORMATTERS,
|
|
380
383
|
parent_scoped_naming: bool = False,
|
|
381
384
|
) -> None:
|
|
382
385
|
self.keyword_only = keyword_only
|
|
386
|
+
self.frozen_dataclasses = frozen_dataclasses
|
|
383
387
|
self.data_type_manager: DataTypeManager = data_type_manager_type(
|
|
384
388
|
python_version=target_python_version,
|
|
385
389
|
use_standard_collections=use_standard_collections,
|
|
@@ -768,19 +772,31 @@ class Parser(ABC):
|
|
|
768
772
|
continue
|
|
769
773
|
type_names.append(name)
|
|
770
774
|
|
|
771
|
-
#
|
|
772
|
-
|
|
773
|
-
|
|
775
|
+
# First try to get the discriminator value from the const field
|
|
776
|
+
for discriminator_field in discriminator_model.fields:
|
|
777
|
+
if field_name not in {discriminator_field.original_name, discriminator_field.name}:
|
|
778
|
+
continue
|
|
779
|
+
if discriminator_field.extras.get("const"):
|
|
780
|
+
type_names = [discriminator_field.extras["const"]]
|
|
781
|
+
break
|
|
782
|
+
|
|
783
|
+
# If no const value found, try to get it from the mapping
|
|
784
|
+
if not type_names:
|
|
785
|
+
# Check the main discriminator model path
|
|
786
|
+
if mapping:
|
|
787
|
+
check_paths(discriminator_model, mapping) # pyright: ignore[reportArgumentType]
|
|
788
|
+
|
|
789
|
+
# Check the base_classes if they exist
|
|
790
|
+
if len(type_names) == 0:
|
|
791
|
+
for base_class in discriminator_model.base_classes:
|
|
792
|
+
check_paths(base_class.reference, mapping) # pyright: ignore[reportArgumentType]
|
|
793
|
+
else:
|
|
794
|
+
type_names = [discriminator_model.path.split("/")[-1]]
|
|
774
795
|
|
|
775
|
-
# Check the base_classes if they exist
|
|
776
|
-
if len(type_names) == 0:
|
|
777
|
-
for base_class in discriminator_model.base_classes:
|
|
778
|
-
check_paths(base_class.reference, mapping) # pyright: ignore[reportArgumentType]
|
|
779
|
-
else:
|
|
780
|
-
type_names = [discriminator_model.path.split("/")[-1]]
|
|
781
796
|
if not type_names: # pragma: no cover
|
|
782
797
|
msg = f"Discriminator type is not found. {data_type.reference.path}"
|
|
783
798
|
raise RuntimeError(msg)
|
|
799
|
+
|
|
784
800
|
has_one_literal = False
|
|
785
801
|
for discriminator_field in discriminator_model.fields:
|
|
786
802
|
if field_name not in {discriminator_field.original_name, discriminator_field.name}:
|
|
@@ -1119,6 +1135,9 @@ class Parser(ABC):
|
|
|
1119
1135
|
if self.data_model_type != pydantic_model_v2.BaseModel:
|
|
1120
1136
|
return
|
|
1121
1137
|
for model in models:
|
|
1138
|
+
if model.base_class == "Enum":
|
|
1139
|
+
continue
|
|
1140
|
+
|
|
1122
1141
|
for field in model.fields:
|
|
1123
1142
|
filed_name = field.name
|
|
1124
1143
|
filed_name_resolver = ModelResolver(snake_case_field=self.snake_case_field, remove_suffix_number=True)
|
|
@@ -1197,7 +1216,10 @@ class Parser(ABC):
|
|
|
1197
1216
|
) -> None:
|
|
1198
1217
|
for model in models:
|
|
1199
1218
|
for model_field in model.fields:
|
|
1200
|
-
if
|
|
1219
|
+
if (
|
|
1220
|
+
model_field.data_type.type in all_model_field_names
|
|
1221
|
+
and model_field.data_type.type == model_field.name
|
|
1222
|
+
):
|
|
1201
1223
|
alias = model_field.data_type.type + "_aliased"
|
|
1202
1224
|
model_field.data_type.type = alias
|
|
1203
1225
|
if model_field.data_type.import_: # pragma: no cover
|
|
@@ -17,6 +17,7 @@ from datamodel_code_generator import (
|
|
|
17
17
|
)
|
|
18
18
|
from datamodel_code_generator.model import DataModel, DataModelFieldBase
|
|
19
19
|
from datamodel_code_generator.model import pydantic as pydantic_model
|
|
20
|
+
from datamodel_code_generator.model.dataclass import DataClass
|
|
20
21
|
from datamodel_code_generator.model.enum import Enum
|
|
21
22
|
from datamodel_code_generator.model.scalar import DataTypeScalar
|
|
22
23
|
from datamodel_code_generator.model.union import DataTypeUnion
|
|
@@ -152,6 +153,7 @@ class GraphQLParser(Parser):
|
|
|
152
153
|
default_field_extras: dict[str, Any] | None = None,
|
|
153
154
|
target_datetime_class: DatetimeClassType = DatetimeClassType.Datetime,
|
|
154
155
|
keyword_only: bool = False,
|
|
156
|
+
frozen_dataclasses: bool = False,
|
|
155
157
|
no_alias: bool = False,
|
|
156
158
|
formatters: list[Formatter] = DEFAULT_FORMATTERS,
|
|
157
159
|
parent_scoped_naming: bool = False,
|
|
@@ -227,6 +229,7 @@ class GraphQLParser(Parser):
|
|
|
227
229
|
default_field_extras=default_field_extras,
|
|
228
230
|
target_datetime_class=target_datetime_class,
|
|
229
231
|
keyword_only=keyword_only,
|
|
232
|
+
frozen_dataclasses=frozen_dataclasses,
|
|
230
233
|
no_alias=no_alias,
|
|
231
234
|
formatters=formatters,
|
|
232
235
|
parent_scoped_naming=parent_scoped_naming,
|
|
@@ -283,6 +286,13 @@ class GraphQLParser(Parser):
|
|
|
283
286
|
|
|
284
287
|
self.support_graphql_types[resolved_type].append(type_)
|
|
285
288
|
|
|
289
|
+
def _create_data_model(self, model_type: type[DataModel] | None = None, **kwargs: Any) -> DataModel:
|
|
290
|
+
"""Create data model instance with conditional frozen parameter for DataClass."""
|
|
291
|
+
data_model_class = model_type or self.data_model_type
|
|
292
|
+
if issubclass(data_model_class, DataClass):
|
|
293
|
+
kwargs["frozen"] = self.frozen_dataclasses
|
|
294
|
+
return data_model_class(**kwargs)
|
|
295
|
+
|
|
286
296
|
def _typename_field(self, name: str) -> DataModelFieldBase:
|
|
287
297
|
return self.data_model_field_type(
|
|
288
298
|
name="typename__",
|
|
@@ -445,7 +455,7 @@ class GraphQLParser(Parser):
|
|
|
445
455
|
if hasattr(obj, "interfaces"): # pragma: no cover
|
|
446
456
|
base_classes = [self.references[i.name] for i in obj.interfaces] # pyright: ignore[reportAttributeAccessIssue]
|
|
447
457
|
|
|
448
|
-
data_model_type = self.
|
|
458
|
+
data_model_type = self._create_data_model(
|
|
449
459
|
reference=self.references[obj.name],
|
|
450
460
|
fields=fields,
|
|
451
461
|
base_classes=base_classes,
|
|
@@ -6,7 +6,7 @@ from contextlib import contextmanager
|
|
|
6
6
|
from functools import cached_property, lru_cache
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import TYPE_CHECKING, Any, Callable, ClassVar, Optional, Union
|
|
9
|
-
from urllib.parse import ParseResult
|
|
9
|
+
from urllib.parse import ParseResult, unquote
|
|
10
10
|
from warnings import warn
|
|
11
11
|
|
|
12
12
|
from pydantic import (
|
|
@@ -23,6 +23,7 @@ from datamodel_code_generator.format import DEFAULT_FORMATTERS, Formatter, Pytho
|
|
|
23
23
|
from datamodel_code_generator.model import DataModel, DataModelFieldBase
|
|
24
24
|
from datamodel_code_generator.model import pydantic as pydantic_model
|
|
25
25
|
from datamodel_code_generator.model.base import UNDEFINED, get_module_name
|
|
26
|
+
from datamodel_code_generator.model.dataclass import DataClass
|
|
26
27
|
from datamodel_code_generator.model.enum import Enum
|
|
27
28
|
from datamodel_code_generator.parser import DefaultPutDict, LiteralType
|
|
28
29
|
from datamodel_code_generator.parser.base import (
|
|
@@ -58,16 +59,26 @@ if TYPE_CHECKING:
|
|
|
58
59
|
from collections.abc import Generator, Iterable, Iterator, Mapping, Sequence
|
|
59
60
|
|
|
60
61
|
|
|
62
|
+
def unescape_json_pointer_segment(segment: str) -> str:
|
|
63
|
+
# Unescape ~1, ~0, and percent-encoding
|
|
64
|
+
return unquote(segment.replace("~1", "/").replace("~0", "~"))
|
|
65
|
+
|
|
66
|
+
|
|
61
67
|
def get_model_by_path(schema: dict[str, Any] | list[Any], keys: list[str] | list[int]) -> dict[Any, Any]:
|
|
62
68
|
model: dict[Any, Any] | list[Any]
|
|
63
69
|
if not keys:
|
|
64
70
|
model = schema
|
|
65
|
-
elif len(keys) == 1:
|
|
66
|
-
model = schema.get(str(keys[0]), {}) if isinstance(schema, dict) else schema[int(keys[0])]
|
|
67
|
-
elif isinstance(schema, dict):
|
|
68
|
-
model = get_model_by_path(schema[str(keys[0])], keys[1:])
|
|
69
71
|
else:
|
|
70
|
-
|
|
72
|
+
# Unescape the key if it's a string (JSON pointer segment)
|
|
73
|
+
key = keys[0]
|
|
74
|
+
if isinstance(key, str):
|
|
75
|
+
key = unescape_json_pointer_segment(key)
|
|
76
|
+
if len(keys) == 1:
|
|
77
|
+
model = schema.get(str(key), {}) if isinstance(schema, dict) else schema[int(key)]
|
|
78
|
+
elif isinstance(schema, dict):
|
|
79
|
+
model = get_model_by_path(schema[str(key)], keys[1:])
|
|
80
|
+
else:
|
|
81
|
+
model = get_model_by_path(schema[int(key)], keys[1:])
|
|
71
82
|
if isinstance(model, dict):
|
|
72
83
|
return model
|
|
73
84
|
msg = f"Does not support json pointer to array. schema={schema}, key={keys}"
|
|
@@ -421,6 +432,7 @@ class JsonSchemaParser(Parser):
|
|
|
421
432
|
default_field_extras: dict[str, Any] | None = None,
|
|
422
433
|
target_datetime_class: DatetimeClassType = DatetimeClassType.Datetime,
|
|
423
434
|
keyword_only: bool = False,
|
|
435
|
+
frozen_dataclasses: bool = False,
|
|
424
436
|
no_alias: bool = False,
|
|
425
437
|
formatters: list[Formatter] = DEFAULT_FORMATTERS,
|
|
426
438
|
parent_scoped_naming: bool = False,
|
|
@@ -496,6 +508,7 @@ class JsonSchemaParser(Parser):
|
|
|
496
508
|
default_field_extras=default_field_extras,
|
|
497
509
|
target_datetime_class=target_datetime_class,
|
|
498
510
|
keyword_only=keyword_only,
|
|
511
|
+
frozen_dataclasses=frozen_dataclasses,
|
|
499
512
|
no_alias=no_alias,
|
|
500
513
|
formatters=formatters,
|
|
501
514
|
parent_scoped_naming=parent_scoped_naming,
|
|
@@ -627,7 +640,7 @@ class JsonSchemaParser(Parser):
|
|
|
627
640
|
result[key] = self._deep_merge(result[key], value)
|
|
628
641
|
continue
|
|
629
642
|
if isinstance(result[key], list) and isinstance(value, list):
|
|
630
|
-
result[key]
|
|
643
|
+
result[key] = result[key] + value # noqa: PLR6104
|
|
631
644
|
continue
|
|
632
645
|
result[key] = value
|
|
633
646
|
return result
|
|
@@ -686,6 +699,13 @@ class JsonSchemaParser(Parser):
|
|
|
686
699
|
def parse_one_of(self, name: str, obj: JsonSchemaObject, path: list[str]) -> list[DataType]:
|
|
687
700
|
return self.parse_combined_schema(name, obj, path, "oneOf")
|
|
688
701
|
|
|
702
|
+
def _create_data_model(self, model_type: type[DataModel] | None = None, **kwargs: Any) -> DataModel:
|
|
703
|
+
"""Create data model instance with conditional frozen parameter for DataClass."""
|
|
704
|
+
data_model_class = model_type or self.data_model_type
|
|
705
|
+
if issubclass(data_model_class, DataClass):
|
|
706
|
+
kwargs["frozen"] = self.frozen_dataclasses
|
|
707
|
+
return data_model_class(**kwargs)
|
|
708
|
+
|
|
689
709
|
def _parse_object_common_part( # noqa: PLR0913, PLR0917
|
|
690
710
|
self,
|
|
691
711
|
name: str,
|
|
@@ -733,7 +753,8 @@ class JsonSchemaParser(Parser):
|
|
|
733
753
|
name = obj.title
|
|
734
754
|
reference = self.model_resolver.add(path, name, class_name=True, loaded=True)
|
|
735
755
|
self.set_additional_properties(reference.name, obj)
|
|
736
|
-
|
|
756
|
+
|
|
757
|
+
data_model_type = self._create_data_model(
|
|
737
758
|
reference=reference,
|
|
738
759
|
fields=fields,
|
|
739
760
|
base_classes=base_classes,
|
|
@@ -874,7 +895,7 @@ class JsonSchemaParser(Parser):
|
|
|
874
895
|
exclude_field_names: set[str] = set()
|
|
875
896
|
for original_field_name, field in properties.items():
|
|
876
897
|
field_name, alias = self.model_resolver.get_valid_field_name_and_alias(
|
|
877
|
-
original_field_name, exclude_field_names
|
|
898
|
+
original_field_name, excludes=exclude_field_names
|
|
878
899
|
)
|
|
879
900
|
modular_name = f"{module_name}.{field_name}" if module_name else field_name
|
|
880
901
|
|
|
@@ -972,7 +993,9 @@ class JsonSchemaParser(Parser):
|
|
|
972
993
|
data_model_type_class = self.data_model_root_type
|
|
973
994
|
|
|
974
995
|
self.set_additional_properties(class_name, obj)
|
|
975
|
-
|
|
996
|
+
|
|
997
|
+
data_model_type = self._create_data_model(
|
|
998
|
+
model_type=data_model_type_class,
|
|
976
999
|
reference=reference,
|
|
977
1000
|
fields=fields,
|
|
978
1001
|
custom_base_class=obj.custom_base_path or self.base_class,
|
|
@@ -42,6 +42,7 @@ if TYPE_CHECKING:
|
|
|
42
42
|
from pathlib import Path
|
|
43
43
|
from urllib.parse import ParseResult
|
|
44
44
|
|
|
45
|
+
|
|
45
46
|
RE_APPLICATION_JSON_PATTERN: Pattern[str] = re.compile(r"^application/.*json$")
|
|
46
47
|
|
|
47
48
|
OPERATION_NAMES: list[str] = [
|
|
@@ -214,6 +215,7 @@ class OpenAPIParser(JsonSchemaParser):
|
|
|
214
215
|
default_field_extras: dict[str, Any] | None = None,
|
|
215
216
|
target_datetime_class: DatetimeClassType = DatetimeClassType.Datetime,
|
|
216
217
|
keyword_only: bool = False,
|
|
218
|
+
frozen_dataclasses: bool = False,
|
|
217
219
|
no_alias: bool = False,
|
|
218
220
|
formatters: list[Formatter] = DEFAULT_FORMATTERS,
|
|
219
221
|
parent_scoped_naming: bool = False,
|
|
@@ -289,6 +291,7 @@ class OpenAPIParser(JsonSchemaParser):
|
|
|
289
291
|
default_field_extras=default_field_extras,
|
|
290
292
|
target_datetime_class=target_datetime_class,
|
|
291
293
|
keyword_only=keyword_only,
|
|
294
|
+
frozen_dataclasses=frozen_dataclasses,
|
|
292
295
|
no_alias=no_alias,
|
|
293
296
|
formatters=formatters,
|
|
294
297
|
parent_scoped_naming=parent_scoped_naming,
|
|
@@ -483,8 +486,10 @@ class OpenAPIParser(JsonSchemaParser):
|
|
|
483
486
|
)
|
|
484
487
|
|
|
485
488
|
if OpenAPIScope.Parameters in self.open_api_scopes and fields:
|
|
489
|
+
# Using _create_data_model from parent class JsonSchemaParser
|
|
490
|
+
# This method automatically adds frozen=True for DataClass types
|
|
486
491
|
self.results.append(
|
|
487
|
-
self.
|
|
492
|
+
self._create_data_model(
|
|
488
493
|
fields=fields,
|
|
489
494
|
reference=reference,
|
|
490
495
|
custom_base_class=self.base_class,
|
|
@@ -6,13 +6,27 @@ from enum import Enum, auto
|
|
|
6
6
|
from functools import lru_cache
|
|
7
7
|
from itertools import chain
|
|
8
8
|
from re import Pattern
|
|
9
|
-
from typing import
|
|
9
|
+
from typing import (
|
|
10
|
+
TYPE_CHECKING,
|
|
11
|
+
Any,
|
|
12
|
+
Callable,
|
|
13
|
+
ClassVar,
|
|
14
|
+
Optional,
|
|
15
|
+
Protocol,
|
|
16
|
+
TypeVar,
|
|
17
|
+
Union,
|
|
18
|
+
runtime_checkable,
|
|
19
|
+
)
|
|
10
20
|
|
|
11
21
|
import pydantic
|
|
12
22
|
from packaging import version
|
|
13
23
|
from pydantic import StrictBool, StrictInt, StrictStr, create_model
|
|
14
24
|
|
|
15
|
-
from datamodel_code_generator.format import
|
|
25
|
+
from datamodel_code_generator.format import (
|
|
26
|
+
DatetimeClassType,
|
|
27
|
+
PythonVersion,
|
|
28
|
+
PythonVersionMin,
|
|
29
|
+
)
|
|
16
30
|
from datamodel_code_generator.imports import (
|
|
17
31
|
IMPORT_ABC_MAPPING,
|
|
18
32
|
IMPORT_ABC_SEQUENCE,
|
|
@@ -167,53 +181,57 @@ def _remove_none_from_type(type_: str, split_pattern: Pattern[str], delimiter: s
|
|
|
167
181
|
return types
|
|
168
182
|
|
|
169
183
|
|
|
170
|
-
def _remove_none_from_union(type_: str, *, use_union_operator: bool) -> str: # noqa:
|
|
184
|
+
def _remove_none_from_union(type_: str, *, use_union_operator: bool) -> str: # noqa: PLR0912
|
|
171
185
|
if use_union_operator:
|
|
172
186
|
if " | " not in type_:
|
|
173
187
|
return type_
|
|
188
|
+
separator = "|"
|
|
189
|
+
inner_text = type_
|
|
190
|
+
else:
|
|
191
|
+
if not type_.startswith(UNION_PREFIX):
|
|
192
|
+
return type_
|
|
193
|
+
separator = ","
|
|
194
|
+
inner_text = type_[len(UNION_PREFIX) : -1]
|
|
174
195
|
|
|
175
|
-
# Process each part of the union
|
|
176
|
-
parts = UNION_OPERATOR_PATTERN.split(type_)
|
|
177
|
-
processed_parts = []
|
|
178
|
-
for part in parts:
|
|
179
|
-
if part == NONE:
|
|
180
|
-
continue
|
|
181
|
-
|
|
182
|
-
# Check if this part contains a nested union
|
|
183
|
-
processed_part = _remove_none_from_union(part, use_union_operator=True) if " | " in part else part
|
|
184
|
-
processed_parts.append(processed_part)
|
|
185
|
-
|
|
186
|
-
if not processed_parts:
|
|
187
|
-
return NONE
|
|
188
|
-
|
|
189
|
-
return UNION_OPERATOR_DELIMITER.join(processed_parts)
|
|
190
|
-
if not type_.startswith(UNION_PREFIX):
|
|
191
|
-
return type_
|
|
192
|
-
|
|
193
|
-
inner_text = type_[len(UNION_PREFIX) : -1]
|
|
194
196
|
parts = []
|
|
195
197
|
inner_count = 0
|
|
196
198
|
current_part = ""
|
|
197
199
|
|
|
200
|
+
# With this variable we count any non-escaped round bracket, whenever we are inside a
|
|
201
|
+
# constraint string expression. Once found a part starting with `constr(`, we increment
|
|
202
|
+
# this counter for each non-escaped opening round bracket and decrement it for each
|
|
203
|
+
# non-escaped closing round bracket.
|
|
204
|
+
in_constr = 0
|
|
205
|
+
|
|
198
206
|
# Parse union parts carefully to handle nested structures
|
|
199
207
|
for char in inner_text:
|
|
200
208
|
current_part += char
|
|
201
|
-
if char == "[":
|
|
209
|
+
if char == "[" and in_constr == 0:
|
|
202
210
|
inner_count += 1
|
|
203
|
-
elif char == "]":
|
|
211
|
+
elif char == "]" and in_constr == 0:
|
|
204
212
|
inner_count -= 1
|
|
205
|
-
elif char == "
|
|
213
|
+
elif char == "(":
|
|
214
|
+
if current_part.strip().startswith("constr(") and current_part[-2] != "\\":
|
|
215
|
+
# non-escaped opening round bracket found inside constraint string expression
|
|
216
|
+
in_constr += 1
|
|
217
|
+
elif char == ")":
|
|
218
|
+
if in_constr > 0 and current_part[-2] != "\\":
|
|
219
|
+
# non-escaped closing round bracket found inside constraint string expression
|
|
220
|
+
in_constr -= 1
|
|
221
|
+
elif char == separator and inner_count == 0 and in_constr == 0:
|
|
206
222
|
part = current_part[:-1].strip()
|
|
207
223
|
if part != NONE:
|
|
208
224
|
# Process nested unions recursively
|
|
209
|
-
|
|
225
|
+
# only UNION_PREFIX might be nested but not union_operator
|
|
226
|
+
if not use_union_operator and part.startswith(UNION_PREFIX):
|
|
210
227
|
part = _remove_none_from_union(part, use_union_operator=False)
|
|
211
228
|
parts.append(part)
|
|
212
229
|
current_part = ""
|
|
213
230
|
|
|
214
231
|
part = current_part.strip()
|
|
215
232
|
if current_part and part != NONE:
|
|
216
|
-
|
|
233
|
+
# only UNION_PREFIX might be nested but not union_operator
|
|
234
|
+
if not use_union_operator and part.startswith(UNION_PREFIX):
|
|
217
235
|
part = _remove_none_from_union(part, use_union_operator=False)
|
|
218
236
|
parts.append(part)
|
|
219
237
|
|
|
@@ -222,6 +240,9 @@ def _remove_none_from_union(type_: str, *, use_union_operator: bool) -> str: #
|
|
|
222
240
|
if len(parts) == 1:
|
|
223
241
|
return parts[0]
|
|
224
242
|
|
|
243
|
+
if use_union_operator:
|
|
244
|
+
return UNION_OPERATOR_DELIMITER.join(parts)
|
|
245
|
+
|
|
225
246
|
return f"{UNION_PREFIX}{UNION_DELIMITER.join(parts)}]"
|
|
226
247
|
|
|
227
248
|
|
{datamodel_code_generator-0.30.0.dist-info → datamodel_code_generator-0.30.2.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: datamodel-code-generator
|
|
3
|
-
Version: 0.30.
|
|
3
|
+
Version: 0.30.2
|
|
4
4
|
Summary: Datamodel Code Generator
|
|
5
5
|
Project-URL: Homepage, https://github.com/koxudaxi/datamodel-code-generator
|
|
6
6
|
Project-URL: Source, https://github.com/koxudaxi/datamodel-code-generator
|
|
@@ -505,6 +505,8 @@ Model customization:
|
|
|
505
505
|
Enable faux immutability
|
|
506
506
|
--enable-version-header
|
|
507
507
|
Enable package version on file headers
|
|
508
|
+
--frozen-dataclasses Generate frozen dataclasses (dataclass(frozen=True)). Only applies
|
|
509
|
+
to dataclass output.
|
|
508
510
|
--keep-model-order Keep generated models'' order
|
|
509
511
|
--keyword-only Defined models as keyword only (for example
|
|
510
512
|
dataclass(kw_only=True)).
|
{datamodel_code_generator-0.30.0.dist-info → datamodel_code_generator-0.30.2.dist-info}/RECORD
RENAMED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
datamodel_code_generator/__init__.py,sha256=
|
|
2
|
-
datamodel_code_generator/__main__.py,sha256=
|
|
3
|
-
datamodel_code_generator/arguments.py,sha256=
|
|
4
|
-
datamodel_code_generator/format.py,sha256=
|
|
1
|
+
datamodel_code_generator/__init__.py,sha256=h0yxjBBK7_RJdAqUVimMfSqsq0tXqH_nzcNZRgL3Yrs,20530
|
|
2
|
+
datamodel_code_generator/__main__.py,sha256=Sl3ch890tr6J8voedlz8nyCq2QYLZDtibFa3o2SfQ_k,22703
|
|
3
|
+
datamodel_code_generator/arguments.py,sha256=T8iPrqTq9MJLJyi4PHB3_JQC2PgfLpYNmXpDFZWsXAs,16838
|
|
4
|
+
datamodel_code_generator/format.py,sha256=ZlnTCAl1H4og685smvCBSzexgpYbZtyYLIrt7lwUNcY,8934
|
|
5
5
|
datamodel_code_generator/http.py,sha256=LE94GC7I9D8lWIg_YAGWedfy0XNxOXTmiYKuNMTwouo,887
|
|
6
6
|
datamodel_code_generator/imports.py,sha256=Nq83WbEGCegntg3WX4VbKfzAIs84alZ7IrYyNPrlUbc,5517
|
|
7
7
|
datamodel_code_generator/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
8
|
datamodel_code_generator/pydantic_patch.py,sha256=co1IUDvZqQ-xEZ3C9gbV-BVm2Cin1vfyZNr2Dr0LdHY,718
|
|
9
9
|
datamodel_code_generator/reference.py,sha256=OobfjN5hWaKzv4ECdCPc9Q3ODkoG93B4qaKlzDEcDrY,26748
|
|
10
|
-
datamodel_code_generator/types.py,sha256=
|
|
10
|
+
datamodel_code_generator/types.py,sha256=1g4RZZ1e5oC4EHaNyyDmsIfF4o1WAjQ2KaLjwc9mYss,21883
|
|
11
11
|
datamodel_code_generator/util.py,sha256=mZW8-6CbFe6T4IY5OM9Av6cH-0VknQGe2eIKjTM6Jzo,2729
|
|
12
12
|
datamodel_code_generator/model/__init__.py,sha256=pJlJ1juQ-Gv17ZKXy6OAfJSSoOAmYQ7QCbdneu1BENU,3594
|
|
13
|
-
datamodel_code_generator/model/base.py,sha256=
|
|
14
|
-
datamodel_code_generator/model/dataclass.py,sha256=
|
|
13
|
+
datamodel_code_generator/model/base.py,sha256=ZQ3Xy4Fs_I8M01tk1ps0EhWJM9vR-n7umPkz5NmHTjw,15050
|
|
14
|
+
datamodel_code_generator/model/dataclass.py,sha256=8Z02XY3S6byNe9Pb46LisE5opQcvpx8FVvPjUrlAacE,6309
|
|
15
15
|
datamodel_code_generator/model/enum.py,sha256=yriQslY1hag_Qk-Xv3vl_LkPnbmMZ3iRTAGiiyMN0Io,4003
|
|
16
16
|
datamodel_code_generator/model/imports.py,sha256=PTc09UzIBSsa5yAPoieb6hCGIohU2T1Y7igNy_pYarg,820
|
|
17
17
|
datamodel_code_generator/model/msgspec.py,sha256=qL2DIEwBfpn-vd8p8KEmUViMUce6RgI4Ql-drOmPR7M,11845
|
|
@@ -26,7 +26,7 @@ datamodel_code_generator/model/pydantic/custom_root_type.py,sha256=VJpEAmGFe3TzM
|
|
|
26
26
|
datamodel_code_generator/model/pydantic/dataclass.py,sha256=jgjkqQk71CQP4RbTcPGSEOQDNqjTQnzFavvl5LjWTBw,455
|
|
27
27
|
datamodel_code_generator/model/pydantic/imports.py,sha256=nWPiLgDeYNPHcAs8M-gaUUZg1daQRHdBPpjYuX3b5u4,2225
|
|
28
28
|
datamodel_code_generator/model/pydantic/types.py,sha256=ttTiDsQ6FV3h4C_NTEhvPUmUpeqxBNQt-DJJFpKZS8s,13356
|
|
29
|
-
datamodel_code_generator/model/pydantic_v2/__init__.py,sha256=
|
|
29
|
+
datamodel_code_generator/model/pydantic_v2/__init__.py,sha256=6liTgL3VeH4ohYXkRoiCSafEc0nHxgbnYO3Rl2iQDx8,1290
|
|
30
30
|
datamodel_code_generator/model/pydantic_v2/base_model.py,sha256=J_DxR6Auw0L-zHn0F5l9K8XtSmfEvDT26Bj-VZxihiE,8353
|
|
31
31
|
datamodel_code_generator/model/pydantic_v2/imports.py,sha256=K3XD2kF9YCKmo5_7b2ipV5bGUrjz0avS-SiyDMVIpF0,299
|
|
32
32
|
datamodel_code_generator/model/pydantic_v2/root_model.py,sha256=H4rwtg56N65-I3QHlPvlNhDcSPV0m56KSAgfGmxYXAQ,888
|
|
@@ -37,7 +37,7 @@ datamodel_code_generator/model/template/TypedDict.jinja2,sha256=J_Pe_CiuvTOb-EUC
|
|
|
37
37
|
datamodel_code_generator/model/template/TypedDictClass.jinja2,sha256=URwp5__WyR8G21Hoyc17aMzoast-NppXnXe19VFi5wQ,377
|
|
38
38
|
datamodel_code_generator/model/template/TypedDictFunction.jinja2,sha256=KjSij5_w4ow4a12SR3orYOndmXGkIvJBBUN735bQ6G0,321
|
|
39
39
|
datamodel_code_generator/model/template/Union.jinja2,sha256=sq7o--2ESUSfIL4kCfgnr5ZXPFa_VeioqbATTY-N-5I,258
|
|
40
|
-
datamodel_code_generator/model/template/dataclass.jinja2,sha256=
|
|
40
|
+
datamodel_code_generator/model/template/dataclass.jinja2,sha256=c3gs1ZwDEwLpmZ2PpOEWjHjfdl6kPP64xm18mt9lZMk,1007
|
|
41
41
|
datamodel_code_generator/model/template/msgspec.jinja2,sha256=qMuFOH6SFFh558wImdI6uIjG4Mtam3J_ox8Hmgqkv0g,1174
|
|
42
42
|
datamodel_code_generator/model/template/root.jinja2,sha256=3OTtibxLcGA-FMdR0QDCJUJQgf_kRW0OafeCTPFSFFo,162
|
|
43
43
|
datamodel_code_generator/model/template/pydantic/BaseModel.jinja2,sha256=sYZa-47YAXqZrd5cYKVnPrsbDvLkHEJOUd7M0nAosP8,1084
|
|
@@ -48,12 +48,12 @@ datamodel_code_generator/model/template/pydantic_v2/BaseModel.jinja2,sha256=i1Wg
|
|
|
48
48
|
datamodel_code_generator/model/template/pydantic_v2/ConfigDict.jinja2,sha256=xHvBYrh__32O1xRCSl6_u5zbyYIjB8a5k8fZiTo0spY,149
|
|
49
49
|
datamodel_code_generator/model/template/pydantic_v2/RootModel.jinja2,sha256=XQBlML7Hm5hN6_AExENNvVc_yxNWijcIfTTbbmegCpE,1223
|
|
50
50
|
datamodel_code_generator/parser/__init__.py,sha256=3XtFcDPocaetfjmWFqj_CubqNCDipb7vXZHsYKdJXXU,851
|
|
51
|
-
datamodel_code_generator/parser/base.py,sha256=
|
|
52
|
-
datamodel_code_generator/parser/graphql.py,sha256=
|
|
53
|
-
datamodel_code_generator/parser/jsonschema.py,sha256=
|
|
54
|
-
datamodel_code_generator/parser/openapi.py,sha256=
|
|
55
|
-
datamodel_code_generator-0.30.
|
|
56
|
-
datamodel_code_generator-0.30.
|
|
57
|
-
datamodel_code_generator-0.30.
|
|
58
|
-
datamodel_code_generator-0.30.
|
|
59
|
-
datamodel_code_generator-0.30.
|
|
51
|
+
datamodel_code_generator/parser/base.py,sha256=2rNtyo9Gcu4lSihbQKtawL6W7KD4keM2CR1KHgnrNso,62794
|
|
52
|
+
datamodel_code_generator/parser/graphql.py,sha256=w4rnPXxQWGluG9dKlfv9XG1sv8T7HJOq9wySd-EsfDs,23250
|
|
53
|
+
datamodel_code_generator/parser/jsonschema.py,sha256=cjxYs2vQ2aka5YpqagDkNhyhvBwNXz7YOrdsJz7bVeQ,71034
|
|
54
|
+
datamodel_code_generator/parser/openapi.py,sha256=oY_qOngrxr4fHk9b6YUNI2IQCWkUeAJ9-CvTCTym8WM,27527
|
|
55
|
+
datamodel_code_generator-0.30.2.dist-info/METADATA,sha256=AXlPlc6b1QyG1ylx4pUj6NZqLI-P59K3QZw4e2vSjlY,25430
|
|
56
|
+
datamodel_code_generator-0.30.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
57
|
+
datamodel_code_generator-0.30.2.dist-info/entry_points.txt,sha256=cJVcHiEViQMANaoM5C1xR5hzmyCqH6hHHMpV8W00in8,77
|
|
58
|
+
datamodel_code_generator-0.30.2.dist-info/licenses/LICENSE,sha256=K54Lwc6_jduycsy8oFFjQEeSSuEiqvVIjCGIXOMnuTQ,1068
|
|
59
|
+
datamodel_code_generator-0.30.2.dist-info/RECORD,,
|
{datamodel_code_generator-0.30.0.dist-info → datamodel_code_generator-0.30.2.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|