pymaxlines
A pre-commit hook that fails when a Python file or function has too many lines of code.
Requires Python 3.12+.
Why
A file that runs into the thousands of lines is hard to navigate, test, and review, yet few Python
linters enforce a limit. Ruff has no max-lines rule and does not plan to add one.
McCabe complexity catches convoluted control flow but ignores sheer size — a 600-line function with
simple branches passes just fine.
The problem compounds with large language model (LLM) coding agents. Long files exhaust the context window and push agents toward destructive rewrites — splitting a file on a token boundary instead of a logical one. Enforcing a line budget per file and per function keeps the codebase in a shape that both humans and agents can work with.
pymaxlines counts only code lines, the way oxlint's max-lines rule does
with skipBlankLines and skipComments, so docstrings, comments, and blank lines stay free. It
applies one limit per file and one per function, with separate thresholds for test files.
Quick example
A failing run prints one line per offender and exits 1:
$ pymaxlines src/app/service.py
src/app/service.py: 412 code lines (max 400)
src/app/service.py:88: function 'handle_request' has 73 code lines (max 60)
Installation
Add the hook to .pre-commit-config.yaml and run pre-commit install or
prek install:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/jeffzi/pymaxlines
rev: main # pin to a release tag once one is published
hooks:
- id: check-max-lines
To run it outside pre-commit:
uvx pymaxlines file1.py file2.py
Usage
The hook runs on .py and .pyi files. A file is a test file when it sits under a tests/
directory or is named test_*.py or *_test.py. Everything else is a source file. The flag
table below uses these terms in its "Applies to" column.
Defaults, all overridable with flags:
| Flag | Applies to | Default | Meaning |
|---|---|---|---|
--max-lines |
source | 400 | code lines per file |
--max-lines-test |
test | 800 | code lines per file |
--max-lines-per-function |
source | 60 | code lines per function, 0 disables |
--max-lines-per-function-test |
test | 0 | code lines per function, 0 disables |
--skip-blank-lines |
all | True | exclude blank lines from counts |
--skip-comments |
all | True | exclude comment-only lines |
--skip-docstrings |
all | True | exclude standalone docstrings |
Pass flags through the hook's args:
- id: check-max-lines
args: [--max-lines, "300", --max-lines-per-function, "40"]
Each --skip-* flag has a --no-skip-* counterpart. To count comment-only and blank lines:
- id: check-max-lines
args: [--no-skip-comments, --no-skip-blank-lines]
What counts as a code line
By default (all --skip-* flags on):
- Blank lines, comment-only lines, and standalone docstrings (module, class, or function) are free.
- Whitespace-only lines inside a multi-line string are free.
- Every other line counts once, including non-blank lines inside multi-line strings.
- A function's count includes its
defline and every code line up to the end of its body, nested functions included. - Decorator lines count toward the file total but not toward the decorated function's count.
Turning a --skip-* flag off (e.g. --no-skip-blank-lines) makes that category count toward both
file and function totals.
pymaxlines reports unreadable files (missing, undecodable, or syntax errors) and fails the run.
Contributing
Run task --list for the development workflow. task check runs every hook; task test:matrix
runs the suite on each supported Python version.
License
MIT. See LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pymaxlines-0.3.0.tar.gz.
File metadata
- Download URL: pymaxlines-0.3.0.tar.gz
- Upload date:
- Size: 7.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5c6c5940285d02547dc45085e0774e68f31b713584c2b000e7fe7fd81c14da1b
|
|
| MD5 |
0ae1252742b9e286507ce24cf32d1fc8
|
|
| BLAKE2b-256 |
1aedf590a161e50d0d9aba0d3e86cea25413f02b8a032fb7f8f78efa2050e51a
|
Provenance
The following attestation bundles were made for pymaxlines-0.3.0.tar.gz:
Publisher:
publish.yml on jeffzi/pymaxlines
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pymaxlines-0.3.0.tar.gz -
Subject digest:
5c6c5940285d02547dc45085e0774e68f31b713584c2b000e7fe7fd81c14da1b - Sigstore transparency entry: 2699370983
- Sigstore integration time:
-
Permalink:
jeffzi/pymaxlines@21b359c77f80f10a644f198f9d69bd04b8f0c494 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/jeffzi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@21b359c77f80f10a644f198f9d69bd04b8f0c494 -
Trigger Event:
release
-
Statement type:
File details
Details for the file pymaxlines-0.3.0-py3-none-any.whl.
File metadata
- Download URL: pymaxlines-0.3.0-py3-none-any.whl
- Upload date:
- Size: 7.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e21e7f87b2652ba0af872d726558b6219bc6f23eca06aa429be3296a41659e6f
|
|
| MD5 |
aa93c3c509be9971e1e68fdfd4477e25
|
|
| BLAKE2b-256 |
59f956922936d65a344a0cd3e26156fe16f78f2f22d343708faa06689def4b9b
|
Provenance
The following attestation bundles were made for pymaxlines-0.3.0-py3-none-any.whl:
Publisher:
publish.yml on jeffzi/pymaxlines
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pymaxlines-0.3.0-py3-none-any.whl -
Subject digest:
e21e7f87b2652ba0af872d726558b6219bc6f23eca06aa429be3296a41659e6f - Sigstore transparency entry: 2699371136
- Sigstore integration time:
-
Permalink:
jeffzi/pymaxlines@21b359c77f80f10a644f198f9d69bd04b8f0c494 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/jeffzi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@21b359c77f80f10a644f198f9d69bd04b8f0c494 -
Trigger Event:
release
-
Statement type: