Merge pull request #7050 from DDSRem-Dev/codex-comment-migration

docs: 统一全项目中文注释和结构化契约说明
This commit is contained in:
DDSRem
2026-10-17 19:26:29 +08:00
committed by GitHub
parent 3c7431b7da
commit 2de976f6b3
2345 changed files with 143990 additions and 61534 deletions

View File

@@ -1,4 +1,6 @@
"""检测关键 async 路径中新引入的直接阻塞调用。"""
"""
检测关键 async 路径中新引入的直接阻塞调用。
"""
from __future__ import annotations
@@ -15,6 +17,7 @@ DEFAULT_BASELINE = PROJECT_ROOT / "tests/fixtures/architecture/async-blocking-ba
SCAN_ROOTS = ("app",)
EXCLUDED_ROOTS = ("app/plugins",)
# RequestUtils 中直接执行同步 HTTP 请求或管理同步响应的方法。
_SYNC_HTTP_METHODS = {
"delete_res",
"get",
@@ -38,6 +41,7 @@ _REQUESTS_METHODS = {
"put",
"request",
}
# 这些 pathlib 操作会访问文件系统,不能视为纯路径计算。
_PATH_IO_METHODS = {
"exists",
"glob",
@@ -73,6 +77,7 @@ _SUBPROCESS_METHODS = {
"run",
}
_OS_IO_METHODS = {"listdir", "scandir", "walk"}
# SystemConfigOper 的这些读取由内存快照提供,不计入同步数据库阻塞。
_SYSTEM_CONFIG_MEMORY_READS = {
"app.db.oper.SystemConfigOper.all",
"app.db.oper.SystemConfigOper.get",
@@ -83,14 +88,22 @@ _SYSTEM_CONFIG_MEMORY_READS = {
@dataclass(frozen=True)
class _Binding:
"""描述由明确 import 或局部赋值建立的符号来源。"""
"""
描述由明确导入或局部赋值建立的符号来源。
:param family: 能力来源分类,区分同步或异步 HTTP、路径、数据库和普通模块;unknown 表示来源不明
:param qualified_name: 已解析的完整模块、类或方法路径;来源不明时为空字符串
:param kind: 符号形态,区分模块、类、实例、可调用对象、集合和未知来源;默认 module
"""
family: str
qualified_name: str
kind: str = "module"
# 显式未知绑定用于遮蔽外层同名导入,避免错误继承其能力来源。
_UNKNOWN_BINDING = _Binding("unknown", "", "unknown")
# 控制流合并时优先保留这些同步能力,防止分支遮蔽已知阻塞风险。
_BLOCKING_FAMILIES = {
"os",
"requests",
@@ -104,7 +117,9 @@ _BLOCKING_FAMILIES = {
def _binding_for_qualified(qualified_name: str) -> _Binding:
"""按稳定模块路径识别门禁关心的符号族。"""
"""
按稳定模块路径识别门禁关心的符号族。
"""
if qualified_name == "app.adapters.network.http.RequestUtils":
return _Binding("sync_http", qualified_name, "class")
if qualified_name == "app.adapters.network.http.AsyncRequestUtils":
@@ -134,7 +149,9 @@ def _resolve_binding(
expression: ast.expr,
bindings: dict[str, _Binding],
) -> _Binding | None:
"""解析明确 import、构造、属性访问和简单路径派生。"""
"""
解析明确导入、构造、属性访问和简单路径派生。
"""
if isinstance(expression, ast.Name):
return bindings.get(expression.id)
if isinstance(expression, ast.Attribute):
@@ -169,7 +186,9 @@ def _merge_binding_options(
name: str,
options: Sequence[_Binding | None],
) -> _Binding | None:
"""控制流合流时保留任一分支可能进入的同步阻塞类型。"""
"""
控制流合流时保留任一分支可能进入的同步阻塞类型。
"""
if options and all(option == options[0] for option in options):
return options[0]
blocking = sorted(
@@ -190,7 +209,9 @@ def _merge_binding_options(
def _merge_binding_states(
states: Sequence[dict[str, _Binding]],
) -> dict[str, _Binding]:
"""合并互斥控制流的局部符号表。"""
"""
合并互斥控制流的局部符号表。
"""
names = set().union(*(state.keys() for state in states))
merged: dict[str, _Binding] = {}
for name in names:
@@ -201,7 +222,9 @@ def _merge_binding_states(
def _load_oper_methods(root: Path) -> dict[str, set[str]]:
"""从 Oper 源码建立同步方法索引,避免按方法名猜测数据库调用。"""
"""
从 Oper 源码建立同步方法索引,避免按方法名猜测数据库调用。
"""
methods: dict[str, set[str]] = {}
oper_root = root / "app/db/oper"
if not oper_root.is_dir():
@@ -225,18 +248,29 @@ def _load_oper_methods(root: Path) -> dict[str, set[str]]:
class _ImportCollector(ast.NodeVisitor):
"""收集模块级 import,作为每个 async 函数的初始符号表。"""
"""
收集模块级导入,作为每个 async 函数的初始符号表。
"""
def __init__(self) -> None:
"""
初始化模块级符号来源表。
"""
self.bindings: dict[str, _Binding] = {}
def visit_Import(self, node: ast.Import) -> None:
"""
记录模块导入及别名对应的完整来源。
"""
for alias in node.names:
local_name = alias.asname or alias.name.split(".", 1)[0]
qualified_name = alias.name if alias.asname else local_name
self.bindings[local_name] = _binding_for_qualified(qualified_name)
def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
"""
记录绝对导入的符号来源,跳过相对导入和通配导入。
"""
if node.level or not node.module:
return
for alias in node.names:
@@ -247,6 +281,9 @@ class _ImportCollector(ast.NodeVisitor):
self.bindings[local_name] = _binding_for_qualified(qualified_name)
def _bind_target(self, target: ast.expr, binding: _Binding | None) -> None:
"""
传播简单赋值来源;解包目标标记为未知,避免猜测各元素类型。
"""
if isinstance(target, ast.Name):
self.bindings[target.id] = binding or _UNKNOWN_BINDING
elif isinstance(target, (ast.Tuple, ast.List)):
@@ -254,26 +291,43 @@ class _ImportCollector(ast.NodeVisitor):
self._bind_target(element, None)
def visit_Assign(self, node: ast.Assign) -> None:
"""
传播模块级简单别名,并清除无法证明来源的旧绑定。
"""
binding = _resolve_binding(node.value, self.bindings)
for target in node.targets:
self._bind_target(target, binding)
def visit_AnnAssign(self, node: ast.AnnAssign) -> None:
"""
按赋值右值传播模块级来源;没有右值时标记为未知。
"""
binding = _resolve_binding(node.value, self.bindings) if node.value else None
self._bind_target(node.target, binding)
def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
"""
跳过函数体,避免把局部导入泄漏到模块符号表。
"""
return
def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None:
"""
跳过异步函数体,保持模块级导入收集边界。
"""
return
def visit_ClassDef(self, node: ast.ClassDef) -> None:
"""
跳过类体,避免将类内绑定当作模块全局来源。
"""
return
class _AsyncCallVisitor(ast.NodeVisitor):
"""用局部符号传播识别一个 async 函数直接执行的阻塞调用。"""
"""
用局部符号传播识别一个 async 函数直接执行的阻塞调用。
"""
def __init__(
self,
@@ -283,6 +337,14 @@ class _AsyncCallVisitor(ast.NodeVisitor):
*,
record_calls: bool,
) -> None:
"""
复制可见符号来源并绑定参数,为单个函数建立调用统计。
:param function: 本次分析的函数定义
:param module_bindings: 定义处可见的外层符号来源,不原地修改
:param oper_methods: Oper 完整类名到同步方法名集合的索引
:param record_calls: 是否统计当前函数中的阻塞调用;关闭时仍追踪嵌套函数
"""
self.calls: Counter[str] = Counter()
self.nested_functions: list[
tuple[ast.FunctionDef | ast.AsyncFunctionDef, dict[str, _Binding]]
@@ -296,6 +358,9 @@ class _AsyncCallVisitor(ast.NodeVisitor):
self,
function: ast.FunctionDef | ast.AsyncFunctionDef,
) -> None:
"""
用参数遮蔽外层同名绑定,并从显式注解恢复已知能力来源。
"""
arguments = (
*function.args.posonlyargs,
*function.args.args,
@@ -309,6 +374,11 @@ class _AsyncCallVisitor(ast.NodeVisitor):
self._bindings[argument.arg] = _UNKNOWN_BINDING
def _annotation_binding(self, annotation: ast.expr | None) -> _Binding | None:
"""
从联合、可选和容器注解中解析实例或集合元素来源。
:return: 可证明的实例或集合绑定;未识别时返回 None
"""
if annotation is None:
return None
if isinstance(annotation, ast.Subscript):
@@ -345,6 +415,9 @@ class _AsyncCallVisitor(ast.NodeVisitor):
@staticmethod
def _call_label(binding: _Binding) -> str:
"""
把完整调用路径归一为基线使用的稳定名称。
"""
parts = binding.qualified_name.split(".")
if binding.family == "sync_oper" and len(parts) >= 2:
return ".".join(parts[-2:])
@@ -357,6 +430,11 @@ class _AsyncCallVisitor(ast.NodeVisitor):
return binding.qualified_name
def _record_call(self, node: ast.Call, binding: _Binding | None) -> None:
"""
仅在启用统计且来源命中同步能力规则时累计调用次数。
未被局部符号遮蔽的 open 按内置阻塞调用处理;系统配置内存读取不计入数据库阻塞。
"""
if not self._record_calls:
return
if not binding:
@@ -389,6 +467,9 @@ class _AsyncCallVisitor(ast.NodeVisitor):
self.calls[self._call_label(binding)] += 1
def _bind_target(self, target: ast.expr, binding: _Binding | None) -> None:
"""
传播简单目标的已知来源;解包赋值将各目标标记为未知。
"""
if isinstance(target, ast.Name):
self._bindings[target.id] = binding or _UNKNOWN_BINDING
elif isinstance(target, (ast.Tuple, ast.List)):
@@ -396,22 +477,34 @@ class _AsyncCallVisitor(ast.NodeVisitor):
self._bind_target(element, None)
def visit_Import(self, node: ast.Import) -> None:
"""
把函数内的模块导入加入当前符号表。
"""
collector = _ImportCollector()
collector.visit_Import(node)
self._bindings.update(collector.bindings)
def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
"""
把函数内可解析的绝对符号导入加入当前符号表。
"""
collector = _ImportCollector()
collector.visit_ImportFrom(node)
self._bindings.update(collector.bindings)
def visit_Assign(self, node: ast.Assign) -> None:
"""
先统计右值调用,再将可证明来源传播给赋值目标。
"""
self.visit(node.value)
binding = self._resolve(node.value)
for target in node.targets:
self._bind_target(target, binding)
def visit_AnnAssign(self, node: ast.AnnAssign) -> None:
"""
统计右值调用,并依次使用右值来源和类型注解绑定目标。
"""
if node.value:
self.visit(node.value)
binding = self._resolve(node.value) if node.value else None
@@ -419,6 +512,9 @@ class _AsyncCallVisitor(ast.NodeVisitor):
self._bind_target(node.target, binding)
def visit_AugAssign(self, node: ast.AugAssign) -> None:
"""
统计增量赋值右值,并使目标的旧来源失效。
"""
self.visit(node.value)
self._bind_target(node.target, None)
@@ -427,6 +523,11 @@ class _AsyncCallVisitor(ast.NodeVisitor):
statements: Sequence[ast.stmt],
initial: dict[str, _Binding],
) -> dict[str, _Binding]:
"""
在指定符号副本中分析分支,并恢复调用方的符号表。
:return: 分支执行后的来源映射;调用统计仍累计在当前访问器中
"""
saved = self._bindings
self._bindings = dict(initial)
for statement in statements:
@@ -436,6 +537,9 @@ class _AsyncCallVisitor(ast.NodeVisitor):
return result
def visit_If(self, node: ast.If) -> None:
"""
分别分析互斥分支并合并来源,保留任一路径可能存在的阻塞能力。
"""
self.visit(node.test)
initial = dict(self._bindings)
body = self._visit_branch(node.body, initial)
@@ -443,6 +547,9 @@ class _AsyncCallVisitor(ast.NodeVisitor):
self._bindings = _merge_binding_states((body, alternate))
def visit_For(self, node: ast.For | ast.AsyncFor) -> None:
"""
从集合来源推断循环元素,并合并零次执行与循环完成路径。
"""
self.visit(node.iter)
initial = dict(self._bindings)
collection = self._resolve(node.iter)
@@ -459,6 +566,9 @@ class _AsyncCallVisitor(ast.NodeVisitor):
self._bindings = _merge_binding_states((initial, completed))
def visit_AsyncFor(self, node: ast.AsyncFor) -> None:
"""
按相同的元素传播和零次执行规则分析异步循环。
"""
self.visit_For(node)
def _visit_comprehension(
@@ -466,6 +576,9 @@ class _AsyncCallVisitor(ast.NodeVisitor):
generators: Sequence[ast.comprehension],
outputs: Sequence[ast.expr],
) -> None:
"""
在独立符号副本中分析推导式的迭代、条件和输出表达式。
"""
saved = self._bindings
self._bindings = dict(saved)
for generator in generators:
@@ -484,22 +597,40 @@ class _AsyncCallVisitor(ast.NodeVisitor):
self._bindings = saved
def visit_ListComp(self, node: ast.ListComp) -> None:
"""
隔离列表推导式的目标绑定,并统计其表达式中的阻塞调用。
"""
self._visit_comprehension(node.generators, (node.elt,))
def visit_SetComp(self, node: ast.SetComp) -> None:
"""
隔离集合推导式的目标绑定,并统计其表达式中的阻塞调用。
"""
self._visit_comprehension(node.generators, (node.elt,))
def visit_GeneratorExp(self, node: ast.GeneratorExp) -> None:
"""
在独立符号表中扫描生成器表达式,记录其中可证明的阻塞调用。
"""
self._visit_comprehension(node.generators, (node.elt,))
def visit_DictComp(self, node: ast.DictComp) -> None:
"""
隔离字典推导式的目标绑定,同时扫描键和值表达式。
"""
self._visit_comprehension(node.generators, (node.key, node.value))
def visit_NamedExpr(self, node: ast.NamedExpr) -> None:
"""
统计赋值表达式的右值,并传播其来源到当前作用域。
"""
self.visit(node.value)
self._bind_target(node.target, self._resolve(node.value))
def visit_Call(self, node: ast.Call) -> None:
"""
记录已知阻塞调用,并继续检查参数和立即调用的匿名函数体。
"""
binding = self._resolve(node.func)
self._record_call(node, binding)
if isinstance(node.func, ast.Lambda):
@@ -516,6 +647,9 @@ class _AsyncCallVisitor(ast.NodeVisitor):
self,
node: ast.FunctionDef | ast.AsyncFunctionDef,
) -> None:
"""
扫描函数定义时执行的装饰器和默认参数,不进入函数体。
"""
for decorator in node.decorator_list:
self.visit(decorator)
for default in node.args.defaults:
@@ -528,17 +662,29 @@ class _AsyncCallVisitor(ast.NodeVisitor):
self,
node: ast.FunctionDef | ast.AsyncFunctionDef,
) -> None:
"""
记录嵌套函数及定义处的来源快照,交由独立分析统计函数体。
"""
self._visit_definition_expressions(node)
self.nested_functions.append((node, dict(self._bindings)))
self._bindings[node.name] = _UNKNOWN_BINDING
def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
"""
扫描定义期表达式,并将同步嵌套函数加入后续分析队列。
"""
self._visit_nested_function(node)
def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None:
"""
扫描定义期表达式,并将异步嵌套函数加入后续分析队列。
"""
self._visit_nested_function(node)
def _visit_lambda_defaults(self, node: ast.Lambda) -> None:
"""
统计匿名函数创建时执行的默认参数表达式。
"""
for default in node.args.defaults:
self.visit(default)
for keyword_default in node.args.kw_defaults:
@@ -546,13 +692,18 @@ class _AsyncCallVisitor(ast.NodeVisitor):
self.visit(keyword_default)
def visit_Lambda(self, node: ast.Lambda) -> None:
"""
仅扫描匿名函数默认参数,函数体由直接调用路径另行访问。
"""
self._visit_lambda_defaults(node)
def _root_functions(
tree: ast.Module,
) -> Iterator[tuple[str, ast.AsyncFunctionDef]]:
"""产出模块顶层和类直接拥有的 async 入口。"""
"""
产出模块顶层和类直接拥有的 async 入口。
"""
for node in tree.body:
if isinstance(node, ast.AsyncFunctionDef):
yield node.name, node
@@ -563,7 +714,9 @@ def _root_functions(
def _is_excluded_path(root: Path, path: Path) -> bool:
"""判断源码路径是否位于明确排除的插件目录。"""
"""
判断源码路径是否位于明确排除的插件目录。
"""
relative = path.relative_to(root).as_posix()
return any(
relative == excluded or relative.startswith(f"{excluded}/")
@@ -572,7 +725,9 @@ def _is_excluded_path(root: Path, path: Path) -> bool:
def _scan_paths(root: Path, scan_roots: Sequence[str | Path]) -> Iterator[Path]:
"""按稳定顺序产出存在且不属于插件目录的扫描目标。"""
"""
按稳定顺序产出存在且不属于插件目录的扫描目标。
"""
for scan_root in scan_roots:
target = root / scan_root
if target.is_file():
@@ -590,7 +745,13 @@ def collect_async_blocking(
root: Path = PROJECT_ROOT,
scan_roots: Sequence[str | Path] = SCAN_ROOTS,
) -> dict[str, int]:
"""扫描关键目录并以文件、函数、调用名聚合存量次数。"""
"""
扫描关键目录并以文件、函数、调用名聚合存量次数。
:param root: 仓库根目录,同时用于读取 Oper 同步方法声明
:param scan_roots: 相对于仓库根目录的文件或目录;插件路径始终排除
:return: 文件、函数限定名与调用名组成的键到静态调用次数的映射
"""
debt: Counter[str] = Counter()
oper_methods = _load_oper_methods(root)
for path in _scan_paths(root, scan_roots):
@@ -639,7 +800,13 @@ def collect_async_blocking(
def compare_async_blocking(
baseline: dict[str, int], current: dict[str, int]
) -> list[str]:
"""允许存量减少或删除,拒绝新增阻塞调用和调用次数增长。"""
"""
允许存量减少或删除,拒绝新增阻塞调用和调用次数增长。
:param baseline: 已审查的阻塞调用次数上限
:param current: 当前源码收集出的阻塞调用次数
:return: 新增或增长条目的诊断列表;下降不构成问题
"""
problems = []
for entry, count in current.items():
previous = baseline.get(entry)
@@ -651,7 +818,9 @@ def compare_async_blocking(
def main() -> int:
"""执行 async 阻塞 baseline check 或显式 write。"""
"""
执行 async 阻塞基线检查或显式写入。
"""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--write", action="store_true", help="写入当前阻塞债务")
parser.add_argument("--baseline", type=Path, default=DEFAULT_BASELINE)