A pytest fixture for high-precision SQL testing in SQLAlchemy.
Project description
pytest-capquery
Testing your business logic is good, but testing your database interactions is critical.
pytest-capquery treats your SQL queries as first-class citizens in your test suite. By asserting
the exact queries executed, you create living documentation of what is truly happening behind the
scenes. This guarantees deterministic performance, catches N+1 regressions instantly, and ensures
your application behaves exactly as intended.
pytest-capquery is a strict, strongly-typed SQLAlchemy pytest plugin designed to enforce exact
chronological query execution. It asserts precise SQL strings, parameter bindings, and transaction
boundaries (BEGIN, COMMIT, ROLLBACK).
Used By
pytest-capquery is actively used to protect the database performance of:
Installation
Install via pip:
pip install pytest-capquery
Quick Start
The capquery fixture captures all SQLAlchemy statements executed by your code. You can use it to
assert precise SQL queries, exact bound parameters, and transaction events in deterministic order.
Here is how you can use the capquery fixture alongside your SQLAlchemy models (e.g., AlarmPanel
and Sensor):
from sqlalchemy.orm import Session
from tests.models import AlarmPanel, Sensor
def test_insert_alarm_panel(db_session: Session, capquery):
# Setup
panel = AlarmPanel(mac_address="00:11:22:33:44:55", is_online=True)
sensor = Sensor(name="Front Door", sensor_type="Contact")
panel.sensors.append(sensor)
db_session.add(panel)
# Clear any unrelated previous queries
capquery.statements.clear()
# Trigger database flush
db_session.flush()
# Assert exact chronological execution, parameters, and transaction boundaries
capquery.assert_executed_queries(
"BEGIN",
(
# language=SQL
"""
INSERT INTO alarm_panels (mac_address, is_online)
VALUES (?, ?)
""",
("00:11:22:33:44:55", 1)
),
(
# language=SQL
"""
INSERT INTO sensors (panel_id, name, sensor_type)
VALUES (?, ?, ?)
""",
(1, "Front Door", "Contact")
)
)
The N+1 Problem Showcase
One of the most powerful use cases for pytest-capquery is catching performance regressions
associated with the 1+N lazy-loading problem. It clearly contrasts inefficient DB loops with
optimized joined-loading.
Catching a 1+N Lazy-Loading Regression
If a developer drops the joinedload behavior, pytest-capquery will expose the exact 1+N
lazy-loading queries:
def test_demonstrate_n_plus_one_problem(db_session: Session, capquery):
capquery.statements.clear()
# Query all panels WITHOUT eagerly loading sensors
panels = db_session.query(AlarmPanel).all()
# Accessing the lazy relationship triggers N+1 queries
for panel in panels:
_ = panel.sensors
# Asserting the resulting N+1 problem
capquery.assert_executed_queries(
# The 1 Query
# language=SQL
"""
SELECT
alarm_panels.id AS alarm_panels_id,
alarm_panels.mac_address AS alarm_panels_mac_address,
alarm_panels.is_online AS alarm_panels_is_online
FROM alarm_panels
""",
# The +N Queries
(
# language=SQL
"""
SELECT
sensors.id AS sensors_id,
sensors.panel_id AS sensors_panel_id,
sensors.name AS sensors_name,
sensors.sensor_type AS sensors_sensor_type
FROM sensors
WHERE ? = sensors.panel_id
""",
(1,)
),
(
# language=SQL
"""
SELECT
sensors.id AS sensors_id,
sensors.panel_id AS sensors_panel_id,
sensors.name AS sensors_name,
sensors.sensor_type AS sensors_sensor_type
FROM sensors
WHERE ? = sensors.panel_id
""",
(2,)
),
(
# language=SQL
"""
SELECT
sensors.id AS sensors_id,
sensors.panel_id AS sensors_panel_id,
sensors.name AS sensors_name,
sensors.sensor_type AS sensors_sensor_type
FROM sensors
WHERE ? = sensors.panel_id
""",
(3,)
)
)
Fixing the N+1 problem with joined-loading
When developers optimize their query with joinedload, pytest-capquery verifies the problem is
fixed:
from sqlalchemy.orm import joinedload
def test_avoid_n_plus_one_queries(db_session: Session, capquery):
capquery.statements.clear()
# Query WITH eager loading
panels = db_session.query(AlarmPanel).options(joinedload(AlarmPanel.sensors)).all()
# Accessing the relationship no longer triggers additional queries
for panel in panels:
_ = panel.sensors
# Asserting only a single JOIN query was executed
capquery.assert_executed_queries(
# language=SQL
"""
SELECT
alarm_panels.id AS alarm_panels_id,
alarm_panels.mac_address AS alarm_panels_mac_address,
alarm_panels.is_online AS alarm_panels_is_online,
sensors_1.id AS sensors_1_id,
sensors_1.panel_id AS sensors_1_panel_id,
sensors_1.name AS sensors_1_name,
sensors_1.sensor_type AS sensors_1_sensor_type
FROM alarm_panels
LEFT OUTER JOIN sensors AS sensors_1 ON alarm_panels.id = sensors_1.panel_id
"""
)
Contributing
We welcome contributions to make this plugin even better! To ensure a smooth process, please follow these steps:
- Open an Issue: Before writing any code, please open an issue to discuss the feature, enhancement, or bug fix you have in mind.
- Contribute the Code: Once discussed, fork the repository, create your branch, make your changes, and submit a Pull Request.
- Review & Release: Felipe will review your PR. 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:
# Clone the repository
git clone [https://github.com/fmartins/pytest-capquery.git](https://github.com/fmartins/pytest-capquery.git)
cd pytest-capquery
# Install Python, dependencies, and pre-commit hooks
make setup
# Run the full test suite (requires Docker for E2E multi-dialect tests)
make test
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
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pytest_capquery-0.1.0.tar.gz.
File metadata
- Download URL: pytest_capquery-0.1.0.tar.gz
- Upload date:
- Size: 13.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7e68ee91f3519e6fc3f0ee36ba774f1b21131f8b67a8e901b738226e010b6c20
|
|
| MD5 |
0b36a131bd4e7e464f66c85f5e93c95e
|
|
| BLAKE2b-256 |
d6af360d0c942c64068d7dcece805dab3ddd984c2d10d05ec1645ba0591c3f66
|
Provenance
The following attestation bundles were made for pytest_capquery-0.1.0.tar.gz:
Publisher:
publish.yml on fmartins/pytest-capquery
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pytest_capquery-0.1.0.tar.gz -
Subject digest:
7e68ee91f3519e6fc3f0ee36ba774f1b21131f8b67a8e901b738226e010b6c20 - Sigstore transparency entry: 1208387945
- Sigstore integration time:
-
Permalink:
fmartins/pytest-capquery@10f638db5490979971e415b7926023b589cd1d29 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/fmartins
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@10f638db5490979971e415b7926023b589cd1d29 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pytest_capquery-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pytest_capquery-0.1.0-py3-none-any.whl
- Upload date:
- Size: 11.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
792a9f5991d98925442d33bf30983bff6aa917c060e053a0b94875340b840b24
|
|
| MD5 |
794ba566c9b3a53933d7d8aa7cae7f32
|
|
| BLAKE2b-256 |
45d285b7dcde1819e5f570a92bdea5b61f89dae78aad6e756198387f2dd04df5
|
Provenance
The following attestation bundles were made for pytest_capquery-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on fmartins/pytest-capquery
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pytest_capquery-0.1.0-py3-none-any.whl -
Subject digest:
792a9f5991d98925442d33bf30983bff6aa917c060e053a0b94875340b840b24 - Sigstore transparency entry: 1208387997
- Sigstore integration time:
-
Permalink:
fmartins/pytest-capquery@10f638db5490979971e415b7926023b589cd1d29 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/fmartins
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@10f638db5490979971e415b7926023b589cd1d29 -
Trigger Event:
push
-
Statement type: