trader.persistence.db module

Engine, session factory, and schema creation for the PostgreSQL database (issue #26 — migrated off SQLite; see docs/invariants.md for why).

exception trader.persistence.db.DatabaseError(statement, params, orig, hide_parameters=False, connection_invalidated=False, code=None, ismulti=None)[source]

Bases: DBAPIError

Wraps a DB-API DatabaseError.

Parameters:
  • statement (Optional[str])

  • params (Optional[_AnyExecuteParams])

  • orig (BaseException)

  • hide_parameters (bool)

  • connection_invalidated (bool)

  • code (Optional[str])

  • ismulti (Optional[bool])

code = '4xp6'
trader.persistence.db.create_db_engine(database_url)[source]

Build an engine for the Postgres database at database_url.

pool_pre_ping=True: unlike a SQLite file, Postgres is a real network/ socket service that can go away mid-process (a brew services restart, a dropped Unix socket). Without this, a stale pooled connection surfaces as a mystifying error mid-cycle instead of being detected and replaced before use.

No PRAGMA setup here (the pre-migration SQLite version of this function had one): journal_mode/busy_timeout have no Postgres equivalent — MVCC and its own connection-level lock manager already give concurrent readers/writers what WAL mode existed to provide — and foreign_keys=ON is unconditional on Postgres, never opt-in.

Parameters:

database_url (str)

Return type:

Engine

trader.persistence.db.create_session_factory(engine)[source]

Build a session factory bound to engine.

Parameters:

engine (Engine)

Return type:

sessionmaker[Session]

trader.persistence.db.init_db(database_url, stamp=True)[source]

Create every table in the (already-existing) Postgres database at database_url. Safe to run repeatedly.

Uses create_all rather than replaying migrations because the test suite builds around a hundred-plus throwaway databases and replaying history for each costs far more than it proves. The drift that would otherwise allow is caught by tests/persistence/test_migrations.py.

The new database is then stamped at head, so Alembic knows its tables already exist and a later upgrade applies only genuinely new revisions. Pass stamp=False to skip that — worth it only for a throwaway database in a tight loop, since stamping opens a second connection.

Unlike the pre-migration SQLite version, this does not create the database itself — CREATE DATABASE is a privileged, out-of-band operation (see tests/persistence/pg_testing.py for the test-suite fixture that does it), never something an app connection triggers implicitly by connecting.

Parameters:
  • database_url (str)

  • stamp (bool)

Return type:

Engine