driftstack-sdk 0.1.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.
- driftstack_sdk-0.1.0/.gitignore +45 -0
- driftstack_sdk-0.1.0/PKG-INFO +226 -0
- driftstack_sdk-0.1.0/README.md +191 -0
- driftstack_sdk-0.1.0/pyproject.toml +112 -0
- driftstack_sdk-0.1.0/src/driftstack/__init__.py +60 -0
- driftstack_sdk-0.1.0/src/driftstack/_generated/__init__.py +6 -0
- driftstack_sdk-0.1.0/src/driftstack/_generated/models.py +494 -0
- driftstack_sdk-0.1.0/src/driftstack/_version.py +10 -0
- driftstack_sdk-0.1.0/src/driftstack/client.py +120 -0
- driftstack_sdk-0.1.0/src/driftstack/errors.py +194 -0
- driftstack_sdk-0.1.0/src/driftstack/http.py +291 -0
- driftstack_sdk-0.1.0/src/driftstack/py.typed +0 -0
- driftstack_sdk-0.1.0/src/driftstack/resources/__init__.py +29 -0
- driftstack_sdk-0.1.0/src/driftstack/resources/_common.py +38 -0
- driftstack_sdk-0.1.0/src/driftstack/resources/api_keys.py +61 -0
- driftstack_sdk-0.1.0/src/driftstack/resources/sessions.py +156 -0
- driftstack_sdk-0.1.0/src/driftstack/resources/usage.py +29 -0
- driftstack_sdk-0.1.0/src/driftstack/resources/webhooks.py +110 -0
- driftstack_sdk-0.1.0/src/driftstack/retry.py +105 -0
- driftstack_sdk-0.1.0/src/driftstack/webhook_signature.py +106 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
node_modules/
|
|
2
|
+
dist/
|
|
3
|
+
build/
|
|
4
|
+
coverage/
|
|
5
|
+
.tsbuildinfo
|
|
6
|
+
*.tsbuildinfo
|
|
7
|
+
|
|
8
|
+
# Env
|
|
9
|
+
.env
|
|
10
|
+
.env.*
|
|
11
|
+
!.env.example
|
|
12
|
+
|
|
13
|
+
# Editor
|
|
14
|
+
.vscode/
|
|
15
|
+
.idea/
|
|
16
|
+
*.swp
|
|
17
|
+
.DS_Store
|
|
18
|
+
|
|
19
|
+
# Logs
|
|
20
|
+
*.log
|
|
21
|
+
npm-debug.log*
|
|
22
|
+
|
|
23
|
+
# Test artifacts
|
|
24
|
+
test-results/
|
|
25
|
+
playwright-report/
|
|
26
|
+
playwright/.cache/
|
|
27
|
+
|
|
28
|
+
# Drizzle
|
|
29
|
+
drizzle/.migrations-applied
|
|
30
|
+
|
|
31
|
+
# Python SDK
|
|
32
|
+
.venv/
|
|
33
|
+
__pycache__/
|
|
34
|
+
*.egg-info/
|
|
35
|
+
.pytest_cache/
|
|
36
|
+
.mypy_cache/
|
|
37
|
+
.ruff_cache/
|
|
38
|
+
packages/sdk-python/dist/
|
|
39
|
+
.npmrc
|
|
40
|
+
|
|
41
|
+
# GUI client (Tauri)
|
|
42
|
+
apps/gui-client/dist/
|
|
43
|
+
apps/gui-client/src-tauri/target/
|
|
44
|
+
apps/gui-client/src-tauri/gen/
|
|
45
|
+
# Cargo.lock SHOULD be committed for binaries (Tauri app is a binary).
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: driftstack-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Driftstack Python SDK — stealth iPhone Safari automation. Import as `driftstack`.
|
|
5
|
+
Project-URL: Homepage, https://driftstack.dev
|
|
6
|
+
Project-URL: Repository, https://github.com/driftstackdev/driftstack-api
|
|
7
|
+
Project-URL: Issues, https://github.com/driftstackdev/driftstack-api/issues
|
|
8
|
+
Author: Driftstack
|
|
9
|
+
License: MIT
|
|
10
|
+
Keywords: browser-automation,driftstack,ios,safari,stealth
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Requires-Dist: httpx<1.0,>=0.27
|
|
26
|
+
Requires-Dist: pydantic[email]<3.0,>=2.5
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: datamodel-code-generator[http]>=0.25; extra == 'dev'
|
|
29
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
33
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# Driftstack Python SDK
|
|
37
|
+
|
|
38
|
+
Stealth iPhone Safari automation, called from Python. Sync (`Driftstack`) and async (`AsyncDriftstack`) clients in one package, sharing the same typed resources, error hierarchy, and retry policy.
|
|
39
|
+
|
|
40
|
+
> **Status:** alpha. The SDK is built, tested, and wheel-buildable, but **not yet published to PyPI** — gated on entity setup. Until then, install from a local checkout or a tagged commit.
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install driftstack-sdk
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The dist name on PyPI is `driftstack-sdk`; the import name is `driftstack`.
|
|
49
|
+
|
|
50
|
+
Requires Python 3.10+.
|
|
51
|
+
|
|
52
|
+
## Quickstart (sync)
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from driftstack import Driftstack
|
|
56
|
+
|
|
57
|
+
with Driftstack(api_key="ds_live_…") as client:
|
|
58
|
+
session = client.sessions.create({"label": "ci-run"})
|
|
59
|
+
client.sessions.navigate(str(session.id), {"url": "https://example.com/"})
|
|
60
|
+
state = client.sessions.get_state(str(session.id))
|
|
61
|
+
print(state.url, state.title)
|
|
62
|
+
client.sessions.destroy(str(session.id))
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Quickstart (async)
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
import asyncio
|
|
69
|
+
from driftstack import AsyncDriftstack
|
|
70
|
+
|
|
71
|
+
async def main():
|
|
72
|
+
async with AsyncDriftstack(api_key="ds_live_…") as client:
|
|
73
|
+
s = await client.sessions.create()
|
|
74
|
+
await client.sessions.navigate(str(s.id), {"url": "https://example.com/"})
|
|
75
|
+
await client.sessions.destroy(str(s.id))
|
|
76
|
+
|
|
77
|
+
asyncio.run(main())
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Resources
|
|
81
|
+
|
|
82
|
+
Every public API endpoint is a typed method on a resource accessor:
|
|
83
|
+
|
|
84
|
+
| Accessor | Methods |
|
|
85
|
+
| ----------------- | ------------------------------------------------------------------------------------------ |
|
|
86
|
+
| `client.sessions` | `create`, `list`, `get`, `navigate`, `interact`, `wait`, `get_state`, `capture`, `destroy` |
|
|
87
|
+
| `client.api_keys` | `create`, `list`, `revoke` |
|
|
88
|
+
| `client.usage` | `current_period` |
|
|
89
|
+
| `client.webhooks` | `create`, `list`, `get`, `delete`, `list_deliveries` |
|
|
90
|
+
|
|
91
|
+
Inputs accept either a Pydantic model OR a plain `dict` (both serialize identically on the wire). Outputs are typed Pydantic models — IDEs autocomplete every field.
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
# Either of these works:
|
|
95
|
+
from driftstack._generated.models import CreateSessionRequest
|
|
96
|
+
client.sessions.create(CreateSessionRequest(label="ci"))
|
|
97
|
+
client.sessions.create({"label": "ci"})
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Error handling
|
|
101
|
+
|
|
102
|
+
Every server `application/problem+json` response is mapped to a typed exception. The base class is `DriftstackError`; subclasses cover the documented problem types.
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from driftstack import (
|
|
106
|
+
AuthError,
|
|
107
|
+
ConcurrencyLimitError,
|
|
108
|
+
DriftstackError,
|
|
109
|
+
QuotaExceededError,
|
|
110
|
+
RateLimitError,
|
|
111
|
+
ValidationError,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
session = client.sessions.create()
|
|
116
|
+
except AuthError:
|
|
117
|
+
... # invalid / expired / revoked key
|
|
118
|
+
except ConcurrencyLimitError as e:
|
|
119
|
+
... # e.current_sessions / e.limit
|
|
120
|
+
except QuotaExceededError as e:
|
|
121
|
+
... # e.current / e.limit / e.record_type
|
|
122
|
+
except RateLimitError as e:
|
|
123
|
+
time.sleep(e.retry_after_seconds or 1)
|
|
124
|
+
except ValidationError as e:
|
|
125
|
+
... # e.message has the server's detail
|
|
126
|
+
except DriftstackError as e:
|
|
127
|
+
... # catch-all for anything else
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The full hierarchy lives in `driftstack/errors.py`; the URI → exception mapping is in `PROBLEM_TYPE_TO_ERROR`.
|
|
131
|
+
|
|
132
|
+
## Retry
|
|
133
|
+
|
|
134
|
+
Default policy: 3 retries with exponential backoff and full jitter. Honours `Retry-After` from rate-limit responses. Customize via `RetryConfig`:
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from driftstack import Driftstack
|
|
138
|
+
from driftstack.retry import RetryConfig
|
|
139
|
+
|
|
140
|
+
client = Driftstack(
|
|
141
|
+
api_key="ds_live_…",
|
|
142
|
+
retry=RetryConfig(max_retries=5, initial_delay_ms=500, max_delay_ms=10_000),
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# Disable entirely for predictable testing:
|
|
146
|
+
client = Driftstack(api_key="…", retry=RetryConfig(enabled=False))
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Retryable errors by default: `TransportError` (network / timeout / parse) + `RateLimitError`. Other typed errors (auth, validation, quota, concurrency) propagate immediately.
|
|
150
|
+
|
|
151
|
+
## Webhook signature verification
|
|
152
|
+
|
|
153
|
+
Stripe-style HMAC-SHA256 over `<unix_seconds>.<raw_body>`. Constant-time comparison via `hmac.compare_digest`. 5-minute default tolerance.
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from driftstack import verify_webhook_signature
|
|
157
|
+
|
|
158
|
+
@app.post("/driftstack-webhook")
|
|
159
|
+
def receive():
|
|
160
|
+
raw = request.get_data() # framework-specific raw body
|
|
161
|
+
ok = verify_webhook_signature(
|
|
162
|
+
body=raw,
|
|
163
|
+
header=request.headers.get("x-driftstack-signature"),
|
|
164
|
+
secret=os.environ["DRIFTSTACK_WEBHOOK_SECRET"],
|
|
165
|
+
)
|
|
166
|
+
if not ok:
|
|
167
|
+
return ("", 401)
|
|
168
|
+
# ... process event ...
|
|
169
|
+
return ("", 204)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
A complete stdlib-only receiver lives in [`examples/webhook_receiver.py`](examples/webhook_receiver.py).
|
|
173
|
+
|
|
174
|
+
## Examples
|
|
175
|
+
|
|
176
|
+
- [`quickstart.py`](examples/quickstart.py) — minimal create/navigate/capture/destroy.
|
|
177
|
+
- [`error_handling.py`](examples/error_handling.py) — granular catch + custom retry loop.
|
|
178
|
+
- [`webhook_receiver.py`](examples/webhook_receiver.py) — stdlib HTTP receiver with signature verify + dispatch.
|
|
179
|
+
- [`langchain_tool.py`](examples/langchain_tool.py) — LangChain `Tool` adapter for AI-agent QA pipelines.
|
|
180
|
+
- [`pytest_fixture.py`](examples/pytest_fixture.py) — drop-in `mock_driftstack` fixture for customer test suites.
|
|
181
|
+
|
|
182
|
+
## Configuration
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
client = Driftstack(
|
|
186
|
+
api_key="ds_live_…", # required
|
|
187
|
+
base_url="https://api.driftstack.dev", # default; override for self-host or test
|
|
188
|
+
timeout_s=30.0, # per-request timeout
|
|
189
|
+
retry=RetryConfig(...), # see above
|
|
190
|
+
http_client=httpx.Client(...) # advanced: BYO httpx.Client
|
|
191
|
+
)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The async client takes the same arguments; pass `httpx.AsyncClient(...)` instead of `httpx.Client(...)`.
|
|
195
|
+
|
|
196
|
+
## Development
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
# from packages/sdk-python/
|
|
200
|
+
python3.10 -m venv .venv
|
|
201
|
+
source .venv/bin/activate
|
|
202
|
+
pip install -e '.[dev]'
|
|
203
|
+
pytest
|
|
204
|
+
ruff check . && ruff format --check .
|
|
205
|
+
mypy src
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Re-generate Pydantic models from a fresh OpenAPI spec:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
# from the repo root
|
|
212
|
+
npm run sdk:python:dump-spec # writes packages/sdk-python/openapi.json
|
|
213
|
+
npm run sdk:python:generate # runs datamodel-codegen
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Build the wheel:
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
# from packages/sdk-python/
|
|
220
|
+
python -m pip install build
|
|
221
|
+
python -m build # → dist/driftstack-X.Y.Z-py3-none-any.whl + sdist
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## License
|
|
225
|
+
|
|
226
|
+
MIT.
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# Driftstack Python SDK
|
|
2
|
+
|
|
3
|
+
Stealth iPhone Safari automation, called from Python. Sync (`Driftstack`) and async (`AsyncDriftstack`) clients in one package, sharing the same typed resources, error hierarchy, and retry policy.
|
|
4
|
+
|
|
5
|
+
> **Status:** alpha. The SDK is built, tested, and wheel-buildable, but **not yet published to PyPI** — gated on entity setup. Until then, install from a local checkout or a tagged commit.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install driftstack-sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The dist name on PyPI is `driftstack-sdk`; the import name is `driftstack`.
|
|
14
|
+
|
|
15
|
+
Requires Python 3.10+.
|
|
16
|
+
|
|
17
|
+
## Quickstart (sync)
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from driftstack import Driftstack
|
|
21
|
+
|
|
22
|
+
with Driftstack(api_key="ds_live_…") as client:
|
|
23
|
+
session = client.sessions.create({"label": "ci-run"})
|
|
24
|
+
client.sessions.navigate(str(session.id), {"url": "https://example.com/"})
|
|
25
|
+
state = client.sessions.get_state(str(session.id))
|
|
26
|
+
print(state.url, state.title)
|
|
27
|
+
client.sessions.destroy(str(session.id))
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quickstart (async)
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
import asyncio
|
|
34
|
+
from driftstack import AsyncDriftstack
|
|
35
|
+
|
|
36
|
+
async def main():
|
|
37
|
+
async with AsyncDriftstack(api_key="ds_live_…") as client:
|
|
38
|
+
s = await client.sessions.create()
|
|
39
|
+
await client.sessions.navigate(str(s.id), {"url": "https://example.com/"})
|
|
40
|
+
await client.sessions.destroy(str(s.id))
|
|
41
|
+
|
|
42
|
+
asyncio.run(main())
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Resources
|
|
46
|
+
|
|
47
|
+
Every public API endpoint is a typed method on a resource accessor:
|
|
48
|
+
|
|
49
|
+
| Accessor | Methods |
|
|
50
|
+
| ----------------- | ------------------------------------------------------------------------------------------ |
|
|
51
|
+
| `client.sessions` | `create`, `list`, `get`, `navigate`, `interact`, `wait`, `get_state`, `capture`, `destroy` |
|
|
52
|
+
| `client.api_keys` | `create`, `list`, `revoke` |
|
|
53
|
+
| `client.usage` | `current_period` |
|
|
54
|
+
| `client.webhooks` | `create`, `list`, `get`, `delete`, `list_deliveries` |
|
|
55
|
+
|
|
56
|
+
Inputs accept either a Pydantic model OR a plain `dict` (both serialize identically on the wire). Outputs are typed Pydantic models — IDEs autocomplete every field.
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
# Either of these works:
|
|
60
|
+
from driftstack._generated.models import CreateSessionRequest
|
|
61
|
+
client.sessions.create(CreateSessionRequest(label="ci"))
|
|
62
|
+
client.sessions.create({"label": "ci"})
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Error handling
|
|
66
|
+
|
|
67
|
+
Every server `application/problem+json` response is mapped to a typed exception. The base class is `DriftstackError`; subclasses cover the documented problem types.
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from driftstack import (
|
|
71
|
+
AuthError,
|
|
72
|
+
ConcurrencyLimitError,
|
|
73
|
+
DriftstackError,
|
|
74
|
+
QuotaExceededError,
|
|
75
|
+
RateLimitError,
|
|
76
|
+
ValidationError,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
session = client.sessions.create()
|
|
81
|
+
except AuthError:
|
|
82
|
+
... # invalid / expired / revoked key
|
|
83
|
+
except ConcurrencyLimitError as e:
|
|
84
|
+
... # e.current_sessions / e.limit
|
|
85
|
+
except QuotaExceededError as e:
|
|
86
|
+
... # e.current / e.limit / e.record_type
|
|
87
|
+
except RateLimitError as e:
|
|
88
|
+
time.sleep(e.retry_after_seconds or 1)
|
|
89
|
+
except ValidationError as e:
|
|
90
|
+
... # e.message has the server's detail
|
|
91
|
+
except DriftstackError as e:
|
|
92
|
+
... # catch-all for anything else
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The full hierarchy lives in `driftstack/errors.py`; the URI → exception mapping is in `PROBLEM_TYPE_TO_ERROR`.
|
|
96
|
+
|
|
97
|
+
## Retry
|
|
98
|
+
|
|
99
|
+
Default policy: 3 retries with exponential backoff and full jitter. Honours `Retry-After` from rate-limit responses. Customize via `RetryConfig`:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from driftstack import Driftstack
|
|
103
|
+
from driftstack.retry import RetryConfig
|
|
104
|
+
|
|
105
|
+
client = Driftstack(
|
|
106
|
+
api_key="ds_live_…",
|
|
107
|
+
retry=RetryConfig(max_retries=5, initial_delay_ms=500, max_delay_ms=10_000),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Disable entirely for predictable testing:
|
|
111
|
+
client = Driftstack(api_key="…", retry=RetryConfig(enabled=False))
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Retryable errors by default: `TransportError` (network / timeout / parse) + `RateLimitError`. Other typed errors (auth, validation, quota, concurrency) propagate immediately.
|
|
115
|
+
|
|
116
|
+
## Webhook signature verification
|
|
117
|
+
|
|
118
|
+
Stripe-style HMAC-SHA256 over `<unix_seconds>.<raw_body>`. Constant-time comparison via `hmac.compare_digest`. 5-minute default tolerance.
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from driftstack import verify_webhook_signature
|
|
122
|
+
|
|
123
|
+
@app.post("/driftstack-webhook")
|
|
124
|
+
def receive():
|
|
125
|
+
raw = request.get_data() # framework-specific raw body
|
|
126
|
+
ok = verify_webhook_signature(
|
|
127
|
+
body=raw,
|
|
128
|
+
header=request.headers.get("x-driftstack-signature"),
|
|
129
|
+
secret=os.environ["DRIFTSTACK_WEBHOOK_SECRET"],
|
|
130
|
+
)
|
|
131
|
+
if not ok:
|
|
132
|
+
return ("", 401)
|
|
133
|
+
# ... process event ...
|
|
134
|
+
return ("", 204)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
A complete stdlib-only receiver lives in [`examples/webhook_receiver.py`](examples/webhook_receiver.py).
|
|
138
|
+
|
|
139
|
+
## Examples
|
|
140
|
+
|
|
141
|
+
- [`quickstart.py`](examples/quickstart.py) — minimal create/navigate/capture/destroy.
|
|
142
|
+
- [`error_handling.py`](examples/error_handling.py) — granular catch + custom retry loop.
|
|
143
|
+
- [`webhook_receiver.py`](examples/webhook_receiver.py) — stdlib HTTP receiver with signature verify + dispatch.
|
|
144
|
+
- [`langchain_tool.py`](examples/langchain_tool.py) — LangChain `Tool` adapter for AI-agent QA pipelines.
|
|
145
|
+
- [`pytest_fixture.py`](examples/pytest_fixture.py) — drop-in `mock_driftstack` fixture for customer test suites.
|
|
146
|
+
|
|
147
|
+
## Configuration
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
client = Driftstack(
|
|
151
|
+
api_key="ds_live_…", # required
|
|
152
|
+
base_url="https://api.driftstack.dev", # default; override for self-host or test
|
|
153
|
+
timeout_s=30.0, # per-request timeout
|
|
154
|
+
retry=RetryConfig(...), # see above
|
|
155
|
+
http_client=httpx.Client(...) # advanced: BYO httpx.Client
|
|
156
|
+
)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The async client takes the same arguments; pass `httpx.AsyncClient(...)` instead of `httpx.Client(...)`.
|
|
160
|
+
|
|
161
|
+
## Development
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
# from packages/sdk-python/
|
|
165
|
+
python3.10 -m venv .venv
|
|
166
|
+
source .venv/bin/activate
|
|
167
|
+
pip install -e '.[dev]'
|
|
168
|
+
pytest
|
|
169
|
+
ruff check . && ruff format --check .
|
|
170
|
+
mypy src
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Re-generate Pydantic models from a fresh OpenAPI spec:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
# from the repo root
|
|
177
|
+
npm run sdk:python:dump-spec # writes packages/sdk-python/openapi.json
|
|
178
|
+
npm run sdk:python:generate # runs datamodel-codegen
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Build the wheel:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
# from packages/sdk-python/
|
|
185
|
+
python -m pip install build
|
|
186
|
+
python -m build # → dist/driftstack-X.Y.Z-py3-none-any.whl + sdist
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## License
|
|
190
|
+
|
|
191
|
+
MIT.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.21"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "driftstack-sdk"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Driftstack Python SDK — stealth iPhone Safari automation. Import as `driftstack`."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Driftstack" }
|
|
14
|
+
]
|
|
15
|
+
keywords = ["driftstack", "browser-automation", "ios", "safari", "stealth"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Programming Language :: Python :: 3.13",
|
|
27
|
+
"Programming Language :: Python :: 3.14",
|
|
28
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
29
|
+
"Typing :: Typed",
|
|
30
|
+
]
|
|
31
|
+
dependencies = [
|
|
32
|
+
"httpx>=0.27,<1.0",
|
|
33
|
+
"pydantic[email]>=2.5,<3.0",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.optional-dependencies]
|
|
37
|
+
dev = [
|
|
38
|
+
"pytest>=8.0",
|
|
39
|
+
"pytest-asyncio>=0.23",
|
|
40
|
+
"respx>=0.21",
|
|
41
|
+
"ruff>=0.5",
|
|
42
|
+
"mypy>=1.10",
|
|
43
|
+
"datamodel-code-generator[http]>=0.25",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
[project.urls]
|
|
47
|
+
Homepage = "https://driftstack.dev"
|
|
48
|
+
Repository = "https://github.com/driftstackdev/driftstack-api"
|
|
49
|
+
Issues = "https://github.com/driftstackdev/driftstack-api/issues"
|
|
50
|
+
|
|
51
|
+
[tool.hatch.build.targets.wheel]
|
|
52
|
+
packages = ["src/driftstack"]
|
|
53
|
+
|
|
54
|
+
[tool.hatch.build.targets.sdist]
|
|
55
|
+
include = [
|
|
56
|
+
"/src/driftstack",
|
|
57
|
+
"/README.md",
|
|
58
|
+
"/pyproject.toml",
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
# ──────────────────────────────────────────────────────────────────
|
|
62
|
+
# pytest
|
|
63
|
+
# ──────────────────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
[tool.pytest.ini_options]
|
|
66
|
+
testpaths = ["tests"]
|
|
67
|
+
asyncio_mode = "auto"
|
|
68
|
+
filterwarnings = [
|
|
69
|
+
"error",
|
|
70
|
+
# Pydantic emits deprecation warnings for some patterns we generate
|
|
71
|
+
# via datamodel-code-generator; tolerate them so tests don't fail on
|
|
72
|
+
# codegen output we don't fully control.
|
|
73
|
+
"default::DeprecationWarning:pydantic",
|
|
74
|
+
"default::DeprecationWarning:driftstack._generated",
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
# ──────────────────────────────────────────────────────────────────
|
|
78
|
+
# ruff
|
|
79
|
+
# ──────────────────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
[tool.ruff]
|
|
82
|
+
line-length = 100
|
|
83
|
+
target-version = "py310"
|
|
84
|
+
extend-exclude = ["src/driftstack/_generated"]
|
|
85
|
+
|
|
86
|
+
[tool.ruff.lint]
|
|
87
|
+
# Conservative starter set; tighten in follow-up commits.
|
|
88
|
+
select = ["E", "F", "I", "B", "UP", "PT"]
|
|
89
|
+
ignore = []
|
|
90
|
+
|
|
91
|
+
[tool.ruff.lint.per-file-ignores]
|
|
92
|
+
"tests/**" = ["B", "PT011"]
|
|
93
|
+
|
|
94
|
+
# ──────────────────────────────────────────────────────────────────
|
|
95
|
+
# mypy — strict on hand-written code; permissive on _generated
|
|
96
|
+
# ──────────────────────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
[tool.mypy]
|
|
99
|
+
python_version = "3.10"
|
|
100
|
+
strict = true
|
|
101
|
+
warn_unused_configs = true
|
|
102
|
+
exclude = ["build/", "dist/"]
|
|
103
|
+
|
|
104
|
+
[[tool.mypy.overrides]]
|
|
105
|
+
module = "driftstack._generated.*"
|
|
106
|
+
# Codegen output isn't always strict-clean — `conint(...)` /
|
|
107
|
+
# `constr(...)` factory calls aren't valid type annotations to mypy
|
|
108
|
+
# even though Pydantic v2 accepts them at runtime. Skip type-checking
|
|
109
|
+
# for the codegen module entirely; the wrapper layer that customers
|
|
110
|
+
# actually touch stays strict, and the generated models are runtime-
|
|
111
|
+
# tested via tests/test_generated_models.py.
|
|
112
|
+
ignore_errors = true
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Driftstack Python SDK.
|
|
2
|
+
|
|
3
|
+
Customer-facing entry points re-exported here so callers can write::
|
|
4
|
+
|
|
5
|
+
from driftstack import Driftstack, AsyncDriftstack, DriftstackError
|
|
6
|
+
|
|
7
|
+
Resource accessors live on the client instance::
|
|
8
|
+
|
|
9
|
+
client = Driftstack(api_key="ds_live_...")
|
|
10
|
+
session = client.sessions.create()
|
|
11
|
+
client.sessions.navigate(session.id, url="https://example.com")
|
|
12
|
+
client.sessions.destroy(session.id)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from driftstack._version import __version__
|
|
18
|
+
from driftstack.client import AsyncDriftstack, Driftstack
|
|
19
|
+
from driftstack.errors import (
|
|
20
|
+
AuthError,
|
|
21
|
+
ConcurrencyLimitError,
|
|
22
|
+
ConflictError,
|
|
23
|
+
DriftstackError,
|
|
24
|
+
DriverError,
|
|
25
|
+
ExpiredKeyError,
|
|
26
|
+
ForbiddenError,
|
|
27
|
+
InvalidKeyError,
|
|
28
|
+
NotFoundError,
|
|
29
|
+
QuotaExceededError,
|
|
30
|
+
RateLimitError,
|
|
31
|
+
RevokedKeyError,
|
|
32
|
+
SessionDestroyedError,
|
|
33
|
+
SessionNotFoundError,
|
|
34
|
+
TransportError,
|
|
35
|
+
ValidationError,
|
|
36
|
+
)
|
|
37
|
+
from driftstack.webhook_signature import verify_webhook_signature
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
"__version__",
|
|
41
|
+
"Driftstack",
|
|
42
|
+
"AsyncDriftstack",
|
|
43
|
+
"DriftstackError",
|
|
44
|
+
"AuthError",
|
|
45
|
+
"ForbiddenError",
|
|
46
|
+
"InvalidKeyError",
|
|
47
|
+
"ExpiredKeyError",
|
|
48
|
+
"RevokedKeyError",
|
|
49
|
+
"ConflictError",
|
|
50
|
+
"NotFoundError",
|
|
51
|
+
"RateLimitError",
|
|
52
|
+
"QuotaExceededError",
|
|
53
|
+
"ConcurrencyLimitError",
|
|
54
|
+
"SessionNotFoundError",
|
|
55
|
+
"SessionDestroyedError",
|
|
56
|
+
"DriverError",
|
|
57
|
+
"ValidationError",
|
|
58
|
+
"TransportError",
|
|
59
|
+
"verify_webhook_signature",
|
|
60
|
+
]
|