stackshift 0.1.1__py3-none-any.whl → 0.1.2__py3-none-any.whl
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.
- stackshift/assets.py +129 -34
- {stackshift-0.1.1.dist-info → stackshift-0.1.2.dist-info}/METADATA +1 -1
- {stackshift-0.1.1.dist-info → stackshift-0.1.2.dist-info}/RECORD +5 -5
- {stackshift-0.1.1.dist-info → stackshift-0.1.2.dist-info}/WHEEL +1 -1
- {stackshift-0.1.1.dist-info → stackshift-0.1.2.dist-info}/top_level.txt +0 -0
stackshift/assets.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import hashlib
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
from typing import Any, BinaryIO
|
|
6
7
|
from urllib.parse import quote
|
|
@@ -66,7 +67,7 @@ class AssetsClient:
|
|
|
66
67
|
"key": key,
|
|
67
68
|
"folder": folder,
|
|
68
69
|
"visibility": visibility,
|
|
69
|
-
"
|
|
70
|
+
"cache_control": cache_control,
|
|
70
71
|
"metadata": json.dumps(metadata) if metadata else None,
|
|
71
72
|
})
|
|
72
73
|
|
|
@@ -77,21 +78,25 @@ class AssetsClient:
|
|
|
77
78
|
def get(self, asset_id: str) -> dict[str, Any]:
|
|
78
79
|
return self._client.request("GET", f"/assets/{asset_id}")
|
|
79
80
|
|
|
80
|
-
def delete(self, asset_id: str) -> dict[str, Any]:
|
|
81
|
-
|
|
81
|
+
def delete(self, asset_id: str, revision: int, *, idempotency_key: str | None = None) -> dict[str, Any]:
|
|
82
|
+
headers = {"If-Match": f'"{revision}"'}
|
|
83
|
+
if idempotency_key:
|
|
84
|
+
headers["Idempotency-Key"] = idempotency_key
|
|
85
|
+
return self._client.request("DELETE", f"/assets/{asset_id}", headers=headers)
|
|
82
86
|
|
|
83
87
|
def replace(
|
|
84
88
|
self,
|
|
85
89
|
asset_id: str,
|
|
90
|
+
revision: int,
|
|
86
91
|
file: str | Path | BinaryIO,
|
|
87
92
|
*,
|
|
88
93
|
cache_control: str | None = None,
|
|
89
94
|
metadata: dict[str, Any] | None = None,
|
|
90
95
|
) -> dict[str, Any]:
|
|
91
96
|
return self._multipart("PUT", f"/assets/{asset_id}/replace", file, {
|
|
92
|
-
"
|
|
97
|
+
"cache_control": cache_control,
|
|
93
98
|
"metadata": json.dumps(metadata) if metadata else None,
|
|
94
|
-
})
|
|
99
|
+
}, headers={"If-Match": f'"{revision}"'})
|
|
95
100
|
|
|
96
101
|
def signed_url(
|
|
97
102
|
self,
|
|
@@ -101,16 +106,18 @@ class AssetsClient:
|
|
|
101
106
|
max_downloads: int | None = None,
|
|
102
107
|
) -> dict[str, Any]:
|
|
103
108
|
return self._client.request("POST", f"/assets/{asset_id}/signed-url", json={
|
|
104
|
-
"
|
|
105
|
-
"
|
|
109
|
+
"expires_in": expires_in,
|
|
110
|
+
"max_downloads": max_downloads,
|
|
106
111
|
})
|
|
107
112
|
|
|
108
113
|
def signed_upload_url(self, **payload: Any) -> dict[str, Any]:
|
|
109
114
|
return self.create_upload_session(**payload)
|
|
110
115
|
|
|
111
116
|
def create_upload_session(self, **payload: Any) -> dict[str, Any]:
|
|
117
|
+
idempotency_key = payload.pop("idempotency_key", None)
|
|
112
118
|
clean = {key: value for key, value in payload.items() if value not in (None, "")}
|
|
113
|
-
|
|
119
|
+
headers = {"Idempotency-Key": idempotency_key} if idempotency_key else None
|
|
120
|
+
return self._client.request("POST", "/assets/upload-sessions", json=clean, headers=headers)
|
|
114
121
|
|
|
115
122
|
def create_chunked_upload_session(self, **payload: Any) -> dict[str, Any]:
|
|
116
123
|
return self.create_upload_session(mode="chunked", **payload)
|
|
@@ -126,10 +133,11 @@ class AssetsClient:
|
|
|
126
133
|
origin = self._client.base_url.removesuffix("/api/v1")
|
|
127
134
|
target = f"{origin}/uploads/{quote(upload_url_or_token)}"
|
|
128
135
|
|
|
136
|
+
raw = chunk if isinstance(chunk, bytes) else chunk.read()
|
|
129
137
|
response = self._client.session.put(
|
|
130
138
|
f"{target}/parts/{part_number}",
|
|
131
|
-
data=
|
|
132
|
-
headers={"User-Agent": "StackShift-Python/0.1"},
|
|
139
|
+
data=raw,
|
|
140
|
+
headers={"User-Agent": "StackShift-Python/0.1", "X-Content-SHA256": hashlib.sha256(raw).hexdigest()},
|
|
133
141
|
)
|
|
134
142
|
payload = response.json() if response.content else None
|
|
135
143
|
if response.status_code < 200 or response.status_code >= 300:
|
|
@@ -146,6 +154,27 @@ class AssetsClient:
|
|
|
146
154
|
def cancel_upload_session(self, session_id: str) -> dict[str, Any]:
|
|
147
155
|
return self._client.request("DELETE", f"/assets/upload-sessions/{session_id}")
|
|
148
156
|
|
|
157
|
+
def list_buckets(self) -> dict[str, Any]:
|
|
158
|
+
return self._client.request("GET", "/assets/buckets")
|
|
159
|
+
|
|
160
|
+
def create_bucket(self, **payload: Any) -> dict[str, Any]:
|
|
161
|
+
return self._client.request("POST", "/assets/buckets", json=payload)
|
|
162
|
+
|
|
163
|
+
def update_bucket(self, bucket_id: str, revision: int, **payload: Any) -> dict[str, Any]:
|
|
164
|
+
return self._client.request("PUT", f"/assets/buckets/{bucket_id}", json=payload, headers={"If-Match": f'"{revision}"'})
|
|
165
|
+
|
|
166
|
+
def delete_bucket(self, bucket_id: str, revision: int) -> dict[str, Any]:
|
|
167
|
+
return self._client.request("DELETE", f"/assets/buckets/{bucket_id}", headers={"If-Match": f'"{revision}"'})
|
|
168
|
+
|
|
169
|
+
def set_tags(self, asset_id: str, revision: int, tags: list[str]) -> dict[str, Any]:
|
|
170
|
+
return self._client.request("PUT", f"/assets/{asset_id}/tags", json={"tags": tags}, headers={"If-Match": f'"{revision}"'})
|
|
171
|
+
|
|
172
|
+
def patch_metadata(self, asset_id: str, revision: int, metadata: dict[str, Any]) -> dict[str, Any]:
|
|
173
|
+
return self._client.request("PATCH", f"/assets/{asset_id}/metadata", json={"metadata": metadata}, headers={"If-Match": f'"{revision}"'})
|
|
174
|
+
|
|
175
|
+
def patch(self, asset_id: str, revision: int, **payload: Any) -> dict[str, Any]:
|
|
176
|
+
return self._client.request("PATCH", f"/assets/{asset_id}", json=payload, headers={"If-Match": f'"{revision}"'})
|
|
177
|
+
|
|
149
178
|
def url(self, asset_id: str, **options: Any) -> str:
|
|
150
179
|
spec = _transform_spec(options)
|
|
151
180
|
return f"{self._client.cdn_base_url}/assets/{quote(asset_id)}/tr/{quote(spec)}"
|
|
@@ -153,18 +182,6 @@ class AssetsClient:
|
|
|
153
182
|
def named_url(self, asset_id: str, name: str) -> str:
|
|
154
183
|
return f"{self._client.cdn_base_url}/t/{quote(name)}/assets/{quote(asset_id)}"
|
|
155
184
|
|
|
156
|
-
def video_url(
|
|
157
|
-
self,
|
|
158
|
-
asset_id: str,
|
|
159
|
-
kind: str,
|
|
160
|
-
profile: str = "default",
|
|
161
|
-
token: str | None = None,
|
|
162
|
-
) -> str:
|
|
163
|
-
prefix = "/private/assets" if token else "/assets"
|
|
164
|
-
base = f"{self._client.cdn_base_url}{prefix}/{quote(asset_id)}"
|
|
165
|
-
result = f"{base}/video/{quote(kind)}/{quote(profile)}"
|
|
166
|
-
return f"{result}?token={quote(token)}" if token else result
|
|
167
|
-
|
|
168
185
|
def version_url(self, asset_id: str, version_id: str, token: str | None = None) -> str:
|
|
169
186
|
prefix = "/private/assets" if token else "/assets"
|
|
170
187
|
result = f"{self._client.cdn_base_url}{prefix}/{quote(asset_id)}/versions/{quote(version_id)}"
|
|
@@ -175,26 +192,35 @@ class AssetsClient:
|
|
|
175
192
|
result = f"{self._client.cdn_base_url}{prefix}/{quote(asset_id)}/branches/{quote(branch)}"
|
|
176
193
|
return f"{result}?token={quote(token)}" if token else result
|
|
177
194
|
|
|
195
|
+
def video_url(self, asset_id: str, kind: str, profile: str = "default", token: str | None = None) -> str:
|
|
196
|
+
prefix = "/private/assets" if token else "/assets"
|
|
197
|
+
result = f"{self._client.cdn_base_url}{prefix}/{quote(asset_id)}/video/{quote(kind)}/{quote(profile)}"
|
|
198
|
+
return f"{result}?token={quote(token)}" if token else result
|
|
199
|
+
|
|
178
200
|
def ai(self, asset_id: str) -> dict[str, Any]:
|
|
179
201
|
return self._client.request("GET", f"/assets/{asset_id}/ai")
|
|
180
202
|
|
|
181
|
-
def request_ai_analyze(self, asset_id: str) -> dict[str, Any]:
|
|
182
|
-
return self.
|
|
203
|
+
def request_ai_analyze(self, asset_id: str, *, idempotency_key: str | None = None) -> dict[str, Any]:
|
|
204
|
+
return self._request_ai_job(f"/assets/{asset_id}/ai/analyze", idempotency_key)
|
|
183
205
|
|
|
184
|
-
def request_moderation(self, asset_id: str) -> dict[str, Any]:
|
|
185
|
-
return self.
|
|
206
|
+
def request_moderation(self, asset_id: str, *, idempotency_key: str | None = None) -> dict[str, Any]:
|
|
207
|
+
return self._request_ai_job(f"/assets/{asset_id}/moderation", idempotency_key)
|
|
186
208
|
|
|
187
209
|
def transcript(self, asset_id: str) -> dict[str, Any]:
|
|
188
210
|
return self._client.request("GET", f"/assets/{asset_id}/transcript")
|
|
189
211
|
|
|
190
|
-
def request_transcript(self, asset_id: str) -> dict[str, Any]:
|
|
191
|
-
return self.
|
|
212
|
+
def request_transcript(self, asset_id: str, *, idempotency_key: str | None = None) -> dict[str, Any]:
|
|
213
|
+
return self._request_ai_job(f"/assets/{asset_id}/transcript", idempotency_key)
|
|
214
|
+
|
|
215
|
+
def request_smart_crop(self, asset_id: str, *, idempotency_key: str | None = None) -> dict[str, Any]:
|
|
216
|
+
return self._request_ai_job(f"/assets/{asset_id}/smart-crop", idempotency_key)
|
|
192
217
|
|
|
193
|
-
def
|
|
194
|
-
return self.
|
|
218
|
+
def request_background_removal(self, asset_id: str, *, idempotency_key: str | None = None) -> dict[str, Any]:
|
|
219
|
+
return self._request_ai_job(f"/assets/{asset_id}/background-remove", idempotency_key)
|
|
195
220
|
|
|
196
|
-
def
|
|
197
|
-
|
|
221
|
+
def _request_ai_job(self, path: str, idempotency_key: str | None) -> dict[str, Any]:
|
|
222
|
+
headers = {"Idempotency-Key": idempotency_key} if idempotency_key else None
|
|
223
|
+
return self._client.request("POST", path, headers=headers)
|
|
198
224
|
|
|
199
225
|
def versions(self, asset_id: str) -> dict[str, Any]:
|
|
200
226
|
return self._client.request("GET", f"/assets/{asset_id}/versions")
|
|
@@ -265,7 +291,74 @@ class AssetsClient:
|
|
|
265
291
|
clean = {key: value for key, value in options.items() if value not in (None, "")}
|
|
266
292
|
return self._client.request("POST", f"/assets/{asset_id}/transform-url", json=clean)
|
|
267
293
|
|
|
268
|
-
def
|
|
294
|
+
def get_ai_config(self) -> dict[str, Any]:
|
|
295
|
+
return self._client.request("GET", "/assets/ai/config")
|
|
296
|
+
|
|
297
|
+
def update_ai_config(self, revision: int, *, enabled_actions: list[str], monthly_spend_cap_micros: int) -> dict[str, Any]:
|
|
298
|
+
return self._client.request("PUT", "/assets/ai/config", headers={"If-Match": f'"{revision}"'}, json={
|
|
299
|
+
"enabled_actions": enabled_actions,
|
|
300
|
+
"monthly_spend_cap_micros": monthly_spend_cap_micros,
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
def job(self, job_id: str) -> dict[str, Any]:
|
|
304
|
+
return self._client.request("GET", f"/assets/jobs/{quote(job_id)}")
|
|
305
|
+
|
|
306
|
+
def cancel_job(self, job_id: str) -> dict[str, Any]:
|
|
307
|
+
return self._client.request("POST", f"/assets/jobs/{quote(job_id)}/cancel")
|
|
308
|
+
|
|
309
|
+
def create_lifecycle_rule(self, **payload: Any) -> dict[str, Any]:
|
|
310
|
+
return self._client.request("POST", "/assets/lifecycle-rules", json=payload)
|
|
311
|
+
|
|
312
|
+
def list_lifecycle_rules(self) -> dict[str, Any]:
|
|
313
|
+
return self._client.request("GET", "/assets/lifecycle-rules")
|
|
314
|
+
|
|
315
|
+
def delete_lifecycle_rule(self, rule_id: str) -> dict[str, Any]:
|
|
316
|
+
return self._client.request("DELETE", f"/assets/lifecycle-rules/{quote(rule_id)}")
|
|
317
|
+
|
|
318
|
+
def create_domain(self, domain: str) -> dict[str, Any]:
|
|
319
|
+
return self._client.request("POST", "/assets/domains", json={"domain": domain})
|
|
320
|
+
|
|
321
|
+
def list_domains(self) -> dict[str, Any]:
|
|
322
|
+
return self._client.request("GET", "/assets/domains")
|
|
323
|
+
|
|
324
|
+
def verify_domain(self, domain_id: str) -> dict[str, Any]:
|
|
325
|
+
return self._client.request("POST", f"/assets/domains/{quote(domain_id)}/verify")
|
|
326
|
+
|
|
327
|
+
def delete_domain(self, domain_id: str) -> dict[str, Any]:
|
|
328
|
+
return self._client.request("DELETE", f"/assets/domains/{quote(domain_id)}")
|
|
329
|
+
|
|
330
|
+
def analytics(self) -> dict[str, Any]:
|
|
331
|
+
return self._client.request("GET", "/assets/analytics")
|
|
332
|
+
|
|
333
|
+
def usage_summary(self) -> dict[str, Any]:
|
|
334
|
+
return self._client.request("GET", "/assets/summary")
|
|
335
|
+
|
|
336
|
+
def events(self, limit: int = 50) -> dict[str, Any]:
|
|
337
|
+
return self._client.request("GET", "/assets/events", params={"limit": limit})
|
|
338
|
+
|
|
339
|
+
def create_webhook(self, url: str, event_types: list[str] | None = None) -> dict[str, Any]:
|
|
340
|
+
return self._client.request("POST", "/assets/webhooks", json={"url": url, "event_types": event_types or ["asset.*"]})
|
|
341
|
+
|
|
342
|
+
def list_webhooks(self) -> dict[str, Any]:
|
|
343
|
+
return self._client.request("GET", "/assets/webhooks")
|
|
344
|
+
|
|
345
|
+
def delete_webhook(self, webhook_id: str) -> dict[str, Any]:
|
|
346
|
+
return self._client.request("DELETE", f"/assets/webhooks/{quote(webhook_id)}")
|
|
347
|
+
|
|
348
|
+
def list_webhook_deliveries(self, webhook_id: str, limit: int = 50) -> dict[str, Any]:
|
|
349
|
+
return self._client.request("GET", f"/assets/webhooks/{quote(webhook_id)}/deliveries", params={"limit": limit})
|
|
350
|
+
|
|
351
|
+
def retry_webhook_delivery(self, delivery_id: str) -> dict[str, Any]:
|
|
352
|
+
return self._client.request("POST", f"/assets/webhook-deliveries/{quote(delivery_id)}/retry")
|
|
353
|
+
|
|
354
|
+
def purge(self, asset_id: str) -> dict[str, Any]:
|
|
355
|
+
return self._client.request("POST", f"/assets/{quote(asset_id)}/purge")
|
|
356
|
+
|
|
357
|
+
def bulk(self, *, action: str, items: list[dict[str, Any]], idempotency_key: str | None = None, **payload: Any) -> dict[str, Any]:
|
|
358
|
+
headers = {"Idempotency-Key": idempotency_key} if idempotency_key else None
|
|
359
|
+
return self._client.request("POST", "/assets/bulk", json={"action": action, "items": items, **payload}, headers=headers)
|
|
360
|
+
|
|
361
|
+
def _multipart(self, method: str, suffix: str, file: str | Path | BinaryIO, fields: dict[str, Any], headers: dict[str, str] | None = None) -> dict[str, Any]:
|
|
269
362
|
should_close = False
|
|
270
363
|
if isinstance(file, (str, Path)):
|
|
271
364
|
handle = Path(file).open("rb")
|
|
@@ -277,7 +370,7 @@ class AssetsClient:
|
|
|
277
370
|
|
|
278
371
|
try:
|
|
279
372
|
data = {key: value for key, value in fields.items() if value not in (None, "")}
|
|
280
|
-
return self._client.request(method, suffix, data=data, files={"file": (filename, handle)})
|
|
373
|
+
return self._client.request(method, suffix, data=data, files={"file": (filename, handle)}, headers=headers)
|
|
281
374
|
finally:
|
|
282
375
|
if should_close:
|
|
283
376
|
handle.close()
|
|
@@ -292,4 +385,6 @@ def _transform_spec(options: dict[str, Any]) -> str:
|
|
|
292
385
|
parts.append(f"c_{options.get('crop') or 'fit'}")
|
|
293
386
|
parts.append(f"f_{options.get('format') or 'auto'}")
|
|
294
387
|
parts.append(f"q_{options.get('quality') or 'auto'}")
|
|
388
|
+
parts.append(f"g_{options.get('gravity') or 'centre'}")
|
|
389
|
+
parts.append(f"a_{options.get('animation') or 'first'}")
|
|
295
390
|
return ",".join(parts)
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
stackshift/__init__.py,sha256=e5QZUzE6m_195Cvvawyv8p_M18eNkQzW6sBgBn3SlRo,1008
|
|
2
|
-
stackshift/assets.py,sha256=
|
|
2
|
+
stackshift/assets.py,sha256=qRzL9BNZSneNBc3es_vLsY4_hh_r3zOfdO_rAKQq5LU,22949
|
|
3
3
|
stackshift/client.py,sha256=AcJivv4VRCQ756MRrxCrLi0Q4in-4np0Re8ceQSTF1Y,3000
|
|
4
4
|
stackshift/mail.py,sha256=vegCniMcWBvHtz_UGAfNfVxFYTety8H3KKFQsWfKZSs,13462
|
|
5
5
|
stackshift/mail_advanced.py,sha256=-lzajXqlrkR8qA4YpRX5Sv3VwcEQITtKfIXbWwjRRQE,3533
|
|
6
6
|
stackshift/mail_phase7.py,sha256=Ud2fyKwt-vpaw7vgEayLw5q9tiDIF-fkfTBaoGJMJBI,5377
|
|
7
7
|
stackshift/mail_reputation.py,sha256=mPvPkZeqcQc5808PhBK7gYsgf4Fk_U19nRxSsJ60MGU,1112
|
|
8
8
|
stackshift/projects.py,sha256=oYWaE7xNRR6OXuPEwJH-oFFeOmDA9ozq6jJY09ANIHA,498
|
|
9
|
-
stackshift-0.1.
|
|
10
|
-
stackshift-0.1.
|
|
11
|
-
stackshift-0.1.
|
|
12
|
-
stackshift-0.1.
|
|
9
|
+
stackshift-0.1.2.dist-info/METADATA,sha256=GKbdBmBmZWOUs8DTe-amYxZNbplrh7pAlkaRtkBWQ14,152
|
|
10
|
+
stackshift-0.1.2.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
11
|
+
stackshift-0.1.2.dist-info/top_level.txt,sha256=qe3uWsu_O9mNtq7bIck9Y8rjTIMrH0kzgAlGFDoRGqs,11
|
|
12
|
+
stackshift-0.1.2.dist-info/RECORD,,
|
|
File without changes
|