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 +21 -0
- pixlub-0.3.0/MANIFEST.in +5 -0
- pixlub-0.3.0/PKG-INFO +242 -0
- pixlub-0.3.0/README.md +206 -0
- pixlub-0.3.0/examples/batch_async.py +45 -0
- pixlub-0.3.0/examples/quickstart.py +38 -0
- pixlub-0.3.0/pyproject.toml +72 -0
- pixlub-0.3.0/setup.cfg +4 -0
- pixlub-0.3.0/src/pixlub/__init__.py +71 -0
- pixlub-0.3.0/src/pixlub/_config.py +83 -0
- pixlub-0.3.0/src/pixlub/_core.py +808 -0
- pixlub-0.3.0/src/pixlub/_files.py +393 -0
- pixlub-0.3.0/src/pixlub/_retry.py +78 -0
- pixlub-0.3.0/src/pixlub/_version.py +1 -0
- pixlub-0.3.0/src/pixlub/async_client.py +330 -0
- pixlub-0.3.0/src/pixlub/client.py +341 -0
- pixlub-0.3.0/src/pixlub/errors.py +135 -0
- pixlub-0.3.0/src/pixlub/models.py +183 -0
- pixlub-0.3.0/src/pixlub/py.typed +0 -0
- pixlub-0.3.0/src/pixlub/tools.py +311 -0
- pixlub-0.3.0/src/pixlub.egg-info/PKG-INFO +242 -0
- pixlub-0.3.0/src/pixlub.egg-info/SOURCES.txt +30 -0
- pixlub-0.3.0/src/pixlub.egg-info/dependency_links.txt +1 -0
- pixlub-0.3.0/src/pixlub.egg-info/requires.txt +6 -0
- pixlub-0.3.0/src/pixlub.egg-info/top_level.txt +1 -0
- pixlub-0.3.0/tests/conftest.py +385 -0
- pixlub-0.3.0/tests/test_async_client.py +330 -0
- pixlub-0.3.0/tests/test_client.py +775 -0
- pixlub-0.3.0/tests/test_jobs.py +266 -0
- pixlub-0.3.0/tests/test_live.py +60 -0
- pixlub-0.3.0/tests/test_units.py +565 -0
- pixlub-0.3.0/tests/test_uploads.py +546 -0
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.
|
pixlub-0.3.0/MANIFEST.in
ADDED
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