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.
- exonware/__init__.py +26 -0
- exonware/xwlazy/__init__.py +0 -0
- exonware/xwlazy/common/__init__.py +47 -0
- exonware/xwlazy/common/base.py +56 -0
- exonware/xwlazy/common/cache.py +504 -0
- exonware/xwlazy/common/logger.py +257 -0
- exonware/xwlazy/common/services/__init__.py +72 -0
- exonware/xwlazy/common/services/dependency_mapper.py +232 -0
- exonware/xwlazy/common/services/install_async_utils.py +165 -0
- exonware/xwlazy/common/services/install_cache_utils.py +245 -0
- exonware/xwlazy/common/services/keyword_detection.py +283 -0
- exonware/xwlazy/common/services/spec_cache.py +165 -0
- xwlazy/lazy/lazy_state.py → exonware/xwlazy/common/services/state_manager.py +0 -2
- exonware/xwlazy/common/strategies/__init__.py +28 -0
- exonware/xwlazy/common/strategies/caching_dict.py +44 -0
- exonware/xwlazy/common/strategies/caching_installation.py +88 -0
- exonware/xwlazy/common/strategies/caching_lfu.py +66 -0
- exonware/xwlazy/common/strategies/caching_lru.py +63 -0
- exonware/xwlazy/common/strategies/caching_multitier.py +59 -0
- exonware/xwlazy/common/strategies/caching_ttl.py +59 -0
- {xwlazy/lazy → exonware/xwlazy}/config.py +51 -21
- exonware/xwlazy/contracts.py +1396 -0
- exonware/xwlazy/defs.py +378 -0
- xwlazy/lazy/lazy_errors.py → exonware/xwlazy/errors.py +21 -16
- exonware/xwlazy/facade.py +991 -0
- exonware/xwlazy/module/__init__.py +18 -0
- exonware/xwlazy/module/base.py +565 -0
- exonware/xwlazy/module/data.py +17 -0
- exonware/xwlazy/module/facade.py +246 -0
- exonware/xwlazy/module/importer_engine.py +2117 -0
- exonware/xwlazy/module/strategies/__init__.py +22 -0
- exonware/xwlazy/module/strategies/module_helper_lazy.py +93 -0
- exonware/xwlazy/module/strategies/module_helper_simple.py +65 -0
- exonware/xwlazy/module/strategies/module_manager_advanced.py +111 -0
- exonware/xwlazy/module/strategies/module_manager_simple.py +95 -0
- exonware/xwlazy/package/__init__.py +18 -0
- exonware/xwlazy/package/base.py +798 -0
- xwlazy/lazy/host_conf.py → exonware/xwlazy/package/conf.py +61 -16
- exonware/xwlazy/package/data.py +17 -0
- exonware/xwlazy/package/facade.py +480 -0
- exonware/xwlazy/package/services/__init__.py +84 -0
- exonware/xwlazy/package/services/async_install_handle.py +87 -0
- exonware/xwlazy/package/services/config_manager.py +245 -0
- exonware/xwlazy/package/services/discovery.py +370 -0
- {xwlazy/lazy → exonware/xwlazy/package/services}/host_packages.py +43 -20
- exonware/xwlazy/package/services/install_async.py +277 -0
- exonware/xwlazy/package/services/install_cache.py +145 -0
- exonware/xwlazy/package/services/install_interactive.py +59 -0
- exonware/xwlazy/package/services/install_policy.py +156 -0
- exonware/xwlazy/package/services/install_registry.py +54 -0
- exonware/xwlazy/package/services/install_result.py +17 -0
- exonware/xwlazy/package/services/install_sbom.py +153 -0
- exonware/xwlazy/package/services/install_utils.py +79 -0
- exonware/xwlazy/package/services/installer_engine.py +406 -0
- exonware/xwlazy/package/services/lazy_installer.py +718 -0
- {xwlazy/lazy → exonware/xwlazy/package/services}/manifest.py +40 -33
- exonware/xwlazy/package/services/strategy_registry.py +186 -0
- exonware/xwlazy/package/strategies/__init__.py +57 -0
- exonware/xwlazy/package/strategies/package_discovery_file.py +129 -0
- exonware/xwlazy/package/strategies/package_discovery_hybrid.py +84 -0
- exonware/xwlazy/package/strategies/package_discovery_manifest.py +101 -0
- exonware/xwlazy/package/strategies/package_execution_async.py +113 -0
- exonware/xwlazy/package/strategies/package_execution_cached.py +90 -0
- exonware/xwlazy/package/strategies/package_execution_pip.py +99 -0
- exonware/xwlazy/package/strategies/package_execution_wheel.py +106 -0
- exonware/xwlazy/package/strategies/package_mapping_discovery_first.py +100 -0
- exonware/xwlazy/package/strategies/package_mapping_hybrid.py +105 -0
- exonware/xwlazy/package/strategies/package_mapping_manifest_first.py +100 -0
- exonware/xwlazy/package/strategies/package_policy_allow_list.py +57 -0
- exonware/xwlazy/package/strategies/package_policy_deny_list.py +57 -0
- exonware/xwlazy/package/strategies/package_policy_permissive.py +46 -0
- exonware/xwlazy/package/strategies/package_timing_clean.py +67 -0
- exonware/xwlazy/package/strategies/package_timing_full.py +66 -0
- exonware/xwlazy/package/strategies/package_timing_smart.py +68 -0
- exonware/xwlazy/package/strategies/package_timing_temporary.py +66 -0
- exonware/xwlazy/runtime/__init__.py +18 -0
- exonware/xwlazy/runtime/adaptive_learner.py +129 -0
- exonware/xwlazy/runtime/base.py +274 -0
- exonware/xwlazy/runtime/facade.py +94 -0
- exonware/xwlazy/runtime/intelligent_selector.py +170 -0
- exonware/xwlazy/runtime/metrics.py +60 -0
- exonware/xwlazy/runtime/performance.py +37 -0
- exonware/xwlazy/version.py +2 -2
- {exonware_xwlazy-0.1.0.11.dist-info → exonware_xwlazy-0.1.0.20.dist-info}/METADATA +89 -11
- exonware_xwlazy-0.1.0.20.dist-info/RECORD +87 -0
- exonware_xwlazy-0.1.0.11.dist-info/RECORD +0 -20
- xwlazy/__init__.py +0 -34
- xwlazy/lazy/__init__.py +0 -301
- xwlazy/lazy/bootstrap.py +0 -106
- xwlazy/lazy/lazy_base.py +0 -465
- xwlazy/lazy/lazy_contracts.py +0 -290
- xwlazy/lazy/lazy_core.py +0 -3727
- xwlazy/lazy/logging_utils.py +0 -194
- xwlazy/version.py +0 -77
- {exonware_xwlazy-0.1.0.11.dist-info → exonware_xwlazy-0.1.0.20.dist-info}/WHEEL +0 -0
- {exonware_xwlazy-0.1.0.11.dist-info → exonware_xwlazy-0.1.0.20.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Async Execution 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
|
+
Async execution strategy - async pip install using asyncio.
|
|
11
|
+
Uses shared utilities from common/services/install_async_utils.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import sys
|
|
15
|
+
import asyncio
|
|
16
|
+
import subprocess
|
|
17
|
+
from typing import List, Any
|
|
18
|
+
from ...package.base import AInstallExecutionStrategy
|
|
19
|
+
from ...package.services.install_result import InstallResult, InstallStatus
|
|
20
|
+
from ...common.services.install_async_utils import async_install_package
|
|
21
|
+
|
|
22
|
+
class AsyncExecution(AInstallExecutionStrategy):
|
|
23
|
+
"""
|
|
24
|
+
Async execution strategy - installs packages asynchronously using asyncio.
|
|
25
|
+
|
|
26
|
+
Uses asyncio subprocess for non-blocking installation.
|
|
27
|
+
Uses shared utilities from common/services/install_async_utils.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def execute_install(self, package_name: str, policy_args: List[str]) -> Any:
|
|
31
|
+
"""
|
|
32
|
+
Execute installation asynchronously.
|
|
33
|
+
|
|
34
|
+
Note: This is a synchronous wrapper that runs async code.
|
|
35
|
+
For true async, use the async methods directly.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
package_name: Package name to install
|
|
39
|
+
policy_args: Policy arguments
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
InstallResult with success status
|
|
43
|
+
"""
|
|
44
|
+
try:
|
|
45
|
+
# Run async install in event loop
|
|
46
|
+
loop = asyncio.get_event_loop()
|
|
47
|
+
if loop.is_running():
|
|
48
|
+
# If loop is running, use run_coroutine_threadsafe
|
|
49
|
+
future = asyncio.run_coroutine_threadsafe(
|
|
50
|
+
self._async_install(package_name, policy_args),
|
|
51
|
+
loop
|
|
52
|
+
)
|
|
53
|
+
return future.result(timeout=600) # 10 min timeout
|
|
54
|
+
else:
|
|
55
|
+
# If no loop running, use asyncio.run
|
|
56
|
+
return asyncio.run(self._async_install(package_name, policy_args))
|
|
57
|
+
except Exception as e:
|
|
58
|
+
return InstallResult(
|
|
59
|
+
package_name=package_name,
|
|
60
|
+
success=False,
|
|
61
|
+
status=InstallStatus.FAILED,
|
|
62
|
+
error=str(e)
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
async def _async_install(self, package_name: str, policy_args: List[str]) -> InstallResult:
|
|
66
|
+
"""
|
|
67
|
+
Async installation implementation.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
package_name: Package name to install
|
|
71
|
+
policy_args: Policy arguments
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
InstallResult with success status
|
|
75
|
+
"""
|
|
76
|
+
success, error_msg = await async_install_package(package_name, policy_args)
|
|
77
|
+
|
|
78
|
+
if success:
|
|
79
|
+
return InstallResult(
|
|
80
|
+
package_name=package_name,
|
|
81
|
+
success=True,
|
|
82
|
+
status=InstallStatus.SUCCESS,
|
|
83
|
+
source="pip-async"
|
|
84
|
+
)
|
|
85
|
+
else:
|
|
86
|
+
return InstallResult(
|
|
87
|
+
package_name=package_name,
|
|
88
|
+
success=False,
|
|
89
|
+
status=InstallStatus.FAILED,
|
|
90
|
+
error=error_msg or "Unknown error"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
def execute_uninstall(self, package_name: str) -> bool:
|
|
94
|
+
"""
|
|
95
|
+
Execute uninstallation using pip.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
package_name: Package name to uninstall
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
True if successful, False otherwise
|
|
102
|
+
"""
|
|
103
|
+
try:
|
|
104
|
+
result = subprocess.run(
|
|
105
|
+
[sys.executable, '-m', 'pip', 'uninstall', '-y', package_name],
|
|
106
|
+
capture_output=True,
|
|
107
|
+
text=True,
|
|
108
|
+
check=True
|
|
109
|
+
)
|
|
110
|
+
return result.returncode == 0
|
|
111
|
+
except Exception:
|
|
112
|
+
return False
|
|
113
|
+
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cached Execution 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
|
+
Cached execution strategy - install from cached tree.
|
|
11
|
+
Uses shared utilities from common/services/install_cache_utils.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import sys
|
|
15
|
+
import subprocess
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import List, Any, Optional
|
|
18
|
+
from ...package.base import AInstallExecutionStrategy
|
|
19
|
+
from ...package.services.install_result import InstallResult, InstallStatus
|
|
20
|
+
from ...common.services.install_cache_utils import (
|
|
21
|
+
get_cache_dir,
|
|
22
|
+
install_from_cached_tree,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
class CachedExecution(AInstallExecutionStrategy):
|
|
26
|
+
"""
|
|
27
|
+
Cached execution strategy - installs packages from cached installation tree.
|
|
28
|
+
|
|
29
|
+
Fastest installation method - copies from pre-extracted cache.
|
|
30
|
+
Uses shared utilities from common/services/install_cache_utils.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self, cache_dir: Optional[Path] = None):
|
|
34
|
+
"""
|
|
35
|
+
Initialize cached execution strategy.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
cache_dir: Optional cache directory for installation trees
|
|
39
|
+
"""
|
|
40
|
+
self._cache_dir = cache_dir
|
|
41
|
+
|
|
42
|
+
def execute_install(self, package_name: str, policy_args: List[str]) -> Any:
|
|
43
|
+
"""
|
|
44
|
+
Execute installation from cached tree.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
package_name: Package name to install
|
|
48
|
+
policy_args: Policy arguments (ignored for cached install)
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
InstallResult with success status
|
|
52
|
+
"""
|
|
53
|
+
success = install_from_cached_tree(package_name, self._cache_dir)
|
|
54
|
+
|
|
55
|
+
if success:
|
|
56
|
+
return InstallResult(
|
|
57
|
+
package_name=package_name,
|
|
58
|
+
success=True,
|
|
59
|
+
status=InstallStatus.SUCCESS,
|
|
60
|
+
source="cache-tree"
|
|
61
|
+
)
|
|
62
|
+
else:
|
|
63
|
+
return InstallResult(
|
|
64
|
+
package_name=package_name,
|
|
65
|
+
success=False,
|
|
66
|
+
status=InstallStatus.FAILED,
|
|
67
|
+
error="Cached installation tree not found"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def execute_uninstall(self, package_name: str) -> bool:
|
|
71
|
+
"""
|
|
72
|
+
Execute uninstallation using pip.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
package_name: Package name to uninstall
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
True if successful, False otherwise
|
|
79
|
+
"""
|
|
80
|
+
try:
|
|
81
|
+
result = subprocess.run(
|
|
82
|
+
[sys.executable, '-m', 'pip', 'uninstall', '-y', package_name],
|
|
83
|
+
capture_output=True,
|
|
84
|
+
text=True,
|
|
85
|
+
check=True
|
|
86
|
+
)
|
|
87
|
+
return result.returncode == 0
|
|
88
|
+
except Exception:
|
|
89
|
+
return False
|
|
90
|
+
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pip Execution 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
|
+
Pip execution strategy - direct pip install.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import sys
|
|
14
|
+
import subprocess
|
|
15
|
+
from typing import List, Any
|
|
16
|
+
from ...package.base import AInstallExecutionStrategy
|
|
17
|
+
from ...package.services.install_result import InstallResult, InstallStatus
|
|
18
|
+
|
|
19
|
+
class PipExecution(AInstallExecutionStrategy):
|
|
20
|
+
"""
|
|
21
|
+
Pip execution strategy - installs packages directly using pip.
|
|
22
|
+
|
|
23
|
+
This is the default execution strategy that uses pip install.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def execute_install(self, package_name: str, policy_args: List[str]) -> Any:
|
|
27
|
+
"""
|
|
28
|
+
Execute installation using pip.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
package_name: Package name to install
|
|
32
|
+
policy_args: Policy arguments (index URLs, trusted hosts, etc.)
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
InstallResult with success status
|
|
36
|
+
"""
|
|
37
|
+
try:
|
|
38
|
+
pip_args = [sys.executable, '-m', 'pip', 'install']
|
|
39
|
+
if policy_args:
|
|
40
|
+
pip_args.extend(policy_args)
|
|
41
|
+
pip_args.append(package_name)
|
|
42
|
+
|
|
43
|
+
result = subprocess.run(
|
|
44
|
+
pip_args,
|
|
45
|
+
capture_output=True,
|
|
46
|
+
text=True,
|
|
47
|
+
check=True
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
if result.returncode == 0:
|
|
51
|
+
return InstallResult(
|
|
52
|
+
package_name=package_name,
|
|
53
|
+
success=True,
|
|
54
|
+
status=InstallStatus.SUCCESS,
|
|
55
|
+
source="pip"
|
|
56
|
+
)
|
|
57
|
+
else:
|
|
58
|
+
return InstallResult(
|
|
59
|
+
package_name=package_name,
|
|
60
|
+
success=False,
|
|
61
|
+
status=InstallStatus.FAILED,
|
|
62
|
+
error=result.stderr or "Unknown error"
|
|
63
|
+
)
|
|
64
|
+
except subprocess.CalledProcessError as e:
|
|
65
|
+
return InstallResult(
|
|
66
|
+
package_name=package_name,
|
|
67
|
+
success=False,
|
|
68
|
+
status=InstallStatus.FAILED,
|
|
69
|
+
error=e.stderr or str(e)
|
|
70
|
+
)
|
|
71
|
+
except Exception as e:
|
|
72
|
+
return InstallResult(
|
|
73
|
+
package_name=package_name,
|
|
74
|
+
success=False,
|
|
75
|
+
status=InstallStatus.FAILED,
|
|
76
|
+
error=str(e)
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def execute_uninstall(self, package_name: str) -> bool:
|
|
80
|
+
"""
|
|
81
|
+
Execute uninstallation using pip.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
package_name: Package name to uninstall
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
True if successful, False otherwise
|
|
88
|
+
"""
|
|
89
|
+
try:
|
|
90
|
+
result = subprocess.run(
|
|
91
|
+
[sys.executable, '-m', 'pip', 'uninstall', '-y', package_name],
|
|
92
|
+
capture_output=True,
|
|
93
|
+
text=True,
|
|
94
|
+
check=True
|
|
95
|
+
)
|
|
96
|
+
return result.returncode == 0
|
|
97
|
+
except Exception:
|
|
98
|
+
return False
|
|
99
|
+
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Wheel Execution 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
|
+
Wheel execution strategy - install from wheel files.
|
|
11
|
+
Uses shared utilities from common/services/install_cache_utils.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import sys
|
|
15
|
+
import subprocess
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import List, Any, Optional
|
|
18
|
+
from ...package.base import AInstallExecutionStrategy
|
|
19
|
+
from ...package.services.install_result import InstallResult, InstallStatus
|
|
20
|
+
from ...common.services.install_cache_utils import (
|
|
21
|
+
get_wheel_path,
|
|
22
|
+
ensure_cached_wheel,
|
|
23
|
+
pip_install_from_path,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
class WheelExecution(AInstallExecutionStrategy):
|
|
27
|
+
"""
|
|
28
|
+
Wheel execution strategy - installs packages from wheel files.
|
|
29
|
+
|
|
30
|
+
Downloads wheel first, then installs from local wheel file.
|
|
31
|
+
Uses shared utilities from common/services/install_cache_utils.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, cache_dir: Optional[Path] = None):
|
|
35
|
+
"""
|
|
36
|
+
Initialize wheel execution strategy.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
cache_dir: Optional cache directory for wheels
|
|
40
|
+
"""
|
|
41
|
+
self._cache_dir = cache_dir
|
|
42
|
+
|
|
43
|
+
def execute_install(self, package_name: str, policy_args: List[str]) -> Any:
|
|
44
|
+
"""
|
|
45
|
+
Execute installation from wheel file.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
package_name: Package name to install
|
|
49
|
+
policy_args: Policy arguments
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
InstallResult with success status
|
|
53
|
+
"""
|
|
54
|
+
# Check if wheel already exists
|
|
55
|
+
wheel_path = get_wheel_path(package_name, self._cache_dir)
|
|
56
|
+
|
|
57
|
+
if not wheel_path.exists():
|
|
58
|
+
# Download wheel first
|
|
59
|
+
wheel_path = ensure_cached_wheel(package_name, policy_args, self._cache_dir)
|
|
60
|
+
if wheel_path is None:
|
|
61
|
+
return InstallResult(
|
|
62
|
+
package_name=package_name,
|
|
63
|
+
success=False,
|
|
64
|
+
status=InstallStatus.FAILED,
|
|
65
|
+
error="Failed to download wheel"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Install from wheel
|
|
69
|
+
success = pip_install_from_path(wheel_path, policy_args)
|
|
70
|
+
|
|
71
|
+
if success:
|
|
72
|
+
return InstallResult(
|
|
73
|
+
package_name=package_name,
|
|
74
|
+
success=True,
|
|
75
|
+
status=InstallStatus.SUCCESS,
|
|
76
|
+
source="wheel"
|
|
77
|
+
)
|
|
78
|
+
else:
|
|
79
|
+
return InstallResult(
|
|
80
|
+
package_name=package_name,
|
|
81
|
+
success=False,
|
|
82
|
+
status=InstallStatus.FAILED,
|
|
83
|
+
error="Failed to install from wheel"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
def execute_uninstall(self, package_name: str) -> bool:
|
|
87
|
+
"""
|
|
88
|
+
Execute uninstallation using pip.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
package_name: Package name to uninstall
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
True if successful, False otherwise
|
|
95
|
+
"""
|
|
96
|
+
try:
|
|
97
|
+
result = subprocess.run(
|
|
98
|
+
[sys.executable, '-m', 'pip', 'uninstall', '-y', package_name],
|
|
99
|
+
capture_output=True,
|
|
100
|
+
text=True,
|
|
101
|
+
check=True
|
|
102
|
+
)
|
|
103
|
+
return result.returncode == 0
|
|
104
|
+
except Exception:
|
|
105
|
+
return False
|
|
106
|
+
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Discovery-First Mapping 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
|
+
Discovery-first mapping strategy - discovery takes precedence over manifest.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from typing import Optional, List
|
|
14
|
+
from ...package.base import AMappingStrategy
|
|
15
|
+
from ...package.services.manifest import get_manifest_loader
|
|
16
|
+
|
|
17
|
+
class DiscoveryFirstMapping(AMappingStrategy):
|
|
18
|
+
"""
|
|
19
|
+
Discovery-first mapping strategy.
|
|
20
|
+
|
|
21
|
+
Priority order:
|
|
22
|
+
1. Discovery mappings (automatic discovery - highest priority)
|
|
23
|
+
2. Manifest dependencies (explicit user configuration)
|
|
24
|
+
3. Common mappings (fallback)
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, package_name: str = 'default'):
|
|
28
|
+
"""
|
|
29
|
+
Initialize discovery-first mapping strategy.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
package_name: Package name for isolation
|
|
33
|
+
"""
|
|
34
|
+
self._package_name = package_name
|
|
35
|
+
self._discovery = None # Lazy init
|
|
36
|
+
|
|
37
|
+
def _get_discovery(self):
|
|
38
|
+
"""Get discovery instance (lazy init)."""
|
|
39
|
+
if self._discovery is None:
|
|
40
|
+
from ...package.services.discovery import LazyDiscovery
|
|
41
|
+
self._discovery = LazyDiscovery(self._package_name)
|
|
42
|
+
return self._discovery
|
|
43
|
+
|
|
44
|
+
def map_import_to_package(self, import_name: str) -> Optional[str]:
|
|
45
|
+
"""
|
|
46
|
+
Map import name to package name.
|
|
47
|
+
|
|
48
|
+
Priority: Discovery > Manifest > Common mappings
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
import_name: Import name (e.g., 'cv2')
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Package name (e.g., 'opencv-python') or None
|
|
55
|
+
"""
|
|
56
|
+
# Check discovery mappings FIRST
|
|
57
|
+
discovery = self._get_discovery()
|
|
58
|
+
discovery_mapping = discovery.get_import_package_mapping()
|
|
59
|
+
package = discovery_mapping.get(import_name)
|
|
60
|
+
if package:
|
|
61
|
+
return package
|
|
62
|
+
|
|
63
|
+
# Check manifest (fallback)
|
|
64
|
+
loader = get_manifest_loader()
|
|
65
|
+
manifest = loader.get_manifest(self._package_name)
|
|
66
|
+
if manifest:
|
|
67
|
+
package = manifest.get_dependency(import_name)
|
|
68
|
+
if package:
|
|
69
|
+
return package
|
|
70
|
+
|
|
71
|
+
# Fallback to common mappings
|
|
72
|
+
common_mappings = discovery.COMMON_MAPPINGS
|
|
73
|
+
return common_mappings.get(import_name)
|
|
74
|
+
|
|
75
|
+
def map_package_to_imports(self, package_name: str) -> List[str]:
|
|
76
|
+
"""
|
|
77
|
+
Map package name to possible import names.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
package_name: Package name (e.g., 'opencv-python')
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
List of possible import names (e.g., ['cv2'])
|
|
84
|
+
"""
|
|
85
|
+
# Check discovery mappings FIRST
|
|
86
|
+
discovery = self._get_discovery()
|
|
87
|
+
package_mapping = discovery.get_package_import_mapping()
|
|
88
|
+
imports = package_mapping.get(package_name, [])
|
|
89
|
+
|
|
90
|
+
# Also check manifest (reverse lookup)
|
|
91
|
+
loader = get_manifest_loader()
|
|
92
|
+
manifest = loader.get_manifest(self._package_name)
|
|
93
|
+
if manifest:
|
|
94
|
+
for import_name, pkg in manifest.dependencies.items():
|
|
95
|
+
if pkg.lower() == package_name.lower():
|
|
96
|
+
if import_name not in imports:
|
|
97
|
+
imports.append(import_name)
|
|
98
|
+
|
|
99
|
+
return imports if imports else [package_name]
|
|
100
|
+
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Hybrid Mapping 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 mapping strategy - tries both manifest and discovery, uses best match.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from typing import Optional, List
|
|
14
|
+
from ...package.base import AMappingStrategy
|
|
15
|
+
from ...package.services.manifest import get_manifest_loader
|
|
16
|
+
|
|
17
|
+
class HybridMapping(AMappingStrategy):
|
|
18
|
+
"""
|
|
19
|
+
Hybrid mapping strategy.
|
|
20
|
+
|
|
21
|
+
Tries both manifest and discovery, uses the best match.
|
|
22
|
+
If both exist, prefers the one that's more specific.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, package_name: str = 'default'):
|
|
26
|
+
"""
|
|
27
|
+
Initialize hybrid mapping strategy.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
package_name: Package name for isolation
|
|
31
|
+
"""
|
|
32
|
+
self._package_name = package_name
|
|
33
|
+
self._discovery = None # Lazy init
|
|
34
|
+
|
|
35
|
+
def _get_discovery(self):
|
|
36
|
+
"""Get discovery instance (lazy init)."""
|
|
37
|
+
if self._discovery is None:
|
|
38
|
+
from ...package.services.discovery import LazyDiscovery
|
|
39
|
+
self._discovery = LazyDiscovery(self._package_name)
|
|
40
|
+
return self._discovery
|
|
41
|
+
|
|
42
|
+
def map_import_to_package(self, import_name: str) -> Optional[str]:
|
|
43
|
+
"""
|
|
44
|
+
Map import name to package name.
|
|
45
|
+
|
|
46
|
+
Tries both manifest and discovery, prefers more specific match.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
import_name: Import name (e.g., 'cv2')
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Package name (e.g., 'opencv-python') or None
|
|
53
|
+
"""
|
|
54
|
+
# Try manifest first
|
|
55
|
+
loader = get_manifest_loader()
|
|
56
|
+
manifest = loader.get_manifest(self._package_name)
|
|
57
|
+
manifest_package = None
|
|
58
|
+
if manifest:
|
|
59
|
+
manifest_package = manifest.get_dependency(import_name)
|
|
60
|
+
|
|
61
|
+
# Try discovery
|
|
62
|
+
discovery = self._get_discovery()
|
|
63
|
+
discovery_mapping = discovery.get_import_package_mapping()
|
|
64
|
+
discovery_package = discovery_mapping.get(import_name)
|
|
65
|
+
|
|
66
|
+
# Prefer manifest if both exist (more explicit)
|
|
67
|
+
if manifest_package:
|
|
68
|
+
return manifest_package
|
|
69
|
+
|
|
70
|
+
if discovery_package:
|
|
71
|
+
return discovery_package
|
|
72
|
+
|
|
73
|
+
# Fallback to common mappings
|
|
74
|
+
common_mappings = discovery.COMMON_MAPPINGS
|
|
75
|
+
return common_mappings.get(import_name)
|
|
76
|
+
|
|
77
|
+
def map_package_to_imports(self, package_name: str) -> List[str]:
|
|
78
|
+
"""
|
|
79
|
+
Map package name to possible import names.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
package_name: Package name (e.g., 'opencv-python')
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
List of possible import names (e.g., ['cv2'])
|
|
86
|
+
"""
|
|
87
|
+
imports = []
|
|
88
|
+
|
|
89
|
+
# Check discovery mappings
|
|
90
|
+
discovery = self._get_discovery()
|
|
91
|
+
package_mapping = discovery.get_package_import_mapping()
|
|
92
|
+
discovery_imports = package_mapping.get(package_name, [])
|
|
93
|
+
imports.extend(discovery_imports)
|
|
94
|
+
|
|
95
|
+
# Check manifest (reverse lookup)
|
|
96
|
+
loader = get_manifest_loader()
|
|
97
|
+
manifest = loader.get_manifest(self._package_name)
|
|
98
|
+
if manifest:
|
|
99
|
+
for import_name, pkg in manifest.dependencies.items():
|
|
100
|
+
if pkg.lower() == package_name.lower():
|
|
101
|
+
if import_name not in imports:
|
|
102
|
+
imports.append(import_name)
|
|
103
|
+
|
|
104
|
+
return imports if imports else [package_name]
|
|
105
|
+
|