Lint#
The static checks behind python -m modelly.lint. The rules, with examples, are in
Lint.
Static checks for Modelly models: bugs that break reproducibility and loops that scale badly.
Run as python -m modelly.lint PATH [PATH ...]. Nothing is imported or executed: the source
is parsed and walked. A finding on a line ending in # noqa (or # noqa: MDL101) is
skipped, and [tool.modelly.lint] in pyproject.toml selects and ignores rules (see
modelly.lint.config). The rules, with examples, are in docs/lint.md; --explain
CODE prints one of them.
- class Finding(path: str, line: int, column: int, rule: Rule)[source]#
One rule hit at a place in a file.
- class Rule(code: str, severity: str, message: str, hint: str)[source]#
One check: its code, how serious it is, what it found and what to do instead.
- lint_paths(paths: Iterable[str | Path]) list[Finding][source]#
Return the findings for every Python file in
paths.- Raises:
SyntaxError – when a file is not valid Python.
- lint_source(source: str, path: str = '<string>', siblings: SiblingColumns | None = None) list[Finding][source]#
Return the findings for one module’s source, sorted by position.
siblingsresolves the agent classes the module imports from other files of the project, so MDL107 knows their Column attributes; without it only this source counts.- Raises:
SyntaxError – when
sourceis not valid Python.
The rules of modelly.lint and the AST helpers they share.
Codes below 100 are errors: the model runs but gives a wrong or unreproducible result. Codes from 100 are warnings: the model is right but its cost grows faster than it needs to.
- class Rule(code: str, severity: str, message: str, hint: str)[source]#
One check: its code, how serious it is, what it found and what to do instead.
- dotted(node: AST) list[str][source]#
Return the names along an attribute chain:
self.model.agents-> self, model, agents.
- is_population(node: AST, in_model: bool) bool[source]#
Whether
nodeis the model’s whole population or one type of it, not a cell’s agents.
- is_set_expr(node: AST) bool[source]#
Whether
nodebuilds a set, whose iteration order follows memory addresses.
- is_mutable_literal(node: AST | None) bool[source]#
Whether
nodeis[],{}, a set literal or an empty list/dict/set call.
- calls_super_init(func: FunctionDef) bool[source]#
Whether
funccallssuper().__init__or<Base>.__init__(self, ...).
- branches(func: FunctionDef) int[source]#
Count the decision points of
func: its cyclomatic complexity minus one.
The [tool.modelly.lint] table of pyproject.toml: which findings to keep.
`toml
[tool.modelly.lint]
select = ["MDL0", "MDL101"] # only these codes or prefixes (default: all)
ignore = ["MDL105"] # drop these codes or prefixes
per-file-ignores = { "tests/*" = ["MDL001"], "setup_*.py" = ["MDL1"] }
`
The file is the first pyproject.toml found from the working directory upwards;
per-file patterns are globs on the path relative to that file’s folder, or on the bare
file name. --select and --ignore on the command line replace the file’s values.
- exception ConfigError[source]#
The
[tool.modelly.lint]table has a key or value the linter does not know.
- prefixes(value: str | list[str]) tuple[str, ...][source]#
Normalise
"MDL0, MDL101"or["MDL0", "MDL101"]to upper-case prefixes.
- class Config(select: tuple[str, ...] = (), ignore: tuple[str, ...] = (), per_file_ignores: tuple[tuple[str, tuple[str, ...]], ...] = (), root: Path = PosixPath('.'))[source]#
Which findings to keep: the select, ignore and per-file-ignores settings.
- keeps(finding: lint.Finding) bool[source]#
Whether
findingsurvives select, ignore and its file’s ignores.
- find_pyproject(start: Path) Path | None[source]#
The first
pyproject.tomlinstartor one of its parents.
- load_config(start: Path | None = None) Config[source]#
Read
[tool.modelly.lint]from the nearest pyproject.toml; empty when there is none.- Raises:
ConfigError – on an unknown key or a value that is not a list of codes.
tomllib.TOMLDecodeError – when the file is not valid TOML.