Skip to content

Commit 8294f42

Browse files
emmatypingilevkivskyi
authored andcommitted
Support from ... import ... with module level __getattr__ (python#3779)
PEP 484 specifies one can define ``__getattr__`` to specify that unresolved names should be ``Any`` (or the return type of the ``__getattr__`` method). This change adds support for access to these attributes via the ``from module import name`` pattern. The ``module.name`` pattern is already supported.
1 parent e85b17d commit 8294f42

File tree

2 files changed

+67
-1
lines changed

2 files changed

+67
-1
lines changed

mypy/semanal.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1377,7 +1377,23 @@ def visit_import_from(self, imp: ImportFrom) -> None:
13771377
self.add_submodules_to_parent_modules(possible_module_id, True)
13781378
elif possible_module_id in self.missing_modules:
13791379
missing = True
1380-
1380+
# If it is still not resolved, and the module is a stub
1381+
# check for a module level __getattr__
1382+
if module and not node and module.is_stub and '__getattr__' in module.names:
1383+
getattr_defn = module.names['__getattr__']
1384+
if isinstance(getattr_defn.node, FuncDef):
1385+
if isinstance(getattr_defn.node.type, CallableType):
1386+
typ = getattr_defn.node.type.ret_type
1387+
else:
1388+
typ = AnyType()
1389+
if as_id:
1390+
name = as_id
1391+
else:
1392+
name = id
1393+
ast_node = Var(name, type=typ)
1394+
symbol = SymbolTableNode(GDEF, ast_node, name)
1395+
self.add_symbol(name, symbol, imp)
1396+
return
13811397
if node and node.kind != UNBOUND_IMPORTED:
13821398
node = self.normalize_type_alias(node, imp)
13831399
if not node:

test-data/unit/check-modules.test

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1766,3 +1766,53 @@ main:3: error: Module has no attribute "any_attribute"
17661766
[case testModuleLevelGetattribute]
17671767

17681768
def __getattribute__(): ... # E: __getattribute__ is not valid at the module level
1769+
1770+
[case testModuleLevelGetattrImportFrom]
1771+
from has_attr import name
1772+
reveal_type(name) # E: Revealed type is 'Any'
1773+
1774+
[file has_attr.pyi]
1775+
from typing import Any
1776+
def __getattr__(name: str) -> Any: ...
1777+
1778+
[builtins fixtures/module.pyi]
1779+
1780+
[case testModuleLevelGetattrImportFromRetType]
1781+
from has_attr import int_attr
1782+
reveal_type(int_attr) # E: Revealed type is 'builtins.int'
1783+
1784+
[file has_attr.pyi]
1785+
def __getattr__(name: str) -> int: ...
1786+
1787+
[builtins fixtures/module.pyi]
1788+
1789+
[case testModuleLevelGetattrImportFromNotStub]
1790+
from non_stub import name
1791+
reveal_type(name)
1792+
1793+
[file non_stub.py]
1794+
from typing import Any
1795+
def __getattr__(name: str) -> Any: ...
1796+
1797+
[out]
1798+
tmp/non_stub.py:2: error: __getattr__ is not valid at the module level outside a stub file
1799+
main:1: error: Module 'non_stub' has no attribute 'name'
1800+
main:2: error: Revealed type is 'Any'
1801+
1802+
[builtins fixtures/module.pyi]
1803+
1804+
[case testModuleLevelGetattrImportFromAs]
1805+
from has_attr import name as n
1806+
reveal_type(name)
1807+
reveal_type(n)
1808+
1809+
[file has_attr.pyi]
1810+
from typing import Any
1811+
def __getattr__(name: str) -> Any: ...
1812+
1813+
[out]
1814+
main:2: error: Revealed type is 'Any'
1815+
main:2: error: Name 'name' is not defined
1816+
main:3: error: Revealed type is 'Any'
1817+
1818+
[builtins fixtures/module.pyi]

0 commit comments

Comments
 (0)