mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-10-14 21:07:40 +00:00
chore(backend): 新增 basedpyright 校准配置,并修复它暴露的三处真缺陷
背景:仓库此前没有 pyright/basedpyright 配置,basedpyright 使用自身默认(≈recommended),
在 app/ 下产生 247 errors / 3799 warnings,其中 143 条是 reportMissingTypeArgument
(未参数化泛型)这类注解完整度噪音,会把真缺陷淹没(例如 OSS 的 Client.uploader 方法误用)。
配置(backend/pyrightconfig.json)
- typeCheckingMode: basic;显式把能抓真缺陷的规则保持为 error
(reportAttributeAccessIssue / reportArgumentType / reportOptionalMemberAccess /
reportIncompatibleMethodOverride / reportConstantRedefinition / reportMissingImports 等)
- 关闭注解完整度与风格类规则;tests/ 用 executionEnvironments 单独放宽(extraPaths 保证 app 可解析)
- 结果:app + main.py + tests 全量 0 errors
- 红绿验证:合成「str | None 传参」「MethodType 上取属性」「Optional 成员访问」三类
仍全部报 error,证明校准没有把真缺陷一起关掉
修复配置暴露的三处真缺陷
1) template_safety.py:_SQLALCHEMY_TYPE_WHITELIST 对全大写常量二次赋值 → 合并为一次成型
2) template_safety.py:normalize_column_type 假定 _TYPE_BASE_RE 必命中(用 # type: ignore 盖住,
None 时 .group() 会崩)→ 改为 None 安全,去掉 type-ignore
3) node/service.py ×2:CustomException(msg=reason) 中 reason 为 str | None,客户端可能收到空文案
→ 新增 SchedulerUtil.require_schedulable() 收敛「判定 + 抛异常 + 兜底文案」,调用点改为一行
验证:basedpyright 0 errors(原 247);ruff All checks passed;pytest 97 passed;
已部署并线上复验(四端 200、Host 收窄、伪造令牌被拒、验证码过期 400 保留业务文案、
容器内 require_schedulable 与 OSS 调用修正均已生效)
This commit is contained in:
@@ -28,6 +28,7 @@
|
||||
import ast
|
||||
import keyword
|
||||
import re
|
||||
from collections.abc import Iterable
|
||||
|
||||
from app.common.constant import GenConstant
|
||||
|
||||
@@ -305,11 +306,10 @@ _TYPE_BASE_RE = re.compile(r"^[A-Za-z][A-Za-z0-9_ ]*")
|
||||
# 基名里需要剔除的修饰词(它们不属于类型名,参与查表会导致 miss)
|
||||
_BASE_MODIFIER_RE = re.compile(r"\s+(unsigned|zerofill)$", re.IGNORECASE)
|
||||
|
||||
# 映射常量值的白名单(类型名只能取自这里,杜绝自由文本进产物)
|
||||
_SQLALCHEMY_TYPE_WHITELIST: frozenset[str] = frozenset(GenConstant.DB_TO_SQLALCHEMY.values())
|
||||
|
||||
# Boolean 由 MySQL tinyint(1) 规则显式产出(常量表中未必有该值),显式纳入白名单
|
||||
_SQLALCHEMY_TYPE_WHITELIST = _SQLALCHEMY_TYPE_WHITELIST | {"Boolean"}
|
||||
# 映射常量值的白名单(类型名只能取自这里,杜绝自由文本进产物)。
|
||||
# Boolean 由 MySQL tinyint(1) 规则显式产出(常量表中未必有该值),此处一次成型,
|
||||
# 避免对全大写常量二次赋值(类型检查会报 reportConstantRedefinition,也是不必要的可变性)。
|
||||
_SQLALCHEMY_TYPE_WHITELIST: frozenset[str] = frozenset(GenConstant.DB_TO_SQLALCHEMY.values()) | {"Boolean"}
|
||||
|
||||
|
||||
def _lookup_sqlalchemy_type(base: str) -> str:
|
||||
@@ -338,7 +338,11 @@ def normalize_column_type(column_type: object) -> str:
|
||||
"""
|
||||
text = "" if column_type is None else str(column_type).strip()
|
||||
if _ENUM_SET_RE.match(text):
|
||||
return _TYPE_BASE_RE.match(text).group(0).strip() # type: ignore[union-attr]
|
||||
# 不假定 _TYPE_BASE_RE 一定命中:None 安全处理,命中不了就原样返回,
|
||||
# 交给 is_valid_column_type 的形状白名单继续拦截(不在这里静默放行)。
|
||||
base_match = _TYPE_BASE_RE.match(text)
|
||||
if base_match is not None:
|
||||
return base_match.group(0).strip()
|
||||
return text
|
||||
|
||||
|
||||
@@ -589,7 +593,7 @@ def assert_text_structure(
|
||||
reference: str,
|
||||
*,
|
||||
sentinel: str,
|
||||
dynamic_values: object = (),
|
||||
dynamic_values: Iterable[str] = (),
|
||||
filename: str = "<generated>",
|
||||
) -> None:
|
||||
"""``.vue`` / ``.ts`` / ``.toml`` 产物的粗粒度结构护栏。
|
||||
|
||||
@@ -327,9 +327,7 @@ def _add_job_with_trigger(
|
||||
raise ValueError("任务代码块不能为空")
|
||||
# 可调度性判定(单一事实来源):builtin: 引用走白名单、不经 exec,因此**开关关闭时也允许注册**;
|
||||
# 只有"原始代码块"才会被 SCHEDULER_ALLOW_CODE_EXEC=False 拦住,并在提示里给出 builtin 替代写法。
|
||||
schedulable, reason = SchedulerUtil.describe_schedulability(code_block)
|
||||
if not schedulable:
|
||||
raise CustomException(msg=reason)
|
||||
SchedulerUtil.require_schedulable(code_block)
|
||||
|
||||
jobstore = job_info.jobstore or "default"
|
||||
executor = job_info.executor or "threadpool"
|
||||
@@ -416,9 +414,7 @@ def run_node_once(node: NodeModel) -> str:
|
||||
if not node.func or not node.func.strip():
|
||||
raise ValueError("任务代码块不能为空")
|
||||
# 手动执行同样按可调度性判定:builtin 引用可直接执行;原始代码块在开关关闭时给出替代写法
|
||||
schedulable, reason = SchedulerUtil.describe_schedulability(node.func)
|
||||
if not schedulable:
|
||||
raise CustomException(msg=reason)
|
||||
SchedulerUtil.require_schedulable(node.func)
|
||||
temp_job_id = f"{node.id}{_MANUAL_JOB_PREFIX}{datetime.now():%Y%m%d%H%M%S}"
|
||||
trigger = DateTrigger(run_date=datetime.now() + timedelta(seconds=0.1), timezone="Asia/Shanghai")
|
||||
_add_job_with_trigger(node, trigger, job_id_override=temp_job_id, name_suffix=" - 手动执行")
|
||||
|
||||
Reference in New Issue
Block a user