Skip to main content

pytest-layout-enforcer

pytest-layout-enforcer keeps test files structurally aligned with the Python modules they exercise. It is both a pytest plugin and a standalone command, so the same invariant can run during test collection, in CI, in pre-commit, or immediately after an automated refactor.

Given:

src/my_package/
├── core.py
└── services/
    └── billing.py

these test paths are valid:

tests/
├── test_core.py
├── test_core_parsing.py
└── services/
    ├── test_billing.py
    └── test_billing_refunds.py

Installation

uv add --dev pytest-layout-enforcer

The package uses uv_build and can be built locally with:

uv build

Configuration

Add at least one source-to-test mapping to the host project's pyproject.toml:

[tool.pytest-layout-enforcer]
enabled = true
feature-pattern = "[a-z][a-z0-9_]*"
feature-separator = "_"
require-tests = false
include-init = false

exclude-sources = ["**/_version.py", "**/migrations/**"]
exclude-tests = ["**/test_integration_*.py"]

[[tool.pytest-layout-enforcer.layouts]]
source = "src/my_package"
tests = "tests"

For tests stored inside the package, only the test root changes:

[[tool.pytest-layout-enforcer.layouts]]
source = "src/my_package"
tests = "src/my_package/tests"

Paths are relative to pyproject.toml. Add more layout tables for monorepos. The tool ignores __init__.py by default and only examines files named test_*.py in test roots. conftest.py and test helper modules are therefore left alone.

require-tests = false checks that every discovered test maps to a source module without requiring every source module to have tests. Set it to true to require at least one matching test file per source module.

Usage

The plugin runs automatically before pytest collection when configuration is present:

uv run pytest

Run the same check without running tests:

uv run pytest-layout-enforcer check
uv run pytest-layout-enforcer check --format json

The command exits with status 0 on success, 1 for layout violations, and 2 for configuration errors.

Publishing

CI runs the test suite on Python 3.11 through 3.14 for every pull request and push to main. Version tags such as v0.1.0 build, smoke-test, attest, and publish the wheel and source distribution to PyPI using Trusted Publishing.

Before the first release:

  1. Create a GitHub environment named pypi in the repository settings.
  2. In the PyPI project's Publishing settings, add a GitHub Trusted Publisher for this repository, workflow release.yml, and environment pypi.
  3. Make sure the version in pyproject.toml matches the release tag.

Then publish a release with:

git tag -a v0.1.0 -m "v0.1.0"
git push origin main --follow-tags

No PyPI API token or GitHub repository secret is required.

Naming and ambiguity

A module such as data_loader.py permits test_data_loader.py and feature files such as test_data_loader_cache.py. If a feature filename could refer to multiple modules—for example when both data.py and data_loader.py exist—the tool reports an ambiguous-test violation instead of guessing.

For projects that prefer unambiguous names, configure a double underscore:

[tool.pytest-layout-enforcer]
feature-separator = "__"

That produces names such as test_data_loader__cache.py.

Metadata

Release files for pytest-layout-enforcer 0.1.0

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-layout-enforcer 0.1.0
File Size Uploaded
pytest_layout_enforcer-0.1.0.tar.gz 7.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pytest-layout-enforcer 0.1.0
File Interpreter ABI Platform
pytest_layout_enforcer-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 16.8 kB

Release files / pytest_layout_enforcer-0.1.0.tar.gz

Download URL pytest_layout_enforcer-0.1.0.tar.gz
Size 7.0 kB
Tags Source
SHA-256 checksum
How to use checksums
1b9afe26619d57cf7f05edf649712632de34d839c13514bedf8f519c96d96082
BLAKE2b-256 checksum
How to use checksums
580a99da67da8496a18443c918ac6e956a3f53a8bee3ebda42057d88fa86c782
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

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 Sep 28, 2026.

Transparency log

Release files / pytest_layout_enforcer-0.1.0-py3-none-any.whl

Download URL pytest_layout_enforcer-0.1.0-py3-none-any.whl
Size 9.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9856e9679aeeac06519f9ebcfd85477867e6abd84b0c5ffe275f96e263da5f7c
BLAKE2b-256 checksum
How to use checksums
5e4efc0c8a6a11202d9bef6163add73037364b8cbcf80cc3a3181d3b5a44f01c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

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 Sep 28, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

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