doow-track 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.
- doow_track-0.1.0/.gitignore +52 -0
- doow_track-0.1.0/LICENSE +21 -0
- doow_track-0.1.0/PKG-INFO +392 -0
- doow_track-0.1.0/README.md +361 -0
- doow_track-0.1.0/pyproject.toml +65 -0
- doow_track-0.1.0/src/doow_track/__init__.py +94 -0
- doow_track-0.1.0/src/doow_track/errors.py +56 -0
- doow_track-0.1.0/src/doow_track/management.py +412 -0
- doow_track-0.1.0/src/doow_track/py.typed +0 -0
- doow_track-0.1.0/src/doow_track/tracker.py +533 -0
- doow_track-0.1.0/src/doow_track/types.py +420 -0
- doow_track-0.1.0/tests/__init__.py +1 -0
- doow_track-0.1.0/tests/test_management.py +168 -0
- doow_track-0.1.0/tests/test_tracker.py +174 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Distribution / packaging
|
|
7
|
+
.Python
|
|
8
|
+
build/
|
|
9
|
+
develop-eggs/
|
|
10
|
+
dist/
|
|
11
|
+
downloads/
|
|
12
|
+
eggs/
|
|
13
|
+
.eggs/
|
|
14
|
+
lib/
|
|
15
|
+
lib64/
|
|
16
|
+
parts/
|
|
17
|
+
sdist/
|
|
18
|
+
var/
|
|
19
|
+
wheels/
|
|
20
|
+
*.egg-info/
|
|
21
|
+
.installed.cfg
|
|
22
|
+
*.egg
|
|
23
|
+
|
|
24
|
+
# Virtual environments
|
|
25
|
+
.env
|
|
26
|
+
.venv
|
|
27
|
+
env/
|
|
28
|
+
venv/
|
|
29
|
+
ENV/
|
|
30
|
+
|
|
31
|
+
# IDE
|
|
32
|
+
.idea/
|
|
33
|
+
.vscode/
|
|
34
|
+
*.swp
|
|
35
|
+
*.swo
|
|
36
|
+
|
|
37
|
+
# Testing
|
|
38
|
+
.pytest_cache/
|
|
39
|
+
.coverage
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
|
|
44
|
+
# Type checking
|
|
45
|
+
.mypy_cache/
|
|
46
|
+
|
|
47
|
+
# Ruff
|
|
48
|
+
.ruff_cache/
|
|
49
|
+
|
|
50
|
+
# Local dev
|
|
51
|
+
.env.local
|
|
52
|
+
*.log
|
doow_track-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Doow
|
|
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.
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: doow-track
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Doow SDK for Python - usage telemetry and management
|
|
5
|
+
Project-URL: Homepage, https://doow.co
|
|
6
|
+
Project-URL: Documentation, https://docs.doow.co/sdks/python
|
|
7
|
+
Project-URL: Repository, https://github.com/Doow-Dev/doow-track-python
|
|
8
|
+
Author-email: Doow <support@doow.co>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: billing,doow,metering,saas,telemetry,usage
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Requires-Dist: httpx>=0.25.0
|
|
23
|
+
Requires-Dist: pydantic>=2.0.0
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: mypy>=1.0.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-httpx>=0.22.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# Doow Track Python SDK
|
|
33
|
+
|
|
34
|
+
[](https://pypi.org/project/doow-track/)
|
|
35
|
+
[](https://pypi.org/project/doow-track/)
|
|
36
|
+
[](LICENSE)
|
|
37
|
+
|
|
38
|
+
Official Python SDK for [Doow](https://doow.co) usage telemetry and management. Track SaaS usage, manage contracts, and monitor expenses.
|
|
39
|
+
|
|
40
|
+
## Features
|
|
41
|
+
|
|
42
|
+
| Feature | Description |
|
|
43
|
+
|---------|-------------|
|
|
44
|
+
| **Sync & Async** | Both synchronous and async APIs |
|
|
45
|
+
| **Batching** | Events queued and sent in configurable batches |
|
|
46
|
+
| **Compression** | Automatic gzip for payloads >1KB |
|
|
47
|
+
| **Retries** | Exponential backoff with configurable retry count |
|
|
48
|
+
| **Offline Storage** | Persist failed batches to disk |
|
|
49
|
+
| **Type Safety** | Full Pydantic models with type hints |
|
|
50
|
+
| **Hooks** | `before_send`, `before_flush`, `on_error` callbacks |
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install doow-track
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Quick Start
|
|
63
|
+
|
|
64
|
+
### Track Usage Events
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from doow_track import Tracker, TrackEvent
|
|
68
|
+
|
|
69
|
+
# Sync tracker
|
|
70
|
+
with Tracker("dk_your_api_key") as tracker:
|
|
71
|
+
tracker.track(TrackEvent(
|
|
72
|
+
metric="api_calls",
|
|
73
|
+
quantity=1,
|
|
74
|
+
license_id="lic_abc123",
|
|
75
|
+
))
|
|
76
|
+
|
|
77
|
+
# Rich event with attribution
|
|
78
|
+
tracker.track(TrackEvent(
|
|
79
|
+
metric="tokens_generated",
|
|
80
|
+
quantity=1500,
|
|
81
|
+
unit="tokens",
|
|
82
|
+
license_id="lic_abc123",
|
|
83
|
+
attribution={"model": "gpt-4", "region": "us-east-1"},
|
|
84
|
+
metadata={"request_id": "req_xyz"},
|
|
85
|
+
))
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Async Tracker
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
import asyncio
|
|
92
|
+
from doow_track import AsyncTracker, TrackEvent
|
|
93
|
+
|
|
94
|
+
async def main():
|
|
95
|
+
async with AsyncTracker("dk_your_api_key") as tracker:
|
|
96
|
+
await tracker.track(TrackEvent(
|
|
97
|
+
metric="api_calls",
|
|
98
|
+
quantity=1,
|
|
99
|
+
license_id="lic_abc123",
|
|
100
|
+
))
|
|
101
|
+
|
|
102
|
+
asyncio.run(main())
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Management API
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from doow_track import (
|
|
109
|
+
Management,
|
|
110
|
+
CreateAppInput,
|
|
111
|
+
CreateContractInput,
|
|
112
|
+
CreateMetricInput,
|
|
113
|
+
LicenseInput,
|
|
114
|
+
ContractType,
|
|
115
|
+
LicenseType,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
with Management("dk_your_api_key") as mgmt:
|
|
119
|
+
# Create an app
|
|
120
|
+
app = mgmt.apps.create(CreateAppInput(
|
|
121
|
+
name="My SaaS Platform",
|
|
122
|
+
description="Usage-based billing for API calls",
|
|
123
|
+
))
|
|
124
|
+
print(f"Created app: {app.id}")
|
|
125
|
+
|
|
126
|
+
# Create a contract with license
|
|
127
|
+
contract = mgmt.contracts.create(app.id, CreateContractInput(
|
|
128
|
+
title="Enterprise Plan",
|
|
129
|
+
contract_type=ContractType.PAY_AS_YOU_GO,
|
|
130
|
+
licenses=[
|
|
131
|
+
LicenseInput(name="API Usage", license_type=LicenseType.USAGE_BASED),
|
|
132
|
+
],
|
|
133
|
+
))
|
|
134
|
+
license_id = contract.licenses[0].id
|
|
135
|
+
|
|
136
|
+
# Create a metric
|
|
137
|
+
metric = mgmt.metrics.create(license_id, CreateMetricInput(
|
|
138
|
+
metric_type="api_calls",
|
|
139
|
+
))
|
|
140
|
+
print(f"Created metric: {metric.id}")
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Configuration
|
|
146
|
+
|
|
147
|
+
### Tracker Options
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
from doow_track import Tracker, TrackerOptions, FileOfflineStore
|
|
151
|
+
|
|
152
|
+
tracker = Tracker("dk_your_api_key", TrackerOptions(
|
|
153
|
+
# Connection
|
|
154
|
+
endpoint="https://api.doow.co",
|
|
155
|
+
timeout=10.0,
|
|
156
|
+
|
|
157
|
+
# Batching
|
|
158
|
+
flush_at=20, # Flush after N events
|
|
159
|
+
flush_interval=10.0, # Flush every N seconds
|
|
160
|
+
max_payload_bytes=450*1024,
|
|
161
|
+
max_queue_size=10000,
|
|
162
|
+
|
|
163
|
+
# Reliability
|
|
164
|
+
retry_count=3,
|
|
165
|
+
shutdown_timeout=5.0,
|
|
166
|
+
disable_compression=False,
|
|
167
|
+
|
|
168
|
+
# Persistence
|
|
169
|
+
offline_store=FileOfflineStore(),
|
|
170
|
+
|
|
171
|
+
# SDK-level attribution
|
|
172
|
+
attribution={
|
|
173
|
+
"service": "api-gateway",
|
|
174
|
+
"version": "1.2.3",
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
# Hooks
|
|
178
|
+
on_error=lambda e: print(f"Error: {e}"),
|
|
179
|
+
before_send=lambda e: e if e.quantity > 0 else None,
|
|
180
|
+
before_flush=lambda events: events,
|
|
181
|
+
|
|
182
|
+
# Control
|
|
183
|
+
enabled=True,
|
|
184
|
+
debug=True,
|
|
185
|
+
))
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Environment Variables
|
|
189
|
+
|
|
190
|
+
| Variable | Description | Default |
|
|
191
|
+
|----------|-------------|---------|
|
|
192
|
+
| `DOOW_TRACK_API_KEY` | API key (overrides constructor) | — |
|
|
193
|
+
| `DOOW_TRACK_ENDPOINT` | Custom API endpoint | `https://api.doow.co` |
|
|
194
|
+
| `DOOW_TRACK_DISABLED` | Set `true` to disable tracking | `false` |
|
|
195
|
+
| `DOOW_TRACK_DEBUG` | Set `true` for debug logs | `false` |
|
|
196
|
+
| `DOOW_TRACK_FLUSH_AT` | Events before flush | `20` |
|
|
197
|
+
| `DOOW_TRACK_FLUSH_INTERVAL` | Milliseconds between flushes | `10000` |
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Management API Reference
|
|
202
|
+
|
|
203
|
+
### Apps
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
# List
|
|
207
|
+
apps = mgmt.apps.list()
|
|
208
|
+
for app in apps.data:
|
|
209
|
+
print(app.name)
|
|
210
|
+
|
|
211
|
+
# Create
|
|
212
|
+
app = mgmt.apps.create(CreateAppInput(name="My App"))
|
|
213
|
+
|
|
214
|
+
# Get
|
|
215
|
+
app = mgmt.apps.get("app_id")
|
|
216
|
+
|
|
217
|
+
# Update
|
|
218
|
+
app = mgmt.apps.update("app_id", UpdateAppInput(name="New Name"))
|
|
219
|
+
|
|
220
|
+
# Delete
|
|
221
|
+
mgmt.apps.delete("app_id")
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Contracts
|
|
225
|
+
|
|
226
|
+
```python
|
|
227
|
+
# List contracts for an app
|
|
228
|
+
contracts = mgmt.contracts.list(app_id)
|
|
229
|
+
|
|
230
|
+
# Create with licenses
|
|
231
|
+
contract = mgmt.contracts.create(app_id, CreateContractInput(
|
|
232
|
+
title="Pro Plan",
|
|
233
|
+
contract_type=ContractType.PAY_AS_YOU_GO,
|
|
234
|
+
licenses=[
|
|
235
|
+
LicenseInput(name="API", license_type=LicenseType.USAGE_BASED),
|
|
236
|
+
],
|
|
237
|
+
))
|
|
238
|
+
|
|
239
|
+
# Get / Update / Delete
|
|
240
|
+
contract = mgmt.contracts.get(contract_id)
|
|
241
|
+
contract = mgmt.contracts.update(contract_id, UpdateContractInput(...))
|
|
242
|
+
mgmt.contracts.delete(contract_id)
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Licenses
|
|
246
|
+
|
|
247
|
+
```python
|
|
248
|
+
# List licenses for a contract
|
|
249
|
+
licenses = mgmt.licenses.list(contract_id)
|
|
250
|
+
|
|
251
|
+
# Get / Update / Delete
|
|
252
|
+
license = mgmt.licenses.get(license_id)
|
|
253
|
+
license = mgmt.licenses.update(license_id, UpdateLicenseInput(...))
|
|
254
|
+
mgmt.licenses.delete(license_id)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Metrics
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
# List metrics for a license
|
|
261
|
+
metrics = mgmt.metrics.list(license_id)
|
|
262
|
+
|
|
263
|
+
# Create
|
|
264
|
+
metric = mgmt.metrics.create(license_id, CreateMetricInput(
|
|
265
|
+
metric_type="api_calls",
|
|
266
|
+
))
|
|
267
|
+
|
|
268
|
+
# Get / Update / Delete
|
|
269
|
+
metric = mgmt.metrics.get(metric_id)
|
|
270
|
+
metric = mgmt.metrics.update(metric_id, UpdateMetricInput(...))
|
|
271
|
+
mgmt.metrics.delete(metric_id)
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Expenses
|
|
275
|
+
|
|
276
|
+
```python
|
|
277
|
+
from doow_track import ListExpensesParams
|
|
278
|
+
|
|
279
|
+
# List expenses
|
|
280
|
+
expenses = mgmt.expenses.list(ListExpensesParams(
|
|
281
|
+
app_id=app_id,
|
|
282
|
+
year=2026,
|
|
283
|
+
month=9,
|
|
284
|
+
))
|
|
285
|
+
|
|
286
|
+
# Get single expense
|
|
287
|
+
expense = mgmt.expenses.get(expense_id)
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## Error Handling
|
|
293
|
+
|
|
294
|
+
```python
|
|
295
|
+
from doow_track import APIError
|
|
296
|
+
|
|
297
|
+
try:
|
|
298
|
+
app = mgmt.apps.get("invalid_id")
|
|
299
|
+
except APIError as e:
|
|
300
|
+
if e.is_not_found():
|
|
301
|
+
print("Resource not found")
|
|
302
|
+
elif e.is_unauthorized():
|
|
303
|
+
print("Invalid API key")
|
|
304
|
+
elif e.is_forbidden():
|
|
305
|
+
print("Missing required scope")
|
|
306
|
+
elif e.is_rate_limited():
|
|
307
|
+
print("Rate limited, retry later")
|
|
308
|
+
else:
|
|
309
|
+
print(f"API error {e.status}: {e.message}")
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## Offline Storage
|
|
315
|
+
|
|
316
|
+
Persist failed batches to disk and automatically drain when connectivity returns:
|
|
317
|
+
|
|
318
|
+
```python
|
|
319
|
+
from doow_track import Tracker, TrackerOptions, FileOfflineStore
|
|
320
|
+
|
|
321
|
+
tracker = Tracker("dk_your_api_key", TrackerOptions(
|
|
322
|
+
offline_store=FileOfflineStore(), # defaults to ~/.doow/offline
|
|
323
|
+
))
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## Framework Integration
|
|
329
|
+
|
|
330
|
+
### FastAPI
|
|
331
|
+
|
|
332
|
+
```python
|
|
333
|
+
from contextlib import asynccontextmanager
|
|
334
|
+
from fastapi import FastAPI
|
|
335
|
+
from doow_track import AsyncTracker, TrackEvent
|
|
336
|
+
|
|
337
|
+
tracker: AsyncTracker
|
|
338
|
+
|
|
339
|
+
@asynccontextmanager
|
|
340
|
+
async def lifespan(app: FastAPI):
|
|
341
|
+
global tracker
|
|
342
|
+
tracker = AsyncTracker("dk_your_api_key")
|
|
343
|
+
yield
|
|
344
|
+
await tracker.shutdown()
|
|
345
|
+
|
|
346
|
+
app = FastAPI(lifespan=lifespan)
|
|
347
|
+
|
|
348
|
+
@app.post("/api/generate")
|
|
349
|
+
async def generate():
|
|
350
|
+
await tracker.track(TrackEvent(
|
|
351
|
+
metric="api_calls",
|
|
352
|
+
quantity=1,
|
|
353
|
+
license_id="lic_abc123",
|
|
354
|
+
))
|
|
355
|
+
return {"result": "ok"}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Django
|
|
359
|
+
|
|
360
|
+
```python
|
|
361
|
+
# settings.py
|
|
362
|
+
DOOW_API_KEY = "dk_your_api_key"
|
|
363
|
+
|
|
364
|
+
# apps.py
|
|
365
|
+
from django.apps import AppConfig
|
|
366
|
+
from doow_track import Tracker
|
|
367
|
+
|
|
368
|
+
class MyAppConfig(AppConfig):
|
|
369
|
+
name = "myapp"
|
|
370
|
+
tracker = None
|
|
371
|
+
|
|
372
|
+
def ready(self):
|
|
373
|
+
from django.conf import settings
|
|
374
|
+
MyAppConfig.tracker = Tracker(settings.DOOW_API_KEY)
|
|
375
|
+
|
|
376
|
+
# views.py
|
|
377
|
+
from doow_track import TrackEvent
|
|
378
|
+
from .apps import MyAppConfig
|
|
379
|
+
|
|
380
|
+
def my_view(request):
|
|
381
|
+
MyAppConfig.tracker.track(TrackEvent(
|
|
382
|
+
metric="page_views",
|
|
383
|
+
quantity=1,
|
|
384
|
+
license_id="lic_abc123",
|
|
385
|
+
))
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## License
|
|
391
|
+
|
|
392
|
+
MIT
|