pixlub 0.3.0__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.
pixlub-0.3.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pixlub
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,5 @@
1
+ # Source distribution: setuptools adds the package, README and tests/test_*.py;
2
+ # the tests also need their fixtures, and the examples ship alongside.
3
+ include tests/conftest.py
4
+ recursive-include examples *.py
5
+ global-exclude __pycache__ *.py[cod]
pixlub-0.3.0/PKG-INFO ADDED
@@ -0,0 +1,242 @@
1
+ Metadata-Version: 2.4
2
+ Name: pixlub
3
+ Version: 0.3.0
4
+ Summary: Official Python SDK for the Pixlub image API.
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://pixlub.com
7
+ Project-URL: Documentation, https://pixlub.com/en/docs/api/getting-started
8
+ Project-URL: Modules, https://pixlub.com/en/docs/modules
9
+ Project-URL: API reference, https://api.pixlub.com/docs
10
+ Project-URL: Source, https://github.com/pixlub/pixlub-python
11
+ Project-URL: Issues, https://github.com/pixlub/pixlub-python/issues
12
+ Keywords: pixlub,image,image processing,background removal,api,sdk
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
+ Classifier: Topic :: Multimedia :: Graphics
25
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
26
+ Classifier: Typing :: Typed
27
+ Requires-Python: >=3.9
28
+ Description-Content-Type: text/markdown
29
+ License-File: LICENSE
30
+ Requires-Dist: httpx<1,>=0.27
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest>=8; extra == "dev"
33
+ Requires-Dist: ruff>=0.5; extra == "dev"
34
+ Requires-Dist: mypy>=1.10; extra == "dev"
35
+ Dynamic: license-file
36
+
37
+ # pixlub
38
+
39
+ Official Python SDK for the [Pixlub](https://pixlub.com) API. Python 3.9+, one
40
+ dependency: [httpx](https://www.python-httpx.org/).
41
+
42
+ Documentation: [getting started with the API](https://pixlub.com/en/docs/api/getting-started)
43
+ and [every module, its inputs and settings](https://pixlub.com/en/docs/modules).
44
+
45
+ ## Install
46
+
47
+ ```bash
48
+ pip install pixlub
49
+ ```
50
+
51
+ ## Quick start
52
+
53
+ 1. Create an API key in your Pixlub dashboard. API keys need a paid plan.
54
+ 2. Put it in an environment variable, never in your source code:
55
+
56
+ ```bash
57
+ export PIXLUB_API_KEY="pix.xxxxxxxx.xxxxxxxx..."
58
+ ```
59
+
60
+ 3. Run a tool:
61
+
62
+ ```python
63
+ import pixlub
64
+
65
+ client = pixlub.Client()
66
+ result = client.tools.run("removeBgApi", image="photo.jpg")
67
+ result.save(f"photo-nobg{result.extension}")
68
+ ```
69
+
70
+ `result.extension` matches the produced file: `.webp` for `removeBgApi`, `.png`
71
+ for most tools. `save()` refuses to replace an existing file unless
72
+ `overwrite=True`.
73
+
74
+ ## Results
75
+
76
+ ```python
77
+ result = client.tools.run("faceBlurApi", image="photo.jpg", params={"mode": "auto"})
78
+
79
+ result.image # bytes of the produced file (None if the tool only returns data)
80
+ result.data # the data output, parsed JSON
81
+ result.cost # credits charged
82
+ result.credits_balance # credits left
83
+ result.output("out") # an output by key, or by name ("image", "data", "file")
84
+ for output in result.outputs:
85
+ print(output.key, output.name, output.content_type, len(output.content))
86
+ ```
87
+
88
+ In a batch, output keys get the rank of the file: `out#0`, `out#1`… Each
89
+ `output.url` is a download link valid for one hour.
90
+
91
+ ## Tools and settings
92
+
93
+ ```python
94
+ for tool in client.tools.list(executable_only=True):
95
+ print(tool.id, [i.id for i in tool.inputs], [o.key for o in tool.outputs])
96
+
97
+ schema = client.tools.schema("flip") # JSON Schema of the settings
98
+ client.tools.run("flip", image="photo.jpg", params={"axis": "v"})
99
+ ```
100
+
101
+ Invalid settings raise `InvalidRequestError`, with the reasons in `details`.
102
+
103
+ ## Several files
104
+
105
+ A tool with several inputs takes `inputs=`, keyed by input id. An input that
106
+ takes several files takes a list:
107
+
108
+ ```python
109
+ client.tools.run("mask", inputs={"image": "photo.png", "mask": "mask.png"})
110
+ client.tools.run("merge", inputs={"base": "bg.png", "overlay": ["1.png", "2.png"]})
111
+ client.tools.run("flip", image=["a.png", "b.png", "c.png"], params={"axis": "h"}) # 3 runs
112
+ ```
113
+
114
+ When `tool.inputs[i].per_file` is true, each file is a separate run, charged
115
+ separately. A palette is passed in `params`, as a list of colors.
116
+
117
+ A file is a path, bytes, or a binary file object. To use a file from the web,
118
+ pass a `pixlub.SourceURL`; a plain string is always a local path.
119
+
120
+ ```python
121
+ client.tools.run("removeBgApi", image=pixlub.SourceURL("https://example.com/photo.jpg"))
122
+ ```
123
+
124
+ Large files are handled automatically: call `run()` the same way.
125
+
126
+ ## Limits
127
+
128
+ | Limit | Value |
129
+ | --- | --- |
130
+ | File size, by plan | Free 10 MiB, Basic 50 MiB, Pro 3 GiB |
131
+ | File size, by tool | images 100 MiB, video and audio 3 GiB, Office 250 MiB |
132
+ | Image size | 40 megapixels |
133
+ | Files per run | 64 |
134
+ | Runs | 30 a minute per account, bursts of 10 |
135
+ | Download links | 1 hour |
136
+
137
+ ## Async
138
+
139
+ `AsyncClient` has the same methods:
140
+
141
+ ```python
142
+ import asyncio
143
+ import pixlub
144
+
145
+
146
+ async def main() -> None:
147
+ async with pixlub.AsyncClient() as client:
148
+ result = await client.tools.run("removeBgApi", image="photo.jpg")
149
+ result.save(f"photo-nobg{result.extension}")
150
+
151
+
152
+ asyncio.run(main())
153
+ ```
154
+
155
+ ## Timeouts
156
+
157
+ `run()` waits for long runs until they finish, up to `job_timeout` (30 minutes
158
+ by default). After that, `JobTimeoutError` gives the `job_id`: the run may
159
+ still finish and may have been charged, so check it with `GET /v1/jobs/{job_id}`
160
+ rather than running the tool again.
161
+
162
+ ```python
163
+ client = pixlub.Client(timeout=30, job_timeout=600)
164
+ result = client.tools.run("removeBgApi", image="photo.jpg", job_timeout=3600)
165
+ ```
166
+
167
+ ## Errors
168
+
169
+ API errors and refused arguments raise a `pixlub.PixlubError`. A wrong argument
170
+ type raises `TypeError`; a file that `save()` cannot write raises `OSError`.
171
+
172
+ | Exception | When |
173
+ | --- | --- |
174
+ | `ConfigurationError` | Missing or malformed key or base URL. |
175
+ | `InvalidInputError` | An argument was refused before anything was sent. |
176
+ | `UnknownToolError` | Unknown tool id. |
177
+ | `InvalidRequestError` | HTTP 400 or 422: invalid settings (`details`), `image_too_large` (`max_pixels`)… |
178
+ | `FileTooLargeError` | HTTP 413: file too large for the tool or your plan (`max_bytes`). |
179
+ | `AuthenticationError` | HTTP 401: unknown or revoked key. |
180
+ | `InsufficientCreditsError` | HTTP 402: not enough credits (`required_credits`). |
181
+ | `PermissionDeniedError` | HTTP 403: not included in your plan (`code`, `limit`). |
182
+ | `NotFoundError` | HTTP 404. |
183
+ | `RateLimitError` | HTTP 429: too many requests (`retry_after`). Never charged. |
184
+ | `ServerError`, `ServiceUnavailableError` | HTTP 5xx (`retry_after`). |
185
+ | `APIConnectionError`, `APITimeoutError` | No answer (`request_sent`). |
186
+ | `JobTimeoutError` | A run outlived `job_timeout` (`job_id`). |
187
+ | `UnexpectedResponseError` | The answer could not be read. |
188
+
189
+ Every `APIError` has `http_status`, `code` and `message`.
190
+
191
+ ```python
192
+ try:
193
+ result = client.tools.run("removeBgApi", image="photo.jpg")
194
+ except pixlub.InsufficientCreditsError as error:
195
+ print("Top up your credits:", error.required_credits)
196
+ except pixlub.APIError as error:
197
+ print(f"Refused ({error.http_status}): {error.message}")
198
+ ```
199
+
200
+ ## Retries and credits
201
+
202
+ The SDK retries a request only when it cannot cause a second charge, up to
203
+ `max_retries` times (2 by default, 0 to disable), and waits as long as the API
204
+ asks. Arguments are checked before anything is sent: a mistake costs nothing.
205
+ After a timeout or a server error on a run, check your balance before running
206
+ it again.
207
+
208
+ ## Configuration
209
+
210
+ | Argument | Environment variable | Default |
211
+ | --- | --- | --- |
212
+ | `api_key` | `PIXLUB_API_KEY` | required |
213
+ | `base_url` | `PIXLUB_BASE_URL` | `https://api.pixlub.com` (`http://` only for localhost) |
214
+ | `timeout` | | 10 s connect, 180 s read, 60 s write |
215
+ | `job_timeout` | | 1800 s |
216
+ | `max_retries` | | 2 (0 to 5) |
217
+ | `max_input_bytes` | | 3 GiB |
218
+ | `http_client` | | a new `httpx.Client` (pass one for proxies or custom TLS) |
219
+
220
+ ## Examples
221
+
222
+ [`examples/`](https://github.com/pixlub/pixlub-python/tree/main/examples):
223
+ `quickstart.py` for a first script, `batch_async.py` for many images at once.
224
+
225
+ ## Development
226
+
227
+ ```bash
228
+ pip install -e ".[dev]"
229
+ pytest
230
+ ruff check .
231
+ mypy
232
+ ```
233
+
234
+ Live tests run against a real API, and running a tool costs credits:
235
+
236
+ ```bash
237
+ PIXLUB_BASE_URL=https://api.pixlub.com PIXLUB_API_KEY=... pytest tests/test_live.py
238
+ ```
239
+
240
+ ## License
241
+
242
+ MIT: see [LICENSE](https://github.com/pixlub/pixlub-python/blob/main/LICENSE).
pixlub-0.3.0/README.md ADDED
@@ -0,0 +1,206 @@
1
+ # pixlub
2
+
3
+ Official Python SDK for the [Pixlub](https://pixlub.com) API. Python 3.9+, one
4
+ dependency: [httpx](https://www.python-httpx.org/).
5
+
6
+ Documentation: [getting started with the API](https://pixlub.com/en/docs/api/getting-started)
7
+ and [every module, its inputs and settings](https://pixlub.com/en/docs/modules).
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pip install pixlub
13
+ ```
14
+
15
+ ## Quick start
16
+
17
+ 1. Create an API key in your Pixlub dashboard. API keys need a paid plan.
18
+ 2. Put it in an environment variable, never in your source code:
19
+
20
+ ```bash
21
+ export PIXLUB_API_KEY="pix.xxxxxxxx.xxxxxxxx..."
22
+ ```
23
+
24
+ 3. Run a tool:
25
+
26
+ ```python
27
+ import pixlub
28
+
29
+ client = pixlub.Client()
30
+ result = client.tools.run("removeBgApi", image="photo.jpg")
31
+ result.save(f"photo-nobg{result.extension}")
32
+ ```
33
+
34
+ `result.extension` matches the produced file: `.webp` for `removeBgApi`, `.png`
35
+ for most tools. `save()` refuses to replace an existing file unless
36
+ `overwrite=True`.
37
+
38
+ ## Results
39
+
40
+ ```python
41
+ result = client.tools.run("faceBlurApi", image="photo.jpg", params={"mode": "auto"})
42
+
43
+ result.image # bytes of the produced file (None if the tool only returns data)
44
+ result.data # the data output, parsed JSON
45
+ result.cost # credits charged
46
+ result.credits_balance # credits left
47
+ result.output("out") # an output by key, or by name ("image", "data", "file")
48
+ for output in result.outputs:
49
+ print(output.key, output.name, output.content_type, len(output.content))
50
+ ```
51
+
52
+ In a batch, output keys get the rank of the file: `out#0`, `out#1`… Each
53
+ `output.url` is a download link valid for one hour.
54
+
55
+ ## Tools and settings
56
+
57
+ ```python
58
+ for tool in client.tools.list(executable_only=True):
59
+ print(tool.id, [i.id for i in tool.inputs], [o.key for o in tool.outputs])
60
+
61
+ schema = client.tools.schema("flip") # JSON Schema of the settings
62
+ client.tools.run("flip", image="photo.jpg", params={"axis": "v"})
63
+ ```
64
+
65
+ Invalid settings raise `InvalidRequestError`, with the reasons in `details`.
66
+
67
+ ## Several files
68
+
69
+ A tool with several inputs takes `inputs=`, keyed by input id. An input that
70
+ takes several files takes a list:
71
+
72
+ ```python
73
+ client.tools.run("mask", inputs={"image": "photo.png", "mask": "mask.png"})
74
+ client.tools.run("merge", inputs={"base": "bg.png", "overlay": ["1.png", "2.png"]})
75
+ client.tools.run("flip", image=["a.png", "b.png", "c.png"], params={"axis": "h"}) # 3 runs
76
+ ```
77
+
78
+ When `tool.inputs[i].per_file` is true, each file is a separate run, charged
79
+ separately. A palette is passed in `params`, as a list of colors.
80
+
81
+ A file is a path, bytes, or a binary file object. To use a file from the web,
82
+ pass a `pixlub.SourceURL`; a plain string is always a local path.
83
+
84
+ ```python
85
+ client.tools.run("removeBgApi", image=pixlub.SourceURL("https://example.com/photo.jpg"))
86
+ ```
87
+
88
+ Large files are handled automatically: call `run()` the same way.
89
+
90
+ ## Limits
91
+
92
+ | Limit | Value |
93
+ | --- | --- |
94
+ | File size, by plan | Free 10 MiB, Basic 50 MiB, Pro 3 GiB |
95
+ | File size, by tool | images 100 MiB, video and audio 3 GiB, Office 250 MiB |
96
+ | Image size | 40 megapixels |
97
+ | Files per run | 64 |
98
+ | Runs | 30 a minute per account, bursts of 10 |
99
+ | Download links | 1 hour |
100
+
101
+ ## Async
102
+
103
+ `AsyncClient` has the same methods:
104
+
105
+ ```python
106
+ import asyncio
107
+ import pixlub
108
+
109
+
110
+ async def main() -> None:
111
+ async with pixlub.AsyncClient() as client:
112
+ result = await client.tools.run("removeBgApi", image="photo.jpg")
113
+ result.save(f"photo-nobg{result.extension}")
114
+
115
+
116
+ asyncio.run(main())
117
+ ```
118
+
119
+ ## Timeouts
120
+
121
+ `run()` waits for long runs until they finish, up to `job_timeout` (30 minutes
122
+ by default). After that, `JobTimeoutError` gives the `job_id`: the run may
123
+ still finish and may have been charged, so check it with `GET /v1/jobs/{job_id}`
124
+ rather than running the tool again.
125
+
126
+ ```python
127
+ client = pixlub.Client(timeout=30, job_timeout=600)
128
+ result = client.tools.run("removeBgApi", image="photo.jpg", job_timeout=3600)
129
+ ```
130
+
131
+ ## Errors
132
+
133
+ API errors and refused arguments raise a `pixlub.PixlubError`. A wrong argument
134
+ type raises `TypeError`; a file that `save()` cannot write raises `OSError`.
135
+
136
+ | Exception | When |
137
+ | --- | --- |
138
+ | `ConfigurationError` | Missing or malformed key or base URL. |
139
+ | `InvalidInputError` | An argument was refused before anything was sent. |
140
+ | `UnknownToolError` | Unknown tool id. |
141
+ | `InvalidRequestError` | HTTP 400 or 422: invalid settings (`details`), `image_too_large` (`max_pixels`)… |
142
+ | `FileTooLargeError` | HTTP 413: file too large for the tool or your plan (`max_bytes`). |
143
+ | `AuthenticationError` | HTTP 401: unknown or revoked key. |
144
+ | `InsufficientCreditsError` | HTTP 402: not enough credits (`required_credits`). |
145
+ | `PermissionDeniedError` | HTTP 403: not included in your plan (`code`, `limit`). |
146
+ | `NotFoundError` | HTTP 404. |
147
+ | `RateLimitError` | HTTP 429: too many requests (`retry_after`). Never charged. |
148
+ | `ServerError`, `ServiceUnavailableError` | HTTP 5xx (`retry_after`). |
149
+ | `APIConnectionError`, `APITimeoutError` | No answer (`request_sent`). |
150
+ | `JobTimeoutError` | A run outlived `job_timeout` (`job_id`). |
151
+ | `UnexpectedResponseError` | The answer could not be read. |
152
+
153
+ Every `APIError` has `http_status`, `code` and `message`.
154
+
155
+ ```python
156
+ try:
157
+ result = client.tools.run("removeBgApi", image="photo.jpg")
158
+ except pixlub.InsufficientCreditsError as error:
159
+ print("Top up your credits:", error.required_credits)
160
+ except pixlub.APIError as error:
161
+ print(f"Refused ({error.http_status}): {error.message}")
162
+ ```
163
+
164
+ ## Retries and credits
165
+
166
+ The SDK retries a request only when it cannot cause a second charge, up to
167
+ `max_retries` times (2 by default, 0 to disable), and waits as long as the API
168
+ asks. Arguments are checked before anything is sent: a mistake costs nothing.
169
+ After a timeout or a server error on a run, check your balance before running
170
+ it again.
171
+
172
+ ## Configuration
173
+
174
+ | Argument | Environment variable | Default |
175
+ | --- | --- | --- |
176
+ | `api_key` | `PIXLUB_API_KEY` | required |
177
+ | `base_url` | `PIXLUB_BASE_URL` | `https://api.pixlub.com` (`http://` only for localhost) |
178
+ | `timeout` | | 10 s connect, 180 s read, 60 s write |
179
+ | `job_timeout` | | 1800 s |
180
+ | `max_retries` | | 2 (0 to 5) |
181
+ | `max_input_bytes` | | 3 GiB |
182
+ | `http_client` | | a new `httpx.Client` (pass one for proxies or custom TLS) |
183
+
184
+ ## Examples
185
+
186
+ [`examples/`](https://github.com/pixlub/pixlub-python/tree/main/examples):
187
+ `quickstart.py` for a first script, `batch_async.py` for many images at once.
188
+
189
+ ## Development
190
+
191
+ ```bash
192
+ pip install -e ".[dev]"
193
+ pytest
194
+ ruff check .
195
+ mypy
196
+ ```
197
+
198
+ Live tests run against a real API, and running a tool costs credits:
199
+
200
+ ```bash
201
+ PIXLUB_BASE_URL=https://api.pixlub.com PIXLUB_API_KEY=... pytest tests/test_live.py
202
+ ```
203
+
204
+ ## License
205
+
206
+ MIT: see [LICENSE](https://github.com/pixlub/pixlub-python/blob/main/LICENSE).
@@ -0,0 +1,45 @@
1
+ """Run one tool on many images, a few at a time.
2
+
3
+ python examples/batch_async.py out/ photo1.jpg photo2.jpg photo3.jpg
4
+
5
+ At most CONCURRENCY runs at once. A failed image is reported and the others carry on.
6
+ """
7
+
8
+ import asyncio
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ import pixlub
13
+
14
+ TOOL = "removeBgApi"
15
+ CONCURRENCY = 4
16
+
17
+
18
+ async def process(
19
+ client: pixlub.AsyncClient, gate: asyncio.Semaphore, index: int, source: Path, out_dir: Path
20
+ ) -> str:
21
+ async with gate:
22
+ try:
23
+ result = await client.tools.run(TOOL, image=source)
24
+ except pixlub.PixlubError as error:
25
+ return f"FAILED {source.name}: {error}"
26
+ # The index keeps two inputs with the same file name from overwriting each other.
27
+ target = out_dir / f"{index:03d}-{source.stem}-{TOOL}{result.extension}"
28
+ result.save(target, overwrite=True)
29
+ return f"ok {source.name} -> {target}"
30
+
31
+
32
+ async def main(out_dir: Path, sources: list) -> None:
33
+ out_dir.mkdir(parents=True, exist_ok=True)
34
+ gate = asyncio.Semaphore(CONCURRENCY)
35
+ async with pixlub.AsyncClient() as client:
36
+ lines = await asyncio.gather(
37
+ *(process(client, gate, i, Path(s), out_dir) for i, s in enumerate(sources, 1))
38
+ )
39
+ print("\n".join(lines))
40
+
41
+
42
+ if __name__ == "__main__":
43
+ if len(sys.argv) < 3:
44
+ raise SystemExit("usage: python examples/batch_async.py <out-dir> <image> [<image> ...]")
45
+ asyncio.run(main(Path(sys.argv[1]), sys.argv[2:]))
@@ -0,0 +1,38 @@
1
+ """List the tools, then remove the background of an image.
2
+
3
+ export PIXLUB_API_KEY=pix....
4
+ python examples/quickstart.py photo.jpg
5
+ """
6
+
7
+ import sys
8
+
9
+ import pixlub
10
+
11
+
12
+ def main() -> int:
13
+ if len(sys.argv) != 2:
14
+ print("usage: python examples/quickstart.py <image>")
15
+ return 2
16
+
17
+ client = pixlub.Client()
18
+ tools = client.tools.list(executable_only=True)
19
+ print(f"{len(tools)} tools available, for example: {', '.join(t.id for t in tools[:5])}")
20
+
21
+ try:
22
+ result = client.tools.run("removeBgApi", image=sys.argv[1])
23
+ except pixlub.InsufficientCreditsError as error:
24
+ print(f"Not enough credits: {error}")
25
+ return 1
26
+ except pixlub.APIError as error:
27
+ print(f"The API refused the run: {error}")
28
+ return 1
29
+
30
+ output = result.save(f"output{result.extension}", overwrite=True)
31
+ print(f"Saved {output} ({len(result.image)} bytes)")
32
+ if result.credits_balance is not None:
33
+ print(f"Credits left: {result.credits_balance}")
34
+ return 0
35
+
36
+
37
+ if __name__ == "__main__":
38
+ raise SystemExit(main())
@@ -0,0 +1,72 @@
1
+ [build-system]
2
+ # 77: first setuptools to read `license` as an SPDX expression (PEP 639).
3
+ requires = ["setuptools>=77", "wheel"]
4
+ build-backend = "setuptools.build_meta"
5
+
6
+ [project]
7
+ name = "pixlub"
8
+ # The version lives in src/pixlub/_version.py only (read without importing the package).
9
+ dynamic = ["version"]
10
+ description = "Official Python SDK for the Pixlub image API."
11
+ readme = "README.md"
12
+ requires-python = ">=3.9"
13
+ license = "MIT"
14
+ license-files = ["LICENSE"]
15
+ dependencies = ["httpx>=0.27,<1"]
16
+ keywords = ["pixlub", "image", "image processing", "background removal", "api", "sdk"]
17
+ classifiers = [
18
+ "Development Status :: 4 - Beta",
19
+ "Intended Audience :: Developers",
20
+ "Operating System :: OS Independent",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3 :: Only",
23
+ "Programming Language :: Python :: 3.9",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Programming Language :: Python :: 3.13",
28
+ "Programming Language :: Python :: 3.14",
29
+ "Topic :: Multimedia :: Graphics",
30
+ "Topic :: Software Development :: Libraries :: Python Modules",
31
+ "Typing :: Typed",
32
+ ]
33
+
34
+ [project.urls]
35
+ Homepage = "https://pixlub.com"
36
+ Documentation = "https://pixlub.com/en/docs/api/getting-started"
37
+ Modules = "https://pixlub.com/en/docs/modules"
38
+ "API reference" = "https://api.pixlub.com/docs"
39
+ Source = "https://github.com/pixlub/pixlub-python"
40
+ Issues = "https://github.com/pixlub/pixlub-python/issues"
41
+
42
+ [project.optional-dependencies]
43
+ dev = ["pytest>=8", "ruff>=0.5", "mypy>=1.10"]
44
+
45
+ [tool.setuptools.dynamic]
46
+ version = { attr = "pixlub._version.__version__" }
47
+
48
+ [tool.setuptools.packages.find]
49
+ where = ["src"]
50
+
51
+ [tool.setuptools.package-data]
52
+ pixlub = ["py.typed"]
53
+
54
+ [tool.pytest.ini_options]
55
+ testpaths = ["tests"]
56
+ pythonpath = ["src"]
57
+
58
+ [tool.mypy]
59
+ strict = true
60
+ files = ["src/pixlub"]
61
+
62
+ [tool.ruff]
63
+ line-length = 100
64
+ target-version = "py39"
65
+
66
+ [tool.ruff.lint]
67
+ select = ["E", "F", "I", "B", "S"]
68
+
69
+ [tool.ruff.lint.per-file-ignores]
70
+ # Tests assert on purpose, and the examples are scripts, not library code.
71
+ "tests/**" = ["S"]
72
+ "examples/**" = ["S"]
pixlub-0.3.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+