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 SQL your test suite executes and reports the queries that step outside their aggregate — whether through __ lookups, select_related, subqueries, or hand-written .raw() SQL.

Install

pip install pytest-orm-boundaries

pytest picks the plugin up 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 works from the SQL your suite actually executes, so it flags any single statement that reads tables from two different aggregates - however the query was written. Each example below joins 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.

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 violations ======================
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 violation(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 violation - their files are clean now.
Remove them from boundaries.toml:
  - app/billing.py

Known gaps

  • prefetch_related loads doesn't detected in current version.

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.4.0.tar.gz (10.7 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.4.0-py3-none-any.whl (14.7 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: pytest_orm_boundaries-0.4.0.tar.gz
  • Upload date:
  • Size: 10.7 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.4.0.tar.gz
Algorithm Hash digest
SHA256 1a0b751b28f9ce121eb980e1a776531f7bf5e368b53f80af644cb989187fb228
MD5 fae9c447a075247b08322c3212a63a8d
BLAKE2b-256 4d0f7c9427e6fbcf1948ce2eee2ecd67ff9b6bfbc38566e95f57d73ec11b3707

See more details on using hashes here.

Provenance

The following attestation bundles were made for pytest_orm_boundaries-0.4.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.4.0-py3-none-any.whl.

File metadata

File hashes

Hashes for pytest_orm_boundaries-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 562e366b4be9817560e13a4c1b732a1cd6b85763259f2bd2de77939a85d2fd9b
MD5 e9f226db4bb2dc2d13d50f83f9b57dd3
BLAKE2b-256 691d08e941ab7c29662d7eeb194b29b1d85325000166fe77e065698e3c329345

See more details on using hashes here.

Provenance

The following attestation bundles were made for pytest_orm_boundaries-0.4.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

0.5.0

2 files

This release

0.4.0 This release

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