@@ -341,23 +341,63 @@ def __init__(self, package_path: Path, package_name: str):
341341 self ._load_lazy_imports ()
342342
343343 def _load_lazy_imports (self ):
344- """Load _LAZY_IMPORTS from __init__.py if available."""
345- init_file = self .package_path / "__init__.py"
346- if not init_file .exists ():
347- return
344+ """Load _LAZY_IMPORTS and _LAZY_GROUPS from all __init__.py files.
348345
349- try :
350- content = init_file .read_text ()
351- match = re .search (r'_LAZY_IMPORTS\s*=\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}' , content , re .DOTALL )
352- if not match :
353- return
354-
346+ Recursively scans the package and all submodules for lazy import patterns.
347+ Supports two patterns:
348+ 1. _LAZY_IMPORTS: flat dict of symbol -> (module, symbol)
349+ 2. _LAZY_GROUPS: nested dict of group -> {symbol: (module, symbol)}
350+ """
351+ # Scan all __init__.py files in the package
352+ init_files = list (self .package_path .rglob ("__init__.py" ))
353+
354+ for init_file in init_files :
355+ try :
356+ content = init_file .read_text ()
357+ self ._parse_lazy_patterns (content )
358+ except Exception :
359+ pass
360+
361+ def _parse_lazy_patterns (self , content : str ):
362+ """Parse various lazy loading patterns from file content.
363+
364+ Supports patterns:
365+ 1. _LAZY_IMPORTS: flat dict of symbol -> (module, symbol)
366+ 2. _LAZY_GROUPS: nested dict of group -> {symbol: (module, symbol)}
367+ 3. TOOL_MAPPINGS: dict of symbol -> (module, class_or_none)
368+ 4. __all__: explicit export list (fallback for inline __getattr__)
369+ """
370+ # Pattern 1: _LAZY_IMPORTS (flat dict)
371+ match = re .search (r'_LAZY_IMPORTS\s*=\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}' , content , re .DOTALL )
372+ if match :
355373 dict_content = match .group (1 )
356374 pattern = r"'(\w+)':\s*\('([^']+)',\s*'([^']+)'\)"
357375 for m in re .finditer (pattern , dict_content ):
358376 self ._lazy_imports [m .group (1 )] = (m .group (2 ), m .group (3 ))
359- except Exception :
360- pass
377+
378+ # Pattern 2: _LAZY_GROUPS (nested dict used by hooks, etc.)
379+ match = re .search (r'_LAZY_GROUPS\s*=\s*\{(.+?)\n\}' , content , re .DOTALL )
380+ if match :
381+ groups_content = match .group (1 )
382+ # Parse all symbol -> (module, symbol) pairs within groups
383+ pattern = r"'(\w+)':\s*\('([^']+)',\s*'([^']+)'\)"
384+ for m in re .finditer (pattern , groups_content ):
385+ symbol_name = m .group (1 )
386+ module_path = m .group (2 )
387+ self ._lazy_imports [symbol_name ] = (module_path , symbol_name )
388+
389+ # Pattern 3: TOOL_MAPPINGS (used by tools/__init__.py)
390+ match = re .search (r'TOOL_MAPPINGS\s*=\s*\{(.+?)\n\}' , content , re .DOTALL )
391+ if match :
392+ mappings_content = match .group (1 )
393+ # Parse 'symbol': ('.module', None) or 'symbol': ('.module', 'ClassName')
394+ pattern = r"'(\w+)':\s*\('([^']+)',\s*(?:None|'([^']*)')\)"
395+ for m in re .finditer (pattern , mappings_content ):
396+ symbol_name = m .group (1 )
397+ self ._lazy_imports [symbol_name ] = (m .group (2 ), symbol_name )
398+
399+
400+
361401
362402 def get_modules (self ) -> List [str ]:
363403 """Get list of modules to document."""
0 commit comments