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.
@@ -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.
@@ -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.0.0"
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
  ]