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
25 changes: 21 additions & 4 deletions src/datamodel_code_generator/model/pydantic/base_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
DataModelFieldBase,
)
from datamodel_code_generator.model._types import WrappedDefault
from datamodel_code_generator.model.base import UNDEFINED
from datamodel_code_generator.model.base import UNDEFINED, repr_set_sorted
Comment thread Dismissed
from datamodel_code_generator.model.pydantic.imports import (
IMPORT_ANYURL,
IMPORT_EXTRA,
Expand Down Expand Up @@ -91,6 +91,8 @@ def validator(self) -> str | None:
@property
def field(self) -> str | None:
"""For backwards compatibility."""
if self.is_class_var:
return None
result = str(self)
if (
self.use_default_kwarg
Expand Down Expand Up @@ -250,17 +252,32 @@ def __str__(self) -> str: # noqa: PLR0912
elif self.required:
field_arguments = ["...", *field_arguments]
elif not default_factory:
from datamodel_code_generator.model.base import repr_set_sorted # noqa: PLC0415

default_repr = repr_set_sorted(self.default) if isinstance(self.default, set) else repr(self.default)
field_arguments = [default_repr, *field_arguments]

if self.is_class_var:
if self.default is UNDEFINED: # pragma: no cover
return ""
return repr_set_sorted(self.default) if isinstance(self.default, set) else repr(self.default)

return f"Field({', '.join(field_arguments)})"

@property
def is_class_var(self) -> bool:
"""Check if this field is a ClassVar."""
return self.extras.get("x-is-classvar") is True

@property
def type_hint(self) -> str:
"""Get the type hint including ClassVar if applicable."""
if self.is_class_var:
return f"ClassVar[{super().type_hint}]"
return super().type_hint
Comment thread
coderabbitai[bot] marked this conversation as resolved.

@property
def annotated(self) -> str | None:
"""Get the Annotated type hint if use_annotated is enabled."""
if not self.use_annotated or not str(self):
if not self.use_annotated or not str(self) or self.is_class_var:
return None
return f"Annotated[{self.type_hint}, {self!s}]"

Expand Down
3 changes: 3 additions & 0 deletions src/datamodel_code_generator/model/pydantic_v2/base_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from datamodel_code_generator.imports import IMPORT_ANY, Import
from datamodel_code_generator.model.base import ALL_MODEL, UNDEFINED, BaseClassDataType, DataModelFieldBase
from datamodel_code_generator.model.imports import IMPORT_CLASSVAR
from datamodel_code_generator.model.pydantic.base_model import (
BaseModelBase,
)
Expand Down Expand Up @@ -192,6 +193,8 @@ def imports(self) -> tuple[Import, ...]:
"""Get all required imports including AliasChoices and Field for discriminator."""
base_imports = super().imports
extra_imports: list[Import] = []
if self.is_class_var:
extra_imports.append(IMPORT_CLASSVAR)
if self.validation_aliases:
from datamodel_code_generator.model.pydantic_v2.imports import IMPORT_ALIAS_CHOICES # noqa: PLC0415

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

from __future__ import annotations

from typing import ClassVar

from pydantic import BaseModel


class Model(BaseModel):
namespace: ClassVar[str | None] = 'test'
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# generated by datamodel-codegen:
# filename: has_classvar_extra_annotated.json
# timestamp: 2019-07-26T00:00:00+00:00

from __future__ import annotations

from typing import ClassVar

from pydantic import BaseModel


class Model(BaseModel):
namespace: ClassVar[str | None] = 'test'
13 changes: 13 additions & 0 deletions tests/data/expected/main/jsonschema/has_classvar_extra_set.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# generated by datamodel-codegen:
# filename: has_classvar_extra_set.json
# timestamp: 2019-07-26T00:00:00+00:00

from __future__ import annotations

from typing import ClassVar

from pydantic import BaseModel


class Model(BaseModel):
tags: ClassVar[set[str] | None] = {'a', 'b'}
10 changes: 10 additions & 0 deletions tests/data/jsonschema/has_classvar_extra.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"type": "object",
"properties": {
"namespace": {
"type": "string",
"x-is-classvar": true,
"default": "test"
}
}
}
11 changes: 11 additions & 0 deletions tests/data/jsonschema/has_classvar_extra_annotated.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"type": "object",
"properties": {
"namespace": {
"type": "string",
"x-is-classvar": true,
"default": "test",
"minLength": 1
}
}
}
14 changes: 14 additions & 0 deletions tests/data/jsonschema/has_classvar_extra_set.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"type": "object",
"properties": {
"tags": {
"type": "array",
"x-is-classvar": true,
"default": ["a", "b"],
"uniqueItems": true,
"items": {
"type": "string"
}
}
}
}
46 changes: 46 additions & 0 deletions tests/main/jsonschema/test_main_jsonschema.py
Original file line number Diff line number Diff line change
Expand Up @@ -8001,6 +8001,52 @@ def test_validators_requires_pydantic_v2(output_file: Path, tmp_path: Path, caps
)


def test_jsonschema_classvar_extra_pydantic_v2(output_file: Path) -> None:
"""Test default value handling."""
run_main_and_assert(
input_path=JSON_SCHEMA_DATA_PATH / "has_classvar_extra.json",
output_path=output_file,
input_file_type="jsonschema",
assert_func=assert_file_content,
expected_file="has_classvar_extra.py",
extra_args=["--output-model-type", "pydantic_v2.BaseModel", "--field-include-all-keys"],
)


def test_jsonschema_classvar_extra_set_pydantic_v2(output_file: Path) -> None:
"""Test ClassVar with set default value."""
run_main_and_assert(
input_path=JSON_SCHEMA_DATA_PATH / "has_classvar_extra_set.json",
output_path=output_file,
input_file_type="jsonschema",
assert_func=assert_file_content,
expected_file="has_classvar_extra_set.py",
extra_args=[
"--output-model-type",
"pydantic_v2.BaseModel",
"--field-include-all-keys",
"--use-unique-items-as-set",
],
)


def test_jsonschema_classvar_extra_annotated_pydantic_v2(output_file: Path) -> None:
"""Test ClassVar with use_annotated option."""
run_main_and_assert(
input_path=JSON_SCHEMA_DATA_PATH / "has_classvar_extra_annotated.json",
output_path=output_file,
input_file_type="jsonschema",
assert_func=assert_file_content,
expected_file="has_classvar_extra_annotated.py",
extra_args=[
"--output-model-type",
"pydantic_v2.BaseModel",
"--field-include-all-keys",
"--use-annotated",
],
)


@PYDANTIC_V2_SKIP
def test_unique_items_enum_set(output_file: Path) -> None:
"""Test set with enum items does not add __hash__ to enum (already hashable)."""
Expand Down
Loading