Skip to content

Generated code uses Optional without importing it (with use_annotated=True + disable_future_imports=True) #3008

@shavtvalishvili

Description

@shavtvalishvili

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions