klint
Architecture-as-Code checks for Python projects.
klint enforces architecture rules from a small klint.yaml file. Use it to
keep module boundaries explicit, block risky patterns in specific layers, pin
important symbols to their intended owner file, and hold files to size and
comment budgets.
It installs as a Python package and runs as a native executable:
pip install klint
klint
For machine-readable output:
klint --json
python -m klint --json
By default, klint looks for klint.yaml or klint.config.json in the current
working directory. Use --config when the config lives somewhere else:
klint --config path/to/project
CLI
The Python package exposes both a console command and a module entrypoint:
klint
python -m klint
Supported options:
| Option | Description |
|---|---|
--config <dir> |
Directory containing klint.yaml or klint.config.json. Defaults to the current working directory. |
--json |
Emit structured JSON to stdout. Useful in CI and agentic lifecycle hooks. |
--version, -V, version |
Print the klint version. |
--help, -h, help, h |
Print CLI usage. |
Arguments are passed straight through to the bundled native binary.
Configuration
Create klint.yaml at the root of your project:
include: ["src"]
rules: {}
arch:
layers:
api: ["src/app/api/**"]
db: ["src/app/db/**"]
jobs: ["src/app/jobs/**"]
include selects which paths are scanned. Prefix an entry with ! to prune a
directory from the walk — for example ["src", "!**/.venv/**"]. Exclusions
match directories, not individual files, so !src/jobs/worker.py has no
effect. arch.layers gives names to file groups so rules can talk about
architecture instead of repeating globs. root optionally sets the directory
that include paths and reported file names resolve against.
rules holds klint's top-level source rules, which are TypeScript/JavaScript
only. For a Python project it stays empty — everything below lives under arch.
Every arch rule accepts severity. Use error (exit code 2) or warn
(reported, exit code 0). Arch rules cannot be off; remove the entry instead.
Import Boundaries
Use arch.imports to block dependencies between layers.
include: ["src"]
rules: {}
arch:
layers:
api: ["src/app/api/**"]
db: ["src/app/db/**"]
imports:
- from: api
deny: db
message: "API code must not import database internals directly"
This flags Python imports such as:
from app.db.session import get_session
from files under src/app/api/**.
Use allow instead of deny to invert the check — anything not matching
allow is denied.
What resolves. Relative imports (from ..lib.auth import load_key) and
absolute project imports (from app.lib.auth import load_key) both resolve.
Absolute imports are matched against the project root and its direct child
directories containing Python files, checking <module>.py,
<module>/__init__.py, and — for PEP 420 namespace packages — a <module>/
directory the scan found .py files under.
Every target of a multi-target statement is checked separately, so
import json, app.lib.auth and from . import helper, sibling each produce one
record per target. Dynamic imports are read off the AST too:
importlib.import_module("…") and __import__("…"), including aliased bindings
such as import importlib as il and
from importlib import import_module as load. A call to a same-named method
that is not bound to importlib is not treated as an import.
Imports that do not resolve to a project file — third-party packages such as
import requests — are ignored by deny/allow. Use deny-packages to reach
those.
Third-party and stdlib packages
deny-packages matches pip packages and standard-library modules, which
deny/allow cannot see because they resolve to no project file. Matching is
per dotted segment, so denying os also catches os.path.
arch:
imports:
- from: jobs
deny-packages: ["os", "requests"]
message: "Jobs must go through the platform adapter"
Type-only imports
Set type-only: allow to exempt imports inside an if TYPE_CHECKING: block,
mirroring how import type is exempted in TypeScript. The else and elif
branches of that guard remain runtime imports.
arch:
imports:
- from: api
deny: db
type-only: allow
Forbidden Patterns
Use arch.forbidden to block text patterns inside a layer.
include: ["src"]
rules: {}
arch:
layers:
jobs: ["src/app/jobs/**"]
forbidden:
- in: jobs
pattern: "print("
message: "Jobs must not print directly"
pattern is a literal substring scanned per line. Prefix it with re: to match
a regular expression instead:
- in: jobs
pattern: "re:^\\s*os\\.environ\\["
message: "Read configuration through settings, not os.environ"
Regexes must stay inside the common regex subset — no lookaround or
backreferences. A literal pattern that itself begins with re: cannot be
expressed; such a value is always read as a regex.
This is useful for project-specific policies such as blocking direct logging, environment access, framework shortcuts, or unsafe helpers in the wrong layer.
Singleton Ownership
Use arch.singleton when a symbol or pattern must only appear in one file.
include: ["src"]
rules: {}
arch:
singleton:
- only: "src/app/config/settings.py"
pattern: "API_KEY"
message: "API_KEY must only live in settings.py"
This allows API_KEY in src/app/config/settings.py and flags the same pattern
anywhere else in scanned files. pattern takes the same literal-or-re: form
as arch.forbidden.
File Size
Use arch.maxLines to cap how long a file may get. The limit counts physical
lines, and the violation is reported at the first line past the limit.
arch:
layers:
jobs: ["src/app/jobs/**"]
maxLines:
- limit: 500
in: jobs
message: "Split this job into smaller modules"
Comment Budgets
Use arch.maxCommentDensity to cap what share of a file may be comments, and
arch.maxCommentBlock to cap how tall a single run of comment lines may get.
arch:
layers:
jobs: ["src/app/jobs/**"]
maxCommentDensity:
- limit: 10
in: jobs
maxCommentBlock:
- limit: 3
in: jobs
Density is measured against total physical lines — code, comments, and blanks —
the same denominator maxLines uses. A comment block violation is reported at
the first line past the limit.
What counts as a comment in Python. # comments count toward both limits.
Docstrings are string expressions rather than comments, so they never count
toward either limit — a module that is nothing but docstrings measures 0%
density. The countDocComments option therefore has no effect on Python files;
it exists for languages whose doc-comments are real comment nodes.
Ignoring structural comments
Some comment lines are machinery rather than prose — tool directives a linter or
codegen step reads. Use ignore to keep them out of both budgets:
arch:
maxCommentDensity:
- limit: 10
in: jobs
ignore: ["re:^\\s*# (noqa|type:|pragma:)"]
ignore takes the same literal-or-re: form as arch.forbidden and tests the
physical source line. Ignored lines still count in the density denominator, and
for maxCommentBlock they connect a run without adding to its height — so a
directive sitting inside a comment block does not split it in two.
Supported Python Rules
The Python package supports:
arch/importsarch/forbiddenarch/singletonarch/max-linesarch/max-comment-densityarch/max-comment-block
These rules are intentionally configuration-driven. They are for enforcing your project's architecture, not for replacing formatters or style linters.
klint's top-level source rules and its sonar plugin are
TypeScript/JavaScript-only and do not apply to .py files.
CI
Run klint in CI after installing your Python dependencies:
pip install klint
klint --json
klint exits with:
0when no errors are found2when rule violations are found1for configuration or runtime errors
Release files for klint 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| klint-0.2.0-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details |
| klint-0.2.0-py3-none-manylinux_2_28_x86_64.whl | Python 3 | none | Linux glibc 2.28+ x86-64 | Details |
| klint-0.2.0-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details |
| klint-0.2.0-py3-none-macosx_10_13_x86_64.whl | Python 3 | none | macOS 10.13+ x86-64 | Details |
Total release size: 8.0 MB
Release files / klint-0.2.0-py3-none-win_amd64.whl
| Download URL | klint-0.2.0-py3-none-win_amd64.whl |
|---|---|
| Size | 1.9 MB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
54bbd244a4a8321aef0120a2e2cd84c277a8e0f91d0d32dba0aa1ed57e07f435
|
|
BLAKE2b-256 checksum How to use checksums |
ceefc9cde0c58f6c4c0d6b0dfe3147fde789ac71180a76577630152c1974b1e1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Jul 30, 2026.
Transparency logRelease files / klint-0.2.0-py3-none-manylinux_2_28_x86_64.whl
| Download URL | klint-0.2.0-py3-none-manylinux_2_28_x86_64.whl |
|---|---|
| Size | 2.1 MB |
| Tags | Linux glibc 2.28+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
600810e953868f85b97f0bde31f928b9176eac279a3d57b95f2fecf11380ce7e
|
|
BLAKE2b-256 checksum How to use checksums |
65ddf655e71a673caf1f7f957008adbe121016294b5016d6c7cd6e0fef8e9adb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Jul 30, 2026.
Transparency logRelease files / klint-0.2.0-py3-none-macosx_11_0_arm64.whl
| Download URL | klint-0.2.0-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 2.0 MB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
9f723ea456e0e04ed03863250f4456f911d059d3f8a9ceffd0e99005c74a6d84
|
|
BLAKE2b-256 checksum How to use checksums |
21fe02711aa34daa5a19c3512d0ec0d0b3f4baf5bb34027a6d7f3fe42131960b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Jul 30, 2026.
Transparency logRelease files / klint-0.2.0-py3-none-macosx_10_13_x86_64.whl
| Download URL | klint-0.2.0-py3-none-macosx_10_13_x86_64.whl |
|---|---|
| Size | 2.0 MB |
| Tags | Python 3 macOS 10.13+ x86-64 |
|
SHA-256 checksum How to use checksums |
2616c48b060f3296fbc778e78893bf86420b87bea1a358f6ecd5836c701812de
|
|
BLAKE2b-256 checksum How to use checksums |
1f2888ad3b7dc423d24bb2a0dc4c237c82de809b0b52a7889f8bed036aa5418f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Jul 30, 2026.
Transparency log