MAINT-20 part 2: same-code script checks -OO code, added/removed files, allowed paths, docstring list

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
glenn schrooyen
2026-11-01 03:27:45 +01:00
co-authored by Claude
parent d4c01daca5
commit 0fb4c2b5e2
+75 -10
View File
@@ -5,12 +5,19 @@
For each FILE (default: every goodwe_controller/app/*.py that differs from
BASE_REF, default release/1.0) the ast.dump() of the tree with docstrings
removed must be identical at BASE_REF and in the working tree. Also prints
code lines vs comment-only + docstring lines (whole lines, via tokenize) before
and after. Run from the repository root. Exit 1 on any code difference.
removed must be identical at BASE_REF and in the working tree, and so must
the `python -OO` compiled code (a wholly removed docstring leaves a no-op
there). An added or removed app/*.py file is a code difference. The diff
against BASE_REF may touch only goodwe_controller/app/*.py,
goodwe_controller/DECISIONS/*.md and this script. Also prints code lines vs
comment-only + docstring lines (whole lines, via tokenize) before and after,
and the docstrings removed entirely or added. Run from the repository root.
Exit 1 on any difference.
"""
import ast
import io
import os
import re
import subprocess
import sys
import tokenize
@@ -53,29 +60,87 @@ def counts(src):
return len(code), len(doc) + len(com - code - doc)
def qualnames(src):
out = {}
def walk(node, prefix):
for n in ast.iter_child_nodes(node):
if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef,
ast.ClassDef)):
q = prefix + n.name
out[q] = docstring(n) is not None
walk(n, q + ".")
out["<module>"] = docstring(ast.parse(src)) is not None
walk(ast.parse(src), "")
return out
def code_sig(co):
consts = tuple(code_sig(c) if hasattr(c, "co_code") else c
for c in co.co_consts)
return (co.co_name, co.co_code, consts, co.co_names, co.co_varnames,
co.co_freevars, co.co_cellvars, co.co_flags, co.co_argcount,
co.co_stacksize, co.co_exceptiontable)
def oo_same(old, new):
return (code_sig(compile(old, "f", "exec", optimize=2))
== code_sig(compile(new, "f", "exec", optimize=2)))
def git(*a):
return subprocess.run(["git", *a], check=True, capture_output=True,
text=True).stdout
def exists(base, f):
return subprocess.run(["git", "cat-file", "-e", f"{base}:{f}"],
capture_output=True).returncode == 0
ALLOWED = re.compile(r"^goodwe_controller/(app/[^/]+\.py|DECISIONS/[^/]+\.md|"
r"tools/maint20_same_code\.py)$")
def ratio(m, c):
return f"{m / c:.2f}" if c else "n/a"
def main(argv):
base = argv[0] if argv else "release/1.0"
files = argv[1:] or [
f for f in git("diff", "--name-only", base, "--",
"goodwe_controller/app").split()
if f.endswith(".py")]
bad = 0
print(f"base {base} = {git('rev-parse', base).strip()}")
changed = git("diff", "--name-only", base).split()
for f in changed:
if not ALLOWED.match(f):
print(f"FAIL: diff touches {f}, outside app/*.py, DECISIONS/*.md "
"and this script")
bad += 1
files = argv[1:] or [f for f in changed if re.match(
r"^goodwe_controller/app/[^/]+\.py$", f)]
for f in files:
in_base, in_head = exists(base, f), os.path.exists(f)
if in_base != in_head:
print(f"CODE DIFFERS {f} "
f"{'removed' if in_base else 'added'} file")
bad += 1
continue
old = git("show", f"{base}:{f}")
new = open(f, encoding="utf-8").read()
same = dump(old) == dump(new)
oo = oo_same(old, new)
(c0, m0), (c1, m1) = counts(old), counts(new)
print(f"{'SAME CODE' if same else 'CODE DIFFERS'} {f} "
f"code {c0}->{c1} comment+doc {m0}->{m1} "
f"ratio {m0 / c0:.2f}->{m1 / c1:.2f}")
bad += not same
print("OK: no code change" if not bad else f"FAIL: {bad} file(s) differ")
f"ratio {ratio(m0, c0)}->{ratio(m1, c1)} "
f"-OO {'same' if oo else 'DIFFERS'}")
bad += (not same) + (not oo)
qo, qn = qualnames(old), qualnames(new)
for q in sorted(q for q in qo if qo[q] and not qn.get(q, True)):
print(f" docstring REMOVED: {f}:{q}")
for q in sorted(q for q in qn if qn[q] and not qo.get(q, True)):
print(f" docstring ADDED: {f}:{q}")
print("OK: no code change" if not bad else f"FAIL: {bad} problem(s)")
return 1 if bad else 0