pytest-fsd
Validate Feature-Sliced Design (FSD) architecture in a Python project using pytest. The rules are adapted from the Steiger FSD plugin, with Python-specific checks where appropriate. This is an independent Python package, not an official Steiger port.
Install
pip install pytest-fsd
# or
uv add --dev pytest-fsd
Python 3.8–3.14 is supported.
Use
Add the layers used by your project, from highest to lowest, to pyproject.toml:
[tool.pytest_fsd]
base_path = "src"
layers = ["app", "pages", "widgets", "features", "entities", "shared"]
# ignore_paths = ["my_non_fsd_package"]
Create an architecture test:
# tests/test_architecture.py
from pytest_fsd import validate_fsd_architecture
def test_architecture():
validate_fsd_architecture()
Run pytest tests/test_architecture.py. Violations appear as assertion failures with a rule name and path. If your source root is different, set base_path accordingly. The validator reads [tool.pytest_fsd] from the project root; validate_fsd_architecture(project_root="...") can target another project.
Rules
The default checks cover layer import direction (forbidden-imports), slice independence (no-cross-imports), public APIs (public-api, no-public-api-sidestep, no-layer-public-api), structure (no-segmentless-slices, no-segments-on-sliced-layers, typo-in-layer-name, ambiguous-slice-names), naming (segments-by-purpose, repetitive-naming), and no-ui-in-app.
segments-by-purpose includes the generic names added in Steiger's FSD plugin 0.7.0, such as schemas, handlers, fixtures, middlewares, validators, resolvers, mutations, and assets. These names are checked when used as FSD segments. Python fixtures.py in a slice is also flagged because this package checks file segments as well as directories.
no-ui-in-app rejects an app/ui directory, as in Steiger. It also retains the Python-specific check for direct imports of common desktop GUI frameworks from the app layer.
Enable additional checks only when they suit your project:
[tool.pytest_fsd]
base_path = "src"
layers = ["app", "pages", "widgets", "features", "entities", "shared"]
extra_rules = [
"excessive-slicing", # More than 20 slices per layer
"shared-lib-grouping", # More than 15 loose Python files in shared/lib
"no-file-segments", # Segments must be directories
"no-reserved-folder-names", # No nested segment names such as model/model
]
no-file-segments is a Python package option; it is not registered as a rule in Steiger FSD plugin 0.7.0. no-cross-imports is enabled here for compatibility, whereas the upstream plugin does not enable it by default. These choices are intentionally different from Steiger.
See the rule documentation for examples and details.
Scope and limitations
forbidden-importsandno-cross-importsuse pytest-archon. Imports guarded byif TYPE_CHECKING:are invisible to these runtime checks.no-public-api-sidestepanalyzes Python syntax and explicit__all__lists. Dynamically assembled exports may be reported incorrectly.- Steiger's
inconsistent-namingandimport-localityhave no direct equivalent here. Consider Ruff'sNandTIDrules for Python naming and relative imports; their behavior is not identical to Steiger's rules. - Steiger's
insignificant-sliceneeds an import graph and is not implemented. The deprecatedprocesseslayer is not created by this package; omit it fromlayers. - This package checks Python files and filesystem structure. It does not implement every Steiger rule or every JavaScript-specific convention.
See the upstream FSD plugin changelog for Steiger's release history.
License
MIT. See LICENSE.
Release files for pytest-fsd 0.3.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_fsd-0.3.0.tar.gz | 21.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pytest_fsd-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 66.4 kB
Release files / pytest_fsd-0.3.0.tar.gz
| Download URL | pytest_fsd-0.3.0.tar.gz |
|---|---|
| Size | 21.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
470422fcb1989aeaca13fbbdfa72ddfb9bfd29758494624a9c1d67443cd4ecbf
|
|
BLAKE2b-256 checksum How to use checksums |
cd29f1bc850423a5f2e73fbfb9c825c2f7434dc5702bc11b136b9d52bbabc754
|
| 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}
|
Release files / pytest_fsd-0.3.0-py3-none-any.whl
| Download URL | pytest_fsd-0.3.0-py3-none-any.whl |
|---|---|
| Size | 44.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ebaea3c57e59e5a27181e0f4dcf356c05f185deaa1e83af487d324943d788570
|
|
BLAKE2b-256 checksum How to use checksums |
c77caf1c3dfcd2e1c8b041d2d411e1d518bc6eb2a78ed2989bf6ff75e397e270
|
| 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}
|