55Makes it easy to load subpackages and functions on demand.
66"""
77
8- import ast
8+ import _thread
99import importlib
10- import importlib .util
1110import os
1211import sys
13- import threading
1412import types
15- import warnings
1613
1714__version__ = "0.6rc0.dev0"
1815__all__ = ["attach" , "attach_stub" , "load" ]
1916
2017
21- threadlock = threading .Lock ()
18+ # Same lock type as threading.Lock(), without the threading import cost
19+ threadlock = _thread .allocate_lock ()
2220
2321
2422def attach (package_name , submodules = None , submod_attrs = None ):
@@ -72,23 +70,23 @@ def attach(package_name, submodules=None, submod_attrs=None):
7270
7371 def __getattr__ (name ):
7472 if name in submodules :
75- return importlib .import_module (f"{ package_name } .{ name } " )
73+ attr = importlib .import_module (f"{ package_name } .{ name } " )
7674 elif name in attr_to_modules :
7775 submod_path = f"{ package_name } .{ attr_to_modules [name ]} "
7876 submod = importlib .import_module (submod_path )
7977 attr = getattr (submod , name )
80-
81- # If the attribute lives in a file (module) with the same
82- # name as the attribute, ensure that the attribute and *not*
83- # the module is accessible on the package.
84- if name == attr_to_modules [name ]:
85- pkg = sys .modules [package_name ]
86- pkg .__dict__ [name ] = attr
87-
88- return attr
8978 else :
9079 raise AttributeError (f"No { package_name } attribute { name } " )
9180
81+ # Cache the resolved value on the package so that subsequent
82+ # accesses bypass __getattr__; this also ensures an attribute
83+ # shadows a same-named submodule.
84+ pkg = sys .modules .get (package_name )
85+ if pkg is not None :
86+ pkg .__dict__ [name ] = attr
87+
88+ return attr
89+
9290 def __dir__ ():
9391 return __all__ .copy ()
9492
@@ -191,7 +189,11 @@ def myfunc():
191189 if have_module and require is None :
192190 return module
193191
192+ import importlib .util
193+
194194 if not suppress_warning and "." in fullname :
195+ import warnings
196+
195197 msg = (
196198 "subpackages can technically be lazily loaded, but it causes the "
197199 "package to be eagerly loaded even if it is already lazily loaded. "
@@ -276,31 +278,6 @@ def _check_requirement(require: str) -> bool:
276278 )
277279
278280
279- class _StubVisitor (ast .NodeVisitor ):
280- """AST visitor to parse a stub file for submodules and submod_attrs."""
281-
282- def __init__ (self ):
283- self ._submodules = set ()
284- self ._submod_attrs = {}
285-
286- def visit_ImportFrom (self , node : ast .ImportFrom ):
287- if node .level != 1 :
288- raise ValueError (
289- "Only within-module imports are supported (`from .* import`)"
290- )
291- if node .module :
292- attrs : list = self ._submod_attrs .setdefault (node .module , [])
293- aliases = [alias .name for alias in node .names ]
294- if "*" in aliases :
295- raise ValueError (
296- "lazy stub loader does not support star import "
297- f"`from { node .module } import *`"
298- )
299- attrs .extend (aliases )
300- else :
301- self ._submodules .update (alias .name for alias in node .names )
302-
303-
304281def attach_stub (package_name : str , filename : str ):
305282 """Attach lazily loaded submodules, functions from a type stub.
306283
@@ -327,6 +304,32 @@ def attach_stub(package_name: str, filename: str):
327304 If a stub file is not found for `filename`, or if the stubfile is formmated
328305 incorrectly (e.g. if it contains an relative import from outside of the module)
329306 """
307+ import ast
308+
309+ class _StubVisitor (ast .NodeVisitor ):
310+ """AST visitor to parse a stub file for submodules and submod_attrs."""
311+
312+ def __init__ (self ):
313+ self ._submodules = set ()
314+ self ._submod_attrs = {}
315+
316+ def visit_ImportFrom (self , node : ast .ImportFrom ):
317+ if node .level != 1 :
318+ raise ValueError (
319+ "Only within-module imports are supported (`from .* import`)"
320+ )
321+ if node .module :
322+ attrs : list = self ._submod_attrs .setdefault (node .module , [])
323+ aliases = [alias .name for alias in node .names ]
324+ if "*" in aliases :
325+ raise ValueError (
326+ "lazy stub loader does not support star import "
327+ f"`from { node .module } import *`"
328+ )
329+ attrs .extend (aliases )
330+ else :
331+ self ._submodules .update (alias .name for alias in node .names )
332+
330333 stubfile = (
331334 filename if filename .endswith ("i" ) else f"{ os .path .splitext (filename )[0 ]} .pyi"
332335 )
0 commit comments