Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 9579004

Browse filesBrowse files
committed
Let stub-defined __all__ override imports
This enables the use case where objects should be available for lazy-loading while not advertising them. This might be useful for deprecations.
1 parent 4e78314 commit 9579004
Copy full SHA for 9579004

File tree

Expand file treeCollapse file tree

2 files changed

+73
-1
lines changed
Filter options
Expand file treeCollapse file tree

2 files changed

+73
-1
lines changed

‎lazy_loader/__init__.py

Copy file name to clipboardExpand all lines: lazy_loader/__init__.py
+44-1Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@ class _StubVisitor(ast.NodeVisitor):
282282
def __init__(self):
283283
self._submodules = set()
284284
self._submod_attrs = {}
285+
self._all = None
285286

286287
def visit_ImportFrom(self, node: ast.ImportFrom):
287288
if node.level != 1:
@@ -300,6 +301,38 @@ def visit_ImportFrom(self, node: ast.ImportFrom):
300301
else:
301302
self._submodules.update(alias.name for alias in node.names)
302303

304+
def visit_Assign(self, node: ast.Assign):
305+
assigned_list = None
306+
for name in node.targets:
307+
if name.id == "__all__":
308+
assigned_list = node.value
309+
310+
if assigned_list is None:
311+
return # early
312+
elif not isinstance(assigned_list, ast.List):
313+
msg = (
314+
f"expected a list assigned to `__all__`, found {type(assigned_list)!r}"
315+
)
316+
raise ValueError(msg)
317+
318+
if self._all is not None:
319+
msg = "expected only one definition of `__all__` in stub"
320+
raise ValueError(msg)
321+
self._all = set()
322+
323+
for constant in assigned_list.elts:
324+
if (
325+
not isinstance(constant, ast.Constant)
326+
or not isinstance(constant.value, str)
327+
or assigned_list == ""
328+
):
329+
msg = (
330+
"expected `__all__` to contain only non-empty strings, "
331+
f"got {constant!r}"
332+
)
333+
raise ValueError(msg)
334+
self._all.add(constant.value)
335+
303336

304337
def attach_stub(package_name: str, filename: str):
305338
"""Attach lazily loaded submodules, functions from a type stub.
@@ -308,6 +341,10 @@ def attach_stub(package_name: str, filename: str):
308341
infer ``submodules`` and ``submod_attrs``. This allows static type checkers
309342
to find imports, while still providing lazy loading at runtime.
310343
344+
If the stub file defines `__all__`, it must contain a simple list of
345+
non-empty strings. In this case, the content of `__dir__()` may be
346+
intentionally different from `__all__`.
347+
311348
Parameters
312349
----------
313350
package_name : str
@@ -339,4 +376,10 @@ def attach_stub(package_name: str, filename: str):
339376

340377
visitor = _StubVisitor()
341378
visitor.visit(stub_node)
342-
return attach(package_name, visitor._submodules, visitor._submod_attrs)
379+
380+
__getattr__, __dir__, __all__ = attach(
381+
package_name, visitor._submodules, visitor._submod_attrs
382+
)
383+
if visitor._all is not None:
384+
__all__ = visitor._all
385+
return __getattr__, __dir__, __all__

‎lazy_loader/tests/test_lazy_loader.py

Copy file name to clipboardExpand all lines: lazy_loader/tests/test_lazy_loader.py
+29Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,35 @@ def test_stub_loading_parity():
143143
assert stub_getter("some_func") == fake_pkg.some_func
144144

145145

146+
FAKE_STUB_OVERRIDE_ALL = """
147+
__all__ = [
148+
"rank",
149+
"gaussian",
150+
"sobel",
151+
"scharr",
152+
"roberts",
153+
# `prewitt` not included!
154+
"__version__", # included but not imported in stub
155+
]
156+
157+
from . import rank
158+
from ._gaussian import gaussian
159+
from .edges import sobel, scharr, prewitt, roberts
160+
"""
161+
162+
163+
def test_stub_override_all(tmp_path):
164+
stub = tmp_path / "stub.pyi"
165+
stub.write_text(FAKE_STUB_OVERRIDE_ALL)
166+
_get, _dir, _all = lazy.attach_stub("my_module", str(stub))
167+
168+
expect_dir = {"gaussian", "sobel", "scharr", "prewitt", "roberts", "rank"}
169+
assert set(_dir()) == expect_dir
170+
171+
expect_all = {"rank", "gaussian", "sobel", "scharr", "roberts", "__version__"}
172+
assert set(_all) == expect_all
173+
174+
146175
def test_stub_loading_errors(tmp_path):
147176
stub = tmp_path / "stub.pyi"
148177
stub.write_text("from ..mod import func\n")

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.