AutoTeam-F / src /autoteam /_playwright_guard.py
ZRainbow's picture
feat(round-4 infra): docker-guard + playwright-hardening + 调研/PRD/SPEC 文档基线
d9d9e44
Raw
History Blame Contribute Delete
2.07 kB
"""Playwright 一致性硬化(SPEC-4)。
本模块提供白名单常量与运行时守卫,禁止 sync_playwright 进入 asyncio loop。
所有静态测试(tests/static/test_playwright_hygiene.py)与运行时 guard
(api.py:_PlaywrightExecutor)共享本文件作为单一信源(SSOT)。
"""
from __future__ import annotations
import asyncio
import logging
import threading
logger = logging.getLogger(__name__)
# 9 个允许从 playwright.sync_api 导入的符号
ALLOWED_SYNC_NAMES: frozenset[str] = frozenset({
"sync_playwright", # 上下文管理器
"Playwright", # 顶层句柄类型
"Browser", # 浏览器实例类型
"BrowserContext", # 浏览器上下文
"BrowserType", # chromium/firefox/webkit 类型
"Page", # 页面句柄
"Locator", # 定位器
"Error", # Playwright 通用异常
"TimeoutError", # Playwright 超时异常
})
# 1 个 typeshed 类型(仅在 if TYPE_CHECKING 块内允许)
TYPE_CHECKING_ONLY: frozenset[str] = frozenset({
"ElementHandle",
})
# 任何形式的 from <X> import / import <X> 禁止模块
FORBIDDEN_MODULES: frozenset[str] = frozenset({
"playwright.async_api",
})
# per-file 豁免 marker,文件首 5 行内出现即整文件跳过守卫
EXEMPTION_MARKER: str = "autoteam: allow-async-playwright"
def assert_sync_context() -> None:
"""断言当前线程不在 asyncio loop 中。
仅用于 sync_playwright 调用前的前置检查。若检测到 loop,立即抛
RuntimeError(消息含 thread/loop_id 标识,便于诊断)。
期望路径: 普通线程 / 专用 worker 线程 → asyncio.get_running_loop()
抛 RuntimeError → 无 loop → 放行。
"""
try:
loop = asyncio.get_running_loop()
except RuntimeError:
return
raise RuntimeError(
f"[PlaywrightGuard] thread={threading.current_thread().name!r} "
f"loop_id=0x{id(loop):x} — sync_playwright 不允许在 asyncio loop 中调用。"
f"详见 PRD-4 §12 R1 opt-out 流程。"
)