Skip to content

feat: selectable database driver (psycopg or asyncpg) - #62

Draft
Apakottur wants to merge 6 commits into
mainfrom
experiment/asyncpg
Draft

feat: selectable database driver (psycopg or asyncpg)#62
Apakottur wants to merge 6 commits into
mainfrom
experiment/asyncpg

Conversation

@Apakottur

@Apakottur Apakottur commented Jul 12, 2026

Copy link
Copy Markdown
Owner

What

Make the database driver selectable rather than hard-swapping psycopg for asyncpg. Both drivers sit behind the _db abstraction; a driver argument picks one.

  • Add driver to generate / agenerate ("psycopg" default, or "asyncpg"), and --driver / PGMIG_DRIVER on the CLI. Threaded down to introspect_db and the connection.
  • _db.py grows an asyncpg implementation alongside psycopg, chosen with match driver: in connect / execute / introspect.
  • Base package depends on psycopg; add install extras: pgmig[binary] (psycopg prebuilt wheel), pgmig[c] (psycopg compiled against system libpq), pgmig[asyncpg].
  • Cast the "char" catalog columns (attgenerated, matview relkind) to text so asyncpg and psycopg return identical values.
  • Share one asyncio event loop across fixtures and tests (asyncpg binds connections to the loop that created them); add a --driver pytest flag; CI runs one asyncpg cell (py3.14 / pg18).
  • README FAQ documents the driver/build tradeoffs (libpq, binary vs c, asyncpg).

Behavior

No change to emitted SQL. Both drivers produce identical migrations — the full suite (316 tests) passes under each. Default is driver="psycopg", so existing users are unaffected; asyncpg is opt-in via the extra plus the argument.

Notes

Benchmarked psycopg vs asyncpg on introspection: psycopg is ~1.2–1.3x faster (introspection is a handful of one-shot catalog queries, where asyncpg's per-statement prepare cost outweighs its throughput edge; psycopg's binary/c builds widen the gap). Hence psycopg is the default. pgmig[binary] is offered but not the default — its bundled libpq/OpenSSL only update with the wheel, which psycopg discourages for production; pgmig[c] is the production-fast option.

Apakottur and others added 5 commits July 12, 2026 15:41
Replace synchronous psycopg3 with asyncpg for the database introspection
path (src/pgmig/_build/*). build_db_info keeps its sync signature via
asyncio.run, so api.py, the CLI, and tests are untouched.

The 10 loaders and _run_query become async; connect uses server_settings
for the read-only transaction. Bad-DSN/connection failures are surfaced as
PgmigError (catching OSError, PostgresError, InterfaceError).

asyncpg uses the binary protocol, which decodes the pg internal "char" type
as bytes. A non-identity attidentity is byte \x00, which pydantic coerced to
the non-empty string '\x00' and broke the identity check. Casting the four
returned "char" columns (attidentity, contype, prokind, relkind) to ::text
restores the old text-protocol values.

Test fixtures stay on psycopg (DDL setup, orthogonal to the read path).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolve conflicts from the _build->_introspect rename (#112) and the
async-psycopg introspection refactor (#124):
- Drop the obsolete _build/* edits; re-apply the asyncpg driver swap onto
  the current _introspect read path (_core, _engine, _context).
- _connect: asyncpg.connect with session read-only + REPEATABLE READ, and a
  json/jsonb text codec so nested jsonb parses into models.
- run_introspection_query: fetch via asyncpg and build models from records.
- Cast attidentity::text to match the other char columns.
- Regenerate uv.lock for asyncpg.
# Conflicts:
#	src/pgmig/_introspect/_context.py
#	src/pgmig/_introspect/_core.py
#	src/pgmig/_introspect/_engine.py
match driver in _db.py, thread driver arg through api/cli/engine, --driver
pytest flag, both drivers in dev deps, cast char catalog cols to text.
Both driver suites pass (316 each). Lint/type-check + packaging default TBD.
Add a driver argument to generate/agenerate (and --driver / PGMIG_DRIVER on the
CLI) selecting psycopg (default) or asyncpg behind the _db abstraction. Package
psycopg as the base dependency with binary/c/asyncpg install extras. Cast char
catalog columns to text so both drivers agree. Run one asyncpg cell (py3.14/pg18)
in CI. Document the driver/build tradeoffs in the README FAQ.
@Apakottur Apakottur changed the title experiment: swap psycopg3 for asyncpg on introspection read path feat: selectable database driver (psycopg or asyncpg) Jul 16, 2026
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014h6yDcdivduaRr31BviN1C
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