python@v1
Hygiene checks for Python projects (pyproject / setuptools / Poetry / PDM / uv). Adopt it with:
extends: - alint://bundled/python@v1Gated with when: facts.has_python (true if any standard Python
project marker exists anywhere in the tree) plus a per-rule
scope_filter: { has_ancestor: [pyproject.toml, setup.py, requirements.txt] } on per-file content rules so they only
apply to files inside a Python package — useful in polyglot
monorepos where Python packages sit alongside Rust / Node / Go
subdirectories. Override has_python with your own facts:
block if you need a different heuristic.
python-manifest-exists
Section titled “python-manifest-exists”- kind:
file_exists - level:
error - when:
facts.has_python - policy: https://packaging.python.org/en/latest/guides/writing-pyproject-toml/
Python project: a
pyproject.toml(preferred),setup.py, orsetup.cfgat the repo root is required.
python-has-lockfile
Section titled “python-has-lockfile”- kind:
file_exists - level:
warning - when:
facts.has_python
A lockfile should be committed for reproducible installs (uv.lock / poetry.lock / Pipfile.lock / pdm.lock).
python-pyproject-declares-name
Section titled “python-pyproject-declares-name”- kind:
toml_path_matches - level:
warning - when:
facts.has_python - policy: https://peps.python.org/pep-0621/
pyproject.tomlhas noproject.name(PEP 621). Declare the distribution name sopip install ./uv buildwork.
python-pyproject-declares-requires-python
Section titled “python-pyproject-declares-requires-python”- kind:
toml_path_matches - level:
info - when:
facts.has_python
pyproject.tomlhas noproject.requires-python; declare a floor (e.g.>=3.10) so installs fail fast on unsupported interpreters.
python-module-snake-case
Section titled “python-module-snake-case”- kind:
filename_case - level:
info - when:
facts.has_python - policy: https://peps.python.org/pep-0008/#package-and-module-names
Python module filenames should be snake_case (PEP 8).
python-sources-final-newline
Section titled “python-sources-final-newline”- kind:
final_newline - level:
info - when:
facts.has_python
python-sources-no-trailing-whitespace
Section titled “python-sources-no-trailing-whitespace”- kind:
no_trailing_whitespace - level:
info - when:
facts.has_python
python-sources-no-bidi
Section titled “python-sources-no-bidi”- kind:
no_bidi_controls - level:
error - when:
facts.has_python - policy: https://trojansource.codes/
Trojan Source (CVE-2021-42574): bidi override chars in Python sources are rejected.
Source
Section titled “Source”The full ruleset definition is committed at crates/alint-dsl/rulesets/v1/python.yml in the alint repo (the snapshot below is generated verbatim from that file).
# alint://bundled/python@v1## Hygiene checks for Python projects (pyproject / setuptools /# Poetry / PDM / uv). Adopt it with:## extends:# - alint://bundled/python@v1## Gated with `when: facts.has_python` (true if any standard Python# project marker exists anywhere in the tree) plus a per-rule# `scope_filter: { has_ancestor: [pyproject.toml, setup.py,# requirements.txt] }` on per-file content rules so they only# apply to files inside a Python package — useful in polyglot# monorepos where Python packages sit alongside Rust / Node / Go# subdirectories. Override `has_python` with your own `facts:`# block if you need a different heuristic.
version: 1
facts: - id: has_python any_file_exists: - pyproject.toml - "**/pyproject.toml" - setup.py - "**/setup.py" - setup.cfg - "**/setup.cfg" - requirements.txt - "**/requirements.txt"
rules: # --- Manifest + lockfile ------------------------------------------ - id: python-manifest-exists when: facts.has_python # Modern Python wants `pyproject.toml` (PEP 518 / 621), but # `setup.py` + `setup.cfg` are still the reality in older code # bases. Any one of the three satisfies the rule. kind: file_exists paths: ["pyproject.toml", "setup.py", "setup.cfg"] root_only: true level: error message: >- Python project: a `pyproject.toml` (preferred), `setup.py`, or `setup.cfg` at the repo root is required. policy_url: "https://packaging.python.org/en/latest/guides/writing-pyproject-toml/"
- id: python-has-lockfile when: facts.has_python # Accept any of the common lockfile names from uv / Poetry / # Pipenv / PDM. At least one should be committed for # reproducible installs. Skip this rule (`level: off`) if your # project genuinely pins via `requirements.txt` with hashes. kind: file_exists paths: - uv.lock - poetry.lock - Pipfile.lock - pdm.lock root_only: true level: warning message: >- A lockfile should be committed for reproducible installs (uv.lock / poetry.lock / Pipfile.lock / pdm.lock).
# --- pyproject.toml shape ----------------------------------------- # Both rules are scoped to `pyproject.toml` — if a project still # lives on setup.py / setup.cfg without a pyproject.toml at all, # neither rule evaluates and the project passes. When pyproject.toml # does exist but the enforced field is missing, the rule fires # (zero-match is a violation, which is what we want here). - id: python-pyproject-declares-name when: facts.has_python kind: toml_path_matches paths: pyproject.toml path: "$.project.name" matches: '^[A-Za-z][A-Za-z0-9._-]*$' level: warning message: >- `pyproject.toml` has no `project.name` (PEP 621). Declare the distribution name so `pip install .` / `uv build` work. policy_url: "https://peps.python.org/pep-0621/"
- id: python-pyproject-declares-requires-python when: facts.has_python # Bracket notation is required on keys with dashes — JSONPath # dot-notation segments are restricted to [A-Za-z_][A-Za-z0-9_]*. kind: toml_path_matches paths: pyproject.toml path: "$.project['requires-python']" matches: '^.+$' level: info message: >- `pyproject.toml` has no `project.requires-python`; declare a floor (e.g. `>=3.10`) so installs fail fast on unsupported interpreters.
# --- Source-file conventions -------------------------------------- - id: python-module-snake-case when: facts.has_python # PEP 8: module filenames are lowercase with underscores. # Scoped to `src/**` and the top level only — tests commonly # use `test_*.py` (already snake) and conftest.py which are # fine. kind: filename_case paths: ["*.py", "src/**/*.py"] case: snake level: info message: "Python module filenames should be snake_case (PEP 8)." policy_url: "https://peps.python.org/pep-0008/#package-and-module-names"
# v0.9.18: the cosmetic-formatting rules (final-newline + # no-trailing-whitespace) default-exclude the canonical # test-fixture trees. Linters and test-suite repos # (cpython `Lib/test/**`, ruff `crates/*/resources/test/fixtures/**`, # pytest-style `tests/fixtures/**`, Go-style `**/testdata/**`) # carry deliberately-malformed Python files; the rules can't # distinguish "real source" from "intentional bad input" so # the exclude defaults narrow the scope. Users with an # unconventional fixture layout can override via per-rule # `paths:` in their config. The Trojan-Source check # (`python-sources-no-bidi`) deliberately keeps the wider # scope — bidi-control chars in fixtures are still a security # concern. - id: python-sources-final-newline when: facts.has_python kind: final_newline paths: include: ["**/*.py"] exclude: - "tests/fixtures/**" - "**/tests/fixtures/**" - "**/test/fixtures/**" - "**/testdata/**" - "Lib/test/**" - "crates/**/resources/**" scope_filter: has_ancestor: [pyproject.toml, setup.py, requirements.txt] level: info fix: file_append_final_newline: {}
- id: python-sources-no-trailing-whitespace when: facts.has_python kind: no_trailing_whitespace paths: include: ["**/*.py"] exclude: - "tests/fixtures/**" - "**/tests/fixtures/**" - "**/test/fixtures/**" - "**/testdata/**" - "Lib/test/**" - "crates/**/resources/**" scope_filter: has_ancestor: [pyproject.toml, setup.py, requirements.txt] level: info fix: file_trim_trailing_whitespace: {}
# --- Trojan Source defense on Python sources ---------------------- - id: python-sources-no-bidi when: facts.has_python kind: no_bidi_controls paths: "**/*.py" scope_filter: has_ancestor: [pyproject.toml, setup.py, requirements.txt] level: error message: "Trojan Source (CVE-2021-42574): bidi override chars in Python sources are rejected." policy_url: "https://trojansource.codes/"