imgproxykit 0.1.0__tar.gz → 0.2.1__tar.gz
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-0.2.1/PKG-INFO +124 -0
- imgproxykit-0.2.1/README.md +95 -0
- imgproxykit-0.2.1/imgproxykit/client.py +153 -0
- {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/internal/encoding.py +0 -1
- {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/__init__.py +3 -0
- imgproxykit-0.2.1/imgproxykit/options/auto_rotate.py +30 -0
- {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/background.py +5 -6
- imgproxykit-0.2.1/imgproxykit/options/base.py +129 -0
- imgproxykit-0.2.1/imgproxykit/options/blur.py +29 -0
- {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/cache_buster.py +6 -9
- {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/crop.py +10 -11
- {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/dpr.py +6 -14
- imgproxykit-0.2.1/imgproxykit/options/enforce_thumbnail.py +30 -0
- imgproxykit-0.2.1/imgproxykit/options/enlarge.py +29 -0
- imgproxykit-0.2.1/imgproxykit/options/expires.py +29 -0
- {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/extend.py +8 -9
- {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/extend_aspect_ratio.py +8 -9
- {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/filename.py +6 -9
- {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/flip.py +7 -8
- imgproxykit-0.2.1/imgproxykit/options/format.py +30 -0
- {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/format_quality.py +2 -3
- {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/gravity.py +13 -9
- imgproxykit-0.2.1/imgproxykit/options/height.py +30 -0
- imgproxykit-0.2.1/imgproxykit/options/keep_copyright.py +29 -0
- {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/max_animation_frame_resolution.py +7 -16
- imgproxykit-0.2.1/imgproxykit/options/max_animation_frames.py +30 -0
- imgproxykit-0.2.1/imgproxykit/options/max_bytes.py +30 -0
- {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/max_result_dimension.py +7 -16
- imgproxykit-0.2.1/imgproxykit/options/max_src_file_size.py +30 -0
- imgproxykit-0.2.1/imgproxykit/options/max_src_resolution.py +30 -0
- imgproxykit-0.2.1/imgproxykit/options/min_height.py +29 -0
- imgproxykit-0.2.1/imgproxykit/options/min_width.py +29 -0
- {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/padding.py +9 -10
- imgproxykit-0.2.1/imgproxykit/options/pixelate.py +29 -0
- imgproxykit-0.2.1/imgproxykit/options/preserve_hdr.py +29 -0
- imgproxykit-0.2.1/imgproxykit/options/preset.py +39 -0
- imgproxykit-0.2.1/imgproxykit/options/quality.py +30 -0
- imgproxykit-0.2.1/imgproxykit/options/raw.py +30 -0
- {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/resize.py +12 -13
- imgproxykit-0.2.1/imgproxykit/options/resizing_type.py +30 -0
- imgproxykit-0.2.1/imgproxykit/options/return_attachment.py +30 -0
- imgproxykit-0.2.1/imgproxykit/options/rotate.py +29 -0
- imgproxykit-0.2.1/imgproxykit/options/sharpen.py +29 -0
- {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/size.py +9 -10
- {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/skip_processing.py +2 -3
- imgproxykit-0.2.1/imgproxykit/options/strip_color_profile.py +30 -0
- imgproxykit-0.2.1/imgproxykit/options/strip_metadata.py +29 -0
- {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/trim.py +8 -9
- {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/watermark.py +10 -11
- imgproxykit-0.2.1/imgproxykit/options/width.py +30 -0
- {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/zoom.py +5 -6
- imgproxykit-0.2.1/imgproxykit/serialization.py +106 -0
- {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/signing.py +46 -36
- imgproxykit-0.2.1/imgproxykit/transform.py +107 -0
- imgproxykit-0.2.1/imgproxykit/types.py +95 -0
- {imgproxykit-0.1.0 → imgproxykit-0.2.1}/pyproject.toml +31 -7
- imgproxykit-0.1.0/PKG-INFO +0 -150
- imgproxykit-0.1.0/README.md +0 -121
- imgproxykit-0.1.0/imgproxykit/builders.py +0 -132
- imgproxykit-0.1.0/imgproxykit/client.py +0 -167
- imgproxykit-0.1.0/imgproxykit/internal/constants.py +0 -8
- imgproxykit-0.1.0/imgproxykit/internal/empty.py +0 -8
- imgproxykit-0.1.0/imgproxykit/internal/paths.py +0 -21
- imgproxykit-0.1.0/imgproxykit/internal/serialization.py +0 -34
- imgproxykit-0.1.0/imgproxykit/options/auto_rotate.py +0 -39
- imgproxykit-0.1.0/imgproxykit/options/base.py +0 -41
- imgproxykit-0.1.0/imgproxykit/options/blur.py +0 -38
- imgproxykit-0.1.0/imgproxykit/options/enforce_thumbnail.py +0 -39
- imgproxykit-0.1.0/imgproxykit/options/enlarge.py +0 -38
- imgproxykit-0.1.0/imgproxykit/options/expires.py +0 -38
- imgproxykit-0.1.0/imgproxykit/options/height.py +0 -39
- imgproxykit-0.1.0/imgproxykit/options/keep_copyright.py +0 -38
- imgproxykit-0.1.0/imgproxykit/options/max_animation_frames.py +0 -39
- imgproxykit-0.1.0/imgproxykit/options/max_bytes.py +0 -39
- imgproxykit-0.1.0/imgproxykit/options/max_src_file_size.py +0 -39
- imgproxykit-0.1.0/imgproxykit/options/max_src_resolution.py +0 -39
- imgproxykit-0.1.0/imgproxykit/options/min_height.py +0 -38
- imgproxykit-0.1.0/imgproxykit/options/min_width.py +0 -38
- imgproxykit-0.1.0/imgproxykit/options/pixelate.py +0 -38
- imgproxykit-0.1.0/imgproxykit/options/preserve_hdr.py +0 -38
- imgproxykit-0.1.0/imgproxykit/options/quality.py +0 -38
- imgproxykit-0.1.0/imgproxykit/options/raw.py +0 -39
- imgproxykit-0.1.0/imgproxykit/options/resizing_type.py +0 -39
- imgproxykit-0.1.0/imgproxykit/options/return_attachment.py +0 -39
- imgproxykit-0.1.0/imgproxykit/options/rotate.py +0 -38
- imgproxykit-0.1.0/imgproxykit/options/sharpen.py +0 -38
- imgproxykit-0.1.0/imgproxykit/options/strip_color_profile.py +0 -39
- imgproxykit-0.1.0/imgproxykit/options/strip_metadata.py +0 -38
- imgproxykit-0.1.0/imgproxykit/options/width.py +0 -39
- imgproxykit-0.1.0/imgproxykit/transform.py +0 -54
- imgproxykit-0.1.0/imgproxykit/types.py +0 -95
- {imgproxykit-0.1.0 → imgproxykit-0.2.1}/LICENSE +0 -0
- {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/__init__.py +0 -0
- {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/internal/__init__.py +0 -0
- {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/py.typed +0 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: imgproxykit
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: A typed Python toolkit for building imgproxy URLs
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: image-processing,image-transformation,images,imgproxy,url-builder
|
|
8
|
+
Author: Alexander Kondratev
|
|
9
|
+
Author-email: mail@kondratev.dev
|
|
10
|
+
Requires-Python: >=3.12,<4
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
20
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Dist: typing-extensions (>=4.16,<5)
|
|
24
|
+
Project-URL: Changelog, https://github.com/kondratevdev/imgproxykit/releases
|
|
25
|
+
Project-URL: GitHub, https://github.com/kondratevdev/imgproxykit
|
|
26
|
+
Project-URL: Issues, https://github.com/kondratevdev/imgproxykit/issues
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
<h1 align="center">imgproxykit</h1>
|
|
30
|
+
|
|
31
|
+
<p align="center">
|
|
32
|
+
<em>A typed Python toolkit for building imgproxy URLs.</em>
|
|
33
|
+
</p>
|
|
34
|
+
|
|
35
|
+
<div align="center">
|
|
36
|
+
|
|
37
|
+
[](https://github.com/kondratevdev/imgproxykit/actions/workflows/test.yml)
|
|
38
|
+
[](https://pypi.org/project/imgproxykit/)
|
|
39
|
+
[](https://pepy.tech/projects/imgproxykit)
|
|
40
|
+
[](https://pypi.org/project/imgproxykit/)
|
|
41
|
+
[](LICENSE)
|
|
42
|
+
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
## Features
|
|
46
|
+
|
|
47
|
+
- [x] Fully typed and checked with `mypy`, `pyright`, `ty` and `pyrefly` in strict modes
|
|
48
|
+
- [x] Expressive transforms: combine option objects with concise scalar arguments
|
|
49
|
+
- [x] Immutable, reusable transforms for consistent image settings across your app
|
|
50
|
+
- [x] Support for named presets configured on your imgproxy server
|
|
51
|
+
|
|
52
|
+
---------
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
Requires **Python 3.12+** and a running imgproxy server.
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install imgproxykit
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Example
|
|
63
|
+
|
|
64
|
+
Define a WebP thumbnail once and reuse it across a product catalog:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from imgproxykit import Client, Transform
|
|
68
|
+
from imgproxykit.options import Gravity, Resize
|
|
69
|
+
|
|
70
|
+
client = Client('https://imgproxy.your-app.com') # Your imgproxy server.
|
|
71
|
+
|
|
72
|
+
thumbnail = Transform(
|
|
73
|
+
Resize(width=300, height=300, mode='fill'), # Pass option objects.
|
|
74
|
+
Gravity(position='sm'), # Let imgproxy choose the crop's focus.
|
|
75
|
+
quality=80, # Or scalar, equivalent to Quality(80).
|
|
76
|
+
format='webp',
|
|
77
|
+
)
|
|
78
|
+
sources = [
|
|
79
|
+
'https://example.com/products/backpack.jpg',
|
|
80
|
+
'https://example.com/products/sneakers.jpg',
|
|
81
|
+
]
|
|
82
|
+
thumbnail_urls = [client.url(source, transform=thumbnail) for source in sources]
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Use these URLs in your images' `src` attributes; imgproxy handles processing.
|
|
86
|
+
|
|
87
|
+
Sign URLs and reuse presets configured on your imgproxy server:
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from imgproxykit import Client, Transform
|
|
91
|
+
from imgproxykit.options import Preset
|
|
92
|
+
|
|
93
|
+
client = Client(
|
|
94
|
+
'https://imgproxy.your-app.com',
|
|
95
|
+
key='736563726574', # Example hex credentials; use your server's values.
|
|
96
|
+
salt='73616c74',
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
thumbnail = Transform(
|
|
100
|
+
Preset(names=('product-card',)),
|
|
101
|
+
format='webp',
|
|
102
|
+
)
|
|
103
|
+
thumbnail_url = client.url(
|
|
104
|
+
'https://example.com/products/backpack.jpg',
|
|
105
|
+
transform=product_card,
|
|
106
|
+
)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Contributing
|
|
110
|
+
|
|
111
|
+
See the [contributing guide](CONTRIBUTING.md) for development setup and checks.
|
|
112
|
+
Bug reports and feature requests are welcome on the
|
|
113
|
+
[issue tracker](https://github.com/kondratevdev/imgproxykit/issues).
|
|
114
|
+
|
|
115
|
+
## License
|
|
116
|
+
|
|
117
|
+
[MIT](LICENSE)
|
|
118
|
+
|
|
119
|
+
## Credits
|
|
120
|
+
|
|
121
|
+
Generated with
|
|
122
|
+
[`wemake-python-package`](https://github.com/wemake-services/wemake-python-package)
|
|
123
|
+
([template version](https://github.com/wemake-services/wemake-python-package/tree/47d9dddd29da8d6d3d1a403f8a143b323a812083)).
|
|
124
|
+
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
<h1 align="center">imgproxykit</h1>
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<em>A typed Python toolkit for building imgproxy URLs.</em>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<div align="center">
|
|
8
|
+
|
|
9
|
+
[](https://github.com/kondratevdev/imgproxykit/actions/workflows/test.yml)
|
|
10
|
+
[](https://pypi.org/project/imgproxykit/)
|
|
11
|
+
[](https://pepy.tech/projects/imgproxykit)
|
|
12
|
+
[](https://pypi.org/project/imgproxykit/)
|
|
13
|
+
[](LICENSE)
|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
|
|
19
|
+
- [x] Fully typed and checked with `mypy`, `pyright`, `ty` and `pyrefly` in strict modes
|
|
20
|
+
- [x] Expressive transforms: combine option objects with concise scalar arguments
|
|
21
|
+
- [x] Immutable, reusable transforms for consistent image settings across your app
|
|
22
|
+
- [x] Support for named presets configured on your imgproxy server
|
|
23
|
+
|
|
24
|
+
---------
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
Requires **Python 3.12+** and a running imgproxy server.
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install imgproxykit
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Example
|
|
35
|
+
|
|
36
|
+
Define a WebP thumbnail once and reuse it across a product catalog:
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from imgproxykit import Client, Transform
|
|
40
|
+
from imgproxykit.options import Gravity, Resize
|
|
41
|
+
|
|
42
|
+
client = Client('https://imgproxy.your-app.com') # Your imgproxy server.
|
|
43
|
+
|
|
44
|
+
thumbnail = Transform(
|
|
45
|
+
Resize(width=300, height=300, mode='fill'), # Pass option objects.
|
|
46
|
+
Gravity(position='sm'), # Let imgproxy choose the crop's focus.
|
|
47
|
+
quality=80, # Or scalar, equivalent to Quality(80).
|
|
48
|
+
format='webp',
|
|
49
|
+
)
|
|
50
|
+
sources = [
|
|
51
|
+
'https://example.com/products/backpack.jpg',
|
|
52
|
+
'https://example.com/products/sneakers.jpg',
|
|
53
|
+
]
|
|
54
|
+
thumbnail_urls = [client.url(source, transform=thumbnail) for source in sources]
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Use these URLs in your images' `src` attributes; imgproxy handles processing.
|
|
58
|
+
|
|
59
|
+
Sign URLs and reuse presets configured on your imgproxy server:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from imgproxykit import Client, Transform
|
|
63
|
+
from imgproxykit.options import Preset
|
|
64
|
+
|
|
65
|
+
client = Client(
|
|
66
|
+
'https://imgproxy.your-app.com',
|
|
67
|
+
key='736563726574', # Example hex credentials; use your server's values.
|
|
68
|
+
salt='73616c74',
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
thumbnail = Transform(
|
|
72
|
+
Preset(names=('product-card',)),
|
|
73
|
+
format='webp',
|
|
74
|
+
)
|
|
75
|
+
thumbnail_url = client.url(
|
|
76
|
+
'https://example.com/products/backpack.jpg',
|
|
77
|
+
transform=product_card,
|
|
78
|
+
)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Contributing
|
|
82
|
+
|
|
83
|
+
See the [contributing guide](CONTRIBUTING.md) for development setup and checks.
|
|
84
|
+
Bug reports and feature requests are welcome on the
|
|
85
|
+
[issue tracker](https://github.com/kondratevdev/imgproxykit/issues).
|
|
86
|
+
|
|
87
|
+
## License
|
|
88
|
+
|
|
89
|
+
[MIT](LICENSE)
|
|
90
|
+
|
|
91
|
+
## Credits
|
|
92
|
+
|
|
93
|
+
Generated with
|
|
94
|
+
[`wemake-python-package`](https://github.com/wemake-services/wemake-python-package)
|
|
95
|
+
([template version](https://github.com/wemake-services/wemake-python-package/tree/47d9dddd29da8d6d3d1a403f8a143b323a812083)).
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
from typing import ClassVar, overload
|
|
2
|
+
|
|
3
|
+
from imgproxykit.serialization import PathSerializer
|
|
4
|
+
from imgproxykit.signing import UrlSigner
|
|
5
|
+
from imgproxykit.transform import Transform
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Client:
|
|
9
|
+
"""
|
|
10
|
+
Build imgproxy URLs from Python transforms or server presets.
|
|
11
|
+
|
|
12
|
+
Stores the imgproxy address and signing configuration for reuse across
|
|
13
|
+
images. Each call to :meth:`url` returns a URL string; imgproxy loads
|
|
14
|
+
the source and processes the image when that URL is requested.
|
|
15
|
+
|
|
16
|
+
Pass processing options through :class:`~imgproxykit.Transform` as
|
|
17
|
+
option instances or scalar keyword arguments. Preset options reference
|
|
18
|
+
names configured on the imgproxy server.
|
|
19
|
+
|
|
20
|
+
Subclass this client and override :attr:`path_serializer_cls` or
|
|
21
|
+
:attr:`url_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_serializer',
|
|
29
|
+
'_url_signer',
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
#: Component that resolves processing parameters and builds unsigned paths.
|
|
33
|
+
path_serializer_cls: ClassVar[type[PathSerializer]] = PathSerializer
|
|
34
|
+
#: Component that signs paths or returns the unsigned URL marker.
|
|
35
|
+
url_signer_cls: ClassVar[type[UrlSigner]] = UrlSigner
|
|
36
|
+
|
|
37
|
+
@overload
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
base_url: str,
|
|
41
|
+
/,
|
|
42
|
+
*,
|
|
43
|
+
key: None = None,
|
|
44
|
+
salt: None = None,
|
|
45
|
+
unsigned_signature: str = 'unsafe',
|
|
46
|
+
) -> None: ...
|
|
47
|
+
|
|
48
|
+
@overload
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
base_url: str,
|
|
52
|
+
/,
|
|
53
|
+
*,
|
|
54
|
+
key: str,
|
|
55
|
+
salt: str,
|
|
56
|
+
signature_size: int = 32,
|
|
57
|
+
) -> None: ...
|
|
58
|
+
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
base_url: str,
|
|
62
|
+
/,
|
|
63
|
+
*,
|
|
64
|
+
key: str | None = None,
|
|
65
|
+
salt: str | None = None,
|
|
66
|
+
signature_size: int = 32,
|
|
67
|
+
unsigned_signature: str = 'unsafe',
|
|
68
|
+
) -> None:
|
|
69
|
+
"""
|
|
70
|
+
Configure the imgproxy address and create the client components.
|
|
71
|
+
|
|
72
|
+
Supply both ``key`` and ``salt`` for signed URLs. Omit both for
|
|
73
|
+
URLs with the configured unsigned marker (``unsafe`` by default).
|
|
74
|
+
The server must accept unsigned requests or trust that marker.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
base_url: Public imgproxy address, including any deployment path
|
|
78
|
+
prefix. Trailing slashes are removed; the address is otherwise
|
|
79
|
+
stored as supplied, without validation.
|
|
80
|
+
key: Nonempty hex-encoded signing key matching the server.
|
|
81
|
+
Supply together with ``salt``; ``None`` selects unsigned mode.
|
|
82
|
+
salt: Nonempty hex-encoded signing salt matching the server.
|
|
83
|
+
Supply together with ``key``; ``None`` selects unsigned mode.
|
|
84
|
+
signature_size: Positive digest byte count to encode. Must match
|
|
85
|
+
the server's ``IMGPROXY_SIGNATURE_SIZE``. Defaults to ``32``.
|
|
86
|
+
Values of 32 or more use the full digest.
|
|
87
|
+
unsigned_signature: URL marker used when key and salt are omitted.
|
|
88
|
+
Defaults to ``'unsafe'``. With server signing enabled,
|
|
89
|
+
configure this marker in ``IMGPROXY_TRUSTED_SIGNATURES``.
|
|
90
|
+
|
|
91
|
+
Raises:
|
|
92
|
+
ValueError: With the default signer, if only one signing
|
|
93
|
+
credential is supplied, either is empty or invalid hex,
|
|
94
|
+
or ``signature_size`` is not positive, even in unsigned mode.
|
|
95
|
+
|
|
96
|
+
Examples:
|
|
97
|
+
Create an unsigned client:
|
|
98
|
+
|
|
99
|
+
>>> from imgproxykit import Client
|
|
100
|
+
>>> client = Client('https://images.example.com')
|
|
101
|
+
|
|
102
|
+
Create a signed client with example credentials:
|
|
103
|
+
|
|
104
|
+
>>> client = Client(
|
|
105
|
+
... 'https://images.example.com',
|
|
106
|
+
... key='736563726574',
|
|
107
|
+
... salt='73616c74',
|
|
108
|
+
... )
|
|
109
|
+
|
|
110
|
+
"""
|
|
111
|
+
self._base_url = base_url
|
|
112
|
+
self._path_serializer = self.path_serializer_cls()
|
|
113
|
+
self._url_signer = self.url_signer_cls(
|
|
114
|
+
key=key,
|
|
115
|
+
salt=salt,
|
|
116
|
+
signature_size=signature_size,
|
|
117
|
+
unsigned_signature=unsigned_signature,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def url(
|
|
121
|
+
self,
|
|
122
|
+
source: str,
|
|
123
|
+
*,
|
|
124
|
+
transform: Transform | None = None,
|
|
125
|
+
) -> str:
|
|
126
|
+
"""
|
|
127
|
+
Build a URL using processing options and server preset references.
|
|
128
|
+
|
|
129
|
+
Pass option instances or scalar keyword arguments through
|
|
130
|
+
:class:`~imgproxykit.Transform`. Use
|
|
131
|
+
:class:`~imgproxykit.options.Preset` to reference server presets.
|
|
132
|
+
Preset definitions remain on the server and are not expanded here.
|
|
133
|
+
Set the output format with ``Transform(format='webp')`` or a
|
|
134
|
+
:class:`~imgproxykit.options.Format` option. Omitted processing
|
|
135
|
+
parameters are left to the server.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
source: Complete source URI understood by the imgproxy server.
|
|
139
|
+
The default serializer encodes its UTF-8 representation as
|
|
140
|
+
URL-safe Base64 without padding. The URI is neither validated
|
|
141
|
+
nor resolved against ``base_url``.
|
|
142
|
+
transform: Reusable processing options and optional output format.
|
|
143
|
+
Omit or pass ``None`` to add no processing options.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
The complete imgproxy URL, signed when credentials were supplied
|
|
147
|
+
or containing the unsigned marker otherwise. No network request
|
|
148
|
+
is made.
|
|
149
|
+
"""
|
|
150
|
+
path = self._path_serializer(source, transform=transform)
|
|
151
|
+
signature = self._url_signer(path)
|
|
152
|
+
base_url = self._base_url.rstrip('/')
|
|
153
|
+
return f'{base_url}/{signature}{path}'
|
|
@@ -7,7 +7,6 @@ def encode_base64(value: str | bytes) -> str:
|
|
|
7
7
|
|
|
8
8
|
Text is first encoded as UTF-8. Byte values, such as signature digests,
|
|
9
9
|
are encoded directly. Trailing ``=`` padding is removed for URL paths.
|
|
10
|
-
|
|
11
10
|
"""
|
|
12
11
|
raw = value.encode() if isinstance(value, str) else value
|
|
13
12
|
return base64.urlsafe_b64encode(raw).rstrip(b'=').decode()
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from imgproxykit.options.auto_rotate import AutoRotate as AutoRotate
|
|
2
2
|
from imgproxykit.options.background import Background as Background
|
|
3
3
|
from imgproxykit.options.base import Option as Option
|
|
4
|
+
from imgproxykit.options.base import ScalarOption as ScalarOption
|
|
4
5
|
from imgproxykit.options.blur import Blur as Blur
|
|
5
6
|
from imgproxykit.options.cache_buster import CacheBuster as CacheBuster
|
|
6
7
|
from imgproxykit.options.crop import Crop as Crop
|
|
@@ -16,6 +17,7 @@ from imgproxykit.options.extend_aspect_ratio import (
|
|
|
16
17
|
)
|
|
17
18
|
from imgproxykit.options.filename import Filename as Filename
|
|
18
19
|
from imgproxykit.options.flip import Flip as Flip
|
|
20
|
+
from imgproxykit.options.format import Format as Format
|
|
19
21
|
from imgproxykit.options.format_quality import FormatQuality as FormatQuality
|
|
20
22
|
from imgproxykit.options.gravity import Gravity as Gravity
|
|
21
23
|
from imgproxykit.options.height import Height as Height
|
|
@@ -41,6 +43,7 @@ from imgproxykit.options.min_width import MinWidth as MinWidth
|
|
|
41
43
|
from imgproxykit.options.padding import Padding as Padding
|
|
42
44
|
from imgproxykit.options.pixelate import Pixelate as Pixelate
|
|
43
45
|
from imgproxykit.options.preserve_hdr import PreserveHDR as PreserveHDR
|
|
46
|
+
from imgproxykit.options.preset import Preset as Preset
|
|
44
47
|
from imgproxykit.options.quality import Quality as Quality
|
|
45
48
|
from imgproxykit.options.raw import Raw as Raw
|
|
46
49
|
from imgproxykit.options.resize import Resize as Resize
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import ClassVar
|
|
3
|
+
|
|
4
|
+
from imgproxykit.options.base import ScalarOption
|
|
5
|
+
from imgproxykit.types import OptionType
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True, slots=True)
|
|
9
|
+
class AutoRotate(ScalarOption[bool]):
|
|
10
|
+
"""
|
|
11
|
+
Apply the source EXIF orientation before processing.
|
|
12
|
+
|
|
13
|
+
Overrides automatic EXIF orientation handling for this request.
|
|
14
|
+
The orientation tag is removed regardless of this setting.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
value: Whether to apply the source EXIF orientation.
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
|
|
21
|
+
>>> option = AutoRotate(False)
|
|
22
|
+
>>> option.arguments
|
|
23
|
+
(False,)
|
|
24
|
+
|
|
25
|
+
See `imgproxy auto rotate
|
|
26
|
+
<https://docs.imgproxy.net/usage/processing#auto-rotate>`_.
|
|
27
|
+
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
name: ClassVar[OptionType] = 'auto_rotate'
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
|
-
from typing import ClassVar,
|
|
2
|
+
from typing import ClassVar, override
|
|
3
3
|
|
|
4
4
|
from imgproxykit.options.base import Option
|
|
5
|
-
from imgproxykit.types import
|
|
5
|
+
from imgproxykit.types import Argument, OptionType
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
@final
|
|
9
8
|
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
10
9
|
class Background(Option):
|
|
11
10
|
"""
|
|
@@ -28,16 +27,16 @@ class Background(Option):
|
|
|
28
27
|
(255, 255, 255)
|
|
29
28
|
>>> option = Background()
|
|
30
29
|
>>> option.arguments
|
|
31
|
-
(
|
|
30
|
+
(None,)
|
|
32
31
|
|
|
33
32
|
See `imgproxy background
|
|
34
33
|
<https://docs.imgproxy.net/usage/processing#background>`_.
|
|
35
34
|
|
|
36
35
|
"""
|
|
37
36
|
|
|
38
|
-
color: str | tuple[int, int, int] |
|
|
37
|
+
color: str | tuple[int, int, int] | None = None
|
|
39
38
|
|
|
40
|
-
|
|
39
|
+
name: ClassVar[OptionType] = 'background'
|
|
41
40
|
|
|
42
41
|
@property
|
|
43
42
|
@override
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Any, ClassVar, override
|
|
4
|
+
|
|
5
|
+
from imgproxykit.types import Argument, OptionType
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Option(ABC):
|
|
9
|
+
"""
|
|
10
|
+
Base class for typed imgproxy processing options.
|
|
11
|
+
|
|
12
|
+
Concrete options provide a fixed name and positional arguments.
|
|
13
|
+
Pass their instances to :class:`~imgproxykit.Transform`.
|
|
14
|
+
This abstract class cannot be instantiated directly.
|
|
15
|
+
|
|
16
|
+
Arguments retain ``None`` values until
|
|
17
|
+
serialization: internal gaps keep their positions, while trailing
|
|
18
|
+
empty arguments are omitted.
|
|
19
|
+
|
|
20
|
+
See `imgproxy processing options
|
|
21
|
+
<https://docs.imgproxy.net/usage/processing#processing-options>`_.
|
|
22
|
+
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
__slots__ = ()
|
|
26
|
+
|
|
27
|
+
# Public API:
|
|
28
|
+
name: ClassVar[OptionType]
|
|
29
|
+
"""
|
|
30
|
+
All subtypes must provide a supported imgproxy processing option name.
|
|
31
|
+
|
|
32
|
+
The name is shared by all instances and is not a dataclass field
|
|
33
|
+
or a constructor argument.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __post_init__(self) -> None:
|
|
37
|
+
"""Validate option arguments at construction time."""
|
|
38
|
+
self.validate()
|
|
39
|
+
|
|
40
|
+
def validate(self) -> None: # noqa: B027
|
|
41
|
+
"""
|
|
42
|
+
Validate option arguments.
|
|
43
|
+
|
|
44
|
+
Override this method to enforce server-independent constraints.
|
|
45
|
+
The default implementation does nothing.
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
ValueError: If option arguments are invalid.
|
|
49
|
+
|
|
50
|
+
"""
|
|
51
|
+
# TODO: Add per option validation
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
@abstractmethod
|
|
55
|
+
def arguments(self) -> tuple[Argument, ...]:
|
|
56
|
+
"""Positional arguments in imgproxy protocol order."""
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
type _ScalarRegistry = dict[str, type[ScalarOption[Any]]]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen=True, slots=True)
|
|
63
|
+
class ScalarOption[ValueT: Argument](Option):
|
|
64
|
+
"""
|
|
65
|
+
Base class for processing options accepting one scalar value.
|
|
66
|
+
|
|
67
|
+
Subclasses are registered automatically using their processing option
|
|
68
|
+
name, with hyphens replaced by underscores. This allows passing scalar
|
|
69
|
+
values directly to :class:`~imgproxykit.Transform` as keyword arguments.
|
|
70
|
+
|
|
71
|
+
Concrete subclasses specify the value type and processing option name.
|
|
72
|
+
Override :attr:`arguments` when the option requires custom encoding.
|
|
73
|
+
|
|
74
|
+
Attributes:
|
|
75
|
+
value: Scalar processing option value.
|
|
76
|
+
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
value: ValueT
|
|
80
|
+
|
|
81
|
+
_registry: ClassVar[_ScalarRegistry] = {}
|
|
82
|
+
|
|
83
|
+
@override
|
|
84
|
+
def __init_subclass__(cls) -> None:
|
|
85
|
+
"""Register a concrete scalar option for keyword construction."""
|
|
86
|
+
# Explicit super is required because slots=True replaces this class.
|
|
87
|
+
super(ScalarOption, cls).__init_subclass__() # noqa: WPS608
|
|
88
|
+
# Slotted dataclasses create a replacement class. Register it again
|
|
89
|
+
# so keyword construction uses the final class, including its slots.
|
|
90
|
+
ScalarOption._registry[cls._get_kwarg_name()] = cls
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
@override
|
|
94
|
+
def arguments(self) -> tuple[Argument, ...]:
|
|
95
|
+
"""Positional arguments in imgproxy protocol order."""
|
|
96
|
+
return (self.value,)
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def from_kwarg(cls, name: str, value: Argument) -> 'ScalarOption[Any]':
|
|
100
|
+
"""
|
|
101
|
+
Construct a registered scalar option from a Transform keyword.
|
|
102
|
+
|
|
103
|
+
The concrete option constructor handles value validation and
|
|
104
|
+
protocol-specific encoding, just as with positional options.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
name: Python keyword name of a registered scalar option.
|
|
108
|
+
value: Value passed to the concrete option constructor.
|
|
109
|
+
|
|
110
|
+
Raises:
|
|
111
|
+
TypeError: If the keyword does not identify a scalar option.
|
|
112
|
+
ValueError: If the concrete option rejects the supplied value.
|
|
113
|
+
|
|
114
|
+
"""
|
|
115
|
+
option_cls = cls._registry.get(name)
|
|
116
|
+
if option_cls is None:
|
|
117
|
+
raise TypeError(
|
|
118
|
+
f'Unknown scalar processing option {name!r}. '
|
|
119
|
+
'Transform keyword arguments must name a registered scalar '
|
|
120
|
+
'option. '
|
|
121
|
+
'Pass complex processing options as positional Option '
|
|
122
|
+
'instances.',
|
|
123
|
+
)
|
|
124
|
+
return option_cls(value)
|
|
125
|
+
|
|
126
|
+
@classmethod
|
|
127
|
+
def _get_kwarg_name(cls) -> str:
|
|
128
|
+
"""Convert the processing option name to its Python keyword name."""
|
|
129
|
+
return cls.name.replace('-', '_')
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import ClassVar
|
|
3
|
+
|
|
4
|
+
from imgproxykit.options.base import ScalarOption
|
|
5
|
+
from imgproxykit.types import OptionType
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True, slots=True)
|
|
9
|
+
class Blur(ScalarOption[float]):
|
|
10
|
+
"""
|
|
11
|
+
Apply Gaussian blur with the given sigma.
|
|
12
|
+
|
|
13
|
+
The sigma controls the size of the Gaussian blur mask.
|
|
14
|
+
|
|
15
|
+
Attributes:
|
|
16
|
+
value: Gaussian blur sigma.
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
|
|
20
|
+
>>> option = Blur(2.0)
|
|
21
|
+
>>> option.arguments
|
|
22
|
+
(2.0,)
|
|
23
|
+
|
|
24
|
+
See `imgproxy blur
|
|
25
|
+
<https://docs.imgproxy.net/usage/processing#blur>`_.
|
|
26
|
+
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
name: ClassVar[OptionType] = 'blur'
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
|
-
from typing import ClassVar,
|
|
2
|
+
from typing import ClassVar, override
|
|
3
3
|
from urllib.parse import quote
|
|
4
4
|
|
|
5
|
-
from imgproxykit.options.base import
|
|
5
|
+
from imgproxykit.options.base import ScalarOption
|
|
6
6
|
from imgproxykit.types import Argument, OptionType
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
@
|
|
10
|
-
|
|
11
|
-
class CacheBuster(Option):
|
|
9
|
+
@dataclass(frozen=True, slots=True)
|
|
10
|
+
class CacheBuster(ScalarOption[str]):
|
|
12
11
|
"""
|
|
13
12
|
Add an escaped version string to the signed URL to bypass caches.
|
|
14
13
|
|
|
@@ -20,7 +19,7 @@ class CacheBuster(Option):
|
|
|
20
19
|
|
|
21
20
|
Usage:
|
|
22
21
|
|
|
23
|
-
>>> option = CacheBuster(
|
|
22
|
+
>>> option = CacheBuster('v2')
|
|
24
23
|
>>> option.arguments
|
|
25
24
|
('v2',)
|
|
26
25
|
|
|
@@ -29,9 +28,7 @@ class CacheBuster(Option):
|
|
|
29
28
|
|
|
30
29
|
"""
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
_name: ClassVar[OptionType] = 'cb'
|
|
31
|
+
name: ClassVar[OptionType] = 'cachebuster'
|
|
35
32
|
|
|
36
33
|
@property
|
|
37
34
|
@override
|