From b5b068065fe5fbc0c8dc123fb0c976c6c69aced7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=BF=E4=BA=91=E7=A7=8B=E6=9C=88?= <15273589815@163.com> Date: Fri, 11 Sep 2026 20:15:17 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=8E=89=E5=90=88=E5=B9=B6=E5=B8=A6?= =?UTF-8?q?=E5=85=A5=E7=9A=84=203=20=E4=B8=AA=20mypy=20=E9=94=99=EF=BC=88?= =?UTF-8?q?=E5=85=A8=E5=9C=A8=E8=A2=81=E8=81=AA=E7=9A=84=E5=9C=BA=E5=A4=96?= =?UTF-8?q?=E9=82=AE=E4=BB=B6=E6=A8=A1=E5=9D=97=EF=BC=8C=E5=90=84=E4=B8=80?= =?UTF-8?q?=E8=A1=8C=E3=80=81=E4=B8=8D=E5=8A=A8=E9=80=BB=E8=BE=91=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. offsite_document_recognition_adapter.py:788 —— 冗余 cast。 `value in ("summary", ...)` 已经把类型收窄到那个字面量联合,cast 多余,删掉即可。 该文件另有 13 处 cast,删这一个不影响 import。 2. offsite_fund_service.py:1203 —— dict 不变型。 字面量里只有一个 bool,mypy 推断成 dict[str, bool | None],而 dict 是**不变型**, 不是 dict[str, object] 的子类型。运行时本来就是合法值,加一行显式标注即可。 3. offsite_fund_service.py:1240 —— 标注宽于实际。 `get_content()` 只定义在 EmailMessage 上,而调用方传的是 BytesParser(policy=policy.default).parsebytes(...) 的返回值(本来就是 EmailMessage)。 形参从基类 Message 改成 EmailMessage;import 同步替换(Message 仅此一处使用)。 三条都不影响运行:tests/unit/worker/test_offsite_mail_worker.py 的 6 个用例覆盖的正是 这条路径,修前修后都通过。修的意义在于——strict=true 下留着这 3 个错,mypy 在这个文件上 就失去价值,而 offsite_* 是刚合并进来、最需要类型检查兜底的新域。 验证:mypy 163 文件 0 错 / ruff 干净 / unit+contract 810 passed / integration 76 passed。 (另:本机 pytest 默认 basetemp 被权限占住的问题仍在,用重定向 TEMP 绕开,注意先建目录; NL_develop 的 conftest 修复合并后可根治。) --- app/service/offsite_document_recognition_adapter.py | 3 ++- app/service/offsite_fund_service.py | 12 +++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/app/service/offsite_document_recognition_adapter.py b/app/service/offsite_document_recognition_adapter.py index 0b4970f..4b3b9e2 100644 --- a/app/service/offsite_document_recognition_adapter.py +++ b/app/service/offsite_document_recognition_adapter.py @@ -785,7 +785,8 @@ def _confidence_map(value: Mapping[str, object]) -> dict[str, Decimal]: def _document_type(value: object) -> DocumentType: if value in ("summary", "subscription", "redemption", "other"): - return cast(DocumentType, value) + # 不需要 cast:上面的 `in (...)` 已经把类型收窄到那个字面量联合了。 + return value return "other" diff --git a/app/service/offsite_fund_service.py b/app/service/offsite_fund_service.py index 09daee3..4e37973 100644 --- a/app/service/offsite_fund_service.py +++ b/app/service/offsite_fund_service.py @@ -7,7 +7,7 @@ from datetime import UTC, date, datetime from decimal import Decimal from email import policy from email.header import decode_header, make_header -from email.message import Message +from email.message import EmailMessage from email.parser import BytesParser from email.utils import parsedate_to_datetime from pathlib import Path @@ -1190,7 +1190,10 @@ class OffsiteFundService: @staticmethod def _parse_eml(path: str) -> dict[str, object]: - empty = { + # 显式标注:字面量里只有一个 bool,mypy 会把它推断成 `dict[str, bool | None]`, + # 而 dict 是**不变型**,`dict[str, bool | None]` 不是 `dict[str, object]` 的子类型。 + # 运行时本来就是合法值,缺的只是这一个标注。 + empty: dict[str, object] = { "subject": None, "sent_at": None, "has_body": False, @@ -1227,8 +1230,11 @@ class OffsiteFundService: except (LookupError, UnicodeError, ValueError): return value + # 形参标注用 EmailMessage 而不是基类 Message:`get_content()` 只定义在 EmailMessage 上, + # 而调用方传的是 `BytesParser(policy=policy.default).parsebytes(...)` 的返回值 —— + # 它本来就是 EmailMessage。原先标注成基类,mypy 于是在下面报"没有该属性"。 @staticmethod - def _mail_body(message: Message) -> tuple[str | None, str | None]: + def _mail_body(message: EmailMessage) -> tuple[str | None, str | None]: text_body: str | None = None html_body: str | None = None parts = message.walk() if message.is_multipart() else (message,)