I'm working on a Python 3.13.1
project using mypy 1.14.0
for static type checking.
I have a module named module.py
with a function
function that returns a type with a very long name,
Type_whose_name_is_so_long_that_we_do_not_want_to_call_it_over_and_over_again
.
To make the code more readable, I've defined a type alias T
in the corresponding stub file module.pyi
.
Here's a simplified version of my code module.pyi
:
T = Type_whose_name_is_so_long_that_we_do_not_want_to_call_it_over_and_over_again
def function()->T: pass
class Type_whose_name_is_so_long_that_we_do_not_want_to_call_it_over_and_over_again: pass
I want to prevent following illegal_usage_of_T.py
from using the T
type alias.
import module
foo:module.T = module.function()
Ideally, when I run mypy illegal_usage_of_T.py
, I'd like to get an error message indicating that the type T
is undefined.
I've google searched for "mypy type alias only used in stub file" but couldn't find a solution that prevents mypy from recognizing the type alias in other modules. I expected that defining T
only in the stub file would limit its scope, but it seems that mypy is able to find the type alias even in other modules.
I've tried several approaches, including:
T
to _T
to make it less likely to be found by other modules, but this didn't resolve the issue.if TYPE_CHECKING
: I tried conditionally defining the type alias within an if TYPE_CHECKING
block, but this also didn't prevent the type alias from being used in other modules.__all__ = ["function", "Type_whose_name_is_so_long_that_we_do_not_want_to_call_it_over_and_over_again"]
to the module.pyi
file to explicitly control what names are exported, but the type alias T
was still accessible.Here's the modified code for module.pyi
:
from typing import TYPE_CHECKING
if TYPE_CHECKING:
_T = Type_whose_name_is_so_long_that_we_do_not_want_to_call_it_over_and_over_again
def function()->_T: pass
class Type_whose_name_is_so_long_that_we_do_not_want_to_call_it_over_and_over_again: pass
__all__ = ["function", "Type_whose_name_is_so_long_that_we_do_not_want_to_call_it_over_and_over_again"]
And here's the illegal_usage_of_T.py
file:
import module
foo:module._T = module.function() # Still works
I expected that at least one of these approaches would prevent illegal_usage_of_T.py
from accessing the _T
type alias, but none of them worked.
Apart from writing your own mypy plugin, there isn't really a way to do this.
Prefixing items with an underscore is by far the overwhelmingly adopted convention to indicate names which aren't supposed to be exported (used outside of the module it is defined in); you can see this convention adopted in Python's own typeshed project. Some IDEs (like PyCharm or VSCode with pyright) in fact do show errors if you try to access underscore-prefixed items from a module, but this isn't part of mypy.
Apart from just using pyright instead of mypy, the closest thing that exists is Ruff's import-private-name
rule, but this doesn't activate unless you use the name in a runtime context (type annotations, like foo: module.T
, don't count, and won't trigger the linting).
As for the others:
if TYPE_CHECKING
- this has no effect in .pyi
stub files.__all__
- this only has effect for star imports (from module import *
) and names which would otherwise not be re-exported. Direct access to module attributes (like module.T
or from module import T
) is never prevented due to the lack of a name ("T"
) in __all__
, if T
was defined inside module
.