Skip to content

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.typed file inside the import package (next to its __init__.py).
  • Bundled stubs. Ship .pyi stub files alongside the modules, plus the same py.typed marker. Useful when annotations would clutter the implementation.
  • Stub-only packages. Publish types as a separate distribution named with a -stubs suffix, the pattern behind packages like pandas-stubs and the types-* 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

Last updated on