exonware-xwlazy 0.1.0.11__py3-none-any.whl → 0.1.0.19__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 +22 -0
- exonware/xwlazy/__init__.py +0 -0
- exonware/xwlazy/common/__init__.py +47 -0
- exonware/xwlazy/common/base.py +58 -0
- exonware/xwlazy/common/cache.py +506 -0
- exonware/xwlazy/common/logger.py +268 -0
- exonware/xwlazy/common/services/__init__.py +72 -0
- exonware/xwlazy/common/services/dependency_mapper.py +234 -0
- exonware/xwlazy/common/services/install_async_utils.py +169 -0
- exonware/xwlazy/common/services/install_cache_utils.py +257 -0
- exonware/xwlazy/common/services/keyword_detection.py +292 -0
- exonware/xwlazy/common/services/spec_cache.py +173 -0
- exonware/xwlazy/common/strategies/__init__.py +28 -0
- exonware/xwlazy/common/strategies/caching_dict.py +45 -0
- exonware/xwlazy/common/strategies/caching_installation.py +89 -0
- exonware/xwlazy/common/strategies/caching_lfu.py +67 -0
- exonware/xwlazy/common/strategies/caching_lru.py +64 -0
- exonware/xwlazy/common/strategies/caching_multitier.py +60 -0
- exonware/xwlazy/common/strategies/caching_ttl.py +60 -0
- {xwlazy/lazy → exonware/xwlazy}/config.py +52 -20
- exonware/xwlazy/contracts.py +1410 -0
- exonware/xwlazy/defs.py +397 -0
- xwlazy/lazy/lazy_errors.py → exonware/xwlazy/errors.py +21 -8
- exonware/xwlazy/facade.py +1049 -0
- exonware/xwlazy/module/__init__.py +18 -0
- exonware/xwlazy/module/base.py +569 -0
- exonware/xwlazy/module/data.py +17 -0
- exonware/xwlazy/module/facade.py +247 -0
- exonware/xwlazy/module/importer_engine.py +2161 -0
- exonware/xwlazy/module/strategies/__init__.py +22 -0
- exonware/xwlazy/module/strategies/module_helper_lazy.py +94 -0
- exonware/xwlazy/module/strategies/module_helper_simple.py +66 -0
- exonware/xwlazy/module/strategies/module_manager_advanced.py +112 -0
- exonware/xwlazy/module/strategies/module_manager_simple.py +96 -0
- exonware/xwlazy/package/__init__.py +18 -0
- exonware/xwlazy/package/base.py +807 -0
- xwlazy/lazy/host_conf.py → exonware/xwlazy/package/conf.py +62 -10
- exonware/xwlazy/package/data.py +17 -0
- exonware/xwlazy/package/facade.py +481 -0
- exonware/xwlazy/package/services/__init__.py +84 -0
- exonware/xwlazy/package/services/async_install_handle.py +89 -0
- exonware/xwlazy/package/services/config_manager.py +246 -0
- exonware/xwlazy/package/services/discovery.py +374 -0
- {xwlazy/lazy → exonware/xwlazy/package/services}/host_packages.py +43 -16
- exonware/xwlazy/package/services/install_async.py +278 -0
- exonware/xwlazy/package/services/install_cache.py +146 -0
- exonware/xwlazy/package/services/install_interactive.py +60 -0
- exonware/xwlazy/package/services/install_policy.py +158 -0
- exonware/xwlazy/package/services/install_registry.py +56 -0
- exonware/xwlazy/package/services/install_result.py +17 -0
- exonware/xwlazy/package/services/install_sbom.py +154 -0
- exonware/xwlazy/package/services/install_utils.py +83 -0
- exonware/xwlazy/package/services/installer_engine.py +408 -0
- exonware/xwlazy/package/services/lazy_installer.py +720 -0
- {xwlazy/lazy → exonware/xwlazy/package/services}/manifest.py +42 -25
- exonware/xwlazy/package/services/strategy_registry.py +188 -0
- exonware/xwlazy/package/strategies/__init__.py +57 -0
- exonware/xwlazy/package/strategies/package_discovery_file.py +130 -0
- exonware/xwlazy/package/strategies/package_discovery_hybrid.py +85 -0
- exonware/xwlazy/package/strategies/package_discovery_manifest.py +102 -0
- exonware/xwlazy/package/strategies/package_execution_async.py +114 -0
- exonware/xwlazy/package/strategies/package_execution_cached.py +91 -0
- exonware/xwlazy/package/strategies/package_execution_pip.py +100 -0
- exonware/xwlazy/package/strategies/package_execution_wheel.py +107 -0
- exonware/xwlazy/package/strategies/package_mapping_discovery_first.py +101 -0
- exonware/xwlazy/package/strategies/package_mapping_hybrid.py +106 -0
- exonware/xwlazy/package/strategies/package_mapping_manifest_first.py +101 -0
- exonware/xwlazy/package/strategies/package_policy_allow_list.py +58 -0
- exonware/xwlazy/package/strategies/package_policy_deny_list.py +58 -0
- exonware/xwlazy/package/strategies/package_policy_permissive.py +47 -0
- exonware/xwlazy/package/strategies/package_timing_clean.py +68 -0
- exonware/xwlazy/package/strategies/package_timing_full.py +67 -0
- exonware/xwlazy/package/strategies/package_timing_smart.py +69 -0
- exonware/xwlazy/package/strategies/package_timing_temporary.py +67 -0
- exonware/xwlazy/runtime/__init__.py +18 -0
- exonware/xwlazy/runtime/adaptive_learner.py +131 -0
- exonware/xwlazy/runtime/base.py +276 -0
- exonware/xwlazy/runtime/facade.py +95 -0
- exonware/xwlazy/runtime/intelligent_selector.py +173 -0
- exonware/xwlazy/runtime/metrics.py +64 -0
- exonware/xwlazy/runtime/performance.py +39 -0
- exonware/xwlazy/version.py +2 -2
- {exonware_xwlazy-0.1.0.11.dist-info → exonware_xwlazy-0.1.0.19.dist-info}/METADATA +86 -10
- exonware_xwlazy-0.1.0.19.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
- /xwlazy/lazy/lazy_state.py → /exonware/xwlazy/common/services/state_manager.py +0 -0
- {exonware_xwlazy-0.1.0.11.dist-info → exonware_xwlazy-0.1.0.19.dist-info}/WHEEL +0 -0
- {exonware_xwlazy-0.1.0.11.dist-info → exonware_xwlazy-0.1.0.19.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Installation Async Utilities
|
|
3
|
+
|
|
4
|
+
Company: eXonware.com
|
|
5
|
+
Author: Eng. Muhammad AlShehri
|
|
6
|
+
Email: connect@exonware.com
|
|
7
|
+
Version: 0.1.0.19
|
|
8
|
+
Generation Date: 15-Nov-2025
|
|
9
|
+
|
|
10
|
+
Shared utilities for async installation operations.
|
|
11
|
+
Used by both execution strategies and LazyInstaller.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
import sys
|
|
16
|
+
import json
|
|
17
|
+
import asyncio
|
|
18
|
+
import subprocess
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
# Lazy imports
|
|
22
|
+
def _get_logger():
|
|
23
|
+
"""Get logger (lazy import to avoid circular dependency)."""
|
|
24
|
+
from ..logger import get_logger
|
|
25
|
+
return get_logger("xwlazy.install_async_utils")
|
|
26
|
+
|
|
27
|
+
logger = None
|
|
28
|
+
|
|
29
|
+
def _ensure_logging_initialized():
|
|
30
|
+
"""Ensure logging utilities are initialized."""
|
|
31
|
+
global logger
|
|
32
|
+
if logger is None:
|
|
33
|
+
logger = _get_logger()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
async def get_package_size_mb(package_name: str) -> Optional[float]:
|
|
37
|
+
"""
|
|
38
|
+
Get package size in MB by checking pip show or download size.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
package_name: Package name to check
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Size in MB or None if cannot determine
|
|
45
|
+
"""
|
|
46
|
+
try:
|
|
47
|
+
process = await asyncio.create_subprocess_exec(
|
|
48
|
+
sys.executable, '-m', 'pip', 'show', package_name,
|
|
49
|
+
stdout=asyncio.subprocess.PIPE,
|
|
50
|
+
stderr=asyncio.subprocess.PIPE
|
|
51
|
+
)
|
|
52
|
+
stdout, _ = await process.communicate()
|
|
53
|
+
|
|
54
|
+
if process.returncode == 0:
|
|
55
|
+
output = stdout.decode()
|
|
56
|
+
for line in output.split('\n'):
|
|
57
|
+
if line.startswith('Location:'):
|
|
58
|
+
location = line.split(':', 1)[1].strip()
|
|
59
|
+
try:
|
|
60
|
+
total_size = 0
|
|
61
|
+
for dirpath, dirnames, filenames in os.walk(location):
|
|
62
|
+
for filename in filenames:
|
|
63
|
+
filepath = os.path.join(dirpath, filename)
|
|
64
|
+
if os.path.exists(filepath):
|
|
65
|
+
total_size += os.path.getsize(filepath)
|
|
66
|
+
return total_size / (1024 * 1024)
|
|
67
|
+
except Exception:
|
|
68
|
+
pass
|
|
69
|
+
except Exception:
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
# Fallback: Try to get download size from PyPI
|
|
73
|
+
try:
|
|
74
|
+
import urllib.request
|
|
75
|
+
url = f"https://pypi.org/pypi/{package_name}/json"
|
|
76
|
+
with urllib.request.urlopen(url, timeout=5) as response:
|
|
77
|
+
data = json.loads(response.read())
|
|
78
|
+
if 'urls' in data and data['urls']:
|
|
79
|
+
latest = data['urls'][0]
|
|
80
|
+
if 'size' in latest:
|
|
81
|
+
return latest['size'] / (1024 * 1024)
|
|
82
|
+
except Exception:
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
async def async_install_package(
|
|
89
|
+
package_name: str,
|
|
90
|
+
policy_args: Optional[list] = None
|
|
91
|
+
) -> tuple[bool, Optional[str]]:
|
|
92
|
+
"""
|
|
93
|
+
Install a package asynchronously using asyncio subprocess.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
package_name: Package name to install
|
|
97
|
+
policy_args: Optional policy arguments (index URLs, trusted hosts, etc.)
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Tuple of (success: bool, error_message: Optional[str])
|
|
101
|
+
"""
|
|
102
|
+
_ensure_logging_initialized()
|
|
103
|
+
try:
|
|
104
|
+
pip_args = [sys.executable, '-m', 'pip', 'install']
|
|
105
|
+
if policy_args:
|
|
106
|
+
pip_args.extend(policy_args)
|
|
107
|
+
pip_args.append(package_name)
|
|
108
|
+
|
|
109
|
+
process = await asyncio.create_subprocess_exec(
|
|
110
|
+
*pip_args,
|
|
111
|
+
stdout=asyncio.subprocess.PIPE,
|
|
112
|
+
stderr=asyncio.subprocess.PIPE
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
stdout, stderr = await process.communicate()
|
|
116
|
+
|
|
117
|
+
if process.returncode == 0:
|
|
118
|
+
return True, None
|
|
119
|
+
else:
|
|
120
|
+
error_msg = stderr.decode() if stderr else "Unknown error"
|
|
121
|
+
logger.error(f"Failed to install {package_name}: {error_msg}")
|
|
122
|
+
return False, error_msg
|
|
123
|
+
except Exception as e:
|
|
124
|
+
logger.error(f"Error in async install of {package_name}: {e}")
|
|
125
|
+
return False, str(e)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
async def async_uninstall_package(
|
|
129
|
+
package_name: str,
|
|
130
|
+
quiet: bool = True
|
|
131
|
+
) -> bool:
|
|
132
|
+
"""
|
|
133
|
+
Uninstall a package asynchronously.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
package_name: Package name to uninstall
|
|
137
|
+
quiet: If True, suppress output
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
True if successful, False otherwise
|
|
141
|
+
"""
|
|
142
|
+
_ensure_logging_initialized()
|
|
143
|
+
try:
|
|
144
|
+
pip_args = [sys.executable, '-m', 'pip', 'uninstall', '-y', package_name]
|
|
145
|
+
|
|
146
|
+
process = await asyncio.create_subprocess_exec(
|
|
147
|
+
*pip_args,
|
|
148
|
+
stdout=asyncio.subprocess.PIPE if quiet else None,
|
|
149
|
+
stderr=asyncio.subprocess.PIPE if quiet else None
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
await process.communicate()
|
|
153
|
+
|
|
154
|
+
if process.returncode == 0:
|
|
155
|
+
if not quiet:
|
|
156
|
+
logger.info(f"Uninstalled {package_name}")
|
|
157
|
+
return True
|
|
158
|
+
return False
|
|
159
|
+
except Exception as e:
|
|
160
|
+
logger.debug(f"Failed to uninstall {package_name}: {e}")
|
|
161
|
+
return False
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
__all__ = [
|
|
165
|
+
'get_package_size_mb',
|
|
166
|
+
'async_install_package',
|
|
167
|
+
'async_uninstall_package',
|
|
168
|
+
]
|
|
169
|
+
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Installation Cache Utilities
|
|
3
|
+
|
|
4
|
+
Company: eXonware.com
|
|
5
|
+
Author: Eng. Muhammad AlShehri
|
|
6
|
+
Email: connect@exonware.com
|
|
7
|
+
Version: 0.1.0.19
|
|
8
|
+
Generation Date: 15-Nov-2025
|
|
9
|
+
|
|
10
|
+
Shared utilities for cache management (wheels, install trees).
|
|
11
|
+
Used by both execution strategies and LazyInstaller.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
import sys
|
|
16
|
+
import shutil
|
|
17
|
+
import sysconfig
|
|
18
|
+
import tempfile
|
|
19
|
+
import subprocess
|
|
20
|
+
import zipfile
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Optional, List
|
|
23
|
+
from contextlib import suppress
|
|
24
|
+
|
|
25
|
+
# Lazy imports
|
|
26
|
+
def _get_logger():
|
|
27
|
+
"""Get logger (lazy import to avoid circular dependency)."""
|
|
28
|
+
from ..logger import get_logger
|
|
29
|
+
return get_logger("xwlazy.install_cache_utils")
|
|
30
|
+
|
|
31
|
+
logger = None
|
|
32
|
+
|
|
33
|
+
# Environment variables
|
|
34
|
+
_DEFAULT_ASYNC_CACHE_DIR = Path(
|
|
35
|
+
os.environ.get(
|
|
36
|
+
"XWLAZY_ASYNC_CACHE_DIR",
|
|
37
|
+
os.path.join(os.path.expanduser("~"), ".xwlazy", "wheel-cache"),
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
def _ensure_logging_initialized():
|
|
42
|
+
"""Ensure logging utilities are initialized."""
|
|
43
|
+
global logger
|
|
44
|
+
if logger is None:
|
|
45
|
+
logger = _get_logger()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_default_cache_dir() -> Path:
|
|
49
|
+
"""Get the default cache directory."""
|
|
50
|
+
return _DEFAULT_ASYNC_CACHE_DIR
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_cache_dir(cache_dir: Optional[Path] = None) -> Path:
|
|
54
|
+
"""Get cache directory, creating it if necessary."""
|
|
55
|
+
if cache_dir is None:
|
|
56
|
+
cache_dir = _DEFAULT_ASYNC_CACHE_DIR
|
|
57
|
+
path = Path(cache_dir).expanduser()
|
|
58
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
59
|
+
return path
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_wheel_path(package_name: str, cache_dir: Optional[Path] = None) -> Path:
|
|
63
|
+
"""Get the cached wheel file path for a package."""
|
|
64
|
+
cache = get_cache_dir(cache_dir)
|
|
65
|
+
safe = package_name.replace("/", "_").replace("\\", "_").replace(":", "_")
|
|
66
|
+
return cache / f"{safe}.whl"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def get_install_tree_dir(package_name: str, cache_dir: Optional[Path] = None) -> Path:
|
|
70
|
+
"""Get the cached install directory for a package."""
|
|
71
|
+
cache = get_cache_dir(cache_dir)
|
|
72
|
+
safe = package_name.replace("/", "_").replace("\\", "_").replace(":", "_")
|
|
73
|
+
return cache / "installs" / safe
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def get_site_packages_dir() -> Path:
|
|
77
|
+
"""Get the site-packages directory."""
|
|
78
|
+
purelib = sysconfig.get_paths().get("purelib")
|
|
79
|
+
if not purelib:
|
|
80
|
+
purelib = sysconfig.get_paths().get("platlib", sys.prefix)
|
|
81
|
+
path = Path(purelib)
|
|
82
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
83
|
+
return path
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def pip_install_from_path(wheel_path: Path, policy_args: Optional[List[str]] = None) -> bool:
|
|
87
|
+
"""Install a wheel file using pip."""
|
|
88
|
+
try:
|
|
89
|
+
pip_args = [
|
|
90
|
+
sys.executable,
|
|
91
|
+
'-m',
|
|
92
|
+
'pip',
|
|
93
|
+
'install',
|
|
94
|
+
'--no-deps',
|
|
95
|
+
'--no-input',
|
|
96
|
+
'--disable-pip-version-check',
|
|
97
|
+
]
|
|
98
|
+
if policy_args:
|
|
99
|
+
pip_args.extend(policy_args)
|
|
100
|
+
pip_args.append(str(wheel_path))
|
|
101
|
+
result = subprocess.run(
|
|
102
|
+
pip_args,
|
|
103
|
+
capture_output=True,
|
|
104
|
+
text=True,
|
|
105
|
+
check=True,
|
|
106
|
+
)
|
|
107
|
+
return result.returncode == 0
|
|
108
|
+
except subprocess.CalledProcessError:
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def ensure_cached_wheel(
|
|
113
|
+
package_name: str,
|
|
114
|
+
policy_args: Optional[List[str]] = None,
|
|
115
|
+
cache_dir: Optional[Path] = None
|
|
116
|
+
) -> Optional[Path]:
|
|
117
|
+
"""Ensure a wheel is cached, downloading it if necessary."""
|
|
118
|
+
wheel_path = get_wheel_path(package_name, cache_dir)
|
|
119
|
+
if wheel_path.exists():
|
|
120
|
+
return wheel_path
|
|
121
|
+
|
|
122
|
+
cache = get_cache_dir(cache_dir)
|
|
123
|
+
try:
|
|
124
|
+
pip_args = [
|
|
125
|
+
sys.executable,
|
|
126
|
+
'-m',
|
|
127
|
+
'pip',
|
|
128
|
+
'wheel',
|
|
129
|
+
'--no-deps',
|
|
130
|
+
'--disable-pip-version-check',
|
|
131
|
+
]
|
|
132
|
+
if policy_args:
|
|
133
|
+
pip_args.extend(policy_args)
|
|
134
|
+
pip_args.extend(['--wheel-dir', str(cache), package_name])
|
|
135
|
+
result = subprocess.run(
|
|
136
|
+
pip_args,
|
|
137
|
+
capture_output=True,
|
|
138
|
+
text=True,
|
|
139
|
+
check=True,
|
|
140
|
+
)
|
|
141
|
+
if result.returncode != 0:
|
|
142
|
+
return None
|
|
143
|
+
candidates = sorted(cache.glob("*.whl"), key=lambda p: p.stat().st_mtime, reverse=True)
|
|
144
|
+
if not candidates:
|
|
145
|
+
return None
|
|
146
|
+
primary = candidates[0]
|
|
147
|
+
if wheel_path.exists():
|
|
148
|
+
with suppress(Exception):
|
|
149
|
+
wheel_path.unlink()
|
|
150
|
+
primary.rename(wheel_path)
|
|
151
|
+
for leftover in candidates[1:]:
|
|
152
|
+
with suppress(Exception):
|
|
153
|
+
leftover.unlink()
|
|
154
|
+
return wheel_path
|
|
155
|
+
except subprocess.CalledProcessError:
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def install_from_cached_tree(
|
|
160
|
+
package_name: str,
|
|
161
|
+
cache_dir: Optional[Path] = None
|
|
162
|
+
) -> bool:
|
|
163
|
+
"""Install from a cached install tree."""
|
|
164
|
+
_ensure_logging_initialized()
|
|
165
|
+
src = get_install_tree_dir(package_name, cache_dir)
|
|
166
|
+
if not src.exists() or not any(src.iterdir()):
|
|
167
|
+
return False
|
|
168
|
+
target_root = get_site_packages_dir()
|
|
169
|
+
try:
|
|
170
|
+
for item in src.iterdir():
|
|
171
|
+
dest = target_root / item.name
|
|
172
|
+
if dest.exists():
|
|
173
|
+
if dest.is_dir():
|
|
174
|
+
shutil.rmtree(dest, ignore_errors=True)
|
|
175
|
+
else:
|
|
176
|
+
with suppress(FileNotFoundError):
|
|
177
|
+
dest.unlink()
|
|
178
|
+
if item.is_dir():
|
|
179
|
+
shutil.copytree(item, dest)
|
|
180
|
+
else:
|
|
181
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
182
|
+
shutil.copy2(item, dest)
|
|
183
|
+
return True
|
|
184
|
+
except Exception as exc:
|
|
185
|
+
logger.debug("Cached tree install failed for %s: %s", package_name, exc)
|
|
186
|
+
return False
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def materialize_cached_tree(
|
|
190
|
+
package_name: str,
|
|
191
|
+
wheel_path: Path,
|
|
192
|
+
cache_dir: Optional[Path] = None
|
|
193
|
+
) -> None:
|
|
194
|
+
"""Materialize a cached install tree from a wheel file."""
|
|
195
|
+
_ensure_logging_initialized()
|
|
196
|
+
if not wheel_path or not wheel_path.exists():
|
|
197
|
+
return
|
|
198
|
+
target_dir = get_install_tree_dir(package_name, cache_dir)
|
|
199
|
+
if target_dir.exists() and any(target_dir.iterdir()):
|
|
200
|
+
return
|
|
201
|
+
parent = target_dir.parent
|
|
202
|
+
parent.mkdir(parents=True, exist_ok=True)
|
|
203
|
+
temp_dir = Path(
|
|
204
|
+
tempfile.mkdtemp(prefix="xwlazy-cache-", dir=str(parent))
|
|
205
|
+
)
|
|
206
|
+
try:
|
|
207
|
+
with zipfile.ZipFile(wheel_path, "r") as archive:
|
|
208
|
+
archive.extractall(temp_dir)
|
|
209
|
+
if target_dir.exists():
|
|
210
|
+
shutil.rmtree(target_dir, ignore_errors=True)
|
|
211
|
+
shutil.move(str(temp_dir), str(target_dir))
|
|
212
|
+
except Exception as exc:
|
|
213
|
+
logger.debug("Failed to materialize cached tree for %s: %s", package_name, exc)
|
|
214
|
+
with suppress(Exception):
|
|
215
|
+
shutil.rmtree(temp_dir, ignore_errors=True)
|
|
216
|
+
else:
|
|
217
|
+
return
|
|
218
|
+
finally:
|
|
219
|
+
if temp_dir.exists():
|
|
220
|
+
shutil.rmtree(temp_dir, ignore_errors=True)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def has_cached_install_tree(
|
|
224
|
+
package_name: str,
|
|
225
|
+
cache_dir: Optional[Path] = None
|
|
226
|
+
) -> bool:
|
|
227
|
+
"""Check if a cached install tree exists."""
|
|
228
|
+
target = get_install_tree_dir(package_name, cache_dir)
|
|
229
|
+
return target.exists() and any(target.iterdir())
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def install_from_cached_wheel(
|
|
233
|
+
package_name: str,
|
|
234
|
+
policy_args: Optional[List[str]] = None,
|
|
235
|
+
cache_dir: Optional[Path] = None
|
|
236
|
+
) -> bool:
|
|
237
|
+
"""Install from a cached wheel file."""
|
|
238
|
+
wheel_path = get_wheel_path(package_name, cache_dir)
|
|
239
|
+
if not wheel_path.exists():
|
|
240
|
+
return False
|
|
241
|
+
return pip_install_from_path(wheel_path, policy_args)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
__all__ = [
|
|
245
|
+
'get_default_cache_dir',
|
|
246
|
+
'get_cache_dir',
|
|
247
|
+
'get_wheel_path',
|
|
248
|
+
'get_install_tree_dir',
|
|
249
|
+
'get_site_packages_dir',
|
|
250
|
+
'pip_install_from_path',
|
|
251
|
+
'ensure_cached_wheel',
|
|
252
|
+
'install_from_cached_tree',
|
|
253
|
+
'materialize_cached_tree',
|
|
254
|
+
'has_cached_install_tree',
|
|
255
|
+
'install_from_cached_wheel',
|
|
256
|
+
]
|
|
257
|
+
|