impreza-sdk 0.3.0__tar.gz
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.
- impreza_sdk-0.3.0/PKG-INFO +311 -0
- impreza_sdk-0.3.0/README.md +275 -0
- impreza_sdk-0.3.0/impreza/__init__.py +171 -0
- impreza_sdk-0.3.0/impreza/_http.py +277 -0
- impreza_sdk-0.3.0/impreza/_http_async.py +270 -0
- impreza_sdk-0.3.0/impreza/_pagination.py +72 -0
- impreza_sdk-0.3.0/impreza/_polling.py +280 -0
- impreza_sdk-0.3.0/impreza/_topup.py +311 -0
- impreza_sdk-0.3.0/impreza/_tor.py +81 -0
- impreza_sdk-0.3.0/impreza/async_client.py +125 -0
- impreza_sdk-0.3.0/impreza/client.py +145 -0
- impreza_sdk-0.3.0/impreza/exceptions.py +235 -0
- impreza_sdk-0.3.0/impreza/models/__init__.py +85 -0
- impreza_sdk-0.3.0/impreza/models/account.py +97 -0
- impreza_sdk-0.3.0/impreza/models/dns.py +23 -0
- impreza_sdk-0.3.0/impreza/models/domain.py +68 -0
- impreza_sdk-0.3.0/impreza/models/email.py +24 -0
- impreza_sdk-0.3.0/impreza/models/invoice.py +60 -0
- impreza_sdk-0.3.0/impreza/models/order.py +69 -0
- impreza_sdk-0.3.0/impreza/models/product.py +116 -0
- impreza_sdk-0.3.0/impreza/models/service.py +36 -0
- impreza_sdk-0.3.0/impreza/models/tld.py +30 -0
- impreza_sdk-0.3.0/impreza/models/vps.py +30 -0
- impreza_sdk-0.3.0/impreza/models/vps_extras.py +140 -0
- impreza_sdk-0.3.0/impreza/models/webhook.py +104 -0
- impreza_sdk-0.3.0/impreza/py.typed +0 -0
- impreza_sdk-0.3.0/impreza/resources/__init__.py +96 -0
- impreza_sdk-0.3.0/impreza/resources/account.py +171 -0
- impreza_sdk-0.3.0/impreza/resources/catalog.py +139 -0
- impreza_sdk-0.3.0/impreza/resources/domains.py +454 -0
- impreza_sdk-0.3.0/impreza/resources/email.py +228 -0
- impreza_sdk-0.3.0/impreza/resources/hosting.py +92 -0
- impreza_sdk-0.3.0/impreza/resources/invoices.py +69 -0
- impreza_sdk-0.3.0/impreza/resources/orders.py +462 -0
- impreza_sdk-0.3.0/impreza/resources/services.py +136 -0
- impreza_sdk-0.3.0/impreza/resources/vps.py +924 -0
- impreza_sdk-0.3.0/impreza/resources/vps_cloud.py +313 -0
- impreza_sdk-0.3.0/impreza/resources/vps_proxmox.py +350 -0
- impreza_sdk-0.3.0/impreza/resources/webhooks.py +284 -0
- impreza_sdk-0.3.0/impreza/webhooks.py +143 -0
- impreza_sdk-0.3.0/impreza_sdk.egg-info/PKG-INFO +311 -0
- impreza_sdk-0.3.0/impreza_sdk.egg-info/SOURCES.txt +73 -0
- impreza_sdk-0.3.0/impreza_sdk.egg-info/dependency_links.txt +1 -0
- impreza_sdk-0.3.0/impreza_sdk.egg-info/requires.txt +13 -0
- impreza_sdk-0.3.0/impreza_sdk.egg-info/top_level.txt +1 -0
- impreza_sdk-0.3.0/pyproject.toml +80 -0
- impreza_sdk-0.3.0/setup.cfg +4 -0
- impreza_sdk-0.3.0/tests/conftest.py +31 -0
- impreza_sdk-0.3.0/tests/test_account_smoke.py +38 -0
- impreza_sdk-0.3.0/tests/test_account_topup_unit.py +439 -0
- impreza_sdk-0.3.0/tests/test_account_unit.py +252 -0
- impreza_sdk-0.3.0/tests/test_async_account_smoke.py +43 -0
- impreza_sdk-0.3.0/tests/test_async_account_unit.py +148 -0
- impreza_sdk-0.3.0/tests/test_catalog_unit.py +252 -0
- impreza_sdk-0.3.0/tests/test_domains_unit.py +529 -0
- impreza_sdk-0.3.0/tests/test_email_unit.py +240 -0
- impreza_sdk-0.3.0/tests/test_hosting_unit.py +134 -0
- impreza_sdk-0.3.0/tests/test_invoices_unit.py +169 -0
- impreza_sdk-0.3.0/tests/test_orders_unit.py +418 -0
- impreza_sdk-0.3.0/tests/test_pagination.py +61 -0
- impreza_sdk-0.3.0/tests/test_phase_1_3_smoke.py +73 -0
- impreza_sdk-0.3.0/tests/test_phase_1_4a_smoke.py +50 -0
- impreza_sdk-0.3.0/tests/test_phase_1_4b_ii_smoke.py +90 -0
- impreza_sdk-0.3.0/tests/test_phase_1_4b_smoke.py +64 -0
- impreza_sdk-0.3.0/tests/test_phase_1_4c_smoke.py +122 -0
- impreza_sdk-0.3.0/tests/test_phase_1_4d_smoke.py +63 -0
- impreza_sdk-0.3.0/tests/test_phase_1_5_smoke.py +99 -0
- impreza_sdk-0.3.0/tests/test_phase_1_6_smoke.py +100 -0
- impreza_sdk-0.3.0/tests/test_phase_1_7_smoke.py +132 -0
- impreza_sdk-0.3.0/tests/test_polling_unit.py +410 -0
- impreza_sdk-0.3.0/tests/test_services_unit.py +250 -0
- impreza_sdk-0.3.0/tests/test_tor.py +69 -0
- impreza_sdk-0.3.0/tests/test_vps_specific_unit.py +716 -0
- impreza_sdk-0.3.0/tests/test_vps_unit.py +588 -0
- impreza_sdk-0.3.0/tests/test_webhooks_unit.py +484 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: impreza-sdk
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Official Python SDK for the Impreza Host public REST API
|
|
5
|
+
Author-email: Impreza Host <support@imprezahost.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://imprezahost.com
|
|
8
|
+
Project-URL: Documentation, https://docs.imprezahost.com
|
|
9
|
+
Project-URL: Repository, https://github.com/imprezahost/impreza-devkit
|
|
10
|
+
Project-URL: Issues, https://github.com/imprezahost/impreza-devkit/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/imprezahost/impreza-devkit/blob/master/CHANGELOG.md
|
|
12
|
+
Keywords: impreza,hosting,domains,vps,api,offshore,crypto
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
Requires-Dist: httpx[socks]>=0.27
|
|
26
|
+
Requires-Dist: pydantic>=2.5
|
|
27
|
+
Provides-Extra: test
|
|
28
|
+
Requires-Dist: pytest>=8.0; extra == "test"
|
|
29
|
+
Requires-Dist: pytest-cov>=4.1; extra == "test"
|
|
30
|
+
Requires-Dist: respx>=0.20; extra == "test"
|
|
31
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "test"
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: ruff>=0.5; extra == "dev"
|
|
34
|
+
Requires-Dist: mypy>=1.8; extra == "dev"
|
|
35
|
+
Requires-Dist: pre-commit>=3.6; extra == "dev"
|
|
36
|
+
|
|
37
|
+
# `impreza-sdk` — Official Python SDK for Impreza Host
|
|
38
|
+
|
|
39
|
+
Type-safe, sync + async client for the Impreza Host public REST API
|
|
40
|
+
([api.imprezahost.com](https://api.imprezahost.com)). Covers domains,
|
|
41
|
+
hosting (cPanel), managed email (Titan / Workspace), VPS (Proxmox +
|
|
42
|
+
Cloud) with smart dispatch, orders, invoices, webhooks, and
|
|
43
|
+
crypto top-up — all behind one client object.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install impreza-sdk
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Requires Python 3.10+. See [`CHANGELOG.md`](../CHANGELOG.md) for the
|
|
50
|
+
release history.
|
|
51
|
+
|
|
52
|
+
## Quickstart
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from impreza import Client
|
|
56
|
+
|
|
57
|
+
# Reads IMPREZA_API_KEY and IMPREZA_API_SECRET from the environment.
|
|
58
|
+
# Passing them explicitly works too: Client(api_key="...", api_secret="...").
|
|
59
|
+
with Client.from_env() as c:
|
|
60
|
+
me = c.account.get()
|
|
61
|
+
print(me.balance, me.currency)
|
|
62
|
+
|
|
63
|
+
# List active services across all backends
|
|
64
|
+
for svc in c.account.services.list(status="Active"):
|
|
65
|
+
print(svc.id, svc.product, svc.status, svc.vps_backend)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Authentication
|
|
69
|
+
|
|
70
|
+
Two headers, no signing:
|
|
71
|
+
|
|
72
|
+
- `IMPREZA_API_KEY` — `imp_` + 40 hex chars (public identifier)
|
|
73
|
+
- `IMPREZA_API_SECRET` — 64 hex chars (shown once at creation)
|
|
74
|
+
|
|
75
|
+
Generate keys in your Impreza Account. Add the source IP of every
|
|
76
|
+
machine that calls the API to the key's IP whitelist.
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
export IMPREZA_API_KEY="imp_..."
|
|
80
|
+
export IMPREZA_API_SECRET="..."
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Crypto top-up
|
|
84
|
+
|
|
85
|
+
Top up account balance with BTC / XMR / TRX / USDT-TRC20 via the
|
|
86
|
+
existing `btcpayinline` gateway:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
invoice = c.account.topup(amount=50, method="xmr")
|
|
90
|
+
print(invoice.payment_url) # btcpayinline URL
|
|
91
|
+
print(invoice.expires_at) # ISO timestamp, 2h from creation
|
|
92
|
+
|
|
93
|
+
# Poll until paid (or fail on cancellation / refund / expiry)
|
|
94
|
+
invoice.wait_until_paid(timeout=7200, poll_interval=30)
|
|
95
|
+
print(invoice.balance_after)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
For push delivery instead of polling, subscribe a webhook to the
|
|
99
|
+
`topup.paid` event (see below).
|
|
100
|
+
|
|
101
|
+
## Domains
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
# Availability
|
|
105
|
+
print(c.domains.check(["example.com", "example.net"]))
|
|
106
|
+
|
|
107
|
+
# Lookup
|
|
108
|
+
domain = c.domains.get("example.com")
|
|
109
|
+
print(domain.status, domain.expiry_date)
|
|
110
|
+
|
|
111
|
+
# DNS CRUD (nested under domains)
|
|
112
|
+
records = c.domains.dns.list("example.com")
|
|
113
|
+
c.domains.dns.add("example.com", type="A", name="@", value="1.2.3.4", ttl=3600)
|
|
114
|
+
c.domains.dns.update("example.com", type="A", name="@",
|
|
115
|
+
old_value="1.2.3.4", new_value="5.6.7.8")
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## VPS (smart dispatch)
|
|
119
|
+
|
|
120
|
+
One `c.vps.get(service_id)` call returns a backend-aware bound model.
|
|
121
|
+
The SDK looks up which backend (Proxmox or Cloud) the service is on
|
|
122
|
+
and routes operations transparently:
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
vps = c.vps.get(17988) # service id from c.account.services.list()
|
|
126
|
+
print(vps.backend) # "proxmox" or "cloud"
|
|
127
|
+
|
|
128
|
+
# Common surface — same on both backends
|
|
129
|
+
status = vps.status()
|
|
130
|
+
print(status.power_state) # "running" (Proxmox) / "online" (Cloud)
|
|
131
|
+
vps.reboot()
|
|
132
|
+
|
|
133
|
+
# Backend-specific sub-resources (Proxmox)
|
|
134
|
+
vps.snapshots.list()
|
|
135
|
+
op = vps.snapshots.rollback("pre-update") # returns Operation future
|
|
136
|
+
op.wait(timeout=600) # blocks until queue completes
|
|
137
|
+
|
|
138
|
+
# Backend-specific sub-resources (Cloud)
|
|
139
|
+
vps.images.list() # account-scoped
|
|
140
|
+
vps.rescue.enable()
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Wrong-backend access raises `BackendNotSupported` client-side — no
|
|
144
|
+
network call:
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
vps = c.vps.get(17987) # Cloud VPS
|
|
148
|
+
vps.snapshots.list()
|
|
149
|
+
# raises BackendNotSupported("snapshots is not supported on the 'cloud'
|
|
150
|
+
# VPS backend. Use vps.images instead.")
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Webhooks
|
|
154
|
+
|
|
155
|
+
Subscribe to events server-side:
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
sub = c.webhooks.create(
|
|
159
|
+
url="https://example.com/hooks/impreza",
|
|
160
|
+
events=["topup.paid", "vps.power_state_changed", "domain.*"],
|
|
161
|
+
)
|
|
162
|
+
print(sub.secret) # 64 hex chars; shown ONCE — store it
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Verify deliveries on your receiver (timing-safe HMAC-SHA256):
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
from impreza.webhooks import verify_signature, WebhookSignatureMismatch
|
|
169
|
+
|
|
170
|
+
# Inside your Flask / FastAPI / Django handler
|
|
171
|
+
try:
|
|
172
|
+
event = verify_signature(
|
|
173
|
+
body=request.body, # raw bytes, NOT a re-serialized JSON
|
|
174
|
+
signature_header=request.headers["X-Impreza-Signature"],
|
|
175
|
+
secret=os.environ["IMPREZA_WEBHOOK_SECRET"],
|
|
176
|
+
)
|
|
177
|
+
except WebhookSignatureMismatch:
|
|
178
|
+
return ("invalid signature", 401)
|
|
179
|
+
|
|
180
|
+
if event.type == "topup.paid":
|
|
181
|
+
credit_user(event.data.client_id, event.data.amount)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Async
|
|
185
|
+
|
|
186
|
+
Every resource has an async counterpart via `AsyncClient`. **Reach for it
|
|
187
|
+
when you need high-fanout calls** — provisioning many VPSs, polling a
|
|
188
|
+
bulk of invoices in parallel, etc. For one-shot scripts and interactive
|
|
189
|
+
use the sync `Client` is simpler and has the same surface.
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
import asyncio
|
|
193
|
+
from impreza import AsyncClient
|
|
194
|
+
|
|
195
|
+
async def main():
|
|
196
|
+
async with AsyncClient.from_env() as c:
|
|
197
|
+
# Read calls fan out trivially:
|
|
198
|
+
me = await c.account.get()
|
|
199
|
+
|
|
200
|
+
# Concurrent reboot across every VPS — gather() returns when
|
|
201
|
+
# all complete (or any one raises). 10 calls take ~one
|
|
202
|
+
# round-trip's worth of wall-clock, not 10x.
|
|
203
|
+
vpss = await c.vps.list()
|
|
204
|
+
await asyncio.gather(*[v.reboot() for v in vpss])
|
|
205
|
+
|
|
206
|
+
# Async futures work the same way as their sync counterparts:
|
|
207
|
+
op = await c.vps.get(17988).snapshots.rollback("pre-update")
|
|
208
|
+
await op.wait(timeout=600) # AsyncOperation.wait()
|
|
209
|
+
|
|
210
|
+
invoice = await c.account.topup(amount=50, method="xmr")
|
|
211
|
+
await invoice.wait_until_paid(timeout=7200) # AsyncTopupInvoice
|
|
212
|
+
|
|
213
|
+
asyncio.run(main())
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
The `impreza` CLI is **sync by design** — CLI invocations are one-shot
|
|
217
|
+
processes where the async-fanout speedup doesn't apply. The async path
|
|
218
|
+
exists for library users; everything the CLI does has a `Client.*`
|
|
219
|
+
equivalent. If you're integrating into a long-running async app
|
|
220
|
+
(FastAPI, an event consumer, a batch worker), import the SDK directly
|
|
221
|
+
and skip the CLI.
|
|
222
|
+
|
|
223
|
+
## Tor routing
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
# Explicit
|
|
227
|
+
c = Client(api_key="...", api_secret="...",
|
|
228
|
+
proxy="socks5://127.0.0.1:9050")
|
|
229
|
+
|
|
230
|
+
# Opt-in via flag or env var
|
|
231
|
+
c = Client.from_env(use_tor=True) # equivalent to IMPREZA_USE_TOR=1
|
|
232
|
+
|
|
233
|
+
# Probe Tor; fall back to clearnet if not running
|
|
234
|
+
c = Client.from_env(auto_tor=True)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Backed by `httpx[socks]`. Sync and async parity. The probe used by
|
|
238
|
+
`auto_tor=True` never raises — failure means clearnet.
|
|
239
|
+
|
|
240
|
+
## Error handling
|
|
241
|
+
|
|
242
|
+
All errors subclass `ImprezaError`, so a single `except` catches
|
|
243
|
+
everything the SDK can throw:
|
|
244
|
+
|
|
245
|
+
```python
|
|
246
|
+
from impreza import (
|
|
247
|
+
Client, ImprezaError,
|
|
248
|
+
InsufficientCredit, RateLimitExceeded, IpNotWhitelisted,
|
|
249
|
+
OperationFailed, TopupFailed,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
try:
|
|
253
|
+
c.orders.create(product_id=12, billing_cycle="monthly", domain="example.com")
|
|
254
|
+
except InsufficientCredit as e:
|
|
255
|
+
needed = e.details.get("amount_needed")
|
|
256
|
+
print(f"Need ${needed} more — opening a top-up invoice")
|
|
257
|
+
inv = c.account.topup(amount=needed, method="btc")
|
|
258
|
+
print(inv.payment_url)
|
|
259
|
+
except RateLimitExceeded as e:
|
|
260
|
+
print(f"Rate limited — retry after {e.retry_after}s")
|
|
261
|
+
except IpNotWhitelisted:
|
|
262
|
+
print("Your current IP isn't on the API key's whitelist")
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Action polling
|
|
266
|
+
|
|
267
|
+
Long-running Proxmox operations return `Operation` futures:
|
|
268
|
+
|
|
269
|
+
```python
|
|
270
|
+
op = c.vps.get(17988).backups.create()
|
|
271
|
+
print(op.uuid, op.status) # 'queued' initially
|
|
272
|
+
op.wait(timeout=1800, poll_interval=10)
|
|
273
|
+
print(op.is_success(), op.finished_at)
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
`wait()` raises `OperationTimeout` if it doesn't finish, or
|
|
277
|
+
`OperationFailed` on a terminal failure state.
|
|
278
|
+
|
|
279
|
+
## Development
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
git clone https://github.com/imprezahost/impreza-devkit.git
|
|
283
|
+
cd impreza-devkit/sdk-python
|
|
284
|
+
python -m venv .venv
|
|
285
|
+
# Linux/macOS: source .venv/bin/activate
|
|
286
|
+
# Windows PowerShell: .venv\Scripts\Activate.ps1
|
|
287
|
+
pip install -e ".[test,dev]"
|
|
288
|
+
|
|
289
|
+
pytest # 266 unit tests + mocks
|
|
290
|
+
ruff check
|
|
291
|
+
mypy --strict impreza
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
To run the live integration smokes:
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
export IMPREZA_API_KEY="imp_..."
|
|
298
|
+
export IMPREZA_API_SECRET="..."
|
|
299
|
+
# Optional — only needed for the full topup decode smoke
|
|
300
|
+
export IMPREZA_TOPUP_INVOICE_ID="<id of an existing AddFunds invoice>"
|
|
301
|
+
|
|
302
|
+
pytest -v -s tests/
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
The destructive tests (real top-up creation, real Proxmox backup
|
|
306
|
+
creation) are gated behind `IMPREZA_DESTRUCTIVE_TESTS=1` and skipped
|
|
307
|
+
by default.
|
|
308
|
+
|
|
309
|
+
## License
|
|
310
|
+
|
|
311
|
+
MIT. See [`../LICENSE`](../LICENSE) at the repository root.
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# `impreza-sdk` — Official Python SDK for Impreza Host
|
|
2
|
+
|
|
3
|
+
Type-safe, sync + async client for the Impreza Host public REST API
|
|
4
|
+
([api.imprezahost.com](https://api.imprezahost.com)). Covers domains,
|
|
5
|
+
hosting (cPanel), managed email (Titan / Workspace), VPS (Proxmox +
|
|
6
|
+
Cloud) with smart dispatch, orders, invoices, webhooks, and
|
|
7
|
+
crypto top-up — all behind one client object.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install impreza-sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Requires Python 3.10+. See [`CHANGELOG.md`](../CHANGELOG.md) for the
|
|
14
|
+
release history.
|
|
15
|
+
|
|
16
|
+
## Quickstart
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from impreza import Client
|
|
20
|
+
|
|
21
|
+
# Reads IMPREZA_API_KEY and IMPREZA_API_SECRET from the environment.
|
|
22
|
+
# Passing them explicitly works too: Client(api_key="...", api_secret="...").
|
|
23
|
+
with Client.from_env() as c:
|
|
24
|
+
me = c.account.get()
|
|
25
|
+
print(me.balance, me.currency)
|
|
26
|
+
|
|
27
|
+
# List active services across all backends
|
|
28
|
+
for svc in c.account.services.list(status="Active"):
|
|
29
|
+
print(svc.id, svc.product, svc.status, svc.vps_backend)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Authentication
|
|
33
|
+
|
|
34
|
+
Two headers, no signing:
|
|
35
|
+
|
|
36
|
+
- `IMPREZA_API_KEY` — `imp_` + 40 hex chars (public identifier)
|
|
37
|
+
- `IMPREZA_API_SECRET` — 64 hex chars (shown once at creation)
|
|
38
|
+
|
|
39
|
+
Generate keys in your Impreza Account. Add the source IP of every
|
|
40
|
+
machine that calls the API to the key's IP whitelist.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
export IMPREZA_API_KEY="imp_..."
|
|
44
|
+
export IMPREZA_API_SECRET="..."
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Crypto top-up
|
|
48
|
+
|
|
49
|
+
Top up account balance with BTC / XMR / TRX / USDT-TRC20 via the
|
|
50
|
+
existing `btcpayinline` gateway:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
invoice = c.account.topup(amount=50, method="xmr")
|
|
54
|
+
print(invoice.payment_url) # btcpayinline URL
|
|
55
|
+
print(invoice.expires_at) # ISO timestamp, 2h from creation
|
|
56
|
+
|
|
57
|
+
# Poll until paid (or fail on cancellation / refund / expiry)
|
|
58
|
+
invoice.wait_until_paid(timeout=7200, poll_interval=30)
|
|
59
|
+
print(invoice.balance_after)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
For push delivery instead of polling, subscribe a webhook to the
|
|
63
|
+
`topup.paid` event (see below).
|
|
64
|
+
|
|
65
|
+
## Domains
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
# Availability
|
|
69
|
+
print(c.domains.check(["example.com", "example.net"]))
|
|
70
|
+
|
|
71
|
+
# Lookup
|
|
72
|
+
domain = c.domains.get("example.com")
|
|
73
|
+
print(domain.status, domain.expiry_date)
|
|
74
|
+
|
|
75
|
+
# DNS CRUD (nested under domains)
|
|
76
|
+
records = c.domains.dns.list("example.com")
|
|
77
|
+
c.domains.dns.add("example.com", type="A", name="@", value="1.2.3.4", ttl=3600)
|
|
78
|
+
c.domains.dns.update("example.com", type="A", name="@",
|
|
79
|
+
old_value="1.2.3.4", new_value="5.6.7.8")
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## VPS (smart dispatch)
|
|
83
|
+
|
|
84
|
+
One `c.vps.get(service_id)` call returns a backend-aware bound model.
|
|
85
|
+
The SDK looks up which backend (Proxmox or Cloud) the service is on
|
|
86
|
+
and routes operations transparently:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
vps = c.vps.get(17988) # service id from c.account.services.list()
|
|
90
|
+
print(vps.backend) # "proxmox" or "cloud"
|
|
91
|
+
|
|
92
|
+
# Common surface — same on both backends
|
|
93
|
+
status = vps.status()
|
|
94
|
+
print(status.power_state) # "running" (Proxmox) / "online" (Cloud)
|
|
95
|
+
vps.reboot()
|
|
96
|
+
|
|
97
|
+
# Backend-specific sub-resources (Proxmox)
|
|
98
|
+
vps.snapshots.list()
|
|
99
|
+
op = vps.snapshots.rollback("pre-update") # returns Operation future
|
|
100
|
+
op.wait(timeout=600) # blocks until queue completes
|
|
101
|
+
|
|
102
|
+
# Backend-specific sub-resources (Cloud)
|
|
103
|
+
vps.images.list() # account-scoped
|
|
104
|
+
vps.rescue.enable()
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Wrong-backend access raises `BackendNotSupported` client-side — no
|
|
108
|
+
network call:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
vps = c.vps.get(17987) # Cloud VPS
|
|
112
|
+
vps.snapshots.list()
|
|
113
|
+
# raises BackendNotSupported("snapshots is not supported on the 'cloud'
|
|
114
|
+
# VPS backend. Use vps.images instead.")
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Webhooks
|
|
118
|
+
|
|
119
|
+
Subscribe to events server-side:
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
sub = c.webhooks.create(
|
|
123
|
+
url="https://example.com/hooks/impreza",
|
|
124
|
+
events=["topup.paid", "vps.power_state_changed", "domain.*"],
|
|
125
|
+
)
|
|
126
|
+
print(sub.secret) # 64 hex chars; shown ONCE — store it
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Verify deliveries on your receiver (timing-safe HMAC-SHA256):
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from impreza.webhooks import verify_signature, WebhookSignatureMismatch
|
|
133
|
+
|
|
134
|
+
# Inside your Flask / FastAPI / Django handler
|
|
135
|
+
try:
|
|
136
|
+
event = verify_signature(
|
|
137
|
+
body=request.body, # raw bytes, NOT a re-serialized JSON
|
|
138
|
+
signature_header=request.headers["X-Impreza-Signature"],
|
|
139
|
+
secret=os.environ["IMPREZA_WEBHOOK_SECRET"],
|
|
140
|
+
)
|
|
141
|
+
except WebhookSignatureMismatch:
|
|
142
|
+
return ("invalid signature", 401)
|
|
143
|
+
|
|
144
|
+
if event.type == "topup.paid":
|
|
145
|
+
credit_user(event.data.client_id, event.data.amount)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Async
|
|
149
|
+
|
|
150
|
+
Every resource has an async counterpart via `AsyncClient`. **Reach for it
|
|
151
|
+
when you need high-fanout calls** — provisioning many VPSs, polling a
|
|
152
|
+
bulk of invoices in parallel, etc. For one-shot scripts and interactive
|
|
153
|
+
use the sync `Client` is simpler and has the same surface.
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
import asyncio
|
|
157
|
+
from impreza import AsyncClient
|
|
158
|
+
|
|
159
|
+
async def main():
|
|
160
|
+
async with AsyncClient.from_env() as c:
|
|
161
|
+
# Read calls fan out trivially:
|
|
162
|
+
me = await c.account.get()
|
|
163
|
+
|
|
164
|
+
# Concurrent reboot across every VPS — gather() returns when
|
|
165
|
+
# all complete (or any one raises). 10 calls take ~one
|
|
166
|
+
# round-trip's worth of wall-clock, not 10x.
|
|
167
|
+
vpss = await c.vps.list()
|
|
168
|
+
await asyncio.gather(*[v.reboot() for v in vpss])
|
|
169
|
+
|
|
170
|
+
# Async futures work the same way as their sync counterparts:
|
|
171
|
+
op = await c.vps.get(17988).snapshots.rollback("pre-update")
|
|
172
|
+
await op.wait(timeout=600) # AsyncOperation.wait()
|
|
173
|
+
|
|
174
|
+
invoice = await c.account.topup(amount=50, method="xmr")
|
|
175
|
+
await invoice.wait_until_paid(timeout=7200) # AsyncTopupInvoice
|
|
176
|
+
|
|
177
|
+
asyncio.run(main())
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The `impreza` CLI is **sync by design** — CLI invocations are one-shot
|
|
181
|
+
processes where the async-fanout speedup doesn't apply. The async path
|
|
182
|
+
exists for library users; everything the CLI does has a `Client.*`
|
|
183
|
+
equivalent. If you're integrating into a long-running async app
|
|
184
|
+
(FastAPI, an event consumer, a batch worker), import the SDK directly
|
|
185
|
+
and skip the CLI.
|
|
186
|
+
|
|
187
|
+
## Tor routing
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
# Explicit
|
|
191
|
+
c = Client(api_key="...", api_secret="...",
|
|
192
|
+
proxy="socks5://127.0.0.1:9050")
|
|
193
|
+
|
|
194
|
+
# Opt-in via flag or env var
|
|
195
|
+
c = Client.from_env(use_tor=True) # equivalent to IMPREZA_USE_TOR=1
|
|
196
|
+
|
|
197
|
+
# Probe Tor; fall back to clearnet if not running
|
|
198
|
+
c = Client.from_env(auto_tor=True)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Backed by `httpx[socks]`. Sync and async parity. The probe used by
|
|
202
|
+
`auto_tor=True` never raises — failure means clearnet.
|
|
203
|
+
|
|
204
|
+
## Error handling
|
|
205
|
+
|
|
206
|
+
All errors subclass `ImprezaError`, so a single `except` catches
|
|
207
|
+
everything the SDK can throw:
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
from impreza import (
|
|
211
|
+
Client, ImprezaError,
|
|
212
|
+
InsufficientCredit, RateLimitExceeded, IpNotWhitelisted,
|
|
213
|
+
OperationFailed, TopupFailed,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
try:
|
|
217
|
+
c.orders.create(product_id=12, billing_cycle="monthly", domain="example.com")
|
|
218
|
+
except InsufficientCredit as e:
|
|
219
|
+
needed = e.details.get("amount_needed")
|
|
220
|
+
print(f"Need ${needed} more — opening a top-up invoice")
|
|
221
|
+
inv = c.account.topup(amount=needed, method="btc")
|
|
222
|
+
print(inv.payment_url)
|
|
223
|
+
except RateLimitExceeded as e:
|
|
224
|
+
print(f"Rate limited — retry after {e.retry_after}s")
|
|
225
|
+
except IpNotWhitelisted:
|
|
226
|
+
print("Your current IP isn't on the API key's whitelist")
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Action polling
|
|
230
|
+
|
|
231
|
+
Long-running Proxmox operations return `Operation` futures:
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
op = c.vps.get(17988).backups.create()
|
|
235
|
+
print(op.uuid, op.status) # 'queued' initially
|
|
236
|
+
op.wait(timeout=1800, poll_interval=10)
|
|
237
|
+
print(op.is_success(), op.finished_at)
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
`wait()` raises `OperationTimeout` if it doesn't finish, or
|
|
241
|
+
`OperationFailed` on a terminal failure state.
|
|
242
|
+
|
|
243
|
+
## Development
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
git clone https://github.com/imprezahost/impreza-devkit.git
|
|
247
|
+
cd impreza-devkit/sdk-python
|
|
248
|
+
python -m venv .venv
|
|
249
|
+
# Linux/macOS: source .venv/bin/activate
|
|
250
|
+
# Windows PowerShell: .venv\Scripts\Activate.ps1
|
|
251
|
+
pip install -e ".[test,dev]"
|
|
252
|
+
|
|
253
|
+
pytest # 266 unit tests + mocks
|
|
254
|
+
ruff check
|
|
255
|
+
mypy --strict impreza
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
To run the live integration smokes:
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
export IMPREZA_API_KEY="imp_..."
|
|
262
|
+
export IMPREZA_API_SECRET="..."
|
|
263
|
+
# Optional — only needed for the full topup decode smoke
|
|
264
|
+
export IMPREZA_TOPUP_INVOICE_ID="<id of an existing AddFunds invoice>"
|
|
265
|
+
|
|
266
|
+
pytest -v -s tests/
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
The destructive tests (real top-up creation, real Proxmox backup
|
|
270
|
+
creation) are gated behind `IMPREZA_DESTRUCTIVE_TESTS=1` and skipped
|
|
271
|
+
by default.
|
|
272
|
+
|
|
273
|
+
## License
|
|
274
|
+
|
|
275
|
+
MIT. See [`../LICENSE`](../LICENSE) at the repository root.
|