Search code examples
pythonpython-typingmypy

How to avoid stubtest's “[symbol] is not present at runtime” error


Example
Given the file ./mylib/minimalistic_repro.py

class Foo:
    def __init__(self, param):
        self.param = param

class Bar:
    def __init__(self, param):
        self.param = param

def foobar(par1, par2):
    return par1.param + par2.param

and its stub ./mylib/minimalistic_repro.pyi

from typing import TypeAlias

FooBar: TypeAlias = Foo | Bar

class Foo:
    param: int
    def __init__(self, param: int) -> None: ...

class Bar:
    param: int
    def __init__(self, param: int) -> None: ...

def foobar(par1: FooBar, par2: FooBar) -> int: ...

Note the type alias on the third line: FooBar: TypeAlias = Foo | Bar

mypy --strict mylib, flake8 mylib and ruff check --select E,F,B,SIM all pass.

When running stubtest however:
python -m mypy.stubtest mylib

I get the following error:
error: mylib.minimalistic_repro.FooBar is not present at runtime


My current workaround is to use an allowlist (stubtest --generate-allowlist).


Question(s)
● Is there a “better” way to avoid this “error”? / …
● … Am I doing something fundamentally wrong? …
● … and if not: Might this be worth a feature request?


Other approaches
● Of course I could declare
def foobar(par1: Foo | Bar, par2: Foo | Bar),
 but my actual task (writing type hints for a third party pypi package) requires a union of up to 18 types.

● I got the above example to run with stubtest by placing the FooBar type alias definition in a .py file (tp_aliases.py) and then reimporting. This approach failed to work in the case of with my actual pypi package type hinting task (tp_aliases.py is not part of the pypi package).


Solution

  • stubtest is complaining because it thinks your FooBar is a public API symbol, which might cause type checkers/IDE autocomplete to make incorrect assumptions and suggestions.

    The "correct" way to fix it is to make it private; that is, precede the name with an underscore:

    _FooBar: TypeAlias = Foo | Bar
    def foobar(par1: _FooBar, par2: _FooBar) -> int: ...
    

    For classes and functions, you can alternatively use typing.type_check_only:

    # type_check_only is not available at runtime and can only be used in stubs
    from typing import type_check_only
    
    @type_check_only
    def this_function_is_not_available_at_runtime() -> None: ...
    
    @type_check_only
    class AndSoDoesThisClass: ...
    

    There's also the command line option --ignore-missing-stub which will suppress all errors of this kind.