# -*- coding: utf-8 -*- """ 代码指纹 (零依赖, 不连库, 不 import 业务模块) ================================================ 运行: python scripts/code_fingerprint.py 只打印一行 12 位短哈希 为什么需要它 ------------ **源码是打进镜像的**, `docker compose run --rm pms-web python scripts/xxx.py` 跑的是 **镜像里的那份**, 不是工作树里刚 `git pull` 下来的那份。于是有一条特别难发现的路径: git pull → make test → ALL SUITES PASS 这个 PASS 是**旧代码的 PASS**。它读起来像"新改的代码验过了", 实际上新代码一行都没跑过 —— 连单测清单都还是旧的 (新增的一批测试文件根本不在镜像里, `run_tests.py` 只会打印 "跳过 xxx (文件不存在)" 然后照样 ALL SUITES PASS)。2026-07-31 实机就是这样: `make t-pre` 的返回里少了新加的字段, 才反推出容器跑的是旧镜像。 这跟本项目一直在治的是同一种病 —— **一个不成立的结论长得像成立**。所以给它一个指纹: `make test` 会把容器里的指纹和工作树的指纹对一遍, 对不上就喊, 而不是让那个 PASS 蒙人。 指纹只覆盖 .py 源码 (app/ scripts/ config/)。改 Makefile、compose、DDL 不影响它 —— 那几样不进 Python 运行时, 另有各自的生效路径。 """ import hashlib import os import sys ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) DIRS = ("app", "scripts", "config") def fingerprint(root: str = ROOT) -> str: h = hashlib.sha256() files = [] for d in DIRS: base = os.path.join(root, d) for dirpath, dirnames, filenames in os.walk(base): # __pycache__ 里是编译产物, 跟着源码走, 不该进指纹 dirnames[:] = [x for x in dirnames if x != "__pycache__"] for fn in filenames: if fn.endswith(".py"): files.append(os.path.join(dirpath, fn)) for p in sorted(files, key=lambda x: os.path.relpath(x, root)): rel = os.path.relpath(p, root).replace(os.sep, "/") h.update(rel.encode("utf-8")) h.update(b"\0") with open(p, "rb") as f: h.update(hashlib.sha256(f.read()).digest()) return h.hexdigest()[:12] if __name__ == "__main__": # 只打印指纹本身, 一行, 不带任何前后缀 —— 调用方要拿它做字符串比较 sys.stdout.write(fingerprint() + "\n")