drip-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.
@@ -0,0 +1,30 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ *.so
5
+ .Python
6
+ build/
7
+ develop-eggs/
8
+ dist/
9
+ downloads/
10
+ eggs/
11
+ .eggs/
12
+ lib/
13
+ lib64/
14
+ parts/
15
+ sdist/
16
+ var/
17
+ wheels/
18
+ *.egg-info/
19
+ .installed.cfg
20
+ *.egg
21
+ .env
22
+ .venv
23
+ env/
24
+ venv/
25
+ .pytest_cache/
26
+ .mypy_cache/
27
+ .ruff_cache/
28
+ .coverage
29
+ htmlcov/
30
+ .DS_Store
drip_sdk-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Drip Team
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,420 @@
1
+ Metadata-Version: 2.4
2
+ Name: drip-sdk
3
+ Version: 0.1.0
4
+ Summary: Official Python SDK for Drip - usage-based billing infrastructure with on-chain settlement
5
+ Project-URL: Homepage, https://drippay.dev
6
+ Project-URL: Documentation, https://docs.drippay.dev
7
+ Project-URL: Repository, https://github.com/drip/sdk-python
8
+ Author-email: Drip <sdk@drippay.dev>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: api,billing,blockchain,drip,metered-billing,payments,sdk,usage-based
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
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.10
22
+ Requires-Dist: httpx>=0.25.0
23
+ Requires-Dist: pydantic>=2.0.0
24
+ Provides-Extra: all
25
+ Requires-Dist: fastapi>=0.100.0; extra == 'all'
26
+ Requires-Dist: flask>=2.0.0; extra == 'all'
27
+ Requires-Dist: langchain-core>=0.1.0; extra == 'all'
28
+ Requires-Dist: python-dotenv>=1.0.0; extra == 'all'
29
+ Requires-Dist: starlette>=0.27.0; extra == 'all'
30
+ Provides-Extra: dev
31
+ Requires-Dist: mypy>=1.0.0; extra == 'dev'
32
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
33
+ Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
34
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
35
+ Requires-Dist: respx>=0.20.0; extra == 'dev'
36
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
37
+ Provides-Extra: dotenv
38
+ Requires-Dist: python-dotenv>=1.0.0; extra == 'dotenv'
39
+ Provides-Extra: fastapi
40
+ Requires-Dist: fastapi>=0.100.0; extra == 'fastapi'
41
+ Requires-Dist: starlette>=0.27.0; extra == 'fastapi'
42
+ Provides-Extra: flask
43
+ Requires-Dist: flask>=2.0.0; extra == 'flask'
44
+ Provides-Extra: langchain
45
+ Requires-Dist: langchain-core>=0.1.0; extra == 'langchain'
46
+ Description-Content-Type: text/markdown
47
+
48
+ # Drip SDK (Python)
49
+
50
+ Drip is a Python SDK for **usage-based tracking and execution logging** in systems where spend is tied to computation — AI agents, APIs, batch jobs, and infra workloads.
51
+
52
+ This **Core SDK** is optimized for pilots: capture usage and run data first, add billing later.
53
+
54
+ **One line to start tracking:** `drip.track_usage(customer_id, meter, quantity)`
55
+
56
+ [![PyPI version](https://img.shields.io/pypi/v/drip-sdk.svg)](https://pypi.org/project/drip-sdk/)
57
+ [![Python](https://img.shields.io/pypi/pyversions/drip-sdk.svg)](https://pypi.org/project/drip-sdk/)
58
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
59
+
60
+ ---
61
+
62
+ ## 60-Second Quickstart (Core SDK)
63
+
64
+ ### 1. Install
65
+
66
+ ```bash
67
+ pip install drip-sdk
68
+ ```
69
+
70
+ ### 2. Set your API key
71
+
72
+ ```bash
73
+ # Secret key — full access (server-side only)
74
+ export DRIP_API_KEY=sk_test_...
75
+
76
+ # Or public key — usage, customers, billing (safe for client-side)
77
+ export DRIP_API_KEY=pk_test_...
78
+ ```
79
+
80
+ Or use a `.env` file (recommended):
81
+
82
+ ```bash
83
+ pip install drip-sdk[dotenv] # or: pip install python-dotenv
84
+ ```
85
+
86
+ ```env
87
+ # .env
88
+ DRIP_API_KEY=sk_test_...
89
+ DRIP_API_URL=https://api.drippay.dev # optional, defaults to this
90
+ ```
91
+
92
+ The SDK auto-loads `.env` files when `python-dotenv` is installed — no extra code needed.
93
+
94
+ ### 3. Create a customer and track usage
95
+
96
+ ```python
97
+ from drip import drip
98
+
99
+ # Create a customer first
100
+ customer = drip.create_customer(external_customer_id="user_123")
101
+
102
+ # Track usage — that's it
103
+ drip.track_usage(customer_id=customer.id, meter="api_calls", quantity=1)
104
+ ```
105
+
106
+ The `drip` singleton reads `DRIP_API_KEY` from your environment automatically.
107
+
108
+ ### Alternative: Explicit Configuration
109
+
110
+ ```python
111
+ from drip import Drip
112
+
113
+ # Auto-reads DRIP_API_KEY from environment
114
+ client = Drip()
115
+
116
+ # Or pass config explicitly with a secret key (full access)
117
+ client = Drip(api_key="sk_test_...")
118
+
119
+ # Or with a public key (limited scope, safe for client-side)
120
+ client = Drip(api_key="pk_test_...")
121
+ ```
122
+
123
+ ### Full Example
124
+
125
+ ```python
126
+ from drip import drip
127
+
128
+ # Verify connectivity
129
+ drip.ping()
130
+
131
+ # Create a customer (at least one of external_customer_id or onchain_address required)
132
+ customer = drip.create_customer(external_customer_id="user_123")
133
+
134
+ # Record usage
135
+ drip.track_usage(
136
+ customer_id=customer.id,
137
+ meter="llm_tokens",
138
+ quantity=842,
139
+ metadata={"model": "gpt-4o-mini"}
140
+ )
141
+
142
+ # Record execution lifecycle
143
+ drip.record_run(
144
+ customer_id=customer.id,
145
+ workflow="research-agent",
146
+ events=[
147
+ {"event_type": "llm.call", "quantity": 1700, "units": "tokens"},
148
+ {"event_type": "tool.call", "quantity": 1},
149
+ ],
150
+ status="COMPLETED"
151
+ )
152
+
153
+ print(f"Customer {customer.id}: usage + run recorded")
154
+ ```
155
+
156
+ **Expected result:**
157
+ - No exceptions
158
+ - Events appear in the Drip dashboard within seconds
159
+
160
+ ---
161
+
162
+ ## Core Concepts
163
+
164
+ | Concept | Description |
165
+ |---------|-------------|
166
+ | `customer_id` | The entity you're attributing usage to |
167
+ | `meter` | What's being measured (tokens, calls, time, etc.) |
168
+ | `quantity` | Numeric usage value |
169
+ | `run` | A single request or job execution |
170
+ | `correlation_id` | Optional. Your trace/request ID for linking Drip data with your APM (OpenTelemetry, Datadog, etc.) |
171
+
172
+ **Status values:** `PENDING` | `RUNNING` | `COMPLETED` | `FAILED`
173
+
174
+ **Event schema:** Payloads are schema-flexible. Drip stores events as structured JSON and does not enforce a fixed event taxonomy. CamelCase is accepted.
175
+
176
+ > **Distributed tracing:** Pass `correlation_id` to `start_run()`, `record_run()`, or `emit_event()` to cross-reference Drip billing with your observability stack. See [FULL_SDK.md](./FULL_SDK.md#distributed-tracing-correlation_id) for details.
177
+
178
+ ---
179
+
180
+ ## Idempotency Keys
181
+
182
+ Every mutating SDK method (`charge`, `track_usage`, `emit_event`) requires an `idempotency_key`. The server uses this key to deduplicate requests — if two requests share the same key, only the first is processed. The parameter is optional in the method signature because **the SDK always generates one for you if you don't provide it**.
183
+
184
+ `record_run` generates idempotency keys internally for its batch events (using `external_run_id` when provided, otherwise deterministic keys).
185
+
186
+ ### Auto-generated keys (default)
187
+
188
+ When you omit `idempotency_key`, the SDK generates one automatically — this works for both `drip.core.Drip` and the full `Drip` client. The auto key is:
189
+
190
+ - **Unique per call** — two separate calls with identical parameters produce different keys (a monotonic counter ensures this).
191
+ - **Stable across retries** — the key is generated once and reused for all retry attempts of that call, so network retries are safely deduplicated.
192
+ - **Deterministic** — no randomness; keys are reproducible for debugging.
193
+
194
+ This means you get **free retry safety** with zero configuration.
195
+
196
+ ### When to pass explicit keys
197
+
198
+ Pass your own `idempotency_key` when you need **application-level deduplication** — e.g., to guarantee that a specific business operation is billed exactly once, even across process restarts:
199
+
200
+ ```python
201
+ customer = drip.create_customer(external_customer_id="user_123")
202
+
203
+ drip.charge(
204
+ customer_id=customer.id,
205
+ meter="api_calls",
206
+ quantity=1,
207
+ idempotency_key=f"order_{order_id}_charge", # your business-level key
208
+ )
209
+ ```
210
+
211
+ Common patterns:
212
+ - `order_{order_id}` — one charge per order
213
+ - `run_{run_id}_step_{step_index}` — one charge per pipeline step
214
+ - `invoice_{invoice_id}` — one charge per invoice
215
+
216
+ ---
217
+
218
+ ## Installation Options
219
+
220
+ ```bash
221
+ pip install drip-sdk # core only
222
+ pip install drip-sdk[fastapi] # FastAPI helpers
223
+ pip install drip-sdk[flask] # Flask helpers
224
+ pip install drip-sdk[all] # everything
225
+ ```
226
+
227
+ ---
228
+
229
+ ## SDK Variants
230
+
231
+ | Variant | Description |
232
+ |---------|-------------|
233
+ | **Core SDK** (recommended for pilots) | Usage tracking + execution logging only |
234
+ | **Full SDK** | Includes billing, balances, and workflows (for later stages) |
235
+
236
+ ---
237
+
238
+ ## Core SDK Methods
239
+
240
+ All methods are on the `Drip` / `AsyncDrip` class. Start with these for pilots:
241
+
242
+ | Method | Description |
243
+ |--------|-------------|
244
+ | `ping()` | Verify API connection |
245
+ | `create_customer(...)` | Create a customer (see below) |
246
+ | `get_or_create_customer(external_customer_id)` | Idempotently create or retrieve a customer by external ID |
247
+ | `get_customer(customer_id)` | Get customer details |
248
+ | `list_customers(options)` | List all customers |
249
+ | `track_usage(params)` | Record metered usage |
250
+ | `charge(customer_id, meter, quantity, ...)` | Create a billable charge (sync — waits for settlement) |
251
+ | `charge_async(customer_id, meter, quantity, ...)` | Async charge — returns immediately, processes in background |
252
+ | `list_charges(options)` | List charges for your business |
253
+ | `get_charge(charge_id)` | Get a single charge by ID |
254
+ | `list_events(options)` | List execution events with filters |
255
+ | `get_event(event_id)` | Get a single event by ID |
256
+ | `get_event_trace(event_id)` | Get event causality trace (ancestors, children, retries) |
257
+ | `record_run(params)` | Log complete agent run (simplified) |
258
+ | `start_run(params)` | Start execution trace |
259
+ | `emit_event(params)` | Log event within run |
260
+ | `emit_events_batch(params)` | Batch log events |
261
+ | `end_run(run_id, params)` | Complete execution trace |
262
+ | `get_run(run_id)` | Get run details and summary |
263
+ | `get_run_timeline(run_id)` | Get execution timeline |
264
+ | `get_balance(customer_id)` | Get customer balance |
265
+ | `check_entitlement(customer_id, feature_key, quantity?)` | Pre-request authorization check |
266
+ | `run(workflow, customer_id)` | Context manager for run tracking (see below) |
267
+
268
+ ### Run Context Manager
269
+
270
+ ```python
271
+ with drip.run("research-agent", customer_id=customer.id) as run:
272
+ run.event("llm.call", quantity=1700, units="tokens")
273
+ run.event("tool.call", quantity=1)
274
+ # Run auto-completes on exit, or marks FAILED on exception
275
+ ```
276
+
277
+ ### Creating Customers
278
+
279
+ All parameters are optional, but at least one of `external_customer_id` or `onchain_address` must be provided:
280
+
281
+ ```python
282
+ # Simplest — just your internal user ID
283
+ customer = drip.create_customer(external_customer_id="user_123")
284
+
285
+ # With an on-chain address (for on-chain billing)
286
+ customer = drip.create_customer(
287
+ onchain_address="0x1234...",
288
+ external_customer_id="user_123"
289
+ )
290
+
291
+ # Internal/non-billing customer (for tracking only)
292
+ customer = drip.create_customer(
293
+ external_customer_id="internal-team",
294
+ is_internal=True
295
+ )
296
+ ```
297
+
298
+ | Parameter | Type | Required | Description |
299
+ |-----------|------|----------|-------------|
300
+ | `external_customer_id` | `str` | No* | Your internal user/account ID |
301
+ | `onchain_address` | `str` | No* | Customer's Ethereum address |
302
+ | `is_internal` | `bool` | No | Mark as internal (non-billing). Default: `False` |
303
+ | `metadata` | `dict` | No | Arbitrary key-value metadata |
304
+
305
+ \*At least one of `external_customer_id` or `onchain_address` is required.
306
+
307
+ ---
308
+
309
+ ## Async Core SDK
310
+
311
+ ```python
312
+ from drip import AsyncDrip
313
+
314
+ async with AsyncDrip(api_key="sk_test_...") as client:
315
+ await client.ping()
316
+
317
+ # Create a customer
318
+ customer = await client.create_customer(external_customer_id="user_123")
319
+
320
+ await client.track_usage(
321
+ customer_id=customer.id,
322
+ meter="api_calls",
323
+ quantity=1
324
+ )
325
+
326
+ result = await client.record_run(
327
+ customer_id=customer.id,
328
+ workflow="research-agent",
329
+ events=[...],
330
+ status="COMPLETED"
331
+ )
332
+ ```
333
+
334
+ ---
335
+
336
+ ## Who This Is For
337
+
338
+ - AI agents (token metering, tool calls, execution traces)
339
+ - API companies (per-request billing, endpoint attribution)
340
+ - RPC providers (multi-chain call tracking)
341
+ - Cloud/infra (compute seconds, storage, bandwidth)
342
+
343
+ ---
344
+
345
+ ## Full SDK (Billing, Entitlements, Webhooks, Subscriptions, Invoices)
346
+
347
+ For billing, entitlements, subscriptions, invoices, contracts, webhooks, middleware, and advanced features:
348
+
349
+ ```python
350
+ from drip import Drip
351
+
352
+ client = Drip(api_key="sk_test_...")
353
+
354
+ # Check if a customer can use a feature before processing
355
+ check = client.check_entitlement(customer.id, "search")
356
+
357
+ if not check.allowed:
358
+ # Over quota — return 429 without wasting compute
359
+ pass
360
+ ```
361
+
362
+ Key methods:
363
+
364
+ | Method | Description |
365
+ |--------|-------------|
366
+ | `get_balance(customer_id)` | Get customer balance (USDC, pending, available) |
367
+ | `check_entitlement(customer_id, feature_key, quantity?)` | Pre-request authorization check (allowed/denied + remaining quota) |
368
+ | `set_customer_spending_cap(customer_id, cap_type, limit_value)` | Set daily/monthly/single-charge spending cap |
369
+ | `get_customer_spending_caps(customer_id)` | List active spending caps |
370
+ | `remove_customer_spending_cap(customer_id, cap_id)` | Remove a spending cap |
371
+ | `checkout(params)` | Create hosted checkout session for top-ups |
372
+
373
+ Highlights:
374
+ - **Billing** — `charge()`, `list_charges()`, `get_charge()`, `get_balance()`
375
+ - **Cost Estimation** — `estimate_from_usage()`, `estimate_from_hypothetical()` for budget planning
376
+ - **Spending Caps** — per-customer daily/monthly limits with multi-level alerts at 50%, 80%, 95%, 100%
377
+ - **Entitlements** — pre-request quota gating with `check_entitlement()`
378
+ - **Subscription billing** — create, update, pause, resume, cancel
379
+ - **Invoices** — available via REST API (SDK methods planned)
380
+ - **Contracts** — available via REST API (SDK methods planned)
381
+ - **Webhooks** — create, verify, manage webhook endpoints
382
+ - **Middleware** — FastAPI and Flask integrations
383
+
384
+ See **[FULL_SDK.md](./FULL_SDK.md)** for complete documentation.
385
+
386
+ ---
387
+
388
+ ## Error Handling
389
+
390
+ ```python
391
+ from drip import Drip, DripError, DripAPIError
392
+
393
+ client = Drip(api_key="sk_test_...")
394
+ customer = client.create_customer(external_customer_id="user_123")
395
+
396
+ try:
397
+ result = client.track_usage(customer_id=customer.id, meter="api_calls", quantity=1)
398
+ except DripAPIError as e:
399
+ print(f"API error {e.status_code}: {e.message}")
400
+ except DripError as e:
401
+ print(f"Error: {e}")
402
+ ```
403
+
404
+ ---
405
+
406
+ ## Requirements
407
+
408
+ - Python 3.10+
409
+ - httpx
410
+ - pydantic
411
+
412
+ ## Links
413
+
414
+ - [Full SDK Documentation](./FULL_SDK.md)
415
+ - [API Documentation](https://docs.drippay.dev/api-reference)
416
+ - [PyPI](https://pypi.org/project/drip-sdk/)
417
+
418
+ ## License
419
+
420
+ MIT