Skip to content

Commit e9cf4f2

Browse files
bluetechilevkivskyi
authored andcommitted
Don't reexport star-imported symbols when implicit reexports are disabled (#7361)
Based on #7099. Currently, in regular files, symbols imported into module A with `from B import *` are considered explicitly exported from module A as well, i.e. they are considered part of module's A public API. This behavior makes sense in stub files as a shorthand (and is specified in PEP 484), but for regular files I think it is better to be explicit: add the symbols to `__all__`. Fixes #7042 Further discussion: #7042 (comment)
1 parent 9bc4317 commit e9cf4f2

File tree

4 files changed

+60
-7
lines changed

4 files changed

+60
-7
lines changed

docs/source/command_line.rst

+5-2
Original file line numberDiff line numberDiff line change
@@ -429,15 +429,18 @@ of the above sections.
429429
``--no-implicit-reexport``
430430
By default, imported values to a module are treated as exported and mypy allows
431431
other modules to import them. This flag changes the behavior to not re-export unless
432-
the item is imported using from-as. Note this is always treated as enabled for
433-
stub files. For example:
432+
the item is imported using from-as or is included in ``__all__``. Note this is
433+
always treated as enabled for stub files. For example:
434434

435435
.. code-block:: python
436436
437437
# This won't re-export the value
438438
from foo import bar
439439
# This will re-export it as bar and allow other modules to import it
440440
from foo import bar as bar
441+
# This will also re-export bar
442+
from foo import bar
443+
__all__ = ['bar']
441444
442445
443446
``--strict-equality``

docs/source/config_file.rst

+5-2
Original file line numberDiff line numberDiff line change
@@ -310,15 +310,18 @@ Miscellaneous strictness flags
310310
``implicit_reexport`` (bool, default True)
311311
By default, imported values to a module are treated as exported and mypy allows
312312
other modules to import them. When false, mypy will not re-export unless
313-
the item is imported using from-as. Note that mypy treats stub files as if this
314-
is always disabled. For example:
313+
the item is imported using from-as or is included in ``__all__``. Note that mypy
314+
treats stub files as if this is always disabled. For example:
315315

316316
.. code-block:: python
317317
318318
# This won't re-export the value
319319
from foo import bar
320320
# This will re-export it as bar and allow other modules to import it
321321
from foo import bar as bar
322+
# This will also re-export bar
323+
from foo import bar
324+
__all__ = ['bar']
322325
323326
``strict_equality`` (bool, default False)
324327
Prohibit equality checks, identity checks, and container checks between

mypy/semanal.py

+14-3
Original file line numberDiff line numberDiff line change
@@ -463,10 +463,16 @@ def add_builtin_aliases(self, tree: MypyFile) -> None:
463463
del tree.names[name]
464464

465465
def adjust_public_exports(self) -> None:
466-
"""Make variables not in __all__ not be public"""
466+
"""Adjust the module visibility of globals due to __all__."""
467467
if '__all__' in self.globals:
468468
for name, g in self.globals.items():
469-
if name not in self.all_exports:
469+
# Being included in __all__ explicitly exports and makes public.
470+
if name in self.all_exports:
471+
g.module_public = True
472+
g.module_hidden = False
473+
# But when __all__ is defined, and a symbol is not included in it,
474+
# it cannot be public.
475+
else:
470476
g.module_public = False
471477

472478
@contextmanager
@@ -1863,7 +1869,12 @@ def visit_import_all(self, i: ImportAll) -> None:
18631869
if self.process_import_over_existing_name(
18641870
name, existing_symbol, node, i):
18651871
continue
1866-
self.add_imported_symbol(name, node, i)
1872+
# In stub files, `from x import *` always reexports the symbols.
1873+
# In regular files, only if implicit reexports are enabled.
1874+
module_public = self.is_stub_file or self.options.implicit_reexport
1875+
self.add_imported_symbol(name, node, i,
1876+
module_public=module_public,
1877+
module_hidden=not module_public)
18671878
else:
18681879
# Don't add any dummy symbols for 'from x import *' if 'x' is unknown.
18691880
pass

test-data/unit/check-flags.test

+36
Original file line numberDiff line numberDiff line change
@@ -1145,6 +1145,42 @@ from other_module_1 import a
11451145
[out]
11461146
main:2: error: Module 'other_module_2' has no attribute 'a'
11471147

1148+
[case testNoImplicitReexportRespectsAll]
1149+
# flags: --no-implicit-reexport
1150+
from other_module_2 import a
1151+
from other_module_2 import b
1152+
[file other_module_1.py]
1153+
a = 5
1154+
b = 6
1155+
[file other_module_2.py]
1156+
from other_module_1 import a, b
1157+
__all__ = ('b',)
1158+
[out]
1159+
main:2: error: Module 'other_module_2' has no attribute 'a'
1160+
1161+
[case testNoImplicitReexportStarConsideredImplicit]
1162+
# flags: --no-implicit-reexport
1163+
from other_module_2 import a
1164+
[file other_module_1.py]
1165+
a = 5
1166+
[file other_module_2.py]
1167+
from other_module_1 import *
1168+
[out]
1169+
main:2: error: Module 'other_module_2' has no attribute 'a'
1170+
1171+
[case testNoImplicitReexportStarCanBeReexportedWithAll]
1172+
# flags: --no-implicit-reexport
1173+
from other_module_2 import a
1174+
from other_module_2 import b
1175+
[file other_module_1.py]
1176+
a = 5
1177+
b = 6
1178+
[file other_module_2.py]
1179+
from other_module_1 import *
1180+
__all__ = ('b',)
1181+
[out]
1182+
main:2: error: Module 'other_module_2' has no attribute 'a'
1183+
11481184
[case testNoImplicitReexportMypyIni]
11491185
# flags: --config-file tmp/mypy.ini
11501186
from other_module_2 import a

0 commit comments

Comments
 (0)