stackshift 1.3.0__py3-none-any.whl → 1.3.1__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
@@ -21,6 +21,7 @@ from .mail_mailboxes import (
21
21
  MailboxesClient,
22
22
  MailboxRoutesClient,
23
23
  )
24
+ from .partner import PartnerClient
24
25
  from .projects import ProjectsClient
25
26
  from .workload_security import WorkloadSecurityClient
26
27
 
@@ -49,6 +50,7 @@ __all__ = [
49
50
  "MailboxImportsClient",
50
51
  "MailboxesClient",
51
52
  "MailboxRoutesClient",
53
+ "PartnerClient",
52
54
  "ProjectsClient",
53
55
  "WorkloadSecurityClient",
54
56
  "asset_transform_options",
stackshift/assets.py CHANGED
@@ -230,6 +230,23 @@ class AssetsClient(AssetWorkflowsMixin):
230
230
  headers = {"Idempotency-Key": idempotency_key} if idempotency_key else None
231
231
  return self._client.request("POST", path, headers=headers)
232
232
 
233
+ def usage(self, asset_id: str) -> dict[str, Any]:
234
+ return self._client.request("GET", f"/assets/{quote(asset_id, safe='')}/usage")
235
+
236
+ def hold(self, asset_id: str) -> dict[str, Any]:
237
+ return self._client.request("GET", f"/assets/{quote(asset_id, safe='')}/hold")
238
+
239
+ def create_reupload_request(
240
+ self, asset_id: str, *, email: str | None = None, message: str | None = None
241
+ ) -> dict[str, Any]:
242
+ payload = {key: value for key, value in {"email": email, "message": message}.items() if value}
243
+ return self._client.request(
244
+ "POST", f"/assets/{quote(asset_id, safe='')}/reupload-requests", json=payload
245
+ )
246
+
247
+ def reupload_requests(self, asset_id: str) -> dict[str, Any]:
248
+ return self._client.request("GET", f"/assets/{quote(asset_id, safe='')}/reupload-requests")
249
+
233
250
  def versions(self, asset_id: str) -> dict[str, Any]:
234
251
  return self._client.request("GET", f"/assets/{asset_id}/versions")
235
252
 
@@ -17,6 +17,14 @@ class AssetWorkflowsMixin:
17
17
  json={"name": name, "definition": definition, "eager": eager},
18
18
  )
19
19
 
20
+ def estimate_transform(
21
+ self, asset_id: str, version_id: str, definition: dict[str, Any]
22
+ ) -> dict[str, Any]:
23
+ return self._client.request(
24
+ "POST", f"/assets/{quote(asset_id, safe='')}/transforms/estimate",
25
+ json={"version_id": version_id, "definition": definition},
26
+ )
27
+
20
28
  def materialize_transform(
21
29
  self,
22
30
  asset_id: str,
stackshift/client.py CHANGED
@@ -29,6 +29,7 @@ class StackShift:
29
29
  from .ai_credits import AICreditsClient
30
30
  from .assets import AssetsClient
31
31
  from .mail import MailClient
32
+ from .partner import PartnerClient
32
33
  from .projects import ProjectsClient
33
34
  from .workload_security import WorkloadSecurityClient
34
35
 
@@ -36,6 +37,7 @@ class StackShift:
36
37
  self.projects = ProjectsClient(self)
37
38
  self.assets = AssetsClient(self)
38
39
  self.mail = MailClient(self)
40
+ self.partner = PartnerClient(self)
39
41
  self.workload_security = WorkloadSecurityClient(self)
40
42
 
41
43
  def request(self, method: str, path: str, **kwargs: Any) -> Any:
stackshift/mail.py CHANGED
@@ -311,12 +311,14 @@ class MailSuppressionsClient:
311
311
  *,
312
312
  email: str | None = None,
313
313
  reason: str | None = None,
314
+ brand_id: str | None = None,
314
315
  limit: int | None = None,
315
316
  cursor: str | None = None,
316
317
  ) -> dict[str, Any]:
317
318
  params = {
318
319
  "email": email,
319
320
  "reason": reason,
321
+ "brandId": brand_id,
320
322
  "limit": limit,
321
323
  "cursor": cursor,
322
324
  }
@@ -7,3 +7,6 @@ class MailBillingClient:
7
7
 
8
8
  def summary(self) -> dict[str, Any]:
9
9
  return self._client.mail_request("GET", "/mail/billing")
10
+
11
+ def period_usage(self, period: str) -> dict[str, Any]:
12
+ return self._client.mail_request("GET", "/mail/billing", params={"period": period})
stackshift/mail_brands.py CHANGED
@@ -28,7 +28,7 @@ class MailBrandsClient:
28
28
 
29
29
 
30
30
  def _brand_fields(fields: dict[str, Any]) -> dict[str, Any]:
31
- names = {"logo_url": "logoUrl", "sending_domain_id": "sendingDomainId", "default_from": "defaultFrom", "reply_to": "replyTo", "postal_address": "postalAddress", "website": "website", "support_email": "supportEmail", "editable_fields": "editableFields"}
31
+ names = {"logo_url": "logoUrl", "sending_domain_id": "sendingDomainId", "default_from": "defaultFrom", "reply_to": "replyTo", "postal_address": "postalAddress", "website": "website", "support_email": "supportEmail", "editable_fields": "editableFields", "merchant_ref": "merchantRef"}
32
32
  unknown = fields.keys() - names.keys() - set(names.values())
33
33
  if unknown:
34
34
  raise ValueError("Unknown brand fields: " + ", ".join(sorted(unknown)))
@@ -12,8 +12,9 @@ class MailAudiencesClient:
12
12
  payload = {"brandId": brand_id, "name": name, "description": description}
13
13
  return self._client.mail_request("POST", "/mail/audiences", json={key: value for key, value in payload.items() if value is not None})
14
14
 
15
- def list(self) -> dict[str, Any]:
16
- return self._client.mail_request("GET", "/mail/audiences")
15
+ def list(self, *, brand_id: str | None = None) -> dict[str, Any]:
16
+ params = {"brandId": brand_id} if brand_id else None
17
+ return self._client.mail_request("GET", "/mail/audiences", params=params)
17
18
 
18
19
  def get(self, audience_id: str) -> dict[str, Any]:
19
20
  return self._client.mail_request("GET", f"/mail/audiences/{quote(audience_id, safe='')}")
@@ -83,8 +84,9 @@ class MailCampaignsClient:
83
84
  }
84
85
  return self._client.mail_request("POST", "/mail/campaigns", json={key: value for key, value in payload.items() if value is not None})
85
86
 
86
- def list(self) -> dict[str, Any]:
87
- return self._client.mail_request("GET", "/mail/campaigns")
87
+ def list(self, *, brand_id: str | None = None) -> dict[str, Any]:
88
+ params = {"brandId": brand_id} if brand_id else None
89
+ return self._client.mail_request("GET", "/mail/campaigns", params=params)
88
90
 
89
91
  def get(self, campaign_id: str) -> dict[str, Any]:
90
92
  return self._client.mail_request("GET", f"/mail/campaigns/{quote(campaign_id)}")
@@ -79,6 +79,15 @@ class MailWebhooksClient:
79
79
  def rotate_secret(self, webhook_id: str) -> dict[str, Any]:
80
80
  return self._client.mail_request("POST", f"/mail/webhooks/{quote(webhook_id)}/rotate-secret")
81
81
 
82
+ def reveal_secret(self, webhook_id: str) -> dict[str, Any]:
83
+ return self._client.mail_request("POST", f"/mail/webhooks/{quote(webhook_id)}/reveal-secret")
84
+
85
+ def diagnosis(self, webhook_id: str) -> dict[str, Any]:
86
+ return self._client.mail_request("GET", f"/mail/webhooks/{quote(webhook_id)}/diagnosis")
87
+
88
+ def diagnose(self, webhook_id: str) -> dict[str, Any]:
89
+ return self._client.mail_request("POST", f"/mail/webhooks/{quote(webhook_id)}/diagnosis")
90
+
82
91
  def delete(self, webhook_id: str) -> None:
83
92
  self._client.mail_request("DELETE", f"/mail/webhooks/{quote(webhook_id)}")
84
93
 
stackshift/partner.py ADDED
@@ -0,0 +1,93 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+ from urllib.parse import quote
5
+
6
+
7
+ class PartnerClient:
8
+ def __init__(self, client: Any) -> None:
9
+ self._client = client
10
+
11
+ def overview(self) -> dict[str, Any]:
12
+ return self._client.request("GET", "/partner")
13
+
14
+ def update_settings(self, *, complaint_pause_rate: float, bounce_pause_rate: float) -> dict[str, Any]:
15
+ return self._client.request(
16
+ "PUT", "/partner/settings",
17
+ json={"complaintPauseRate": complaint_pause_rate, "bouncePauseRate": bounce_pause_rate},
18
+ )
19
+
20
+ def purchase_domain(
21
+ self,
22
+ *,
23
+ domain: str,
24
+ years: int,
25
+ merchant_ref: str,
26
+ registrant: dict[str, Any],
27
+ brand: dict[str, Any],
28
+ replies: dict[str, Any],
29
+ idempotency_key: str,
30
+ currency: str | None = None,
31
+ ) -> dict[str, Any]:
32
+ body = {"domain": domain, "years": years, "merchantRef": merchant_ref, "registrant": registrant, "brand": brand, "replies": replies}
33
+ if currency is not None:
34
+ body["currency"] = currency
35
+ return self._client.request("POST", "/partner/domains", headers={"Idempotency-Key": idempotency_key}, json=body)
36
+
37
+ def list_domains(
38
+ self,
39
+ *,
40
+ merchant_ref: str | None = None,
41
+ q: str | None = None,
42
+ attention: bool | None = None,
43
+ limit: int | None = None,
44
+ offset: int | None = None,
45
+ ) -> dict[str, Any]:
46
+ params = {"merchantRef": merchant_ref, "q": q, "attention": "true" if attention else None, "limit": limit, "offset": offset}
47
+ return self._client.request("GET", "/partner/domains", params={key: value for key, value in params.items() if value is not None})
48
+
49
+ def get_domain(self, domain_id: str) -> dict[str, Any]:
50
+ return self._client.request("GET", f"/partner/domains/{quote(domain_id, safe='')}")
51
+
52
+ def retry_domain(self, domain_id: str) -> dict[str, Any]:
53
+ return self._client.request("POST", f"/partner/domains/{quote(domain_id, safe='')}/retry")
54
+
55
+ def transfer_out(self, domain_id: str) -> dict[str, Any]:
56
+ return self._client.request("POST", f"/partner/domains/{quote(domain_id, safe='')}/transfer-out")
57
+
58
+ def get_diagnosis(self, domain_id: str) -> dict[str, Any]:
59
+ return self._client.request("GET", f"/partner/domains/{quote(domain_id, safe='')}/diagnosis")
60
+
61
+ def restore_dns(self, domain_id: str) -> dict[str, Any]:
62
+ return self._client.request("POST", f"/partner/domains/{quote(domain_id, safe='')}/restore-dns")
63
+
64
+ def resume_brand(self, brand_id: str) -> dict[str, Any]:
65
+ return self._client.request("POST", f"/partner/brands/{quote(brand_id, safe='')}/resume")
66
+
67
+ def get_complaints(self, brand_id: str) -> dict[str, Any]:
68
+ return self._client.request("GET", f"/partner/brands/{quote(brand_id, safe='')}/complaints")
69
+
70
+ def list_complaint_sources(self, brand_id: str) -> list[dict[str, Any]]:
71
+ return self._client.request("GET", f"/partner/brands/{quote(brand_id, safe='')}/complaints/sources")
72
+
73
+ def suppress_never_opened(self, brand_id: str, *, audience_id: str) -> dict[str, Any]:
74
+ return self._client.request(
75
+ "POST", f"/partner/brands/{quote(brand_id, safe='')}/suppressions/bulk",
76
+ json={"audienceId": audience_id, "reason": "never_opened"},
77
+ )
78
+
79
+ def undo_suppression(self, brand_id: str, bulk_id: str) -> dict[str, Any]:
80
+ return self._client.request("POST", f"/partner/brands/{quote(brand_id, safe='')}/suppressions/bulk/{quote(bulk_id, safe='')}/undo")
81
+
82
+ def usage(self, *, start: str | None = None, end: str | None = None) -> dict[str, Any]:
83
+ params = {"from": start, "to": end}
84
+ return self._client.request("GET", "/partner/usage", params={key: value for key, value in params.items() if value is not None})
85
+
86
+ def list_requests(self) -> dict[str, Any]:
87
+ return self._client.request("GET", "/partner/requests")
88
+
89
+ def create_request(self, *, kind: str, message: str | None = None) -> dict[str, Any]:
90
+ body = {"kind": kind}
91
+ if message is not None:
92
+ body["message"] = message
93
+ return self._client.request("POST", "/partner/requests", json=body)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stackshift
3
- Version: 1.3.0
3
+ Version: 1.3.1
4
4
  Summary: Official Python SDK for StackShift.
5
5
  Requires-Python: >=3.9
6
6
  Description-Content-Type: text/markdown
@@ -16,6 +16,25 @@ Official Python SDK for StackShift.
16
16
  pip install stackshift
17
17
  ```
18
18
 
19
+ ## Partner platforms
20
+
21
+ Staff-enabled partner accounts buy domains for merchants from their StackShift wallet. Use an API token with the `partner:manage` scope (and `mail:manage` to run campaigns) on your server:
22
+
23
+ ```python
24
+ domain = stackshift.partner.purchase_domain(
25
+ domain="merchantshop.com",
26
+ years=1,
27
+ merchant_ref="m_123",
28
+ registrant={"first_name": "Ada", "last_name": "Obi", "email": "ada@merchantshop.com", "phone": "+2348012345678", "address": "1 Marina", "city": "Lagos", "state": "Lagos", "postal_code": "101001", "country": "NG"},
29
+ brand={"name": "Merchant Shop", "postalAddress": "1 Marina, Lagos, Nigeria"},
30
+ replies={"mode": "forward", "forwardTo": "ada@gmail.com"},
31
+ idempotency_key="order-m_123-1",
32
+ )
33
+ status = stackshift.partner.get_domain(domain["id"])
34
+ ```
35
+
36
+ `partner` also exposes `overview`, `update_settings`, `list_domains` (with `q` and `attention` to find the merchants that need you), `retry_domain`, `get_diagnosis`, `restore_dns`, `transfer_out`, `resume_brand`, `get_complaints`, `list_complaint_sources`, `suppress_never_opened`, `undo_suppression`, `usage`, `list_requests` and `create_request`. `transfer_out` needs the `partner:transfer` scope (or `*`); `partner:manage` alone is refused. Purchases are capped at 30 per minute and, per partner, at 25 domains in a rolling 24 hours (429 `daily_purchase_limit`; staff can raise it). Validation errors are 400, state conflicts are 409. The `brand` and `replies` dictionaries use API camelCase keys.
37
+
19
38
  ## AI credits
20
39
 
21
40
  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:
@@ -1,18 +1,18 @@
1
- stackshift/__init__.py,sha256=ZCrX0NbSdv2Df1hQ3ctmhqk0ocdTAPG6CTHVKxhxDx8,2700
1
+ stackshift/__init__.py,sha256=B_V3evqxubv9l7wQvO5Tq1vTHv5jykhKvIgAZHpW3IE,2756
2
2
  stackshift/ai_credits.py,sha256=2u5zGSBo_spHqgYJhtmChaEiBq9jUVaTjR1uprxMpNQ,1142
3
- stackshift/assets.py,sha256=qjV59NFxQEF6_56GdMZVrN_QadExFbDWbcbiBFYXCt0,23292
3
+ stackshift/assets.py,sha256=ElOFd7V6D2ceIprDnBcwXbqK4jnU9YXz63hWTtvnx9E,24127
4
4
  stackshift/assets_dam.py,sha256=dz0QgSZuQcwSSYoWFqy1pCx3xgMGYZLc9tCcQE0_kYo,6659
5
5
  stackshift/assets_media.py,sha256=S_7TWu6hm5I8WUPMfQsGE9U15mDiYRG4ygXJzWqwbDE,1523
6
6
  stackshift/assets_platform.py,sha256=w2Eke0YFXinmtHt1oaZlaiP_J32VymfhVWWLOYrX93M,4125
7
7
  stackshift/assets_video.py,sha256=KBIOTsVspHcMltkELsbTmfu-VB6_36dpYQ7HKTqAa2E,2374
8
- stackshift/assets_workflows.py,sha256=6L8_1d9huhfSrKovwP26tfGKc38o0sbSM-75dt__HRE,6950
9
- stackshift/client.py,sha256=hY-1AQjyiiim6zMQi4JriDQ7HfK5zyPE_WkkhbOkmvw,3224
10
- stackshift/mail.py,sha256=XWqEwmmlKEmKVLMpXKeJvkqb3el1KLAHkdUTzyryjQE,16491
8
+ stackshift/assets_workflows.py,sha256=YDGVfzbMW-y5umPWqrHQ9Pp6tGjUC0fnIfNew73V7-c,7274
9
+ stackshift/client.py,sha256=LY7MLo6ljOt1ASNH1HYqi-1k6KEW5pyu6Dz9QFOaLJM,3310
10
+ stackshift/mail.py,sha256=PZCDfaJ8w-sTqVxRAPzGPnxmA_MNHpBH2Jz4ToKUrBY,16561
11
11
  stackshift/mail_advanced.py,sha256=sLte3we0Oy3pSuwjl5c4lfELH6gzaSUKug_WZDUB2DE,4363
12
- stackshift/mail_billing.py,sha256=n1SMNTHW5lVs7phJhZBiKw6ok-T3kbW0A3-4uF1w6Yg,232
13
- stackshift/mail_brands.py,sha256=aaOzETtfix0pFpaWBOHYPVrfUZezWqt53ZXJQk1F7mY,1627
14
- stackshift/mail_campaigns.py,sha256=wgEz2MD3k5db6p1jh-bMYP_aNxM73gekwmi4cNxTvBA,5438
15
- stackshift/mail_events_webhooks.py,sha256=iVYz_5DvGaE-Acw5uh_JN7P5LWvgfN-2bLnb_z1CMRU,5712
12
+ stackshift/mail_billing.py,sha256=bxZlcZkhenugW2G6Qy6TQ9At2Xq10e-4w7keNjOkw8w,384
13
+ stackshift/mail_brands.py,sha256=bNvE-thtrGo6iZkeVzuN3x0XQFUZt4fKD_DivMkqeT8,1658
14
+ stackshift/mail_campaigns.py,sha256=KDFlLLOBbwL2pVya2RmgVGRMuoPXvSqxjQqowTrTmTs,5654
15
+ stackshift/mail_events_webhooks.py,sha256=4w74jZx2C2WNt1RLRgXBBDHZATcwOYe3fEC6igkkt9c,6195
16
16
  stackshift/mail_exports.py,sha256=sbwBbypS1APv2DnunYmyViEjUHP1PMSrM0tVcbxcits,1054
17
17
  stackshift/mail_mailboxes.py,sha256=I5L1C2LyU0MGDZu6VZCh0J7oOqMiCnLSVWlbnkJZxq0,11872
18
18
  stackshift/mail_marketing.py,sha256=7uMZQYjU0siMyqFas_i5YWJTrwT9eYMrWElRThxZMr4,1077
@@ -20,6 +20,7 @@ stackshift/mail_migration.py,sha256=j-VvEbJP92ndQ5HMhdu7obJ4S_4gka7oqNRirIvYkyE,
20
20
  stackshift/mail_reputation.py,sha256=mPvPkZeqcQc5808PhBK7gYsgf4Fk_U19nRxSsJ60MGU,1112
21
21
  stackshift/mail_streams.py,sha256=F7iBzHtWhCFw1cTjwPixNJfCvxdjvsDprZQG2A9-NIs,504
22
22
  stackshift/mail_tracking.py,sha256=SRcWTI9Otm5fBIY7nJJ-5qZA13aybUuMdh_PqakBpUg,1575
23
+ stackshift/partner.py,sha256=D_oFDXIQ52H4KrPJdbKRTRa6GAZbWLa0IqqUXxn6Lzc,4248
23
24
  stackshift/projects.py,sha256=oYWaE7xNRR6OXuPEwJH-oFFeOmDA9ozq6jJY09ANIHA,498
24
25
  stackshift/support_action_manifest.py,sha256=tCm3Qi_R3MidcJBPcsdKE-g8iaXw2xeU2PzxNuAaYbs,4088
25
26
  stackshift/support_action_receiver.py,sha256=PJtXpwt6XurE2sYE-mvjOeAhzWLUoBZsejwHGoC_dFI,3228
@@ -27,7 +28,7 @@ stackshift/support_action_sqlite.py,sha256=Ict1yNNbzp2eEGNF8S7QSlKb9-dQIYiFhJ2ET
27
28
  stackshift/support_action_types.py,sha256=i7xADrXFSIHJaqwcS77LU4jvni5JKrRUzot5WaWpLLU,2213
28
29
  stackshift/support_actions.py,sha256=tZYlb8gdE875SKZdzfN01jfEfIQTyH08XThgCYQ820o,8744
29
30
  stackshift/workload_security.py,sha256=5JtJH9fX1kqSY3ipXUa79JslUULWsN5_gyn9tTGfzlA,2941
30
- stackshift-1.3.0.dist-info/METADATA,sha256=ClHPib_juMAge4YKZ2AOnQVQAx0F9p8ejnnnRxVUZTo,13959
31
- stackshift-1.3.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
32
- stackshift-1.3.0.dist-info/top_level.txt,sha256=qe3uWsu_O9mNtq7bIck9Y8rjTIMrH0kzgAlGFDoRGqs,11
33
- stackshift-1.3.0.dist-info/RECORD,,
31
+ stackshift-1.3.1.dist-info/METADATA,sha256=6eJPmX60mVa8nb3bCLcFBuG9l-BnjxXJvQoTrc4SXAk,15458
32
+ stackshift-1.3.1.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
33
+ stackshift-1.3.1.dist-info/top_level.txt,sha256=qe3uWsu_O9mNtq7bIck9Y8rjTIMrH0kzgAlGFDoRGqs,11
34
+ stackshift-1.3.1.dist-info/RECORD,,