imgproxykit 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- imgproxykit/__init__.py +2 -0
- imgproxykit/builders.py +132 -0
- imgproxykit/client.py +167 -0
- imgproxykit/internal/__init__.py +0 -0
- imgproxykit/internal/constants.py +8 -0
- imgproxykit/internal/empty.py +8 -0
- imgproxykit/internal/encoding.py +13 -0
- imgproxykit/internal/paths.py +21 -0
- imgproxykit/internal/serialization.py +34 -0
- imgproxykit/options/__init__.py +62 -0
- imgproxykit/options/auto_rotate.py +39 -0
- imgproxykit/options/background.py +46 -0
- imgproxykit/options/base.py +41 -0
- imgproxykit/options/blur.py +38 -0
- imgproxykit/options/cache_buster.py +40 -0
- imgproxykit/options/crop.py +61 -0
- imgproxykit/options/dpr.py +40 -0
- imgproxykit/options/enforce_thumbnail.py +39 -0
- imgproxykit/options/enlarge.py +38 -0
- imgproxykit/options/expires.py +38 -0
- imgproxykit/options/extend.py +50 -0
- imgproxykit/options/extend_aspect_ratio.py +50 -0
- imgproxykit/options/filename.py +41 -0
- imgproxykit/options/flip.py +44 -0
- imgproxykit/options/format_quality.py +38 -0
- imgproxykit/options/gravity.py +48 -0
- imgproxykit/options/height.py +39 -0
- imgproxykit/options/keep_copyright.py +38 -0
- imgproxykit/options/max_animation_frame_resolution.py +39 -0
- imgproxykit/options/max_animation_frames.py +39 -0
- imgproxykit/options/max_bytes.py +39 -0
- imgproxykit/options/max_result_dimension.py +39 -0
- imgproxykit/options/max_src_file_size.py +39 -0
- imgproxykit/options/max_src_resolution.py +39 -0
- imgproxykit/options/min_height.py +38 -0
- imgproxykit/options/min_width.py +38 -0
- imgproxykit/options/padding.py +50 -0
- imgproxykit/options/pixelate.py +38 -0
- imgproxykit/options/preserve_hdr.py +38 -0
- imgproxykit/options/quality.py +38 -0
- imgproxykit/options/raw.py +39 -0
- imgproxykit/options/resize.py +55 -0
- imgproxykit/options/resizing_type.py +39 -0
- imgproxykit/options/return_attachment.py +39 -0
- imgproxykit/options/rotate.py +38 -0
- imgproxykit/options/sharpen.py +38 -0
- imgproxykit/options/size.py +45 -0
- imgproxykit/options/skip_processing.py +39 -0
- imgproxykit/options/strip_color_profile.py +39 -0
- imgproxykit/options/strip_metadata.py +38 -0
- imgproxykit/options/trim.py +52 -0
- imgproxykit/options/watermark.py +60 -0
- imgproxykit/options/width.py +39 -0
- imgproxykit/options/zoom.py +45 -0
- imgproxykit/py.typed +0 -0
- imgproxykit/signing.py +114 -0
- imgproxykit/transform.py +54 -0
- imgproxykit/types.py +95 -0
- imgproxykit-0.1.0.dist-info/METADATA +150 -0
- imgproxykit-0.1.0.dist-info/RECORD +62 -0
- imgproxykit-0.1.0.dist-info/WHEEL +4 -0
- imgproxykit-0.1.0.dist-info/licenses/LICENSE +21 -0
imgproxykit/__init__.py
ADDED
imgproxykit/builders.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
from imgproxykit.internal.constants import PRESET
|
|
2
|
+
from imgproxykit.internal.paths import build_path
|
|
3
|
+
from imgproxykit.internal.serialization import (
|
|
4
|
+
serialize_option,
|
|
5
|
+
serialize_segment,
|
|
6
|
+
)
|
|
7
|
+
from imgproxykit.transform import Transform
|
|
8
|
+
from imgproxykit.types import EMPTY, FormatType
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PathBuilder:
|
|
12
|
+
"""
|
|
13
|
+
Resolve processing options and build an unsigned imgproxy path.
|
|
14
|
+
|
|
15
|
+
Used by :class:`~imgproxykit.Client` before signing the resulting path.
|
|
16
|
+
Sources are Base64-encoded without padding; processing options and an
|
|
17
|
+
optional output extension are included in the same path.
|
|
18
|
+
|
|
19
|
+
Python transforms and server presets are mutually exclusive.
|
|
20
|
+
Preset definitions remain on the imgproxy server.
|
|
21
|
+
|
|
22
|
+
Subclass this builder and override :meth:`__call__` to customize path
|
|
23
|
+
construction. Set the subclass on
|
|
24
|
+
:attr:`imgproxykit.Client.path_builder_cls` to use it in a client.
|
|
25
|
+
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
__slots__ = ()
|
|
29
|
+
|
|
30
|
+
def __call__(
|
|
31
|
+
self,
|
|
32
|
+
source: str,
|
|
33
|
+
*,
|
|
34
|
+
transform: Transform | EMPTY = EMPTY,
|
|
35
|
+
presets: tuple[str, ...] | EMPTY = EMPTY,
|
|
36
|
+
output_format: FormatType | EMPTY = EMPTY,
|
|
37
|
+
) -> str:
|
|
38
|
+
"""
|
|
39
|
+
Build a path from a Python transform or server preset names.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
source: Complete source URI understood by imgproxy. Its contents
|
|
43
|
+
are encoded as supplied, without resolving relative paths.
|
|
44
|
+
transform: Processing options and output format defined in Python.
|
|
45
|
+
Omit together with ``presets`` to add no processing options.
|
|
46
|
+
presets: Server preset names in the order they should be supplied.
|
|
47
|
+
Cannot be combined with ``transform``.
|
|
48
|
+
output_format: Output extension when using ``presets``.
|
|
49
|
+
For a Python transform, set its ``format`` instead.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Unsigned path beginning with ``/``, ready for signing.
|
|
53
|
+
Contains neither the imgproxy address nor a signature segment.
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
ValueError: If ``transform`` and ``presets`` are both supplied,
|
|
57
|
+
or ``output_format`` is supplied without ``presets``.
|
|
58
|
+
|
|
59
|
+
Usage:
|
|
60
|
+
|
|
61
|
+
>>> from imgproxykit import Transform
|
|
62
|
+
>>> from imgproxykit.builders import PathBuilder
|
|
63
|
+
>>> from imgproxykit.options import Resize
|
|
64
|
+
>>> builder = PathBuilder()
|
|
65
|
+
>>> source = 'https://example.com/photo.jpg'
|
|
66
|
+
>>> thumbnail = Transform(
|
|
67
|
+
... options=(Resize(width=96, height=96, mode='fill'),),
|
|
68
|
+
... format='webp',
|
|
69
|
+
... )
|
|
70
|
+
>>> local_path = builder(source, transform=thumbnail)
|
|
71
|
+
>>> preset_path = builder(
|
|
72
|
+
... source,
|
|
73
|
+
... presets=('thumbnail',),
|
|
74
|
+
... output_format='webp',
|
|
75
|
+
... )
|
|
76
|
+
|
|
77
|
+
"""
|
|
78
|
+
options, selected_format = self._resolve_options(
|
|
79
|
+
transform,
|
|
80
|
+
presets,
|
|
81
|
+
output_format,
|
|
82
|
+
)
|
|
83
|
+
return build_path(
|
|
84
|
+
source,
|
|
85
|
+
options=options,
|
|
86
|
+
output_format=selected_format,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def _resolve_options(
|
|
90
|
+
self,
|
|
91
|
+
transform: Transform | EMPTY,
|
|
92
|
+
presets: tuple[str, ...] | EMPTY,
|
|
93
|
+
output_format: FormatType | EMPTY,
|
|
94
|
+
) -> tuple[tuple[str, ...], FormatType | EMPTY]:
|
|
95
|
+
"""
|
|
96
|
+
Resolve mutually exclusive inputs into path segments and a format.
|
|
97
|
+
|
|
98
|
+
Python options are serialized in their supplied order. Server preset
|
|
99
|
+
names form a single ``pr`` segment. Omitted processing parameters
|
|
100
|
+
produce no segments and leave the output format unspecified.
|
|
101
|
+
|
|
102
|
+
"""
|
|
103
|
+
self._validate_options(transform, presets, output_format)
|
|
104
|
+
if presets is not EMPTY:
|
|
105
|
+
return (serialize_segment(PRESET, presets),), output_format
|
|
106
|
+
|
|
107
|
+
if transform is EMPTY:
|
|
108
|
+
return (), EMPTY
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
tuple(serialize_option(option) for option in transform.options),
|
|
112
|
+
transform.format,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
def _validate_options(
|
|
116
|
+
self,
|
|
117
|
+
transform: Transform | EMPTY,
|
|
118
|
+
presets: tuple[str, ...] | EMPTY,
|
|
119
|
+
output_format: FormatType | EMPTY,
|
|
120
|
+
) -> None:
|
|
121
|
+
"""
|
|
122
|
+
Validate processing options before path serialization.
|
|
123
|
+
|
|
124
|
+
Python transforms cannot be combined with server presets.
|
|
125
|
+
A separate output format is only accepted with server presets.
|
|
126
|
+
|
|
127
|
+
"""
|
|
128
|
+
if transform is not EMPTY and presets is not EMPTY:
|
|
129
|
+
raise ValueError('Choose transform or presets.')
|
|
130
|
+
|
|
131
|
+
if presets is EMPTY and output_format is not EMPTY:
|
|
132
|
+
raise ValueError('Set format on the Python transform.')
|
imgproxykit/client.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
from typing import ClassVar, overload
|
|
2
|
+
|
|
3
|
+
from imgproxykit.builders import PathBuilder
|
|
4
|
+
from imgproxykit.signing import URLSigner
|
|
5
|
+
from imgproxykit.transform import Transform
|
|
6
|
+
from imgproxykit.types import EMPTY, FormatType
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Client:
|
|
10
|
+
"""
|
|
11
|
+
Build imgproxy URLs from Python transforms or server presets.
|
|
12
|
+
|
|
13
|
+
Stores the imgproxy address and signing configuration for reuse across
|
|
14
|
+
images. Each call to :meth:`url` returns a URL string; imgproxy loads
|
|
15
|
+
the source and processes the image when that URL is requested.
|
|
16
|
+
|
|
17
|
+
Processing parameters can come from a :class:`~imgproxykit.Transform`
|
|
18
|
+
defined in Python or from preset names configured on the imgproxy server.
|
|
19
|
+
|
|
20
|
+
Subclass this client and override :attr:`path_builder_cls` or
|
|
21
|
+
:attr:`signer_cls` to customize path construction or signing.
|
|
22
|
+
Each client creates its own instances of these components.
|
|
23
|
+
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
__slots__ = (
|
|
27
|
+
'_base_url',
|
|
28
|
+
'_path_builder',
|
|
29
|
+
'_signer',
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
#: Component that resolves processing parameters and builds unsigned paths.
|
|
33
|
+
path_builder_cls: ClassVar[type[PathBuilder]] = PathBuilder
|
|
34
|
+
#: Component that signs paths or returns the unsigned URL marker.
|
|
35
|
+
signer_cls: ClassVar[type[URLSigner]] = URLSigner
|
|
36
|
+
|
|
37
|
+
@overload
|
|
38
|
+
def __init__(self, base_url: str, /) -> None: ...
|
|
39
|
+
|
|
40
|
+
@overload
|
|
41
|
+
def __init__(self, base_url: str, /, *, key: str, salt: str) -> None: ...
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
base_url: str,
|
|
46
|
+
/,
|
|
47
|
+
*,
|
|
48
|
+
key: str | EMPTY = EMPTY,
|
|
49
|
+
salt: str | EMPTY = EMPTY,
|
|
50
|
+
) -> None:
|
|
51
|
+
"""
|
|
52
|
+
Configure the imgproxy address and create the client components.
|
|
53
|
+
|
|
54
|
+
Supply both ``key`` and ``salt`` for signed URLs. Omit both for
|
|
55
|
+
URLs with an ``unsafe`` signature marker, which can be used with
|
|
56
|
+
an imgproxy server that has signature checking disabled.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
base_url: Public imgproxy address, including any deployment path
|
|
60
|
+
prefix. Required positional argument. Trailing slashes
|
|
61
|
+
are removed.
|
|
62
|
+
key: Hex-encoded signing key matching the server configuration.
|
|
63
|
+
salt: Hex-encoded signing salt matching the server configuration.
|
|
64
|
+
|
|
65
|
+
Raises:
|
|
66
|
+
ValueError: With the default signer, if only one signing
|
|
67
|
+
credential is supplied, or either is empty or invalid hex.
|
|
68
|
+
|
|
69
|
+
Usage:
|
|
70
|
+
|
|
71
|
+
>>> from imgproxykit import Client
|
|
72
|
+
>>> unsigned = Client('https://images.example.com')
|
|
73
|
+
>>> signed = Client(
|
|
74
|
+
... 'https://images.example.com',
|
|
75
|
+
... key='736563726574',
|
|
76
|
+
... salt='73616c74',
|
|
77
|
+
... )
|
|
78
|
+
|
|
79
|
+
The signing credentials above are examples for local development.
|
|
80
|
+
|
|
81
|
+
"""
|
|
82
|
+
# TODO: Define runtime validation for base URLs and empty sources.
|
|
83
|
+
self._base_url = base_url.rstrip('/')
|
|
84
|
+
self._path_builder = self.path_builder_cls()
|
|
85
|
+
self._signer = self.signer_cls(key=key, salt=salt)
|
|
86
|
+
|
|
87
|
+
@overload
|
|
88
|
+
def url(
|
|
89
|
+
self,
|
|
90
|
+
source: str,
|
|
91
|
+
*,
|
|
92
|
+
transform: Transform | EMPTY = ...,
|
|
93
|
+
) -> str: ...
|
|
94
|
+
|
|
95
|
+
@overload
|
|
96
|
+
def url(
|
|
97
|
+
self,
|
|
98
|
+
source: str,
|
|
99
|
+
*,
|
|
100
|
+
presets: tuple[str, ...],
|
|
101
|
+
format: FormatType | EMPTY = EMPTY,
|
|
102
|
+
) -> str: ...
|
|
103
|
+
|
|
104
|
+
def url(
|
|
105
|
+
self,
|
|
106
|
+
source: str,
|
|
107
|
+
*,
|
|
108
|
+
transform: Transform | EMPTY = EMPTY,
|
|
109
|
+
presets: tuple[str, ...] | EMPTY = EMPTY,
|
|
110
|
+
format: FormatType | EMPTY = EMPTY, # noqa: A002
|
|
111
|
+
) -> str:
|
|
112
|
+
"""
|
|
113
|
+
Build a URL for one source using a transform or server presets.
|
|
114
|
+
|
|
115
|
+
Pass ``transform`` for processing parameters defined in Python.
|
|
116
|
+
Pass ``presets`` to reference named server presets instead.
|
|
117
|
+
Preset definitions remain on the server and are not expanded here.
|
|
118
|
+
|
|
119
|
+
With a Python transform, set the output format on that transform.
|
|
120
|
+
With server presets, use the ``format`` argument. Omitted processing
|
|
121
|
+
parameters and formats are left to the server.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
source: Complete source URI understood by the imgproxy server.
|
|
125
|
+
The default builder Base64-encodes it without changing its
|
|
126
|
+
contents or resolving storage-relative paths.
|
|
127
|
+
transform: Reusable processing options and optional output format.
|
|
128
|
+
Omitting both ``transform`` and ``presets`` adds no options.
|
|
129
|
+
presets: Names of server presets, in the supplied order.
|
|
130
|
+
Mutually exclusive with ``transform``.
|
|
131
|
+
format: Output format when using ``presets``. Must be omitted
|
|
132
|
+
when using a Python transform.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
The complete imgproxy URL, signed when credentials were supplied.
|
|
136
|
+
|
|
137
|
+
Raises:
|
|
138
|
+
ValueError: With the default builder, if ``transform`` and
|
|
139
|
+
``presets`` are supplied together, or ``format`` is supplied
|
|
140
|
+
without ``presets``.
|
|
141
|
+
|
|
142
|
+
Usage:
|
|
143
|
+
|
|
144
|
+
>>> from imgproxykit import Client, Transform
|
|
145
|
+
>>> from imgproxykit.options import Resize
|
|
146
|
+
>>> client = Client('https://images.example.com')
|
|
147
|
+
>>> thumbnail = Transform(
|
|
148
|
+
... options=(Resize(width=300, height=300, mode='fill'),),
|
|
149
|
+
... format='webp',
|
|
150
|
+
... )
|
|
151
|
+
>>> source = 'https://example.com/photo.jpg'
|
|
152
|
+
>>> local_options_url = client.url(source, transform=thumbnail)
|
|
153
|
+
>>> server_presets_url = client.url(
|
|
154
|
+
... source,
|
|
155
|
+
... presets=('thumbnail', 'watermarked'),
|
|
156
|
+
... format='webp',
|
|
157
|
+
... )
|
|
158
|
+
|
|
159
|
+
"""
|
|
160
|
+
path = self._path_builder(
|
|
161
|
+
source,
|
|
162
|
+
transform=transform,
|
|
163
|
+
presets=presets,
|
|
164
|
+
output_format=format,
|
|
165
|
+
)
|
|
166
|
+
signature = self._signer(path)
|
|
167
|
+
return f'{self._base_url}/{signature}{path}'
|
|
File without changes
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from typing import Final
|
|
2
|
+
|
|
3
|
+
# imgproxy verifies URL signatures using HMAC-SHA256.
|
|
4
|
+
SIGNATURE_DIGEST: Final = 'sha256'
|
|
5
|
+
# A URL still needs a signature segment when server signature checking is off.
|
|
6
|
+
UNSAFE: Final = 'unsafe'
|
|
7
|
+
# imgproxy's short option name for referencing server-side presets.
|
|
8
|
+
PRESET: Final = 'pr'
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from typing_extensions import Sentinel
|
|
2
|
+
|
|
3
|
+
# NOTE: this module must not import anything from `imgproxykit`,
|
|
4
|
+
# so it can be used anywhere without creating import cycles.
|
|
5
|
+
|
|
6
|
+
#: Default singleton for empty values.
|
|
7
|
+
#: Re-exported as ``imgproxykit.types.EMPTY``.
|
|
8
|
+
EMPTY = Sentinel('EMPTY')
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def encode_base64(value: str | bytes) -> str:
|
|
5
|
+
"""
|
|
6
|
+
Encode text or bytes as URL-safe Base64 without padding.
|
|
7
|
+
|
|
8
|
+
Text is first encoded as UTF-8. Byte values, such as signature digests,
|
|
9
|
+
are encoded directly. Trailing ``=`` padding is removed for URL paths.
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
raw = value.encode() if isinstance(value, str) else value
|
|
13
|
+
return base64.urlsafe_b64encode(raw).rstrip(b'=').decode()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from imgproxykit.internal.encoding import encode_base64
|
|
2
|
+
from imgproxykit.types import EMPTY, FormatType
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def build_path(
|
|
6
|
+
source: str,
|
|
7
|
+
*,
|
|
8
|
+
options: tuple[str, ...],
|
|
9
|
+
output_format: FormatType | EMPTY = EMPTY,
|
|
10
|
+
) -> str:
|
|
11
|
+
"""
|
|
12
|
+
Assemble serialized options, an encoded source, and an output extension.
|
|
13
|
+
|
|
14
|
+
The leading ``/`` is part of the path supplied to the signer.
|
|
15
|
+
The source is encoded as URL-safe Base64 without padding. An omitted
|
|
16
|
+
output format adds no extension, leaving format selection to imgproxy.
|
|
17
|
+
|
|
18
|
+
"""
|
|
19
|
+
segments = '/'.join((*options, encode_base64(source)))
|
|
20
|
+
path = f'/{segments}'
|
|
21
|
+
return path if output_format is EMPTY else f'{path}.{output_format}'
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from imgproxykit.options import Option
|
|
2
|
+
from imgproxykit.types import EMPTY, Argument
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def serialize_option(option: Option) -> str:
|
|
6
|
+
"""Serialize a typed processing option into an imgproxy path segment."""
|
|
7
|
+
return serialize_segment(option.name, option.arguments)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def serialize_segment(name: str, arguments: tuple[Argument, ...]) -> str:
|
|
11
|
+
"""
|
|
12
|
+
Serialize an option name and positional arguments into a path segment.
|
|
13
|
+
|
|
14
|
+
Internal :data:`~imgproxykit.types.EMPTY` values become empty positions;
|
|
15
|
+
trailing empty values are omitted. Explicit zeroes and empty strings
|
|
16
|
+
retain their positions. Booleans are serialized as ``1`` and ``0``.
|
|
17
|
+
|
|
18
|
+
"""
|
|
19
|
+
# TODO: Verify reserved characters in string arguments and preset names
|
|
20
|
+
# against imgproxy before choosing encoding or rejection. Cover colons,
|
|
21
|
+
# slashes, percent signs, Unicode, and empty strings.
|
|
22
|
+
end = len(arguments)
|
|
23
|
+
while end and arguments[end - 1] is EMPTY:
|
|
24
|
+
end -= 1
|
|
25
|
+
return ':'.join((name, *map(_serialize_argument, arguments[:end])))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _serialize_argument(value: Argument) -> str:
|
|
29
|
+
"""Serialize one argument without treating false values as omitted."""
|
|
30
|
+
if value is EMPTY:
|
|
31
|
+
return ''
|
|
32
|
+
if isinstance(value, bool):
|
|
33
|
+
return str(int(value))
|
|
34
|
+
return str(value)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from imgproxykit.options.auto_rotate import AutoRotate as AutoRotate
|
|
2
|
+
from imgproxykit.options.background import Background as Background
|
|
3
|
+
from imgproxykit.options.base import Option as Option
|
|
4
|
+
from imgproxykit.options.blur import Blur as Blur
|
|
5
|
+
from imgproxykit.options.cache_buster import CacheBuster as CacheBuster
|
|
6
|
+
from imgproxykit.options.crop import Crop as Crop
|
|
7
|
+
from imgproxykit.options.dpr import DPR as DPR
|
|
8
|
+
from imgproxykit.options.enforce_thumbnail import (
|
|
9
|
+
EnforceThumbnail as EnforceThumbnail,
|
|
10
|
+
)
|
|
11
|
+
from imgproxykit.options.enlarge import Enlarge as Enlarge
|
|
12
|
+
from imgproxykit.options.expires import Expires as Expires
|
|
13
|
+
from imgproxykit.options.extend import Extend as Extend
|
|
14
|
+
from imgproxykit.options.extend_aspect_ratio import (
|
|
15
|
+
ExtendAspectRatio as ExtendAspectRatio,
|
|
16
|
+
)
|
|
17
|
+
from imgproxykit.options.filename import Filename as Filename
|
|
18
|
+
from imgproxykit.options.flip import Flip as Flip
|
|
19
|
+
from imgproxykit.options.format_quality import FormatQuality as FormatQuality
|
|
20
|
+
from imgproxykit.options.gravity import Gravity as Gravity
|
|
21
|
+
from imgproxykit.options.height import Height as Height
|
|
22
|
+
from imgproxykit.options.keep_copyright import KeepCopyright as KeepCopyright
|
|
23
|
+
from imgproxykit.options.max_animation_frame_resolution import (
|
|
24
|
+
MaxAnimationFrameResolution as MaxAnimationFrameResolution,
|
|
25
|
+
)
|
|
26
|
+
from imgproxykit.options.max_animation_frames import (
|
|
27
|
+
MaxAnimationFrames as MaxAnimationFrames,
|
|
28
|
+
)
|
|
29
|
+
from imgproxykit.options.max_bytes import MaxBytes as MaxBytes
|
|
30
|
+
from imgproxykit.options.max_result_dimension import (
|
|
31
|
+
MaxResultDimension as MaxResultDimension,
|
|
32
|
+
)
|
|
33
|
+
from imgproxykit.options.max_src_file_size import (
|
|
34
|
+
MaxSrcFileSize as MaxSrcFileSize,
|
|
35
|
+
)
|
|
36
|
+
from imgproxykit.options.max_src_resolution import (
|
|
37
|
+
MaxSrcResolution as MaxSrcResolution,
|
|
38
|
+
)
|
|
39
|
+
from imgproxykit.options.min_height import MinHeight as MinHeight
|
|
40
|
+
from imgproxykit.options.min_width import MinWidth as MinWidth
|
|
41
|
+
from imgproxykit.options.padding import Padding as Padding
|
|
42
|
+
from imgproxykit.options.pixelate import Pixelate as Pixelate
|
|
43
|
+
from imgproxykit.options.preserve_hdr import PreserveHDR as PreserveHDR
|
|
44
|
+
from imgproxykit.options.quality import Quality as Quality
|
|
45
|
+
from imgproxykit.options.raw import Raw as Raw
|
|
46
|
+
from imgproxykit.options.resize import Resize as Resize
|
|
47
|
+
from imgproxykit.options.resizing_type import ResizingType as ResizingType
|
|
48
|
+
from imgproxykit.options.return_attachment import (
|
|
49
|
+
ReturnAttachment as ReturnAttachment,
|
|
50
|
+
)
|
|
51
|
+
from imgproxykit.options.rotate import Rotate as Rotate
|
|
52
|
+
from imgproxykit.options.sharpen import Sharpen as Sharpen
|
|
53
|
+
from imgproxykit.options.size import Size as Size
|
|
54
|
+
from imgproxykit.options.skip_processing import SkipProcessing as SkipProcessing
|
|
55
|
+
from imgproxykit.options.strip_color_profile import (
|
|
56
|
+
StripColorProfile as StripColorProfile,
|
|
57
|
+
)
|
|
58
|
+
from imgproxykit.options.strip_metadata import StripMetadata as StripMetadata
|
|
59
|
+
from imgproxykit.options.trim import Trim as Trim
|
|
60
|
+
from imgproxykit.options.watermark import Watermark as Watermark
|
|
61
|
+
from imgproxykit.options.width import Width as Width
|
|
62
|
+
from imgproxykit.options.zoom import Zoom as Zoom
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import ClassVar, final, override
|
|
3
|
+
|
|
4
|
+
from imgproxykit.options.base import Option
|
|
5
|
+
from imgproxykit.types import Argument, OptionType
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@final
|
|
9
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
10
|
+
class AutoRotate(Option):
|
|
11
|
+
"""
|
|
12
|
+
Apply the source EXIF orientation before processing.
|
|
13
|
+
|
|
14
|
+
Overrides automatic EXIF orientation handling for this request.
|
|
15
|
+
The orientation tag is removed regardless of this setting.
|
|
16
|
+
|
|
17
|
+
Attributes:
|
|
18
|
+
value: Whether to apply the source EXIF orientation.
|
|
19
|
+
|
|
20
|
+
Usage:
|
|
21
|
+
|
|
22
|
+
>>> option = AutoRotate(value=False)
|
|
23
|
+
>>> option.arguments
|
|
24
|
+
(False,)
|
|
25
|
+
|
|
26
|
+
See `imgproxy auto rotate
|
|
27
|
+
<https://docs.imgproxy.net/usage/processing#auto-rotate>`_.
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
value: bool
|
|
32
|
+
|
|
33
|
+
_name: ClassVar[OptionType] = 'ar'
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
@override
|
|
37
|
+
def arguments(self) -> tuple[Argument, ...]:
|
|
38
|
+
"""Positional arguments in imgproxy protocol order."""
|
|
39
|
+
return (self.value,)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import ClassVar, final, override
|
|
3
|
+
|
|
4
|
+
from imgproxykit.options.base import Option
|
|
5
|
+
from imgproxykit.types import EMPTY, Argument, OptionType
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@final
|
|
9
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
10
|
+
class Background(Option):
|
|
11
|
+
"""
|
|
12
|
+
Fill the background with a hex color or RGB tuple; omit to disable.
|
|
13
|
+
|
|
14
|
+
Useful when converting transparent images to JPEG. Omit the color
|
|
15
|
+
to disable background manipulation.
|
|
16
|
+
|
|
17
|
+
Attributes:
|
|
18
|
+
color: Hex color without a leading hash, or an RGB tuple with
|
|
19
|
+
channel values from 0 to 255. Omission disables filling.
|
|
20
|
+
|
|
21
|
+
Usage:
|
|
22
|
+
|
|
23
|
+
>>> option = Background(color='ffffff')
|
|
24
|
+
>>> option.arguments
|
|
25
|
+
('ffffff',)
|
|
26
|
+
>>> option = Background(color=(255, 255, 255))
|
|
27
|
+
>>> option.arguments
|
|
28
|
+
(255, 255, 255)
|
|
29
|
+
>>> option = Background()
|
|
30
|
+
>>> option.arguments
|
|
31
|
+
(EMPTY,)
|
|
32
|
+
|
|
33
|
+
See `imgproxy background
|
|
34
|
+
<https://docs.imgproxy.net/usage/processing#background>`_.
|
|
35
|
+
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
color: str | tuple[int, int, int] | EMPTY = EMPTY
|
|
39
|
+
|
|
40
|
+
_name: ClassVar[OptionType] = 'bg'
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
@override
|
|
44
|
+
def arguments(self) -> tuple[Argument, ...]:
|
|
45
|
+
"""Positional arguments in imgproxy protocol order."""
|
|
46
|
+
return self.color if isinstance(self.color, tuple) else (self.color,)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import ClassVar
|
|
3
|
+
|
|
4
|
+
from imgproxykit.types import Argument, OptionType
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Option(ABC):
|
|
8
|
+
"""
|
|
9
|
+
Base class for typed imgproxy processing options.
|
|
10
|
+
|
|
11
|
+
Concrete options provide a fixed name and positional arguments.
|
|
12
|
+
Pass their instances to :class:`~imgproxykit.Transform`.
|
|
13
|
+
This abstract class cannot be instantiated directly.
|
|
14
|
+
|
|
15
|
+
Arguments retain :data:`~imgproxykit.types.EMPTY` values until
|
|
16
|
+
serialization: internal gaps keep their positions, while trailing
|
|
17
|
+
empty arguments are omitted.
|
|
18
|
+
|
|
19
|
+
See `imgproxy processing options
|
|
20
|
+
<https://docs.imgproxy.net/usage/processing#processing-options>`_.
|
|
21
|
+
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
__slots__ = ()
|
|
25
|
+
|
|
26
|
+
# TODO: Define runtime validation boundaries for processing option values.
|
|
27
|
+
# TODO: Allow custom option names without type ignores; decide whether
|
|
28
|
+
# OptionType should remain limited to built-in processing options.
|
|
29
|
+
|
|
30
|
+
# Private API:
|
|
31
|
+
_name: ClassVar[OptionType]
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def name(self) -> OptionType:
|
|
35
|
+
"""The fixed imgproxy processing option name."""
|
|
36
|
+
return self._name
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
@abstractmethod
|
|
40
|
+
def arguments(self) -> tuple[Argument, ...]:
|
|
41
|
+
"""Positional arguments in imgproxy protocol order."""
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import ClassVar, final, override
|
|
3
|
+
|
|
4
|
+
from imgproxykit.options.base import Option
|
|
5
|
+
from imgproxykit.types import Argument, OptionType
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@final
|
|
9
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
10
|
+
class Blur(Option):
|
|
11
|
+
"""
|
|
12
|
+
Apply Gaussian blur with the given sigma.
|
|
13
|
+
|
|
14
|
+
The sigma controls the size of the Gaussian blur mask.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
value: Gaussian blur sigma.
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
|
|
21
|
+
>>> option = Blur(value=2.0)
|
|
22
|
+
>>> option.arguments
|
|
23
|
+
(2.0,)
|
|
24
|
+
|
|
25
|
+
See `imgproxy blur
|
|
26
|
+
<https://docs.imgproxy.net/usage/processing#blur>`_.
|
|
27
|
+
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
value: float
|
|
31
|
+
|
|
32
|
+
_name: ClassVar[OptionType] = 'bl'
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
@override
|
|
36
|
+
def arguments(self) -> tuple[Argument, ...]:
|
|
37
|
+
"""Positional arguments in imgproxy protocol order."""
|
|
38
|
+
return (self.value,)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import ClassVar, final, override
|
|
3
|
+
from urllib.parse import quote
|
|
4
|
+
|
|
5
|
+
from imgproxykit.options.base import Option
|
|
6
|
+
from imgproxykit.types import Argument, OptionType
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@final
|
|
10
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
11
|
+
class CacheBuster(Option):
|
|
12
|
+
"""
|
|
13
|
+
Add an escaped version string to the signed URL to bypass caches.
|
|
14
|
+
|
|
15
|
+
Changing the value produces a new URL for browser and CDN caches
|
|
16
|
+
without changing image processing. Special characters are escaped.
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
value: Cache version string included in the signed path.
|
|
20
|
+
|
|
21
|
+
Usage:
|
|
22
|
+
|
|
23
|
+
>>> option = CacheBuster(value='v2')
|
|
24
|
+
>>> option.arguments
|
|
25
|
+
('v2',)
|
|
26
|
+
|
|
27
|
+
See `imgproxy cache buster
|
|
28
|
+
<https://docs.imgproxy.net/usage/processing#cache-buster>`_.
|
|
29
|
+
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
value: str
|
|
33
|
+
|
|
34
|
+
_name: ClassVar[OptionType] = 'cb'
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
@override
|
|
38
|
+
def arguments(self) -> tuple[Argument, ...]:
|
|
39
|
+
"""Positional arguments in imgproxy protocol order."""
|
|
40
|
+
return (quote(self.value, safe=''),)
|