exonware-xwlazy 0.1.0.22__py3-none-any.whl → 0.1.0.23__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 +20 -1
- exonware/xwlazy/__init__.py +14 -2
- exonware/xwlazy/common/__init__.py +8 -0
- exonware/xwlazy/common/base.py +11 -2
- exonware/xwlazy/common/cache.py +5 -5
- exonware/xwlazy/common/logger.py +5 -5
- exonware/xwlazy/common/services/dependency_mapper.py +31 -13
- exonware/xwlazy/common/services/install_async_utils.py +5 -0
- exonware/xwlazy/common/services/install_cache_utils.py +4 -4
- exonware/xwlazy/common/services/spec_cache.py +2 -2
- exonware/xwlazy/common/services/state_manager.py +4 -4
- exonware/xwlazy/common/strategies/caching_dict.py +2 -2
- exonware/xwlazy/common/strategies/caching_lfu.py +2 -2
- exonware/xwlazy/common/strategies/caching_ttl.py +2 -2
- exonware/xwlazy/common/utils.py +142 -0
- exonware/xwlazy/config.py +1 -1
- exonware/xwlazy/contracts.py +162 -25
- exonware/xwlazy/defs.py +15 -15
- exonware/xwlazy/facade.py +175 -29
- exonware/xwlazy/host/__init__.py +8 -0
- exonware/xwlazy/host/conf.py +16 -0
- exonware/xwlazy/module/base.py +61 -4
- exonware/xwlazy/module/facade.py +1 -1
- exonware/xwlazy/module/importer_engine.py +1017 -170
- exonware/xwlazy/module/partial_module_detector.py +275 -0
- exonware/xwlazy/module/strategies/module_helper_lazy.py +3 -3
- exonware/xwlazy/package/base.py +106 -41
- exonware/xwlazy/package/conf.py +6 -6
- exonware/xwlazy/package/services/config_manager.py +20 -16
- exonware/xwlazy/package/services/discovery.py +81 -16
- exonware/xwlazy/package/services/host_packages.py +41 -6
- exonware/xwlazy/package/services/install_async.py +16 -2
- exonware/xwlazy/package/services/install_cache.py +4 -4
- exonware/xwlazy/package/services/install_policy.py +14 -14
- exonware/xwlazy/package/services/install_registry.py +3 -3
- exonware/xwlazy/package/services/install_sbom.py +1 -1
- exonware/xwlazy/package/services/installer_engine.py +3 -3
- exonware/xwlazy/package/services/lazy_installer.py +102 -17
- exonware/xwlazy/package/services/manifest.py +43 -36
- exonware/xwlazy/package/services/strategy_registry.py +150 -12
- exonware/xwlazy/package/strategies/package_discovery_file.py +2 -2
- exonware/xwlazy/package/strategies/package_discovery_hybrid.py +2 -2
- exonware/xwlazy/package/strategies/package_discovery_manifest.py +2 -2
- exonware/xwlazy/package/strategies/package_execution_async.py +3 -3
- exonware/xwlazy/package/strategies/package_execution_cached.py +2 -2
- exonware/xwlazy/package/strategies/package_execution_pip.py +2 -2
- exonware/xwlazy/package/strategies/package_execution_wheel.py +2 -2
- exonware/xwlazy/package/strategies/package_mapping_discovery_first.py +2 -2
- exonware/xwlazy/package/strategies/package_mapping_hybrid.py +2 -2
- exonware/xwlazy/package/strategies/package_mapping_manifest_first.py +2 -2
- exonware/xwlazy/package/strategies/package_policy_allow_list.py +4 -4
- exonware/xwlazy/package/strategies/package_policy_deny_list.py +4 -4
- exonware/xwlazy/package/strategies/package_policy_permissive.py +3 -3
- exonware/xwlazy/package/strategies/package_timing_clean.py +2 -2
- exonware/xwlazy/package/strategies/package_timing_full.py +2 -2
- exonware/xwlazy/package/strategies/package_timing_smart.py +2 -2
- exonware/xwlazy/package/strategies/package_timing_temporary.py +2 -2
- exonware/xwlazy/runtime/adaptive_learner.py +7 -7
- exonware/xwlazy/runtime/base.py +14 -14
- exonware/xwlazy/runtime/facade.py +7 -7
- exonware/xwlazy/runtime/intelligent_selector.py +6 -6
- exonware/xwlazy/runtime/metrics.py +6 -6
- exonware/xwlazy/runtime/performance.py +5 -5
- exonware/xwlazy/version.py +2 -2
- {exonware_xwlazy-0.1.0.22.dist-info → exonware_xwlazy-0.1.0.23.dist-info}/METADATA +2 -6
- exonware_xwlazy-0.1.0.23.dist-info/RECORD +93 -0
- xwlazy/__init__.py +14 -0
- xwlazy/lazy.py +30 -0
- exonware_xwlazy-0.1.0.22.dist-info/RECORD +0 -87
- {exonware_xwlazy-0.1.0.22.dist-info → exonware_xwlazy-0.1.0.23.dist-info}/WHEEL +0 -0
- {exonware_xwlazy-0.1.0.22.dist-info → exonware_xwlazy-0.1.0.23.dist-info}/licenses/LICENSE +0 -0
exonware/xwlazy/facade.py
CHANGED
|
@@ -19,11 +19,12 @@ Design Pattern: Facade Pattern
|
|
|
19
19
|
- Centralizes public API
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
+
import os
|
|
22
23
|
import sys
|
|
23
24
|
import subprocess
|
|
24
25
|
import importlib
|
|
25
26
|
import importlib.util
|
|
26
|
-
from typing import
|
|
27
|
+
from typing import Optional, Any
|
|
27
28
|
from types import ModuleType
|
|
28
29
|
|
|
29
30
|
# Import from contracts for types
|
|
@@ -70,6 +71,9 @@ from .module.importer_engine import (
|
|
|
70
71
|
is_import_hook_installed as _is_import_hook_installed,
|
|
71
72
|
register_lazy_module_prefix as _register_lazy_module_prefix,
|
|
72
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,
|
|
73
77
|
LazyImporter,
|
|
74
78
|
LazyModuleRegistry,
|
|
75
79
|
)
|
|
@@ -94,7 +98,7 @@ class LazyModeFacade:
|
|
|
94
98
|
def __init__(self):
|
|
95
99
|
self._enabled = False
|
|
96
100
|
self._strategy = "on_demand"
|
|
97
|
-
self._configs:
|
|
101
|
+
self._configs: dict[str, Any] = {}
|
|
98
102
|
|
|
99
103
|
def enable(self, strategy: str = "on_demand", **kwargs) -> None:
|
|
100
104
|
"""Enable lazy mode with specified strategy."""
|
|
@@ -112,7 +116,7 @@ class LazyModeFacade:
|
|
|
112
116
|
"""Check if lazy mode is currently enabled."""
|
|
113
117
|
return self._enabled
|
|
114
118
|
|
|
115
|
-
def get_stats(self) ->
|
|
119
|
+
def get_stats(self) -> dict[str, Any]:
|
|
116
120
|
"""Get lazy mode performance statistics."""
|
|
117
121
|
return {
|
|
118
122
|
"enabled": self._enabled,
|
|
@@ -139,16 +143,17 @@ def is_lazy_mode_enabled() -> bool:
|
|
|
139
143
|
"""Check if lazy mode is currently enabled."""
|
|
140
144
|
return _lazy_facade.is_enabled()
|
|
141
145
|
|
|
142
|
-
def get_lazy_mode_stats() ->
|
|
146
|
+
def get_lazy_mode_stats() -> dict[str, Any]:
|
|
143
147
|
"""Get lazy mode performance statistics."""
|
|
144
148
|
return _lazy_facade.get_stats()
|
|
145
149
|
|
|
146
150
|
def configure_lazy_mode(package_name: str, config: LazyModeConfig) -> None:
|
|
147
151
|
"""Configure lazy mode for a specific package."""
|
|
148
|
-
|
|
152
|
+
# Use set() method with mode_config parameter
|
|
153
|
+
LazyInstallConfig.set(package_name, True, mode_config=config)
|
|
149
154
|
logger.info(f"Configured lazy mode for {package_name}")
|
|
150
155
|
|
|
151
|
-
def preload_modules(package_name: str, modules:
|
|
156
|
+
def preload_modules(package_name: str, modules: list[str]) -> None:
|
|
152
157
|
"""Preload specified modules for a package."""
|
|
153
158
|
for module_name in modules:
|
|
154
159
|
_lazy_importer.preload_module(module_name)
|
|
@@ -159,6 +164,134 @@ def optimize_lazy_mode(package_name: str) -> None:
|
|
|
159
164
|
_lazy_module_registry.preload_frequently_used()
|
|
160
165
|
logger.info(f"Optimization completed for {package_name}")
|
|
161
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
|
+
|
|
162
295
|
# =============================================================================
|
|
163
296
|
# PUBLIC API FUNCTIONS - Installation
|
|
164
297
|
# =============================================================================
|
|
@@ -195,7 +328,7 @@ def install_missing_package(package_name: str, module_name: str, installer_packa
|
|
|
195
328
|
logger.error(f"Failed to install package {package_name} for {installer_package}: {e}")
|
|
196
329
|
return False
|
|
197
330
|
|
|
198
|
-
def install_and_import(module_name: str, package_name: str = None, installer_package: str = 'default') ->
|
|
331
|
+
def install_and_import(module_name: str, package_name: str = None, installer_package: str = 'default') -> tuple[Optional[ModuleType], bool]:
|
|
199
332
|
"""Install package and import module."""
|
|
200
333
|
try:
|
|
201
334
|
installer = LazyInstallerRegistry.get_instance(installer_package)
|
|
@@ -207,7 +340,7 @@ def install_and_import(module_name: str, package_name: str = None, installer_pac
|
|
|
207
340
|
logger.error(f"Failed to install and import {module_name} for {installer_package}: {e}")
|
|
208
341
|
return None, False
|
|
209
342
|
|
|
210
|
-
def get_lazy_install_stats(package_name: str) ->
|
|
343
|
+
def get_lazy_install_stats(package_name: str) -> dict[str, Any]:
|
|
211
344
|
"""Get installation statistics for a package."""
|
|
212
345
|
try:
|
|
213
346
|
installer = LazyInstallerRegistry.get_instance(package_name)
|
|
@@ -224,7 +357,7 @@ def get_lazy_install_stats(package_name: str) -> Dict[str, Any]:
|
|
|
224
357
|
'total_failed': 0,
|
|
225
358
|
}
|
|
226
359
|
|
|
227
|
-
def get_all_lazy_install_stats() ->
|
|
360
|
+
def get_all_lazy_install_stats() -> dict[str, dict[str, Any]]:
|
|
228
361
|
"""Get installation statistics for all packages."""
|
|
229
362
|
try:
|
|
230
363
|
all_instances = LazyInstallerRegistry.get_all_instances()
|
|
@@ -233,7 +366,7 @@ def get_all_lazy_install_stats() -> Dict[str, Dict[str, Any]]:
|
|
|
233
366
|
logger.error(f"Failed to get all stats: {e}")
|
|
234
367
|
return {}
|
|
235
368
|
|
|
236
|
-
def lazy_import_with_install(module_name: str, package_name: str = None, installer_package: str = 'default') ->
|
|
369
|
+
def lazy_import_with_install(module_name: str, package_name: str = None, installer_package: str = 'default') -> tuple[Optional[ModuleType], bool]:
|
|
237
370
|
"""Lazy import with automatic installation."""
|
|
238
371
|
try:
|
|
239
372
|
installer = LazyInstallerRegistry.get_instance(installer_package)
|
|
@@ -260,7 +393,7 @@ def install_import_hook(package_name: str = 'default') -> None:
|
|
|
260
393
|
"""Install performant import hook for automatic lazy installation."""
|
|
261
394
|
try:
|
|
262
395
|
_install_import_hook(package_name)
|
|
263
|
-
logger.
|
|
396
|
+
logger.debug(f"Import hook installed for {package_name}")
|
|
264
397
|
except Exception as e:
|
|
265
398
|
logger.error(f"Failed to install import hook for {package_name}: {e}")
|
|
266
399
|
raise
|
|
@@ -269,7 +402,7 @@ def uninstall_import_hook(package_name: str = 'default') -> None:
|
|
|
269
402
|
"""Uninstall import hook for a package."""
|
|
270
403
|
try:
|
|
271
404
|
_uninstall_import_hook(package_name)
|
|
272
|
-
logger.
|
|
405
|
+
logger.debug(f"Import hook uninstalled for {package_name}")
|
|
273
406
|
except Exception as e:
|
|
274
407
|
logger.error(f"Failed to uninstall import hook for {package_name}: {e}")
|
|
275
408
|
raise
|
|
@@ -298,13 +431,11 @@ def enable_lazy_imports(mode: LazyLoadMode = LazyLoadMode.AUTO, package_name: Op
|
|
|
298
431
|
"""
|
|
299
432
|
try:
|
|
300
433
|
_lazy_importer.enable(mode)
|
|
301
|
-
#
|
|
302
|
-
from .module.importer_engine import _patch_import_module
|
|
303
|
-
_patch_import_module()
|
|
434
|
+
# Note: _patch_import_module removed - using sys.meta_path hooks instead
|
|
304
435
|
if package_name:
|
|
305
|
-
logger.
|
|
436
|
+
logger.debug(f"Lazy imports enabled for {package_name} with mode {mode}")
|
|
306
437
|
else:
|
|
307
|
-
logger.
|
|
438
|
+
logger.debug(f"Lazy imports enabled with mode {mode}")
|
|
308
439
|
except Exception as e:
|
|
309
440
|
if package_name:
|
|
310
441
|
logger.error(f"Failed to enable lazy imports for {package_name}: {e}")
|
|
@@ -418,7 +549,7 @@ def get_lazy_module(module_name: str, package_name: str = None) -> Optional[Modu
|
|
|
418
549
|
import sys
|
|
419
550
|
return sys.modules.get(module_name)
|
|
420
551
|
|
|
421
|
-
def get_loading_stats(package_name: str) ->
|
|
552
|
+
def get_loading_stats(package_name: str) -> dict[str, Any]:
|
|
422
553
|
"""Get loading statistics for a package."""
|
|
423
554
|
try:
|
|
424
555
|
return _lazy_module_registry.get_stats()
|
|
@@ -440,7 +571,7 @@ def preload_frequently_used(package_name: str) -> None:
|
|
|
440
571
|
except Exception as e:
|
|
441
572
|
logger.error(f"Failed to preload frequently used for {package_name}: {e}")
|
|
442
573
|
|
|
443
|
-
def get_lazy_import_stats(package_name: str) ->
|
|
574
|
+
def get_lazy_import_stats(package_name: str) -> dict[str, Any]:
|
|
444
575
|
"""Get lazy import statistics for a package."""
|
|
445
576
|
try:
|
|
446
577
|
return _lazy_importer.get_stats()
|
|
@@ -535,7 +666,18 @@ def config_package_lazy_install_enabled(
|
|
|
535
666
|
mode_config=mode_config,
|
|
536
667
|
)
|
|
537
668
|
|
|
538
|
-
#
|
|
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
|
|
539
681
|
if install_hook and enabled:
|
|
540
682
|
try:
|
|
541
683
|
install_import_hook(package_name)
|
|
@@ -543,7 +685,7 @@ def config_package_lazy_install_enabled(
|
|
|
543
685
|
logger.warning(f"Failed to install import hook for {package_name}: {hook_error}")
|
|
544
686
|
|
|
545
687
|
result = LazyInstallConfig.is_enabled(package_name)
|
|
546
|
-
logger.
|
|
688
|
+
logger.debug(f"Configured lazy installation for {package_name}: enabled={result}, mode={mode}")
|
|
547
689
|
return result
|
|
548
690
|
except Exception as e:
|
|
549
691
|
logger.error(f"Failed to configure lazy installation for {package_name}: {e}")
|
|
@@ -626,7 +768,7 @@ def sync_manifest_configuration(package_name: str) -> None:
|
|
|
626
768
|
if manifest and manifest.class_wrap_prefixes:
|
|
627
769
|
_set_package_class_hints(package_key, manifest.class_wrap_prefixes)
|
|
628
770
|
|
|
629
|
-
logger.
|
|
771
|
+
logger.debug(f"Manifest configuration synced for {package_name}")
|
|
630
772
|
except Exception as e:
|
|
631
773
|
logger.error(f"Failed to sync manifest configuration for {package_name}: {e}")
|
|
632
774
|
raise
|
|
@@ -644,7 +786,7 @@ def refresh_lazy_manifests() -> None:
|
|
|
644
786
|
# SECURITY & POLICY FUNCTIONS
|
|
645
787
|
# =============================================================================
|
|
646
788
|
|
|
647
|
-
def set_package_allow_list(package_name: str, allowed_packages:
|
|
789
|
+
def set_package_allow_list(package_name: str, allowed_packages: list[str]) -> None:
|
|
648
790
|
"""Set allow list for a package."""
|
|
649
791
|
try:
|
|
650
792
|
LazyInstallPolicy.set_allow_list(package_name, allowed_packages)
|
|
@@ -653,7 +795,7 @@ def set_package_allow_list(package_name: str, allowed_packages: List[str]) -> No
|
|
|
653
795
|
logger.error(f"Failed to set allow list for {package_name}: {e}")
|
|
654
796
|
raise
|
|
655
797
|
|
|
656
|
-
def set_package_deny_list(package_name: str, denied_packages:
|
|
798
|
+
def set_package_deny_list(package_name: str, denied_packages: list[str]) -> None:
|
|
657
799
|
"""Set deny list for a package."""
|
|
658
800
|
try:
|
|
659
801
|
LazyInstallPolicy.set_deny_list(package_name, denied_packages)
|
|
@@ -689,7 +831,7 @@ def set_package_index_url(package_name: str, index_url: str) -> None:
|
|
|
689
831
|
logger.error(f"Failed to set index URL for {package_name}: {e}")
|
|
690
832
|
raise
|
|
691
833
|
|
|
692
|
-
def set_package_extra_index_urls(package_name: str, extra_index_urls:
|
|
834
|
+
def set_package_extra_index_urls(package_name: str, extra_index_urls: list[str]) -> None:
|
|
693
835
|
"""Set extra index URLs for a package."""
|
|
694
836
|
try:
|
|
695
837
|
LazyInstallPolicy.set_extra_index_urls(package_name, extra_index_urls)
|
|
@@ -716,7 +858,7 @@ def set_package_lockfile(package_name: str, lockfile_path: str) -> None:
|
|
|
716
858
|
logger.error(f"Failed to set lockfile path for {package_name}: {e}")
|
|
717
859
|
raise
|
|
718
860
|
|
|
719
|
-
def generate_package_sbom(package_name: str, output_path: Optional[str] = None) ->
|
|
861
|
+
def generate_package_sbom(package_name: str, output_path: Optional[str] = None) -> dict[str, Any]:
|
|
720
862
|
"""Generate SBOM for a package."""
|
|
721
863
|
try:
|
|
722
864
|
installer = LazyInstallerRegistry.get_instance(package_name)
|
|
@@ -752,7 +894,7 @@ def register_lazy_module_prefix(prefix: str) -> None:
|
|
|
752
894
|
logger.error(f"Failed to register lazy module prefix {prefix}: {e}")
|
|
753
895
|
raise
|
|
754
896
|
|
|
755
|
-
def register_lazy_module_methods(prefix: str, methods:
|
|
897
|
+
def register_lazy_module_methods(prefix: str, methods: tuple[str, ...]) -> None:
|
|
756
898
|
"""Register methods for a lazy module."""
|
|
757
899
|
try:
|
|
758
900
|
_register_lazy_module_methods(prefix, methods)
|
|
@@ -839,7 +981,7 @@ def get_keyword_detection_keyword(package_name: Optional[str] = None) -> Optiona
|
|
|
839
981
|
logger.error(f"Failed to get keyword detection keyword: {e}")
|
|
840
982
|
return None
|
|
841
983
|
|
|
842
|
-
def check_package_keywords(package_name: Optional[str] = None, keywords: Optional[
|
|
984
|
+
def check_package_keywords(package_name: Optional[str] = None, keywords: Optional[list[str]] = None) -> bool:
|
|
843
985
|
"""
|
|
844
986
|
Check if a package (or any package) has the specified keyword in its metadata.
|
|
845
987
|
|
|
@@ -872,7 +1014,7 @@ def get_lazy_discovery(package_name: str = 'default') -> Optional[APackageHelper
|
|
|
872
1014
|
logger.error(f"Failed to get discovery instance for {package_name}: {e}")
|
|
873
1015
|
return None
|
|
874
1016
|
|
|
875
|
-
def discover_dependencies(package_name: str = 'default') ->
|
|
1017
|
+
def discover_dependencies(package_name: str = 'default') -> dict[str, str]:
|
|
876
1018
|
"""Discover dependencies for a package."""
|
|
877
1019
|
try:
|
|
878
1020
|
discovery = _get_lazy_discovery()
|
|
@@ -916,6 +1058,10 @@ __all__ = [
|
|
|
916
1058
|
'configure_lazy_mode',
|
|
917
1059
|
'preload_modules',
|
|
918
1060
|
'optimize_lazy_mode',
|
|
1061
|
+
# One-line activation API
|
|
1062
|
+
'auto_enable_lazy',
|
|
1063
|
+
# Lazy-loader compatible API
|
|
1064
|
+
'attach',
|
|
919
1065
|
# Public API functions
|
|
920
1066
|
'enable_lazy_install',
|
|
921
1067
|
'disable_lazy_install',
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Host-facing configuration module for xwlazy.
|
|
3
|
+
|
|
4
|
+
This module provides the get_conf_module function that host packages
|
|
5
|
+
like xwsystem use to enable lazy mode via exonware.conf.
|
|
6
|
+
|
|
7
|
+
Company: eXonware.com
|
|
8
|
+
Author: Eng. Muhammad AlShehri
|
|
9
|
+
Email: connect@exonware.com
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
# Re-export from the actual implementation location
|
|
13
|
+
from ..package.conf import get_conf_module
|
|
14
|
+
|
|
15
|
+
__all__ = ['get_conf_module']
|
|
16
|
+
|
exonware/xwlazy/module/base.py
CHANGED
|
@@ -15,13 +15,14 @@ This module defines the abstract base class for module operations.
|
|
|
15
15
|
import sys
|
|
16
16
|
import threading
|
|
17
17
|
from abc import ABC, abstractmethod
|
|
18
|
-
from typing import
|
|
18
|
+
from typing import Optional, Any
|
|
19
19
|
from types import ModuleType
|
|
20
20
|
|
|
21
21
|
from ..contracts import (
|
|
22
22
|
IModuleHelper,
|
|
23
23
|
IModuleHelperStrategy,
|
|
24
24
|
IModuleManagerStrategy,
|
|
25
|
+
ILoadStrategy,
|
|
25
26
|
)
|
|
26
27
|
from ..package.base import APackageHelper
|
|
27
28
|
|
|
@@ -291,7 +292,7 @@ class AModuleHelper(IModuleHelper, ABC):
|
|
|
291
292
|
# Uninstall the packages (package_helper handles deduplication and caching)
|
|
292
293
|
self._get_package_helper().uninstall(*package_names)
|
|
293
294
|
|
|
294
|
-
def load(self, *module_names: str) ->
|
|
295
|
+
def load(self, *module_names: str) -> list[ModuleType]:
|
|
295
296
|
"""
|
|
296
297
|
Load one or more modules into memory.
|
|
297
298
|
|
|
@@ -405,7 +406,7 @@ class AModuleHelper(IModuleHelper, ABC):
|
|
|
405
406
|
# Note: Many methods from IModuleHelper are already implemented above.
|
|
406
407
|
# The following are stubs that need concrete implementations:
|
|
407
408
|
|
|
408
|
-
def install_and_import(self, module_name: str, package_name: Optional[str] = None) ->
|
|
409
|
+
def install_and_import(self, module_name: str, package_name: Optional[str] = None) -> tuple[Optional[ModuleType], bool]:
|
|
409
410
|
"""Install package and import module (from IModuleInstaller)."""
|
|
410
411
|
raise NotImplementedError("Subclasses must implement install_and_import")
|
|
411
412
|
|
|
@@ -465,7 +466,7 @@ class AModuleHelper(IModuleHelper, ABC):
|
|
|
465
466
|
"""Check if a root module name is being watched (from IWatchedRegistry)."""
|
|
466
467
|
raise NotImplementedError("Subclasses must implement has_root")
|
|
467
468
|
|
|
468
|
-
def get_matching_prefixes(self, fullname: str) ->
|
|
469
|
+
def get_matching_prefixes(self, fullname: str) -> tuple[str, ...]:
|
|
469
470
|
"""Get all watched prefixes that match a module name (from IWatchedRegistry)."""
|
|
470
471
|
raise NotImplementedError("Subclasses must implement get_matching_prefixes")
|
|
471
472
|
|
|
@@ -557,9 +558,65 @@ class AModuleManagerStrategy(IModuleManagerStrategy, ABC):
|
|
|
557
558
|
# EXPORT ALL
|
|
558
559
|
# =============================================================================
|
|
559
560
|
|
|
561
|
+
# =============================================================================
|
|
562
|
+
# ABSTRACT LOADING STRATEGY (Enhanced for Runtime Swapping)
|
|
563
|
+
# =============================================================================
|
|
564
|
+
|
|
565
|
+
class ALoadStrategy(ILoadStrategy, ABC):
|
|
566
|
+
"""
|
|
567
|
+
Abstract base class for module loading strategies.
|
|
568
|
+
|
|
569
|
+
Enables runtime strategy swapping for different loading methods
|
|
570
|
+
(lazy, simple, advanced, etc.).
|
|
571
|
+
"""
|
|
572
|
+
|
|
573
|
+
@abstractmethod
|
|
574
|
+
def load(self, module_name: str) -> ModuleType:
|
|
575
|
+
"""
|
|
576
|
+
Load a module.
|
|
577
|
+
|
|
578
|
+
Args:
|
|
579
|
+
module_name: Module name to load
|
|
580
|
+
|
|
581
|
+
Returns:
|
|
582
|
+
Loaded module
|
|
583
|
+
"""
|
|
584
|
+
...
|
|
585
|
+
|
|
586
|
+
def should_lazy_load(self, module_name: str) -> bool:
|
|
587
|
+
"""
|
|
588
|
+
Determine if module should be lazy loaded.
|
|
589
|
+
|
|
590
|
+
Default implementation returns True.
|
|
591
|
+
Override for strategy-specific logic.
|
|
592
|
+
|
|
593
|
+
Args:
|
|
594
|
+
module_name: Module name to check
|
|
595
|
+
|
|
596
|
+
Returns:
|
|
597
|
+
True if should lazy load, False otherwise
|
|
598
|
+
"""
|
|
599
|
+
return True
|
|
600
|
+
|
|
601
|
+
@abstractmethod
|
|
602
|
+
def unload(self, module_name: str) -> None:
|
|
603
|
+
"""
|
|
604
|
+
Unload a module.
|
|
605
|
+
|
|
606
|
+
Args:
|
|
607
|
+
module_name: Module name to unload
|
|
608
|
+
"""
|
|
609
|
+
...
|
|
610
|
+
|
|
611
|
+
# =============================================================================
|
|
612
|
+
# EXPORT ALL
|
|
613
|
+
# =============================================================================
|
|
614
|
+
|
|
560
615
|
__all__ = [
|
|
561
616
|
'AModuleHelper',
|
|
562
617
|
'AModuleHelperStrategy',
|
|
563
618
|
'AModuleManagerStrategy',
|
|
619
|
+
# Enhanced Strategy Interfaces for Runtime Swapping
|
|
620
|
+
'ALoadStrategy',
|
|
564
621
|
]
|
|
565
622
|
|
exonware/xwlazy/module/facade.py
CHANGED
|
@@ -9,7 +9,7 @@ Uses strategy pattern for caching, helper, and manager strategies.
|
|
|
9
9
|
import sys
|
|
10
10
|
import importlib
|
|
11
11
|
import importlib.util
|
|
12
|
-
from typing import Optional
|
|
12
|
+
from typing import Optional
|
|
13
13
|
from types import ModuleType
|
|
14
14
|
|
|
15
15
|
from .base import AModuleHelper, APackageHelper
|