diff --git a/pydantic/_internal/_core_utils.py b/pydantic/_internal/_core_utils.py index ebf12ec0fa..501e5bbc44 100644 --- a/pydantic/_internal/_core_utils.py +++ b/pydantic/_internal/_core_utils.py @@ -8,15 +8,15 @@ Hashable, TypeVar, Union, - _GenericAlias, # type: ignore cast, ) from pydantic_core import CoreSchema, core_schema from pydantic_core import validate_core_schema as _validate_core_schema -from typing_extensions import TypeAliasType, TypeGuard, get_args +from typing_extensions import TypeAliasType, TypeGuard, get_args, get_origin from . import _repr +from ._typing_extra import is_generic_alias AnyFunctionSchema = Union[ core_schema.AfterValidatorFunctionSchema, @@ -86,8 +86,9 @@ def get_type_ref(type_: type[Any], args_override: tuple[type[Any], ...] | None = This `args_override` argument was added for the purpose of creating valid recursive references when creating generic models without needing to create a concrete class. """ - origin = type_ - args = get_args(type_) if isinstance(type_, _GenericAlias) else (args_override or ()) + origin = get_origin(type_) or type_ + + args = get_args(type_) if is_generic_alias(type_) else (args_override or ()) generic_metadata = getattr(type_, '__pydantic_generic_metadata__', None) if generic_metadata: origin = generic_metadata['origin'] or origin diff --git a/pydantic/_internal/_generate_schema.py b/pydantic/_internal/_generate_schema.py index 6980b414b0..15f16bc1eb 100644 --- a/pydantic/_internal/_generate_schema.py +++ b/pydantic/_internal/_generate_schema.py @@ -1052,18 +1052,20 @@ def _type_alias_type_schema( self, obj: Any, # TypeAliasType ) -> CoreSchema: - origin = get_origin(obj) - origin = origin or obj - with self.defs.get_schema_or_ref(origin) as (ref, maybe_schema): + with self.defs.get_schema_or_ref(obj) as (ref, maybe_schema): if maybe_schema is not None: return maybe_schema + origin = get_origin(obj) or obj + namespace = (self._types_namespace or {}).copy() new_namespace = {**_typing_extra.get_cls_types_namespace(origin), **namespace} annotation = origin.__value__ self._types_namespace = new_namespace typevars_map = get_standard_typevars_map(obj) + + annotation = _typing_extra.eval_type_lenient(annotation, self._types_namespace, None) annotation = replace_types(annotation, typevars_map) schema = self.generate_schema(annotation) assert schema['type'] != 'definitions' diff --git a/pydantic/_internal/_typing_extra.py b/pydantic/_internal/_typing_extra.py index e83e03d2b8..8b94d472d7 100644 --- a/pydantic/_internal/_typing_extra.py +++ b/pydantic/_internal/_typing_extra.py @@ -433,3 +433,14 @@ def is_dataclass(_cls: type[Any]) -> TypeGuard[type[StandardDataclass]]: def origin_is_type_alias_type(origin: Any) -> TypeGuard[TypeAliasType]: return isinstance(origin, TypeAliasType) + + +if sys.version_info >= (3, 10): + + def is_generic_alias(type_: type[Any]) -> bool: + return isinstance(type_, (types.GenericAlias, typing._GenericAlias)) # type: ignore[attr-defined] + +else: + + def is_generic_alias(type_: type[Any]) -> bool: + return isinstance(type_, typing._GenericAlias) # type: ignore diff --git a/tests/test_type_alias_type.py b/tests/test_type_alias_type.py index cff2752c6e..23659ca027 100644 --- a/tests/test_type_alias_type.py +++ b/tests/test_type_alias_type.py @@ -5,7 +5,7 @@ from annotated_types import MaxLen from typing_extensions import Annotated, TypeAliasType -from pydantic import Field, ValidationError +from pydantic import BaseModel, Field, ValidationError from pydantic.type_adapter import TypeAdapter T = TypeVar('T') @@ -182,11 +182,11 @@ def test_type_alias_annotated_defs() -> None: 'type': 'array', 'minItems': 2, 'prefixItems': [ - {'$ref': '#/$defs/MyList_MaxLen_max_length_1_'}, - {'$ref': '#/$defs/MyList_MaxLen_max_length_1_'}, + {'$ref': '#/$defs/MyList_int__MaxLen_max_length_1_'}, + {'$ref': '#/$defs/MyList_int__MaxLen_max_length_1_'}, ], 'maxItems': 2, - '$defs': {'MyList_MaxLen_max_length_1_': {'type': 'array', 'items': {'type': 'integer'}, 'maxItems': 1}}, + '$defs': {'MyList_int__MaxLen_max_length_1_': {'type': 'array', 'items': {'type': 'integer'}, 'maxItems': 1}}, } @@ -220,11 +220,11 @@ def test_recursive_generic_type_alias() -> None: ] assert t.json_schema() == { - 'allOf': [{'$ref': '#/$defs/RecursiveGenericAlias'}], + 'allOf': [{'$ref': '#/$defs/RecursiveGenericAlias_int_'}], '$defs': { - 'RecursiveGenericAlias': { + 'RecursiveGenericAlias_int_': { 'type': 'array', - 'items': {'anyOf': [{'$ref': '#/$defs/RecursiveGenericAlias'}, {'type': 'integer'}]}, + 'items': {'anyOf': [{'$ref': '#/$defs/RecursiveGenericAlias_int_'}, {'type': 'integer'}]}, } }, } @@ -250,12 +250,12 @@ def test_recursive_generic_type_alias_annotated() -> None: # insert_assert(t.json_schema()) assert t.json_schema() == { 'type': 'array', - 'items': {'anyOf': [{'$ref': '#/$defs/RecursiveGenericAlias'}, {'type': 'integer'}]}, + 'items': {'anyOf': [{'$ref': '#/$defs/RecursiveGenericAlias_int_'}, {'type': 'integer'}]}, 'maxItems': 1, '$defs': { - 'RecursiveGenericAlias': { + 'RecursiveGenericAlias_int_': { 'type': 'array', - 'items': {'anyOf': [{'$ref': '#/$defs/RecursiveGenericAlias'}, {'type': 'integer'}]}, + 'items': {'anyOf': [{'$ref': '#/$defs/RecursiveGenericAlias_int_'}, {'type': 'integer'}]}, } }, } @@ -284,18 +284,18 @@ def test_recursive_generic_type_alias_annotated_defs() -> None: 'type': 'array', 'minItems': 2, 'prefixItems': [ - {'$ref': '#/$defs/RecursiveGenericAlias_MaxLen_max_length_1_'}, - {'$ref': '#/$defs/RecursiveGenericAlias_MaxLen_max_length_1_'}, + {'$ref': '#/$defs/RecursiveGenericAlias_int__MaxLen_max_length_1_'}, + {'$ref': '#/$defs/RecursiveGenericAlias_int__MaxLen_max_length_1_'}, ], 'maxItems': 2, '$defs': { - 'RecursiveGenericAlias': { + 'RecursiveGenericAlias_int_': { 'type': 'array', - 'items': {'anyOf': [{'$ref': '#/$defs/RecursiveGenericAlias'}, {'type': 'integer'}]}, + 'items': {'anyOf': [{'$ref': '#/$defs/RecursiveGenericAlias_int_'}, {'type': 'integer'}]}, }, - 'RecursiveGenericAlias_MaxLen_max_length_1_': { + 'RecursiveGenericAlias_int__MaxLen_max_length_1_': { 'type': 'array', - 'items': {'anyOf': [{'$ref': '#/$defs/RecursiveGenericAlias'}, {'type': 'integer'}]}, + 'items': {'anyOf': [{'$ref': '#/$defs/RecursiveGenericAlias_int_'}, {'type': 'integer'}]}, 'maxItems': 1, }, }, @@ -314,3 +314,26 @@ def test_field() -> None: 'allOf': [{'$ref': '#/$defs/SomeAlias'}], 'title': 'abc', } + + +def test_nested_generic_type_alias_type() -> None: + class MyModel(BaseModel): + field_1: MyList[bool] + field_2: MyList[str] + + model = MyModel(field_1=[True], field_2=['abc']) + + assert model.model_json_schema() == { + '$defs': { + 'MyList_bool_': {'items': {'type': 'boolean'}, 'type': 'array'}, + 'MyList_str_': {'items': {'type': 'string'}, 'type': 'array'}, + }, + 'properties': {'field_1': {'$ref': '#/$defs/MyList_bool_'}, 'field_2': {'$ref': '#/$defs/MyList_str_'}}, + 'required': ['field_1', 'field_2'], + 'title': 'MyModel', + 'type': 'object', + } + + +def test_non_specified_generic_type_alias_type() -> None: + assert TypeAdapter(MyList).json_schema() == {'items': {}, 'type': 'array'}