You publish a library, mypkg, with every function annotated. A user
installs it and runs mypy --strict on this file:
from mypkg import greet
greet(123)
mypy does not report the bad argument. It reports this instead:
error: Skipping analyzing "mypkg": module is installed, but missing library stubs or py.typed marker [import-untyped]
mypy reads the annotations of an installed package only when the package
says it has them. The package says so with an empty file named py.typed.
Without that file, everything from mypkg is Any, a type that turns
checking off. If the user also passes --ignore-missing-imports, as this
site's grader does, the error goes away and mypy accepts greet(123).
Add the marker
Put py.typed next to the package's __init__.py:
pyproject.toml
src/
mypkg/
__init__.py
py.typed
One file covers the package and all its subpackages. It has to be in the
wheel you publish, not only in your source folder. Recent setuptools
releases include it automatically. To say so explicitly, add this to
pyproject.toml:
[tool.setuptools.package-data]
mypkg = ["py.typed"]
Other build backends have their own settings. Whichever you use, list the
files in the built wheel with python -m zipfile -l and look for
mypkg/py.typed.
Test the package a user installs
When you check your own source folder, mypy reads the files directly and never looks for the marker. So that check cannot catch a missing one. Test what a user gets instead. Build the wheel, install it and mypy into a fresh virtual environment, and check two small files from a folder outside your project.
The first file must pass:
from typing import assert_type
from mypkg import greet
assert_type(greet("Ada"), str)
assert_type makes mypy report an error unless the expression has exactly
the type you give. If the marker is missing, greet("Ada") is Any, so this
file fails even under --ignore-missing-imports.
The second file is the one from the top of this article, and it must fail
with an arg-type error. Check that the output has that error code. A
failing exit status is not enough, because a typo in the import also makes
mypy fail.
Skip editable installs (pip install -e) for this test. An editable install
runs your source folder, so it can hide a file that the wheel leaves out.
Check your understanding
Your source folder has src/mypkg/py.typed, but users still see import-untyped. What do you check first?
Show answer
Hide answer
Check the wheel. List its files with python -m zipfile -l and look for
mypkg/py.typed. Users install the wheel, not your source folder, so if the
build left the marker out, they never get it. Fix the build settings,
rebuild, and check the two small files again against the installed wheel.
Stubs can replace a library's types
A stub file is a .pyi file that holds only type information for a
module, with ... in place of function bodies. Some libraries ship no
types, and other people publish stubs for them as a separate package. For
example, types-requests installs a requests-stubs folder, and mypy uses
it when your code says import requests.
If a library has annotations of its own and a stub package for it is also
installed, mypy uses the stubs and ignores the annotations. If the stub
package is older than the library, a function added
since then is missing for mypy, even though it works at runtime. So when an
installed library's types look wrong, check which types- packages are
installed and whether they match the library's version.
The next exercise has you write a small stub for a package that has no types at all.
Going further
To silence missing types for one dependency, and keep the error for every
other import, set it per module in pyproject.toml:
[[tool.mypy.overrides]]
module = ["legacy_vendor_sdk.*"]
ignore_missing_imports = true
PEP 561 defines the marker. The typing specification's chapter on distributing types also covers partial stub packages, namespace packages, and the order in which a checker looks for type information.