exonware-xwlazy 0.1.0.1__py3-none-any.whl → 0.1.0.9__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 (95) hide show
  1. exonware/xwlazy/__init__.py +0 -0
  2. exonware/xwlazy/version.py +2 -2
  3. exonware_xwlazy-0.1.0.9.dist-info/METADATA +0 -0
  4. exonware_xwlazy-0.1.0.9.dist-info/RECORD +6 -0
  5. exonware/__init__.py +0 -42
  6. exonware/xwlazy/common/__init__.py +0 -55
  7. exonware/xwlazy/common/base.py +0 -65
  8. exonware/xwlazy/common/cache.py +0 -504
  9. exonware/xwlazy/common/logger.py +0 -257
  10. exonware/xwlazy/common/services/__init__.py +0 -72
  11. exonware/xwlazy/common/services/dependency_mapper.py +0 -250
  12. exonware/xwlazy/common/services/install_async_utils.py +0 -170
  13. exonware/xwlazy/common/services/install_cache_utils.py +0 -245
  14. exonware/xwlazy/common/services/keyword_detection.py +0 -283
  15. exonware/xwlazy/common/services/spec_cache.py +0 -165
  16. exonware/xwlazy/common/services/state_manager.py +0 -84
  17. exonware/xwlazy/common/strategies/__init__.py +0 -28
  18. exonware/xwlazy/common/strategies/caching_dict.py +0 -44
  19. exonware/xwlazy/common/strategies/caching_installation.py +0 -88
  20. exonware/xwlazy/common/strategies/caching_lfu.py +0 -66
  21. exonware/xwlazy/common/strategies/caching_lru.py +0 -63
  22. exonware/xwlazy/common/strategies/caching_multitier.py +0 -59
  23. exonware/xwlazy/common/strategies/caching_ttl.py +0 -59
  24. exonware/xwlazy/common/utils.py +0 -142
  25. exonware/xwlazy/config.py +0 -193
  26. exonware/xwlazy/contracts.py +0 -1533
  27. exonware/xwlazy/defs.py +0 -378
  28. exonware/xwlazy/errors.py +0 -276
  29. exonware/xwlazy/facade.py +0 -1137
  30. exonware/xwlazy/host/__init__.py +0 -8
  31. exonware/xwlazy/host/conf.py +0 -16
  32. exonware/xwlazy/module/__init__.py +0 -18
  33. exonware/xwlazy/module/base.py +0 -643
  34. exonware/xwlazy/module/data.py +0 -17
  35. exonware/xwlazy/module/facade.py +0 -246
  36. exonware/xwlazy/module/importer_engine.py +0 -2964
  37. exonware/xwlazy/module/partial_module_detector.py +0 -275
  38. exonware/xwlazy/module/strategies/__init__.py +0 -22
  39. exonware/xwlazy/module/strategies/module_helper_lazy.py +0 -93
  40. exonware/xwlazy/module/strategies/module_helper_simple.py +0 -65
  41. exonware/xwlazy/module/strategies/module_manager_advanced.py +0 -111
  42. exonware/xwlazy/module/strategies/module_manager_simple.py +0 -95
  43. exonware/xwlazy/package/__init__.py +0 -18
  44. exonware/xwlazy/package/base.py +0 -877
  45. exonware/xwlazy/package/conf.py +0 -324
  46. exonware/xwlazy/package/data.py +0 -17
  47. exonware/xwlazy/package/facade.py +0 -480
  48. exonware/xwlazy/package/services/__init__.py +0 -84
  49. exonware/xwlazy/package/services/async_install_handle.py +0 -87
  50. exonware/xwlazy/package/services/config_manager.py +0 -249
  51. exonware/xwlazy/package/services/discovery.py +0 -435
  52. exonware/xwlazy/package/services/host_packages.py +0 -180
  53. exonware/xwlazy/package/services/install_async.py +0 -291
  54. exonware/xwlazy/package/services/install_cache.py +0 -145
  55. exonware/xwlazy/package/services/install_interactive.py +0 -59
  56. exonware/xwlazy/package/services/install_policy.py +0 -156
  57. exonware/xwlazy/package/services/install_registry.py +0 -54
  58. exonware/xwlazy/package/services/install_result.py +0 -17
  59. exonware/xwlazy/package/services/install_sbom.py +0 -153
  60. exonware/xwlazy/package/services/install_utils.py +0 -79
  61. exonware/xwlazy/package/services/installer_engine.py +0 -406
  62. exonware/xwlazy/package/services/lazy_installer.py +0 -803
  63. exonware/xwlazy/package/services/manifest.py +0 -503
  64. exonware/xwlazy/package/services/strategy_registry.py +0 -324
  65. exonware/xwlazy/package/strategies/__init__.py +0 -57
  66. exonware/xwlazy/package/strategies/package_discovery_file.py +0 -129
  67. exonware/xwlazy/package/strategies/package_discovery_hybrid.py +0 -84
  68. exonware/xwlazy/package/strategies/package_discovery_manifest.py +0 -101
  69. exonware/xwlazy/package/strategies/package_execution_async.py +0 -113
  70. exonware/xwlazy/package/strategies/package_execution_cached.py +0 -90
  71. exonware/xwlazy/package/strategies/package_execution_pip.py +0 -99
  72. exonware/xwlazy/package/strategies/package_execution_wheel.py +0 -106
  73. exonware/xwlazy/package/strategies/package_mapping_discovery_first.py +0 -100
  74. exonware/xwlazy/package/strategies/package_mapping_hybrid.py +0 -105
  75. exonware/xwlazy/package/strategies/package_mapping_manifest_first.py +0 -100
  76. exonware/xwlazy/package/strategies/package_policy_allow_list.py +0 -57
  77. exonware/xwlazy/package/strategies/package_policy_deny_list.py +0 -57
  78. exonware/xwlazy/package/strategies/package_policy_permissive.py +0 -46
  79. exonware/xwlazy/package/strategies/package_timing_clean.py +0 -67
  80. exonware/xwlazy/package/strategies/package_timing_full.py +0 -66
  81. exonware/xwlazy/package/strategies/package_timing_smart.py +0 -68
  82. exonware/xwlazy/package/strategies/package_timing_temporary.py +0 -66
  83. exonware/xwlazy/runtime/__init__.py +0 -18
  84. exonware/xwlazy/runtime/adaptive_learner.py +0 -129
  85. exonware/xwlazy/runtime/base.py +0 -274
  86. exonware/xwlazy/runtime/facade.py +0 -94
  87. exonware/xwlazy/runtime/intelligent_selector.py +0 -170
  88. exonware/xwlazy/runtime/metrics.py +0 -60
  89. exonware/xwlazy/runtime/performance.py +0 -37
  90. exonware_xwlazy-0.1.0.1.dist-info/METADATA +0 -454
  91. exonware_xwlazy-0.1.0.1.dist-info/RECORD +0 -93
  92. xwlazy/__init__.py +0 -14
  93. xwlazy/lazy.py +0 -30
  94. {exonware_xwlazy-0.1.0.1.dist-info → exonware_xwlazy-0.1.0.9.dist-info}/WHEEL +0 -0
  95. {exonware_xwlazy-0.1.0.1.dist-info → exonware_xwlazy-0.1.0.9.dist-info}/licenses/LICENSE +0 -0
@@ -1,877 +0,0 @@
1
- """
2
- #exonware/xwlazy/src/exonware/xwlazy/package/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 Package Operations
11
-
12
- This module defines the abstract base class for package operations.
13
- """
14
-
15
- import threading
16
- from abc import ABC, abstractmethod
17
- from pathlib import Path
18
- from typing import Optional, Any
19
- from types import ModuleType
20
-
21
- from ..defs import (
22
- DependencyInfo,
23
- LazyInstallMode,
24
- )
25
- from ..contracts import (
26
- IPackageHelper,
27
- IPackageHelperStrategy,
28
- IPackageManagerStrategy,
29
- IInstallExecutionStrategy,
30
- IInstallTimingStrategy,
31
- IDiscoveryStrategy,
32
- IPolicyStrategy,
33
- IMappingStrategy,
34
- IInstallStrategy,
35
- )
36
-
37
- # =============================================================================
38
- # ABSTRACT PACKAGE (Unified - Merges APackageDiscovery + APackageInstaller + APackageCache + APackageHelper)
39
- # =============================================================================
40
-
41
- class APackageHelper(IPackageHelper, ABC):
42
- """
43
- Unified abstract base for package operations.
44
-
45
- Merges functionality from APackageDiscovery, APackageInstaller, APackageCache, and APackageHelper.
46
- Provides comprehensive package operations: discovery, installation, caching, configuration, manifest loading, and dependency mapping.
47
-
48
- This abstract class combines:
49
- - Package discovery (mapping import names to package names)
50
- - Package installation (installing/uninstalling packages)
51
- - Package caching (caching installation status and metadata)
52
- - Configuration management (per-package lazy installation configuration)
53
- - Manifest loading (loading and caching dependency manifests)
54
- - Dependency mapping (mapping import names to package names)
55
- """
56
-
57
- __slots__ = (
58
- # From APackageDiscovery
59
- 'project_root', 'discovered_dependencies', '_discovery_sources',
60
- '_cached_dependencies', '_file_mtimes', '_cache_valid',
61
- # From APackageInstaller
62
- '_package_name', '_enabled', '_mode', '_installed_packages',
63
- '_failed_packages',
64
- # From APackageCache
65
- '_cache',
66
- # From APackageHelper
67
- '_uninstalled_packages',
68
- # Common
69
- '_lock'
70
- )
71
-
72
- def __init__(self, package_name: str = 'default', project_root: Optional[str] = None):
73
- """
74
- Initialize unified package operations.
75
-
76
- Args:
77
- package_name: Name of package this instance is for (for isolation)
78
- project_root: Root directory of project (auto-detected if None)
79
- """
80
- # From APackageDiscovery
81
- self.project_root = Path(project_root) if project_root else self._find_project_root()
82
- self.discovered_dependencies: dict[str, DependencyInfo] = {}
83
- self._discovery_sources: list[str] = []
84
- self._cached_dependencies: dict[str, str] = {}
85
- self._file_mtimes: dict[str, float] = {}
86
- self._cache_valid = False
87
-
88
- # From APackageInstaller
89
- self._package_name = package_name
90
- self._enabled = False
91
- self._mode = LazyInstallMode.SMART
92
- self._installed_packages: set[str] = set()
93
- self._failed_packages: set[str] = set()
94
-
95
- # From APackageCache
96
- self._cache: dict[str, Any] = {}
97
-
98
- # From APackageHelper
99
- self._uninstalled_packages: set[str] = set()
100
-
101
- # Common
102
- self._lock = threading.RLock()
103
-
104
- # ========================================================================
105
- # Package Discovery Methods (from APackageDiscovery)
106
- # ========================================================================
107
-
108
- def _find_project_root(self) -> Path:
109
- """
110
- Find the project root directory by looking for markers.
111
-
112
- Uses the shared utility function from common.utils.
113
- """
114
- from ..common.utils import find_project_root
115
- return find_project_root()
116
-
117
- def discover_all_dependencies(self) -> dict[str, str]:
118
- """
119
- Template method: Discover all dependencies from all sources.
120
-
121
- Workflow:
122
- 1. Check if cache is valid
123
- 2. If not, discover from sources
124
- 3. Add common mappings
125
- 4. Update cache
126
- 5. Return dependencies
127
-
128
- Returns:
129
- Dict mapping import_name -> package_name
130
- """
131
- # Return cached result if still valid
132
- if self._is_cache_valid():
133
- return self._cached_dependencies.copy()
134
-
135
- # Cache invalid - rediscover
136
- self.discovered_dependencies.clear()
137
- self._discovery_sources.clear()
138
-
139
- # Discover from all sources (abstract method)
140
- self._discover_from_sources()
141
-
142
- # Add common mappings
143
- self._add_common_mappings()
144
-
145
- # Convert to simple dict format and cache
146
- result = {}
147
- for import_name, dep_info in self.discovered_dependencies.items():
148
- result[import_name] = dep_info.package_name
149
-
150
- # Update cache
151
- self._cached_dependencies = result.copy()
152
- self._cache_valid = True
153
- self._update_file_mtimes()
154
-
155
- return result
156
-
157
- @abstractmethod
158
- def _discover_from_sources(self) -> None:
159
- """
160
- Discover dependencies from all sources (abstract step).
161
-
162
- Implementations should discover from:
163
- - pyproject.toml
164
- - requirements.txt
165
- - setup.py
166
- - custom config files
167
- """
168
- pass
169
-
170
- @abstractmethod
171
- def _is_cache_valid(self) -> bool:
172
- """
173
- Check if cached dependencies are still valid (abstract step).
174
-
175
- Returns:
176
- True if cache is valid, False otherwise
177
- """
178
- pass
179
-
180
- @abstractmethod
181
- def _add_common_mappings(self) -> None:
182
- """Add common import -> package mappings (abstract step)."""
183
- pass
184
-
185
- @abstractmethod
186
- def _update_file_mtimes(self) -> None:
187
- """Update file modification times for cache validation (abstract step)."""
188
- pass
189
-
190
- def get_discovery_sources(self) -> list[str]:
191
- """Get list of sources used for discovery."""
192
- return self._discovery_sources.copy()
193
-
194
- # ========================================================================
195
- # Package Installation Methods (from APackageInstaller)
196
- # ========================================================================
197
-
198
- def get_package_name(self, import_name: Optional[str] = None) -> Optional[str]:
199
- """
200
- Get package name.
201
- If import_name is None, returns the package name this instance is for.
202
- If import_name is provided, maps it to a package name (via IDependencyMapper).
203
- """
204
- if import_name is None:
205
- return self._package_name
206
-
207
- # For IDependencyMapper implementation - must be implemented by subclasses
208
- # Note: This method signature conflicts with the property getter, so subclasses
209
- # should implement this variant if they implement IDependencyMapper
210
- raise NotImplementedError("Subclasses must implement get_package_name(import_name)")
211
-
212
- def set_mode(self, mode: LazyInstallMode) -> None:
213
- """Set the installation mode."""
214
- with self._lock:
215
- self._mode = mode
216
-
217
- def get_mode(self) -> LazyInstallMode:
218
- """Get the current installation mode."""
219
- return self._mode
220
-
221
- def enable(self) -> None:
222
- """Enable lazy installation."""
223
- with self._lock:
224
- self._enabled = True
225
-
226
- def disable(self) -> None:
227
- """Disable lazy installation."""
228
- with self._lock:
229
- self._enabled = False
230
-
231
- def is_enabled(self) -> bool:
232
- """Check if lazy installation is enabled."""
233
- return self._enabled
234
-
235
- @abstractmethod
236
- def install_package(self, package_name: str, module_name: Optional[str] = None) -> bool:
237
- """
238
- Install a package (abstract method).
239
-
240
- Args:
241
- package_name: Name of package to install
242
- module_name: Name of module being imported (for interactive mode)
243
-
244
- Returns:
245
- True if installation successful, False otherwise
246
- """
247
- pass
248
-
249
- @abstractmethod
250
- def _check_security_policy(self, package_name: str) -> tuple[bool, str]:
251
- """
252
- Check security policy for package (abstract method).
253
-
254
- Args:
255
- package_name: Package to check
256
-
257
- Returns:
258
- Tuple of (allowed: bool, reason: str)
259
- """
260
- pass
261
-
262
- @abstractmethod
263
- def _run_pip_install(self, package_name: str, args: list[str]) -> bool:
264
- """
265
- Run pip install with arguments (abstract method).
266
-
267
- Args:
268
- package_name: Package to install
269
- args: Additional pip arguments
270
-
271
- Returns:
272
- True if successful, False otherwise
273
- """
274
- pass
275
-
276
- def get_stats(self) -> dict[str, Any]:
277
- """Get installation statistics."""
278
- with self._lock:
279
- return {
280
- 'enabled': self._enabled,
281
- 'mode': self._mode.value,
282
- 'package_name': self._package_name,
283
- 'installed_packages': list(self._installed_packages),
284
- 'failed_packages': list(self._failed_packages),
285
- 'total_installed': len(self._installed_packages),
286
- 'total_failed': len(self._failed_packages)
287
- }
288
-
289
- # ========================================================================
290
- # Package Caching Methods (from APackageCache)
291
- # ========================================================================
292
-
293
- def get_cached(self, key: str) -> Optional[Any]:
294
- """
295
- Get cached value (abstract method).
296
-
297
- Args:
298
- key: Cache key
299
-
300
- Returns:
301
- Cached value or None if not found
302
- """
303
- with self._lock:
304
- return self._cache.get(key)
305
-
306
- def set_cached(self, key: str, value: Any) -> None:
307
- """
308
- Set cached value (abstract method).
309
-
310
- Args:
311
- key: Cache key
312
- value: Value to cache
313
- """
314
- with self._lock:
315
- self._cache[key] = value
316
-
317
- def clear_cache(self) -> None:
318
- """Clear all cached values."""
319
- with self._lock:
320
- self._cache.clear()
321
-
322
- @abstractmethod
323
- def is_cache_valid(self, key: str) -> bool:
324
- """
325
- Check if cache entry is still valid (abstract method).
326
-
327
- Args:
328
- key: Cache key
329
-
330
- Returns:
331
- True if valid, False otherwise
332
- """
333
- pass
334
-
335
- # ========================================================================
336
- # Package Helper Methods (from APackageHelper)
337
- # ========================================================================
338
-
339
- def installed(self, package_name: str) -> bool:
340
- """
341
- Check if a package is installed.
342
-
343
- Uses cache first to avoid expensive operations.
344
- Checks persistent cache, then in-memory cache, then importability.
345
-
346
- Args:
347
- package_name: Package name to check (e.g., 'pymongo', 'msgpack')
348
-
349
- Returns:
350
- True if package is installed, False otherwise
351
- """
352
- # Check in-memory cache first (fast)
353
- with self._lock:
354
- if package_name in self._installed_packages:
355
- return True
356
- if package_name in self._uninstalled_packages:
357
- return False
358
-
359
- # Check persistent cache (abstract method)
360
- if self._check_persistent_cache(package_name):
361
- with self._lock:
362
- self._installed_packages.add(package_name)
363
- self._uninstalled_packages.discard(package_name)
364
- return True
365
-
366
- # Check actual installation (expensive) - abstract method
367
- is_installed = self._check_importability(package_name)
368
-
369
- # Update caches
370
- with self._lock:
371
- if is_installed:
372
- self._installed_packages.add(package_name)
373
- self._uninstalled_packages.discard(package_name)
374
- self._mark_installed_in_persistent_cache(package_name)
375
- else:
376
- self._uninstalled_packages.add(package_name)
377
- self._installed_packages.discard(package_name)
378
-
379
- return is_installed
380
-
381
- def uninstalled(self, package_name: str) -> bool:
382
- """
383
- Check if a package is uninstalled.
384
-
385
- Uses cache first to avoid expensive operations.
386
-
387
- Args:
388
- package_name: Package name to check (e.g., 'pymongo', 'msgpack')
389
-
390
- Returns:
391
- True if package is uninstalled, False otherwise
392
- """
393
- return not self.installed(package_name)
394
-
395
- def install(self, *package_names: str) -> None:
396
- """
397
- Install one or more packages using pip.
398
-
399
- Skips packages that are already installed (using cache).
400
- Only installs unique packages to avoid duplicate operations.
401
- Updates cache after successful installation.
402
-
403
- Args:
404
- *package_names: One or more package names to install (e.g., 'pymongo', 'msgpack')
405
-
406
- Raises:
407
- subprocess.CalledProcessError: If installation fails
408
- """
409
- if not package_names:
410
- return
411
-
412
- # Get unique packages only (preserves order while removing duplicates)
413
- unique_names = list(dict.fromkeys(package_names))
414
-
415
- # Filter out packages that are already installed (check cache first)
416
- to_install = []
417
- with self._lock:
418
- for name in unique_names:
419
- if name not in self._installed_packages:
420
- # Double-check if not in cache
421
- if not self.installed(name):
422
- to_install.append(name)
423
-
424
- if not to_install:
425
- # All packages already installed
426
- return
427
-
428
- # Install packages (abstract method)
429
- self._run_install(*to_install)
430
-
431
- # Update cache after successful installation
432
- with self._lock:
433
- for name in to_install:
434
- self._installed_packages.add(name)
435
- self._uninstalled_packages.discard(name)
436
- self._mark_installed_in_persistent_cache(name)
437
-
438
- def uninstall(self, *package_names: str) -> None:
439
- """
440
- Uninstall one or more packages using pip.
441
-
442
- Skips packages that are already uninstalled (using cache).
443
- Only uninstalls unique packages to avoid duplicate operations.
444
- Updates cache after successful uninstallation.
445
-
446
- Args:
447
- *package_names: One or more package names to uninstall (e.g., 'pymongo', 'msgpack')
448
-
449
- Raises:
450
- subprocess.CalledProcessError: If uninstallation fails
451
- """
452
- if not package_names:
453
- return
454
-
455
- # Get unique packages only (preserves order while removing duplicates)
456
- unique_names = list(dict.fromkeys(package_names))
457
-
458
- # Filter out packages that are already uninstalled (check cache first)
459
- to_uninstall = []
460
- with self._lock:
461
- for name in unique_names:
462
- if name not in self._uninstalled_packages:
463
- # Double-check if not uninstalled
464
- if self.installed(name):
465
- to_uninstall.append(name)
466
-
467
- if not to_uninstall:
468
- # All packages already uninstalled
469
- return
470
-
471
- # Uninstall packages (abstract method)
472
- self._run_uninstall(*to_uninstall)
473
-
474
- # Update cache after successful uninstallation
475
- with self._lock:
476
- for name in to_uninstall:
477
- self._uninstalled_packages.add(name)
478
- self._installed_packages.discard(name)
479
- self._mark_uninstalled_in_persistent_cache(name)
480
-
481
- @abstractmethod
482
- def _check_importability(self, package_name: str) -> bool:
483
- """
484
- Check if package is importable (abstract method).
485
-
486
- Concrete implementations should use importlib.util.find_spec or similar.
487
-
488
- Args:
489
- package_name: Package name to check
490
-
491
- Returns:
492
- True if importable, False otherwise
493
- """
494
- pass
495
-
496
- @abstractmethod
497
- def _check_persistent_cache(self, package_name: str) -> bool:
498
- """
499
- Check persistent cache for package installation status (abstract method).
500
-
501
- Args:
502
- package_name: Package name to check
503
-
504
- Returns:
505
- True if found in persistent cache as installed, False otherwise
506
- """
507
- pass
508
-
509
- @abstractmethod
510
- def _mark_installed_in_persistent_cache(self, package_name: str) -> None:
511
- """
512
- Mark package as installed in persistent cache (abstract method).
513
-
514
- Args:
515
- package_name: Package name to mark
516
- """
517
- pass
518
-
519
- @abstractmethod
520
- def _mark_uninstalled_in_persistent_cache(self, package_name: str) -> None:
521
- """
522
- Mark package as uninstalled in persistent cache (abstract method).
523
-
524
- Args:
525
- package_name: Package name to mark
526
- """
527
- pass
528
-
529
- @abstractmethod
530
- def _run_install(self, *package_names: str) -> None:
531
- """
532
- Run pip install for packages (abstract method).
533
-
534
- Args:
535
- *package_names: Package names to install
536
-
537
- Raises:
538
- subprocess.CalledProcessError: If installation fails
539
- """
540
- pass
541
-
542
- @abstractmethod
543
- def _run_uninstall(self, *package_names: str) -> None:
544
- """
545
- Run pip uninstall for packages (abstract method).
546
-
547
- Args:
548
- *package_names: Package names to uninstall
549
-
550
- Raises:
551
- subprocess.CalledProcessError: If uninstallation fails
552
- """
553
- pass
554
-
555
- # ========================================================================
556
- # IPackageHelper Interface Methods (stubs - to be implemented by subclasses)
557
- # ========================================================================
558
-
559
- # Note: Many methods from IPackageHelper are already implemented above.
560
- # The following are stubs that need concrete implementations:
561
-
562
- @abstractmethod
563
- def install_and_import(self, module_name: str, package_name: Optional[str] = None) -> tuple[Optional[ModuleType], bool]:
564
- """Install package and import module (from IPackageInstaller)."""
565
- pass
566
-
567
- @abstractmethod
568
- def get_package_for_import(self, import_name: str) -> Optional[str]:
569
- """Get package name for a given import name (from IPackageDiscovery)."""
570
- pass
571
-
572
- @abstractmethod
573
- def get_imports_for_package(self, package_name: str) -> list[str]:
574
- """Get all possible import names for a package (from IPackageDiscovery)."""
575
- pass
576
-
577
- # def get_package_name(self, import_name: str) -> Optional[str]:
578
- # """Get package name for an import name (from IDependencyMapper)."""
579
- # raise NotImplementedError("Subclasses must implement get_package_name")
580
-
581
- @abstractmethod
582
- def get_import_names(self, package_name: str) -> list[str]:
583
- """Get all import names for a package (from IDependencyMapper)."""
584
- pass
585
-
586
- @abstractmethod
587
- def is_stdlib_or_builtin(self, import_name: str) -> bool:
588
- """Check if import name is stdlib or builtin (from IDependencyMapper)."""
589
- pass
590
-
591
- # Note: is_enabled(package_name) from IConfigManager is removed to avoid conflict
592
- # with is_enabled() instance method. Use LazyInstallConfig.is_enabled(package_name) instead.
593
-
594
- @abstractmethod
595
- def get_mode(self, package_name: str) -> str:
596
- """Get installation mode for a package (from IConfigManager)."""
597
- pass
598
-
599
- @abstractmethod
600
- def get_load_mode(self, package_name: str) -> Any:
601
- """Get load mode for a package (from IConfigManager)."""
602
- pass
603
-
604
- @abstractmethod
605
- def get_install_mode(self, package_name: str) -> Any:
606
- """Get install mode for a package (from IConfigManager)."""
607
- pass
608
-
609
- @abstractmethod
610
- def get_mode_config(self, package_name: str) -> Optional[Any]:
611
- """Get full mode configuration for a package (from IConfigManager)."""
612
- pass
613
-
614
- @abstractmethod
615
- def get_manifest_signature(self, package_name: str) -> Optional[tuple[str, float, float]]:
616
- """Get manifest file signature (from IManifestLoader)."""
617
- pass
618
-
619
- @abstractmethod
620
- def get_shared_dependencies(self, package_name: str, signature: Optional[tuple[str, float, float]] = None) -> dict[str, str]:
621
- """Get shared dependencies from manifest (from IManifestLoader)."""
622
- pass
623
-
624
- @abstractmethod
625
- def get_watched_prefixes(self, package_name: str) -> tuple[str, ...]:
626
- """Get watched prefixes from manifest (from IManifestLoader)."""
627
- pass
628
-
629
- # =============================================================================
630
- # DEPRECATED CLASSES (for backward compatibility)
631
- # =============================================================================
632
-
633
- # =============================================================================
634
- # ABSTRACT PACKAGE HELPER STRATEGY
635
- # =============================================================================
636
-
637
- class APackageHelperStrategy(IPackageHelperStrategy, ABC):
638
- """
639
- Abstract base class for package helper strategies.
640
-
641
- Operations on a single package (installing, uninstalling, checking).
642
- All package helper strategies must extend this class.
643
- """
644
-
645
- @abstractmethod
646
- def install(self, package_name: str) -> bool:
647
- """Install the package."""
648
- ...
649
-
650
- @abstractmethod
651
- def uninstall(self, package_name: str) -> None:
652
- """Uninstall the package."""
653
- ...
654
-
655
- @abstractmethod
656
- def check_installed(self, name: str) -> bool:
657
- """Check if package is installed."""
658
- ...
659
-
660
- @abstractmethod
661
- def get_version(self, name: str) -> Optional[str]:
662
- """Get installed version."""
663
- ...
664
-
665
- # =============================================================================
666
- # ABSTRACT PACKAGE MANAGER STRATEGY
667
- # =============================================================================
668
-
669
- class APackageManagerStrategy(IPackageManagerStrategy, ABC):
670
- """
671
- Abstract base class for package manager strategies.
672
-
673
- Orchestrates multiple packages (installation, discovery, policy).
674
- All package manager strategies must extend this class.
675
- """
676
-
677
- @abstractmethod
678
- def install_package(self, package_name: str, module_name: Optional[str] = None) -> bool:
679
- """Install a package."""
680
- ...
681
-
682
- @abstractmethod
683
- def uninstall_package(self, package_name: str) -> None:
684
- """Uninstall a package."""
685
- ...
686
-
687
- @abstractmethod
688
- def discover_dependencies(self) -> dict[str, str]:
689
- """Discover dependencies."""
690
- ...
691
-
692
- @abstractmethod
693
- def check_security_policy(self, package_name: str) -> tuple[bool, str]:
694
- """Check security policy."""
695
- ...
696
-
697
- # =============================================================================
698
- # ABSTRACT INSTALLATION EXECUTION STRATEGY
699
- # =============================================================================
700
-
701
- class AInstallExecutionStrategy(IInstallExecutionStrategy, ABC):
702
- """
703
- Abstract base class for installation execution strategies.
704
-
705
- HOW to execute installation (pip, wheel, cached, async).
706
- """
707
-
708
- @abstractmethod
709
- def execute_install(self, package_name: str, policy_args: list[str]) -> Any:
710
- """Execute installation of a package."""
711
- ...
712
-
713
- @abstractmethod
714
- def execute_uninstall(self, package_name: str) -> bool:
715
- """Execute uninstallation of a package."""
716
- ...
717
-
718
- # =============================================================================
719
- # ABSTRACT INSTALLATION TIMING STRATEGY
720
- # =============================================================================
721
-
722
- class AInstallTimingStrategy(IInstallTimingStrategy, ABC):
723
- """
724
- Abstract base class for installation timing strategies.
725
-
726
- WHEN to install packages (on-demand, upfront, temporary, etc.).
727
- """
728
-
729
- @abstractmethod
730
- def should_install_now(self, package_name: str, context: Any) -> bool:
731
- """Determine if package should be installed now."""
732
- ...
733
-
734
- @abstractmethod
735
- def should_uninstall_after(self, package_name: str, context: Any) -> bool:
736
- """Determine if package should be uninstalled after use."""
737
- ...
738
-
739
- @abstractmethod
740
- def get_install_priority(self, packages: list[str]) -> list[str]:
741
- """Get priority order for installing packages."""
742
- ...
743
-
744
- # =============================================================================
745
- # ABSTRACT DISCOVERY STRATEGY
746
- # =============================================================================
747
-
748
- class ADiscoveryStrategy(IDiscoveryStrategy, ABC):
749
- """
750
- Abstract base class for discovery strategies.
751
-
752
- HOW to discover dependencies (from files, manifest, auto-detect).
753
- """
754
-
755
- @abstractmethod
756
- def discover(self, project_root: Any) -> dict[str, str]:
757
- """Discover dependencies from sources."""
758
- ...
759
-
760
- @abstractmethod
761
- def get_source(self, import_name: str) -> Optional[str]:
762
- """Get the source of a discovered dependency."""
763
- ...
764
-
765
- # =============================================================================
766
- # ABSTRACT POLICY STRATEGY
767
- # =============================================================================
768
-
769
- class APolicyStrategy(IPolicyStrategy, ABC):
770
- """
771
- Abstract base class for policy strategies.
772
-
773
- WHAT can be installed (security/policy enforcement).
774
- """
775
-
776
- @abstractmethod
777
- def is_allowed(self, package_name: str) -> tuple[bool, str]:
778
- """Check if package is allowed to be installed."""
779
- ...
780
-
781
- @abstractmethod
782
- def get_pip_args(self, package_name: str) -> list[str]:
783
- """Get pip arguments based on policy."""
784
- ...
785
-
786
- # =============================================================================
787
- # ABSTRACT MAPPING STRATEGY
788
- # =============================================================================
789
-
790
- class AMappingStrategy(IMappingStrategy, ABC):
791
- """
792
- Abstract base class for mapping strategies.
793
-
794
- HOW to map import names to package names.
795
- """
796
-
797
- @abstractmethod
798
- def map_import_to_package(self, import_name: str) -> Optional[str]:
799
- """Map import name to package name."""
800
- ...
801
-
802
- @abstractmethod
803
- def map_package_to_imports(self, package_name: str) -> list[str]:
804
- """Map package name to possible import names."""
805
- ...
806
-
807
- # =============================================================================
808
- # EXPORT ALL
809
- # =============================================================================
810
-
811
- __all__ = [
812
- 'APackageHelper',
813
- 'APackageHelperStrategy',
814
- 'APackageManagerStrategy',
815
- 'AInstallExecutionStrategy',
816
- 'AInstallTimingStrategy',
817
- 'ADiscoveryStrategy',
818
- 'APolicyStrategy',
819
- 'AMappingStrategy',
820
- # Enhanced Strategy Interfaces for Runtime Swapping
821
- 'AInstallStrategy',
822
- ]
823
-
824
- # =============================================================================
825
- # ABSTRACT INSTALLATION STRATEGY (Enhanced for Runtime Swapping)
826
- # =============================================================================
827
-
828
- class AInstallStrategy(IInstallStrategy, ABC):
829
- """
830
- Abstract base class for installation strategies.
831
-
832
- Enables runtime strategy swapping for different installation methods
833
- (pip, wheel, async, cached, etc.).
834
- """
835
-
836
- @abstractmethod
837
- def install(self, package_name: str, version: Optional[str] = None) -> bool:
838
- """
839
- Install a package.
840
-
841
- Args:
842
- package_name: Package name to install
843
- version: Optional version specification
844
-
845
- Returns:
846
- True if installation successful, False otherwise
847
- """
848
- ...
849
-
850
- def can_install(self, package_name: str) -> bool:
851
- """
852
- Check if this strategy can install a package.
853
-
854
- Default implementation returns True.
855
- Override for strategy-specific logic.
856
-
857
- Args:
858
- package_name: Package name to check
859
-
860
- Returns:
861
- True if can install, False otherwise
862
- """
863
- return True
864
-
865
- @abstractmethod
866
- def uninstall(self, package_name: str) -> bool:
867
- """
868
- Uninstall a package.
869
-
870
- Args:
871
- package_name: Package name to uninstall
872
-
873
- Returns:
874
- True if uninstallation successful, False otherwise
875
- """
876
- ...
877
-