You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在GitHub Actions中使用mypy时如何忽略ModuleNotFoundError?

解决Django项目Mypy类型检查的依赖缺失问题

我正在将mypy集成到GitHub Actions工作流中,用于仅检查Django项目中变更的文件,当前工作流配置如下:

name: Pull Request Backend Lint and Format

on: [push, pull_request]

jobs:
  backend-lint-and-check:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: backend
    steps:
    - uses: actions/checkout@v3
      with:
        fetch-depth: 0

    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.x'

    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install ruff mypy mypy-extensions django-stubs django-stubs-ext

    - name: Get changed files
      id: changed-files
      run: |
        if [ "${{ github.event_name }}" == "pull_request" ]; then
          BASE_SHA=${{ github.event.pull_request.base.sha }}
        else
          BASE_SHA=$(git rev-parse HEAD^)
        fi
        CHANGED_FILES=$(git diff --name-only --diff-filter=d $BASE_SHA...HEAD | grep '^backend/' | sed 's|^backend/||' | grep '\.py$' | tr '\n' ' ')
        echo "files<<EOF" >> $GITHUB_OUTPUT
        echo "$CHANGED_FILES" >> $GITHUB_OUTPUT
        echo "EOF" >> $GITHUB_OUTPUT

    - name: Run Ruff check
      if: steps.changed-files.outputs.files
      run: |
        FILES="${{ steps.changed-files.outputs.files }}"
        if [ ! -z "$FILES" ]; then
          echo "Running Ruff check on files:"
          echo "$FILES"
          for file in $FILES; do
            ruff check "$file" || echo "Ruff check found issues in $file"
          done
        else
          echo "No Python files to check"
        fi

    - name: Run Ruff format check
      if: steps.changed-files.outputs.files
      run: |
        FILES="${{ steps.changed-files.outputs.files }}"
        if [ ! -z "$FILES" ]; then
          echo "Running Ruff format check on files:"
          echo "$FILES"
          for file in $FILES; do
            ruff format --check -v "$file" || echo "Ruff format check found issues in $file"
          done
        else
          echo "No Python files to check"
        fi

    - name: Set up environment
      run: |
        if [ -f credentials/secrets.sh ]; then
          source credentials/secrets.sh
        fi
        if [ -f deployment/config-dev.sh ]; then
          source deployment/config-dev.sh
        fi

    - name: Run Mypy
      if: steps.changed-files.outputs.files
      run: |
        FILES="${{ steps.changed-files.outputs.files }}"
        if [ ! -z "$FILES" ]; then
          echo "Running Mypy on files:"
          echo "$FILES"
          for file in $FILES; do
            mypy --config-file setup.cfg --follow-imports=skip "$file" || echo "Mypy found issues in $file"
          done
        else
          echo "No Python files to check"
        fi

运行时遇到ModuleNotFoundError,报错信息如下:

Running Mypy on files:
gh-actions-test.py 
Traceback (most recent call last):
Error constructing plugin instance of NewSemanalDjangoPlugin
  File "/opt/hostedtoolcache/Python/3.12.5/x64/bin/mypy", line 8, in <module>
    sys.exit(console_entry())
             ^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/mypy/__main__.py", line 15, in console_entry
    main()
  File "mypy/main.py", line 103, in main
  File "mypy/main.py", line 187, in run_build
  File "mypy/build.py", line 193, in build
  File "mypy/build.py", line 238, in _build
  File "mypy/build.py", line 503, in load_plugins
  File "mypy/build.py", line 484, in load_plugins_from_config
  File "/opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/mypy_django_plugin/main.py", line 71, in __init__
    self.django_context = DjangoContext(self.plugin_config.django_settings_module)
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/mypy_django_plugin/django/context.py", line 127, in __init__
    apps, settings = initialize_django(self.django_settings_module)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/mypy_django_plugin/django/context.py", line 81, in initialize_django
    settings._setup()  # type: ignore[misc]
    ^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/django/conf/__init__.py", line 68, in _setup
    self._wrapped = Settings(settings_module)
                    ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/django/conf/__init__.py", line 166, in __init__
    mod = importlib.import_module(self.SETTINGS_MODULE)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/importlib/__init__.py", line 90, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1331, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 935, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 995, in exec_module
  File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
  File "/home/runner/work/myproject/myproject/backend/myproject/settings.py", line 13, in <module>
    import openai
ModuleNotFoundError: No module named 'openai'
Mypy found issues in gh-actions-test.py

安装全量依赖(pip install -r requirements.txt)可解决问题,但会大幅拖慢工作流。以下是几种无需安装全量依赖即可跳过这类错误、获得有效类型检查的方法:

方法1:安装缺失依赖的类型Stub包

Mypy无需实际依赖包,只要有对应的类型定义包(通常命名为types-<package名>)就能完成类型检查。在GitHub Actions的依赖安装步骤中,添加需要的类型Stub包即可:

pip install ruff mypy mypy-extensions django-stubs django-stubs-ext types-openai

这种方式比安装全量依赖快很多,因为Stub包体积小、安装耗时短。

方法2:修改Django配置文件,避免强制导入第三方依赖

在项目的settings.py中,对第三方依赖做条件导入处理,避免Mypy初始化Django上下文时触发导入错误:

# settings.py
try:
    import openai
    OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
except ImportError:
    openai = None
    OPENAI_API_KEY = ""

这样即使没有安装openai,Django配置文件也能正常加载,Mypy插件初始化不会失败。

方法3:使用Mock版Django配置文件

创建一个简化版的mypy_settings.py,仅包含Mypy类型检查必须的Django配置,移除所有第三方依赖相关的导入和配置:

# mypy_settings.py
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent

SECRET_KEY = "dummy-key-for-mypy"
DEBUG = True

INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    # 仅保留项目核心应用,移除依赖第三方包的应用
]

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.sqlite3",
        "NAME": BASE_DIR / "db.sqlite3",
    }
}

然后在Mypy运行命令中指定使用这个Mock配置:

mypy --config-file setup.cfg --follow-imports=skip --django-settings-module=mypy_settings "$file"

方法4:调整Mypy插件配置,跳过Django上下文初始化

如果不需要Django特定的类型检查支持,可以禁用django-stubs插件,仅用基础Mypy检查:
在setup.cfg中注释或删除Django插件配置:

# [mypy]
# plugins = mypy_django_plugin.main

这种方式会丢失Django相关的类型提示(比如模型、视图的类型检查),但能快速完成基础类型检查,适合对Django特定类型要求不高的场景。

内容的提问来源于stack exchange,提问作者Mustafa Marzouk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 23:45:55