Skip to content

pyinfo.getsyspath() doesn't exclude stdlib from package_path when install path contains symlinks #21474

@Pedro-Muller29

Description

@Pedro-Muller29

Bug Report

When the Python install path contains a symlink (common with Homebrew, pyenv, and python-build-standalone), mypy/pyinfo.py:getsyspath() fails to exclude the stdlib from SearchPaths.package_path. As a result, stdlib modules that are unavailable at the configured --python-version are reported as [import-untyped] instead of [import-not-found]. A pre-existing # type: ignore[import-not-found] comment that works on platforms without symlinks (e.g. a typical Linux CI runner) stops working for users on macOS Homebrew, even with everything else identical.

To Reproduce

Minimal repro, run on a host whose Python's stdlib path is reached via a symlink (Homebrew always installs formulae as opt/<name>Cellar/<name>/<version>):

$ python --version
Python 3.13.7
$ echo "import tomllib" > /tmp/t.py
$ mypy --python-version=3.10 /tmp/t.py
/tmp/t.py:1: error: Skipping analyzing "tomllib": module is installed, but missing library stubs or py.typed marker  [import-untyped]

Same command in python:3.13-slim Linux container (no symlinks in the install path):

/tmp/t.py:1: error: Cannot find implementation or library stub for module named "tomllib"  [import-not-found]

Both behaviours are deterministic; reproduced with env -i HOME=/tmp/empty --no-incremental --cache-dir=/dev/null, and with pip freeze byte-identical between the two environments.

Expected Behavior

Same error code on both platforms. With --python-version=3.10, tomllib doesn't exist in the configured stdlib, so [import-not-found] is the right diagnosis regardless of whether the host's install path is symlinked.

Actual Behavior

Inspecting the module finder shows the divergence:

# macOS, Homebrew Python 3.13.7
package_path = (
    '/opt/homebrew/Cellar/python@3.13/3.13.7/.../lib/python313.zip',
    '/opt/homebrew/Cellar/python@3.13/3.13.7/.../lib/python3.13',          # stdlib leaked in
    '/opt/homebrew/Cellar/python@3.13/3.13.7/.../lib/python3.13/lib-dynload',
    '/tmp/mypy-py313/lib/python3.13/site-packages',
)
find_module('tomllib') -> ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS

# Linux, python:3.13-slim
package_path = ('/usr/local/lib/python3.13/site-packages', ...)
find_module('tomllib') -> ModuleNotFoundReason.NOT_FOUND

As far as I can tell, the divergence comes from mypy/pyinfo.py:getsyspath(). It builds excludes from sys.base_exec_prefix and sysconfig.get_path("stdlib"), then filters sys.path with os.path.abspath, which does not resolve symlinks. On Homebrew, excludes contains /opt/homebrew/opt/python@3.13/... (the symlink path) while the entries in sys.path come pre-resolved to /opt/homebrew/Cellar/python@3.13/3.13.7/..., so the set membership check never matches and the stdlib stays in package_path:

>>> stdlib_in_excludes = sysconfig.get_path("stdlib")
>>> stdlib_in_sys_path = next(p for p in sys.path if p.endswith("/lib/python3.13"))
>>> os.path.abspath(stdlib_in_excludes) == os.path.abspath(stdlib_in_sys_path)
False
>>> os.path.realpath(stdlib_in_excludes) == os.path.realpath(stdlib_in_sys_path)
True
>>> os.path.samefile(stdlib_in_excludes, stdlib_in_sys_path)
True

I haven't tested a patch, but my guess is that using os.path.realpath (or os.path.samefile for the comparison) in getsyspath() (both for the excludes construction and the sys.path normalisation) would address this.

Environment

  • Mypy version used: 1.20.2 (compiled: yes)
  • Mypy command-line flags: --python-version=3.10
  • Mypy configuration: none required (reproduces with empty env and no config file)
  • Python version used: 3.13.7 (Homebrew) and 3.13.13 (python-build-standalone via uv) on macOS aarch64 — both reproduce. python:3.13-slim Linux container does not reproduce.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugmypy got something wrong

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions