v0.1.0-dev: initial public extraction + new abstractions

This commit is contained in:
Sulkta 2026-04-23 18:04:00 -07:00
commit e38120cf11
17 changed files with 2429 additions and 0 deletions

69
cardano_checkout/store.py Normal file
View file

@ -0,0 +1,69 @@
"""Persistence abstraction for Invoice objects.
The SDK does not prescribe a database. Consumers implement
:class:`InvoiceStore` against whatever backend suits them SQLAlchemy
(the host app pattern), SQLite (example-studio pattern), Postgres raw
(high-volume pattern), in-memory dict (tests).
All methods are async so the same Protocol works cleanly for both
asyncpg/asyncio-sqlalchemy backends and synchronous backends wrapped
with ``asyncio.to_thread``.
"""
from __future__ import annotations
from typing import Optional, Protocol, runtime_checkable
from cardano_checkout.invoice import Invoice, InvoiceStatus
@runtime_checkable
class InvoiceStore(Protocol):
"""Persistence backend for invoices.
Consumers implement the six methods below. The SDK's monitor + scheduler
modules operate entirely through this interface, never touching a specific
ORM or driver.
"""
async def create(self, invoice: Invoice) -> None:
"""Insert a new invoice. Should raise if an invoice with the same id exists."""
...
async def get(self, invoice_id: str) -> Optional[Invoice]:
"""Fetch one invoice by id. Returns None if not found."""
...
async def list_by_status(
self, status: InvoiceStatus, limit: int = 100
) -> list[Invoice]:
"""List invoices in a given state, newest-first. Used by the monitor poll loop."""
...
async def update(self, invoice: Invoice) -> None:
"""Persist the current state of an invoice.
Implementations should compare-and-set on ``invoice.id`` if the row
doesn't exist the call should raise. Does NOT create; see :meth:`create`.
"""
...
async def next_derivation_index(self, merchant_id: str) -> int:
"""Return the next unused receive-address index for a merchant.
Should be transactionally safe against concurrent invoice creation;
consumers typically implement this via ``SELECT COALESCE(MAX(index), -1) + 1 ... FOR UPDATE``
or an atomic counter row.
"""
...
async def record_tx(
self, invoice_id: str, tx_hash: str, lovelace_delta: int
) -> None:
"""Record an observed inbound UTxO against an invoice.
Must be idempotent on (invoice_id, tx_hash) monitor loops will
re-observe the same UTxO until the invoice transitions to a terminal
state.
"""
...