from collections.abc import Mapping
from typing import ClassVar, overload
from imgproxykit.serialization import PathSerializer
from imgproxykit.signing import UrlSigner
from imgproxykit.transform import Transform
[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` or
:attr:`url_signer_cls` to customize path construction or signing.
Each client creates its own instances of these components.
"""
__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
@overload
def __init__(
self,
base_url: str,
/,
*,
key: None = None,
salt: None = None,
unsigned_signature: str = 'unsafe',
arguments_separator: str = ':',
) -> None: ...
@overload
def __init__(
self,
base_url: str,
/,
*,
key: str,
salt: str,
signature_size: int = 32,
arguments_separator: str = ':',
) -> None: ...
def __init__( # noqa: WPS211
self,
base_url: str,
/,
*,
key: str | None = None,
salt: str | None = None,
signature_size: int = 32,
unsigned_signature: str = 'unsafe',
arguments_separator: str = ':',
) -> 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.
salt: Nonempty hex-encoded signing salt matching the server.
Supply together with ``key``; ``None`` selects unsigned mode.
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_signature: 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>`__.
arguments_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 ``':'``.
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(
arguments_separator=arguments_separator,
)
self._url_signer = self.url_signer_cls(
key=key,
salt=salt,
signature_size=signature_size,
unsigned_signature=unsigned_signature,
)
[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 encodes its UTF-8 representation as
URL-safe Base64 without padding. 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}'
[docs]
def variants[Key: str](
self,
source: str,
*,
variants: Mapping[Key, Transform],
) -> dict[Key, str]:
"""
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.
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.
No network request is made.
"""
return {
name: self.url(source, transform=transform)
for name, transform in variants.items()
}