exonware-xwlazy 0.1.0.11__py3-none-any.whl → 0.1.0.20__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. exonware/__init__.py +26 -0
  2. exonware/xwlazy/__init__.py +0 -0
  3. exonware/xwlazy/common/__init__.py +47 -0
  4. exonware/xwlazy/common/base.py +56 -0
  5. exonware/xwlazy/common/cache.py +504 -0
  6. exonware/xwlazy/common/logger.py +257 -0
  7. exonware/xwlazy/common/services/__init__.py +72 -0
  8. exonware/xwlazy/common/services/dependency_mapper.py +232 -0
  9. exonware/xwlazy/common/services/install_async_utils.py +165 -0
  10. exonware/xwlazy/common/services/install_cache_utils.py +245 -0
  11. exonware/xwlazy/common/services/keyword_detection.py +283 -0
  12. exonware/xwlazy/common/services/spec_cache.py +165 -0
  13. xwlazy/lazy/lazy_state.py → exonware/xwlazy/common/services/state_manager.py +0 -2
  14. exonware/xwlazy/common/strategies/__init__.py +28 -0
  15. exonware/xwlazy/common/strategies/caching_dict.py +44 -0
  16. exonware/xwlazy/common/strategies/caching_installation.py +88 -0
  17. exonware/xwlazy/common/strategies/caching_lfu.py +66 -0
  18. exonware/xwlazy/common/strategies/caching_lru.py +63 -0
  19. exonware/xwlazy/common/strategies/caching_multitier.py +59 -0
  20. exonware/xwlazy/common/strategies/caching_ttl.py +59 -0
  21. {xwlazy/lazy → exonware/xwlazy}/config.py +51 -21
  22. exonware/xwlazy/contracts.py +1396 -0
  23. exonware/xwlazy/defs.py +378 -0
  24. xwlazy/lazy/lazy_errors.py → exonware/xwlazy/errors.py +21 -16
  25. exonware/xwlazy/facade.py +991 -0
  26. exonware/xwlazy/module/__init__.py +18 -0
  27. exonware/xwlazy/module/base.py +565 -0
  28. exonware/xwlazy/module/data.py +17 -0
  29. exonware/xwlazy/module/facade.py +246 -0
  30. exonware/xwlazy/module/importer_engine.py +2117 -0
  31. exonware/xwlazy/module/strategies/__init__.py +22 -0
  32. exonware/xwlazy/module/strategies/module_helper_lazy.py +93 -0
  33. exonware/xwlazy/module/strategies/module_helper_simple.py +65 -0
  34. exonware/xwlazy/module/strategies/module_manager_advanced.py +111 -0
  35. exonware/xwlazy/module/strategies/module_manager_simple.py +95 -0
  36. exonware/xwlazy/package/__init__.py +18 -0
  37. exonware/xwlazy/package/base.py +798 -0
  38. xwlazy/lazy/host_conf.py → exonware/xwlazy/package/conf.py +61 -16
  39. exonware/xwlazy/package/data.py +17 -0
  40. exonware/xwlazy/package/facade.py +480 -0
  41. exonware/xwlazy/package/services/__init__.py +84 -0
  42. exonware/xwlazy/package/services/async_install_handle.py +87 -0
  43. exonware/xwlazy/package/services/config_manager.py +245 -0
  44. exonware/xwlazy/package/services/discovery.py +370 -0
  45. {xwlazy/lazy → exonware/xwlazy/package/services}/host_packages.py +43 -20
  46. exonware/xwlazy/package/services/install_async.py +277 -0
  47. exonware/xwlazy/package/services/install_cache.py +145 -0
  48. exonware/xwlazy/package/services/install_interactive.py +59 -0
  49. exonware/xwlazy/package/services/install_policy.py +156 -0
  50. exonware/xwlazy/package/services/install_registry.py +54 -0
  51. exonware/xwlazy/package/services/install_result.py +17 -0
  52. exonware/xwlazy/package/services/install_sbom.py +153 -0
  53. exonware/xwlazy/package/services/install_utils.py +79 -0
  54. exonware/xwlazy/package/services/installer_engine.py +406 -0
  55. exonware/xwlazy/package/services/lazy_installer.py +718 -0
  56. {xwlazy/lazy → exonware/xwlazy/package/services}/manifest.py +40 -33
  57. exonware/xwlazy/package/services/strategy_registry.py +186 -0
  58. exonware/xwlazy/package/strategies/__init__.py +57 -0
  59. exonware/xwlazy/package/strategies/package_discovery_file.py +129 -0
  60. exonware/xwlazy/package/strategies/package_discovery_hybrid.py +84 -0
  61. exonware/xwlazy/package/strategies/package_discovery_manifest.py +101 -0
  62. exonware/xwlazy/package/strategies/package_execution_async.py +113 -0
  63. exonware/xwlazy/package/strategies/package_execution_cached.py +90 -0
  64. exonware/xwlazy/package/strategies/package_execution_pip.py +99 -0
  65. exonware/xwlazy/package/strategies/package_execution_wheel.py +106 -0
  66. exonware/xwlazy/package/strategies/package_mapping_discovery_first.py +100 -0
  67. exonware/xwlazy/package/strategies/package_mapping_hybrid.py +105 -0
  68. exonware/xwlazy/package/strategies/package_mapping_manifest_first.py +100 -0
  69. exonware/xwlazy/package/strategies/package_policy_allow_list.py +57 -0
  70. exonware/xwlazy/package/strategies/package_policy_deny_list.py +57 -0
  71. exonware/xwlazy/package/strategies/package_policy_permissive.py +46 -0
  72. exonware/xwlazy/package/strategies/package_timing_clean.py +67 -0
  73. exonware/xwlazy/package/strategies/package_timing_full.py +66 -0
  74. exonware/xwlazy/package/strategies/package_timing_smart.py +68 -0
  75. exonware/xwlazy/package/strategies/package_timing_temporary.py +66 -0
  76. exonware/xwlazy/runtime/__init__.py +18 -0
  77. exonware/xwlazy/runtime/adaptive_learner.py +129 -0
  78. exonware/xwlazy/runtime/base.py +274 -0
  79. exonware/xwlazy/runtime/facade.py +94 -0
  80. exonware/xwlazy/runtime/intelligent_selector.py +170 -0
  81. exonware/xwlazy/runtime/metrics.py +60 -0
  82. exonware/xwlazy/runtime/performance.py +37 -0
  83. exonware/xwlazy/version.py +2 -2
  84. {exonware_xwlazy-0.1.0.11.dist-info → exonware_xwlazy-0.1.0.20.dist-info}/METADATA +89 -11
  85. exonware_xwlazy-0.1.0.20.dist-info/RECORD +87 -0
  86. exonware_xwlazy-0.1.0.11.dist-info/RECORD +0 -20
  87. xwlazy/__init__.py +0 -34
  88. xwlazy/lazy/__init__.py +0 -301
  89. xwlazy/lazy/bootstrap.py +0 -106
  90. xwlazy/lazy/lazy_base.py +0 -465
  91. xwlazy/lazy/lazy_contracts.py +0 -290
  92. xwlazy/lazy/lazy_core.py +0 -3727
  93. xwlazy/lazy/logging_utils.py +0 -194
  94. xwlazy/version.py +0 -77
  95. {exonware_xwlazy-0.1.0.11.dist-info → exonware_xwlazy-0.1.0.20.dist-info}/WHEEL +0 -0
  96. {exonware_xwlazy-0.1.0.11.dist-info → exonware_xwlazy-0.1.0.20.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,991 @@
1
+ """
2
+ #exonware/xwlazy/src/exonware/xwlazy/facade.py
3
+
4
+ Company: eXonware.com
5
+ Author: Eng. Muhammad AlShehri
6
+ Email: connect@exonware.com
7
+
8
+ Generation Date: 10-Oct-2025
9
+
10
+ Facade for Lazy Loading System
11
+
12
+ This module provides a unified public API facade for the lazy loading system
13
+ following GUIDE_ARCH.md structure. It consolidates all public APIs into a
14
+ single entry point.
15
+
16
+ Design Pattern: Facade Pattern
17
+ - Provides simplified interface to complex subsystem
18
+ - Hides implementation details
19
+ - Centralizes public API
20
+ """
21
+
22
+ import sys
23
+ import subprocess
24
+ import importlib
25
+ import importlib.util
26
+ from typing import Dict, List, Optional, Tuple, Any
27
+ from types import ModuleType
28
+
29
+ # Import from contracts for types
30
+ from .defs import LazyInstallMode, LazyLoadMode, LazyModeConfig
31
+ from .defs import PRESET_MODES, get_preset_mode
32
+
33
+ # Import from new structure modules
34
+ from .package.services.config_manager import LazyInstallConfig
35
+ from .common.services import LazyStateManager
36
+ from .runtime.metrics import MetricsCollector, get_metrics_collector
37
+ from .runtime.performance import LazyPerformanceMonitor
38
+ from .package.services.manifest import get_manifest_loader, refresh_manifest_cache
39
+ from .common.logger import get_logger, log_event as _log
40
+ # Import directly from submodule bases
41
+ from .package.base import APackageHelper
42
+ from .module.base import AModuleHelper
43
+ from .runtime.base import ARuntimeHelper
44
+ # Import concrete implementations from new folder structure
45
+ from .package import XWPackageHelper
46
+ from .module import XWModuleHelper
47
+ from .runtime import XWRuntimeHelper
48
+
49
+ # Import from domain modules
50
+ from .package.services.discovery import get_lazy_discovery as _get_lazy_discovery
51
+ from .common.services.dependency_mapper import DependencyMapper
52
+ from .common.services import (
53
+ enable_keyword_detection as _enable_keyword_detection,
54
+ is_keyword_detection_enabled as _is_keyword_detection_enabled,
55
+ get_keyword_detection_keyword as _get_keyword_detection_keyword,
56
+ check_package_keywords as _check_package_keywords,
57
+ _detect_lazy_installation,
58
+ _detect_meta_info_mode,
59
+ )
60
+ from .package.services import (
61
+ LazyInstallerRegistry,
62
+ LazyInstaller,
63
+ LazyInstallPolicy,
64
+ is_externally_managed as _is_externally_managed,
65
+ )
66
+ from .common.cache import InstallationCache
67
+ from .module.importer_engine import (
68
+ install_import_hook as _install_import_hook,
69
+ uninstall_import_hook as _uninstall_import_hook,
70
+ is_import_hook_installed as _is_import_hook_installed,
71
+ register_lazy_module_prefix as _register_lazy_module_prefix,
72
+ register_lazy_module_methods as _register_lazy_module_methods,
73
+ LazyImporter,
74
+ LazyModuleRegistry,
75
+ )
76
+
77
+ logger = get_logger("xwlazy.facade")
78
+
79
+ # Global instances
80
+ _lazy_importer = LazyImporter()
81
+ _lazy_module_registry = LazyModuleRegistry()
82
+
83
+ # =============================================================================
84
+ # FACADE CLASS
85
+ # =============================================================================
86
+
87
+ class LazyModeFacade:
88
+ """
89
+ Facade class for managing lazy mode configuration and operations.
90
+
91
+ This class provides a unified interface to all lazy loading functionality.
92
+ """
93
+
94
+ def __init__(self):
95
+ self._enabled = False
96
+ self._strategy = "on_demand"
97
+ self._configs: Dict[str, Any] = {}
98
+
99
+ def enable(self, strategy: str = "on_demand", **kwargs) -> None:
100
+ """Enable lazy mode with specified strategy."""
101
+ self._enabled = True
102
+ self._strategy = strategy
103
+ self._configs.update(kwargs)
104
+ logger.info(f"Lazy mode enabled with strategy: {strategy}")
105
+
106
+ def disable(self) -> None:
107
+ """Disable lazy mode and cleanup resources."""
108
+ self._enabled = False
109
+ logger.info("Lazy mode disabled")
110
+
111
+ def is_enabled(self) -> bool:
112
+ """Check if lazy mode is currently enabled."""
113
+ return self._enabled
114
+
115
+ def get_stats(self) -> Dict[str, Any]:
116
+ """Get lazy mode performance statistics."""
117
+ return {
118
+ "enabled": self._enabled,
119
+ "strategy": self._strategy,
120
+ "configs": self._configs.copy(),
121
+ }
122
+
123
+ # Global facade instance
124
+ _lazy_facade = LazyModeFacade()
125
+
126
+ # =============================================================================
127
+ # FACADE FUNCTIONS
128
+ # =============================================================================
129
+
130
+ def enable_lazy_mode(strategy: str = "on_demand", **kwargs) -> None:
131
+ """Enable lazy mode with specified strategy."""
132
+ _lazy_facade.enable(strategy, **kwargs)
133
+
134
+ def disable_lazy_mode() -> None:
135
+ """Disable lazy mode and cleanup resources."""
136
+ _lazy_facade.disable()
137
+
138
+ def is_lazy_mode_enabled() -> bool:
139
+ """Check if lazy mode is currently enabled."""
140
+ return _lazy_facade.is_enabled()
141
+
142
+ def get_lazy_mode_stats() -> Dict[str, Any]:
143
+ """Get lazy mode performance statistics."""
144
+ return _lazy_facade.get_stats()
145
+
146
+ def configure_lazy_mode(package_name: str, config: LazyModeConfig) -> None:
147
+ """Configure lazy mode for a specific package."""
148
+ LazyInstallConfig.set_mode_config(package_name, config)
149
+ logger.info(f"Configured lazy mode for {package_name}")
150
+
151
+ def preload_modules(package_name: str, modules: List[str]) -> None:
152
+ """Preload specified modules for a package."""
153
+ for module_name in modules:
154
+ _lazy_importer.preload_module(module_name)
155
+ logger.info(f"Preloaded {len(modules)} modules for {package_name}")
156
+
157
+ def optimize_lazy_mode(package_name: str) -> None:
158
+ """Optimize lazy mode configuration for a package."""
159
+ _lazy_module_registry.preload_frequently_used()
160
+ logger.info(f"Optimization completed for {package_name}")
161
+
162
+ # =============================================================================
163
+ # PUBLIC API FUNCTIONS - Installation
164
+ # =============================================================================
165
+
166
+ def enable_lazy_install(package_name: str) -> None:
167
+ """Enable lazy installation for a package."""
168
+ LazyInstallConfig.set(package_name, True)
169
+
170
+ def disable_lazy_install(package_name: str) -> None:
171
+ """Disable lazy installation for a package."""
172
+ LazyInstallConfig.set(package_name, False)
173
+
174
+ def is_lazy_install_enabled(package_name: str) -> bool:
175
+ """Check if lazy installation is enabled for a package."""
176
+ return LazyInstallConfig.is_enabled(package_name)
177
+
178
+ def set_lazy_install_mode(package_name: str, mode: LazyInstallMode) -> None:
179
+ """Set lazy installation mode for a package."""
180
+ LazyInstallConfig.set_install_mode(package_name, mode)
181
+
182
+ def get_lazy_install_mode(package_name: str) -> LazyInstallMode:
183
+ """Get lazy installation mode for a package."""
184
+ return LazyInstallConfig.get_install_mode(package_name)
185
+
186
+ def install_missing_package(package_name: str, module_name: str, installer_package: str = 'default') -> bool:
187
+ """Install a missing package for a module."""
188
+ try:
189
+ installer = LazyInstallerRegistry.get_instance(installer_package)
190
+ if not installer.is_enabled():
191
+ logger.debug(f"Lazy installation disabled for {installer_package}")
192
+ return False
193
+ return installer.install_package(package_name, module_name)
194
+ except Exception as e:
195
+ logger.error(f"Failed to install package {package_name} for {installer_package}: {e}")
196
+ return False
197
+
198
+ def install_and_import(module_name: str, package_name: str = None, installer_package: str = 'default') -> Tuple[Optional[ModuleType], bool]:
199
+ """Install package and import module."""
200
+ try:
201
+ installer = LazyInstallerRegistry.get_instance(installer_package)
202
+ if not installer.is_enabled():
203
+ logger.debug(f"Lazy installation disabled for {installer_package}")
204
+ return None, False
205
+ return installer.install_and_import(module_name, package_name)
206
+ except Exception as e:
207
+ logger.error(f"Failed to install and import {module_name} for {installer_package}: {e}")
208
+ return None, False
209
+
210
+ def get_lazy_install_stats(package_name: str) -> Dict[str, Any]:
211
+ """Get installation statistics for a package."""
212
+ try:
213
+ installer = LazyInstallerRegistry.get_instance(package_name)
214
+ return installer.get_stats()
215
+ except Exception as e:
216
+ logger.error(f"Failed to get stats for {package_name}: {e}")
217
+ return {
218
+ 'enabled': False,
219
+ 'mode': 'unknown',
220
+ 'package_name': package_name,
221
+ 'installed_packages': [],
222
+ 'failed_packages': [],
223
+ 'total_installed': 0,
224
+ 'total_failed': 0,
225
+ }
226
+
227
+ def get_all_lazy_install_stats() -> Dict[str, Dict[str, Any]]:
228
+ """Get installation statistics for all packages."""
229
+ try:
230
+ all_instances = LazyInstallerRegistry.get_all_instances()
231
+ return {pkg_name: installer.get_stats() for pkg_name, installer in all_instances.items()}
232
+ except Exception as e:
233
+ logger.error(f"Failed to get all stats: {e}")
234
+ return {}
235
+
236
+ def lazy_import_with_install(module_name: str, package_name: str = None, installer_package: str = 'default') -> Tuple[Optional[ModuleType], bool]:
237
+ """Lazy import with automatic installation."""
238
+ try:
239
+ installer = LazyInstallerRegistry.get_instance(installer_package)
240
+ if not installer.is_enabled():
241
+ logger.debug(f"Lazy installation disabled for {installer_package}")
242
+ return None, False
243
+ return installer.install_and_import(module_name, package_name)
244
+ except Exception as e:
245
+ logger.error(f"Failed to lazy import with install {module_name} for {installer_package}: {e}")
246
+ return None, False
247
+
248
+ def xwimport(module_name: str, package_name: str = None, installer_package: str = 'default') -> Any:
249
+ """Simple lazy import with automatic installation."""
250
+ module, available = lazy_import_with_install(module_name, package_name, installer_package)
251
+ if not available:
252
+ raise ImportError(f"Module {module_name} is not available and could not be installed")
253
+ return module
254
+
255
+ # =============================================================================
256
+ # HOOK FUNCTIONS
257
+ # =============================================================================
258
+
259
+ def install_import_hook(package_name: str = 'default') -> None:
260
+ """Install performant import hook for automatic lazy installation."""
261
+ try:
262
+ _install_import_hook(package_name)
263
+ logger.info(f"Import hook installed for {package_name}")
264
+ except Exception as e:
265
+ logger.error(f"Failed to install import hook for {package_name}: {e}")
266
+ raise
267
+
268
+ def uninstall_import_hook(package_name: str = 'default') -> None:
269
+ """Uninstall import hook for a package."""
270
+ try:
271
+ _uninstall_import_hook(package_name)
272
+ logger.info(f"Import hook uninstalled for {package_name}")
273
+ except Exception as e:
274
+ logger.error(f"Failed to uninstall import hook for {package_name}: {e}")
275
+ raise
276
+
277
+ def is_import_hook_installed(package_name: str = 'default') -> bool:
278
+ """Check if import hook is installed for a package."""
279
+ try:
280
+ return _is_import_hook_installed(package_name)
281
+ except Exception:
282
+ return False
283
+
284
+ # =============================================================================
285
+ # LAZY LOADING FUNCTIONS
286
+ # =============================================================================
287
+
288
+ def enable_lazy_imports(mode: LazyLoadMode = LazyLoadMode.AUTO, package_name: Optional[str] = None) -> None:
289
+ """
290
+ Enable lazy imports.
291
+
292
+ This is a global setting that applies to all packages. The package_name
293
+ parameter is optional and used only for logging purposes.
294
+
295
+ Args:
296
+ mode: The lazy load mode to use (default: LazyLoadMode.AUTO)
297
+ package_name: Optional package name for logging purposes
298
+ """
299
+ try:
300
+ _lazy_importer.enable(mode)
301
+ # Also patch import_module to be lazy-aware (from archive)
302
+ from .module.importer_engine import _patch_import_module
303
+ _patch_import_module()
304
+ if package_name:
305
+ logger.info(f"Lazy imports enabled for {package_name} with mode {mode}")
306
+ else:
307
+ logger.info(f"Lazy imports enabled with mode {mode}")
308
+ except Exception as e:
309
+ if package_name:
310
+ logger.error(f"Failed to enable lazy imports for {package_name}: {e}")
311
+ else:
312
+ logger.error(f"Failed to enable lazy imports: {e}")
313
+ raise
314
+
315
+ def disable_lazy_imports(package_name: Optional[str] = None) -> None:
316
+ """
317
+ Disable lazy imports.
318
+
319
+ This is a global setting that applies to all packages. The package_name
320
+ parameter is optional and used only for logging purposes.
321
+
322
+ Args:
323
+ package_name: Optional package name for logging purposes
324
+ """
325
+ try:
326
+ _lazy_importer.disable()
327
+ # Also unpatch import_module (from archive)
328
+ from .module.importer_engine import _unpatch_import_module
329
+ _unpatch_import_module()
330
+ if package_name:
331
+ logger.info(f"Lazy imports disabled for {package_name}")
332
+ else:
333
+ logger.info("Lazy imports disabled")
334
+ except Exception as e:
335
+ if package_name:
336
+ logger.error(f"Failed to disable lazy imports for {package_name}: {e}")
337
+ else:
338
+ logger.error(f"Failed to disable lazy imports: {e}")
339
+ raise
340
+
341
+ def is_lazy_import_enabled(package_name: Optional[str] = None) -> bool:
342
+ """
343
+ Check if lazy imports are enabled.
344
+
345
+ This checks a global setting that applies to all packages. The package_name
346
+ parameter is optional and used only for logging purposes.
347
+
348
+ Args:
349
+ package_name: Optional package name for logging purposes
350
+
351
+ Returns:
352
+ True if lazy imports are enabled globally
353
+ """
354
+ try:
355
+ return _lazy_importer.is_enabled()
356
+ except (AttributeError, RuntimeError) as e:
357
+ logger.debug(f"Error checking lazy import status: {e}")
358
+ return False
359
+ except Exception as e:
360
+ # Unexpected errors - log but return False for safety
361
+ logger.warning(f"Unexpected error checking lazy import status: {e}")
362
+ return False
363
+
364
+ def lazy_import(module_name: str, package_name: str = None) -> Optional[ModuleType]:
365
+ """Lazy import a module."""
366
+ try:
367
+ return _lazy_importer.import_module(module_name, package_name)
368
+ except Exception as e:
369
+ logger.error(f"Failed to lazy import {module_name}: {e}")
370
+ return None
371
+
372
+ def register_lazy_module(module_name: str, package_name: str = None, module_path: str = None) -> None:
373
+ """Register a lazy module loader."""
374
+ try:
375
+ if module_path is None:
376
+ module_path = module_name
377
+ _lazy_importer.register_lazy_module(module_name, module_path)
378
+ # Also register in global registry (from archive)
379
+ _lazy_module_registry.register_module(module_name, module_path)
380
+ logger.info(f"Lazy module registered: {module_name}")
381
+ except Exception as e:
382
+ logger.error(f"Failed to register lazy module {module_name}: {e}")
383
+ raise
384
+
385
+ def preload_module(module_name: str, package_name: str = None) -> None:
386
+ """Preload a lazy module."""
387
+ try:
388
+ success = _lazy_importer.preload_module(module_name)
389
+ if success:
390
+ logger.info(f"Preload completed: {module_name}")
391
+ else:
392
+ logger.warning(f"Preload failed for {module_name}")
393
+ except Exception as e:
394
+ logger.error(f"Failed to preload module {module_name}: {e}")
395
+ raise
396
+
397
+ def get_lazy_module(module_name: str, package_name: str = None) -> Optional[ModuleType]:
398
+ """Get a lazy module if loaded."""
399
+ # Check if module is already loaded in importer
400
+ stats = _lazy_importer.get_stats()
401
+ if module_name in stats.get('loaded_modules', []):
402
+ # Module is loaded, return it via import (safe since it's cached)
403
+ try:
404
+ import importlib
405
+ return importlib.import_module(module_name)
406
+ except ImportError:
407
+ pass
408
+
409
+ # Fallback to registry
410
+ try:
411
+ loader = _lazy_module_registry.get_module(module_name)
412
+ if loader.is_loaded():
413
+ return loader.load_module()
414
+ except KeyError:
415
+ pass
416
+
417
+ # Check sys.modules as final fallback
418
+ import sys
419
+ return sys.modules.get(module_name)
420
+
421
+ def get_loading_stats(package_name: str) -> Dict[str, Any]:
422
+ """Get loading statistics for a package."""
423
+ try:
424
+ return _lazy_module_registry.get_stats()
425
+ except Exception as e:
426
+ logger.error(f"Failed to get loading stats for {package_name}: {e}")
427
+ return {
428
+ 'total_registered': 0,
429
+ 'loaded_count': 0,
430
+ 'unloaded_count': 0,
431
+ 'access_counts': {},
432
+ 'load_times': {},
433
+ }
434
+
435
+ def preload_frequently_used(package_name: str) -> None:
436
+ """Preload frequently used modules for a package."""
437
+ try:
438
+ _lazy_module_registry.preload_frequently_used()
439
+ logger.info(f"Preload frequently used completed for {package_name}")
440
+ except Exception as e:
441
+ logger.error(f"Failed to preload frequently used for {package_name}: {e}")
442
+
443
+ def get_lazy_import_stats(package_name: str) -> Dict[str, Any]:
444
+ """Get lazy import statistics for a package."""
445
+ try:
446
+ return _lazy_importer.get_stats()
447
+ except Exception as e:
448
+ logger.error(f"Failed to get lazy import stats for {package_name}: {e}")
449
+ return {
450
+ 'enabled': False,
451
+ 'registered_modules': [],
452
+ 'loaded_modules': [],
453
+ 'access_counts': {},
454
+ 'total_registered': 0,
455
+ 'total_loaded': 0,
456
+ }
457
+
458
+ # =============================================================================
459
+ # CONFIGURATION FUNCTIONS
460
+ # =============================================================================
461
+
462
+ def config_package_lazy_install_enabled(
463
+ package_name: str,
464
+ enabled: bool = None,
465
+ mode: str = "auto",
466
+ install_hook: bool = True,
467
+ load_mode: Optional[LazyLoadMode] = None,
468
+ install_mode: Optional[LazyInstallMode] = None,
469
+ mode_config: Optional[LazyModeConfig] = None,
470
+ # Strategy parameters
471
+ execution_strategy: Optional[Any] = None,
472
+ timing_strategy: Optional[Any] = None,
473
+ discovery_strategy: Optional[Any] = None,
474
+ policy_strategy: Optional[Any] = None,
475
+ mapping_strategy: Optional[Any] = None,
476
+ ) -> bool:
477
+ """
478
+ Configure lazy installation for a package.
479
+
480
+ Args:
481
+ package_name: Package name to configure
482
+ enabled: Whether lazy installation is enabled (None = auto-detect)
483
+ mode: Installation mode string (e.g., "smart", "full", "clean")
484
+ install_hook: Whether to install the import hook
485
+ load_mode: Optional explicit load mode
486
+ install_mode: Optional explicit install mode
487
+ mode_config: Optional full mode configuration
488
+ execution_strategy: Optional custom execution strategy instance
489
+ timing_strategy: Optional custom timing strategy instance
490
+ discovery_strategy: Optional custom discovery strategy instance
491
+ policy_strategy: Optional custom policy strategy instance
492
+ mapping_strategy: Optional custom mapping strategy instance
493
+
494
+ Returns:
495
+ True if enabled, False otherwise
496
+ """
497
+ try:
498
+ # Store strategies if provided
499
+ from .package.services.strategy_registry import StrategyRegistry
500
+ if execution_strategy is not None:
501
+ StrategyRegistry.set_package_strategy(package_name, 'execution', execution_strategy)
502
+ if timing_strategy is not None:
503
+ StrategyRegistry.set_package_strategy(package_name, 'timing', timing_strategy)
504
+ if discovery_strategy is not None:
505
+ StrategyRegistry.set_package_strategy(package_name, 'discovery', discovery_strategy)
506
+ if policy_strategy is not None:
507
+ StrategyRegistry.set_package_strategy(package_name, 'policy', policy_strategy)
508
+ if mapping_strategy is not None:
509
+ StrategyRegistry.set_package_strategy(package_name, 'mapping', mapping_strategy)
510
+
511
+ manual_override = enabled is not None
512
+ if enabled is None:
513
+ enabled = _detect_lazy_installation(package_name)
514
+
515
+ # Check meta info for mode override
516
+ if mode == "auto" and enabled:
517
+ meta_mode = _detect_meta_info_mode(package_name)
518
+ if meta_mode:
519
+ mode = meta_mode
520
+
521
+ # Resolve preset mode if provided
522
+ if load_mode is None and install_mode is None and mode_config is None:
523
+ preset = get_preset_mode(mode)
524
+ if preset:
525
+ mode_config = preset
526
+
527
+ LazyInstallConfig.set(
528
+ package_name,
529
+ enabled,
530
+ mode,
531
+ install_hook=install_hook,
532
+ manual=manual_override,
533
+ load_mode=load_mode,
534
+ install_mode=install_mode,
535
+ mode_config=mode_config,
536
+ )
537
+
538
+ # Install hook if requested and enabled
539
+ if install_hook and enabled:
540
+ try:
541
+ install_import_hook(package_name)
542
+ except Exception as hook_error:
543
+ logger.warning(f"Failed to install import hook for {package_name}: {hook_error}")
544
+
545
+ result = LazyInstallConfig.is_enabled(package_name)
546
+ logger.info(f"Configured lazy installation for {package_name}: enabled={result}, mode={mode}")
547
+ return result
548
+ except Exception as e:
549
+ logger.error(f"Failed to configure lazy installation for {package_name}: {e}")
550
+ raise
551
+
552
+ def config_module_lazy_load_enabled(
553
+ package_name: str,
554
+ enabled: bool = True,
555
+ load_mode: Optional[LazyLoadMode] = None,
556
+ # Strategy parameters
557
+ helper_strategy: Optional[Any] = None,
558
+ manager_strategy: Optional[Any] = None,
559
+ caching_strategy: Optional[Any] = None,
560
+ ) -> bool:
561
+ """
562
+ Configure lazy loading for modules in a package.
563
+
564
+ Args:
565
+ package_name: Package name to configure
566
+ enabled: Whether lazy loading is enabled (default: True)
567
+ load_mode: Optional explicit load mode
568
+ helper_strategy: Optional custom helper strategy instance
569
+ manager_strategy: Optional custom manager strategy instance
570
+ caching_strategy: Optional custom caching strategy instance
571
+
572
+ Returns:
573
+ True if enabled, False otherwise
574
+ """
575
+ try:
576
+ # Store strategies if provided
577
+ from .package.services.strategy_registry import StrategyRegistry
578
+ if helper_strategy is not None:
579
+ StrategyRegistry.set_module_strategy(package_name, 'helper', helper_strategy)
580
+ if manager_strategy is not None:
581
+ StrategyRegistry.set_module_strategy(package_name, 'manager', manager_strategy)
582
+ if caching_strategy is not None:
583
+ StrategyRegistry.set_module_strategy(package_name, 'caching', caching_strategy)
584
+
585
+ # Enable lazy imports if requested
586
+ if enabled:
587
+ if load_mode is None:
588
+ load_mode = LazyLoadMode.AUTO
589
+ enable_lazy_imports(load_mode, package_name=package_name)
590
+ else:
591
+ disable_lazy_imports(package_name=package_name)
592
+
593
+ logger.info(f"Configured lazy loading for {package_name}: enabled={enabled}, load_mode={load_mode}")
594
+ return enabled
595
+ except Exception as e:
596
+ logger.error(f"Failed to configure lazy loading for {package_name}: {e}")
597
+ raise
598
+
599
+ def sync_manifest_configuration(package_name: str) -> None:
600
+ """
601
+ Sync configuration from manifest for a specific package.
602
+
603
+ This syncs all manifest settings including:
604
+ - Dependencies
605
+ - Watched prefixes
606
+ - Class wrap prefixes (registered as package class hints)
607
+ - Async configuration
608
+ """
609
+ try:
610
+ from .module.importer_engine import _set_package_class_hints
611
+
612
+ from .package.services.manifest import _normalize_package_name, get_manifest_loader as _get_manifest_loader
613
+ package_key = _normalize_package_name(package_name)
614
+
615
+ # Get the loader instance - this ensures we use the same instance throughout
616
+ # This is critical for tests that patch get_manifest_loader
617
+ loader = _get_manifest_loader()
618
+
619
+ # Sync manifest configuration using the loader instance directly
620
+ # This ensures we use the same loader instance that we'll use to get the manifest
621
+ loader.sync_manifest_configuration(package_name)
622
+
623
+ # Register class wrap prefixes from manifest as package class hints
624
+ # After sync, the manifest cache is cleared, so get_manifest will reload it
625
+ manifest = loader.get_manifest(package_key)
626
+ if manifest and manifest.class_wrap_prefixes:
627
+ _set_package_class_hints(package_key, manifest.class_wrap_prefixes)
628
+
629
+ logger.info(f"Manifest configuration synced for {package_name}")
630
+ except Exception as e:
631
+ logger.error(f"Failed to sync manifest configuration for {package_name}: {e}")
632
+ raise
633
+
634
+ def refresh_lazy_manifests() -> None:
635
+ """Refresh all lazy manifest caches."""
636
+ try:
637
+ refresh_manifest_cache()
638
+ logger.info("Refreshed all lazy manifest caches")
639
+ except Exception as e:
640
+ logger.error(f"Failed to refresh lazy manifest caches: {e}")
641
+ raise
642
+
643
+ # =============================================================================
644
+ # SECURITY & POLICY FUNCTIONS
645
+ # =============================================================================
646
+
647
+ def set_package_allow_list(package_name: str, allowed_packages: List[str]) -> None:
648
+ """Set allow list for a package."""
649
+ try:
650
+ LazyInstallPolicy.set_allow_list(package_name, allowed_packages)
651
+ logger.debug(f"Set allow list for {package_name}: {allowed_packages}")
652
+ except Exception as e:
653
+ logger.error(f"Failed to set allow list for {package_name}: {e}")
654
+ raise
655
+
656
+ def set_package_deny_list(package_name: str, denied_packages: List[str]) -> None:
657
+ """Set deny list for a package."""
658
+ try:
659
+ LazyInstallPolicy.set_deny_list(package_name, denied_packages)
660
+ logger.debug(f"Set deny list for {package_name}: {denied_packages}")
661
+ except Exception as e:
662
+ logger.error(f"Failed to set deny list for {package_name}: {e}")
663
+ raise
664
+
665
+ def add_to_package_allow_list(package_name: str, allowed_package: str) -> None:
666
+ """Add single package to allow list."""
667
+ try:
668
+ LazyInstallPolicy.add_to_allow_list(package_name, allowed_package)
669
+ logger.debug(f"Added {allowed_package} to allow list for {package_name}")
670
+ except Exception as e:
671
+ logger.error(f"Failed to add {allowed_package} to allow list for {package_name}: {e}")
672
+ raise
673
+
674
+ def add_to_package_deny_list(package_name: str, denied_package: str) -> None:
675
+ """Add single package to deny list."""
676
+ try:
677
+ LazyInstallPolicy.add_to_deny_list(package_name, denied_package)
678
+ logger.debug(f"Added {denied_package} to deny list for {package_name}")
679
+ except Exception as e:
680
+ logger.error(f"Failed to add {denied_package} to deny list for {package_name}: {e}")
681
+ raise
682
+
683
+ def set_package_index_url(package_name: str, index_url: str) -> None:
684
+ """Set package index URL for a package."""
685
+ try:
686
+ LazyInstallPolicy.set_index_url(package_name, index_url)
687
+ logger.debug(f"Set index URL for {package_name}: {index_url}")
688
+ except Exception as e:
689
+ logger.error(f"Failed to set index URL for {package_name}: {e}")
690
+ raise
691
+
692
+ def set_package_extra_index_urls(package_name: str, extra_index_urls: List[str]) -> None:
693
+ """Set extra index URLs for a package."""
694
+ try:
695
+ LazyInstallPolicy.set_extra_index_urls(package_name, extra_index_urls)
696
+ logger.debug(f"Set extra index URLs for {package_name}: {extra_index_urls}")
697
+ except Exception as e:
698
+ logger.error(f"Failed to set extra index URLs for {package_name}: {e}")
699
+ raise
700
+
701
+ def add_package_trusted_host(package_name: str, host: str) -> None:
702
+ """Add trusted host for a package."""
703
+ try:
704
+ LazyInstallPolicy.add_trusted_host(package_name, host)
705
+ logger.debug(f"Added trusted host {host} for {package_name}")
706
+ except Exception as e:
707
+ logger.error(f"Failed to add trusted host {host} for {package_name}: {e}")
708
+ raise
709
+
710
+ def set_package_lockfile(package_name: str, lockfile_path: str) -> None:
711
+ """Set lockfile path for a package."""
712
+ try:
713
+ LazyInstallPolicy.set_lockfile_path(package_name, lockfile_path)
714
+ logger.debug(f"Set lockfile path for {package_name}: {lockfile_path}")
715
+ except Exception as e:
716
+ logger.error(f"Failed to set lockfile path for {package_name}: {e}")
717
+ raise
718
+
719
+ def generate_package_sbom(package_name: str, output_path: Optional[str] = None) -> Dict[str, Any]:
720
+ """Generate SBOM for a package."""
721
+ try:
722
+ installer = LazyInstallerRegistry.get_instance(package_name)
723
+ sbom = installer.generate_sbom()
724
+ if output_path:
725
+ installer.export_sbom(output_path)
726
+ return sbom
727
+ except Exception as e:
728
+ logger.error(f"Failed to generate SBOM for {package_name}: {e}")
729
+ return {
730
+ "metadata": {
731
+ "format": "xwlazy-sbom",
732
+ "version": "1.0",
733
+ "error": str(e)
734
+ },
735
+ "packages": []
736
+ }
737
+
738
+ def check_externally_managed_environment(package_name: str = 'default') -> bool:
739
+ """Check if environment is externally managed."""
740
+ try:
741
+ return _is_externally_managed()
742
+ except Exception as e:
743
+ logger.error(f"Failed to check externally managed environment: {e}")
744
+ return False
745
+
746
+ def register_lazy_module_prefix(prefix: str) -> None:
747
+ """Register a module prefix mapping."""
748
+ try:
749
+ _register_lazy_module_prefix(prefix)
750
+ logger.debug(f"Registered lazy module prefix: {prefix}")
751
+ except Exception as e:
752
+ logger.error(f"Failed to register lazy module prefix {prefix}: {e}")
753
+ raise
754
+
755
+ def register_lazy_module_methods(prefix: str, methods: Tuple[str, ...]) -> None:
756
+ """Register methods for a lazy module."""
757
+ try:
758
+ _register_lazy_module_methods(prefix, methods)
759
+ logger.debug(f"Registered lazy module methods for prefix {prefix}: {methods}")
760
+ except Exception as e:
761
+ logger.error(f"Failed to register lazy module methods for {prefix}: {e}")
762
+ raise
763
+
764
+ # =============================================================================
765
+ # MODULE REGISTRATION DOMAIN - Internal Utilities
766
+ # =============================================================================
767
+
768
+ # Note: Internal utility functions are available from hooks.finder module
769
+ # They are used internally by the lazy loading system and don't need facade wrappers
770
+
771
+ # =============================================================================
772
+ # KEYWORD-BASED DETECTION FUNCTIONS
773
+ # =============================================================================
774
+
775
+ def enable_keyword_detection(enabled: bool = True, keyword: Optional[str] = None, package_name: Optional[str] = None) -> None:
776
+ """
777
+ Enable keyword-based package detection.
778
+
779
+ This is a global setting that applies to all packages. The package_name
780
+ parameter is optional and used only for logging purposes.
781
+
782
+ Args:
783
+ enabled: Whether to enable keyword detection (default: True)
784
+ keyword: Custom keyword to check (default: "xwlazy-enabled")
785
+ package_name: Optional package name for logging purposes
786
+ """
787
+ try:
788
+ _enable_keyword_detection(enabled, keyword)
789
+ if package_name:
790
+ logger.info(f"Keyword detection {'enabled' if enabled else 'disabled'} for {package_name}")
791
+ else:
792
+ logger.info(f"Keyword detection {'enabled' if enabled else 'disabled'}")
793
+ except Exception as e:
794
+ if package_name:
795
+ logger.error(f"Failed to enable keyword detection for {package_name}: {e}")
796
+ else:
797
+ logger.error(f"Failed to enable keyword detection: {e}")
798
+ raise
799
+
800
+ def is_keyword_detection_enabled(package_name: Optional[str] = None) -> bool:
801
+ """
802
+ Check if keyword detection is enabled.
803
+
804
+ This checks a global setting that applies to all packages. The package_name
805
+ parameter is optional and used only for logging purposes.
806
+
807
+ Args:
808
+ package_name: Optional package name for logging purposes
809
+
810
+ Returns:
811
+ True if keyword detection is enabled globally
812
+ """
813
+ try:
814
+ return _is_keyword_detection_enabled()
815
+ except (AttributeError, RuntimeError) as e:
816
+ logger.debug(f"Error checking keyword detection status: {e}")
817
+ return False
818
+ except Exception as e:
819
+ # Unexpected errors - log but return False for safety
820
+ logger.warning(f"Unexpected error checking keyword detection status: {e}")
821
+ return False
822
+
823
+ def get_keyword_detection_keyword(package_name: Optional[str] = None) -> Optional[str]:
824
+ """
825
+ Get keyword used for detection.
826
+
827
+ This returns the global keyword setting. The package_name parameter is
828
+ optional and used only for logging purposes.
829
+
830
+ Args:
831
+ package_name: Optional package name for logging purposes
832
+
833
+ Returns:
834
+ The keyword being checked for auto-detection
835
+ """
836
+ try:
837
+ return _get_keyword_detection_keyword()
838
+ except Exception as e:
839
+ logger.error(f"Failed to get keyword detection keyword: {e}")
840
+ return None
841
+
842
+ def check_package_keywords(package_name: Optional[str] = None, keywords: Optional[List[str]] = None) -> bool:
843
+ """
844
+ Check if a package (or any package) has the specified keyword in its metadata.
845
+
846
+ Args:
847
+ package_name: The package name to check (or None to check all packages)
848
+ keywords: Optional list of keywords to check (uses first keyword if provided)
849
+
850
+ Returns:
851
+ True if the keyword is found in the package's metadata
852
+ """
853
+ try:
854
+ keyword = keywords[0] if keywords else None
855
+ return _check_package_keywords(package_name, keyword)
856
+ except Exception as e:
857
+ if package_name:
858
+ logger.error(f"Failed to check package keywords for {package_name}: {e}")
859
+ else:
860
+ logger.error(f"Failed to check package keywords: {e}")
861
+ return False
862
+
863
+ # =============================================================================
864
+ # DISCOVERY FUNCTIONS
865
+ # =============================================================================
866
+
867
+ def get_lazy_discovery(package_name: str = 'default') -> Optional[APackageHelper]:
868
+ """Get discovery instance for a package."""
869
+ try:
870
+ return _get_lazy_discovery()
871
+ except Exception as e:
872
+ logger.error(f"Failed to get discovery instance for {package_name}: {e}")
873
+ return None
874
+
875
+ def discover_dependencies(package_name: str = 'default') -> Dict[str, str]:
876
+ """Discover dependencies for a package."""
877
+ try:
878
+ discovery = _get_lazy_discovery()
879
+ if discovery:
880
+ return discovery.discover_all_dependencies()
881
+ except Exception as e:
882
+ logger.error(f"Failed to discover dependencies for {package_name}: {e}")
883
+ return {}
884
+
885
+ def export_dependency_mappings(package_name: str = 'default', output_path: Optional[str] = None) -> None:
886
+ """Export dependency mappings to file."""
887
+ try:
888
+ discovery = _get_lazy_discovery()
889
+ if discovery:
890
+ if output_path:
891
+ discovery.export_to_json(output_path)
892
+ else:
893
+ # Default output path
894
+ from pathlib import Path
895
+ output_path = Path.cwd() / f"{package_name}_dependencies.json"
896
+ discovery.export_to_json(str(output_path))
897
+ logger.info(f"Dependency mappings exported for {package_name} to {output_path}")
898
+ else:
899
+ logger.warning(f"No discovery instance available for {package_name}")
900
+ except Exception as e:
901
+ logger.error(f"Failed to export dependency mappings for {package_name}: {e}")
902
+ raise
903
+
904
+ # =============================================================================
905
+ # PUBLIC API EXPORTS
906
+ # =============================================================================
907
+
908
+ __all__ = [
909
+ # Facade class
910
+ 'LazyModeFacade',
911
+ # Facade functions
912
+ 'enable_lazy_mode',
913
+ 'disable_lazy_mode',
914
+ 'is_lazy_mode_enabled',
915
+ 'get_lazy_mode_stats',
916
+ 'configure_lazy_mode',
917
+ 'preload_modules',
918
+ 'optimize_lazy_mode',
919
+ # Public API functions
920
+ 'enable_lazy_install',
921
+ 'disable_lazy_install',
922
+ 'is_lazy_install_enabled',
923
+ 'set_lazy_install_mode',
924
+ 'get_lazy_install_mode',
925
+ 'install_missing_package',
926
+ 'install_and_import',
927
+ 'get_lazy_install_stats',
928
+ 'get_all_lazy_install_stats',
929
+ 'lazy_import_with_install',
930
+ 'xwimport',
931
+ # Hook functions
932
+ 'install_import_hook',
933
+ 'uninstall_import_hook',
934
+ 'is_import_hook_installed',
935
+ # Lazy loading functions
936
+ 'enable_lazy_imports',
937
+ 'disable_lazy_imports',
938
+ 'is_lazy_import_enabled',
939
+ 'lazy_import',
940
+ 'register_lazy_module',
941
+ 'preload_module',
942
+ 'get_lazy_module',
943
+ 'get_loading_stats',
944
+ 'preload_frequently_used',
945
+ 'get_lazy_import_stats',
946
+ # Configuration
947
+ 'config_package_lazy_install_enabled',
948
+ 'config_module_lazy_load_enabled',
949
+ 'sync_manifest_configuration',
950
+ 'refresh_lazy_manifests',
951
+ # Security & Policy
952
+ 'set_package_allow_list',
953
+ 'set_package_deny_list',
954
+ 'add_to_package_allow_list',
955
+ 'add_to_package_deny_list',
956
+ 'set_package_index_url',
957
+ 'set_package_extra_index_urls',
958
+ 'add_package_trusted_host',
959
+ 'set_package_lockfile',
960
+ 'generate_package_sbom',
961
+ 'check_externally_managed_environment',
962
+ 'register_lazy_module_prefix',
963
+ 'register_lazy_module_methods',
964
+ # Keyword-based detection
965
+ 'enable_keyword_detection',
966
+ 'is_keyword_detection_enabled',
967
+ 'get_keyword_detection_keyword',
968
+ 'check_package_keywords',
969
+ # Discovery functions
970
+ 'get_lazy_discovery',
971
+ 'discover_dependencies',
972
+ 'export_dependency_mappings',
973
+ # Helper classes
974
+ 'XWPackageHelper',
975
+ 'XWModuleHelper',
976
+ ]
977
+
978
+ # =============================================================================
979
+ # CONCRETE HELPER IMPLEMENTATIONS (Simple API Pattern)
980
+ # =============================================================================
981
+
982
+ # XWPackageHelper moved to package/facade.py
983
+ # This is now just an alias (defined above)
984
+ # Removed duplicate class definition - use the one from package/facade.py
985
+ # XWModuleHelper moved to module/facade.py
986
+ # This is now just an alias (defined above)
987
+ # Removed duplicate class definition - use the one from module/facade.py
988
+
989
+ # Global helper instances
990
+ _package_helper = XWPackageHelper()
991
+ _module_helper = XWModuleHelper()