etchv-sdk 1.0.0__tar.gz → 1.2.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.
- etchv_sdk-1.2.0/CHANGELOG.md +33 -0
- etchv_sdk-1.2.0/PKG-INFO +206 -0
- etchv_sdk-1.2.0/README.md +179 -0
- {etchv_sdk-1.0.0 → etchv_sdk-1.2.0}/pyproject.toml +2 -2
- {etchv_sdk-1.0.0 → etchv_sdk-1.2.0}/src/etchv/__init__.py +20 -0
- etchv_sdk-1.2.0/src/etchv/client.py +1290 -0
- etchv_sdk-1.2.0/tests/test_accelerator.py +113 -0
- etchv_sdk-1.2.0/tests/test_batches.py +424 -0
- {etchv_sdk-1.0.0 → etchv_sdk-1.2.0}/tests/test_client.py +2 -0
- {etchv_sdk-1.0.0 → etchv_sdk-1.2.0}/tests/test_resources.py +2 -2
- etchv_sdk-1.2.0/tests/test_uploads.py +105 -0
- etchv_sdk-1.0.0/PKG-INFO +0 -117
- etchv_sdk-1.0.0/README.md +0 -90
- etchv_sdk-1.0.0/src/etchv/client.py +0 -589
- {etchv_sdk-1.0.0 → etchv_sdk-1.2.0}/.gitignore +0 -0
- {etchv_sdk-1.0.0 → etchv_sdk-1.2.0}/LICENSE +0 -0
- {etchv_sdk-1.0.0 → etchv_sdk-1.2.0}/src/etchv/py.typed +0 -0
- {etchv_sdk-1.0.0 → etchv_sdk-1.2.0}/tests/assets.json +0 -0
- {etchv_sdk-1.0.0 → etchv_sdk-1.2.0}/tests/test_assets.py +0 -0
- {etchv_sdk-1.0.0 → etchv_sdk-1.2.0}/tests/test_async.py +0 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.2.0
|
|
4
|
+
|
|
5
|
+
- Batches: watermark up to 100 files in one call. `submit_batch(items, ...)`
|
|
6
|
+
creates the batch, uploads every file to its signed URL (`upload_concurrency`
|
|
7
|
+
at a time, never with the API key) and starts it; `wait_for_batch` polls
|
|
8
|
+
until the batch is final, honoring `Retry-After`; `iter_batch_results` yields
|
|
9
|
+
each item's verified file or its `error_code`; `download_batch_archive` waits
|
|
10
|
+
for and downloads the zip of a batch created with `archive=True`, and
|
|
11
|
+
`download_batch_archive_to` streams it to a path or file object.
|
|
12
|
+
- `submit_batch_zip` sends files already in one zip (up to 55 MB) with a
|
|
13
|
+
manifest. Also `get_batch`, `cancel_batch` and `list_batches`.
|
|
14
|
+
- New typed models: `Batch`, `BatchEntry`, `BatchItemResult`, `BatchPage` and the
|
|
15
|
+
`BatchItem` / `ZipBatchItem` input types.
|
|
16
|
+
- More than 100 items raise `ValueError` before any request. Batch creation
|
|
17
|
+
retries reuse one `Idempotency-Key`. Any failed upload or start raises
|
|
18
|
+
`BatchSubmitError` with the `batch_id` and `idempotency_key` to resume with;
|
|
19
|
+
resuming skips files that already arrived and raises `GoneError` for an
|
|
20
|
+
expired batch.
|
|
21
|
+
- Signed uploads (batches and `upload_file`) use the client `timeout` per
|
|
22
|
+
network read or write, and retry for as long as the file needs at 128 KiB/s
|
|
23
|
+
on top of it.
|
|
24
|
+
- Batch archives above `ARCHIVE_MAX_BYTES` (1 GiB + 64 MiB) are refused with
|
|
25
|
+
`code` `archive_too_large` and not retried.
|
|
26
|
+
|
|
27
|
+
## 1.1.0
|
|
28
|
+
|
|
29
|
+
- Files above 40 MB (`large_file_threshold`) are uploaded once to a signed URL,
|
|
30
|
+
without the API key, and referenced by `upload_id`; retries reuse the same
|
|
31
|
+
upload. `upload_file` is public.
|
|
32
|
+
- Detection accepts delivered files up to 192 MB; image and PDF detection above
|
|
33
|
+
95 MB runs as a job that the call waits for.
|
etchv_sdk-1.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: etchv-sdk
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: Official Python client for the Etchv forensic watermarking API for images, PDFs and videos
|
|
5
|
+
Project-URL: Homepage, https://etchv.com/docs
|
|
6
|
+
Project-URL: Documentation, https://etchv.com/docs/sdks/python
|
|
7
|
+
Project-URL: Repository, https://github.com/etchv-labs/python-sdk
|
|
8
|
+
Project-URL: Issues, https://github.com/etchv-labs/python-sdk/issues
|
|
9
|
+
Author-email: Etchv Labs <hello@etchv.com>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: api,detection,etchv,forensic watermarking,image,invisible watermark,pdf,sdk,video,watermark,watermarking
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
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.14
|
|
19
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
20
|
+
Classifier: Topic :: Multimedia :: Video
|
|
21
|
+
Classifier: Topic :: Security
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: <3.15,>=3.14
|
|
25
|
+
Requires-Dist: httpx<1,>=0.27
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# Etchv Python SDK
|
|
29
|
+
|
|
30
|
+
Official server-side Python client for [Etchv](https://etchv.com): embed and
|
|
31
|
+
detect invisible forensic watermarks in images, PDFs and videos.
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
pip install etchv-sdk
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Requires Python 3.14. Import it as `etchv`. Fully typed.
|
|
40
|
+
|
|
41
|
+
## Quickstart
|
|
42
|
+
|
|
43
|
+
Create an API key in the [Etchv dashboard](https://etchv.com/dashboard/api-keys)
|
|
44
|
+
and set `ETCHV_API_KEY` on your server. Embedding needs the `watermarks:embed`
|
|
45
|
+
scope, detection `watermarks:detect`, and both use credits.
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import os
|
|
49
|
+
from pathlib import Path
|
|
50
|
+
from etchv import Etchv
|
|
51
|
+
|
|
52
|
+
with Etchv(os.environ["ETCHV_API_KEY"]) as client:
|
|
53
|
+
result = client.embed_image(Path("photo.jpg").read_bytes(),
|
|
54
|
+
{"recipient": "customer-123"}, filename="photo.jpg")
|
|
55
|
+
Path(result.filename).write_bytes(result.image) # write bytes as-is
|
|
56
|
+
|
|
57
|
+
detection = client.detect_image(result.image)
|
|
58
|
+
print(detection.watermarked, detection.watermark_id, detection.confidence)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Use `embed_document` / `detect_document` for PDFs and `embed_video` /
|
|
62
|
+
`detect_video` for H.264 MP4/MOV. Image uploads are limited to 50 MB; PDF and
|
|
63
|
+
video uploads to 20 MB. Detection takes the files Etchv delivered: up to 192 MB
|
|
64
|
+
for images, 64 MB for PDFs and 100 MB for video. Detection recovers a SHA-256
|
|
65
|
+
digest of your data, not the data itself.
|
|
66
|
+
|
|
67
|
+
## Large files
|
|
68
|
+
|
|
69
|
+
Files over 40 MB are uploaded once to a signed URL and then referenced by ID,
|
|
70
|
+
so the request never carries the file. This is automatic in every embed,
|
|
71
|
+
detect and submit method; retries reuse the same upload. Image and PDF
|
|
72
|
+
detection above 95 MB runs as a background job and the call waits for it.
|
|
73
|
+
Change the threshold with `Etchv(..., large_file_threshold=...)`, or upload
|
|
74
|
+
explicitly:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
upload = client.upload_file("detect", delivered, filename="delivered.tiff")
|
|
78
|
+
upload["upload_id"] # send as the upload_id form field instead of file
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## GPU processing
|
|
82
|
+
|
|
83
|
+
Business and Enterprise plans can pass `accelerator="gpu"` to any embed,
|
|
84
|
+
detect or submit method (other plans get `PermissionDeniedError`). GPU
|
|
85
|
+
operations use 3× credits. If no GPU is ready, the job runs on CPU at normal
|
|
86
|
+
credits instead. `result.accelerator` reports the hardware actually used
|
|
87
|
+
(`"gpu"` or `"cpu"`), and job receipts include `accelerator_requested` and
|
|
88
|
+
`accelerator`.
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
result = client.embed_video(video_bytes, {"recipient": "customer-123"}, accelerator="gpu")
|
|
92
|
+
print(result.accelerator) # "gpu", or "cpu" after a fallback
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Async jobs
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
job = client.submit_embed("documents", pdf_bytes, {"recipient": "customer-123"},
|
|
99
|
+
filename="report.pdf", idempotency_key="report-001")
|
|
100
|
+
status = client.get_job(job["request_id"]) # ["status"]: queued, running, retrying, succeeded, failed
|
|
101
|
+
result = client.get_embed_result(job["request_id"]) # waits for the file
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`submit_detection`, `get_job(..., detect=True)` and `get_detection_result` work
|
|
105
|
+
the same way. Reusing an `idempotency_key` with the same input returns the saved
|
|
106
|
+
result (kept 24 hours) without another charge.
|
|
107
|
+
|
|
108
|
+
## Many files at once
|
|
109
|
+
|
|
110
|
+
A batch watermarks up to 100 images, PDFs and videos with one call.
|
|
111
|
+
`submit_batch` creates the batch, uploads every file to its own signed URL
|
|
112
|
+
(four at a time, never with your API key) and starts it. Each item has its own
|
|
113
|
+
forensic data; the filename's extension sets the media type.
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from pathlib import Path
|
|
117
|
+
|
|
118
|
+
batch = client.submit_batch(
|
|
119
|
+
[{"filename": p.name, "file": p, "data": {"recipient": p.stem}} for p in Path("in").glob("*.pdf")],
|
|
120
|
+
archive=True, # also zip every result into one download
|
|
121
|
+
)
|
|
122
|
+
client.wait_for_batch(batch.batch_id, timeout=1800) # honors Retry-After between polls
|
|
123
|
+
|
|
124
|
+
for item in client.iter_batch_results(batch.batch_id):
|
|
125
|
+
if item.ok:
|
|
126
|
+
Path("out", item.result.filename).write_bytes(item.result.image)
|
|
127
|
+
else:
|
|
128
|
+
print(item.filename, item.error_code) # not charged, or refunded
|
|
129
|
+
|
|
130
|
+
client.download_batch_archive_to(batch.batch_id, "out.zip") # streams to disk
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`file` is bytes or a path. A batch costs the same credits per file as single
|
|
134
|
+
requests. Files that never arrive or fail their checks are rejected
|
|
135
|
+
(`upload_not_received`, `invalid_input`, ...) without a charge, and failed
|
|
136
|
+
files are refunded, so one bad file never stops the rest. Results are kept 24
|
|
137
|
+
hours; the archive (with `archive=True`) holds every successful result plus
|
|
138
|
+
`manifest.json` and is limited to 1 GB. `download_batch_archive_to` streams it
|
|
139
|
+
to a path or file object; `download_batch_archive` returns it as bytes.
|
|
140
|
+
|
|
141
|
+
More than 100 items raise `ValueError` before any request; split larger sets
|
|
142
|
+
into several batches. Pass `webhook_id` to get one `watermark.batch.completed`,
|
|
143
|
+
`watermark.batch.failed` or `watermark.batch.cancelled` event when the batch
|
|
144
|
+
ends, and `accelerator` or `storage_destination_id` as for single files.
|
|
145
|
+
Retries reuse the same `idempotency_key` (generated when omitted). If an upload
|
|
146
|
+
or the start still fails, `BatchSubmitError` carries `batch_id` and
|
|
147
|
+
`idempotency_key`; calling `submit_batch` again with that key and the same items
|
|
148
|
+
uploads only the files that have not arrived and starts the batch. A batch not
|
|
149
|
+
started within 24 hours expires, and resuming it raises `GoneError`.
|
|
150
|
+
|
|
151
|
+
Files already in one zip (up to 55 MB) can go in one request; list every
|
|
152
|
+
member:
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
batch = client.submit_batch_zip(Path("in.zip"), [{"filename": "in/a.png", "data": {"recipient": "a"}}])
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Also: `get_batch`, `cancel_batch` (files still waiting are canceled and
|
|
159
|
+
refunded; queued and running files finish) and
|
|
160
|
+
`list_batches(limit=20, before=...)`.
|
|
161
|
+
|
|
162
|
+
## Also included
|
|
163
|
+
|
|
164
|
+
- API key check: `check_api_key()` (no credits used).
|
|
165
|
+
- Assets: `list_assets`, `get_asset`, `update_asset`, `download_asset`,
|
|
166
|
+
`delete_asset`, `delete_assets`.
|
|
167
|
+
- Webhooks: `create_webhook`, `list_webhooks`, `update_webhook`,
|
|
168
|
+
`delete_webhook`, `list_webhook_deliveries`, `redeliver_webhook`; verify
|
|
169
|
+
deliveries with `verify_webhook(raw_body, headers, signing_secret)`, which
|
|
170
|
+
raises `WebhookVerificationError`.
|
|
171
|
+
- Customer storage (S3, GCS, Azure): `create_storage_destination`,
|
|
172
|
+
`list_storage_destinations`, `update_storage_destination`,
|
|
173
|
+
`verify_storage_destination`, `delete_storage_destination`,
|
|
174
|
+
`create_storage_delivery`, `list_storage_deliveries`, `get_storage_delivery`,
|
|
175
|
+
`retry_storage_delivery`, `download_storage_delivery`.
|
|
176
|
+
|
|
177
|
+
## Errors
|
|
178
|
+
|
|
179
|
+
API failures raise `EtchvError` or a subclass such as `AuthenticationError`,
|
|
180
|
+
`PermissionDeniedError`, `ConflictError`, `GoneError`, `RateLimitError` or
|
|
181
|
+
`DeadlineExceededError`. Each carries `status_code`, `detail` and `request_id`
|
|
182
|
+
(quote it to support). Messages never include your API key. Structured API
|
|
183
|
+
errors also set `code` (for example `rate_limited` or `concurrency_limited`),
|
|
184
|
+
`message` (also shown in the error text) and `limit`, and `retry_after` holds
|
|
185
|
+
the `Retry-After` seconds (`None` when not sent).
|
|
186
|
+
Embedding, video detection and job calls retry HTTP 429, 502, 503 and 504
|
|
187
|
+
within `timeout`, waiting for `Retry-After` (up to 5 seconds per wait) on 429.
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
from etchv import EtchvError
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
client.detect_image(image)
|
|
194
|
+
except EtchvError as error:
|
|
195
|
+
print(error.status_code, error.request_id)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Links
|
|
199
|
+
|
|
200
|
+
- Full guide: https://etchv.com/docs/sdks/python
|
|
201
|
+
- API reference: https://etchv.com/docs
|
|
202
|
+
- Support: hello@etchv.com
|
|
203
|
+
|
|
204
|
+
License: MIT (covers this SDK, not the hosted Etchv service).
|
|
205
|
+
|
|
206
|
+
Questions or bug reports: open an issue here or email hello@etchv.com.
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# Etchv Python SDK
|
|
2
|
+
|
|
3
|
+
Official server-side Python client for [Etchv](https://etchv.com): embed and
|
|
4
|
+
detect invisible forensic watermarks in images, PDFs and videos.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
pip install etchv-sdk
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Requires Python 3.14. Import it as `etchv`. Fully typed.
|
|
13
|
+
|
|
14
|
+
## Quickstart
|
|
15
|
+
|
|
16
|
+
Create an API key in the [Etchv dashboard](https://etchv.com/dashboard/api-keys)
|
|
17
|
+
and set `ETCHV_API_KEY` on your server. Embedding needs the `watermarks:embed`
|
|
18
|
+
scope, detection `watermarks:detect`, and both use credits.
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
import os
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from etchv import Etchv
|
|
24
|
+
|
|
25
|
+
with Etchv(os.environ["ETCHV_API_KEY"]) as client:
|
|
26
|
+
result = client.embed_image(Path("photo.jpg").read_bytes(),
|
|
27
|
+
{"recipient": "customer-123"}, filename="photo.jpg")
|
|
28
|
+
Path(result.filename).write_bytes(result.image) # write bytes as-is
|
|
29
|
+
|
|
30
|
+
detection = client.detect_image(result.image)
|
|
31
|
+
print(detection.watermarked, detection.watermark_id, detection.confidence)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Use `embed_document` / `detect_document` for PDFs and `embed_video` /
|
|
35
|
+
`detect_video` for H.264 MP4/MOV. Image uploads are limited to 50 MB; PDF and
|
|
36
|
+
video uploads to 20 MB. Detection takes the files Etchv delivered: up to 192 MB
|
|
37
|
+
for images, 64 MB for PDFs and 100 MB for video. Detection recovers a SHA-256
|
|
38
|
+
digest of your data, not the data itself.
|
|
39
|
+
|
|
40
|
+
## Large files
|
|
41
|
+
|
|
42
|
+
Files over 40 MB are uploaded once to a signed URL and then referenced by ID,
|
|
43
|
+
so the request never carries the file. This is automatic in every embed,
|
|
44
|
+
detect and submit method; retries reuse the same upload. Image and PDF
|
|
45
|
+
detection above 95 MB runs as a background job and the call waits for it.
|
|
46
|
+
Change the threshold with `Etchv(..., large_file_threshold=...)`, or upload
|
|
47
|
+
explicitly:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
upload = client.upload_file("detect", delivered, filename="delivered.tiff")
|
|
51
|
+
upload["upload_id"] # send as the upload_id form field instead of file
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## GPU processing
|
|
55
|
+
|
|
56
|
+
Business and Enterprise plans can pass `accelerator="gpu"` to any embed,
|
|
57
|
+
detect or submit method (other plans get `PermissionDeniedError`). GPU
|
|
58
|
+
operations use 3× credits. If no GPU is ready, the job runs on CPU at normal
|
|
59
|
+
credits instead. `result.accelerator` reports the hardware actually used
|
|
60
|
+
(`"gpu"` or `"cpu"`), and job receipts include `accelerator_requested` and
|
|
61
|
+
`accelerator`.
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
result = client.embed_video(video_bytes, {"recipient": "customer-123"}, accelerator="gpu")
|
|
65
|
+
print(result.accelerator) # "gpu", or "cpu" after a fallback
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Async jobs
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
job = client.submit_embed("documents", pdf_bytes, {"recipient": "customer-123"},
|
|
72
|
+
filename="report.pdf", idempotency_key="report-001")
|
|
73
|
+
status = client.get_job(job["request_id"]) # ["status"]: queued, running, retrying, succeeded, failed
|
|
74
|
+
result = client.get_embed_result(job["request_id"]) # waits for the file
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`submit_detection`, `get_job(..., detect=True)` and `get_detection_result` work
|
|
78
|
+
the same way. Reusing an `idempotency_key` with the same input returns the saved
|
|
79
|
+
result (kept 24 hours) without another charge.
|
|
80
|
+
|
|
81
|
+
## Many files at once
|
|
82
|
+
|
|
83
|
+
A batch watermarks up to 100 images, PDFs and videos with one call.
|
|
84
|
+
`submit_batch` creates the batch, uploads every file to its own signed URL
|
|
85
|
+
(four at a time, never with your API key) and starts it. Each item has its own
|
|
86
|
+
forensic data; the filename's extension sets the media type.
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from pathlib import Path
|
|
90
|
+
|
|
91
|
+
batch = client.submit_batch(
|
|
92
|
+
[{"filename": p.name, "file": p, "data": {"recipient": p.stem}} for p in Path("in").glob("*.pdf")],
|
|
93
|
+
archive=True, # also zip every result into one download
|
|
94
|
+
)
|
|
95
|
+
client.wait_for_batch(batch.batch_id, timeout=1800) # honors Retry-After between polls
|
|
96
|
+
|
|
97
|
+
for item in client.iter_batch_results(batch.batch_id):
|
|
98
|
+
if item.ok:
|
|
99
|
+
Path("out", item.result.filename).write_bytes(item.result.image)
|
|
100
|
+
else:
|
|
101
|
+
print(item.filename, item.error_code) # not charged, or refunded
|
|
102
|
+
|
|
103
|
+
client.download_batch_archive_to(batch.batch_id, "out.zip") # streams to disk
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`file` is bytes or a path. A batch costs the same credits per file as single
|
|
107
|
+
requests. Files that never arrive or fail their checks are rejected
|
|
108
|
+
(`upload_not_received`, `invalid_input`, ...) without a charge, and failed
|
|
109
|
+
files are refunded, so one bad file never stops the rest. Results are kept 24
|
|
110
|
+
hours; the archive (with `archive=True`) holds every successful result plus
|
|
111
|
+
`manifest.json` and is limited to 1 GB. `download_batch_archive_to` streams it
|
|
112
|
+
to a path or file object; `download_batch_archive` returns it as bytes.
|
|
113
|
+
|
|
114
|
+
More than 100 items raise `ValueError` before any request; split larger sets
|
|
115
|
+
into several batches. Pass `webhook_id` to get one `watermark.batch.completed`,
|
|
116
|
+
`watermark.batch.failed` or `watermark.batch.cancelled` event when the batch
|
|
117
|
+
ends, and `accelerator` or `storage_destination_id` as for single files.
|
|
118
|
+
Retries reuse the same `idempotency_key` (generated when omitted). If an upload
|
|
119
|
+
or the start still fails, `BatchSubmitError` carries `batch_id` and
|
|
120
|
+
`idempotency_key`; calling `submit_batch` again with that key and the same items
|
|
121
|
+
uploads only the files that have not arrived and starts the batch. A batch not
|
|
122
|
+
started within 24 hours expires, and resuming it raises `GoneError`.
|
|
123
|
+
|
|
124
|
+
Files already in one zip (up to 55 MB) can go in one request; list every
|
|
125
|
+
member:
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
batch = client.submit_batch_zip(Path("in.zip"), [{"filename": "in/a.png", "data": {"recipient": "a"}}])
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Also: `get_batch`, `cancel_batch` (files still waiting are canceled and
|
|
132
|
+
refunded; queued and running files finish) and
|
|
133
|
+
`list_batches(limit=20, before=...)`.
|
|
134
|
+
|
|
135
|
+
## Also included
|
|
136
|
+
|
|
137
|
+
- API key check: `check_api_key()` (no credits used).
|
|
138
|
+
- Assets: `list_assets`, `get_asset`, `update_asset`, `download_asset`,
|
|
139
|
+
`delete_asset`, `delete_assets`.
|
|
140
|
+
- Webhooks: `create_webhook`, `list_webhooks`, `update_webhook`,
|
|
141
|
+
`delete_webhook`, `list_webhook_deliveries`, `redeliver_webhook`; verify
|
|
142
|
+
deliveries with `verify_webhook(raw_body, headers, signing_secret)`, which
|
|
143
|
+
raises `WebhookVerificationError`.
|
|
144
|
+
- Customer storage (S3, GCS, Azure): `create_storage_destination`,
|
|
145
|
+
`list_storage_destinations`, `update_storage_destination`,
|
|
146
|
+
`verify_storage_destination`, `delete_storage_destination`,
|
|
147
|
+
`create_storage_delivery`, `list_storage_deliveries`, `get_storage_delivery`,
|
|
148
|
+
`retry_storage_delivery`, `download_storage_delivery`.
|
|
149
|
+
|
|
150
|
+
## Errors
|
|
151
|
+
|
|
152
|
+
API failures raise `EtchvError` or a subclass such as `AuthenticationError`,
|
|
153
|
+
`PermissionDeniedError`, `ConflictError`, `GoneError`, `RateLimitError` or
|
|
154
|
+
`DeadlineExceededError`. Each carries `status_code`, `detail` and `request_id`
|
|
155
|
+
(quote it to support). Messages never include your API key. Structured API
|
|
156
|
+
errors also set `code` (for example `rate_limited` or `concurrency_limited`),
|
|
157
|
+
`message` (also shown in the error text) and `limit`, and `retry_after` holds
|
|
158
|
+
the `Retry-After` seconds (`None` when not sent).
|
|
159
|
+
Embedding, video detection and job calls retry HTTP 429, 502, 503 and 504
|
|
160
|
+
within `timeout`, waiting for `Retry-After` (up to 5 seconds per wait) on 429.
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
from etchv import EtchvError
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
client.detect_image(image)
|
|
167
|
+
except EtchvError as error:
|
|
168
|
+
print(error.status_code, error.request_id)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Links
|
|
172
|
+
|
|
173
|
+
- Full guide: https://etchv.com/docs/sdks/python
|
|
174
|
+
- API reference: https://etchv.com/docs
|
|
175
|
+
- Support: hello@etchv.com
|
|
176
|
+
|
|
177
|
+
License: MIT (covers this SDK, not the hosted Etchv service).
|
|
178
|
+
|
|
179
|
+
Questions or bug reports: open an issue here or email hello@etchv.com.
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "etchv-sdk"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.2.0"
|
|
8
8
|
description = "Official Python client for the Etchv forensic watermarking API for images, PDFs and videos"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.14,<3.15"
|
|
@@ -37,4 +37,4 @@ Issues = "https://github.com/etchv-labs/python-sdk/issues"
|
|
|
37
37
|
packages = ["src/etchv"]
|
|
38
38
|
|
|
39
39
|
[tool.hatch.build.targets.sdist]
|
|
40
|
-
include = ["src/etchv", "tests", "README.md", "LICENSE", "pyproject.toml"]
|
|
40
|
+
include = ["src/etchv", "tests", "README.md", "CHANGELOG.md", "LICENSE", "pyproject.toml"]
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
"""Official Python client for the Etchv forensic watermarking API."""
|
|
2
2
|
from .client import (
|
|
3
3
|
__version__,
|
|
4
|
+
Accelerator,
|
|
4
5
|
AuthenticationError,
|
|
6
|
+
Batch,
|
|
7
|
+
BatchEntry,
|
|
8
|
+
BatchItem,
|
|
9
|
+
BatchItemResult,
|
|
10
|
+
BatchItemStatus,
|
|
11
|
+
BatchPage,
|
|
12
|
+
BatchStatus,
|
|
13
|
+
BatchSubmitError,
|
|
5
14
|
ConflictError,
|
|
6
15
|
DeadlineExceededError,
|
|
7
16
|
DetectionResult,
|
|
@@ -15,12 +24,14 @@ from .client import (
|
|
|
15
24
|
PermissionDeniedError,
|
|
16
25
|
RateLimitError,
|
|
17
26
|
WebhookVerificationError,
|
|
27
|
+
ZipBatchItem,
|
|
18
28
|
verify_webhook,
|
|
19
29
|
)
|
|
20
30
|
|
|
21
31
|
__all__ = [
|
|
22
32
|
"__version__",
|
|
23
33
|
"Etchv",
|
|
34
|
+
"Accelerator",
|
|
24
35
|
"EtchvError",
|
|
25
36
|
"AuthenticationError",
|
|
26
37
|
"PaymentRequiredError",
|
|
@@ -33,6 +44,15 @@ __all__ = [
|
|
|
33
44
|
"EmbedResult",
|
|
34
45
|
"DetectionResult",
|
|
35
46
|
"DetectionUnit",
|
|
47
|
+
"Batch",
|
|
48
|
+
"BatchEntry",
|
|
49
|
+
"BatchItem",
|
|
50
|
+
"BatchItemResult",
|
|
51
|
+
"BatchItemStatus",
|
|
52
|
+
"BatchPage",
|
|
53
|
+
"BatchStatus",
|
|
54
|
+
"BatchSubmitError",
|
|
55
|
+
"ZipBatchItem",
|
|
36
56
|
"WebhookVerificationError",
|
|
37
57
|
"verify_webhook",
|
|
38
58
|
]
|