用 deptry 检查 Python 依赖腐化并接入 CI 门禁

举报
yd_246123679 发表于 2026/11/07 11:32:58 2026/11/07
【摘要】 依赖表与代码导入长期不同步会埋下隐患。本文用 deptry 静态分析 Python 项目,配置包名映射与忽略规则,定位未声明依赖、未使用依赖和误用传递依赖,并给出可运行示例与 CI 门禁脚本。

问题场景:依赖表会慢慢腐化

Python 项目里的 pyproject.toml 或 requirements.txt 是一份人工维护的清单,而代码里的 import 是另一份事实清单。两份清单长期不同步,就会出现三类典型问题:

  • 代码导入了,但依赖没声明:本地因为装了别的包顺带带上,或者开发机上装过,测试能过,换到干净环境就 ModuleNotFoundError。
  • 依赖声明了,但代码没用:重构删了代码没删依赖,安装变慢、镜像变大,还多背一份升级与漏洞治理成本。
  • 直接使用传递依赖:自己没声明,却 import 了上游包带进来的模块。上游哪天换实现或去掉该依赖,构建就断。

人工 review 很难稳定发现这些问题。deptry 通过对源码做 AST 静态分析,把「导入的模块」与「声明的依赖」对齐,用固定规则输出问题清单。

deptry 的规则含义

常见规则编号(以所用版本文档为准):

规则 含义
DEP001 代码导入了但依赖未声明
DEP002 依赖声明了但代码未使用
DEP003 导入了传递依赖(非直接声明)
DEP004 在非开发代码中导入了开发依赖

最小可运行示例

目录结构:

demo-svc/
├── pyproject.toml
└── src/demo/
    ├── main.py
    └── loader.py

pyproject.toml:

[project]
name = "demo-svc"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = [
  "requests>=2.31",
  "python-dateutil>=2.8",
  "pyyaml>=6.0",
]

[project.optional-dependencies]
dev = ["pytest>=8.0", "deptry>=0.20"]

[tool.deptry]
exclude = ["build", "tests"]
per_rule_ignores = { DEP002 = ["pyyaml"] }
package_module_name_map = { "python-dateutil" = "dateutil" }

src/demo/main.py(dateutil 是包名与模块名不一致的例子):

import requests
from dateutil import parser


def parse_time(raw: str) -> str:
    return parser.isoparse(raw).isoformat()


def fetch(url: str) -> dict:
    resp = requests.get(url, timeout=5)  # 占位地址
    resp.raise_for_status()
    return resp.json()

src/demo/loader.py(故意制造两个问题):

import urllib3  # 传递依赖,未被直接声明


def version() -> str:
    return urllib3.__version__

这里的 urllib3 只通过 requests 间接进入环境,属于 DEP003;pyyaml 已声明但没有任何模块导入它,会报 DEP002,示例中用 per_rule_ignores 演示如何按需豁免(真实项目里更推荐直接删依赖)。

运行方法

python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"

deptry .

要点:先安装依赖再运行。deptry 需要已安装包的元数据来完成「发行包名 → 模块名」的映射,纯空环境容易产生 DEP001 误报。输出非零退出码即代表发现问题,可直接作为门禁条件。需要更细的定位时加 --verbose,需要机器可读结果时用 --json-output(具体参数名以 deptry --help 为准)。

接入 CI 门禁

以常见的流水线为例,核心只有「装依赖 + 跑 deptry」两步:

name: deps-check
on: [push, pull_request]

jobs:
  deptry:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
      - name: Install
        run: pip install -e ".[dev]"
      - name: Check dependency hygiene
        run: deptry .

迁移到其他 CI 平台时,把最后一行放进构建脚本即可。建议先从「只检查、只告警」开始,把存量问题用忽略清单收敛,再逐步改为阻断,避免一次开闸就让所有分支红灯。忽略清单要写进版本库并附带原因注释,否则豁免本身也会腐化。

边界与限制

  • 只做静态分析。importlib.import_module、__import__、字符串拼出来的导入不会被识别,这类插件式加载需要在配置里按包名忽略。
  • 可选依赖与 extras。只在特定环境启用的依赖,常会被判成 DEP002;不同版本对可选依赖组的支持方式不同,必要时用 per_rule_ignores 精确豁免。
  • 命名空间包与 C 扩展包。元数据缺失或模块布局特殊时可能误报,package_module_name_map 是最直接的兜底手段。
  • 动态声明的依赖。通过变量或文件动态生成的依赖列表,deptry 无法推断,需要显式指定依赖文件。
  • 多包仓库。monorepo 中应针对每个子包分别运行并设置 exclude,不要对整个仓库一次性扫描。
  • 能力边界。deptry 不检查版本范围是否合理,也不检查已知漏洞,那属于其他工具的职责。
  • 开箱即红的存量项目。首次运行大概率会列出大量问题,建议先统计、分类,再决定删除依赖还是补充声明。

小结

依赖腐化是渐进式的,单次代码评审很难拦住。把 deptry 放进流水线,配合包名映射和少量有理由的忽略规则,可以把「导入与声明一致」变成每次提交都要通过的硬约束,让依赖清单重新成为可信的构建输入。

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。