Skip to content

Commit f7ffbd0

Browse files
authored
Fix handling of ClassVar fields in msgspec rendering to avoid conflicts (#2663)
1 parent dbddbac commit f7ffbd0

15 files changed

Lines changed: 46 additions & 84 deletions

src/datamodel_code_generator/model/msgspec.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
from datamodel_code_generator.model import DataModel, DataModelFieldBase
2323
from datamodel_code_generator.model.base import UNDEFINED
2424
from datamodel_code_generator.model.imports import (
25-
IMPORT_CLASSVAR,
2625
IMPORT_MSGSPEC_CONVERT,
2726
IMPORT_MSGSPEC_FIELD,
2827
IMPORT_MSGSPEC_META,
@@ -82,6 +81,8 @@ def import_extender(cls: type[DataModelFieldBaseT]) -> type[DataModelFieldBaseT]
8281

8382
@wraps(original_imports.fget) # pyright: ignore[reportArgumentType]
8483
def new_imports(self: DataModelFieldBaseT) -> tuple[Import, ...]:
84+
if self.extras.get("is_classvar"):
85+
return ()
8586
extra_imports = []
8687
field = self.field
8788
# TODO: Improve field detection
@@ -91,8 +92,6 @@ def new_imports(self: DataModelFieldBaseT) -> tuple[Import, ...]:
9192
extra_imports.append(IMPORT_MSGSPEC_CONVERT)
9293
if isinstance(self, DataModelField) and self.needs_meta_import:
9394
extra_imports.append(IMPORT_MSGSPEC_META)
94-
if self.extras.get("is_classvar"):
95-
extra_imports.append(IMPORT_CLASSVAR)
9695
if not self.required and not self.nullable:
9796
extra_imports.append(IMPORT_MSGSPEC_UNSETTYPE)
9897
if not self.data_type.use_union_operator:

src/datamodel_code_generator/model/template/msgspec.jinja2

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ class {{ class_name }}:
1313
{{ description | indent(4) }}
1414
"""
1515
{%- endif %}
16-
{%- if not fields and not description %}
17-
pass
18-
{%- endif %}
16+
{%- set ns = namespace(has_rendered_field=false) -%}
1917
{%- for field in fields -%}
20-
{%- if not field.annotated and field.field %}
18+
{%- if field.extras.get('is_classvar') %}
19+
{#- Skip fields with is_classvar=True - they are managed by msgspec tag_field -#}
20+
{%- elif not field.annotated and field.field %}
21+
{%- set ns.has_rendered_field = true %}
2122
{{ field.name }}: {{ field.type_hint }} = {{ field.field }}
2223
{%- else %}
24+
{%- set ns.has_rendered_field = true %}
2325
{%- if field.annotated and not field.field %}
2426
{{ field.name }}: {{ field.annotated }}
2527
{%- elif field.annotated and field.field %}
@@ -34,17 +36,20 @@ class {{ class_name }}:
3436

3537

3638

37-
{%- if field.docstring %}
39+
{%- if not field.extras.get('is_classvar') and field.docstring %}
3840
"""
3941
{{ field.docstring | indent(4) }}
4042
"""
4143
{%- if field.use_inline_field_description and not loop.last %}
4244

4345
{% endif %}
44-
{%- elif field.inline_field_docstring %}
46+
{%- elif not field.extras.get('is_classvar') and field.inline_field_docstring %}
4547
{{ field.inline_field_docstring }}
4648
{%- if not loop.last %}
4749

4850
{% endif %}
4951
{%- endif %}
5052
{%- endfor -%}
53+
{%- if not ns.has_rendered_field and not description %}
54+
pass
55+
{%- endif -%}

src/datamodel_code_generator/parser/base.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2028,6 +2028,8 @@ def walk_data_type(data_type: DataType) -> None:
20282028
for import_ in model.imports:
20292029
add(import_.alias or import_.import_.split(".")[-1])
20302030
for field in model.fields:
2031+
if field.extras.get("is_classvar"):
2032+
continue
20312033
add(field.name)
20322034
add(field.alias)
20332035
walk_data_type(field.data_type)
@@ -2495,7 +2497,9 @@ class Processed(NamedTuple):
24952497
)
24962498
]
24972499
for from_, import_ in unused_imports:
2498-
processed_model.imports.remove(Import(from_=from_, import_=import_))
2500+
import_obj = Import(from_=from_, import_=import_)
2501+
while processed_model.imports.counter.get((from_, import_), 0) > 0:
2502+
processed_model.imports.remove(import_obj)
24992503

25002504
for module, mod_key, models, init, imports, scoped_model_resolver in processed_models: # noqa: B007
25012505
# process after removing unused models

tests/data/expected/main/jsonschema/discriminator_literals_msgspec.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,17 @@
44

55
from __future__ import annotations
66

7-
from typing import Annotated, ClassVar, Literal, Union
7+
from typing import Annotated, Union
88

99
from msgspec import Meta, Struct, UnsetType
1010

1111

1212
class Type1(Struct, tag_field='type_', tag='a'):
13-
type_: ClassVar[Annotated[Union[Literal['a'], UnsetType], Meta(title='Type ')]] = (
14-
'a'
15-
)
13+
pass
1614

1715

1816
class Type2(Struct, tag_field='type_', tag='b'):
19-
type_: ClassVar[Annotated[Union[Literal['b'], UnsetType], Meta(title='Type ')]] = (
20-
'b'
21-
)
17+
pass
2218

2319

2420
class UnrelatedType(Struct):

tests/data/expected/main/jsonschema/discriminator_literals_msgspec_keyword_only.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,17 @@
44

55
from __future__ import annotations
66

7-
from typing import Annotated, ClassVar, Literal, Union
7+
from typing import Annotated, Union
88

99
from msgspec import Meta, Struct, UnsetType
1010

1111

1212
class Type1(Struct, kw_only=True, tag_field='type_', tag='a'):
13-
type_: ClassVar[Annotated[Union[Literal['a'], UnsetType], Meta(title='Type ')]] = (
14-
'a'
15-
)
13+
pass
1614

1715

1816
class Type2(Struct, kw_only=True, tag_field='type_', tag='b'):
19-
type_: ClassVar[Annotated[Union[Literal['b'], UnsetType], Meta(title='Type ')]] = (
20-
'b'
21-
)
17+
pass
2218

2319

2420
class UnrelatedType(Struct, kw_only=True):

tests/data/expected/main/jsonschema/discriminator_literals_msgspec_keyword_only_omit_defaults.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,17 @@
44

55
from __future__ import annotations
66

7-
from typing import Annotated, ClassVar, Literal, Union
7+
from typing import Annotated, Union
88

99
from msgspec import Meta, Struct, UnsetType
1010

1111

1212
class Type1(Struct, omit_defaults=True, kw_only=True, tag_field='type_', tag='a'):
13-
type_: ClassVar[Annotated[Union[Literal['a'], UnsetType], Meta(title='Type ')]] = (
14-
'a'
15-
)
13+
pass
1614

1715

1816
class Type2(Struct, omit_defaults=True, kw_only=True, tag_field='type_', tag='b'):
19-
type_: ClassVar[Annotated[Union[Literal['b'], UnsetType], Meta(title='Type ')]] = (
20-
'b'
21-
)
17+
pass
2218

2319

2420
class UnrelatedType(Struct, omit_defaults=True, kw_only=True):

tests/data/expected/main/jsonschema/discriminator_with_external_reference_msgspec.py

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,42 +4,31 @@
44

55
from __future__ import annotations
66

7-
from typing import Annotated, ClassVar, Literal, Union
7+
from typing import Annotated, Union
88

99
from msgspec import UNSET, Meta, Struct, UnsetType
1010

1111

1212
class Type1(Struct, tag_field='type_', tag='a'):
13-
type_: ClassVar[Annotated[Union[Literal['a'], UnsetType], Meta(title='Type ')]] = (
14-
'a'
15-
)
13+
pass
1614

1715

1816
class Type2(Struct, tag_field='type_', tag='b'):
19-
type_: ClassVar[Annotated[Union[Literal['b'], UnsetType], Meta(title='Type ')]] = (
20-
'b'
21-
)
2217
ref_type: Union[
2318
Annotated[Type1, Meta(description='A referenced type.')], UnsetType
2419
] = UNSET
2520

2621

2722
class Type4(Struct, tag_field='type_', tag='d'):
28-
type_: ClassVar[Annotated[Union[Literal['d'], UnsetType], Meta(title='Type ')]] = (
29-
'd'
30-
)
23+
pass
3124

3225

3326
class Type5(Struct, tag_field='type_', tag='e'):
34-
type_: ClassVar[Annotated[Union[Literal['e'], UnsetType], Meta(title='Type ')]] = (
35-
'e'
36-
)
27+
pass
3728

3829

3930
class Type3(Struct, tag_field='type_', tag='c'):
40-
type_: ClassVar[Annotated[Union[Literal['c'], UnsetType], Meta(title='Type ')]] = (
41-
'c'
42-
)
31+
pass
4332

4433

4534
class Response(Struct):

tests/data/expected/main/jsonschema/discriminator_with_external_references_folder_msgspec/inner_folder/artificial_folder/type_1.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,8 @@
44

55
from __future__ import annotations
66

7-
from typing import Annotated, ClassVar, Literal, Union
8-
9-
from msgspec import Meta, Struct, UnsetType
7+
from msgspec import Struct
108

119

1210
class Type1(Struct, tag_field='type_', tag='a'):
13-
type_: ClassVar[Annotated[Union[Literal['a'], UnsetType], Meta(title='Type ')]] = (
14-
'a'
15-
)
11+
pass

tests/data/expected/main/jsonschema/discriminator_with_external_references_folder_msgspec/inner_folder/schema.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44

55
from __future__ import annotations
66

7-
from typing import Annotated, ClassVar, Literal, Union
7+
from typing import Annotated, Union
88

9-
from msgspec import Meta, Struct, UnsetType
9+
from msgspec import Meta, Struct
1010

1111
from .. import type_4
1212
from ..subfolder import type_5
@@ -15,9 +15,7 @@
1515

1616

1717
class Type3(Struct, tag_field='type_', tag='c'):
18-
type_: ClassVar[Annotated[Union[Literal['c'], UnsetType], Meta(title='Type ')]] = (
19-
'c'
20-
)
18+
pass
2119

2220

2321
class Response(Struct):

tests/data/expected/main/jsonschema/discriminator_with_external_references_folder_msgspec/inner_folder/type_2.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,14 @@
44

55
from __future__ import annotations
66

7-
from typing import Annotated, ClassVar, Literal, Union
7+
from typing import Annotated, Union
88

99
from msgspec import UNSET, Meta, Struct, UnsetType
1010

1111
from .artificial_folder import type_1
1212

1313

1414
class Type2(Struct, tag_field='type_', tag='b'):
15-
type_: ClassVar[Annotated[Union[Literal['b'], UnsetType], Meta(title='Type ')]] = (
16-
'b'
17-
)
1815
ref_type: Union[
1916
Annotated[type_1.Type1, Meta(description='A referenced type.')], UnsetType
2017
] = UNSET

0 commit comments

Comments
 (0)