I’ve run a simple experiment where I’ve created a very simple python package containing the following files:
In the folder my_package
:
# example.py def foo(number: int, text: str) -> None: print(number) print(text)
An empty __init__.py
In the root folder, a setup.py
file:
from setuptools import setup setup( name='ExamplePackage', version='0.1.0', author='An Awesome Coder', author_email='aac@example.com', packages=['my_package'], description='An awesome package that does something', )
After building the package using python ./setup.py bdist_wheel
I’ve copied the .whl
file to another python project, ran pip install ExamplePackage-0.1.0-py3-none-any.whl
and created the following file main.py
:
from my_package.example import foo # No mypy errors at all! x = foo() def internal_foo(number: int, text: str) -> None: print(number) print(text) # Missing positional arguments "number", "text" in call to "internal_foo" [call-arg] # "internal_foo" does not return a value [func-returns-value] y = internal_foo()
My mypy.ini
looks like this:
[mypy] ignore_missing_imports = True show_error_codes = True strict = True check_untyped_defs = True raise_exceptions = True
I’m struggling to understand why mypy which could easily infer the signature of the imported foo
function, does not show any errors regarding the wrong usage
Any help will be much appreciated
Advertisement
Answer
According to PEP-561 (a python standard that regulates use of typing data for packages), there are two general ways to declare types for package.
The first (applicable in your case) is py.typed
marker. It is an empty file named py.typed
, placed in package root (the same place where the topmost __init__.py
resides; for namespace packages prefer adding py.typed
to every submodule to avoid inconsistent interpretation). It declares that the package contains inline type information.
It is important not to forget to add it as package data. You need to add it as MANIFEST.in
entry + any configuration necessary to enable it – nothing for pyproject.toml
-based packages, include_package_data = True
for setup.py
and setup.cfg
, something else (similar?) for non-setuptools distros (Poetry, flit, hatch or whatever you need for some reason). Alternatively, it can be declared in [tool.setuptools.package-data]
section of pyproject.toml
, package_data
in setup.py
, [options.package_data]
in setup.cfg
and something similar for other build systems. There are other options like VCS integration (from .gitignore
and similar files) with appropriate plugins, refer to setuptools documentation here or docs for system of your choice.
Another supported solution is using stub files – these are separate files with .pyi
extension, providing only typing for functions/methods and variables/attributes. They have a higher precedence (e.g. if there are both stubs and py.typed
, stubs types are used), but this should not happen (if code can be annotated inline, then there is no need in separate stubs). Stub packages are named <package>-stubs
. There are also “side-by-side” approach that allows putting .py
and .pyi
near each other.