stackshift 1.1.0__py3-none-any.whl → 1.2.0__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/__init__.py CHANGED
@@ -1,4 +1,5 @@
1
1
  from .client import StackShift
2
+ from .ai_credits import AICreditsClient
2
3
  from .assets import ASSET_TRANSFORM_PRESETS, AssetsClient, asset_transform_options, asset_transform_spec, get_asset_transform_preset
3
4
  from .mail import (
4
5
  MailClient,
@@ -17,6 +18,7 @@ from .workload_security import WorkloadSecurityClient
17
18
 
18
19
  __all__ = [
19
20
  "StackShift",
21
+ "AICreditsClient",
20
22
  "ASSET_TRANSFORM_PRESETS",
21
23
  "AssetsClient",
22
24
  "MailClient",
@@ -0,0 +1,24 @@
1
+ from typing import Any
2
+
3
+
4
+ class AICreditsClient:
5
+ def __init__(self, client: Any) -> None:
6
+ self._client = client
7
+
8
+ def balance(self) -> dict[str, Any]:
9
+ return self._client.request("GET", "/billing/ai-credits")
10
+
11
+ def usage(self, **filters: Any) -> dict[str, Any]:
12
+ return self._client.request("GET", "/billing/ai-credits/usage", params=filters)
13
+
14
+ def quote(self, credits: int, currency: str) -> dict[str, Any]:
15
+ return self._client.request("POST", "/billing/ai-credits/top-ups/quote", json={"credits": credits, "currency": currency})
16
+
17
+ def checkout(self, quote_id: str, idempotency_key: str, provider: str) -> dict[str, Any]:
18
+ return self._client.request("POST", "/billing/ai-credits/top-ups/checkout", json={"quote_id": quote_id, "idempotency_key": idempotency_key, "provider": provider})
19
+
20
+ def funding(self) -> dict[str, str]:
21
+ return self._client.request("GET", "/billing/ai-credits/funding")
22
+
23
+ def set_funding(self, product: str, mode: str) -> dict[str, str]:
24
+ return self._client.request("PUT", "/billing/ai-credits/funding", json={"product": product, "mode": mode})
stackshift/assets.py CHANGED
@@ -9,6 +9,7 @@ from urllib.parse import quote
9
9
  from .assets_workflows import AssetWorkflowsMixin
10
10
  from .assets_dam import AssetDAMClient
11
11
  from .assets_video import AssetVideoClient
12
+ from .assets_platform import AssetPlatformClient
12
13
 
13
14
 
14
15
  ASSET_TRANSFORM_PRESETS: list[dict[str, Any]] = [
@@ -56,6 +57,7 @@ class AssetsClient(AssetWorkflowsMixin):
56
57
  self._client = client
57
58
  self.dam = AssetDAMClient(client)
58
59
  self.video = AssetVideoClient(client)
60
+ self.platform = AssetPlatformClient(client)
59
61
 
60
62
  def upload(
61
63
  self,
@@ -0,0 +1,99 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+ from urllib.parse import quote
5
+
6
+
7
+ def _id(value: str) -> str:
8
+ return quote(value, safe="")
9
+
10
+
11
+ class _ScopedTransport:
12
+ def __init__(self, client: Any, space_id: str) -> None:
13
+ self._client = client
14
+ self._space_id = space_id
15
+
16
+ def request(self, method: str, path: str, **kwargs: Any) -> dict[str, Any]:
17
+ headers = dict(kwargs.pop("headers", None) or {})
18
+ if self._space_id:
19
+ headers["X-Asset-Space-ID"] = self._space_id
20
+ return self._client.request(method, path, headers=headers, **kwargs)
21
+
22
+
23
+ class AssetConnectionsClient:
24
+ def __init__(self, client: Any) -> None:
25
+ self._client = client
26
+
27
+ def list(self) -> dict[str, Any]:
28
+ return self._client.request("GET", "/assets/connections")
29
+
30
+ def get(self, connection_id: str) -> dict[str, Any]:
31
+ return self._client.request("GET", f"/assets/connections/{_id(connection_id)}")
32
+
33
+ def create(self, input: dict[str, Any]) -> dict[str, Any]:
34
+ return self._client.request("POST", "/assets/connections", json=input)
35
+
36
+ def update(self, connection_id: str, input: dict[str, Any]) -> dict[str, Any]:
37
+ return self._client.request("PATCH", f"/assets/connections/{_id(connection_id)}", json=input)
38
+
39
+ def verify(self, connection_id: str) -> dict[str, Any]:
40
+ return self._client.request("POST", f"/assets/connections/{_id(connection_id)}/verify")
41
+
42
+ def delete(self, connection_id: str) -> dict[str, Any]:
43
+ return self._client.request("DELETE", f"/assets/connections/{_id(connection_id)}")
44
+
45
+
46
+ class AssetStorageClient:
47
+ def __init__(self, client: Any) -> None:
48
+ self._client = client
49
+
50
+ def configure(self, bucket_id: str, revision: int, input: dict[str, Any]) -> dict[str, Any]:
51
+ return self._client.request("PUT", f"/assets/buckets/{_id(bucket_id)}/storage",
52
+ json=input, headers={"If-Match": f'"{revision}"'})
53
+
54
+ def list_s2_buckets(self) -> dict[str, Any]:
55
+ return self._client.request("GET", "/assets/s2-buckets")
56
+
57
+ def create_s2_bucket(self, input: dict[str, Any]) -> dict[str, Any]:
58
+ return self._client.request("POST", "/assets/s2-buckets", json=input)
59
+
60
+ def relocate(self, bucket_id: str, input: dict[str, Any] | None = None) -> dict[str, Any]:
61
+ return self._client.request("POST", f"/assets/buckets/{_id(bucket_id)}/relocations",
62
+ json=input or {})
63
+
64
+
65
+ class AssetMigrationsClient:
66
+ def __init__(self, client: Any) -> None:
67
+ self._client = client
68
+
69
+ def start(self, input: dict[str, Any]) -> dict[str, Any]:
70
+ return self._client.request("POST", "/assets/migrations", json=input)
71
+
72
+ def list(self, limit: int | None = None) -> dict[str, Any]:
73
+ return self._client.request("GET", "/assets/migrations",
74
+ params={"limit": limit} if limit is not None else None)
75
+
76
+ def get(self, migration_id: str) -> dict[str, Any]:
77
+ return self._client.request("GET", f"/assets/migrations/{_id(migration_id)}")
78
+
79
+ def cancel(self, migration_id: str) -> dict[str, Any]:
80
+ return self._client.request("POST", f"/assets/migrations/{_id(migration_id)}/cancel")
81
+
82
+ def retry_item(self, migration_id: str, item_id: str) -> dict[str, Any]:
83
+ return self._client.request("POST", f"/assets/migrations/{_id(migration_id)}/items/{_id(item_id)}/retry")
84
+
85
+ def report(self, migration_id: str) -> dict[str, Any]:
86
+ return self._client.request("GET", f"/assets/migrations/{_id(migration_id)}/report",
87
+ params={"format": "json"})
88
+
89
+
90
+ class AssetPlatformClient:
91
+ def __init__(self, client: Any, space_id: str = "") -> None:
92
+ self._client = client
93
+ transport = _ScopedTransport(client, space_id)
94
+ self.connections = AssetConnectionsClient(transport)
95
+ self.storage = AssetStorageClient(transport)
96
+ self.migrations = AssetMigrationsClient(transport)
97
+
98
+ def for_space(self, space_id: str) -> AssetPlatformClient:
99
+ return type(self)(self._client, space_id)
stackshift/client.py CHANGED
@@ -26,11 +26,13 @@ class StackShift:
26
26
  self.cdn_base_url = cdn_base_url.rstrip("/")
27
27
  self.session = session or requests.Session()
28
28
 
29
+ from .ai_credits import AICreditsClient
29
30
  from .assets import AssetsClient
30
31
  from .mail import MailClient
31
32
  from .projects import ProjectsClient
32
33
  from .workload_security import WorkloadSecurityClient
33
34
 
35
+ self.ai_credits = AICreditsClient(self)
34
36
  self.projects = ProjectsClient(self)
35
37
  self.assets = AssetsClient(self)
36
38
  self.mail = MailClient(self)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stackshift
3
- Version: 1.1.0
3
+ Version: 1.2.0
4
4
  Summary: Official Python SDK for StackShift.
5
5
  Requires-Python: >=3.9
6
6
  Description-Content-Type: text/markdown
@@ -16,6 +16,27 @@ Official Python SDK for StackShift.
16
16
  pip install stackshift
17
17
  ```
18
18
 
19
+ ## AI credits
20
+
21
+ AI credits are account-scoped. Use the shared client to inspect usage, start a top-up, or select managed billing versus your own provider key:
22
+
23
+ ```python
24
+ from stackshift import StackShift
25
+
26
+ stackshift = StackShift()
27
+ balance = stackshift.ai_credits.balance()
28
+ usage = stackshift.ai_credits.usage(product="assets", limit=25)
29
+ quote = stackshift.ai_credits.quote(1_000, "USD")
30
+ checkout = stackshift.ai_credits.checkout(
31
+ quote["id"],
32
+ "unique-checkout-id",
33
+ "paystack",
34
+ )
35
+ stackshift.ai_credits.set_funding("assets", "managed")
36
+ ```
37
+
38
+ Keep account API keys and checkout creation on your server.
39
+
19
40
  ## Send email
20
41
 
21
42
  ```python
@@ -250,3 +271,14 @@ workspace = video.workspace(asset_id)
250
271
  Mutations carry the supplied revision in `If-Match`. Model preparation and rendering carry an idempotency key; render submission also carries the accepted maximum units. Playback renewal and events authenticate with the session credential, not the account key. Keep these clients on your backend and return grants with `Cache-Control: private, no-store`.
251
272
 
252
273
  See the [Assets SDK guide](https://docs.stackshift.cloud/assets/sdk-media-workflows) and its linked feature guides for complete language examples, inputs, permissions, review transitions and browser callbacks.
274
+
275
+ ## Assets storage and Cloudinary migration
276
+
277
+ `sdk.assets.platform.for_space(space_id)` exposes `connections`, `storage`, and `migrations`. Use a verified Cloudinary connection, run a dry migration first, inspect its report, then start the transfer with the same folder prefix and conflict policy.
278
+
279
+ ```python
280
+ platform = sdk.assets.platform.for_space(space_id)
281
+ preview = platform.migrations.start({"source_connection_id": connection_id,
282
+ "destination_bucket_id": bucket_id, "source_prefix": "campaigns/2026", "dry_run": True})
283
+ report = platform.migrations.report(preview["id"])
284
+ ```
@@ -1,10 +1,12 @@
1
- stackshift/__init__.py,sha256=W4xxuSjitbULpjEhX5SGCE4cNgmcWwEwB1ErPWc4Lmg,1362
2
- stackshift/assets.py,sha256=xCG1fY7WlSZFAEbk0200r8o8308wu4McoxEDeuAk1s8,23191
1
+ stackshift/__init__.py,sha256=6w_s5ELWH4aYV4NXysotLKIX1XdHif0ny_3WGaiGZSY,1425
2
+ stackshift/ai_credits.py,sha256=2u5zGSBo_spHqgYJhtmChaEiBq9jUVaTjR1uprxMpNQ,1142
3
+ stackshift/assets.py,sha256=qjV59NFxQEF6_56GdMZVrN_QadExFbDWbcbiBFYXCt0,23292
3
4
  stackshift/assets_dam.py,sha256=dz0QgSZuQcwSSYoWFqy1pCx3xgMGYZLc9tCcQE0_kYo,6659
4
5
  stackshift/assets_media.py,sha256=S_7TWu6hm5I8WUPMfQsGE9U15mDiYRG4ygXJzWqwbDE,1523
6
+ stackshift/assets_platform.py,sha256=w2Eke0YFXinmtHt1oaZlaiP_J32VymfhVWWLOYrX93M,4125
5
7
  stackshift/assets_video.py,sha256=KBIOTsVspHcMltkELsbTmfu-VB6_36dpYQ7HKTqAa2E,2374
6
8
  stackshift/assets_workflows.py,sha256=6L8_1d9huhfSrKovwP26tfGKc38o0sbSM-75dt__HRE,6950
7
- stackshift/client.py,sha256=gLz0477uMg1e6JQr2kPazpaKgsFfWfcKosGmoPlarDo,3124
9
+ stackshift/client.py,sha256=4osuRDTVvBoZGKKhYBmZkx-oCAezg9zWxB2olXlnqsM,3220
8
10
  stackshift/mail.py,sha256=Ny8aEE846vzanT0B8uqyBB0BGm1p4OmERC0zX_6mGPg,15989
9
11
  stackshift/mail_advanced.py,sha256=sLte3we0Oy3pSuwjl5c4lfELH6gzaSUKug_WZDUB2DE,4363
10
12
  stackshift/mail_billing.py,sha256=n1SMNTHW5lVs7phJhZBiKw6ok-T3kbW0A3-4uF1w6Yg,232
@@ -19,7 +21,7 @@ stackshift/mail_streams.py,sha256=F7iBzHtWhCFw1cTjwPixNJfCvxdjvsDprZQG2A9-NIs,50
19
21
  stackshift/mail_tracking.py,sha256=SRcWTI9Otm5fBIY7nJJ-5qZA13aybUuMdh_PqakBpUg,1575
20
22
  stackshift/projects.py,sha256=oYWaE7xNRR6OXuPEwJH-oFFeOmDA9ozq6jJY09ANIHA,498
21
23
  stackshift/workload_security.py,sha256=5JtJH9fX1kqSY3ipXUa79JslUULWsN5_gyn9tTGfzlA,2941
22
- stackshift-1.1.0.dist-info/METADATA,sha256=n4QI9iFnOJ_wVDhzaeX7g-7DQo1jLHjrQeBo1VqjD9M,9056
23
- stackshift-1.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
24
- stackshift-1.1.0.dist-info/top_level.txt,sha256=qe3uWsu_O9mNtq7bIck9Y8rjTIMrH0kzgAlGFDoRGqs,11
25
- stackshift-1.1.0.dist-info/RECORD,,
24
+ stackshift-1.2.0.dist-info/METADATA,sha256=DZCSjepFeKmKNajeXmUZbNsMaxoG8en0kcxOit3JCPk,10250
25
+ stackshift-1.2.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
26
+ stackshift-1.2.0.dist-info/top_level.txt,sha256=qe3uWsu_O9mNtq7bIck9Y8rjTIMrH0kzgAlGFDoRGqs,11
27
+ stackshift-1.2.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (84.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5