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.
@@ -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
@@ -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
+ [![PyPI](https://img.shields.io/pypi/v/doow-track?color=blue)](https://pypi.org/project/doow-track/)
35
+ [![Python](https://img.shields.io/pypi/pyversions/doow-track)](https://pypi.org/project/doow-track/)
36
+ [![License](https://img.shields.io/badge/License-MIT-blue.svg)](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