-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
mypy doesn't recognize intra-package imports when qualified by __all__
#10826
Comments
This also happens when your "import" module starts with underline Suppose the following project structure:
And the following file contents: File "my_lib/func1/_concepts/some/some_concept.py": has a class and associated definitionsMany of such module files exist, grouped by a sub-package (in this case, from abc import ABCMeta
from typing import TypeVar
TMyConcept = TypeVar('TMyConcept', bound='MyConcept')
TMyConcept_cov = TypeVar('TMyConcept_cov', bound='MyConcept', covariant=True)
class MyConcept(metaclass=ABCMeta):
"""Class representing some concept.""" File "my_lib/func1/_concepts/some/__init__.py": exports all elements of internal module "_concepts/some"from .some_concept import TMyConcept, MyConcept, TMyConcept_cov
__all__ = [
'TMyConcept',
'MyConcept',
'TMyConcept_cov',
] File "my_lib/func1/_concepts/__init__.py": exports all public elements of internal module "_concepts" (uses star import)from .some import *
from .some import __all__ as _some_all
__all__ = [*_some_all] File "my_lib/func1/__init__.py": exports all public elements of external module "func1"; not supposed to be a namespace packageNote that from ._concepts import *
from ._concepts import __all__ as _concepts_all
__all__ = [*_concepts_all] File "my_lib/__init__.py": a namespace (and public) package# AFAICT, from the PEPs, I think this file can simply be deleted and it would work,
# but in my codebase that's how it is now, and I'm trying to replicate the bug, so...
__path__ = __import__('pkgutil').extend_path(__path__, __name__) Note that Now, looking at the My guess is that mypy understands But mypy could perhaps identify when # Class names are fictitious
# ---
# In module "my_mod._internal1
from typing import Final
from .class1_1 import Class1_1
from .class1_2 import Class1_2
# ...
# Here, __all__:
# 1. is definitely final,
# 2. is non-conditionally defined, and
# 3. has a clearly immutable value (a tuple)
__all__: Final = ("Class1_1", "Class1_2", ...)
#---
# In module "my_mod._internal2
from typing import Final
from .class1_1 import Class1_1
from .class1_2 import Class1_2
# ...
# Same here
__all__: Final = ("Class2_1", "Class2_2", ...)
#---
# in module "my_mod"
from typing import Final
import .internal1
import .internal2
# ...
# Because __all__ from other modules are final, and non-conditionally
# defined, I think it's safe to assume the contents of __all__ as
# immutable and statically inferrable as well.
__all__: Final = (*internal1.__all__, *internal2.__all__) Anyways, I'm not sure if that's already the case, please correct me if I'm wrong. |
The dunder all mechanism presents a challenge for static type checking in general. I've seen some "creative" (i.e. unnecessarily convoluted) dynamic manipulation of The proposed list is documented here: https://github.com/python/typing/blob/master/docs/source/libraries.rst#library-interface. Pyright implements all of these idioms (and does not implement any others) and emits a diagnostic if it detects the use of an unsupported manipulation of Note that __all__ = ["Class2_1", "Class2_2"]
__all__ += internal1.__all__
__all__ += internal2.__all__ I'm open to changing (augmenting, etc.) my proposed list if there's good justification for doing so and it helps us get consensus among Python tool authors and library maintainers. Standardization in this area will benefit everyone. I apologize if I went a bit off topic from the original bug report, but I thought this information might be relevant. |
@erictraut your input was invaluable, and I learned a lot from the proposed list you linked! Thank you very much! The idiom I proposed was written out of ignorance in regards to how In my case, specifically, could just put everything at once at variable definition time, like in the examples, without any need for In fact, looking into the proposal, for some of the codebases I work with, I realise that I can omit So, in a way, your comment unblocked me for some use cases, and I thank you for that. :) But it seems like pyright (or pylance + some other selected linter? I'm using VSCode) identifies public imports in |
Bug Report
I have a package and am trying to control which classes/functions are available at the 'top level' of the package. For this purpose, I am using the
__all__
dunder and star imports in the__init__.py
files. I get unexpected behaviour frommypy
when doing this: some of my objects are "not defined", when they in fact are.To Reproduce
This bug has to do with imports between files, so to reproduce this, we need a bit of a package file structure. Consider the package
gym
:File contents would be:
Now, when I run
mypy gym/
I get the following errors:However, when I remove the
__all__
dunder from thesup/__init__.py
file, everything works as expected, and I get no errors! Unfortunately this has the unwanted side-effect that the submodulegym.sup.bro
becomes available asgym.bro
, which I do not want.Somehow that
__all__
dunder stops mypy from recognizing that the Bro object should arrive at the module level above it. I can also work around the error by using the full path to the Bro object,gym.sup.bro.Bro
, or evengym.sup.Bro
, but notgym.Bro
. This unfortunately gets very verbose in larger packages.What's going on here?
Your Environment
mypy.ini
(and other config files): NoneThe text was updated successfully, but these errors were encountered: