Skip to content
← Back to explainers

Python explainer

Ship Your Library's Type Hints with py.typed

About 4 min to read. Allow extra time to work through examples.

Add a py.typed marker so mypy reads your library's annotations after someone installs it, and test the built wheel the way a user would.

This article uses Python. You can make it your practice mode to browse related content across the site.

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

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.

step 21 of 26

Interfaces with Protocols and ABCs

Up next Write a Minimal Stub for an Untyped Package