This repo contains BestDoctor's pre-commit hooks for python projects.
Create a .pre-commit-config.yaml in your repo and put this inside:
repos:
- repo: https://github.com/best-doctor/pre-commit-hooks
rev: v1.0.0
hooks:
- id: no-assertsList the hooks you'd like to enable under the hooks: section.
Hooks read shared settings from pyproject.toml first, then fall back to legacy setup.cfg.
Exclude paths for most hooks (flake8-compatible):
pyproject.toml -> [tool.flake8] -> exclude- or
setup.cfg -> [flake8] -> exclude
Any path listed there will not be checked.
Per-hook excludes can be configured with regular expression(s) in
exclude parameter of a hook configuration in .pre-commit-config.yaml
Analogous to flake8-adjustable-complexity, this validator complains about:
- functions with high (>8) cyclomatic complexity
- variables with too "generic" names (like
items,foo,vals,varsetc.).
Configuration options ([tool.flake8] in pyproject.toml or [flake8] in setup.cfg):
adjustable-default-max-complexity— maximum allowed cyclomatic complexityper-path-max-complexity— list offile_name: complexityoverrides per file
pyproject.toml example
[tool.flake8]
adjustable-default-max-complexity = 8
per-path-max-complexity = [
"legacy_module.py: 12",
]
exclude = ["venv", ".venv"]Ensures a file does not contain more than --lines n lines (default: 1000).
Validates API schema annotations:
- View/ViewSet defines
schema_tagsparameter (check_schema_tags_presence_in_views_and_viewsets) - View/ViewSet/Serializer has a docstring (check_docstring)
- Serializer's attributes have
help_textparameter (check_help_text_attribute_in_serializer_fields) - ViewSet defines
serializer_class_map(check_viewset_has_serializer_class_map) - ViewSet's
lookup_field!=id(check_viewset_lookup_field_has_valid_value) SerializerMethodFieldis wrapped intoSchemaWrapper(unless method's return type isstr)- View's/ViewSet's actions have docstrings (get/put/post/patch/delete, custom
actions)
All nullable fields in django models have to be commented with # null_by_design and/or # null_for_compatibility
All deprecated fields in django models have to be commented with # deprecated <ticket_id> <deprecation_date>
Configuration options:
--deprecation-comment-marker-regex- A regex to match deprecation comment. Indicates thad field was deprecated (without checking whether the deprecation comment is valid or not). Defaults todeprecated--valid-deprecation-comment-regex- A regex to validate deprecation comment. Defaults to#? deprecated (?P<ticket_id>[A-Z][A-Z,0-9]+-[0-9]+) (?P<deprecation_date>\d{2}\.\d{2}\.\d{4})
Example
In .pre-commit-config.yaml
repos:
- repo: https://github.com/best-doctor/pre-commit-hooks
rev: v1.0.0
hooks:
- id: validate_deprecated_model_field_comments
args: [--deprecation-comment-marker-regex=deprecated, --valid-deprecation-comment-regex="#? deprecated (?P<ticket_id>[A-Z][A-Z,0-9]+-[0-9]+)"]Validates django models' field names against BestDoctor guidelines.
Ensures code block's (function, class, loop, if-expr) complexity <= 9 (unconfigurable)
Forces GraphQL types (graphene-django) to explicitly define accessible fields in class Meta.
Just to make sure you won't expose data you'd better not to.
Prohibits assert statements in python files (ignores tests, of course).
Forbids blacklisted imports
Configuration ([tool.project_structure] in pyproject.toml or [project_structure] in setup.cfg):
forbidden_imports— list of blacklisted module names
pyproject.toml example
[tool.project_structure]
forbidden_imports = [
"django.db.backends",
]Makes sure type annotations are not string literals
module structure validator. Checks if:
models.pycontains only model definitions- all
Enums are defined inenums.py - File's name doesn't end with
_utils/_helperspostfix (place 'em inutils/) - There are no empty files (
__init__.pyexcluded) - There are only class-based views in
views.py urls.pycontainsurlpatternsandurlpatternscontainpaths, noturls
Requires all settings from DJANGO_SETTINGS module to be configurable with environment variables.
Checks that each setting contains an ast.Call (assuming it's getenv(), values.Value(), etc.) or
is commented with # noqa: allowed straight assignment / # noqa: static object.
Forces all tests names to start with either test_ or _.
Makes sure a password/token/apikey accidentally left in one of your tracked files won't make its way into outer world.
Requires gitleaks to run.
Installation is as easy as downloading a binary
and dropping it somewhere in your $PATH.
Linux:
wget https://github.com/zricethezav/gitleaks/releases/download/v7.3.0/gitleaks-linux-amd64
sudo mv gitleaks-linux-amd64 /usr/local/bin/gitleaks
chmod +x /usr/local/bin/gitleakshomebrew does the job if you're on MacOS:
brew install gitleaksThis repo uses PDM for dependency management.
pip install pdmLockfile: pdm.lock. Declared dependencies: pyproject.toml.
Exported requirements.txt / requirements_dev.txt are generated for tools that still expect pip-style lockfiles.
make install-
Edit dependencies in
pyproject.toml([project.dependencies]or[dependency-groups].dev). -
Lock and export:
make lock
-
Stage & commit
pyproject.toml,pdm.lock, and exportedrequirements*.txtif you ranlock-export.
Copy an existing hook, rename it and make it do something useful.
Next, register it in .pre-commit-hooks.yaml:
- id: panzerfaust
name: Fausts panzers
entry: panzerfaustize # console script name from pyproject.toml (see below)
language: pythonRegister the script in pyproject.toml:
[project.scripts]
panzerfaustize = "hooks.panzerfaustize:main"Ensure your hook is tracked, or try-repo won't load it:
git add hooks/panzerfaustize.py# execute this in a repo were you want your pre-commit hooks ran
pre-commit try-repo ../my-pre-commit-hooks/ panzerfaust\
--files ./src/killroy.py ./src/was.py ./src/here.py