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.
- ads_dev/__init__.py +40 -0
- ads_dev/_async/client.py +335 -0
- ads_dev/_core.py +101 -0
- ads_dev/_sync/client.py +335 -0
- ads_dev/py.typed +0 -0
- ads_dev/types.py +2126 -0
- ads_dev-1.0.0.dist-info/METADATA +158 -0
- ads_dev-1.0.0.dist-info/RECORD +10 -0
- ads_dev-1.0.0.dist-info/WHEEL +4 -0
- ads_dev-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ads_dev
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: The ads.dev API for Python: image, video and audio generation on your brand, the brand kit and library, and competitor ads
|
|
5
|
+
Keywords: ads.dev,sandstorm,ads,image generation,video generation,ai,api
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Typing :: Typed
|
|
12
|
+
Requires-Dist: anyio>=4.15.1
|
|
13
|
+
Requires-Dist: httpx>=0.28.1
|
|
14
|
+
Requires-Dist: pydantic>=2.14.1
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Project-URL: Homepage, https://ads.dev
|
|
17
|
+
Project-URL: Repository, https://github.com/Sandstorm-App/Sandstorm
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# ads_dev
|
|
21
|
+
|
|
22
|
+
The [ads.dev](https://ads.dev) API for Python: image, video and audio generation with 34 models on your brand, the brand kit and library, and the ads your competitors run. It has a sync client and an async one, and every answer is a typed pydantic model.
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install ads_dev
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from ads_dev import Sandstorm
|
|
30
|
+
|
|
31
|
+
sandstorm = Sandstorm()
|
|
32
|
+
|
|
33
|
+
generation = sandstorm.generations.run("nano-banana-pro", {"prompt": "A ceramic mug on a sunlit bench, steam rising", "aspect_ratio": "4:5"})
|
|
34
|
+
print(generation.output[0].url)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from ads_dev import AsyncSandstorm
|
|
39
|
+
|
|
40
|
+
async with AsyncSandstorm() as sandstorm:
|
|
41
|
+
generation = await sandstorm.generations.run("nano-banana-2-lite", {"prompt": "Two lemons in a blue bowl"})
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## A key
|
|
45
|
+
|
|
46
|
+
`Sandstorm()` reads `SANDSTORM_API_KEY`. Make a key in Sandstorm under Settings → API keys, or in one command, which signs you in in the browser and writes the key to `.env`:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
curl -fsSL https://ads.dev/login.mjs -o sandstorm-login.mjs && node sandstorm-login.mjs
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
Sandstorm(api_key="sk_live_…", brand="6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f", base_url="https://ads.dev", max_retries=2, timeout=60)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
A key acts on the brand it was made on; `brand` names another brand of the same organization. `base_url` defaults to `SANDSTORM_API_URL`, then `https://ads.dev`. Pass `http=` an `httpx.Client` (or `httpx.AsyncClient`) of your own to set proxies or limits. Use the client as a context manager, or call `close()`, to release its connections.
|
|
57
|
+
|
|
58
|
+
## Generations
|
|
59
|
+
|
|
60
|
+
`run` makes a generation and answers it once it has finished, holding each request on the API with `Prefer: wait` rather than polling. A generation that fails or is canceled raises `GenerationFailed`, which carries it.
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
still = sandstorm.generations.run("nano-banana-2-lite", {"prompt": "Two lemons in a blue bowl"})
|
|
64
|
+
|
|
65
|
+
clip = sandstorm.generations.run(
|
|
66
|
+
"seedance-2.5/image-to-video",
|
|
67
|
+
{"start_image": still.output[0].id, "prompt": "Slow push in as the light warms", "resolution": "720p"},
|
|
68
|
+
)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Every media input takes an asset id (`asset_…`) or a public https URL, so the output of one generation goes straight into the next. `ads_dev.types` has a model for every model's input, such as `CreateNanoBananaProInput`, if you want an input checked before it is sent.
|
|
72
|
+
|
|
73
|
+
For work that runs long, queue it and come back to it, or take a webhook:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
queued = sandstorm.generations.create("kling-3-pro/image-to-video", input, webhook_url="https://yourapp.com/hooks/sandstorm")
|
|
77
|
+
finished = sandstorm.generations.wait(queued.id)
|
|
78
|
+
|
|
79
|
+
sandstorm.generations.get(queued.id)
|
|
80
|
+
sandstorm.generations.cancel(queued.id)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
A generation is `queued`, then `running`, then `succeeded`, `failed` or `canceled`; `credits` is what it was charged once it finished, and a failed one is free. Cancelling a queued generation costs nothing; one the model has already started stops where the model allows it, and otherwise finishes and is charged.
|
|
84
|
+
|
|
85
|
+
`create` sends an `Idempotency-Key` with every call and keeps it across its retries, so a retry never makes a second generation. Pass your own to make a call safe across process restarts: `create(..., idempotency_key="campaign-42-hero")`.
|
|
86
|
+
|
|
87
|
+
## Lists
|
|
88
|
+
|
|
89
|
+
A list walks every page when iterated, and `first_page()` answers one:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
for asset in sandstorm.assets.list(type="image"):
|
|
93
|
+
print(asset.id, asset.url)
|
|
94
|
+
|
|
95
|
+
page = sandstorm.generations.list(status="succeeded", limit=50).first_page()
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
With the async client, iterate with `async for` and `await` the first page.
|
|
99
|
+
|
|
100
|
+
## Everything else
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
sandstorm.models.list(type="video")
|
|
104
|
+
sandstorm.models.get("nano-banana-pro/edit")
|
|
105
|
+
|
|
106
|
+
sandstorm.brand.get()
|
|
107
|
+
sandstorm.assets.import_url("https://yourcdn.com/hero.png", name="Hero shot")
|
|
108
|
+
sandstorm.assets.get("asset_9c1e77aa")
|
|
109
|
+
sandstorm.offers.list(query="cold brew")
|
|
110
|
+
sandstorm.offers.get("offer_0f8e2b6a4c1d4e7f9a3b5c7d9e1f2a4b")
|
|
111
|
+
sandstorm.characters.list(kind="subject")
|
|
112
|
+
sandstorm.voices.list(use_case="advertisement", limit=5)
|
|
113
|
+
|
|
114
|
+
advertiser = next(iter(sandstorm.advertisers.list(query="Liquid Death")))
|
|
115
|
+
sandstorm.ads.list(advertiser=advertiser.id, status="active")
|
|
116
|
+
sandstorm.ads.get("ad_5e1c9a3b")
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Webhooks
|
|
120
|
+
|
|
121
|
+
A generation made with `webhook_url` is POSTed to it once it finishes, signed in `Sandstorm-Signature`. Verify the raw body before you trust it:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from ads_dev import verify_webhook
|
|
125
|
+
|
|
126
|
+
secret = sandstorm.webhooks.secret()
|
|
127
|
+
|
|
128
|
+
event = verify_webhook(secret, request.body, request.headers.get("sandstorm-signature"))
|
|
129
|
+
if event.type == "generation.succeeded":
|
|
130
|
+
print(event.data.output)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
It raises `ValueError` for a body that was changed, a signature from another secret and a delivery older than five minutes. An event's `id` is the same on every retry of it, so a handler can skip one it has seen.
|
|
134
|
+
|
|
135
|
+
## Errors
|
|
136
|
+
|
|
137
|
+
A refusal raises `SandstormError` with the HTTP `status`, its `type` and `code`, the `param` at fault and the `request_id` to quote to support. Requests Sandstorm could not take (408, 429, 500, 502, 503, 504) and network failures are retried twice with backoff, after the `Retry-After` the API asks for.
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
from ads_dev import SandstormError
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
sandstorm.generations.create("nano-banana-pro", {"prompt": "A mug on a bench"})
|
|
144
|
+
except SandstormError as error:
|
|
145
|
+
if error.code == "insufficient_credits":
|
|
146
|
+
print("Add credits in Sandstorm")
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Developing
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
uv sync
|
|
153
|
+
uv run pytest
|
|
154
|
+
uv run python scripts/generate.py
|
|
155
|
+
uv build
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
`ads_dev/types.py` is generated from `../sdk/openapi.json`, the API's own document, and `ads_dev/_sync/client.py` from the async client with `unasync`; `scripts/generate.py` writes both, and the tests fail when either is stale. Build with uv 0.13 or newer.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
ads_dev/__init__.py,sha256=BhchPzV6TE4XNJ2pM3mjqPUQBnm9CAGEFCKjhdcNo58,713
|
|
2
|
+
ads_dev/_async/client.py,sha256=58OwPcf-Qujv81Ves4LwfyTaWpFvYk-E3Z1vVXIrUM8,11035
|
|
3
|
+
ads_dev/_core.py,sha256=bDBbs0yZ-37XMEWsgg1FB-_Tpv9AcmlS-N1Uv7aka7c,3816
|
|
4
|
+
ads_dev/_sync/client.py,sha256=B_Ep8DJugX8Zl_jzpKkRHK0rq7CNLBg0UrKwFCUtY-o,10700
|
|
5
|
+
ads_dev/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
ads_dev/types.py,sha256=dr_IMaOOhNFK-TkVagJVlloOT0hCOAUfmxc2eogii6A,75707
|
|
7
|
+
ads_dev-1.0.0.dist-info/licenses/LICENSE,sha256=KbZZg9IarXcVTdmrt0bG2l1XjWEM4_x80a80M0UETjc,1073
|
|
8
|
+
ads_dev-1.0.0.dist-info/WHEEL,sha256=1dD_GoEqEIxQ7VnrUExgdj_eExY4b4UgfR0iBlGKVac,80
|
|
9
|
+
ads_dev-1.0.0.dist-info/METADATA,sha256=BuK0LufxjkVpenD7VfK5JaxaaA3n-RTF-8sEUFvnfMI,6474
|
|
10
|
+
ads_dev-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Superstorm, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|