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