ads_dev 1.0.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.
@@ -0,0 +1,335 @@
1
+ from __future__ import annotations
2
+
3
+ import math
4
+ import uuid
5
+ from collections.abc import Iterator, Mapping
6
+ from typing import Any, Generic, TypeVar
7
+ from urllib.parse import quote
8
+
9
+ import time
10
+ import httpx
11
+ from pydantic import BaseModel
12
+
13
+ from .._core import (
14
+ LONGEST_WAIT_S,
15
+ RETRYABLE,
16
+ RUN_TIMEOUT_S,
17
+ GenerationFailed,
18
+ delay_of,
19
+ headers_of,
20
+ is_finished,
21
+ query_of,
22
+ refusal_of,
23
+ settings,
24
+ )
25
+ from ..types import (
26
+ Ad,
27
+ AdList,
28
+ AdSummary,
29
+ Advertiser,
30
+ AdvertiserList,
31
+ Asset,
32
+ Brand,
33
+ Character,
34
+ CharacterList,
35
+ Generation,
36
+ GenerationList,
37
+ LibraryAsset,
38
+ LibraryAssetList,
39
+ Model,
40
+ ModelList,
41
+ Offer,
42
+ OfferList,
43
+ OfferSummary,
44
+ Voice,
45
+ VoiceList,
46
+ WebhookSecret,
47
+ )
48
+
49
+ Item = TypeVar("Item", bound=BaseModel)
50
+
51
+ Answer = TypeVar("Answer", bound=BaseModel)
52
+
53
+
54
+ class Page(Generic[Item]):
55
+ def __init__(self, client: Sandstorm, path: str, params: Mapping[str, Any], shape: type[BaseModel]) -> None:
56
+ self._client = client
57
+ self._path = path
58
+ self._params = params
59
+ self._shape = shape
60
+
61
+ def first_page(self) -> Any:
62
+ return self._client._send("GET", self._path, self._shape, query=self._params)
63
+
64
+ def __iter__(self) -> Iterator[Item]:
65
+ return self._items()
66
+
67
+ def _items(self) -> Iterator[Item]:
68
+ cursor: str | None = None
69
+ while True:
70
+ page = self._client._send("GET", self._path, self._shape, query={**self._params, "cursor": cursor})
71
+ for item in page.data:
72
+ yield item
73
+ if not page.has_more or page.next_cursor is None:
74
+ return
75
+ cursor = page.next_cursor
76
+
77
+
78
+ class Generations:
79
+ def __init__(self, client: Sandstorm) -> None:
80
+ self._client = client
81
+
82
+ def create(
83
+ self,
84
+ model: str,
85
+ input: Mapping[str, Any],
86
+ *,
87
+ webhook_url: str | None = None,
88
+ wait: int | None = None,
89
+ idempotency_key: str | None = None,
90
+ ) -> Generation:
91
+ body = {"model": model, "input": dict(input), **({"webhook_url": webhook_url} if webhook_url else {})}
92
+ return self._client._send(
93
+ "POST", "/v1/generations", Generation, body=body, wait=wait, idempotency_key=idempotency_key or str(uuid.uuid4())
94
+ )
95
+
96
+ def get(self, id: str, *, wait: int | None = None) -> Generation:
97
+ return self._client._send("GET", f"/v1/generations/{quote(id, safe='')}", Generation, wait=wait)
98
+
99
+ def wait(self, id: str, *, timeout: float = RUN_TIMEOUT_S) -> Generation:
100
+ deadline = time.monotonic() + timeout
101
+ while True:
102
+ left = math.ceil(deadline - time.monotonic())
103
+ generation = self.get(id, wait=max(1, min(LONGEST_WAIT_S, left)))
104
+ if is_finished(generation):
105
+ return generation
106
+ if left <= 0:
107
+ raise TimeoutError(f"{id} did not finish within {timeout} s; it is still {generation.status}")
108
+
109
+ def run(
110
+ self,
111
+ model: str,
112
+ input: Mapping[str, Any],
113
+ *,
114
+ webhook_url: str | None = None,
115
+ idempotency_key: str | None = None,
116
+ timeout: float = RUN_TIMEOUT_S,
117
+ ) -> Generation:
118
+ created = self.create(model, input, webhook_url=webhook_url, wait=LONGEST_WAIT_S, idempotency_key=idempotency_key)
119
+ generation = created if is_finished(created) else self.wait(created.id, timeout=timeout)
120
+ if generation.status != "succeeded":
121
+ raise GenerationFailed(generation)
122
+ return generation
123
+
124
+ def cancel(self, id: str) -> Generation:
125
+ return self._client._send("POST", f"/v1/generations/{quote(id, safe='')}/cancel", Generation)
126
+
127
+ def list(self, *, status: str | None = None, model: str | None = None, limit: int | None = None) -> Page[Generation]:
128
+ return Page(self._client, "/v1/generations", {"status": status, "model": model, "limit": limit}, GenerationList)
129
+
130
+
131
+ class Models:
132
+ def __init__(self, client: Sandstorm) -> None:
133
+ self._client = client
134
+
135
+ def list(self, *, type: str | None = None) -> Page[Model]:
136
+ return Page(self._client, "/v1/models", {"type": type}, ModelList)
137
+
138
+ def get(self, id: str) -> Model:
139
+ return self._client._send("GET", f"/v1/models/{quote(id, safe='')}", Model)
140
+
141
+
142
+ class Assets:
143
+ def __init__(self, client: Sandstorm) -> None:
144
+ self._client = client
145
+
146
+ def list(
147
+ self,
148
+ *,
149
+ type: str | None = None,
150
+ offer: str | None = None,
151
+ liked: bool | None = None,
152
+ archived: bool | None = None,
153
+ limit: int | None = None,
154
+ ) -> Page[LibraryAsset]:
155
+ return Page(self._client, "/v1/assets", {"type": type, "offer": offer, "liked": liked, "archived": archived, "limit": limit}, LibraryAssetList)
156
+
157
+ def get(self, id: str) -> Asset:
158
+ return self._client._send("GET", f"/v1/assets/{quote(id, safe='')}", Asset)
159
+
160
+ def import_url(self, url: str, *, name: str | None = None, idempotency_key: str | None = None) -> Asset:
161
+ body = {"url": url, **({"name": name} if name else {})}
162
+ return self._client._send("POST", "/v1/assets", Asset, body=body, idempotency_key=idempotency_key or str(uuid.uuid4()))
163
+
164
+
165
+ class Brands:
166
+ def __init__(self, client: Sandstorm) -> None:
167
+ self._client = client
168
+
169
+ def get(self) -> Brand:
170
+ return self._client._send("GET", "/v1/brand", Brand)
171
+
172
+
173
+ class Offers:
174
+ def __init__(self, client: Sandstorm) -> None:
175
+ self._client = client
176
+
177
+ def list(self, *, query: str | None = None, limit: int | None = None) -> Page[OfferSummary]:
178
+ return Page(self._client, "/v1/offers", {"query": query, "limit": limit}, OfferList)
179
+
180
+ def get(self, id: str) -> Offer:
181
+ return self._client._send("GET", f"/v1/offers/{quote(id, safe='')}", Offer)
182
+
183
+
184
+ class Characters:
185
+ def __init__(self, client: Sandstorm) -> None:
186
+ self._client = client
187
+
188
+ def list(self, *, query: str | None = None, kind: str | None = None) -> Page[Character]:
189
+ return Page(self._client, "/v1/characters", {"query": query, "kind": kind}, CharacterList)
190
+
191
+
192
+ class Voices:
193
+ def __init__(self, client: Sandstorm) -> None:
194
+ self._client = client
195
+
196
+ def list(
197
+ self,
198
+ *,
199
+ query: str | None = None,
200
+ gender: str | None = None,
201
+ age: str | None = None,
202
+ accent: str | None = None,
203
+ use_case: str | None = None,
204
+ limit: int | None = None,
205
+ ) -> Page[Voice]:
206
+ params = {"query": query, "gender": gender, "age": age, "accent": accent, "use_case": use_case, "limit": limit}
207
+ return Page(self._client, "/v1/voices", params, VoiceList)
208
+
209
+
210
+ class Advertisers:
211
+ def __init__(self, client: Sandstorm) -> None:
212
+ self._client = client
213
+
214
+ def list(self, *, query: str, limit: int | None = None) -> Page[Advertiser]:
215
+ return Page(self._client, "/v1/advertisers", {"query": query, "limit": limit}, AdvertiserList)
216
+
217
+
218
+ class Ads:
219
+ def __init__(self, client: Sandstorm) -> None:
220
+ self._client = client
221
+
222
+ def list(
223
+ self,
224
+ *,
225
+ query: str | None = None,
226
+ advertiser: str | None = None,
227
+ platform: str | None = None,
228
+ format: str | None = None,
229
+ status: str | None = None,
230
+ min_active_days: int | None = None,
231
+ started_after: str | None = None,
232
+ started_before: str | None = None,
233
+ limit: int | None = None,
234
+ ) -> Page[AdSummary]:
235
+ params = {
236
+ "query": query,
237
+ "advertiser": advertiser,
238
+ "platform": platform,
239
+ "format": format,
240
+ "status": status,
241
+ "min_active_days": min_active_days,
242
+ "started_after": started_after,
243
+ "started_before": started_before,
244
+ "limit": limit,
245
+ }
246
+ return Page(self._client, "/v1/ads", params, AdList)
247
+
248
+ def get(self, id: str) -> Ad:
249
+ return self._client._send("GET", f"/v1/ads/{quote(id, safe='')}", Ad)
250
+
251
+
252
+ class Webhooks:
253
+ def __init__(self, client: Sandstorm) -> None:
254
+ self._client = client
255
+
256
+ def secret(self) -> str:
257
+ return (self._client._send("GET", "/v1/webhooks/secret", WebhookSecret)).secret
258
+
259
+
260
+ class Sandstorm:
261
+ def __init__(
262
+ self,
263
+ *,
264
+ api_key: str | None = None,
265
+ base_url: str | None = None,
266
+ brand: str | None = None,
267
+ max_retries: int = 2,
268
+ timeout: float = 60,
269
+ http: httpx.Client | None = None,
270
+ ) -> None:
271
+ key, url = settings(api_key, base_url)
272
+ self._base_url = url
273
+ self._headers = headers_of(key, brand)
274
+ self._max_retries = max_retries
275
+ self._timeout = timeout
276
+ self._http = http or httpx.Client()
277
+ self.generations = Generations(self)
278
+ self.models = Models(self)
279
+ self.assets = Assets(self)
280
+ self.brand = Brands(self)
281
+ self.offers = Offers(self)
282
+ self.characters = Characters(self)
283
+ self.voices = Voices(self)
284
+ self.advertisers = Advertisers(self)
285
+ self.ads = Ads(self)
286
+ self.webhooks = Webhooks(self)
287
+
288
+ def __enter__(self) -> Sandstorm:
289
+ return self
290
+
291
+ def __exit__(self, *_: object) -> None:
292
+ self.close()
293
+
294
+ def close(self) -> None:
295
+ self._http.close()
296
+
297
+ def _send(
298
+ self,
299
+ method: str,
300
+ path: str,
301
+ shape: type[Answer],
302
+ *,
303
+ query: Mapping[str, Any] | None = None,
304
+ body: Mapping[str, Any] | None = None,
305
+ wait: int | None = None,
306
+ idempotency_key: str | None = None,
307
+ ) -> Answer:
308
+ headers = {
309
+ **self._headers,
310
+ **({"prefer": f"wait={wait}"} if wait is not None else {}),
311
+ **({"idempotency-key": idempotency_key} if idempotency_key is not None else {}),
312
+ }
313
+ attempt = 0
314
+ while True:
315
+ try:
316
+ response = self._http.request(
317
+ method,
318
+ self._base_url + path,
319
+ params=query_of(query or {}),
320
+ json=body,
321
+ headers=headers,
322
+ timeout=self._timeout + (wait or 0),
323
+ )
324
+ except httpx.TransportError:
325
+ if attempt >= self._max_retries:
326
+ raise
327
+ time.sleep(delay_of(attempt, None))
328
+ attempt += 1
329
+ continue
330
+ if response.is_success:
331
+ return shape.model_validate(response.json())
332
+ if response.status_code not in RETRYABLE or attempt >= self._max_retries:
333
+ raise refusal_of(response)
334
+ time.sleep(delay_of(attempt, response))
335
+ attempt += 1
ads_dev/py.typed ADDED
File without changes