Skip to main content

pytest-capquery

Build Status Codecov Python Version License

Testing your business logic is good, but documenting and testing your database interactions is critical.

pytest-capquery treats your SQL queries as first-class citizens in your Pytest suite. By capturing and asserting the exact queries executed, you create a living documentation of what is truly happening behind the ORM abstraction.

This plugin does not force any specific SQLAlchemy architectural changes or optimization strategies. It delegates all design decisions to the developer, acting strictly as a deterministic guardrail. Once you've optimized your query footprint, pytest-capquery locks it in, ensuring cross-dialect equality, validating exact transaction boundaries (BEGIN, COMMIT, ROLLBACK), and catching silent N+1 regressions the second they are introduced.

Key Features

  • Contextual Isolation: Use the capture() context manager to track queries locally without global state leakage or manual resets.
  • SQL Snapshots: Automatically generate and track expected .sql snapshots to easily document executed queries without cluttering test files.
  • Strict Timeline Assertion: Validate the exact chronological sequence of SQL strings and transaction events.
  • Auto-Generating Assertions: When explicit assertions fail, the plugin drops a fully formatted, copy-paste-ready Python block into stdout.
  • Heuristic Guards: Use "loose assertion" mode to enforce maximum query counts.

Used By

pytest-capquery is actively used to protect the database performance of:


Installation

Install via pip:

pip install pytest-capquery

Quick Start

The plugin does not provide a default database fixture, as it is designed to adapt to your specific SQLAlchemy topology. You must define a global fixture in your conftest.py to bind pytest-capquery to your project's database engine.

Quick references:

1. Setting Up Your Fixture (conftest.py)

To intercept queries from your custom engine, use the CapQueryWrapper and inject the capquery_context fixture (which automatically handles snapshot file resolution behind the scenes).

Standard Synchronous Engines

import pytest
from pytest_capquery.plugin import CapQueryWrapper

@pytest.fixture(scope="function")
def postgres_capquery(postgres_engine, capquery_context):
    """Binds capquery to a custom PostgreSQL testing engine."""
    with CapQueryWrapper(postgres_engine, snapshot_manager=capquery_context) as captured:
        yield captured

Asynchronous Engines (AsyncEngine)

If your project uses SQLAlchemy's AsyncEngine (e.g., with asyncpg or aiomysql), you must attach the wrapper to the underlying synchronous engine. SQLAlchemy does not support event listeners directly on async engine proxies.

import pytest
from pytest_capquery.plugin import CapQueryWrapper

@pytest.fixture(scope="function")
def async_pg_capquery(async_pg_engine, capquery_context):
    """
    Binds capquery to an AsyncEngine by intercepting the underlying .sync_engine.
    This prevents 'NotImplementedError: asynchronous events are not implemented' errors.
    """
    with CapQueryWrapper(async_pg_engine.sync_engine, snapshot_manager=capquery_context) as captured:
        yield captured

By following this pattern, your custom fixtures automatically inherit the full snapshot lifecycle, error tracking, and CLI flags (--capquery-update) without needing to manually map test paths or instantiate SnapshotManager objects.

The most efficient way to document and protect your queries is by utilizing physical snapshots. This automatically compares execution behavior against tracked .sql files stored in a __capquery_snapshots__ directory.

Use the custom fixture you defined (e.g., postgres_capquery) and the capture() context manager to isolate specific execution phases.

def test_update_user_status(postgres_session, postgres_capquery):
    # Enable assert_snapshot to verify execution against the disk
    with postgres_capquery.capture(assert_snapshot=True):
        user = postgres_session.query(User).filter_by(id=1).first()
        user.status = "active"
        postgres_session.commit()

Workflow: When writing a new test or updating existing query logic, run Pytest with the update flag to automatically generate or overwrite the snapshot files:

pytest --capquery-update

Future runs without the flag will strictly assert that the runtime queries perfectly match the generated .sql file.

3. Manual Explicit Assertions (Verbose)

If you prefer to explicitly document the executed SQL directly inside your test cases, you can use strict manual assertions.

def test_update_user_status(postgres_session, postgres_capquery):
    with postgres_capquery.capture() as phase:
        user = postgres_session.query(User).filter_by(id=1).first()
        user.status = "active"
        postgres_session.commit()

    # Verify the precise chronological timeline of the transaction
    phase.assert_executed_queries(
        "BEGIN",
        (
            """
            SELECT users.id, users.status
            FROM users
            WHERE users.id = %s
            """,
            (1,)
        ),
        (
            """
            UPDATE users SET status=%s WHERE users.id = %s
            """,
            ("active", 1)
        ),
        "COMMIT"
    )

Auto-Generation on Failure: Maintaining long SQL strings can be tedious. If your code changes and the assertion fails, pytest-capquery will intercept the failure and drop the correct Python assertion block directly into your terminal's stdout. Simply copy and paste the block from your terminal directly into your test to instantly fix the regression!

4. Preventing N+1 Queries (Loose Assertion)

If you want to protect a block of code against N+1 regressions without hardcoding exact SQL strings, you can enforce a strict expected query count at the context boundary:

def test_fetch_users(postgres_session, postgres_capquery):
    # Enforce that exactly 1 query is executed inside this block.
    # If a lazy-loading loop triggers extra queries, this will raise an AssertionError.
    with postgres_capquery.capture(expected_count=1):
        users = postgres_session.query(User).all()
        for user in users:
            _ = user.address

Contributing

We welcome contributions to make this plugin even better! To ensure a smooth process, please follow these steps:

  1. Open an Issue: Before writing any code, please open an issue to discuss the feature, enhancement, or bug fix you have in mind.
  2. Contribute the Code: Once discussed, fork the repository, create your branch, make your changes, and submit a Pull Request.
  3. Review & Release: All PRs will be reviewed. Once approved and merged, the release process will be managed by the maintainer.

Developer Setup

To get your local environment ready for contribution, run the following commands. We prioritize a Test-Driven Development (TDD) workflow to continuously monitor database interactions.

# Clone the repository
git clone https://github.com/fmartins/pytest-capquery.git
cd pytest-capquery

# Install Python, dependencies, and pre-commit hooks
make setup

# Start the TDD watcher (auto-runs tests and updates snapshots on file changes)
make tdd

Makefile Reference

make help

Usage:
  make <target>

Targets:
  help                 Show this help message
  setup                Full local setup: install pyenv python, create venv, and install deps
  setup-env            Install local python version via pyenv (macOS/Linux dev only)
  install              Create venv and install dependencies
  db-up                Start Docker Compose databases
  db-down              Tear down Docker Compose databases
  test                 Run all tests with code coverage and test analytics
  tdd                  Run tests in watch mode for test-driven development
  clean                Remove virtual environment and cached files
  format               Run formatters for python, markdown, yaml, and json files
  check-format         Check if files comply with formatting rules (for CI)

License

This project is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0).

Author: Felipe Cardoso Martins

Metadata

Release files for pytest-capquery 0.3.3

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

Source distribution (sdist)

Source distribution for pytest-capquery 0.3.3
File Size Uploaded Provenance
pytest_capquery-0.3.3.tar.gz 20.6 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for pytest-capquery 0.3.3
File Interpreter ABI Platform
pytest_capquery-0.3.3-py3-none-any.whl Python 3 none any Details

Total release size: 40.1 kB

Release files / pytest_capquery-0.3.3.tar.gz

Download URL pytest_capquery-0.3.3.tar.gz
Size 20.6 kB
Tags Source
SHA-256 checksum
How to use checksums
60bae7330c3ee715a3fe233e32411b9b2bed61157dbf3fd8862f236b5ad445db
BLAKE2b-256 checksum
How to use checksums
f0003e14db9a212e4239fe1e518021441afef775495bfaad3cd991aef93458b8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 4, 2026.

Transparency log

Release files / pytest_capquery-0.3.3-py3-none-any.whl

Download URL pytest_capquery-0.3.3-py3-none-any.whl
Size 19.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
91b944ec959f09f1cd7858d0631d62a343c6e2fbb1c9a651624211218ec3a406
BLAKE2b-256 checksum
How to use checksums
4ebfc71665ff40c18a1bc3f689ee6d0c47a9c861cc511fff4a52d797022bd1e2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 4, 2026.

Transparency log

Security for pytest-capquery 0.3.3

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for pytest_capquery-0.3.3.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 4, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

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