Description
When using generate() with use_annotated=True, use_inline_field_description=True, and disable_future_imports=True, the generated code uses Optional in type annotations but does not import it from typing.
The generated code only imports Annotated but not Optional:
from typing import Annotated # <-- Missing Optional!
contactType: Annotated[Optional[ContactType], Field(description="...")] # <-- NameError
With the default from __future__ import annotations, this bug is masked because annotations become strings and aren't evaluated at class definition time. But it surfaces as soon as Pydantic tries to resolve them (e.g. during model_rebuild()), or immediately if disable_future_imports=True is used.
Minimal Reproduction
import json, sys, warnings
from io import StringIO
from datamodel_code_generator import DataModelType, InputFileType, generate
schema = {
"type": "object",
"properties": {
"clientKey": {"type": "string", "description": "Client key"},
"contactType": {
"type": ["string", "null"],
"enum": ["Main", "Sales", "Service", null],
"description": "Contact type"
}
},
"required": ["clientKey", "contactType"]
}
schema_json = json.dumps(schema, sort_keys=True)
old_stdout = sys.stdout
sys.stdout = StringIO()
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=DeprecationWarning)
generate(
schema_json,
input_file_type=InputFileType.JsonSchema,
output=None,
class_name="TestModel",
output_model_type=DataModelType.PydanticV2BaseModel,
use_annotated=True,
use_inline_field_description=True,
disable_future_imports=True,
formatters=[],
)
code = sys.stdout.getvalue()
sys.stdout = old_stdout
print(code)
# Output shows: from typing import Annotated (no Optional)
# But annotation uses: Annotated[Optional[ContactType], Field(...)]
exec(code, {"__builtins__": __builtins__})
# NameError: name 'Optional' is not defined
Generated Code
from enum import Enum
from typing import Annotated # <-- Missing: Optional
from pydantic import BaseModel, Field
class ContactType(Enum):
Main = 'Main'
Sales = 'Sales'
Service = 'Service'
NoneType_None = None
class TestModel(BaseModel):
clientKey: Annotated[str, Field(description='Client key')]
contactType: Annotated[Optional[ContactType], Field(description='Contact type')]
# ^^^^^^^^ not imported
Impact
- With
disable_future_imports=True: immediate NameError during code execution
- With default
from __future__ import annotations: masked at definition time, but causes PydanticUndefinedAnnotation: name 'Optional' is not defined when Pydantic evaluates the deferred annotation strings
This affects anyone using generate() programmatically where the generated code is executed via exec() or dynamically loaded.
Trigger Conditions
The bug requires all of:
- A nullable field (e.g.
"type": ["string", "null"]) with an enum
- A field description (triggers
Annotated[..., Field(description=...)])
use_annotated=True
The Annotated[..., Field(...)] wrapping seems to cause the import tracker to miss the Optional dependency.
Environment
- datamodel-code-generator: 0.35.0
- pydantic: 2.12.5
- Python: 3.13
Related
Description
When using
generate()withuse_annotated=True,use_inline_field_description=True, anddisable_future_imports=True, the generated code usesOptionalin type annotations but does not import it fromtyping.The generated code only imports
Annotatedbut notOptional:With the default
from __future__ import annotations, this bug is masked because annotations become strings and aren't evaluated at class definition time. But it surfaces as soon as Pydantic tries to resolve them (e.g. duringmodel_rebuild()), or immediately ifdisable_future_imports=Trueis used.Minimal Reproduction
Generated Code
Impact
disable_future_imports=True: immediateNameErrorduring code executionfrom __future__ import annotations: masked at definition time, but causesPydanticUndefinedAnnotation: name 'Optional' is not definedwhen Pydantic evaluates the deferred annotation stringsThis affects anyone using
generate()programmatically where the generated code is executed viaexec()or dynamically loaded.Trigger Conditions
The bug requires all of:
"type": ["string", "null"]) with an enumAnnotated[..., Field(description=...)])use_annotated=TrueThe
Annotated[..., Field(...)]wrapping seems to cause the import tracker to miss theOptionaldependency.Environment
Related
Optional+use_annotatedgenerating problematic patternsLiteralwhen using--enum-field-as-literal=oneoption #2191 - Similar missing import bugs forLiteral