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,56 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Installer Registry
|
|
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
|
+
Registry to manage separate lazy installer instances per package.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import threading
|
|
14
|
+
from typing import Dict, TYPE_CHECKING
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from .lazy_installer import LazyInstaller
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class LazyInstallerRegistry:
|
|
21
|
+
"""Registry to manage separate lazy installer instances per package."""
|
|
22
|
+
_instances: Dict[str, 'LazyInstaller'] = {}
|
|
23
|
+
_lock = threading.RLock()
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def get_instance(cls, package_name: str = 'default') -> 'LazyInstaller':
|
|
27
|
+
"""
|
|
28
|
+
Get or create a lazy installer instance for a package.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
package_name: Package name for isolation
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
LazyInstaller instance for the package
|
|
35
|
+
"""
|
|
36
|
+
with cls._lock:
|
|
37
|
+
if package_name not in cls._instances:
|
|
38
|
+
# Lazy import to avoid circular dependency
|
|
39
|
+
from .lazy_installer import LazyInstaller
|
|
40
|
+
cls._instances[package_name] = LazyInstaller(package_name)
|
|
41
|
+
return cls._instances[package_name]
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def get_all_instances(cls) -> Dict[str, 'LazyInstaller']:
|
|
45
|
+
"""
|
|
46
|
+
Get all lazy installer instances.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Dict mapping package_name -> LazyInstaller
|
|
50
|
+
"""
|
|
51
|
+
with cls._lock:
|
|
52
|
+
return cls._instances.copy()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
__all__ = ['LazyInstallerRegistry']
|
|
56
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Install Result and Status
|
|
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
|
+
Re-export installation result types from defs.py for backward compatibility.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
# Re-export from defs.py
|
|
14
|
+
from ...defs import InstallStatus, InstallResult
|
|
15
|
+
|
|
16
|
+
__all__ = ['InstallStatus', 'InstallResult']
|
|
17
|
+
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SBOM and Audit Mixin
|
|
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
|
+
Mixin for SBOM generation and vulnerability auditing.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import sys
|
|
15
|
+
import subprocess
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Dict
|
|
19
|
+
|
|
20
|
+
from .install_policy import LazyInstallPolicy
|
|
21
|
+
|
|
22
|
+
# Lazy imports
|
|
23
|
+
def _get_logger():
|
|
24
|
+
"""Get logger (lazy import to avoid circular dependency)."""
|
|
25
|
+
from ...common.logger import get_logger
|
|
26
|
+
return get_logger("xwlazy.lazy_installer")
|
|
27
|
+
|
|
28
|
+
def _get_log_event():
|
|
29
|
+
"""Get log_event function (lazy import to avoid circular dependency)."""
|
|
30
|
+
from ...common.logger import log_event
|
|
31
|
+
return log_event
|
|
32
|
+
|
|
33
|
+
logger = None
|
|
34
|
+
_log = None
|
|
35
|
+
|
|
36
|
+
def _ensure_logging_initialized():
|
|
37
|
+
"""Ensure logging utilities are initialized."""
|
|
38
|
+
global logger, _log
|
|
39
|
+
if logger is None:
|
|
40
|
+
logger = _get_logger()
|
|
41
|
+
if _log is None:
|
|
42
|
+
_log = _get_log_event()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class SBOMAuditMixin:
|
|
46
|
+
"""Mixin for SBOM generation and vulnerability auditing."""
|
|
47
|
+
|
|
48
|
+
def _run_vulnerability_audit(self, package_name: str) -> None:
|
|
49
|
+
"""Run vulnerability audit on installed package using pip-audit."""
|
|
50
|
+
_ensure_logging_initialized()
|
|
51
|
+
try:
|
|
52
|
+
result = subprocess.run(
|
|
53
|
+
[sys.executable, '-m', 'pip_audit', '-r', '-', '--format', 'json'],
|
|
54
|
+
input=package_name,
|
|
55
|
+
capture_output=True,
|
|
56
|
+
text=True,
|
|
57
|
+
timeout=30
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
if result.returncode == 0:
|
|
61
|
+
_log("audit", f"Vulnerability audit passed for {package_name}")
|
|
62
|
+
else:
|
|
63
|
+
try:
|
|
64
|
+
audit_data = json.loads(result.stdout)
|
|
65
|
+
if audit_data.get('vulnerabilities'):
|
|
66
|
+
logger.warning(f"[SECURITY] Vulnerabilities found in {package_name}: {audit_data}")
|
|
67
|
+
print(f"[SECURITY WARNING] Package '{package_name}' has known vulnerabilities")
|
|
68
|
+
print(f"Run 'pip-audit' for details")
|
|
69
|
+
except json.JSONDecodeError:
|
|
70
|
+
logger.warning(f"Could not parse audit results for {package_name}")
|
|
71
|
+
except subprocess.TimeoutExpired:
|
|
72
|
+
logger.warning(f"Vulnerability audit timed out for {package_name}")
|
|
73
|
+
except Exception as e:
|
|
74
|
+
logger.debug(f"Vulnerability audit skipped for {package_name}: {e}")
|
|
75
|
+
|
|
76
|
+
def _update_lockfile(self, package_name: str) -> None:
|
|
77
|
+
"""Update lockfile with newly installed package."""
|
|
78
|
+
_ensure_logging_initialized()
|
|
79
|
+
lockfile_path = LazyInstallPolicy.get_lockfile_path(self._package_name) # type: ignore[attr-defined]
|
|
80
|
+
if not lockfile_path:
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
version = self._get_installed_version(package_name) # type: ignore[attr-defined]
|
|
85
|
+
if not version:
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
lockfile_path = Path(lockfile_path)
|
|
89
|
+
if lockfile_path.exists():
|
|
90
|
+
with open(lockfile_path, 'r', encoding='utf-8') as f:
|
|
91
|
+
lockdata = json.load(f)
|
|
92
|
+
else:
|
|
93
|
+
lockdata = {
|
|
94
|
+
"metadata": {
|
|
95
|
+
"generated_by": f"xwlazy-{self._package_name}", # type: ignore[attr-defined]
|
|
96
|
+
"version": "1.0"
|
|
97
|
+
},
|
|
98
|
+
"packages": {}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
lockdata["packages"][package_name] = {
|
|
102
|
+
"version": version,
|
|
103
|
+
"installed_at": datetime.now().isoformat(),
|
|
104
|
+
"installer": self._package_name # type: ignore[attr-defined]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
lockfile_path.parent.mkdir(parents=True, exist_ok=True)
|
|
108
|
+
with open(lockfile_path, 'w', encoding='utf-8') as f:
|
|
109
|
+
json.dump(lockdata, f, indent=2)
|
|
110
|
+
|
|
111
|
+
_log("sbom", f"Updated lockfile: {lockfile_path}")
|
|
112
|
+
except Exception as e:
|
|
113
|
+
logger.warning(f"Failed to update lockfile: {e}")
|
|
114
|
+
|
|
115
|
+
def generate_sbom(self) -> Dict:
|
|
116
|
+
"""Generate Software Bill of Materials (SBOM) for installed packages."""
|
|
117
|
+
sbom = {
|
|
118
|
+
"metadata": {
|
|
119
|
+
"format": "xwlazy-sbom",
|
|
120
|
+
"version": "1.0",
|
|
121
|
+
"generated_at": datetime.now().isoformat(),
|
|
122
|
+
"installer_package": self._package_name # type: ignore[attr-defined]
|
|
123
|
+
},
|
|
124
|
+
"packages": []
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
for pkg in self._installed_packages: # type: ignore[attr-defined]
|
|
128
|
+
version = self._get_installed_version(pkg) # type: ignore[attr-defined]
|
|
129
|
+
sbom["packages"].append({
|
|
130
|
+
"name": pkg,
|
|
131
|
+
"version": version or "unknown",
|
|
132
|
+
"installed_by": self._package_name, # type: ignore[attr-defined]
|
|
133
|
+
"source": "pypi"
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
return sbom
|
|
137
|
+
|
|
138
|
+
def export_sbom(self, output_path: str) -> bool:
|
|
139
|
+
"""Export SBOM to file."""
|
|
140
|
+
_ensure_logging_initialized()
|
|
141
|
+
try:
|
|
142
|
+
sbom = self.generate_sbom()
|
|
143
|
+
output_path = Path(output_path)
|
|
144
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
145
|
+
|
|
146
|
+
with open(output_path, 'w', encoding='utf-8') as f:
|
|
147
|
+
json.dump(sbom, f, indent=2)
|
|
148
|
+
|
|
149
|
+
_log("sbom", f"Exported SBOM to: {output_path}")
|
|
150
|
+
return True
|
|
151
|
+
except Exception as e:
|
|
152
|
+
logger.error(f"Failed to export SBOM: {e}")
|
|
153
|
+
return False
|
|
154
|
+
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Installation 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
|
+
Utility functions for installation operations.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
import inspect
|
|
16
|
+
import subprocess
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Optional
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_trigger_file() -> Optional[str]:
|
|
22
|
+
"""
|
|
23
|
+
Get the file that triggered the import (from call stack).
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Filename that triggered the import, or None
|
|
27
|
+
"""
|
|
28
|
+
try:
|
|
29
|
+
# Walk up the call stack to find the first non-xwlazy file
|
|
30
|
+
# Look for files in xwsystem, xwnode, xwdata, or user code
|
|
31
|
+
for frame_info in inspect.stack():
|
|
32
|
+
filename = frame_info.filename
|
|
33
|
+
# Skip xwlazy internal files and importlib
|
|
34
|
+
if ('xwlazy' not in filename and
|
|
35
|
+
'importlib' not in filename and
|
|
36
|
+
'<frozen' not in filename and
|
|
37
|
+
filename.endswith('.py')):
|
|
38
|
+
# Return just the filename, not full path
|
|
39
|
+
basename = os.path.basename(filename)
|
|
40
|
+
# If it's a serialization file, use that
|
|
41
|
+
if 'serialization' in filename or 'formats' in filename:
|
|
42
|
+
# Extract the format name (e.g., bson.py -> BsonSerializer)
|
|
43
|
+
if basename.endswith('.py'):
|
|
44
|
+
basename = basename[:-3] # Remove .py
|
|
45
|
+
return f"{basename.capitalize()}Serializer" if basename else None
|
|
46
|
+
return basename
|
|
47
|
+
except Exception:
|
|
48
|
+
pass
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def is_externally_managed() -> bool:
|
|
53
|
+
"""
|
|
54
|
+
Check if Python environment is externally managed (PEP 668).
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
True if environment is externally managed
|
|
58
|
+
"""
|
|
59
|
+
marker_file = Path(sys.prefix) / "EXTERNALLY-MANAGED"
|
|
60
|
+
return marker_file.exists()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def check_pip_audit_available() -> bool:
|
|
64
|
+
"""
|
|
65
|
+
Check if pip-audit is available for vulnerability scanning.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
True if pip-audit is available
|
|
69
|
+
"""
|
|
70
|
+
try:
|
|
71
|
+
result = subprocess.run(
|
|
72
|
+
[sys.executable, '-m', 'pip', 'list'],
|
|
73
|
+
capture_output=True,
|
|
74
|
+
text=True,
|
|
75
|
+
timeout=5
|
|
76
|
+
)
|
|
77
|
+
return 'pip-audit' in result.stdout
|
|
78
|
+
except Exception:
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
__all__ = ['get_trigger_file', 'is_externally_managed', 'check_pip_audit_available']
|
|
83
|
+
|