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:
- Create a GitHub environment named
pypiin the repository settings. - In the PyPI project's Publishing settings, add a GitHub Trusted Publisher
for this repository, workflow
release.yml, and environmentpypi. - Make sure the version in
pyproject.tomlmatches 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)
| File | Size | Uploaded | |
|---|---|---|---|
| pytest_layout_enforcer-0.1.0.tar.gz | 7.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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 logRelease 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