What is PEP 561?
PEP 561: Distributing and Packaging Type Information defines how an installed package tells type checkers that it ships type information. Its visible artifact is py.typed, an empty marker file whose presence means “the annotations in this package are intended to be used.”
Without the marker, a library’s carefully written annotations are invisible to mypy, no matter how complete they are.
How does a package declare its types?
PEP 561 gives a package author three options:
- Inline types. Annotate the source and add an empty
py.typedfile inside the import package (next to its__init__.py). - Bundled stubs. Ship
.pyistub files alongside the modules, plus the samepy.typedmarker. Useful when annotations would clutter the implementation. - Stub-only packages. Publish types as a separate distribution named with a
-stubssuffix, the pattern behind packages likepandas-stubsand thetypes-*packages on PyPI. No marker needed; the suffix is the signal.
A package with incomplete types can mark itself partial by putting the word partial in py.typed, telling checkers to fall back to stubs for whatever’s missing.
What happens without the marker?
mypy refuses to read inline annotations from an installed package that lacks the marker:
$ mypy check.py
check.py:1: error: Skipping analyzing "greeter": module is installed, but missing library stubs or py.typed marker [import-untyped]
Every value imported from that package becomes Any, so type errors involving it pass silently. The same project produced this with the marker present: Argument 1 to "greet" has incompatible type "int"; expected "str". One empty file is the difference between those two results.
Pyright and ty behave differently: both read inline annotations from library code even without the marker (in Pyright this is the useLibraryCodeForTypes setting, on by default). That asymmetry is a recurring source of confusion: the same missing marker produces clean runs in VS Code’s Pylance and [import-untyped] errors from mypy in CI.
Ship the marker in your wheel
For library authors, creating py.typed is one touch; the failure mode is the file not making it into the built wheel. Hatchling includes every file inside the package directory automatically, while setuptools needs the file declared in package-data. Check the wheel directly rather than trusting the config:
$ unzip -l dist/greeter-0.1.0-py3-none-any.whl | grep py.typed
0 06-09-2026 14:01 greeter/py.typed
If users report [import-untyped] errors against a package that has the marker in its repository, this is the first thing to check.
Learn More
- PEP 561: Distributing and Packaging Type Information
- Distributing type information in the typing specification
- mypy: Running mypy and managing imports
- What is PEP 484? covers the type-hint system these packages are distributing