Skip to main content

openframe-adapters-db-cockroachdb

CockroachDB database adapter for the OpenFrame Microservice Suite.

Part of the openframe-adapters monorepo. Implements BaseRepository[T] and HealthCheck from openframe-core using asyncpg — the same driver used by openframe-adapters-db-postgres, because CockroachDB speaks the PostgreSQL wire protocol. This package is an adaptation of the Postgres adapter, not a from-scratch build; see "CockroachDB vs. Postgres differences" below for everything that is genuinely different.


Installation

pip install openframe-adapters-db-cockroachdb

Required env var:

COCKROACHDB_URL=postgresql://user:password@host:26257/dbname

Note the scheme is still postgresql:// — asyncpg only speaks the wire protocol, it has no notion of "CockroachDB" as a distinct backend. Point the URL at your CockroachDB node or load balancer exactly as you would a Postgres primary.


Quick start

Raw dict mode

from openframe.adapters.db.cockroachdb import CockroachdbSettings, CockroachdbRepository

settings = CockroachdbSettings()  # reads COCKROACHDB_URL from env
repo = CockroachdbRepository(settings, table="items", id_column="id")

item = await repo.get("abc-123")          # dict | None
items, total = await repo.list(10, 0)     # ([dict, ...], int)
created = await repo.create({"name": "x"})
updated = await repo.update({"id": "abc-123", "name": "y"})
deleted = await repo.delete("abc-123")    # bool

Typed domain mode

from dataclasses import dataclass
from openframe.adapters.db.cockroachdb import CockroachdbSettings, CockroachdbRepository

@dataclass
class Item:
    id: str
    name: str

class ItemRepository(CockroachdbRepository[Item]):
    _table = "items"
    _id_column = "id"

    def _row_to_entity(self, row) -> Item:
        return Item(**dict(row))

    def _entity_to_row(self, entity: Item) -> dict:
        return {"id": entity.id, "name": entity.name}

settings = CockroachdbSettings()
repo = ItemRepository(settings)
item: Item | None = await repo.get("abc-123")

Wiring into an application

For a real service, wire CockroachdbPlugin (the BasePort-satisfying plugin class) through ApplicationBootstrap.compose() from openframe-core. This gives you proper lifecycle management — initialize() / health() / shutdown() — for free, instead of constructing CockroachdbRepository directly and managing the pool yourself:

from openframe.core.runtime import ApplicationBootstrap
from openframe.core.ports import Capability
from openframe.adapters.db.cockroachdb import CockroachdbPlugin, CockroachdbSettings

settings = CockroachdbSettings()  # reads COCKROACHDB_URL from env
plugin = CockroachdbPlugin(settings, table="items", id_column="id")

async with ApplicationBootstrap.compose(plugin) as app:
    repo = app.get(Capability.PERSISTENCE)   # -> CockroachdbRepository
    item = await repo.get("abc-123")
# pool is closed automatically on exit (plugin.shutdown() ran)

compose() calls plugin.initialize() on entry and plugin.shutdown() on exit, so the pool is created, health-checked, and torn down without any manual lifecycle code. Requires openframe-core>=3.3.

Reach for a subclassed ApplicationBootstrap (with a configure() method) only when you need per-port config=/init_timeout= or conditional registration order; use app.registry as an escape hatch for anything neither tier covers. The CockroachdbRepository(settings) construction shown above under "Quick start" remains valid for tests, scripts, or any context that doesn't need plugin lifecycle management.


Raw driver access for niche features

The adapter never hides asyncpg. Access it directly for anything the port does not cover:

from openframe.adapters.db.cockroachdb import get_cockroachdb_pool

class OrderRepository(CockroachdbRepository[Order]):
    _table     = "orders"
    _id_column = "id"

    async def bulk_upsert(self, orders: list[Order]) -> None:
        pool = await get_cockroachdb_pool(self._settings)
        async with pool.acquire() as conn:
            async with conn.transaction():
                for o in orders:
                    await conn.execute(
                        "UPSERT INTO orders (id, total) VALUES ($1, $2)",
                        o.id, o.total,
                    )

CockroachDB vs. Postgres differences

CockroachDB speaks the same wire protocol as Postgres, so connection pooling, the asyncpg exception hierarchy used for connection-vs-query classification, and health checks (SELECT 1) are all identical to the Postgres adapter. Two real differences matter at the schema/transaction boundary:

No SERIAL/BIGSERIAL

CockroachDB does not implement Postgres's SERIAL/BIGSERIAL auto-increment column types the same way. If your schema uses SERIAL against a real CockroachDB cluster, it likely will not behave as you expect. The idiomatic CockroachDB primary-key patterns are:

-- UUID primary key (recommended default)
id UUID PRIMARY KEY DEFAULT gen_random_uuid()

-- or, if you want an integer key
id INT PRIMARY KEY DEFAULT unique_rowid()

This adapter does not translate or rewrite DDL — it only issues the DML your repository methods construct against whatever schema already exists. Design your CREATE TABLE statements with CockroachDB's own primary-key conventions, not a copy-pasted Postgres schema.

CockroachDB transaction retries (SQLSTATE 40001)

CockroachDB always runs at SERIALIZABLE isolation (there is no lower isolation level to opt into). Under contention this can produce a retryable "transaction retry error" — SQLSTATE 40001, with restart transaction in the error message — that requires the client to retry the entire transaction, not just the failing statement.

This adapter's basic CRUD methods (get/list/create/update/delete) each execute as an implicit single-statement transaction, so this class of error is rare in practice for simple single-statement operations — there is no multi-statement transaction for CockroachDB to need to restart.

However, if you use this package's "raw driver access" pattern (above) to run your own explicit multi-statement transaction via conn.transaction(), you are responsible for retrying it on SQLSTATE 40001:

import asyncpg

async def transfer_funds(pool, from_id: str, to_id: str, amount: int) -> None:
    for attempt in range(5):
        try:
            async with pool.acquire() as conn:
                async with conn.transaction():
                    await conn.execute(
                        "UPDATE accounts SET balance = balance - $1 WHERE id = $2",
                        amount, from_id,
                    )
                    await conn.execute(
                        "UPDATE accounts SET balance = balance + $1 WHERE id = $2",
                        amount, to_id,
                    )
            return
        except asyncpg.PostgresError as exc:
            if getattr(exc, "sqlstate", None) == "40001":
                continue  # retry the whole transaction
            raise
    raise RuntimeError("transfer_funds: exhausted retries on SQLSTATE 40001")

This adapter deliberately does not implement automatic retry inside its own CRUD methods — that would be a surprising, undocumented behavior change from how the Postgres adapter behaves for the same driver calls. The difference is documented here so callers doing their own multi-statement transactions know it exists and can implement the retry loop themselves.

Everything else about this adapter — connection pooling, asyncpg exception classification, health checks — is unchanged from the Postgres adapter.


Configuration

All settings are read from environment variables.

Env var Type Default Description
COCKROACHDB_URL str required Full asyncpg DSN (postgresql:// scheme) pointed at a CockroachDB cluster
POOL_SIZE int 10 Pool min/max size
POOL_MAX_INACTIVE_CONN_LIFETIME float 300.0 Idle connection TTL (s)
POOL_COMMAND_TIMEOUT float 60.0 Per-statement timeout (s)
POOL_MAX_QUERIES int 50000 Queries per connection before recycle
CONNECTION_TIMEOUT float 30.0 Pool creation timeout (s)
OPERATION_TIMEOUT float 10.0 Per-operation timeout (s)
MAX_RETRIES int 3 Max retry attempts

Health checks

CockroachdbRepository implements the HealthCheck protocol from openframe-core.

health = await repo.health()   # PluginHealth snapshot — the sole health check, never raises

Exception hierarchy

All exceptions are AdapterError subclasses from openframe.core.exceptions. Raw asyncpg exceptions never escape the adapter.

Situation Exception
Cannot connect to CockroachDB AdapterConnectionError
Invalid COCKROACHDB_URL catalog AdapterConfigurationError
Query failed (constraint, syntax, SQLSTATE 40001, etc.) AdapterQueryError
Entity not found AdapterNotFoundError
Operation exceeded timeout AdapterTimeoutError

A CockroachDB SQLSTATE 40001 transaction-retry error surfaces as AdapterQueryError like any other in-band query failure — see "CockroachDB transaction retries" above for why this adapter does not retry it automatically, and how to retry it yourself for explicit multi-statement transactions.


Development

# from the package directory
pip install -e ".[dev]"
python -m pytest tests/ -v

Protocol conformance

from openframe.core.ports import BaseRepository
from openframe.core.health import HealthCheck

repo = CockroachdbRepository(settings, table="items", id_column="id")
assert isinstance(repo, BaseRepository)   # True — structural check
assert isinstance(repo, HealthCheck)      # True — structural check

No inheritance from either Protocol is required or used.


Resilience — circuit breaking under sustained failure

openframe-core>=3.4 ships openframe.core.resilience.CircuitBreakerProxy — wrap the repository to short-circuit calls after repeated failures instead of blocking every caller until operation_timeout during a sustained outage. No adapter code changes are needed to support this — it composes from the outside exactly like TracingProxy:

from openframe.core.resilience import CircuitBreakerProxy
from openframe.core.tracing import TracingProxy

repo = CircuitBreakerProxy(
    TracingProxy(app.get(Capability.PERSISTENCE).get_repository(), prefix="repository.item"),
    failure_threshold=5,
    reset_timeout=30.0,
)

Wrap the traced repository, not the reverse — a short-circuited call never reaches the adapter, so it shouldn't produce a misleading adapter span.


License

MIT

Metadata

Release files for openframe-adapters-db-cockroachdb 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for openframe-adapters-db-cockroachdb 0.1.0
File Size Uploaded
openframe_adapters_db_cockroachdb-0.1.0.tar.gz 23.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for openframe-adapters-db-cockroachdb 0.1.0
File Interpreter ABI Platform
openframe_adapters_db_cockroachdb-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 41.7 kB

Release files / openframe_adapters_db_cockroachdb-0.1.0.tar.gz

Download URL openframe_adapters_db_cockroachdb-0.1.0.tar.gz
Size 23.1 kB
Tags Source
SHA-256 checksum
How to use checksums
f8309557b66e0a832b87d30a45e1d7ec0b1ee81b638dbbaed1df2a7413e06e38
BLAKE2b-256 checksum
How to use checksums
cd864b3a24ddec56904f03ce84a5c9c5294adfab847ed72d74da9c2ceb3a97fa
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / openframe_adapters_db_cockroachdb-0.1.0-py3-none-any.whl

Download URL openframe_adapters_db_cockroachdb-0.1.0-py3-none-any.whl
Size 18.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
87b8c87408218e149637d9c2ee23d4644af94b8dbc058f49cf33fd78c5c4fe10
BLAKE2b-256 checksum
How to use checksums
3362325b015efc16eb100fae6ff1ded01ad760f754ac36dd8077e3a2b288875f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page