Skip to content
Advertisement

mypy does not detect errors when wrongly using a function imported from an external package

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.

User contributions licensed under: CC BY-SA
9 People found this is helpful
Advertisement