Fixing import-untyped and Missing Imports

TL;DR — mypy [import-untyped] means the module was found but ships no type information — install a types-* stub package or scope an ignore to that module. mypy [import-not-found] means the module could not be resolved at all — fix the virtualenv, dependency, or path. They look similar in CI logs but demand opposite fixes, and treating one as the other either hides a real bug or wastes an afternoon.

The two errors sit next to each other in mypy output and both mention an import, so it is tempting to reach for ignore_missing_imports and move on. Resist that: one is a typing gap you can safely paper over, the other is a broken environment that ignore_missing_imports will silently mask, leaving you shipping Any-typed code that no checker guards.

Diagnosing import-untyped versus import-not-found Ask whether the module imports at runtime; if yes it is untyped and needs stubs, if no it is unresolved and needs an environment fix. mypy flags an import does it import at runtime? YES → [import-untyped] module exists, no types NO → [import-not-found] cannot resolve module install types-* or scope ignore fix venv / deps / MYPYPATH
The runtime import test splits the two errors cleanly: a module that imports needs types; one that does not needs an environment fix.

Confirming which error you have

Run the import in the same interpreter mypy uses. If python -c "import legacypay" succeeds, the module resolves and any mypy complaint is [import-untyped]. If that command raises ModuleNotFoundError, you have an [import-not-found] situation and no amount of stubbing will help until the import itself works.

$ python -c "import legacypay" && echo OK
OK                              # → so mypy's complaint is [import-untyped]

$ python -c "import notinstalled"
ModuleNotFoundError: No module named 'notinstalled'   # → [import-not-found]

Fixing [import-untyped]

The module runs; it just has no types. Prefer a maintained stub package when one exists:

$ mypy app.py
app.py:3: error: Library stubs not installed for "requests"  [import-untyped]
app.py:3: note: Hint: "python3 -m pip install types-requests"

$ pip install types-requests

mypy can even install the suggested stubs for you in CI — non-interactively, so it never blocks a pipeline:

$ mypy --install-types --non-interactive app.py

When no types-* package exists (an internal library, say), scope the suppression to just that module with a per-module override — never globally, which would blind mypy to every third-party gap:

# pyproject.toml — mypy 1.10+
[[tool.mypy.overrides]]
module = ["legacypay.*", "internal_billing.*"]
ignore_missing_imports = true

That tells mypy “treat these specific modules as Any on purpose.” If you would rather generate real types than accept Any, produce stubs instead — the mypy configuration and strictness guide covers where these overrides fit into a broader config.

Fixing [import-not-found]

Here the module genuinely cannot be found, and the cause is environmental:

  • Dependency not installed in the environment mypy runs against — pip install it, and make sure CI installs the project’s real dependencies before checking.
  • mypy running against the wrong interpreter — a global mypy checking a project whose deps live in a virtualenv. Run python -m mypy from inside the venv so it shares sys.path.
  • First-party package not on the path — your own src/ layout isn’t discoverable. Set mypy_path or add the package to the environment with pip install -e ..
# Python 3.12+, checked with mypy 1.10
from internal_billing.ledger import Ledger  # error: Cannot find
# implementation or library stub for module "internal_billing.ledger"
# [import-not-found]  → the package is not installed in this env

The wrong fix here is ignore_missing_imports = true: it silences the message but leaves Ledger typed as Any, so every downstream [attr-defined] and [arg-type] check on it evaporates.

Runtime vs static analysis A module that raises ModuleNotFoundError at runtime and one that imports fine but lacks stubs are completely different failures — yet suppressing mypy with ignore_missing_imports makes both go quiet. The static tool then reports success while the program may still crash on import. Always reproduce the import in the interpreter before deciding a mypy import error is "just a stubs problem."

The pyright equivalents

pyright splits the same distinction across two diagnostics:

  • reportMissingModuleSource — pyright found a stub (or types) but not the runtime module, or the module resolves without type info. This is pyright’s rough analogue of [import-untyped]; it is a warning by default, not an error.
  • reportMissingImports — pyright cannot resolve the import at all, the counterpart of mypy’s [import-not-found].
{
  "reportMissingModuleSource": "none",
  "extraPaths": ["src"]
}

Set reportMissingModuleSource to "none" to accept a known-untyped module, and use extraPaths (pyright’s mypy_path equivalent) to make a first-party src/ layout resolvable so reportMissingImports clears. The full mypy-vs-pyright picture is in pyright vs mypy comparison.

Common mistakes

  • Global ignore_missing_imports = true. It silences [import-untyped] and [import-not-found] everywhere, so a genuinely broken import never surfaces. Scope every ignore to named modules with [[tool.mypy.overrides]].
  • Installing a types-* package for a module that still fails [import-not-found]. Stubs cannot resolve a module that does not import; you fixed the wrong error. Get the runtime import working first.
  • Checking with a different interpreter than the app uses. A global mypy hits [import-not-found] on deps installed only in the project venv. Run python -m mypy inside that venv.
  • Marking a first-party package untyped instead of adding a py.typed marker. For code you own, ship the marker described in shipping inline types with py.typed rather than suppressing it as an untyped third party.

FAQ

Is --install-types --non-interactive safe for CI? Yes. It makes mypy fetch the types-* stubs it recommends without prompting, which unblocks pipelines. Pin the resulting stub versions in your lockfile so CI stays reproducible across runs.

Why does ignore_missing_imports feel like it “fixes everything”? Because it converts the offending module to Any, which makes both [import-untyped] and [import-not-found] disappear. That is exactly why it is dangerous globally — it can hide a real unresolved import as easily as a missing stub. Always scope it to specific modules.

Back to Type Stubs and Third-Party Packages