@@ -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,39 @@ 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+
336+
303337
304338def attach_stub (package_name : str , filename : str ):
305339 """Attach lazily loaded submodules, functions from a type stub.
@@ -308,6 +342,10 @@ def attach_stub(package_name: str, filename: str):
308342 infer ``submodules`` and ``submod_attrs``. This allows static type checkers
309343 to find imports, while still providing lazy loading at runtime.
310344
345+ If the stub file defines `__all__`, it must contain a simple list of
346+ non-empty strings. In this case, the content of `__dir__()` may be
347+ intentionally different from `__all__`.
348+
311349 Parameters
312350 ----------
313351 package_name : str
@@ -339,4 +377,10 @@ def attach_stub(package_name: str, filename: str):
339377
340378 visitor = _StubVisitor ()
341379 visitor .visit (stub_node )
342- return attach (package_name , visitor ._submodules , visitor ._submod_attrs )
380+
381+ __getattr__ , __dir__ , __all__ = attach (
382+ package_name , visitor ._submodules , visitor ._submod_attrs
383+ )
384+ if visitor ._all is not None :
385+ __all__ = visitor ._all
386+ return __getattr__ , __dir__ , __all__
0 commit comments