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
xwlazy/lazy/bootstrap.py DELETED
@@ -1,106 +0,0 @@
1
- #exonware/xwlazy/src/exonware/xwlazy/lazy/bootstrap.py
2
- """
3
- Early bootstrap utilities for lazily installing import hooks.
4
- """
5
-
6
- from __future__ import annotations
7
-
8
- import atexit
9
- import logging
10
- import os
11
- import sys
12
- from typing import Optional
13
-
14
-
15
- def _env_enabled(env_value: Optional[str]) -> Optional[bool]:
16
- if not env_value:
17
- return None
18
- normalized = env_value.strip().lower()
19
- if normalized in ('true', '1', 'yes', 'on'):
20
- return True
21
- if normalized in ('false', '0', 'no', 'off'):
22
- return False
23
- return None
24
-
25
-
26
- def bootstrap_lazy_mode(package_name: str) -> None:
27
- """
28
- Detect whether lazy mode should be enabled for ``package_name`` and bootstrap hooks.
29
- """
30
- package_name = package_name.lower()
31
- env_value = os.environ.get(f"{package_name.upper()}_LAZY_INSTALL")
32
- env_enabled = _env_enabled(env_value)
33
- enabled = env_enabled
34
-
35
- if enabled is None:
36
- try:
37
- from xwlazy.lazy.lazy_core import _detect_lazy_installation
38
- enabled = _detect_lazy_installation(package_name)
39
- except Exception as exc: # pragma: no cover
40
- logging.getLogger("xwlazy.lazy.bootstrap").debug(
41
- "Lazy detection failed: %s", exc, exc_info=True
42
- )
43
- enabled = False
44
-
45
- if not enabled:
46
- return
47
-
48
- try:
49
- from xwlazy.lazy.lazy_core import config_package_lazy_install_enabled
50
-
51
- config_package_lazy_install_enabled(
52
- package_name,
53
- enabled=True,
54
- install_hook=True,
55
- )
56
- except Exception as exc: # pragma: no cover
57
- logging.getLogger("xwlazy.lazy.bootstrap").debug(
58
- "Lazy bootstrap failed for %s: %s", package_name, exc, exc_info=True
59
- )
60
-
61
-
62
- def bootstrap_lazy_mode_deferred(package_name: str) -> None:
63
- """
64
- Schedule lazy mode bootstrap to run AFTER the calling package finishes importing.
65
-
66
- This avoids hook interference with the package's own imports (e.g., requests/certifi in xwsystem).
67
- Uses a post-import hook to detect when the package module is fully loaded.
68
- """
69
- package_name_lower = package_name.lower()
70
- package_module_name = f"exonware.{package_name_lower}"
71
-
72
- # Store original __import__ to restore later
73
- original_import = __builtins__.__import__ if hasattr(__builtins__, '__import__') else __import__
74
-
75
- def _import_hook(name, *args, **kwargs):
76
- # Call original import first
77
- result = original_import(name, *args, **kwargs)
78
-
79
- # Check if the target package just finished importing
80
- if name == package_module_name or name.startswith(f"{package_module_name}."):
81
- # Check if package root is now fully in sys.modules
82
- if package_module_name in sys.modules:
83
- # Package is loaded, install hook on next event loop iteration
84
- import threading
85
- def _install_hook():
86
- # Restore original import
87
- if hasattr(__builtins__, '__import__'):
88
- __builtins__.__import__ = original_import
89
- else:
90
- import builtins
91
- builtins.__import__ = original_import
92
- # Now install the lazy hook
93
- bootstrap_lazy_mode(package_name_lower)
94
-
95
- # Schedule for immediate execution after current import completes
96
- threading.Timer(0.0, _install_hook).start()
97
-
98
- return result
99
-
100
- # Install temporary import hook
101
- if hasattr(__builtins__, '__import__'):
102
- __builtins__.__import__ = _import_hook
103
- else:
104
- import builtins
105
- builtins.__import__ = _import_hook
106
-
xwlazy/lazy/lazy_base.py DELETED
@@ -1,465 +0,0 @@
1
- """
2
- #exonware/xwsystem/src/exonware/xwsystem/utils/lazy_package/lazy_base.py
3
-
4
- Company: eXonware.com
5
- Author: Eng. Muhammad AlShehri
6
- Email: connect@exonware.com
7
- Version: 0.1.0.16
8
- Generation Date: 10-Oct-2025
9
-
10
- Abstract Base Classes for Lazy Loading System
11
-
12
- This module defines all abstract base classes for the lazy loading system
13
- following DEV_GUIDELINES.md structure. All abstract classes start with 'A'
14
- and extend interfaces from lazy_contracts.py.
15
-
16
- Design Patterns:
17
- - Template Method: Base classes define common workflows with abstract steps
18
- - Strategy: Different implementations can be plugged in
19
- - Abstract Factory: Factory methods for creating instances
20
- """
21
-
22
- import threading
23
- from abc import ABC, abstractmethod
24
- from pathlib import Path
25
- from typing import Dict, List, Optional, Any, Set, Tuple
26
- from types import ModuleType
27
-
28
- from .lazy_contracts import (
29
- IPackageDiscovery,
30
- IPackageInstaller,
31
- IImportHook,
32
- IPackageCache,
33
- ILazyLoader,
34
- DependencyInfo,
35
- LazyInstallMode,
36
- )
37
-
38
-
39
- # =============================================================================
40
- # ABSTRACT DISCOVERY (Template Method Pattern)
41
- # =============================================================================
42
-
43
- class APackageDiscovery(IPackageDiscovery, ABC):
44
- """
45
- Abstract base for package discovery.
46
-
47
- Implements Template Method pattern where discover_all_dependencies()
48
- defines the overall workflow, and subclasses implement specific steps.
49
- """
50
-
51
- __slots__ = ('project_root', 'discovered_dependencies', '_discovery_sources',
52
- '_cached_dependencies', '_file_mtimes', '_cache_valid')
53
-
54
- def __init__(self, project_root: Optional[str] = None):
55
- """
56
- Initialize package discovery.
57
-
58
- Args:
59
- project_root: Root directory of project (auto-detected if None)
60
- """
61
- self.project_root = Path(project_root) if project_root else self._find_project_root()
62
- self.discovered_dependencies: Dict[str, DependencyInfo] = {}
63
- self._discovery_sources: List[str] = []
64
- self._cached_dependencies: Dict[str, str] = {}
65
- self._file_mtimes: Dict[str, float] = {}
66
- self._cache_valid = False
67
-
68
- def _find_project_root(self) -> Path:
69
- """Find the project root directory by looking for markers."""
70
- current = Path(__file__).parent
71
- while current != current.parent:
72
- if (current / 'pyproject.toml').exists() or (current / 'setup.py').exists():
73
- return current
74
- current = current.parent
75
- return Path.cwd()
76
-
77
- def discover_all_dependencies(self) -> Dict[str, str]:
78
- """
79
- Template method: Discover all dependencies from all sources.
80
-
81
- Workflow:
82
- 1. Check if cache is valid
83
- 2. If not, discover from sources
84
- 3. Add common mappings
85
- 4. Update cache
86
- 5. Return dependencies
87
-
88
- Returns:
89
- Dict mapping import_name -> package_name
90
- """
91
- # Return cached result if still valid
92
- if self._is_cache_valid():
93
- return self._cached_dependencies.copy()
94
-
95
- # Cache invalid - rediscover
96
- self.discovered_dependencies.clear()
97
- self._discovery_sources.clear()
98
-
99
- # Discover from all sources (abstract method)
100
- self._discover_from_sources()
101
-
102
- # Add common mappings
103
- self._add_common_mappings()
104
-
105
- # Convert to simple dict format and cache
106
- result = {}
107
- for import_name, dep_info in self.discovered_dependencies.items():
108
- result[import_name] = dep_info.package_name
109
-
110
- # Update cache
111
- self._cached_dependencies = result.copy()
112
- self._cache_valid = True
113
- self._update_file_mtimes()
114
-
115
- return result
116
-
117
- @abstractmethod
118
- def _discover_from_sources(self) -> None:
119
- """
120
- Discover dependencies from all sources (abstract step).
121
-
122
- Implementations should discover from:
123
- - pyproject.toml
124
- - requirements.txt
125
- - setup.py
126
- - custom config files
127
- """
128
- pass
129
-
130
- @abstractmethod
131
- def _is_cache_valid(self) -> bool:
132
- """
133
- Check if cached dependencies are still valid (abstract step).
134
-
135
- Returns:
136
- True if cache is valid, False otherwise
137
- """
138
- pass
139
-
140
- @abstractmethod
141
- def _add_common_mappings(self) -> None:
142
- """Add common import -> package mappings (abstract step)."""
143
- pass
144
-
145
- @abstractmethod
146
- def _update_file_mtimes(self) -> None:
147
- """Update file modification times for cache validation (abstract step)."""
148
- pass
149
-
150
- def get_discovery_sources(self) -> List[str]:
151
- """Get list of sources used for discovery."""
152
- return self._discovery_sources.copy()
153
-
154
-
155
- # =============================================================================
156
- # ABSTRACT INSTALLER (Strategy Pattern)
157
- # =============================================================================
158
-
159
- class APackageInstaller(IPackageInstaller, ABC):
160
- """
161
- Abstract base for package installation.
162
-
163
- Implements Strategy pattern for different installation modes:
164
- - AUTO: Automatically install without asking
165
- - INTERACTIVE: Ask user before installing
166
- - WARN: Log warning but don't install
167
- - DISABLED: Don't install anything
168
- - DRY_RUN: Show what would be installed but don't install
169
- """
170
-
171
- __slots__ = ('_package_name', '_enabled', '_mode', '_installed_packages',
172
- '_failed_packages', '_lock')
173
-
174
- def __init__(self, package_name: str = 'default'):
175
- """
176
- Initialize package installer.
177
-
178
- Args:
179
- package_name: Name of package this installer is for (for isolation)
180
- """
181
- self._package_name = package_name
182
- self._enabled = False
183
- self._mode = LazyInstallMode.AUTO
184
- self._installed_packages: Set[str] = set()
185
- self._failed_packages: Set[str] = set()
186
- self._lock = threading.RLock()
187
-
188
- def get_package_name(self) -> str:
189
- """Get the package name this installer is for."""
190
- return self._package_name
191
-
192
- def set_mode(self, mode: LazyInstallMode) -> None:
193
- """Set the installation mode."""
194
- with self._lock:
195
- self._mode = mode
196
-
197
- def get_mode(self) -> LazyInstallMode:
198
- """Get the current installation mode."""
199
- return self._mode
200
-
201
- def enable(self) -> None:
202
- """Enable lazy installation."""
203
- with self._lock:
204
- self._enabled = True
205
-
206
- def disable(self) -> None:
207
- """Disable lazy installation."""
208
- with self._lock:
209
- self._enabled = False
210
-
211
- def is_enabled(self) -> bool:
212
- """Check if lazy installation is enabled."""
213
- return self._enabled
214
-
215
- @abstractmethod
216
- def install_package(self, package_name: str, module_name: str = None) -> bool:
217
- """
218
- Install a package (abstract method).
219
-
220
- Args:
221
- package_name: Name of package to install
222
- module_name: Name of module being imported (for interactive mode)
223
-
224
- Returns:
225
- True if installation successful, False otherwise
226
- """
227
- pass
228
-
229
- @abstractmethod
230
- def _check_security_policy(self, package_name: str) -> Tuple[bool, str]:
231
- """
232
- Check security policy for package (abstract method).
233
-
234
- Args:
235
- package_name: Package to check
236
-
237
- Returns:
238
- Tuple of (allowed: bool, reason: str)
239
- """
240
- pass
241
-
242
- @abstractmethod
243
- def _run_pip_install(self, package_name: str, args: List[str]) -> bool:
244
- """
245
- Run pip install with arguments (abstract method).
246
-
247
- Args:
248
- package_name: Package to install
249
- args: Additional pip arguments
250
-
251
- Returns:
252
- True if successful, False otherwise
253
- """
254
- pass
255
-
256
- def get_stats(self) -> Dict[str, Any]:
257
- """Get installation statistics."""
258
- with self._lock:
259
- return {
260
- 'enabled': self._enabled,
261
- 'mode': self._mode.value,
262
- 'package_name': self._package_name,
263
- 'installed_packages': list(self._installed_packages),
264
- 'failed_packages': list(self._failed_packages),
265
- 'total_installed': len(self._installed_packages),
266
- 'total_failed': len(self._failed_packages)
267
- }
268
-
269
-
270
- # =============================================================================
271
- # ABSTRACT IMPORT HOOK (Observer Pattern)
272
- # =============================================================================
273
-
274
- class AImportHook(IImportHook, ABC):
275
- """
276
- Abstract base for import hooks.
277
-
278
- Implements Observer pattern to observe import failures and trigger
279
- lazy installation when needed.
280
- """
281
-
282
- __slots__ = ('_package_name', '_enabled')
283
-
284
- def __init__(self, package_name: str = 'default'):
285
- """
286
- Initialize import hook.
287
-
288
- Args:
289
- package_name: Package this hook is for
290
- """
291
- self._package_name = package_name
292
- self._enabled = True
293
-
294
- def enable(self) -> None:
295
- """Enable the import hook."""
296
- self._enabled = True
297
-
298
- def disable(self) -> None:
299
- """Disable the import hook."""
300
- self._enabled = False
301
-
302
- def is_enabled(self) -> bool:
303
- """Check if hook is enabled."""
304
- return self._enabled
305
-
306
- @abstractmethod
307
- def install_hook(self) -> None:
308
- """Install the import hook into sys.meta_path (abstract method)."""
309
- pass
310
-
311
- @abstractmethod
312
- def uninstall_hook(self) -> None:
313
- """Uninstall the import hook from sys.meta_path (abstract method)."""
314
- pass
315
-
316
- @abstractmethod
317
- def handle_import_error(self, module_name: str) -> Optional[Any]:
318
- """
319
- Handle ImportError by attempting to install and re-import (abstract method).
320
-
321
- Args:
322
- module_name: Name of module that failed to import
323
-
324
- Returns:
325
- Imported module if successful, None otherwise
326
- """
327
- pass
328
-
329
-
330
- # =============================================================================
331
- # ABSTRACT CACHE (Proxy Pattern)
332
- # =============================================================================
333
-
334
- class APackageCache(IPackageCache, ABC):
335
- """
336
- Abstract base for package caching.
337
-
338
- Implements Proxy pattern to provide cached access to packages
339
- and avoid repeated operations.
340
- """
341
-
342
- __slots__ = ('_cache', '_lock')
343
-
344
- def __init__(self):
345
- """Initialize package cache."""
346
- self._cache: Dict[str, Any] = {}
347
- self._lock = threading.RLock()
348
-
349
- @abstractmethod
350
- def get_cached(self, key: str) -> Optional[Any]:
351
- """
352
- Get cached value (abstract method).
353
-
354
- Args:
355
- key: Cache key
356
-
357
- Returns:
358
- Cached value or None if not found
359
- """
360
- pass
361
-
362
- @abstractmethod
363
- def set_cached(self, key: str, value: Any) -> None:
364
- """
365
- Set cached value (abstract method).
366
-
367
- Args:
368
- key: Cache key
369
- value: Value to cache
370
- """
371
- pass
372
-
373
- def clear_cache(self) -> None:
374
- """Clear all cached values."""
375
- with self._lock:
376
- self._cache.clear()
377
-
378
- @abstractmethod
379
- def is_cache_valid(self, key: str) -> bool:
380
- """
381
- Check if cache entry is still valid (abstract method).
382
-
383
- Args:
384
- key: Cache key
385
-
386
- Returns:
387
- True if valid, False otherwise
388
- """
389
- pass
390
-
391
-
392
- # =============================================================================
393
- # ABSTRACT LAZY LOADER (Proxy Pattern)
394
- # =============================================================================
395
-
396
- class ALazyLoader(ILazyLoader, ABC):
397
- """
398
- Abstract base for lazy loading.
399
-
400
- Implements Proxy pattern to defer module loading until first access.
401
- """
402
-
403
- __slots__ = ('_module_path', '_cached_module', '_lock', '_loading')
404
-
405
- def __init__(self, module_path: str):
406
- """
407
- Initialize lazy loader.
408
-
409
- Args:
410
- module_path: Full module path to load
411
- """
412
- self._module_path = module_path
413
- self._cached_module: Optional[ModuleType] = None
414
- self._lock = threading.RLock()
415
- self._loading = False
416
-
417
- @abstractmethod
418
- def load_module(self, module_path: str) -> ModuleType:
419
- """
420
- Load a module lazily (abstract method).
421
-
422
- Args:
423
- module_path: Full module path to load
424
-
425
- Returns:
426
- Loaded module
427
- """
428
- pass
429
-
430
- def is_loaded(self, module_path: str = None) -> bool:
431
- """
432
- Check if module is already loaded.
433
-
434
- Args:
435
- module_path: Module path to check (uses self._module_path if None)
436
-
437
- Returns:
438
- True if loaded, False otherwise
439
- """
440
- return self._cached_module is not None
441
-
442
- @abstractmethod
443
- def unload_module(self, module_path: str) -> None:
444
- """
445
- Unload a module from cache (abstract method).
446
-
447
- Args:
448
- module_path: Module path to unload
449
- """
450
- pass
451
-
452
-
453
- # =============================================================================
454
- # EXPORT ALL
455
- # =============================================================================
456
-
457
- __all__ = [
458
- # Abstract base classes
459
- 'APackageDiscovery',
460
- 'APackageInstaller',
461
- 'AImportHook',
462
- 'APackageCache',
463
- 'ALazyLoader',
464
- ]
465
-