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

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