mirror of
https://github.com/kuhyx/steam-backlog-enforcer.git
synced 2026-11-11 21:33:49 +01:00
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (mypy, mypy, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (pylint, pylint, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (rest, , mypy,pylint) (push) Waiting to run
Tests / test (push) Waiting to run
Tests / web-coverage (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
The single ci-mirror manual hook (clean venv + pre-commit --all-files +
serial pytest) ran past the 60 s per-command cap, so the tree could not be
stamped for a push. It is now nine gate steps, each under 55 s at load ~40:
- ci-mirror-venv / -precommit / -pytest: scripts/ci_mirror.sh parts;
pytest runs on 4 xdist workers, pytest-cov still enforces 100 %.
- ci-mirror-pylint-{pkg,tests-1,tests-2,tests-3}: per-file checks over a
partition of the pylint hook's targets; -dup and -cycles: the two
cross-file checks on the whole tree. pylint is now a local hook so all of
them share one env (&pylint_env, pylint==4.0.9).
- scripts/pylint_symilar_cache.py: pylint plugin that hashes each file once
for duplicate-code instead of once per file pair (same R0801 output).
Tests: an autouse guard refuses non-loopback DNS (several tests made live
HLTB requests), two web fixtures poll at 0.01 s, and the rate-limit test
uses a fake clock. CLAUDE.md and AGENTS.md get the push line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzxdWWjUkRCFGNMnnXZrSP
78 lines
2.8 KiB
Python
78 lines
2.8 KiB
Python
"""Pylint plugin: hash each file once for the duplicate-code check (R0801).
|
|
|
|
pylint's similarity checker compares every pair of linted files, and
|
|
``Symilar._find_common`` calls ``symilar.hash_lineset`` on BOTH files of every
|
|
pair. With ~550 files that is ~150 000 hashings of the same ~550 files, and it
|
|
was the slowest part of a whole-tree pylint run (well over 28 s of ~55 s under
|
|
the resource cap).
|
|
|
|
``hash_lineset`` is a pure function of a file's stripped lines and the window
|
|
size, and ``_find_common`` only reads the two dicts it returns (the line limits
|
|
it mutates are ``copy.copy``-ed first). Caching the result per (file, window)
|
|
therefore yields the same R0801 messages, computed once per file.
|
|
|
|
Fails closed: if a pylint upgrade changes ``hash_lineset``'s signature, this
|
|
raises instead of caching a function whose contract it no longer knows.
|
|
|
|
Loaded through ``load-plugins`` in pyproject.toml; the ``init-hook`` there puts
|
|
the repo root on ``sys.path`` first, which makes ``scripts.`` importable.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import functools
|
|
import inspect
|
|
from typing import TYPE_CHECKING
|
|
|
|
from pylint.checkers import symilar
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Callable
|
|
|
|
from pylint.lint import PyLinter
|
|
|
|
HashResult = tuple[symilar.HashToIndex_T, symilar.IndexToLines_T]
|
|
HashFunc = Callable[[symilar.LineSet, int], HashResult]
|
|
|
|
EXPECTED_PARAMS = ("lineset", "min_common_lines")
|
|
|
|
|
|
def memoise(func: HashFunc) -> HashFunc:
|
|
"""Wrap ``hash_lineset`` with a per-(file, window) cache.
|
|
|
|
The key is ``id(lineset)``; the cache keeps the lineset itself alive and
|
|
checks identity on a hit, so a recycled id can never return another
|
|
file's hashes.
|
|
"""
|
|
cache: dict[tuple[int, int], tuple[symilar.LineSet, HashResult]] = {}
|
|
|
|
@functools.wraps(func)
|
|
def cached(
|
|
lineset: symilar.LineSet,
|
|
min_common_lines: int = symilar.DEFAULT_MIN_SIMILARITY_LINE,
|
|
) -> HashResult:
|
|
key = (id(lineset), min_common_lines)
|
|
hit = cache.get(key)
|
|
if hit is None or hit[0] is not lineset:
|
|
hit = (lineset, func(lineset, min_common_lines))
|
|
cache[key] = hit
|
|
return hit[1]
|
|
|
|
return cached
|
|
|
|
|
|
def register(_linter: PyLinter) -> None:
|
|
"""Patch ``symilar.hash_lineset`` once per process (pylint plugin hook)."""
|
|
current = symilar.hash_lineset
|
|
if hasattr(current, "__wrapped__"):
|
|
return
|
|
params = tuple(inspect.signature(current).parameters)
|
|
if params != EXPECTED_PARAMS:
|
|
msg = (
|
|
f"pylint_symilar_cache: symilar.hash_lineset{params} no longer "
|
|
f"matches {EXPECTED_PARAMS}; re-check that it is still pure and "
|
|
"update scripts/pylint_symilar_cache.py"
|
|
)
|
|
raise RuntimeError(msg)
|
|
symilar.hash_lineset = memoise(current)
|