Coverage for src / dynapydantic / annotations.py: 100%
25 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-17 17:07 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-17 17:07 +0000
1"""Custom annotations for dynapydantic"""
3import typing as ty
5from pydantic import GetCoreSchemaHandler, PydanticSchemaGenerationError
6from pydantic_core import core_schema
8from .free_funcs import union
9from .subclass_tracking_model import SubclassTrackingModel
10from .tracking_group import TrackingGroup
12ModelT = ty.TypeVar("ModelT", bound=SubclassTrackingModel)
15class PydanticAdapter:
16 """Pydantic type adapter for SubclassTrackingModel"""
18 @staticmethod
19 def __get_pydantic_core_schema__(
20 source_type: type[SubclassTrackingModel],
21 handler: GetCoreSchemaHandler,
22 ) -> core_schema.CoreSchema:
23 """Get the pydantic schema for this type"""
24 return handler(union(source_type))
27if ty.TYPE_CHECKING: # pragma: no cover
28 # From the type checker's perspective, Polymorphic[T] = Annotated[T]
29 Polymorphic = ty.Annotated[ModelT, ...]
30else:
32 class Polymorphic:
33 """Annotation used to mark a type as having duck-typing behavior
35 This annotation is only valid for SubclassTrackingModel's.
37 Similar to SerializeAsAny, a field annotated with this shall serialize as
38 according to its actual type, not the field annotation type. In addition,
39 parsing will function as if the field annotation type were the union of
40 all tracked subclasses.
41 """
43 def __class_getitem__(cls, item: ModelT) -> ty.Any: # noqa: ANN401
44 """Get the annotation for the pydantic field"""
45 if not isinstance(item, type):
46 msg = f"dynapydantic.Polymorphic must be given a type, not {item}"
47 raise TypeError(msg)
49 if getattr(item, "__DYNAPYDANTIC_IMPLICIT_POLYMORPHIC__", False):
50 return item
52 if not issubclass(item, SubclassTrackingModel):
53 msg = (
54 f"Polymorphic was given {item}, which was not a "
55 "SubclassTrackingModel."
56 )
57 raise PydanticSchemaGenerationError(msg)
59 return ty.Annotated[item, PydanticAdapter]
62if ty.TYPE_CHECKING: # pragma: no cover
64 class Union:
65 """Annotation used to get the union out of a dynapydantic entity"""
67 @ty.overload
68 def __class_getitem__(cls, item: type[ModelT]) -> ModelT: ...
70 @ty.overload
71 def __class_getitem__(cls, item: TrackingGroup) -> ty.Any: ... # noqa: ANN401
73 def __class_getitem__(cls, item: TrackingGroup | type[ModelT]) -> object:
74 """Return the union"""
76else:
78 class Union:
79 """Annotation used to get the union out of a dynapydantic entity
81 This annotation is primarily used for using the union of all models in
82 a `TrackingGroup` as a field annotation. It can be used with
83 `SubclassTrackingModel`, but in general, `Polymorphic` is preferable.
84 """
86 def __class_getitem__(
87 cls, item: TrackingGroup | type[SubclassTrackingModel]
88 ) -> ty.Any: # noqa: ANN401
89 """Return the union"""
90 return union(item)