exonware-xwlazy 0.1.0.11__py3-none-any.whl → 0.1.0.19__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 +22 -0
- exonware/xwlazy/__init__.py +0 -0
- exonware/xwlazy/common/__init__.py +47 -0
- exonware/xwlazy/common/base.py +58 -0
- exonware/xwlazy/common/cache.py +506 -0
- exonware/xwlazy/common/logger.py +268 -0
- exonware/xwlazy/common/services/__init__.py +72 -0
- exonware/xwlazy/common/services/dependency_mapper.py +234 -0
- exonware/xwlazy/common/services/install_async_utils.py +169 -0
- exonware/xwlazy/common/services/install_cache_utils.py +257 -0
- exonware/xwlazy/common/services/keyword_detection.py +292 -0
- exonware/xwlazy/common/services/spec_cache.py +173 -0
- exonware/xwlazy/common/strategies/__init__.py +28 -0
- exonware/xwlazy/common/strategies/caching_dict.py +45 -0
- exonware/xwlazy/common/strategies/caching_installation.py +89 -0
- exonware/xwlazy/common/strategies/caching_lfu.py +67 -0
- exonware/xwlazy/common/strategies/caching_lru.py +64 -0
- exonware/xwlazy/common/strategies/caching_multitier.py +60 -0
- exonware/xwlazy/common/strategies/caching_ttl.py +60 -0
- {xwlazy/lazy → exonware/xwlazy}/config.py +52 -20
- exonware/xwlazy/contracts.py +1410 -0
- exonware/xwlazy/defs.py +397 -0
- xwlazy/lazy/lazy_errors.py → exonware/xwlazy/errors.py +21 -8
- exonware/xwlazy/facade.py +1049 -0
- exonware/xwlazy/module/__init__.py +18 -0
- exonware/xwlazy/module/base.py +569 -0
- exonware/xwlazy/module/data.py +17 -0
- exonware/xwlazy/module/facade.py +247 -0
- exonware/xwlazy/module/importer_engine.py +2161 -0
- exonware/xwlazy/module/strategies/__init__.py +22 -0
- exonware/xwlazy/module/strategies/module_helper_lazy.py +94 -0
- exonware/xwlazy/module/strategies/module_helper_simple.py +66 -0
- exonware/xwlazy/module/strategies/module_manager_advanced.py +112 -0
- exonware/xwlazy/module/strategies/module_manager_simple.py +96 -0
- exonware/xwlazy/package/__init__.py +18 -0
- exonware/xwlazy/package/base.py +807 -0
- xwlazy/lazy/host_conf.py → exonware/xwlazy/package/conf.py +62 -10
- exonware/xwlazy/package/data.py +17 -0
- exonware/xwlazy/package/facade.py +481 -0
- exonware/xwlazy/package/services/__init__.py +84 -0
- exonware/xwlazy/package/services/async_install_handle.py +89 -0
- exonware/xwlazy/package/services/config_manager.py +246 -0
- exonware/xwlazy/package/services/discovery.py +374 -0
- {xwlazy/lazy → exonware/xwlazy/package/services}/host_packages.py +43 -16
- exonware/xwlazy/package/services/install_async.py +278 -0
- exonware/xwlazy/package/services/install_cache.py +146 -0
- exonware/xwlazy/package/services/install_interactive.py +60 -0
- exonware/xwlazy/package/services/install_policy.py +158 -0
- exonware/xwlazy/package/services/install_registry.py +56 -0
- exonware/xwlazy/package/services/install_result.py +17 -0
- exonware/xwlazy/package/services/install_sbom.py +154 -0
- exonware/xwlazy/package/services/install_utils.py +83 -0
- exonware/xwlazy/package/services/installer_engine.py +408 -0
- exonware/xwlazy/package/services/lazy_installer.py +720 -0
- {xwlazy/lazy → exonware/xwlazy/package/services}/manifest.py +42 -25
- exonware/xwlazy/package/services/strategy_registry.py +188 -0
- exonware/xwlazy/package/strategies/__init__.py +57 -0
- exonware/xwlazy/package/strategies/package_discovery_file.py +130 -0
- exonware/xwlazy/package/strategies/package_discovery_hybrid.py +85 -0
- exonware/xwlazy/package/strategies/package_discovery_manifest.py +102 -0
- exonware/xwlazy/package/strategies/package_execution_async.py +114 -0
- exonware/xwlazy/package/strategies/package_execution_cached.py +91 -0
- exonware/xwlazy/package/strategies/package_execution_pip.py +100 -0
- exonware/xwlazy/package/strategies/package_execution_wheel.py +107 -0
- exonware/xwlazy/package/strategies/package_mapping_discovery_first.py +101 -0
- exonware/xwlazy/package/strategies/package_mapping_hybrid.py +106 -0
- exonware/xwlazy/package/strategies/package_mapping_manifest_first.py +101 -0
- exonware/xwlazy/package/strategies/package_policy_allow_list.py +58 -0
- exonware/xwlazy/package/strategies/package_policy_deny_list.py +58 -0
- exonware/xwlazy/package/strategies/package_policy_permissive.py +47 -0
- exonware/xwlazy/package/strategies/package_timing_clean.py +68 -0
- exonware/xwlazy/package/strategies/package_timing_full.py +67 -0
- exonware/xwlazy/package/strategies/package_timing_smart.py +69 -0
- exonware/xwlazy/package/strategies/package_timing_temporary.py +67 -0
- exonware/xwlazy/runtime/__init__.py +18 -0
- exonware/xwlazy/runtime/adaptive_learner.py +131 -0
- exonware/xwlazy/runtime/base.py +276 -0
- exonware/xwlazy/runtime/facade.py +95 -0
- exonware/xwlazy/runtime/intelligent_selector.py +173 -0
- exonware/xwlazy/runtime/metrics.py +64 -0
- exonware/xwlazy/runtime/performance.py +39 -0
- exonware/xwlazy/version.py +2 -2
- {exonware_xwlazy-0.1.0.11.dist-info → exonware_xwlazy-0.1.0.19.dist-info}/METADATA +86 -10
- exonware_xwlazy-0.1.0.19.dist-info/RECORD +87 -0
- exonware_xwlazy-0.1.0.11.dist-info/RECORD +0 -20
- xwlazy/__init__.py +0 -34
- xwlazy/lazy/__init__.py +0 -301
- xwlazy/lazy/bootstrap.py +0 -106
- xwlazy/lazy/lazy_base.py +0 -465
- xwlazy/lazy/lazy_contracts.py +0 -290
- xwlazy/lazy/lazy_core.py +0 -3727
- xwlazy/lazy/logging_utils.py +0 -194
- xwlazy/version.py +0 -77
- /xwlazy/lazy/lazy_state.py → /exonware/xwlazy/common/services/state_manager.py +0 -0
- {exonware_xwlazy-0.1.0.11.dist-info → exonware_xwlazy-0.1.0.19.dist-info}/WHEEL +0 -0
- {exonware_xwlazy-0.1.0.11.dist-info → exonware_xwlazy-0.1.0.19.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module Operations Module
|
|
3
|
+
|
|
4
|
+
This module provides concrete implementations for module operations.
|
|
5
|
+
Main facade: XWModuleHelper extends AModuleHelper
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# Lazy imports to avoid circular dependencies
|
|
9
|
+
def __getattr__(name: str):
|
|
10
|
+
if name == 'XWModuleHelper':
|
|
11
|
+
from .facade import XWModuleHelper
|
|
12
|
+
return XWModuleHelper
|
|
13
|
+
if name == 'XWModule': # Backward compatibility alias
|
|
14
|
+
from .facade import XWModuleHelper
|
|
15
|
+
return XWModuleHelper
|
|
16
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
17
|
+
|
|
18
|
+
__all__ = ['XWModuleHelper', 'XWModule'] # XWModule is backward compatibility alias
|
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
"""
|
|
2
|
+
#exonware/xwlazy/src/exonware/xwlazy/module/base.py
|
|
3
|
+
|
|
4
|
+
Company: eXonware.com
|
|
5
|
+
Author: Eng. Muhammad AlShehri
|
|
6
|
+
Email: connect@exonware.com
|
|
7
|
+
Version: 0.1.0.19
|
|
8
|
+
Generation Date: 10-Oct-2025
|
|
9
|
+
|
|
10
|
+
Abstract Base Class for Module Operations
|
|
11
|
+
|
|
12
|
+
This module defines the abstract base class for module operations.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import sys
|
|
16
|
+
import threading
|
|
17
|
+
from abc import ABC, abstractmethod
|
|
18
|
+
from typing import Dict, List, Optional, Any, Tuple
|
|
19
|
+
from types import ModuleType
|
|
20
|
+
|
|
21
|
+
from ..contracts import (
|
|
22
|
+
IModuleHelper,
|
|
23
|
+
IModuleHelperStrategy,
|
|
24
|
+
IModuleManagerStrategy,
|
|
25
|
+
)
|
|
26
|
+
from ..package.base import APackageHelper
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# =============================================================================
|
|
30
|
+
# ABSTRACT MODULE (Unified - Merges AImportHook + ALazyLoader + AModuleHelper)
|
|
31
|
+
# =============================================================================
|
|
32
|
+
|
|
33
|
+
class AModuleHelper(IModuleHelper, ABC):
|
|
34
|
+
"""
|
|
35
|
+
Unified abstract base for module operations.
|
|
36
|
+
|
|
37
|
+
Merges functionality from AImportHook, ALazyLoader, and AModuleHelper.
|
|
38
|
+
Provides comprehensive module operations: installation, hooks, finding, interception, loading, importing, registry, and bytecode caching.
|
|
39
|
+
|
|
40
|
+
This abstract class combines:
|
|
41
|
+
- Module installation (installing and importing modules)
|
|
42
|
+
- Import hooks (intercepting import failures)
|
|
43
|
+
- Meta path finding (sys.meta_path hook for lazy installation)
|
|
44
|
+
- Import interception (high-level import interception)
|
|
45
|
+
- Lazy loading (deferred module loading)
|
|
46
|
+
- Lazy importing (lazy module loading with strategies)
|
|
47
|
+
- Watched registry (tracking watched module prefixes)
|
|
48
|
+
- Bytecode caching (caching compiled Python bytecode)
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
__slots__ = (
|
|
52
|
+
# From AImportHook
|
|
53
|
+
'_package_name', '_enabled',
|
|
54
|
+
# From ALazyLoader
|
|
55
|
+
'_module_path', '_cached_module', '_loading',
|
|
56
|
+
# From AModuleHelper
|
|
57
|
+
'_package_helper', '_dependency_mapper',
|
|
58
|
+
# Common
|
|
59
|
+
'_lock'
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def __init__(self, package_name: str = 'default', package_helper: Optional[APackageHelper] = None):
|
|
63
|
+
"""
|
|
64
|
+
Initialize unified module operations.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
package_name: Package this instance is for
|
|
68
|
+
package_helper: Optional package helper instance. If None, creates default.
|
|
69
|
+
"""
|
|
70
|
+
# From AImportHook
|
|
71
|
+
self._package_name = package_name
|
|
72
|
+
self._enabled = True
|
|
73
|
+
|
|
74
|
+
# From ALazyLoader
|
|
75
|
+
self._module_path: Optional[str] = None
|
|
76
|
+
self._cached_module: Optional[ModuleType] = None
|
|
77
|
+
self._loading = False
|
|
78
|
+
|
|
79
|
+
# From AModuleHelper
|
|
80
|
+
self._package_helper = package_helper
|
|
81
|
+
self._dependency_mapper = None # Lazy init to avoid circular imports
|
|
82
|
+
|
|
83
|
+
# Common
|
|
84
|
+
self._lock = threading.RLock()
|
|
85
|
+
|
|
86
|
+
# ========================================================================
|
|
87
|
+
# Import Hook Methods (from AImportHook)
|
|
88
|
+
# ========================================================================
|
|
89
|
+
|
|
90
|
+
def enable(self) -> None:
|
|
91
|
+
"""Enable the import hook."""
|
|
92
|
+
self._enabled = True
|
|
93
|
+
|
|
94
|
+
def disable(self) -> None:
|
|
95
|
+
"""Disable the import hook."""
|
|
96
|
+
self._enabled = False
|
|
97
|
+
|
|
98
|
+
def is_enabled(self) -> bool:
|
|
99
|
+
"""Check if hook is enabled."""
|
|
100
|
+
return self._enabled
|
|
101
|
+
|
|
102
|
+
@abstractmethod
|
|
103
|
+
def install_hook(self) -> None:
|
|
104
|
+
"""Install the import hook into sys.meta_path (abstract method)."""
|
|
105
|
+
pass
|
|
106
|
+
|
|
107
|
+
@abstractmethod
|
|
108
|
+
def uninstall_hook(self) -> None:
|
|
109
|
+
"""Uninstall the import hook from sys.meta_path (abstract method)."""
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
@abstractmethod
|
|
113
|
+
def handle_import_error(self, module_name: str) -> Optional[Any]:
|
|
114
|
+
"""
|
|
115
|
+
Handle ImportError by attempting to install and re-import (abstract method).
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
module_name: Name of module that failed to import
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Imported module if successful, None otherwise
|
|
122
|
+
"""
|
|
123
|
+
pass
|
|
124
|
+
|
|
125
|
+
# ========================================================================
|
|
126
|
+
# Lazy Loader Methods (from ALazyLoader)
|
|
127
|
+
# ========================================================================
|
|
128
|
+
|
|
129
|
+
@abstractmethod
|
|
130
|
+
def load_module(self, module_path: str) -> ModuleType:
|
|
131
|
+
"""
|
|
132
|
+
Load a module lazily (abstract method).
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
module_path: Full module path to load
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Loaded module
|
|
139
|
+
"""
|
|
140
|
+
pass
|
|
141
|
+
|
|
142
|
+
def is_loaded(self, module_path: str = None) -> bool:
|
|
143
|
+
"""
|
|
144
|
+
Check if module is already loaded.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
module_path: Module path to check (uses self._module_path if None)
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
True if loaded, False otherwise
|
|
151
|
+
"""
|
|
152
|
+
return self._cached_module is not None
|
|
153
|
+
|
|
154
|
+
@abstractmethod
|
|
155
|
+
def unload_module(self, module_path: str) -> None:
|
|
156
|
+
"""
|
|
157
|
+
Unload a module from cache (abstract method).
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
module_path: Module path to unload
|
|
161
|
+
"""
|
|
162
|
+
pass
|
|
163
|
+
|
|
164
|
+
# ========================================================================
|
|
165
|
+
# Module Helper Methods (from AModuleHelper)
|
|
166
|
+
# ========================================================================
|
|
167
|
+
|
|
168
|
+
def _get_package_helper(self) -> APackageHelper:
|
|
169
|
+
"""Get package helper instance (creates if needed)."""
|
|
170
|
+
if self._package_helper is None:
|
|
171
|
+
self._package_helper = self._create_package_helper()
|
|
172
|
+
return self._package_helper
|
|
173
|
+
|
|
174
|
+
def _get_dependency_mapper(self):
|
|
175
|
+
"""Get dependency mapper instance (lazy init)."""
|
|
176
|
+
if self._dependency_mapper is None:
|
|
177
|
+
from ...common.services.dependency_mapper import DependencyMapper
|
|
178
|
+
self._dependency_mapper = DependencyMapper()
|
|
179
|
+
return self._dependency_mapper
|
|
180
|
+
|
|
181
|
+
def to_package(self, module_name: str) -> Optional[str]:
|
|
182
|
+
"""
|
|
183
|
+
Map module name to package name.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
module_name: Module name (e.g., 'bson', 'msgpack')
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
Package name (e.g., 'pymongo', 'msgpack') or None if not found
|
|
190
|
+
"""
|
|
191
|
+
mapper = self._get_dependency_mapper()
|
|
192
|
+
return mapper.get_package_name(module_name)
|
|
193
|
+
|
|
194
|
+
def installed(self, module_name: str) -> bool:
|
|
195
|
+
"""
|
|
196
|
+
Check if a module is installed.
|
|
197
|
+
|
|
198
|
+
Uses cache first to avoid expensive operations.
|
|
199
|
+
Maps module to package and checks if package is installed.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
module_name: Module name to check (e.g., 'bson')
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
True if module is installed, False otherwise
|
|
206
|
+
"""
|
|
207
|
+
# Map module name to package name
|
|
208
|
+
package_name = self.to_package(module_name)
|
|
209
|
+
if package_name:
|
|
210
|
+
# Check if the package is installed
|
|
211
|
+
return self._get_package_helper().installed(package_name)
|
|
212
|
+
|
|
213
|
+
# If no mapping found, check if the module itself is importable (abstract method)
|
|
214
|
+
return self._check_module_importability(module_name)
|
|
215
|
+
|
|
216
|
+
def uninstalled(self, module_name: str) -> bool:
|
|
217
|
+
"""
|
|
218
|
+
Check if a module is uninstalled.
|
|
219
|
+
|
|
220
|
+
Uses cache first to avoid expensive operations.
|
|
221
|
+
Maps module to package and checks if package is uninstalled.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
module_name: Module name to check (e.g., 'bson')
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
True if module is uninstalled, False otherwise
|
|
228
|
+
"""
|
|
229
|
+
return not self.installed(module_name)
|
|
230
|
+
|
|
231
|
+
def install(self, *module_names: str) -> None:
|
|
232
|
+
"""
|
|
233
|
+
Install one or more modules by mapping to packages first.
|
|
234
|
+
|
|
235
|
+
First deduplicates modules, then maps to packages, then installs packages.
|
|
236
|
+
Skips modules that are already installed (using cache).
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
*module_names: One or more module names to install (e.g., 'bson', 'msgpack')
|
|
240
|
+
|
|
241
|
+
Raises:
|
|
242
|
+
subprocess.CalledProcessError: If installation fails
|
|
243
|
+
"""
|
|
244
|
+
if not module_names:
|
|
245
|
+
return
|
|
246
|
+
|
|
247
|
+
# First deduplicate modules (preserves order)
|
|
248
|
+
unique_modules = list(dict.fromkeys(module_names))
|
|
249
|
+
|
|
250
|
+
# Map all module names to package names
|
|
251
|
+
package_names = []
|
|
252
|
+
for name in unique_modules:
|
|
253
|
+
package_name = self.to_package(name)
|
|
254
|
+
if package_name:
|
|
255
|
+
package_names.append(package_name)
|
|
256
|
+
else:
|
|
257
|
+
# If no mapping found, use the name as-is
|
|
258
|
+
package_names.append(name)
|
|
259
|
+
|
|
260
|
+
# Install the packages (package_helper handles deduplication and caching)
|
|
261
|
+
self._get_package_helper().install(*package_names)
|
|
262
|
+
|
|
263
|
+
def uninstall(self, *module_names: str) -> None:
|
|
264
|
+
"""
|
|
265
|
+
Uninstall one or more modules by mapping to packages first.
|
|
266
|
+
|
|
267
|
+
First deduplicates modules, then maps to packages, then uninstalls packages.
|
|
268
|
+
Skips modules that are already uninstalled (using cache).
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
*module_names: One or more module names to uninstall (e.g., 'bson', 'msgpack')
|
|
272
|
+
|
|
273
|
+
Raises:
|
|
274
|
+
subprocess.CalledProcessError: If uninstallation fails
|
|
275
|
+
"""
|
|
276
|
+
if not module_names:
|
|
277
|
+
return
|
|
278
|
+
|
|
279
|
+
# First deduplicate modules (preserves order)
|
|
280
|
+
unique_modules = list(dict.fromkeys(module_names))
|
|
281
|
+
|
|
282
|
+
# Map all module names to package names
|
|
283
|
+
package_names = []
|
|
284
|
+
for name in unique_modules:
|
|
285
|
+
package_name = self.to_package(name)
|
|
286
|
+
if package_name:
|
|
287
|
+
package_names.append(package_name)
|
|
288
|
+
else:
|
|
289
|
+
# If no mapping found, use the name as-is
|
|
290
|
+
package_names.append(name)
|
|
291
|
+
|
|
292
|
+
# Uninstall the packages (package_helper handles deduplication and caching)
|
|
293
|
+
self._get_package_helper().uninstall(*package_names)
|
|
294
|
+
|
|
295
|
+
def load(self, *module_names: str) -> List[ModuleType]:
|
|
296
|
+
"""
|
|
297
|
+
Load one or more modules into memory.
|
|
298
|
+
|
|
299
|
+
Imports modules and returns them. Uses lazy loading if enabled.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
*module_names: One or more module names to load (e.g., 'bson', 'msgpack')
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
List of loaded module objects
|
|
306
|
+
|
|
307
|
+
Raises:
|
|
308
|
+
ImportError: If module cannot be loaded
|
|
309
|
+
"""
|
|
310
|
+
loaded_modules = []
|
|
311
|
+
for name in module_names:
|
|
312
|
+
try:
|
|
313
|
+
module = self._import_module(name)
|
|
314
|
+
loaded_modules.append(module)
|
|
315
|
+
except ImportError as e:
|
|
316
|
+
# Try to install if not found
|
|
317
|
+
if not self.installed(name):
|
|
318
|
+
self.install(name)
|
|
319
|
+
# Try importing again
|
|
320
|
+
module = self._import_module(name)
|
|
321
|
+
loaded_modules.append(module)
|
|
322
|
+
else:
|
|
323
|
+
raise
|
|
324
|
+
return loaded_modules
|
|
325
|
+
|
|
326
|
+
def unload(self, *module_names: str) -> None:
|
|
327
|
+
"""
|
|
328
|
+
Unload one or more modules from memory.
|
|
329
|
+
|
|
330
|
+
Removes modules from sys.modules and clears caches.
|
|
331
|
+
Useful for freeing memory or forcing reload.
|
|
332
|
+
|
|
333
|
+
Args:
|
|
334
|
+
*module_names: One or more module names to unload (e.g., 'bson', 'msgpack')
|
|
335
|
+
"""
|
|
336
|
+
with self._lock:
|
|
337
|
+
for name in module_names:
|
|
338
|
+
# Remove from sys.modules
|
|
339
|
+
if name in sys.modules:
|
|
340
|
+
del sys.modules[name]
|
|
341
|
+
|
|
342
|
+
# Remove submodules too (e.g., 'bson.codec' if 'bson' is unloaded)
|
|
343
|
+
to_remove = [mod for mod in sys.modules.keys() if mod.startswith(name + '.')]
|
|
344
|
+
for mod in to_remove:
|
|
345
|
+
del sys.modules[mod]
|
|
346
|
+
|
|
347
|
+
# Clear import caches (abstract method)
|
|
348
|
+
self._invalidate_import_caches()
|
|
349
|
+
|
|
350
|
+
@abstractmethod
|
|
351
|
+
def _check_module_importability(self, module_name: str) -> bool:
|
|
352
|
+
"""
|
|
353
|
+
Check if module is importable (abstract method).
|
|
354
|
+
|
|
355
|
+
Concrete implementations should use importlib.util.find_spec or similar.
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
module_name: Module name to check
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
True if importable, False otherwise
|
|
362
|
+
"""
|
|
363
|
+
pass
|
|
364
|
+
|
|
365
|
+
@abstractmethod
|
|
366
|
+
def _import_module(self, module_name: str) -> ModuleType:
|
|
367
|
+
"""
|
|
368
|
+
Import a module (abstract method).
|
|
369
|
+
|
|
370
|
+
Concrete implementations should use importlib.import_module.
|
|
371
|
+
|
|
372
|
+
Args:
|
|
373
|
+
module_name: Module name to import
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
Imported module
|
|
377
|
+
|
|
378
|
+
Raises:
|
|
379
|
+
ImportError: If module cannot be imported
|
|
380
|
+
"""
|
|
381
|
+
pass
|
|
382
|
+
|
|
383
|
+
@abstractmethod
|
|
384
|
+
def _invalidate_import_caches(self) -> None:
|
|
385
|
+
"""
|
|
386
|
+
Invalidate import caches (abstract method).
|
|
387
|
+
|
|
388
|
+
Concrete implementations should use importlib.invalidate_caches().
|
|
389
|
+
"""
|
|
390
|
+
pass
|
|
391
|
+
|
|
392
|
+
@abstractmethod
|
|
393
|
+
def _create_package_helper(self) -> APackageHelper:
|
|
394
|
+
"""
|
|
395
|
+
Create a package helper instance (abstract method).
|
|
396
|
+
|
|
397
|
+
Returns:
|
|
398
|
+
APackageHelper instance
|
|
399
|
+
"""
|
|
400
|
+
pass
|
|
401
|
+
|
|
402
|
+
# ========================================================================
|
|
403
|
+
# IModuleHelper Interface Methods (stubs - to be implemented by subclasses)
|
|
404
|
+
# ========================================================================
|
|
405
|
+
|
|
406
|
+
# Note: Many methods from IModuleHelper are already implemented above.
|
|
407
|
+
# The following are stubs that need concrete implementations:
|
|
408
|
+
|
|
409
|
+
def install_and_import(self, module_name: str, package_name: Optional[str] = None) -> Tuple[Optional[ModuleType], bool]:
|
|
410
|
+
"""Install package and import module (from IModuleInstaller)."""
|
|
411
|
+
raise NotImplementedError("Subclasses must implement install_and_import")
|
|
412
|
+
|
|
413
|
+
def is_package_installed(self, package_name: str) -> bool:
|
|
414
|
+
"""Check if package is installed (from IModuleInstaller)."""
|
|
415
|
+
raise NotImplementedError("Subclasses must implement is_package_installed")
|
|
416
|
+
|
|
417
|
+
def mark_installed(self, package_name: str, version: Optional[str] = None) -> None:
|
|
418
|
+
"""Mark package as installed in persistent cache (from IModuleInstaller)."""
|
|
419
|
+
raise NotImplementedError("Subclasses must implement mark_installed")
|
|
420
|
+
|
|
421
|
+
def is_hook_installed(self) -> bool:
|
|
422
|
+
"""Check if hook is installed (from IImportHook)."""
|
|
423
|
+
raise NotImplementedError("Subclasses must implement is_hook_installed")
|
|
424
|
+
|
|
425
|
+
def find_spec(self, fullname: str, path: Optional[str] = None, target=None) -> Optional[Any]:
|
|
426
|
+
"""Find module spec (from IMetaPathFinder)."""
|
|
427
|
+
raise NotImplementedError("Subclasses must implement find_spec")
|
|
428
|
+
|
|
429
|
+
def should_intercept(self, fullname: str) -> bool:
|
|
430
|
+
"""Determine if a module should be intercepted (from IMetaPathFinder)."""
|
|
431
|
+
raise NotImplementedError("Subclasses must implement should_intercept")
|
|
432
|
+
|
|
433
|
+
def is_module_installed(self, fullname: str) -> bool:
|
|
434
|
+
"""Check if module is already installed (from IMetaPathFinder)."""
|
|
435
|
+
raise NotImplementedError("Subclasses must implement is_module_installed")
|
|
436
|
+
|
|
437
|
+
def intercept_missing_import(self, module_name: str) -> Optional[ModuleType]:
|
|
438
|
+
"""Intercept a missing import (from IImportInterceptor)."""
|
|
439
|
+
raise NotImplementedError("Subclasses must implement intercept_missing_import")
|
|
440
|
+
|
|
441
|
+
def should_intercept_module(self, module_name: str) -> bool:
|
|
442
|
+
"""Determine if a module should be intercepted (from IImportInterceptor)."""
|
|
443
|
+
raise NotImplementedError("Subclasses must implement should_intercept_module")
|
|
444
|
+
|
|
445
|
+
def prevent_recursion(self, module_name: str) -> bool:
|
|
446
|
+
"""Check if we should prevent recursion (from IImportInterceptor)."""
|
|
447
|
+
raise NotImplementedError("Subclasses must implement prevent_recursion")
|
|
448
|
+
|
|
449
|
+
def import_module(self, module_name: str, package_name: Optional[str] = None) -> Any:
|
|
450
|
+
"""Import a module with lazy loading (from ILazyImporter)."""
|
|
451
|
+
raise NotImplementedError("Subclasses must implement import_module")
|
|
452
|
+
|
|
453
|
+
def enable_lazy_loading(self, load_mode: Any) -> None:
|
|
454
|
+
"""Enable lazy loading with a mode (from ILazyImporter)."""
|
|
455
|
+
raise NotImplementedError("Subclasses must implement enable_lazy_loading")
|
|
456
|
+
|
|
457
|
+
def disable_lazy_loading(self) -> None:
|
|
458
|
+
"""Disable lazy loading (from ILazyImporter)."""
|
|
459
|
+
raise NotImplementedError("Subclasses must implement disable_lazy_loading")
|
|
460
|
+
|
|
461
|
+
def is_lazy_loading_enabled(self) -> bool:
|
|
462
|
+
"""Check if lazy loading is enabled (from ILazyImporter)."""
|
|
463
|
+
raise NotImplementedError("Subclasses must implement is_lazy_loading_enabled")
|
|
464
|
+
|
|
465
|
+
def has_root(self, root_name: str) -> bool:
|
|
466
|
+
"""Check if a root module name is being watched (from IWatchedRegistry)."""
|
|
467
|
+
raise NotImplementedError("Subclasses must implement has_root")
|
|
468
|
+
|
|
469
|
+
def get_matching_prefixes(self, fullname: str) -> Tuple[str, ...]:
|
|
470
|
+
"""Get all watched prefixes that match a module name (from IWatchedRegistry)."""
|
|
471
|
+
raise NotImplementedError("Subclasses must implement get_matching_prefixes")
|
|
472
|
+
|
|
473
|
+
def is_prefix_owned_by(self, prefix: str, package_name: str) -> bool:
|
|
474
|
+
"""Check if a prefix is owned by a package (from IWatchedRegistry)."""
|
|
475
|
+
raise NotImplementedError("Subclasses must implement is_prefix_owned_by")
|
|
476
|
+
|
|
477
|
+
def is_watched_registry_empty(self) -> bool:
|
|
478
|
+
"""Check if registry is empty (from IWatchedRegistry)."""
|
|
479
|
+
raise NotImplementedError("Subclasses must implement is_watched_registry_empty")
|
|
480
|
+
|
|
481
|
+
def get_bytecode(self, module_path: str, source_code: str) -> Optional[bytes]:
|
|
482
|
+
"""Get cached bytecode for module (from IBytecodeCache)."""
|
|
483
|
+
raise NotImplementedError("Subclasses must implement get_bytecode")
|
|
484
|
+
|
|
485
|
+
def cache_bytecode(self, module_path: str, source_code: str, bytecode: bytes) -> None:
|
|
486
|
+
"""Cache bytecode for module (from IBytecodeCache)."""
|
|
487
|
+
raise NotImplementedError("Subclasses must implement cache_bytecode")
|
|
488
|
+
|
|
489
|
+
def clear_bytecode_cache(self) -> None:
|
|
490
|
+
"""Clear bytecode cache (from IBytecodeCache)."""
|
|
491
|
+
raise NotImplementedError("Subclasses must implement clear_bytecode_cache")
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
# =============================================================================
|
|
495
|
+
# ABSTRACT MODULE HELPER STRATEGY
|
|
496
|
+
# =============================================================================
|
|
497
|
+
|
|
498
|
+
class AModuleHelperStrategy(IModuleHelperStrategy, ABC):
|
|
499
|
+
"""
|
|
500
|
+
Abstract base class for module helper strategies.
|
|
501
|
+
|
|
502
|
+
Operations on a single module (loading, unloading, checking).
|
|
503
|
+
All module helper strategies must extend this class.
|
|
504
|
+
"""
|
|
505
|
+
|
|
506
|
+
@abstractmethod
|
|
507
|
+
def load(self, module_path: str, package_helper: Any) -> ModuleType:
|
|
508
|
+
"""Load the module."""
|
|
509
|
+
...
|
|
510
|
+
|
|
511
|
+
@abstractmethod
|
|
512
|
+
def unload(self, module_path: str) -> None:
|
|
513
|
+
"""Unload the module."""
|
|
514
|
+
...
|
|
515
|
+
|
|
516
|
+
@abstractmethod
|
|
517
|
+
def check_importability(self, path: str) -> bool:
|
|
518
|
+
"""Check if module is importable."""
|
|
519
|
+
...
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
# =============================================================================
|
|
523
|
+
# ABSTRACT MODULE MANAGER STRATEGY
|
|
524
|
+
# =============================================================================
|
|
525
|
+
|
|
526
|
+
class AModuleManagerStrategy(IModuleManagerStrategy, ABC):
|
|
527
|
+
"""
|
|
528
|
+
Abstract base class for module manager strategies.
|
|
529
|
+
|
|
530
|
+
Orchestrates multiple modules (loading, hooks, error handling).
|
|
531
|
+
All module manager strategies must extend this class.
|
|
532
|
+
"""
|
|
533
|
+
|
|
534
|
+
@abstractmethod
|
|
535
|
+
def load_module(self, module_path: str) -> ModuleType:
|
|
536
|
+
"""Load a module."""
|
|
537
|
+
...
|
|
538
|
+
|
|
539
|
+
@abstractmethod
|
|
540
|
+
def unload_module(self, module_path: str) -> None:
|
|
541
|
+
"""Unload a module."""
|
|
542
|
+
...
|
|
543
|
+
|
|
544
|
+
@abstractmethod
|
|
545
|
+
def install_hook(self) -> None:
|
|
546
|
+
"""Install import hook."""
|
|
547
|
+
...
|
|
548
|
+
|
|
549
|
+
@abstractmethod
|
|
550
|
+
def uninstall_hook(self) -> None:
|
|
551
|
+
"""Uninstall import hook."""
|
|
552
|
+
...
|
|
553
|
+
|
|
554
|
+
@abstractmethod
|
|
555
|
+
def handle_import_error(self, module_name: str) -> Optional[ModuleType]:
|
|
556
|
+
"""Handle import error."""
|
|
557
|
+
...
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
# =============================================================================
|
|
561
|
+
# EXPORT ALL
|
|
562
|
+
# =============================================================================
|
|
563
|
+
|
|
564
|
+
__all__ = [
|
|
565
|
+
'AModuleHelper',
|
|
566
|
+
'AModuleHelperStrategy',
|
|
567
|
+
'AModuleManagerStrategy',
|
|
568
|
+
]
|
|
569
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module Data - Immutable data structure for modules.
|
|
3
|
+
|
|
4
|
+
Company: eXonware.com
|
|
5
|
+
Author: Eng. Muhammad AlShehri
|
|
6
|
+
Email: connect@exonware.com
|
|
7
|
+
Version: 0.1.0.19
|
|
8
|
+
Generation Date: 15-Nov-2025
|
|
9
|
+
|
|
10
|
+
Re-export ModuleData from defs.py for backward compatibility.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
# Re-export from defs.py
|
|
14
|
+
from ..defs import ModuleData
|
|
15
|
+
|
|
16
|
+
__all__ = ['ModuleData']
|
|
17
|
+
|