exonware-xwlazy 0.1.0.23__py3-none-any.whl → 1.0.1.2__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 +85 -34
  2. exonware/xwlazy/version.py +5 -5
  3. exonware/xwlazy.py +2546 -0
  4. exonware/xwlazy_external_libs.toml +716 -0
  5. {exonware_xwlazy-0.1.0.23.dist-info → exonware_xwlazy-1.0.1.2.dist-info}/METADATA +5 -1
  6. exonware_xwlazy-1.0.1.2.dist-info/RECORD +8 -0
  7. exonware/xwlazy/__init__.py +0 -379
  8. exonware/xwlazy/common/__init__.py +0 -55
  9. exonware/xwlazy/common/base.py +0 -65
  10. exonware/xwlazy/common/cache.py +0 -504
  11. exonware/xwlazy/common/logger.py +0 -257
  12. exonware/xwlazy/common/services/__init__.py +0 -72
  13. exonware/xwlazy/common/services/dependency_mapper.py +0 -250
  14. exonware/xwlazy/common/services/install_async_utils.py +0 -170
  15. exonware/xwlazy/common/services/install_cache_utils.py +0 -245
  16. exonware/xwlazy/common/services/keyword_detection.py +0 -283
  17. exonware/xwlazy/common/services/spec_cache.py +0 -165
  18. exonware/xwlazy/common/services/state_manager.py +0 -84
  19. exonware/xwlazy/common/strategies/__init__.py +0 -28
  20. exonware/xwlazy/common/strategies/caching_dict.py +0 -44
  21. exonware/xwlazy/common/strategies/caching_installation.py +0 -88
  22. exonware/xwlazy/common/strategies/caching_lfu.py +0 -66
  23. exonware/xwlazy/common/strategies/caching_lru.py +0 -63
  24. exonware/xwlazy/common/strategies/caching_multitier.py +0 -59
  25. exonware/xwlazy/common/strategies/caching_ttl.py +0 -59
  26. exonware/xwlazy/common/utils.py +0 -142
  27. exonware/xwlazy/config.py +0 -193
  28. exonware/xwlazy/contracts.py +0 -1533
  29. exonware/xwlazy/defs.py +0 -378
  30. exonware/xwlazy/errors.py +0 -276
  31. exonware/xwlazy/facade.py +0 -1137
  32. exonware/xwlazy/host/__init__.py +0 -8
  33. exonware/xwlazy/host/conf.py +0 -16
  34. exonware/xwlazy/module/__init__.py +0 -18
  35. exonware/xwlazy/module/base.py +0 -622
  36. exonware/xwlazy/module/data.py +0 -17
  37. exonware/xwlazy/module/facade.py +0 -246
  38. exonware/xwlazy/module/importer_engine.py +0 -2964
  39. exonware/xwlazy/module/partial_module_detector.py +0 -275
  40. exonware/xwlazy/module/strategies/__init__.py +0 -22
  41. exonware/xwlazy/module/strategies/module_helper_lazy.py +0 -93
  42. exonware/xwlazy/module/strategies/module_helper_simple.py +0 -65
  43. exonware/xwlazy/module/strategies/module_manager_advanced.py +0 -111
  44. exonware/xwlazy/module/strategies/module_manager_simple.py +0 -95
  45. exonware/xwlazy/package/__init__.py +0 -18
  46. exonware/xwlazy/package/base.py +0 -863
  47. exonware/xwlazy/package/conf.py +0 -324
  48. exonware/xwlazy/package/data.py +0 -17
  49. exonware/xwlazy/package/facade.py +0 -480
  50. exonware/xwlazy/package/services/__init__.py +0 -84
  51. exonware/xwlazy/package/services/async_install_handle.py +0 -87
  52. exonware/xwlazy/package/services/config_manager.py +0 -249
  53. exonware/xwlazy/package/services/discovery.py +0 -435
  54. exonware/xwlazy/package/services/host_packages.py +0 -180
  55. exonware/xwlazy/package/services/install_async.py +0 -291
  56. exonware/xwlazy/package/services/install_cache.py +0 -145
  57. exonware/xwlazy/package/services/install_interactive.py +0 -59
  58. exonware/xwlazy/package/services/install_policy.py +0 -156
  59. exonware/xwlazy/package/services/install_registry.py +0 -54
  60. exonware/xwlazy/package/services/install_result.py +0 -17
  61. exonware/xwlazy/package/services/install_sbom.py +0 -153
  62. exonware/xwlazy/package/services/install_utils.py +0 -79
  63. exonware/xwlazy/package/services/installer_engine.py +0 -406
  64. exonware/xwlazy/package/services/lazy_installer.py +0 -803
  65. exonware/xwlazy/package/services/manifest.py +0 -503
  66. exonware/xwlazy/package/services/strategy_registry.py +0 -324
  67. exonware/xwlazy/package/strategies/__init__.py +0 -57
  68. exonware/xwlazy/package/strategies/package_discovery_file.py +0 -129
  69. exonware/xwlazy/package/strategies/package_discovery_hybrid.py +0 -84
  70. exonware/xwlazy/package/strategies/package_discovery_manifest.py +0 -101
  71. exonware/xwlazy/package/strategies/package_execution_async.py +0 -113
  72. exonware/xwlazy/package/strategies/package_execution_cached.py +0 -90
  73. exonware/xwlazy/package/strategies/package_execution_pip.py +0 -99
  74. exonware/xwlazy/package/strategies/package_execution_wheel.py +0 -106
  75. exonware/xwlazy/package/strategies/package_mapping_discovery_first.py +0 -100
  76. exonware/xwlazy/package/strategies/package_mapping_hybrid.py +0 -105
  77. exonware/xwlazy/package/strategies/package_mapping_manifest_first.py +0 -100
  78. exonware/xwlazy/package/strategies/package_policy_allow_list.py +0 -57
  79. exonware/xwlazy/package/strategies/package_policy_deny_list.py +0 -57
  80. exonware/xwlazy/package/strategies/package_policy_permissive.py +0 -46
  81. exonware/xwlazy/package/strategies/package_timing_clean.py +0 -67
  82. exonware/xwlazy/package/strategies/package_timing_full.py +0 -66
  83. exonware/xwlazy/package/strategies/package_timing_smart.py +0 -68
  84. exonware/xwlazy/package/strategies/package_timing_temporary.py +0 -66
  85. exonware/xwlazy/runtime/__init__.py +0 -18
  86. exonware/xwlazy/runtime/adaptive_learner.py +0 -129
  87. exonware/xwlazy/runtime/base.py +0 -274
  88. exonware/xwlazy/runtime/facade.py +0 -94
  89. exonware/xwlazy/runtime/intelligent_selector.py +0 -170
  90. exonware/xwlazy/runtime/metrics.py +0 -60
  91. exonware/xwlazy/runtime/performance.py +0 -37
  92. exonware_xwlazy-0.1.0.23.dist-info/RECORD +0 -93
  93. xwlazy/__init__.py +0 -14
  94. xwlazy/lazy.py +0 -30
  95. {exonware_xwlazy-0.1.0.23.dist-info → exonware_xwlazy-1.0.1.2.dist-info}/WHEEL +0 -0
  96. {exonware_xwlazy-0.1.0.23.dist-info → exonware_xwlazy-1.0.1.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,324 +0,0 @@
1
- """
2
- Strategy Registry
3
-
4
- Company: eXonware.com
5
- Author: Eng. Muhammad AlShehri
6
- Email: connect@exonware.com
7
-
8
- Generation Date: 15-Nov-2025
9
-
10
- Registry to store custom strategies per package for both package and module operations.
11
- """
12
-
13
- import threading
14
- from typing import Optional, Any, TYPE_CHECKING
15
-
16
- if TYPE_CHECKING:
17
- from ...contracts import (
18
- IInstallExecutionStrategy,
19
- IInstallTimingStrategy,
20
- IDiscoveryStrategy,
21
- IPolicyStrategy,
22
- IMappingStrategy,
23
- IModuleHelperStrategy,
24
- IModuleManagerStrategy,
25
- ICachingStrategy,
26
- IInstallStrategy,
27
- ILoadStrategy,
28
- ICacheStrategy,
29
- )
30
-
31
- class StrategyRegistry:
32
- """
33
- Registry to store custom strategies per package and enable runtime strategy swapping.
34
-
35
- Supports both:
36
- - Per-package strategies (different strategies for different packages)
37
- - Global strategy swapping (change default strategies used by all packages)
38
- """
39
-
40
- # Package strategies (per-package)
41
- _package_execution_strategies: dict[str, 'IInstallExecutionStrategy'] = {}
42
- _package_timing_strategies: dict[str, 'IInstallTimingStrategy'] = {}
43
- _package_discovery_strategies: dict[str, 'IDiscoveryStrategy'] = {}
44
- _package_policy_strategies: dict[str, 'IPolicyStrategy'] = {}
45
- _package_mapping_strategies: dict[str, 'IMappingStrategy'] = {}
46
-
47
- # Module strategies (per-package)
48
- _module_helper_strategies: dict[str, 'IModuleHelperStrategy'] = {}
49
- _module_manager_strategies: dict[str, 'IModuleManagerStrategy'] = {}
50
- _module_caching_strategies: dict[str, 'ICachingStrategy'] = {}
51
-
52
- # Global strategies (for runtime swapping)
53
- _global_install_strategies: dict[str, 'IInstallStrategy'] = {}
54
- _global_load_strategies: dict[str, 'ILoadStrategy'] = {}
55
- _global_cache_strategies: dict[str, 'ICacheStrategy'] = {}
56
- _default_install_strategy: Optional[str] = None
57
- _default_load_strategy: Optional[str] = None
58
- _default_cache_strategy: Optional[str] = None
59
-
60
- _lock = threading.RLock()
61
-
62
- @classmethod
63
- def set_package_strategy(
64
- cls,
65
- package_name: str,
66
- strategy_type: str,
67
- strategy: Any,
68
- ) -> None:
69
- """
70
- Set a package strategy for a package.
71
-
72
- Args:
73
- package_name: Package name
74
- strategy_type: One of 'execution', 'timing', 'discovery', 'policy', 'mapping'
75
- strategy: Strategy instance
76
- """
77
- package_key = package_name.lower()
78
- with cls._lock:
79
- if strategy_type == 'execution':
80
- cls._package_execution_strategies[package_key] = strategy
81
- elif strategy_type == 'timing':
82
- cls._package_timing_strategies[package_key] = strategy
83
- elif strategy_type == 'discovery':
84
- cls._package_discovery_strategies[package_key] = strategy
85
- elif strategy_type == 'policy':
86
- cls._package_policy_strategies[package_key] = strategy
87
- elif strategy_type == 'mapping':
88
- cls._package_mapping_strategies[package_key] = strategy
89
- else:
90
- raise ValueError(f"Unknown package strategy type: {strategy_type}")
91
-
92
- @classmethod
93
- def get_package_strategy(
94
- cls,
95
- package_name: str,
96
- strategy_type: str,
97
- ) -> Optional[Any]:
98
- """
99
- Get a package strategy for a package.
100
-
101
- Args:
102
- package_name: Package name
103
- strategy_type: One of 'execution', 'timing', 'discovery', 'policy', 'mapping'
104
-
105
- Returns:
106
- Strategy instance or None if not set
107
- """
108
- package_key = package_name.lower()
109
- with cls._lock:
110
- if strategy_type == 'execution':
111
- return cls._package_execution_strategies.get(package_key)
112
- elif strategy_type == 'timing':
113
- return cls._package_timing_strategies.get(package_key)
114
- elif strategy_type == 'discovery':
115
- return cls._package_discovery_strategies.get(package_key)
116
- elif strategy_type == 'policy':
117
- return cls._package_policy_strategies.get(package_key)
118
- elif strategy_type == 'mapping':
119
- return cls._package_mapping_strategies.get(package_key)
120
- else:
121
- raise ValueError(f"Unknown package strategy type: {strategy_type}")
122
-
123
- @classmethod
124
- def set_module_strategy(
125
- cls,
126
- package_name: str,
127
- strategy_type: str,
128
- strategy: Any,
129
- ) -> None:
130
- """
131
- Set a module strategy for a package.
132
-
133
- Args:
134
- package_name: Package name
135
- strategy_type: One of 'helper', 'manager', 'caching'
136
- strategy: Strategy instance
137
- """
138
- package_key = package_name.lower()
139
- with cls._lock:
140
- if strategy_type == 'helper':
141
- cls._module_helper_strategies[package_key] = strategy
142
- elif strategy_type == 'manager':
143
- cls._module_manager_strategies[package_key] = strategy
144
- elif strategy_type == 'caching':
145
- cls._module_caching_strategies[package_key] = strategy
146
- else:
147
- raise ValueError(f"Unknown module strategy type: {strategy_type}")
148
-
149
- @classmethod
150
- def get_module_strategy(
151
- cls,
152
- package_name: str,
153
- strategy_type: str,
154
- ) -> Optional[Any]:
155
- """
156
- Get a module strategy for a package.
157
-
158
- Args:
159
- package_name: Package name
160
- strategy_type: One of 'helper', 'manager', 'caching'
161
-
162
- Returns:
163
- Strategy instance or None if not set
164
- """
165
- package_key = package_name.lower()
166
- with cls._lock:
167
- if strategy_type == 'helper':
168
- return cls._module_helper_strategies.get(package_key)
169
- elif strategy_type == 'manager':
170
- return cls._module_manager_strategies.get(package_key)
171
- elif strategy_type == 'caching':
172
- return cls._module_caching_strategies.get(package_key)
173
- else:
174
- raise ValueError(f"Unknown module strategy type: {strategy_type}")
175
-
176
- @classmethod
177
- def clear_package_strategies(cls, package_name: str) -> None:
178
- """Clear all package strategies for a package."""
179
- package_key = package_name.lower()
180
- with cls._lock:
181
- cls._package_execution_strategies.pop(package_key, None)
182
- cls._package_timing_strategies.pop(package_key, None)
183
- cls._package_discovery_strategies.pop(package_key, None)
184
- cls._package_policy_strategies.pop(package_key, None)
185
- cls._package_mapping_strategies.pop(package_key, None)
186
-
187
- @classmethod
188
- def clear_module_strategies(cls, package_name: str) -> None:
189
- """Clear all module strategies for a package."""
190
- package_key = package_name.lower()
191
- with cls._lock:
192
- cls._module_helper_strategies.pop(package_key, None)
193
- cls._module_manager_strategies.pop(package_key, None)
194
- cls._module_caching_strategies.pop(package_key, None)
195
-
196
- @classmethod
197
- def clear_all_strategies(cls, package_name: str) -> None:
198
- """Clear all strategies (package and module) for a package."""
199
- cls.clear_package_strategies(package_name)
200
- cls.clear_module_strategies(package_name)
201
-
202
- # ========================================================================
203
- # Global Strategy Management (for runtime swapping)
204
- # ========================================================================
205
-
206
- @classmethod
207
- def register_install_strategy(cls, name: str, strategy: 'IInstallStrategy') -> None:
208
- """
209
- Register a global installation strategy for runtime swapping.
210
-
211
- Args:
212
- name: Strategy name (e.g., 'pip', 'wheel', 'async', 'cached')
213
- strategy: Strategy instance implementing IInstallStrategy
214
- """
215
- with cls._lock:
216
- cls._global_install_strategies[name] = strategy
217
- if cls._default_install_strategy is None:
218
- cls._default_install_strategy = name
219
-
220
- @classmethod
221
- def get_install_strategy(cls, name: Optional[str] = None) -> Optional['IInstallStrategy']:
222
- """
223
- Get global installation strategy by name.
224
-
225
- Args:
226
- name: Strategy name (uses default if None)
227
-
228
- Returns:
229
- Strategy instance or None if not found
230
- """
231
- with cls._lock:
232
- if name is None:
233
- name = cls._default_install_strategy
234
- return cls._global_install_strategies.get(name) if name else None
235
-
236
- @classmethod
237
- def register_load_strategy(cls, name: str, strategy: 'ILoadStrategy') -> None:
238
- """
239
- Register a global loading strategy for runtime swapping.
240
-
241
- Args:
242
- name: Strategy name (e.g., 'lazy', 'simple', 'advanced')
243
- strategy: Strategy instance implementing ILoadStrategy
244
- """
245
- with cls._lock:
246
- cls._global_load_strategies[name] = strategy
247
- if cls._default_load_strategy is None:
248
- cls._default_load_strategy = name
249
-
250
- @classmethod
251
- def get_load_strategy(cls, name: Optional[str] = None) -> Optional['ILoadStrategy']:
252
- """
253
- Get global loading strategy by name.
254
-
255
- Args:
256
- name: Strategy name (uses default if None)
257
-
258
- Returns:
259
- Strategy instance or None if not found
260
- """
261
- with cls._lock:
262
- if name is None:
263
- name = cls._default_load_strategy
264
- return cls._global_load_strategies.get(name) if name else None
265
-
266
- @classmethod
267
- def register_cache_strategy(cls, name: str, strategy: 'ICacheStrategy') -> None:
268
- """
269
- Register a global caching strategy for runtime swapping.
270
-
271
- Args:
272
- name: Strategy name (e.g., 'lru', 'lfu', 'ttl', 'multitier')
273
- strategy: Strategy instance implementing ICacheStrategy
274
- """
275
- with cls._lock:
276
- cls._global_cache_strategies[name] = strategy
277
- if cls._default_cache_strategy is None:
278
- cls._default_cache_strategy = name
279
-
280
- @classmethod
281
- def get_cache_strategy(cls, name: Optional[str] = None) -> Optional['ICacheStrategy']:
282
- """
283
- Get global caching strategy by name.
284
-
285
- Args:
286
- name: Strategy name (uses default if None)
287
-
288
- Returns:
289
- Strategy instance or None if not found
290
- """
291
- with cls._lock:
292
- if name is None:
293
- name = cls._default_cache_strategy
294
- return cls._global_cache_strategies.get(name) if name else None
295
-
296
- @classmethod
297
- def swap_install_strategy(cls, name: str) -> bool:
298
- """Swap to a different global installation strategy."""
299
- with cls._lock:
300
- if name in cls._global_install_strategies:
301
- cls._default_install_strategy = name
302
- return True
303
- return False
304
-
305
- @classmethod
306
- def swap_load_strategy(cls, name: str) -> bool:
307
- """Swap to a different global loading strategy."""
308
- with cls._lock:
309
- if name in cls._global_load_strategies:
310
- cls._default_load_strategy = name
311
- return True
312
- return False
313
-
314
- @classmethod
315
- def swap_cache_strategy(cls, name: str) -> bool:
316
- """Swap to a different global caching strategy."""
317
- with cls._lock:
318
- if name in cls._global_cache_strategies:
319
- cls._default_cache_strategy = name
320
- return True
321
- return False
322
-
323
- __all__ = ['StrategyRegistry']
324
-
@@ -1,57 +0,0 @@
1
- """
2
- Package Strategies
3
-
4
- All package strategy implementations.
5
- """
6
-
7
- # Mapping strategies
8
- from .package_mapping_manifest_first import ManifestFirstMapping
9
- from .package_mapping_discovery_first import DiscoveryFirstMapping
10
- from .package_mapping_hybrid import HybridMapping
11
-
12
- # Policy strategies
13
- from .package_policy_permissive import PermissivePolicy
14
- from .package_policy_allow_list import AllowListPolicy
15
- from .package_policy_deny_list import DenyListPolicy
16
-
17
- # Timing strategies
18
- from .package_timing_smart import SmartTiming
19
- from .package_timing_full import FullTiming
20
- from .package_timing_clean import CleanTiming
21
- from .package_timing_temporary import TemporaryTiming
22
-
23
- # Execution strategies
24
- from .package_execution_pip import PipExecution
25
- from .package_execution_wheel import WheelExecution
26
- from .package_execution_cached import CachedExecution
27
- from .package_execution_async import AsyncExecution
28
-
29
- # Discovery strategies
30
- from .package_discovery_file import FileBasedDiscovery
31
- from .package_discovery_manifest import ManifestBasedDiscovery
32
- from .package_discovery_hybrid import HybridDiscovery
33
-
34
- __all__ = [
35
- # Mapping strategies
36
- 'ManifestFirstMapping',
37
- 'DiscoveryFirstMapping',
38
- 'HybridMapping',
39
- # Policy strategies
40
- 'PermissivePolicy',
41
- 'AllowListPolicy',
42
- 'DenyListPolicy',
43
- # Timing strategies
44
- 'SmartTiming',
45
- 'FullTiming',
46
- 'CleanTiming',
47
- 'TemporaryTiming',
48
- # Execution strategies
49
- 'PipExecution',
50
- 'WheelExecution',
51
- 'CachedExecution',
52
- 'AsyncExecution',
53
- # Discovery strategies
54
- 'FileBasedDiscovery',
55
- 'ManifestBasedDiscovery',
56
- 'HybridDiscovery',
57
- ]
@@ -1,129 +0,0 @@
1
- """
2
- File-Based Discovery Strategy
3
-
4
- Company: eXonware.com
5
- Author: Eng. Muhammad AlShehri
6
- Email: connect@exonware.com
7
-
8
- Generation Date: 15-Nov-2025
9
-
10
- File-based discovery - discovers dependencies from project files.
11
- """
12
-
13
- from pathlib import Path
14
- from typing import Optional, Any
15
- from ...package.base import ADiscoveryStrategy
16
-
17
- class FileBasedDiscovery(ADiscoveryStrategy):
18
- """
19
- File-based discovery strategy - discovers dependencies from project files.
20
-
21
- Reads from pyproject.toml, requirements.txt, setup.py, etc.
22
- """
23
-
24
- def __init__(self, project_root: Optional[Path] = None):
25
- """
26
- Initialize file-based discovery strategy.
27
-
28
- Args:
29
- project_root: Project root directory (auto-detected if None)
30
- """
31
- self._project_root = project_root or self._detect_project_root()
32
-
33
- def _detect_project_root(self) -> Path:
34
- """Detect project root directory."""
35
- import os
36
- cwd = Path.cwd()
37
-
38
- # Look for common project markers
39
- markers = ['pyproject.toml', 'requirements.txt', 'setup.py', '.git']
40
- for marker in markers:
41
- current = cwd
42
- for _ in range(5): # Search up to 5 levels
43
- if (current / marker).exists():
44
- return current
45
- parent = current.parent
46
- if parent == current: # Reached filesystem root
47
- break
48
- current = parent
49
-
50
- return cwd
51
-
52
- def discover(self, project_root: Any = None) -> dict[str, str]:
53
- """
54
- Discover dependencies from project files.
55
-
56
- Args:
57
- project_root: Optional project root (uses instance root if None)
58
-
59
- Returns:
60
- Dict mapping import_name -> package_name
61
- """
62
- root = Path(project_root) if project_root else self._project_root
63
- dependencies = {}
64
-
65
- # Check pyproject.toml
66
- pyproject = root / "pyproject.toml"
67
- if pyproject.exists():
68
- try:
69
- import tomllib
70
- except ImportError:
71
- try:
72
- import tomli as tomllib
73
- except ImportError:
74
- tomllib = None
75
-
76
- if tomllib:
77
- with open(pyproject, 'rb') as f:
78
- data = tomllib.load(f)
79
- project = data.get('project', {})
80
- deps = project.get('dependencies', [])
81
- for dep in deps:
82
- # Parse dependency spec (e.g., "pandas>=1.0" -> "pandas")
83
- package_name = dep.split('>=')[0].split('==')[0].split('!=')[0].strip()
84
- # Use package name as import name (heuristic)
85
- import_name = package_name.replace('-', '_')
86
- dependencies[import_name] = package_name
87
-
88
- # Check requirements.txt
89
- requirements = root / "requirements.txt"
90
- if requirements.exists():
91
- with open(requirements, 'r', encoding='utf-8') as f:
92
- for line in f:
93
- line = line.strip()
94
- if line and not line.startswith('#'):
95
- # Parse requirement (e.g., "pandas>=1.0" -> "pandas")
96
- package_name = line.split('>=')[0].split('==')[0].split('!=')[0].strip()
97
- import_name = package_name.replace('-', '_')
98
- dependencies[import_name] = package_name
99
-
100
- return dependencies
101
-
102
- def get_source(self, import_name: str) -> Optional[str]:
103
- """
104
- Get the source of a discovered dependency.
105
-
106
- Args:
107
- import_name: Import name to check
108
-
109
- Returns:
110
- Source file name or None
111
- """
112
- root = self._project_root
113
-
114
- # Check pyproject.toml
115
- pyproject = root / "pyproject.toml"
116
- if pyproject.exists():
117
- deps = self.discover()
118
- if import_name in deps:
119
- return "pyproject.toml"
120
-
121
- # Check requirements.txt
122
- requirements = root / "requirements.txt"
123
- if requirements.exists():
124
- deps = self.discover()
125
- if import_name in deps:
126
- return "requirements.txt"
127
-
128
- return None
129
-
@@ -1,84 +0,0 @@
1
- """
2
- Hybrid Discovery Strategy
3
-
4
- Company: eXonware.com
5
- Author: Eng. Muhammad AlShehri
6
- Email: connect@exonware.com
7
-
8
- Generation Date: 15-Nov-2025
9
-
10
- Hybrid discovery - combines file-based and manifest-based discovery.
11
- """
12
-
13
- from pathlib import Path
14
- from typing import Optional, Any
15
- from ...package.base import ADiscoveryStrategy
16
- from .package_discovery_file import FileBasedDiscovery
17
- from .package_discovery_manifest import ManifestBasedDiscovery
18
-
19
- class HybridDiscovery(ADiscoveryStrategy):
20
- """
21
- Hybrid discovery strategy - combines file-based and manifest-based discovery.
22
-
23
- Priority: Manifest > File-based
24
- """
25
-
26
- def __init__(self, package_name: str = 'default', project_root: Optional[Path] = None):
27
- """
28
- Initialize hybrid discovery strategy.
29
-
30
- Args:
31
- package_name: Package name for isolation
32
- project_root: Project root directory (auto-detected if None)
33
- """
34
- self._package_name = package_name
35
- self._project_root = project_root
36
- self._file_discovery = FileBasedDiscovery(project_root)
37
- self._manifest_discovery = ManifestBasedDiscovery(package_name, project_root)
38
-
39
- def discover(self, project_root: Any = None) -> dict[str, str]:
40
- """
41
- Discover dependencies from all sources.
42
-
43
- Priority: Manifest > File-based
44
-
45
- Args:
46
- project_root: Optional project root (uses instance root if None)
47
-
48
- Returns:
49
- Dict mapping import_name -> package_name
50
- """
51
- dependencies = {}
52
-
53
- # First, get file-based dependencies
54
- file_deps = self._file_discovery.discover(project_root)
55
- dependencies.update(file_deps)
56
-
57
- # Then, overlay manifest dependencies (takes precedence)
58
- manifest_deps = self._manifest_discovery.discover(project_root)
59
- dependencies.update(manifest_deps) # Manifest overrides file-based
60
-
61
- return dependencies
62
-
63
- def get_source(self, import_name: str) -> Optional[str]:
64
- """
65
- Get the source of a discovered dependency.
66
-
67
- Args:
68
- import_name: Import name to check
69
-
70
- Returns:
71
- Source file name or "hybrid"
72
- """
73
- # Check manifest first (higher priority)
74
- manifest_source = self._manifest_discovery.get_source(import_name)
75
- if manifest_source:
76
- return manifest_source
77
-
78
- # Check file-based
79
- file_source = self._file_discovery.get_source(import_name)
80
- if file_source:
81
- return file_source
82
-
83
- return None
84
-
@@ -1,101 +0,0 @@
1
- """
2
- Manifest-Based Discovery Strategy
3
-
4
- Company: eXonware.com
5
- Author: Eng. Muhammad AlShehri
6
- Email: connect@exonware.com
7
-
8
- Generation Date: 15-Nov-2025
9
-
10
- Manifest-based discovery - discovers dependencies from manifest files.
11
- """
12
-
13
- from pathlib import Path
14
- from typing import Optional, Any
15
- from ...package.base import ADiscoveryStrategy
16
-
17
- class ManifestBasedDiscovery(ADiscoveryStrategy):
18
- """
19
- Manifest-based discovery strategy - discovers dependencies from manifest files.
20
-
21
- Reads from xwlazy.manifest.json or pyproject.toml [tool.xwlazy] section.
22
- """
23
-
24
- def __init__(self, package_name: str = 'default', project_root: Optional[Path] = None):
25
- """
26
- Initialize manifest-based discovery strategy.
27
-
28
- Args:
29
- package_name: Package name for isolation
30
- project_root: Project root directory (auto-detected if None)
31
- """
32
- self._package_name = package_name
33
- self._project_root = project_root or self._detect_project_root()
34
-
35
- def _detect_project_root(self) -> Path:
36
- """Detect project root directory."""
37
- import os
38
- cwd = Path.cwd()
39
-
40
- # Look for manifest files
41
- markers = ['xwlazy.manifest.json', 'lazy.manifest.json', '.xwlazy.manifest.json', 'pyproject.toml']
42
- for marker in markers:
43
- current = cwd
44
- for _ in range(5): # Search up to 5 levels
45
- if (current / marker).exists():
46
- return current
47
- parent = current.parent
48
- if parent == current: # Reached filesystem root
49
- break
50
- current = parent
51
-
52
- return cwd
53
-
54
- def discover(self, project_root: Any = None) -> dict[str, str]:
55
- """
56
- Discover dependencies from manifest files.
57
-
58
- Args:
59
- project_root: Optional project root (uses instance root if None)
60
-
61
- Returns:
62
- Dict mapping import_name -> package_name
63
- """
64
- # Lazy import to avoid circular dependency
65
- from ...package.manifest import get_manifest_loader
66
-
67
- loader = get_manifest_loader()
68
- manifest = loader.get_manifest(self._package_name)
69
-
70
- if manifest and manifest.dependencies:
71
- return manifest.dependencies.copy()
72
-
73
- return {}
74
-
75
- def get_source(self, import_name: str) -> Optional[str]:
76
- """
77
- Get the source of a discovered dependency.
78
-
79
- Args:
80
- import_name: Import name to check
81
-
82
- Returns:
83
- Source file name (e.g., "xwlazy.manifest.json")
84
- """
85
- from ...package.manifest import get_manifest_loader
86
-
87
- loader = get_manifest_loader()
88
- manifest = loader.get_manifest(self._package_name)
89
-
90
- if manifest and manifest.dependencies and import_name in manifest.dependencies:
91
- # Try to find which file was used
92
- root = self._project_root
93
- for filename in ['xwlazy.manifest.json', 'lazy.manifest.json', '.xwlazy.manifest.json']:
94
- if (root / filename).exists():
95
- return filename
96
- # Check pyproject.toml
97
- if (root / "pyproject.toml").exists():
98
- return "pyproject.toml [tool.xwlazy]"
99
-
100
- return None
101
-