Skip to content

Commit de949b0

Browse files
authored
Fix ClassVar generation for msgspec Struct discriminator without use_annotated (#2625)
1 parent 0e61504 commit de949b0

3 files changed

Lines changed: 53 additions & 13 deletions

File tree

src/datamodel_code_generator/model/msgspec.py

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -317,40 +317,44 @@ def _get_meta_string(self) -> str | None:
317317

318318
@property
319319
def annotated(self) -> str | None:
320-
"""Get Annotated type hint with Meta constraints."""
320+
"""Get Annotated type hint with Meta constraints.
321+
322+
For ClassVar fields (discriminator tag_field), ClassVar is required
323+
regardless of use_annotated setting.
324+
"""
325+
if self.extras.get("is_classvar"):
326+
meta = self._get_meta_string()
327+
if self.use_annotated and meta:
328+
annotated_type = f"Annotated[{self.type_hint}, {meta}]"
329+
return f"ClassVar[{annotated_type}]"
330+
return f"ClassVar[{self.type_hint}]"
331+
321332
if not self.use_annotated: # pragma: no cover
322333
return None
323334

324335
meta = self._get_meta_string()
325336

326-
if not meta and not self.extras.get("is_classvar"):
337+
if not meta:
327338
return None
328339

329-
if not self.required and not self.extras.get("is_classvar"):
340+
if not self.required:
330341
type_hint = self.data_type.type_hint
331342
annotated_type = f"Annotated[{type_hint}, {meta}]"
332343
return get_neither_required_nor_nullable_type(annotated_type, self.data_type.use_union_operator)
333344

334-
# Handle ClassVar case (for discriminator fields in msgspec)
335-
if self.extras.get("is_classvar"):
336-
if meta:
337-
annotated_type = f"Annotated[{self.type_hint}, {meta}]"
338-
return f"ClassVar[{annotated_type}]"
339-
return f"ClassVar[{self.type_hint}]"
340-
341345
return f"Annotated[{self.type_hint}, {meta}]"
342346

343347
@property
344348
def needs_annotated_import(self) -> bool:
345349
"""Check if this field requires the Annotated import.
346350
347-
ClassVar fields without Meta constraints don't need Annotated.
351+
ClassVar fields with Meta need Annotated only when use_annotated is True.
352+
ClassVar fields without Meta don't need Annotated.
348353
"""
349354
if not self.annotated:
350355
return False
351-
# ClassVar without Meta doesn't use Annotated
352356
if self.extras.get("is_classvar"):
353-
return self._get_meta_string() is not None
357+
return self.use_annotated and self._get_meta_string() is not None
354358
return True
355359

356360
@property
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# generated by datamodel-codegen:
2+
# filename: discriminator_with_type_string.json
3+
# timestamp: 2019-07-26T00:00:00+00:00
4+
5+
from __future__ import annotations
6+
7+
from typing import ClassVar, Literal, Union
8+
9+
from msgspec import UNSET, Struct, UnsetType
10+
11+
12+
class SystemMessage(Struct, tag_field='role', tag='system'):
13+
role: ClassVar[Literal['system']]
14+
content: str
15+
16+
17+
class UserMessage(Struct, tag_field='role', tag='user'):
18+
role: ClassVar[Literal['user']]
19+
content: str
20+
21+
22+
class Model(Struct):
23+
message: Union[SystemMessage, UserMessage, UnsetType] = UNSET

tests/main/jsonschema/test_main_jsonschema.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2521,6 +2521,19 @@ def test_main_msgspec_discriminator_with_meta(output_file: Path) -> None:
25212521
)
25222522

25232523

2524+
@MSGSPEC_LEGACY_BLACK_SKIP
2525+
def test_main_msgspec_discriminator_without_annotated(output_file: Path) -> None:
2526+
"""Test msgspec Struct discriminator generates ClassVar even without use_annotated."""
2527+
generate(
2528+
JSON_SCHEMA_DATA_PATH / "discriminator_with_type_string.json",
2529+
output=output_file,
2530+
output_model_type=DataModelType.MsgspecStruct,
2531+
target_python_version=PythonVersion.PY_310,
2532+
use_annotated=False,
2533+
)
2534+
assert_file_content(output_file, "discriminator_with_type_string_msgspec_no_annotated.py")
2535+
2536+
25242537
@MSGSPEC_LEGACY_BLACK_SKIP
25252538
def test_main_msgspec_null_field(output_file: Path) -> None:
25262539
"""Test msgspec Struct generation with null type fields."""

0 commit comments

Comments
 (0)