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.
Files changed (95) hide show
  1. imgproxykit-0.2.1/PKG-INFO +124 -0
  2. imgproxykit-0.2.1/README.md +95 -0
  3. imgproxykit-0.2.1/imgproxykit/client.py +153 -0
  4. {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/internal/encoding.py +0 -1
  5. {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/__init__.py +3 -0
  6. imgproxykit-0.2.1/imgproxykit/options/auto_rotate.py +30 -0
  7. {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/background.py +5 -6
  8. imgproxykit-0.2.1/imgproxykit/options/base.py +129 -0
  9. imgproxykit-0.2.1/imgproxykit/options/blur.py +29 -0
  10. {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/cache_buster.py +6 -9
  11. {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/crop.py +10 -11
  12. {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/dpr.py +6 -14
  13. imgproxykit-0.2.1/imgproxykit/options/enforce_thumbnail.py +30 -0
  14. imgproxykit-0.2.1/imgproxykit/options/enlarge.py +29 -0
  15. imgproxykit-0.2.1/imgproxykit/options/expires.py +29 -0
  16. {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/extend.py +8 -9
  17. {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/extend_aspect_ratio.py +8 -9
  18. {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/filename.py +6 -9
  19. {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/flip.py +7 -8
  20. imgproxykit-0.2.1/imgproxykit/options/format.py +30 -0
  21. {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/format_quality.py +2 -3
  22. {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/gravity.py +13 -9
  23. imgproxykit-0.2.1/imgproxykit/options/height.py +30 -0
  24. imgproxykit-0.2.1/imgproxykit/options/keep_copyright.py +29 -0
  25. {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/max_animation_frame_resolution.py +7 -16
  26. imgproxykit-0.2.1/imgproxykit/options/max_animation_frames.py +30 -0
  27. imgproxykit-0.2.1/imgproxykit/options/max_bytes.py +30 -0
  28. {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/max_result_dimension.py +7 -16
  29. imgproxykit-0.2.1/imgproxykit/options/max_src_file_size.py +30 -0
  30. imgproxykit-0.2.1/imgproxykit/options/max_src_resolution.py +30 -0
  31. imgproxykit-0.2.1/imgproxykit/options/min_height.py +29 -0
  32. imgproxykit-0.2.1/imgproxykit/options/min_width.py +29 -0
  33. {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/padding.py +9 -10
  34. imgproxykit-0.2.1/imgproxykit/options/pixelate.py +29 -0
  35. imgproxykit-0.2.1/imgproxykit/options/preserve_hdr.py +29 -0
  36. imgproxykit-0.2.1/imgproxykit/options/preset.py +39 -0
  37. imgproxykit-0.2.1/imgproxykit/options/quality.py +30 -0
  38. imgproxykit-0.2.1/imgproxykit/options/raw.py +30 -0
  39. {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/resize.py +12 -13
  40. imgproxykit-0.2.1/imgproxykit/options/resizing_type.py +30 -0
  41. imgproxykit-0.2.1/imgproxykit/options/return_attachment.py +30 -0
  42. imgproxykit-0.2.1/imgproxykit/options/rotate.py +29 -0
  43. imgproxykit-0.2.1/imgproxykit/options/sharpen.py +29 -0
  44. {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/size.py +9 -10
  45. {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/skip_processing.py +2 -3
  46. imgproxykit-0.2.1/imgproxykit/options/strip_color_profile.py +30 -0
  47. imgproxykit-0.2.1/imgproxykit/options/strip_metadata.py +29 -0
  48. {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/trim.py +8 -9
  49. {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/watermark.py +10 -11
  50. imgproxykit-0.2.1/imgproxykit/options/width.py +30 -0
  51. {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/options/zoom.py +5 -6
  52. imgproxykit-0.2.1/imgproxykit/serialization.py +106 -0
  53. {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/signing.py +46 -36
  54. imgproxykit-0.2.1/imgproxykit/transform.py +107 -0
  55. imgproxykit-0.2.1/imgproxykit/types.py +95 -0
  56. {imgproxykit-0.1.0 → imgproxykit-0.2.1}/pyproject.toml +31 -7
  57. imgproxykit-0.1.0/PKG-INFO +0 -150
  58. imgproxykit-0.1.0/README.md +0 -121
  59. imgproxykit-0.1.0/imgproxykit/builders.py +0 -132
  60. imgproxykit-0.1.0/imgproxykit/client.py +0 -167
  61. imgproxykit-0.1.0/imgproxykit/internal/constants.py +0 -8
  62. imgproxykit-0.1.0/imgproxykit/internal/empty.py +0 -8
  63. imgproxykit-0.1.0/imgproxykit/internal/paths.py +0 -21
  64. imgproxykit-0.1.0/imgproxykit/internal/serialization.py +0 -34
  65. imgproxykit-0.1.0/imgproxykit/options/auto_rotate.py +0 -39
  66. imgproxykit-0.1.0/imgproxykit/options/base.py +0 -41
  67. imgproxykit-0.1.0/imgproxykit/options/blur.py +0 -38
  68. imgproxykit-0.1.0/imgproxykit/options/enforce_thumbnail.py +0 -39
  69. imgproxykit-0.1.0/imgproxykit/options/enlarge.py +0 -38
  70. imgproxykit-0.1.0/imgproxykit/options/expires.py +0 -38
  71. imgproxykit-0.1.0/imgproxykit/options/height.py +0 -39
  72. imgproxykit-0.1.0/imgproxykit/options/keep_copyright.py +0 -38
  73. imgproxykit-0.1.0/imgproxykit/options/max_animation_frames.py +0 -39
  74. imgproxykit-0.1.0/imgproxykit/options/max_bytes.py +0 -39
  75. imgproxykit-0.1.0/imgproxykit/options/max_src_file_size.py +0 -39
  76. imgproxykit-0.1.0/imgproxykit/options/max_src_resolution.py +0 -39
  77. imgproxykit-0.1.0/imgproxykit/options/min_height.py +0 -38
  78. imgproxykit-0.1.0/imgproxykit/options/min_width.py +0 -38
  79. imgproxykit-0.1.0/imgproxykit/options/pixelate.py +0 -38
  80. imgproxykit-0.1.0/imgproxykit/options/preserve_hdr.py +0 -38
  81. imgproxykit-0.1.0/imgproxykit/options/quality.py +0 -38
  82. imgproxykit-0.1.0/imgproxykit/options/raw.py +0 -39
  83. imgproxykit-0.1.0/imgproxykit/options/resizing_type.py +0 -39
  84. imgproxykit-0.1.0/imgproxykit/options/return_attachment.py +0 -39
  85. imgproxykit-0.1.0/imgproxykit/options/rotate.py +0 -38
  86. imgproxykit-0.1.0/imgproxykit/options/sharpen.py +0 -38
  87. imgproxykit-0.1.0/imgproxykit/options/strip_color_profile.py +0 -39
  88. imgproxykit-0.1.0/imgproxykit/options/strip_metadata.py +0 -38
  89. imgproxykit-0.1.0/imgproxykit/options/width.py +0 -39
  90. imgproxykit-0.1.0/imgproxykit/transform.py +0 -54
  91. imgproxykit-0.1.0/imgproxykit/types.py +0 -95
  92. {imgproxykit-0.1.0 → imgproxykit-0.2.1}/LICENSE +0 -0
  93. {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/__init__.py +0 -0
  94. {imgproxykit-0.1.0 → imgproxykit-0.2.1}/imgproxykit/internal/__init__.py +0 -0
  95. {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
+ [![Tests Result](https://img.shields.io/github/actions/workflow/status/kondratevdev/imgproxykit/test.yml?branch=master&label=tests&labelColor=black&color=blue)](https://github.com/kondratevdev/imgproxykit/actions/workflows/test.yml)
38
+ [![PyPI Version](https://img.shields.io/pypi/v/imgproxykit.svg?labelColor=black&color=blue)](https://pypi.org/project/imgproxykit/)
39
+ [![PyPI Downloads](https://static.pepy.tech/personalized-badge/imgproxykit?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=BLUE&left_text=downloads)](https://pepy.tech/projects/imgproxykit)
40
+ [![Supported Python Version](https://img.shields.io/pypi/pyversions/imgproxykit.svg?labelColor=black&color=blue)](https://pypi.org/project/imgproxykit/)
41
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg?labelColor=black)](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
+ [![Tests Result](https://img.shields.io/github/actions/workflow/status/kondratevdev/imgproxykit/test.yml?branch=master&label=tests&labelColor=black&color=blue)](https://github.com/kondratevdev/imgproxykit/actions/workflows/test.yml)
10
+ [![PyPI Version](https://img.shields.io/pypi/v/imgproxykit.svg?labelColor=black&color=blue)](https://pypi.org/project/imgproxykit/)
11
+ [![PyPI Downloads](https://static.pepy.tech/personalized-badge/imgproxykit?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=BLUE&left_text=downloads)](https://pepy.tech/projects/imgproxykit)
12
+ [![Supported Python Version](https://img.shields.io/pypi/pyversions/imgproxykit.svg?labelColor=black&color=blue)](https://pypi.org/project/imgproxykit/)
13
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg?labelColor=black)](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, final, override
2
+ from typing import ClassVar, override
3
3
 
4
4
  from imgproxykit.options.base import Option
5
- from imgproxykit.types import EMPTY, Argument, OptionType
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
- (EMPTY,)
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] | EMPTY = EMPTY
37
+ color: str | tuple[int, int, int] | None = None
39
38
 
40
- _name: ClassVar[OptionType] = 'bg'
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, final, override
2
+ from typing import ClassVar, override
3
3
  from urllib.parse import quote
4
4
 
5
- from imgproxykit.options.base import Option
5
+ from imgproxykit.options.base import ScalarOption
6
6
  from imgproxykit.types import Argument, OptionType
7
7
 
8
8
 
9
- @final
10
- @dataclass(frozen=True, slots=True, kw_only=True)
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(value='v2')
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
- value: str
33
-
34
- _name: ClassVar[OptionType] = 'cb'
31
+ name: ClassVar[OptionType] = 'cachebuster'
35
32
 
36
33
  @property
37
34
  @override