Skip to main content

ducto.interface.base module

Abstract credit store interface.

All credit operations happen through a CreditStore adapter. This lets the package work with Supabase (via RPCs), vanilla PostgreSQL, or in-memory stores for testing.

class ducto.interface.base.CreditStore

Bases: ABC

Interface for credit storage backends.

Implementors provide concrete adapters for Supabase, raw PostgreSQL, or in-memory stores.

abstractmethod activate_pricing(version: int) → str

Activate a specific pricing version (deactivates all others).

Args: : version: The version number to activate.

Returns: : The activated config id.

abstractmethod add_credits(user_id: str, amount: int, type: str = 'adjustment', metadata: CreditMetadata | None = None, expires_at: datetime | None = None) → AddCreditsResult

Atomically add credits and log a transaction.

Args: : expires_at: Optional datetime after which the credits expire.

abstractmethod add_team_member(team_id: str, user_id: str, role: str = 'member', spend_cap: int | None = None) → AddTeamMemberResult

Add a user to a team.

Args: : team_id: The team’s UUID. user_id: The user’s UUID. role: Member role (e.g. “member”, “admin”). spend_cap: Optional per-user spend cap.

Returns: : AddTeamMemberResult confirming membership.

abstractmethod aggregate_stats(start: datetime, end: datetime) → AggregateStatsRow

Aggregate statistics across all users in a time window.

Args: : start: Start of time window (inclusive). end: End of time window (inclusive).

Returns: : AggregateStatsRow with total credits consumed, active users, average daily spend, top model, and top user.

abstractmethod check_allowance(user_id: str) → AllowanceResult

Get remaining free allowance for current billing period.

check_feature(user_id: str, feature: str) → CheckFeatureResult

Check whether a user’s plan has a specific feature entitlement.

Convenience method. Default implementation calls get_user_plan() and inspects the features dict. Override in custom stores for optimized queries.

Feature values follow a truthy convention:

  • False / None / absent → has_feature=False
  • True / numeric / string → has_feature=True

abstractmethod check_spend_cap(user_id: str, model: str | None = None, amount: int | None = None) → CapCheckResult

Check whether a pending deduction would exceed any configured cap.

Args: : user_id: The user to check caps for. model: Optional model name for per-model caps. amount: The pending deduction amount.

Returns: : CapCheckResult with the check result.

abstractmethod create_team(name: str, initial_balance: int = 0) → CreateTeamResult

Create a team with a shared credit balance pool.

Args: : name: Human-readable team name. initial_balance: Starting credit balance.

Returns: : CreateTeamResult with the new team id.

abstractmethod daily_spend(start: datetime, end: datetime) → list[DailySpendRow]

Daily spend aggregation in a time window.

Args: : start: Start of time window (inclusive). end: End of time window (inclusive).

Returns: : List of DailySpendRow with per-day totals.

abstractmethod deduct_credits(user_id: str, reservation_id: str, amount: int, idempotency_key: str | None = None, metadata: CreditMetadata | None = None) → DeductionResult

Finalize a credit deduction and release the reservation.

If idempotency_key is provided and a matching transaction already exists, returns the existing result (idempotent replay).

abstractmethod deduct_team(team_id: str, user_id: str, amount: int, metadata: CreditMetadata | None = None) → TeamDeductionResult

Deduct credits from a team pool, attributed to a user.

Args: : team_id: The team’s UUID. user_id: The user to attribute the deduction to. amount: Credits to deduct. metadata: Extra metadata.

Returns: : TeamDeductionResult with transaction details.

abstractmethod get_active_pricing() → PricingConfigResult | None

Fetch the active pricing configuration from the store.

abstractmethod get_balance(user_id: str) → BalanceResult

Return current balance and lifetime purchased amount.

abstractmethod get_pricing_config(version: int) → PricingConfigResult | None

Fetch a specific pricing config by version number.

abstractmethod get_pricing_history() → list[PricingConfigHistoryItem]

List all pricing config versions (newest first).

abstractmethod get_team_balance(team_id: str) → TeamBalanceResult

Fetch team balance and member count.

Args: : team_id: The team’s UUID.

Returns: : TeamBalanceResult with balance and member count.

abstractmethod get_team_members(team_id: str) → list[TeamMember]

List all members of a team.

Args: : team_id: The team’s UUID.

Returns: : List of TeamMember.

abstractmethod get_user_plan(user_id: str) → GetUserPlanResult

Fetch user’s current plan (including feature entitlements).

abstractmethod increment_usage_window(user_id: str, plan_id: str, amount: int) → None

Record allowance consumption for current billing period.

abstractmethod refund_credits(transaction_id: str, amount: int | None = None, reason: str | None = None, metadata: CreditMetadata | None = None) → RefundResult

Refund a previous credit deduction.

Args: : transaction_id: The transaction to refund. amount: Optional partial refund amount. Full refund if omitted. reason: Optional reason for the refund. metadata: Extra metadata to attach to the refund transaction.

Returns: : RefundResult with the refund transaction details, or error set if the transaction doesn’t exist or is already refunded.

abstractmethod reserve_credits(user_id: str, amount: int, operation_type: str, metadata: CreditMetadata | None = None, min_balance: int = 5) → ReserveResult

Reserve credits for an upcoming operation.

Locks the user row to prevent concurrent overspend. Returns a ReserveResult with error set on failure.

abstractmethod set_active_pricing(config: PricingConfigData, label: str | None = None) → str

Publish a new pricing configuration.

Deactivates the previous active config and inserts a new one. Returns the new config id.

abstractmethod set_user_plan(user_id: str, plan_id: str) → SetUserPlanResult

Assign a plan to a user.

abstractmethod setup(database_url: str | None = None) → SetupResult

Run bundled SQL migrations (tables, indexes, RPCs).

Idempotent — safe to call on every deploy.

Args: : database_url: Postgres connection string. Required for stores : that manage schema setup directly (HttpxSupabaseStore, PostgresStore). Ignored by in-memory stores.

abstractmethod spend_by_model(start: datetime, end: datetime) → list[SpendByModelRow]

Aggregate spend by model in a time window.

Args: : start: Start of time window (inclusive). end: End of time window (inclusive).

Returns: : List of SpendByModelRow with totals per model.

abstractmethod spend_by_user(start: datetime, end: datetime) → list[SpendByUserRow]

Aggregate spend by user in a time window.

Args: : start: Start of time window (inclusive). end: End of time window (inclusive).

Returns: : List of SpendByUserRow with totals per user.

abstractmethod sweep_expired_credits(dry_run: bool = False) → SweepResult

Sweep expired credits from all users’ balances.

Args: : dry_run: If True, report what would be expired without modifying.

Returns: : SweepResult with count and amount of expired credits.

abstractmethod top_users(limit: int, start: datetime, end: datetime) → list[TopUserRow]

Top users by spend in a time window.

Args: : limit: Maximum number of users to return. start: Start of time window (inclusive). end: End of time window (inclusive).

Returns: : List of TopUserRow sorted by total_spend descending.

exception ducto.interface.base.StoreError

Bases: Exception

Base exception for store-level errors (connection, timeout, etc.).