cupo 0.0.1__py3-none-any.whl

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.
cupo/__init__.py ADDED
@@ -0,0 +1,26 @@
1
+ """Cupo — usage limits, feature gates, and metering for AI products.
2
+
3
+ This is a name-reservation release. The project is in design phase:
4
+ the full API specification is public and open for feedback at
5
+
6
+ https://github.com/estebangastia/cupo
7
+
8
+ v0.1 (Python SDK, embedded mode, plans-as-code, FastAPI middleware,
9
+ token-aware Anthropic/OpenAI wrappers) is in development.
10
+ """
11
+
12
+ __version__ = "0.0.1"
13
+
14
+
15
+ def _not_yet(*_args, **_kwargs):
16
+ raise NotImplementedError(
17
+ "Cupo is in design phase. Follow progress and leave feedback at "
18
+ "https://github.com/estebangastia/cupo"
19
+ )
20
+
21
+
22
+ class Cupo:
23
+ """Placeholder for the Cupo client. See module docstring."""
24
+
25
+ def __init__(self, *args, **kwargs):
26
+ _not_yet()
@@ -0,0 +1,185 @@
1
+ Metadata-Version: 2.4
2
+ Name: cupo
3
+ Version: 0.0.1
4
+ Summary: Usage limits, feature gates, and metering for AI products. Drop-in for FastAPI and Next.js.
5
+ Project-URL: Homepage, https://github.com/estebangastia/cupo
6
+ Project-URL: Repository, https://github.com/estebangastia/cupo
7
+ Project-URL: Issues, https://github.com/estebangastia/cupo/issues
8
+ Author: Esteban Gastiazoro
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: ai,billing,entitlements,fastapi,llm,metering,rate-limiting,saas,usage-limits
12
+ Classifier: Development Status :: 1 - Planning
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Libraries
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+
23
+ # Cupo
24
+
25
+ **Usage limits, feature gates, and metering for AI products. Drop-in for FastAPI and Next.js.**
26
+
27
+ > ⚠️ **Status: design phase / request for comments.** This README describes the API we intend to build. We're validating the design before writing the code — if this would (or wouldn't) solve a problem for you, please open an issue or comment. Brutal honesty welcome.
28
+
29
+ ---
30
+
31
+ ## The problem
32
+
33
+ Billing platforms (Stripe, Lago, Metronome, Orb) answer *"how much do I charge?"* — they count usage and generate invoices **after the fact**.
34
+
35
+ None of them answer the question your code asks a hundred times per second: *"is this customer allowed to do this, right now?"*
36
+
37
+ So every AI SaaS ends up hand-rolling the same thing:
38
+
39
+ - A `usage` table with counters that break under concurrent requests
40
+ - Plan limits scattered across `if customer.plan == "pro"` checks
41
+ - Token counting glued onto LLM calls (and silently wrong for streaming)
42
+ - A cron job that resets counters monthly (usually in the wrong timezone)
43
+ - No warning to the customer before they hit the wall
44
+
45
+ Cupo is that layer, done once, done right, and open source.
46
+
47
+ ## What it looks like
48
+
49
+ ```python
50
+ from cupo import Cupo
51
+
52
+ cupo = Cupo() # embedded mode: uses your existing Postgres, no server needed
53
+
54
+ @app.post("/chat")
55
+ @cupo.protect(feature="ai_chat") # blocks with 429 + upgrade info if over limit
56
+ async def chat(req: ChatRequest, customer: Customer):
57
+ ...
58
+ ```
59
+
60
+ Or with explicit control:
61
+
62
+ ```python
63
+ res = cupo.check(customer.id, feature="ai_chat", units=1) # atomic check-and-consume
64
+ if not res.allowed:
65
+ return JSONResponse(429, {"error": "plan_limit", "resets_at": res.resets_at,
66
+ "upgrade_url": res.upgrade_url})
67
+
68
+ reply = anthropic.messages.create(...)
69
+
70
+ cupo.track(customer.id, feature="ai_chat",
71
+ tokens=reply.usage.output_tokens,
72
+ idempotency_key=req.id) # safe to retry, never double-counts
73
+ ```
74
+
75
+ ## Plans as code
76
+
77
+ Plans live in a versioned YAML file in your repo — reviewable in a PR, not hidden in a dashboard:
78
+
79
+ ```yaml
80
+ # cupo.yaml
81
+ features:
82
+ ai_chat: { unit: message }
83
+ ai_tokens: { unit: token }
84
+ pdf_export: { unit: export }
85
+
86
+ plans:
87
+ free:
88
+ ai_chat: { limit: 50, window: month }
89
+ ai_tokens: { limit: 100_000, window: month }
90
+ pdf_export: false
91
+
92
+ pro:
93
+ ai_chat: { limit: 5_000, window: month, on_limit: degrade } # block | degrade | bill
94
+ ai_tokens: { limit: 10_000_000, window: month }
95
+ pdf_export: true
96
+
97
+ enterprise:
98
+ ai_chat: unlimited
99
+ ai_tokens: { limit: 100_000_000, window: month, on_limit: bill, overage_price: 0.50/1_000_000 }
100
+ pdf_export: true
101
+ ```
102
+
103
+ `on_limit` policies:
104
+
105
+ - **block** — deny the request (default)
106
+ - **degrade** — allow it, but flag `res.degraded = True` so you can route to a cheaper model
107
+ - **bill** — allow it and emit an overage event your billing system can invoice
108
+
109
+ ## Token-aware AI helpers
110
+
111
+ The part everyone gets wrong. Cupo ships thin wrappers around the Anthropic and OpenAI clients that meter tokens automatically — **including streaming**, where usage is only known when the stream ends:
112
+
113
+ ```python
114
+ from cupo.anthropic import metered
115
+
116
+ client = metered(anthropic.Anthropic(), cupo, feature="ai_tokens")
117
+
118
+ # streaming: tokens are tracked when the stream closes, with the request's idempotency key
119
+ with client.messages.stream(model="claude-sonnet-4-6", ...) as stream:
120
+ for text in stream.text_stream:
121
+ yield text
122
+ ```
123
+
124
+ ## How it works
125
+
126
+ ```
127
+ your app ──▶ Cupo SDK ──▶ counters (your Postgres, or Redis, or Cupo server)
128
+
129
+ ├─ local entitlement cache (checks add ~0 network latency)
130
+ └─ async usage flush (batched, idempotent)
131
+ ```
132
+
133
+ Three problems Cupo solves so you don't have to:
134
+
135
+ 1. **Atomicity.** Two concurrent requests with one credit left: exactly one passes. Counters use atomic operations (`INCR` / row-level locks), never read-modify-write.
136
+ 2. **Latency.** Entitlements are cached in-process and synced in the background. A `check()` is a dictionary lookup, not a network call. Trade-off: near the limit, a small overshoot is possible — this is configurable (`strict: true` forces a synchronous check for expensive features).
137
+ 3. **Idempotency.** Every `track()` takes an idempotency key. Retries, at-least-once queues, and network flakiness never double-count.
138
+
139
+ **Failure mode is yours to choose:** `fail_open` (if Cupo is unreachable, allow the request — default, your product stays up) or `fail_closed` (deny — for features where overshoot costs you real money).
140
+
141
+ ## Deployment modes
142
+
143
+ | Mode | What it needs | For |
144
+ |---|---|---|
145
+ | **Embedded** | Your existing Postgres (Supabase works) | Solo devs, single service |
146
+ | **Server** | Docker container + Redis | Multiple services / languages |
147
+ | **Cloud** (planned) | Nothing — hosted | Teams that want dashboards, analytics, SLA |
148
+
149
+ ## Webhooks
150
+
151
+ The server emits events so you can warn customers *before* they hit the wall:
152
+
153
+ - `usage.threshold` — configurable (e.g. at 80% of any limit)
154
+ - `usage.limit_reached`
155
+ - `usage.overage` — with units and computed price, ready to forward to your billing
156
+
157
+ ## What Cupo is not
158
+
159
+ - **Not a billing platform.** It doesn't generate invoices or charge cards. It pairs with Stripe, Mercado Pago, Lago, or whatever you already use (plan sync integrations are on the roadmap).
160
+ - **Not an API gateway.** It runs inside your app, not in front of it.
161
+ - **Not for enterprise contract management.** If you have negotiated multi-year commits with drawdowns, you want Metronome or Orb.
162
+
163
+ ## FAQ
164
+
165
+ **vs. Lago / Metronome / Orb?** Those meter usage to *bill* it. Cupo meters usage to *enforce* it, in the request path, in real time. Different layer — Cupo can feed them.
166
+
167
+ **vs. Stigg?** Closest neighbor. Stigg is a hosted, dashboard-first entitlements platform aimed at teams. Cupo is open source, config-as-code, and designed so a solo developer is enforcing limits 15 minutes after `pip install cupo`.
168
+
169
+ **vs. rolling my own?** You can. Most people's version has the concurrency bug, misses streaming tokens, and has no idempotency. Ours has tests for all three.
170
+
171
+ **Why should I trust the counters?** Every counter mutation is an atomic operation with tests that hammer it concurrently. The test suite is part of the pitch — read it.
172
+
173
+ ## Roadmap
174
+
175
+ - [ ] **v0.1** — Python SDK, embedded mode (Postgres), plans-as-code, FastAPI middleware, Anthropic/OpenAI metered wrappers, docs + 3 runnable examples
176
+ - [ ] **v0.2** — Standalone server (Docker), TypeScript SDK, webhooks, Redis counters
177
+ - [ ] **v0.3** — Stripe & Mercado Pago plan sync, usage dashboard, hosted cloud (free tier + flat self-serve pricing — no "talk to sales")
178
+
179
+ ## License
180
+
181
+ SDKs: MIT. Server: AGPL-3.0. Self-hosting is free forever; the hosted cloud is how the project sustains itself.
182
+
183
+ ---
184
+
185
+ **Would you use this?** Open an issue titled `feedback:` and tell us — especially if the answer is no and why. If you've hand-rolled this layer before, we'd love 20 minutes of your war stories.
@@ -0,0 +1,5 @@
1
+ cupo/__init__.py,sha256=0ONcxWdnFWHSVK4upYC8GAxYo17nxhke755XVteQVew,733
2
+ cupo-0.0.1.dist-info/METADATA,sha256=0-mA_to0NXq9g1D8e3i7FsM9dUJxhZYkBmKzoHc7Wf4,8029
3
+ cupo-0.0.1.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
4
+ cupo-0.0.1.dist-info/licenses/LICENSE,sha256=3o60Is5xYlltn6oHE7a_8LTGu4cpJMU8LzFXfp9AEis,1075
5
+ cupo-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Esteban Gastiazoro
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.