The typing spec details the module resolution order that type checkers are supposed to follow when resolving a module name to a concrete source file containing Python code. The resolution order is mostly derived from PEP 561[1]:
- Stubs or Python source manually put in the beginning of the path. Type checkers SHOULD provide this to allow the user complete control of which stubs to use, and to patch broken stubs or inline types from packages. In mypy the
$MYPYPATH
environment variable can be used for this. - User code - the files the type checker is running on.
- Stub packages - these packages SHOULD supersede any installed inline package. They can be found in directories named
foopkg-stubs
for packagefoopkg
. - Packages with a
py.typed
marker file - if there is nothing overriding the installed package, and it opts into type checking, the types bundled with the package SHOULD be used (be they in.pyi
type stub files or inline in.py
files). - Typeshed - only for modules in the standard library.
In my opinion, the typing spec is incorrect here in one important respect, and should be amended.
Precedence of the stdlib over site-packages
If at runtime, I have the (long defunct) asyncio
backport installed from PyPI, import asyncio
will not resolve to the module I have pip
-installed. Instead, it will resolve to the stdlib asyncio
module: at runtime, modules in the standard library nearly always take precedence over the same module name in site-packages
.[2]
I experimented locally by installing the asyncio
backport package and then manually adding a py.typed
file to the site-packages/asyncio
directory in my virtual environment. After doing so, it appeared that mypy would still correctly resolve import asyncio
to [the typeshed stub for] the stdlib module, accurately understanding the semantics at runtime. Pyright, however, did not: it resolved import asyncio
to the py.typed
package in site-packages
. But although mypy “gets it right” and pyright “gets it wrong”, pyright is the one that is accurately following the spec here according to the module resolution order given above.
I believe the spec here is incorrect: to accurately resolve modules in a way that reflect’s Python’s runtime semantics, typeshed’s stubs for the standard library must take higher priority than modules installed into site-packages
.
Where typeshed must come last
The original module resolution order in PEP 561 mentioned that vendored typeshed stubs for third-party modules should be placed last in the module resolution order, alongside vendored typeshed stubs for the standard library. In this respect, I think PEP 561 got things right.
At the time when PEP 561 was written and accepted (in 2017-2018), mypy (at the time, by far the most significant type checker) vendored all of typeshed’s third-party stubs. It stopped doing so in 2021, but pyright and pyre still vendor either some or all of typeshed’s third-party stubs.
Although not all type checkers vendor typeshed’s third-party stubs nowadays, if a type checker does vendor any of these stubs, I think it’s important that these vendored third-party stubs should continue to come last. If a user installs a non-typeshed stubs package (say, docutils-stubs
), it would be highly confusing and frustrating for the user if the type checker nonetheless continued to resolve import docutils
to the type checker’s stubs for docutils
in its vendored copy of typeshed.
Another example of where typeshed coming last comes in useful here would be if a user needs to continue using an older version of docutils
at runtime due to some incompatibility at runtime, but their type checker only contained typeshed’s vendored stubs for the latest version. Vendored third-party stubs from typeshed taking lower priority to site-packages
would allow them to manually install an older version of the stubs package, which would then override the vendored stubs from typeshed.
Unlike the original version in PEP 561, the current version of the module resolution order given in the typing specification does not specify where vendored typeshed stubs for third-party packages should come.
Proposed update to the module resolution order in the typing spec
Pursuant to the above arguments, I propose that we make three changes to the module resolution order:
- Move typeshed’s standard-library stubs higher in the resolution order, above any items that originate from
site-packages
. - With the change from point (1), it becomes more important that type checkers provide a clear and easy way for users to override the vendored copy of typeshed’s standard-library stubs with a custom directory of standard-library stubs if they want to. Most major type checkers already implement this (mypy provides the
--custom-typeshed-dir
option; pyright provides thetypeshedPath
configuration-file option; pyre provides the--typeshed
option). The proposed updated wording formally specifies that doing so is highly encouraged. - Bring back an explicit mention in the typing spec of where vendored typeshed stubs for third-party packages (if there are any that the type checker has chosen to vendor) should come in the module resolution order (last!).
The revised module resolution order that I propose is as follows:
- Stubs or Python source manually put in the beginning of the path. Type checkers SHOULD provide this to allow the user complete control of which stubs to use, and to patch broken stubs or inline types from packages. In mypy the
$MYPYPATH
environment variable can be used for this. - User code - the files the type checker is running on.
- Typeshed stubs for the standard library. These will usually be vendored by type checkers, but type checkers SHOULD provide an option for users to provide a path to a directory containing a custom or modified version of typeshed; if this option is provided, type checkers SHOULD use this as the canonical source for standard-library types in this step.
- Stub packages installed into
site-packages
- these packages SHOULD supersede any installed inline package. They can be found in directories namedfoopkg-stubs
for packagefoopkg
. - Packages in
site-packages
with apy.typed
marker file - if there is nothing overriding the installed package, and it opts into type checking, the types bundled with the package SHOULD be used (be they in.pyi
type
stub files or inline in.py
files). - If the type checker chooses to additionally vendor any third-party stubs from typeshed, these SHOULD come last in the module resolution order.
It was slightly amended in [spec] Update typeshed language to conform to reality by srittau · Pull Request #1571 · python/typing · GitHub ↩︎
yes, I know it is possible to install a package in
site-packages
that prioritizes itself over the stdlib, by clever use ofpth
files. But this is hardly common practice nowadays, and not something that I think type checkers should worry themselves with. ↩︎