Source code for imgproxykit.client

from collections.abc import Mapping
from typing import TYPE_CHECKING, ClassVar, overload

from imgproxykit.serialization import PathSerializer
from imgproxykit.signing import UrlSigner
from imgproxykit.transform import Transform
from imgproxykit.types import SourceEncoding
from imgproxykit.variants import DataclassVariantsResolver

if TYPE_CHECKING:
    from _typeshed import DataclassInstance

type _VariantsResult[Key: str] = (
    dict[Key, str] | dict[str, str] | DataclassInstance
)


[docs] class Client: """ Build imgproxy URLs from Python transforms or server presets. Stores the imgproxy address and signing configuration for reuse across images. Each call to :meth:`url` returns a URL string; imgproxy loads the source and processes the image when that URL is requested. Pass processing options through :class:`~imgproxykit.Transform` as option instances or scalar keyword arguments. Preset options reference names configured on the imgproxy server. Subclass this client and override :attr:`path_serializer_cls`, :attr:`url_signer_cls`, or :attr:`variants_resolver_cls` to customize path construction, signing, or dataclass declaration resolution. Each client creates its own serializer and signer. A variants resolver is created for each dataclass declaration passed to :meth:`variants`. """ __slots__ = ( '_base_url', '_path_serializer', '_url_signer', ) #: Component that resolves processing parameters and builds unsigned paths. path_serializer_cls: ClassVar[type[PathSerializer]] = PathSerializer #: Component that signs paths or returns the unsigned URL marker. url_signer_cls: ClassVar[type[UrlSigner]] = UrlSigner #: Component that validates dataclass variant declarations. variants_resolver_cls: ClassVar[type[DataclassVariantsResolver]] = ( DataclassVariantsResolver ) @overload def __init__( self, base_url: str, /, *, key: None = None, salt: None = None, source_encoding: SourceEncoding = 'base64', unsigned_marker: str = 'unsafe', argument_separator: str = ':', use_short_options: bool = False, ) -> None: ... @overload def __init__( self, base_url: str, /, *, key: str, salt: str, source_encoding: SourceEncoding = 'base64', signature_size: int = 32, argument_separator: str = ':', use_short_options: bool = False, ) -> None: ... def __init__( # noqa: WPS211 self, base_url: str, /, *, key: str | None = None, salt: str | None = None, source_encoding: SourceEncoding = 'base64', signature_size: int = 32, unsigned_marker: str = 'unsafe', argument_separator: str = ':', use_short_options: bool = False, ) -> None: """ Configure the imgproxy address and create the client components. Supply both ``key`` and ``salt`` for signed URLs. Omit both for URLs with the configured unsigned marker (``unsafe`` by default). The server must accept unsigned requests or trust that marker. Args: base_url: Public imgproxy address, including any deployment path prefix. Trailing slashes are removed; the address is otherwise stored as supplied, without validation. key: Nonempty hex-encoded signing key matching the server. Supply together with ``salt``; ``None`` selects unsigned mode. See `IMGPROXY_KEY <https://docs.imgproxy.net/configuration/options#url-signature>`__. salt: Nonempty hex-encoded signing salt matching the server. Supply together with ``key``; ``None`` selects unsigned mode. See `IMGPROXY_SALT <https://docs.imgproxy.net/configuration/options#url-signature>`__. signature_size: Positive digest byte count to encode. Must match the server's `IMGPROXY_SIGNATURE_SIZE <https://docs.imgproxy.net/latest/configuration/options#url-signature>`__. Defaults to ``32``. Values of 32 or more use the full digest. unsigned_marker: URL marker used when key and salt are omitted. Defaults to ``'unsafe'``. With server signing enabled, configure this marker in `IMGPROXY_TRUSTED_SIGNATURES <https://docs.imgproxy.net/latest/configuration/options#url-signature>`__. argument_separator: Separator between processing option names and their arguments. Must match the server's `IMGPROXY_ARGUMENTS_SEPARATOR <https://docs.imgproxy.net/latest/configuration/options#miscellaneous>`__. Defaults to ``':'``. source_encoding: Source URL representation: ``'base64'`` or ``'plain'`` with full percent-encoding. See `imgproxy source URLs <https://docs.imgproxy.net/usage/processing#source-url>`__. Defaults to ``'base64'``. use_short_options: Use short processing option aliases when available, falling back to full names. Defaults to ``False``. Raises: ValueError: With the default signer, if only one signing credential is supplied, either is empty or invalid hex, or ``signature_size`` is not positive, even in unsigned mode. Examples: Create an unsigned client: >>> from imgproxykit import Client >>> client = Client('https://imgproxy.example.com') Create a signed client with example credentials: >>> client = Client( ... 'https://imgproxy.example.com', ... key='736563726574', ... salt='73616c74', ... ) """ # Prepare `base_url` only once pre instance self._base_url = base_url.rstrip('/') self._path_serializer = self.path_serializer_cls( argument_separator=argument_separator, use_short_options=use_short_options, source_encoding=source_encoding, ) self._url_signer = self.url_signer_cls( key=key, salt=salt, signature_size=signature_size, unsigned_marker=unsigned_marker, )
[docs] def url( self, source: str, *, transform: Transform | None = None, ) -> str: """ Build a URL using processing options and server preset references. Pass option instances or scalar keyword arguments through :class:`~imgproxykit.Transform`. Use :class:`~imgproxykit.options.Preset` to reference server presets. Preset definitions remain on the server and are not expanded here. Set the output format with ``Transform(format='webp')`` or a :class:`~imgproxykit.options.Format` option. Omitted processing parameters are left to the server. Args: source: Complete source URI understood by the imgproxy server. The default serializer uses URL-safe Base64 without padding or percent-encoding according to ``source_encoding``. The URI is neither validated nor resolved against ``base_url``. transform: Reusable processing options and optional output format. Omit or pass ``None`` to add no processing options. Returns: The complete imgproxy URL, signed when credentials were supplied or containing the unsigned marker otherwise. No network request is made. """ path = self._path_serializer(source, transform=transform) signature = self._url_signer(path) return f'{self._base_url}/{signature}{path}'
@overload def variants[Key: str]( self, source: str, *, variants: Mapping[Key, Transform], ) -> dict[Key, str]: ... @overload def variants[VariantSet: DataclassInstance]( self, source: str, *, variants: type[VariantSet], ) -> VariantSet: ...
[docs] def variants[Key: str]( self, source: str, *, variants: Mapping[Key, Transform] | type['DataclassInstance'], ) -> _VariantsResult[Key]: """ Build named URLs for one source using complete transformations. Args: source: Complete source URI understood by the imgproxy server. variants: Names mapped to the transformation for each variant. Alternatively, a dataclass class with fields declared as ``Annotated[str, Transform(...)]``. Each transformation is passed unchanged to :meth:`url`. Returns: A new dictionary preserving the input keys and iteration order. The key type is preserved, including literal string unions. An empty mapping produces an empty dictionary. A dataclass class produces a new instance of that exact class, with a generated URL in every field. No network request is made. """ transforms = ( variants if isinstance(variants, Mapping) else self.variants_resolver_cls(model=variants)() ) urls = { name: self.url(source, transform=transform) # ty: ignore[invalid-argument-type] for name, transform in transforms.items() } return urls if isinstance(variants, Mapping) else variants(**urls) # ty: ignore[invalid-return-type, invalid-argument-type]