Skip to content

feat: usage metering + monthly quota primitive (usage_metering.py)#198

Open
seonghobae wants to merge 1 commit into
mainfrom
feat/usage-metering
Open

feat: usage metering + monthly quota primitive (usage_metering.py)#198
seonghobae wants to merge 1 commit into
mainfrom
feat/usage-metering

Conversation

@seonghobae

Copy link
Copy Markdown
Contributor

Why

codec-carver has API-key auth (who is calling) but nothing between that and billing (how much they owe). This PR adds the missing monetization primitive: durable per-key usage metering with monthly quota enforcement. It is the foundation for tiered plans (e.g. free = 50 conversions/month, pro = 10 GB/month) and for billing exports.

What

New stdlib-only module usage_metering.py (no existing files touched):

  • UsageStore(db_path) — SQLite-backed (WAL mode), thread-safe via short-lived connections plus a process lock. Schema: usage(api_key, period, conversions, input_bytes, output_bytes, PRIMARY KEY(api_key, period)) with period = "YYYY-MM".
  • record(api_key, *, input_bytes, output_bytes, now) — atomically increments the current period's counters via SQL upsert. now is injected by the caller (the module never calls datetime.now()), so behavior is deterministic and testable.
  • usage(api_key, now) — current-period totals; unknown keys report zero.
  • check_quota(api_key, now, *, max_conversions=None, max_bytes=None) — returns True if allowed; raises QuotaExceededError carrying limit_name, limit, and used so callers/API responses can say exactly which limit was hit.
  • reset(api_key) / all_usage(period) — admin and billing-export helpers.
  • Clear ValueError for malformed db paths (missing parent dir, path is a directory) and negative byte counts.

Tests

tests/test_usage_metering.py — 21 tests on a tmp sqlite file with injected now, no network:

  • accumulation within a period; new period starts fresh; per-key isolation; durability across reopen
  • quota allow/deny exactly at the boundary (conversions and bytes); QuotaExceededError names the limit; quota resets in a new period
  • concurrent record() from 8 threads sums correctly (200 increments)
  • unknown key = zero usage and always allowed; malformed db path errors

Gates

  • python3.14 -m unittest discover -s tests: 134 tests, no new failures (only the 5 pre-existing macOS os.listxattr errors from baseline)
  • python3.14 -m interrogate -c pyproject.toml usage_metering.py: 100% docstring coverage (PASSED)

🤖 Generated with Claude Code

Durable per-API-key usage counters (conversions, input/output bytes)
backed by stdlib sqlite3 in WAL mode, bucketed by YYYY-MM period, with
quota enforcement via check_quota() raising QuotaExceededError that
names the exceeded limit. Callers inject 'now' for deterministic tests.
Includes admin helpers reset() and all_usage(period).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CJRVbDrp1vGYkJgNHMGPpG
@seonghobae seonghobae enabled auto-merge (squash) July 6, 2026 14:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant