from __future__ import annotations
from dataclasses import dataclass
from typing import TYPE_CHECKING, Generic, TypeVar
from litestar.typing import FieldDefinition
if TYPE_CHECKING:
from typing import Any, Callable
from litestar.dto import DTOField
from litestar.dto._backend import DTOBackend
T = TypeVar("T")
[docs]
class DTOData(Generic[T]):
"""DTO validated data and utility methods."""
__slots__ = ("_backend", "_data_as_builtins")
[docs]
def __init__(self, backend: DTOBackend, data_as_builtins: Any) -> None:
self._backend = backend
self._data_as_builtins = data_as_builtins
[docs]
def create_instance(self, **kwargs: Any) -> T:
"""Create an instance of the DTO validated data.
Args:
**kwargs: Additional data to create the instance with. Takes precedence over DTO validated data.
"""
data = dict(self._data_as_builtins)
for k, v in kwargs.items():
_set_nested_dict_value(data, k.split("__"), v)
return self._backend.transfer_data_from_builtins(data) # type: ignore[no-any-return]
[docs]
def update_instance(self, instance: T, **kwargs: Any) -> T:
"""Update an instance with the DTO validated data.
Args:
instance: The instance to update.
**kwargs: Additional data to update the instance with. Takes precedence over DTO validated data.
"""
data = {**self._data_as_builtins, **kwargs}
for k, v in data.items():
setattr(instance, k, v)
return instance
[docs]
def as_builtins(self) -> Any:
"""Return the DTO validated data as builtins."""
return self._data_as_builtins
def _set_nested_dict_value(d: dict[str, Any], keys: list[str], value: Any) -> None:
if len(keys) == 1:
d[keys[0]] = value
else:
key = keys[0]
d.setdefault(key, {})
_set_nested_dict_value(d[key], keys[1:], value)
[docs]
@dataclass(frozen=True)
class DTOFieldDefinition(FieldDefinition):
"""A model field representation for purposes of generating a DTO backend model type."""
__slots__ = (
"default_factory",
"dto_field",
"model_name",
"passthrough_constraints",
)
model_name: str
"""The name of the model for which the field is generated."""
default_factory: Callable[[], Any] | None
"""Default factory of the field."""
dto_field: DTOField
"""DTO field configuration."""
passthrough_constraints: bool
"""Pass constraints of the source annotation to be validated by the DTO backend"""
[docs]
@classmethod
def from_field_definition(
cls,
field_definition: FieldDefinition,
model_name: str,
default_factory: Callable[[], Any] | None,
dto_field: DTOField,
passthrough_constraints: bool = True,
) -> DTOFieldDefinition:
"""Create a :class:`FieldDefinition` from a :class:`FieldDefinition`.
Args:
field_definition: A :class:`FieldDefinition` to create a :class:`FieldDefinition` from.
model_name: The name of the model.
default_factory: Default factory function, if any.
dto_field: DTOField instance.
passthrough_constraints: Pass constraints of the source annotation to be validated by the DTO backend
Returns:
A :class:`FieldDefinition` instance.
"""
return DTOFieldDefinition(
annotation=field_definition.annotation,
args=field_definition.args,
default=field_definition.default,
default_factory=default_factory,
dto_field=dto_field,
extra=field_definition.extra,
inner_types=field_definition.inner_types,
instantiable_origin=field_definition.instantiable_origin,
kwarg_definition=field_definition.kwarg_definition,
metadata=field_definition.metadata,
model_name=model_name,
name=field_definition.name,
origin=field_definition.origin,
raw=field_definition.raw,
safe_generic_origin=field_definition.safe_generic_origin,
type_wrappers=field_definition.type_wrappers,
passthrough_constraints=passthrough_constraints,
)