Skip to main content

pytest-orm-boundaries

💡 Even if you control your imports — boundaries still can leak through the ORM

A pytest-orm-boundaries is a pytest plugin that reports ORM queries crossing your DDD aggregate boundaries.

Currently works with Django ORM.

In domain-driven design, an aggregate is a consistency boundary: code in one aggregate should not reach into the internals of another. Django's __ relation lookups make it easy to cross those boundaries silently:

# Purchase and Client belong to different aggregates — this query couples them.
Purchase.objects.get(client__name="John")

pytest-orm-boundaries watches the queries your test suite executes and reports the ones that step outside their aggregate — through __ lookups, select_related, prefetch_related, subqueries, or hand-written .raw() SQL.

Install

Install the plugin with Django support:

pip install "pytest-orm-boundaries[django]"

pytest discovers the plugin automatically.

Configure

Declare your aggregates in boundaries.toml at the project root (or point at the file with --boundaries-config / the boundaries_config ini option):

[aggregates]
client   = ["bookshop.Client"]
book     = ["bookshop.Book"]
purchase = ["bookshop.Purchase", "bookshop.PurchaseLine"]

Models are written as app_label.Model. Models not listed in any aggregate are not checked. Without a config file the plugin emits a warning and runs no checks.

What it catches

The plugin flags queries that read across an aggregate boundary. Each example below couples the purchase and client aggregates:

  • __ relation lookups:

    Purchase.objects.get(client__name="John")
    
  • select_related:

    Purchase.objects.select_related("client")
    
  • Subqueries - a table reached through a subquery still counts:

    berlin_clients = Client.objects.filter(city="Berlin").values("id")
    Purchase.objects.filter(client_id__in=berlin_clients)
    
  • Hand-written .raw() SQL:

    Purchase.objects.raw(
        "SELECT p.id FROM bookshop_purchase p "
        "JOIN bookshop_client c ON p.client_id = c.id"
    )
    
  • Bare cursor.execute() - the same join reached through a raw cursor.

  • prefetch_related:

    Purchase.objects.prefetch_related("client")
    

Queries that don't actually join across the boundary are not flagged — for example a foreign-key lookup by id, which Django resolves without a join:

Purchase.objects.filter(client_id=42)     # reads one table
Purchase.objects.filter(client__pk=42)    # Django trims the join

The report

At the end of the run, the plugin prints one grouped entry per offending place:

====================== orm-boundaries: boundary crossings ======================
1 place(s) in your code crossed aggregate boundaries, affecting 1 test(s):

bookshop/reports.py:13
    crossed aggregates: client ↔ purchase
    models: bookshop.Client, bookshop.Purchase
    1 test(s) affected:
      test_purchases.py::test_list_purchases_with_client

orm-boundaries: FAILED - 1 boundary crossing(s), run exits non-zero.

Each entry names the aggregates the query crossed and the models it joined. Places are ordered by how many tests they affect. Pass -v to see every affected test (otherwise the list is capped at 5 per place).

Ignoring files

Add exceptions so that known offenders keep passing while you fix them one file at a time:

[ignore]
files = [
    "app/billing.py",
    "app/legacy/*",
]

Each entry is a glob (fnmatch, resolved relative to pytest's root directory and matched against either:

  • the file that issues the query, or
  • the test file.

If an ignored file runs queries through the whole suite without ever crossing a boundary, the plugin says so at the end:

======================= orm-boundaries: stale ignores ========================
These [ignore] entries no longer suppress any boundary crossing - their files are clean now.
Remove them from boundaries.toml:
  - app/billing.py

A note on Django internals

Catching prefetch_related relies on Django internals that come with no stability promise, so new Django releases may require compatibility updates.

Known gaps (on the project roadmap)

  • lazy attribute access (e.g. purchase.client);
  • related-manager reads/writes (client.purchases.all(), client.purchases.create(...));
  • a direct query on another aggregate's model, e.g. Client.objects.get(...) written inside purchase code.

Status

Alpha - testing basic version.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pytest_orm_boundaries-0.5.0.tar.gz (13.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pytest_orm_boundaries-0.5.0-py3-none-any.whl (18.0 kB view details)

Uploaded Python 3

File details

Details for the file pytest_orm_boundaries-0.5.0.tar.gz.

File metadata

  • Download URL: pytest_orm_boundaries-0.5.0.tar.gz
  • Upload date:
  • Size: 13.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pytest_orm_boundaries-0.5.0.tar.gz
Algorithm Hash digest
SHA256 e4fdcca277ed46087f5348906322006b0a0a3c94c56790bbf38dab7cb64d2185
MD5 8726664c9be475c927f915b02b67203f
BLAKE2b-256 360ab89067f4450b97da30fe2170c8d72dd6f1e8cead2ade2f9821779c406310

See more details on using hashes here.

Provenance

The following attestation bundles were made for pytest_orm_boundaries-0.5.0.tar.gz:

Publisher: publish.yml on evchibisova/pytest-orm-boundaries

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pytest_orm_boundaries-0.5.0-py3-none-any.whl.

File metadata

File hashes

Hashes for pytest_orm_boundaries-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 60a179e4ad8da547fec991d2e06585c34e91b038aec288f9fb3e16d07ae957bf
MD5 e1e00b9b22078e23669349d095aadab4
BLAKE2b-256 540360364ec4ad925e6e5c08a17d1d0bf780fd51fdba1947536d318ad0aaecd9

See more details on using hashes here.

Provenance

The following attestation bundles were made for pytest_orm_boundaries-0.5.0-py3-none-any.whl:

Publisher: publish.yml on evchibisova/pytest-orm-boundaries

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.8.1

2 files

0.8.0

2 files

0.7.5

2 files

0.7.4

2 files

0.7.3

2 files

0.7.2

2 files

0.7.1

2 files

0.7.0

2 files

0.6.0

2 files

This release

0.5.0 This release

2 files

0.4.0

2 files

0.3.1

2 files

0.3.0

2 files

0.2.0

2 files

0.1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page