diff --git a/src/datamodel_code_generator/__main__.py b/src/datamodel_code_generator/__main__.py index d79dfaaa5..70f3fba0c 100644 --- a/src/datamodel_code_generator/__main__.py +++ b/src/datamodel_code_generator/__main__.py @@ -689,9 +689,16 @@ def _serialize_callable(args: tuple[type, ...]) -> str: def _get_origin_name(origin: type) -> str: - """Get the name of a generic origin.""" + """Get the fully qualified name of a generic origin. + + For types from builtins, typing, or collections.abc, returns just the name. + For other types (custom generics), returns module.name format. + """ name = getattr(origin, "__name__", None) if name: + module = getattr(origin, "__module__", "") + if module and module not in {"builtins", "typing", "collections.abc"}: + return f"{module}.{name}" return name # Fallback for origins without __name__ (rare edge case) diff --git a/tests/data/python/input_model/pydantic_models.py b/tests/data/python/input_model/pydantic_models.py index d8016f518..24e3b4dc2 100644 --- a/tests/data/python/input_model/pydantic_models.py +++ b/tests/data/python/input_model/pydantic_models.py @@ -2,11 +2,22 @@ from __future__ import annotations +from collections import UserDict from collections.abc import Callable, Mapping, Sequence -from typing import Any, FrozenSet, Optional, Set, Type, Union +from typing import Any, FrozenSet, Generic, Optional, Set, Type, TypeVar, Union from pydantic import BaseModel +# Custom generic type for testing generic type import +TK = TypeVar("TK") +TV = TypeVar("TV") + + +class CustomGenericDict(UserDict[TK, TV], Generic[TK, TV]): + """Custom generic dict for testing generic type import.""" + + pass + class User(BaseModel): """User model with basic fields.""" @@ -86,3 +97,11 @@ class ModelWithUnionCallable(BaseModel): union_callback: Union[Callable[[str], str], int] raw_callable: Callable # Callable without type args + + +class ModelWithCustomGeneric(BaseModel): + """Model with custom generic type that requires module import.""" + + model_config = {"arbitrary_types_allowed": True} + custom_dict: CustomGenericDict[str, int] + optional_custom_dict: CustomGenericDict[str, str] | None diff --git a/tests/test_input_model.py b/tests/test_input_model.py index 01f2fc8ef..ca6be617d 100644 --- a/tests/test_input_model.py +++ b/tests/test_input_model.py @@ -727,6 +727,20 @@ def test_input_model_union_callable(tmp_path: Path) -> None: ) +@SKIP_PYDANTIC_V1 +def test_input_model_custom_generic_type_import(tmp_path: Path) -> None: + """Test that custom generic types are properly imported with full module path.""" + run_input_model_and_assert( + input_model="tests.data.python.input_model.pydantic_models:ModelWithCustomGeneric", + output_path=tmp_path / "output.py", + expected_output_contains=[ + "from tests.data.python.input_model.pydantic_models import CustomGenericDict", + "CustomGenericDict[str, int]", + "CustomGenericDict[str, str] | None", + ], + ) + + # ============================================================================ # --input-model-ref-strategy tests # ============================================================================