Source code for imgproxykit.variants

from dataclasses import Field, InitVar, dataclass, fields
from typing import (
    TYPE_CHECKING,
    Annotated,
    Any,
    get_args,
    get_origin,
    get_type_hints,
)

from imgproxykit.transform import Transform

if TYPE_CHECKING:
    from _typeshed import DataclassInstance


[docs] @dataclass(frozen=True, slots=True, kw_only=True) class DataclassVariantsResolver: """ Validate a dataclass variant declaration and resolve its transformations. Each constructor field must be annotated with ``Annotated[str, Transform(...)]`` and contain exactly one :class:`~imgproxykit.Transform`. Other annotation metadata is ignored. ``InitVar`` and fields declared with ``init=False`` are unsupported. This component only resolves the declaration. URL generation and result construction are handled by :meth:`imgproxykit.Client.variants`. """ #: Dataclass class whose fields declare the named image variants. model: type['DataclassInstance']
[docs] def __call__(self) -> dict[str, Transform]: """ Resolve transformations in dataclass field order. Returns: A new mapping from result field names to their transformations. Raises: TypeError: If the declaration contains unsupported fields, invalid field annotations, or ambiguous transformations. NameError: If a forward reference in the annotations cannot be resolved in the model's defining namespace. """ hints = self._resolve_annotations() return { field.name: self._resolve_field(field, hints[field.name]) for field in fields(self.model) }
def _resolve_annotations(self) -> dict[str, Any]: """Resolve annotation metadata and reject init-only fields.""" hints = get_type_hints(self.model, include_extras=True) model = self.model for field_name, annotation in hints.items(): if annotation is InitVar or isinstance(annotation, InitVar): raise TypeError( f'Field {field_name!r} of {model!r} uses `InitVar`. ' 'Variant declarations do not support init-only fields', ) return hints def _resolve_field(self, field: Field[Any], annotation: Any) -> Transform: """Validate a result field and extract its transformation metadata.""" model = self.model field_name = field.name if not field.init: raise TypeError( f'Field {field_name!r} of {model!r} has `init=False`. ' 'Variant fields must accept a constructor argument', ) arguments = get_args(annotation) if get_origin(annotation) is not Annotated or arguments[0] is not str: raise TypeError( f'Field {field_name!r} of {model!r} must be annotated with ' f'`Annotated[str, Transform(...)]`, got {annotation!r}', ) transforms = [ metadata for metadata in arguments[1:] if isinstance(metadata, Transform) ] transform_count = len(transforms) if transform_count != 1: raise TypeError( f'Field {field_name!r} of {model!r} must declare exactly one ' f'`Transform` in its metadata, got {transform_count}', ) return transforms[0]