from __future__ import annotations """Utilities to find the site and prefix information of a Python executable. This file MUST remain compatible with all Python 3.8+ versions. Since we cannot make any assumptions about the Python being executed, this module should not use *any* dependencies outside of the standard library found in Python 3.8. This file is run each mypy run, so it should be kept as fast as possible. """ import sys if __name__ == "__main__": # HACK: We don't want to pick up mypy.types as the top-level types # module. This could happen if this file is run as a script. # This workaround fixes this for Python versions before 3.11. if sys.version_info < (3, 11): old_sys_path = sys.path sys.path = sys.path[1:] import types # noqa: F401 sys.path = old_sys_path import os import site import sysconfig def getsitepackages() -> list[str]: res = [] if hasattr(site, "getsitepackages"): res.extend(site.getsitepackages()) if hasattr(site, "getusersitepackages") and site.ENABLE_USER_SITE: res.insert(0, site.getusersitepackages()) else: res = [sysconfig.get_paths()["purelib"]] return res def getsyspath() -> list[str]: # Do not include things from the standard library # because those should come from typeshed. stdlib_zip = os.path.join( sys.base_exec_prefix, getattr(sys, "platlibdir", "lib"), f"python{sys.version_info.major}{sys.version_info.minor}.zip", ) stdlib = sysconfig.get_path("stdlib") stdlib_ext = os.path.join(stdlib, "lib-dynload") excludes = {stdlib_zip, stdlib, stdlib_ext} # Drop the first entry of sys.path # - If pyinfo.py is executed as a script (in a subprocess), this is the directory # containing pyinfo.py # - Otherwise, if mypy launched via console script, this is the directory of the script # - Otherwise, if mypy launched via python -m mypy, this is the current directory # In all these cases, it is desirable to drop the first entry # Note that mypy adds the cwd to SearchPaths.python_path, so we still find things on the # cwd consistently (the return value here sets SearchPaths.package_path) # Python 3.11 adds a "safe_path" flag wherein Python won't automatically prepend # anything to sys.path. In this case, the first entry of sys.path is no longer special. offset = 0 if sys.version_info >= (3, 11) and sys.flags.safe_path else 1 abs_sys_path = (os.path.abspath(p) for p in sys.path[offset:]) return [p for p in abs_sys_path if p not in excludes] def getsearchdirs() -> tuple[list[str], list[str]]: return (getsyspath(), getsitepackages()) if __name__ == "__main__": if sys.argv[-1] == "getsearchdirs": print(repr(getsearchdirs())) else: print("ERROR: incorrect argument to pyinfo.py.", file=sys.stderr) sys.exit(1)