Hard-Lint for Python 🐍
Rigorous linting and code quality setup for Python projects with automatic pre-commit hooks.
Features
- Automated Pre-commit Hooks: Validates code before every commit
pre-commit: Runsruff,black, andisortcommit-msg: Validates messages follow Conventional Commits format
- Strict Linting: Ruff checks for errors, complexity, and code quality
- Synchronized Formatters: Ruff, Black, and isort configured with same profile (no conflicts)
- Zero Configuration: Works out-of-the-box with sensible defaults
- Multiplattform: Works on Windows, macOS, and Linux
- Auto-setup: Single command to enable all checks
Installation
poetry add -D hard-lint-py
Quick Start
# 1. Install and setup hooks
hard-lint-py
# 2. Make your first commit
git add .
git commit -m "feat: initial setup"
That's it! Your project now has:
- Ruff for fast Python linting
- Black for code formatting
- isort for import sorting
- Gitlint for commit message validation (Conventional Commits format)
What Gets Installed
Git Hooks
Hooks are created in .hardlint/_/:
-
pre-commit: Runs before commits to validate code
- Fixes issues with
ruff check --fix - Formats with
black - Sorts imports with
isort - Validates no comments exist
- Enforces empty
__init__.pyfiles
- Fixes issues with
-
commit-msg: Validates commit messages
- Must follow Conventional Commits format
- Examples:
feat:,fix:,chore:,docs:, etc.
Linting Rules
Custom Rules (enforced by hard-lint-py, not by Ruff)
| Rule | Description |
|---|---|
| no-comments | Forbids inline comments and docstrings in all Python files |
| empty-init | __init__.py files must be completely empty (no imports, no code) |
Ruff Rules Enforced
| Rule | Category | Description |
|---|---|---|
| E | pycodestyle errors | PEP 8 compliance |
| F | Pyflakes | Unused imports, undefined names |
| W | pycodestyle warnings | Code warnings |
| I | isort | Import sorting |
| N | pep8-naming | Naming conventions |
| C | mccabe | Code complexity |
| B | flake8-bugbear | Common bugs and design problems |
| RUF | Ruff-specific | Additional quality checks |
| UP | pyupgrade | Modernize Python syntax |
Synchronized Formatter Configuration
All three formatters (Ruff, Black, isort) use the same profile to prevent conflicts:
| Aspect | Configuration | Notes |
|---|---|---|
| Line Length | 100 characters | Consistent across all tools |
| Python Version | 3.14+ | Targets modern Python |
| Imports Profile | isort: black |
Compatible with Black |
| Trailing Commas | Enabled | Multi-line consistency |
Configuration
Auto-configured in pyproject.toml:
[tool.black]
line-length = 100
target-version = ["py314"]
[tool.isort]
profile = "black" # Compatible with Black
line_length = 100
known_first_party = ["hard_lint_py"]
[tool.ruff]
line-length = 100
target-version = "py314"
[tool.ruff.lint]
select = ["E", "F", "W", "I", "N", "C", "B", "RUF", "UP"]
Usage
Normal workflow
# Make changes
cat > new_file.py << 'EOF'
def greet(name):
# Greet a person by name
return f"Hello, {name}!"
EOF
# Stage changes
git add new_file.py
# Commit (hooks will run automatically)
git commit -m "feat: add greeting function"
Pre-commit Hook Behavior
When you commit, the hook automatically:
- Ruff: Checks for linting issues, imports, and code quality
- Black: Formats code to 100-character lines
- isort: Sorts and organizes imports
- Blocks commit if any issues can't be auto-fixed
If pre-commit fails:
# Fix will be auto-applied, just re-add and commit
git add .
git commit -m "feat: your message"
If commit-msg fails
Invalid message example:
git commit -m "added this feature" # ❌ Missing type prefix
Valid message example:
git commit -m "feat: added new feature" # ✅ Has type prefix
Skip hooks (use with caution)
git commit -m "..." --no-verify
Configuration
All tools read from pyproject.toml. Customize as needed:
[tool.ruff]
line-length = 120
[tool.ruff.lint]
select = ["E", "F", "W"]
[tool.black]
line-length = 120
[tool.isort]
profile = "black"
line_length = 120
Troubleshooting
Hooks not running?
# Re-run installation
hard-lint-py
Commit-msg validation failing?
- Check that your message starts with a type:
feat:,fix:,chore:, etc.
Formatters conflicting with each other?
- They shouldn't! Ruff, Black, and isort are configured with the same profile
- If you see conflicts, run all three:
ruff check --fix,black .,isort .
Line length conflicts?
- All tools configured to 100 characters
- Black and isort won't fight over formatting
- Ruff respects Black's decisions (E501 ignored)
Supported Python Versions
- Python 3.14+
Development
When developing with hard-lint-py:
Pre-commit Validation
Your code will be validated on every commit:
git commit -m "feat: your feature"
# Runs: ruff check --fix → black → isort → commitlint
Manual Validation
Run validation checks manually:
# All checks
make lint
make format
make test
make test-cov
# Individual tools
poetry run ruff check src/ tests/
poetry run black --check src/ tests/
poetry run isort --check-only src/ tests/
Quality Standards
- ✅ 99% code coverage
- ✅ No unused imports or variables
- ✅ Empty
__init__.pyfiles - ✅ Consistent formatting (100 char lines)
- ✅ Code complexity within limits
- ✅ Modern Python syntax
- ✅ No common bugs or design problems
License
MIT
Author
Naylson Ferreira
Metadata
Release files for hard-lint-py 0.4.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| hard_lint_py-0.4.1.tar.gz | 7.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| hard_lint_py-0.4.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 15.4 kB
Release files / hard_lint_py-0.4.1.tar.gz
| Download URL | hard_lint_py-0.4.1.tar.gz |
|---|---|
| Size | 7.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
daf366e0d3db29dd9087f7ffc393231915c1616b906361f1f939c13e36cf0872
|
|
BLAKE2b-256 checksum How to use checksums |
864e4bf9c01b72549ad06fda21def42c6ac60a298bb9a974d99ad4d2b58571e6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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 May 19, 2026.
Transparency logRelease files / hard_lint_py-0.4.1-py3-none-any.whl
| Download URL | hard_lint_py-0.4.1-py3-none-any.whl |
|---|---|
| Size | 7.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d6a3742136c3b66eb30ed761913499ba8d571dc69598e8a067bfb9018e1b327e
|
|
BLAKE2b-256 checksum How to use checksums |
2159738f164c4602ce0d12482b3066e8b4e3329a18d02c195632898d2c653ad8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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 May 19, 2026.
Transparency log