Skip to content

fastapi-sqlalchemy-pg-catalog: minimal repro sample for keploy/integrations#193#102

Open
AkashKumar7902 wants to merge 10 commits into
mainfrom
add-fastapi-sqlalchemy-pg-catalog
Open

fastapi-sqlalchemy-pg-catalog: minimal repro sample for keploy/integrations#193#102
AkashKumar7902 wants to merge 10 commits into
mainfrom
add-fastapi-sqlalchemy-pg-catalog

Conversation

@AkashKumar7902
Copy link
Copy Markdown
Contributor

Summary

  • Minimal FastAPI + SQLAlchemy 2.0 + psycopg2 + Postgres 13 sample that exercises the Postgres v3 dispatcher's simple-Query ClassCatalog branch via SQLAlchemy's Base.metadata.create_all pg_class probe.
  • Used by the keploy/integrations Woodpecker lane .woodpecker/sqlalchemy-pg-catalog-postgres.yml (companion PR in keploy/integrations) to falsify the bug fixed there.

Why this sample exists

keploy/integrations#193 — the v3 dispatcher's simple-Query ClassCatalog branch in dispatchBySQLHash skips the recorded mock lookup and goes straight to the synthetic catalog engine. The extended-Query branch in runEngineForPortal does the right thing (probe recorded mock first, fall back to synthetic on miss). The two paths diverge on the same SQL.

SQLAlchemy 2.x with psycopg2 routes Base.metadata.create_all's pg_catalog.pg_class probe via the simple-Query protocol (psycopg2 client-side substitutes %(param)s placeholders and sends as a single SQL string, no Bind/Execute). At record time, with the table already present, the probe returns 1 row and CREATE TABLE is skipped. The recorder saves the probe as type: query, class: CATALOG with rows=[("project",)] / CommandComplete="SELECT 1".

At replay, the pre-fix dispatcher ignores that recorded mock, the synthetic catalog engine returns rows: 0, cc: "SELECT 0", SQLAlchemy interprets "table missing", issues an unrecorded CREATE TABLE project (...), the transactional engine misses, and the app worker dies with psycopg2.DatabaseError: keploy-pg-v3: no recorded invocation matched. Every HTTP testcase that follows fails with 404 because uvicorn is dead.

What's in here

fastapi-sqlalchemy-pg-catalog/
├── README.md
├── app/
│   ├── Dockerfile           # python:3.12-slim
│   ├── main.py              # FastAPI + lifespan create_all + 2 endpoints
│   └── requirements.txt     # fastapi 0.115.0, sqlalchemy 2.0.36, psycopg2-binary 2.9.10
├── docker-compose.yml       # postgres:13.22-alpine + app, env-templated for matrix isolation
├── flow.sh                  # GET /health + GET /projects
└── init.sql                 # pre-creates `project` so record-time create_all is a no-op

The compose is env-templated (APP_CONTAINER, DB_CONTAINER, APP_HOST_PORT, COMPOSE_NET) so the integrations 3-cell matrix can isolate concurrent cells on one Docker daemon. All four have sane defaults that match the local repro recipe in the README.

Verification

Local end-to-end repro confirmed against Keploy 3.5.4 (current main carries the bug):

  • Record: 3 testcases captured; mocks.yaml contains the class: CATALOG mock with bind values [project, r, p, f, v, m, pg_catalog] — the exact shape the issue documents.
  • Replay (pre-fix, current main): 0/3 pass. Agent log carries the exact executeSimple dispatch class=CATALOG / catalog: pattern matched pg_class / catalog: response built rows=0 chain quoted in #193, followed by executeSimple dispatch class=DDL CREATE TABLE project and transactional: no invocation matched (candidates=0). App log carries psycopg2.DatabaseError: keploy-pg-v3: no recorded invocation matched and Application startup failed. Exiting.exited with code 3.

Test plan

  • docker compose build works
  • docker compose up -d && bash flow.sh (baseline, no keploy) — both endpoints return 200
  • keploy record produces 3 testcases + the expected class: CATALOG mock in mocks.yaml
  • keploy test against pre-fix dispatcher: 0/3 pass with the documented cascade signature
  • keploy test against the fixed dispatcher: 3/3 pass (validated in the integrations lane PR)

References

Copilot AI review requested due to automatic review settings May 11, 2026 18:40
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new minimal reproduction sample (fastapi-sqlalchemy-pg-catalog) demonstrating the Keploy Postgres v3 simple-query ClassCatalog replay mismatch described in keploy/integrations#193, using FastAPI + SQLAlchemy 2.x + psycopg2 + Postgres 13.

Changes:

  • Introduces a small FastAPI app that runs Base.metadata.create_all() during lifespan startup and exposes /health and /projects.
  • Adds Docker Compose + Postgres init SQL to ensure the project table exists at record time (so the pg_class probe is exercised and CREATE TABLE is skipped/never recorded).
  • Adds a flow.sh script and README with local record/replay instructions.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
fastapi-sqlalchemy-pg-catalog/README.md Documents the bug, local reproduction steps, and sample layout.
fastapi-sqlalchemy-pg-catalog/init.sql Initializes the project table (and seeds a row) to force the intended record-time behavior.
fastapi-sqlalchemy-pg-catalog/flow.sh Drives minimal HTTP traffic for record/replay runs.
fastapi-sqlalchemy-pg-catalog/docker-compose.yml Defines Postgres + app services with healthcheck and env-templated isolation knobs.
fastapi-sqlalchemy-pg-catalog/app/requirements.txt Pins FastAPI/uvicorn/SQLAlchemy/psycopg2-binary versions for the repro.
fastapi-sqlalchemy-pg-catalog/app/main.py Minimal FastAPI + SQLAlchemy app that triggers the pg_class probe during startup.
fastapi-sqlalchemy-pg-catalog/app/Dockerfile Container build for the app service.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread fastapi-sqlalchemy-pg-catalog/flow.sh Outdated
Comment thread fastapi-sqlalchemy-pg-catalog/flow.sh
…ations#193

FastAPI + SQLAlchemy 2.x + psycopg2 + Postgres 13 sample built to
exercise the v3 dispatcher's simple-query ClassCatalog branch. The
sample's init.sql pre-creates the `project` table so SQLAlchemy's
Base.metadata.create_all skips CREATE TABLE at record time -- the
shape the dispatcher bug requires.

Used by the keploy/integrations Woodpecker lane
sqlalchemy-pg-catalog-postgres to assert that recorded `type: query`
mocks with `class: CATALOG` are consulted by the simple-query path,
not just the extended-query path.

Signed-off-by: Akash Kumar <meakash7902@gmail.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Comment thread fastapi-sqlalchemy-pg-catalog/flow.sh Outdated
Comment thread fastapi-sqlalchemy-pg-catalog/flow.sh Outdated
Comment thread fastapi-sqlalchemy-pg-catalog/init.sql Outdated
… review

flow.sh:
- set -Eeuo pipefail so curl/other failures actually fail the script.
- Track readiness explicitly; exit 1 with a clear message if the app
  never reaches /health within READY_TIMEOUT_S (default 60s) instead
  of silently falling through and proceeding against a dead app.
- curl -fsS for the readiness probe and the endpoint calls so HTTP
  failures propagate as exit codes (the previous `curl -sS ... && echo`
  shape silenced non-2xx responses).

init.sql:
- Replace `INSERT ... ON CONFLICT DO NOTHING` with `INSERT ... SELECT
  WHERE NOT EXISTS`. The model has no UNIQUE constraint on name, so
  ON CONFLICT had nothing to fire on; this form is genuinely idempotent
  on re-runs against an existing volume.

Refs Copilot review on #102.

Signed-off-by: Akash Kumar <meakash7902@gmail.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Comment thread fastapi-sqlalchemy-pg-catalog/README.md Outdated
Comment thread fastapi-sqlalchemy-pg-catalog/app/main.py
Addresses two Copilot review nits on #102:

* README.md: the local-repro snippet hardcoded
  `--container-name pg-catalog-repro-app`, but the compose file uses
  `${APP_CONTAINER:-pg-catalog-repro-app}` so a user who overrides
  APP_CONTAINER (e.g. to isolate concurrent runs) would have keploy
  point at a non-existent container. Both keploy invocations now
  thread `${APP_CONTAINER:-pg-catalog-repro-app}` so they pick up
  the same env override compose sees. Also switched the deprecated
  camelCase `--apiTimeout`/`--disableMockUpload` to the kebab forms
  the v3 CLI actually registers (`--api-timeout`; mock-upload flag
  dropped — not registered on v3-dev `test`).

* main.py: SQL echo was unconditional. It is INTENTIONALLY on by
  default for this sample (the whole point is to surface SQLAlchemy's
  pg_class probe + create_all behaviour in the app log so a reader
  can correlate it with the keploy agent log), but the noise is real
  for unrelated investigations. Gated behind SQL_ECHO env var with a
  comment explaining why it's on by default.

Signed-off-by: Akash Kumar <meakash7902@gmail.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Comment thread fastapi-sqlalchemy-pg-catalog/app/main.py Outdated
Comment thread fastapi-sqlalchemy-pg-catalog/app/main.py Outdated
…-safe create_all

Two nits from the Copilot review:

* DATABASE_URL: `os.environ["DATABASE_URL"]` raised a bare KeyError
  at import time, which surfaces as a noisy traceback in container
  logs with no hint about what's missing. Switched to os.getenv with
  an explicit RuntimeError that names the env var and gives a sample
  URL format.

* lifespan: `Base.metadata.create_all(engine)` is synchronous
  psycopg2 I/O running inside an async lifespan, blocking uvicorn's
  event loop until pg_class probe + any CREATE TABLE round-trips
  complete. Switched to `await asyncio.to_thread(...)` so the loop
  stays responsive. For this minimal repro the difference is small,
  but the pattern is the right FastAPI shape for any startup that
  touches a sync DB driver.

Signed-off-by: Akash Kumar <meakash7902@gmail.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

@AkashKumar7902 AkashKumar7902 requested a review from Copilot May 12, 2026 06:19
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Comment thread fastapi-sqlalchemy-pg-catalog/README.md
Comment thread fastapi-sqlalchemy-pg-catalog/README.md
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Comment thread fastapi-sqlalchemy-pg-catalog/app/main.py Outdated
…te_engine

SQLAlchemy 2.x defaults to the future-2.0 behaviour; passing
`future=True` is redundant and can trip a deprecation warning in
some 2.x point releases. Dropped to keep the sample free of incidental
warning noise that would distract from the dispatcher-bug repro.

Refs Copilot review on #102.

Signed-off-by: Akash Kumar <meakash7902@gmail.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Comment thread fastapi-sqlalchemy-pg-catalog/app/main.py Outdated
…utdown

Add try/finally around the lifespan yield so engine.dispose() runs
at shutdown. Releases pooled psycopg2 connections cleanly across
repeated start/stop cycles (local repro loops, CI lane reruns),
which otherwise leak half-open postgres connections.

Refs Copilot review on #102.

Signed-off-by: Akash Kumar <meakash7902@gmail.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Comment thread fastapi-sqlalchemy-pg-catalog/app/main.py Outdated
…y/finally

Copilot noted that the previous try/finally only covered the yield,
so a failure in create_all (the *exact* failure mode this repro
demonstrates: pre-fix keploy makes create_all raise
psycopg2.DatabaseError) wouldn't reach the finally and the engine
pool would leak. Moved the startup logging + create_all call inside
the try block; finally now runs regardless of where the failure
occurs.

Signed-off-by: Akash Kumar <meakash7902@gmail.com>
@AkashKumar7902 AkashKumar7902 requested a review from Copilot May 12, 2026 07:19
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Comment thread fastapi-sqlalchemy-pg-catalog/init.sql Outdated
…lly matters

Copilot noted /docker-entrypoint-initdb.d scripts only run on first
database initialization, so the previous comment's framing ('re-run
against an existing volume') was misleading. Reworded to call out
the actual scenario: this is a single-shot insert on a clean volume,
and NOT EXISTS is defensive coverage for stale-volume reuse.

Signed-off-by: Akash Kumar <meakash7902@gmail.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Comment thread fastapi-sqlalchemy-pg-catalog/init.sql Outdated
… runs on first init

Copilot's repeated note: /docker-entrypoint-initdb.d scripts run only
on first database init. On a stale data volume the script doesn't
run at all, so the NOT EXISTS guard couldn't help anyway. Simplified
to a plain INSERT and updated the comment to tell readers to recreate
the volume (docker compose down -v) if they want a deterministic
repro.

Signed-off-by: Akash Kumar <meakash7902@gmail.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

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.

2 participants