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.
Files changed (96) hide show
  1. exonware/__init__.py +26 -0
  2. exonware/xwlazy/__init__.py +0 -0
  3. exonware/xwlazy/common/__init__.py +47 -0
  4. exonware/xwlazy/common/base.py +56 -0
  5. exonware/xwlazy/common/cache.py +504 -0
  6. exonware/xwlazy/common/logger.py +257 -0
  7. exonware/xwlazy/common/services/__init__.py +72 -0
  8. exonware/xwlazy/common/services/dependency_mapper.py +232 -0
  9. exonware/xwlazy/common/services/install_async_utils.py +165 -0
  10. exonware/xwlazy/common/services/install_cache_utils.py +245 -0
  11. exonware/xwlazy/common/services/keyword_detection.py +283 -0
  12. exonware/xwlazy/common/services/spec_cache.py +165 -0
  13. xwlazy/lazy/lazy_state.py → exonware/xwlazy/common/services/state_manager.py +0 -2
  14. exonware/xwlazy/common/strategies/__init__.py +28 -0
  15. exonware/xwlazy/common/strategies/caching_dict.py +44 -0
  16. exonware/xwlazy/common/strategies/caching_installation.py +88 -0
  17. exonware/xwlazy/common/strategies/caching_lfu.py +66 -0
  18. exonware/xwlazy/common/strategies/caching_lru.py +63 -0
  19. exonware/xwlazy/common/strategies/caching_multitier.py +59 -0
  20. exonware/xwlazy/common/strategies/caching_ttl.py +59 -0
  21. {xwlazy/lazy → exonware/xwlazy}/config.py +51 -21
  22. exonware/xwlazy/contracts.py +1396 -0
  23. exonware/xwlazy/defs.py +378 -0
  24. xwlazy/lazy/lazy_errors.py → exonware/xwlazy/errors.py +21 -16
  25. exonware/xwlazy/facade.py +991 -0
  26. exonware/xwlazy/module/__init__.py +18 -0
  27. exonware/xwlazy/module/base.py +565 -0
  28. exonware/xwlazy/module/data.py +17 -0
  29. exonware/xwlazy/module/facade.py +246 -0
  30. exonware/xwlazy/module/importer_engine.py +2117 -0
  31. exonware/xwlazy/module/strategies/__init__.py +22 -0
  32. exonware/xwlazy/module/strategies/module_helper_lazy.py +93 -0
  33. exonware/xwlazy/module/strategies/module_helper_simple.py +65 -0
  34. exonware/xwlazy/module/strategies/module_manager_advanced.py +111 -0
  35. exonware/xwlazy/module/strategies/module_manager_simple.py +95 -0
  36. exonware/xwlazy/package/__init__.py +18 -0
  37. exonware/xwlazy/package/base.py +798 -0
  38. xwlazy/lazy/host_conf.py → exonware/xwlazy/package/conf.py +61 -16
  39. exonware/xwlazy/package/data.py +17 -0
  40. exonware/xwlazy/package/facade.py +480 -0
  41. exonware/xwlazy/package/services/__init__.py +84 -0
  42. exonware/xwlazy/package/services/async_install_handle.py +87 -0
  43. exonware/xwlazy/package/services/config_manager.py +245 -0
  44. exonware/xwlazy/package/services/discovery.py +370 -0
  45. {xwlazy/lazy → exonware/xwlazy/package/services}/host_packages.py +43 -20
  46. exonware/xwlazy/package/services/install_async.py +277 -0
  47. exonware/xwlazy/package/services/install_cache.py +145 -0
  48. exonware/xwlazy/package/services/install_interactive.py +59 -0
  49. exonware/xwlazy/package/services/install_policy.py +156 -0
  50. exonware/xwlazy/package/services/install_registry.py +54 -0
  51. exonware/xwlazy/package/services/install_result.py +17 -0
  52. exonware/xwlazy/package/services/install_sbom.py +153 -0
  53. exonware/xwlazy/package/services/install_utils.py +79 -0
  54. exonware/xwlazy/package/services/installer_engine.py +406 -0
  55. exonware/xwlazy/package/services/lazy_installer.py +718 -0
  56. {xwlazy/lazy → exonware/xwlazy/package/services}/manifest.py +40 -33
  57. exonware/xwlazy/package/services/strategy_registry.py +186 -0
  58. exonware/xwlazy/package/strategies/__init__.py +57 -0
  59. exonware/xwlazy/package/strategies/package_discovery_file.py +129 -0
  60. exonware/xwlazy/package/strategies/package_discovery_hybrid.py +84 -0
  61. exonware/xwlazy/package/strategies/package_discovery_manifest.py +101 -0
  62. exonware/xwlazy/package/strategies/package_execution_async.py +113 -0
  63. exonware/xwlazy/package/strategies/package_execution_cached.py +90 -0
  64. exonware/xwlazy/package/strategies/package_execution_pip.py +99 -0
  65. exonware/xwlazy/package/strategies/package_execution_wheel.py +106 -0
  66. exonware/xwlazy/package/strategies/package_mapping_discovery_first.py +100 -0
  67. exonware/xwlazy/package/strategies/package_mapping_hybrid.py +105 -0
  68. exonware/xwlazy/package/strategies/package_mapping_manifest_first.py +100 -0
  69. exonware/xwlazy/package/strategies/package_policy_allow_list.py +57 -0
  70. exonware/xwlazy/package/strategies/package_policy_deny_list.py +57 -0
  71. exonware/xwlazy/package/strategies/package_policy_permissive.py +46 -0
  72. exonware/xwlazy/package/strategies/package_timing_clean.py +67 -0
  73. exonware/xwlazy/package/strategies/package_timing_full.py +66 -0
  74. exonware/xwlazy/package/strategies/package_timing_smart.py +68 -0
  75. exonware/xwlazy/package/strategies/package_timing_temporary.py +66 -0
  76. exonware/xwlazy/runtime/__init__.py +18 -0
  77. exonware/xwlazy/runtime/adaptive_learner.py +129 -0
  78. exonware/xwlazy/runtime/base.py +274 -0
  79. exonware/xwlazy/runtime/facade.py +94 -0
  80. exonware/xwlazy/runtime/intelligent_selector.py +170 -0
  81. exonware/xwlazy/runtime/metrics.py +60 -0
  82. exonware/xwlazy/runtime/performance.py +37 -0
  83. exonware/xwlazy/version.py +2 -2
  84. {exonware_xwlazy-0.1.0.11.dist-info → exonware_xwlazy-0.1.0.20.dist-info}/METADATA +89 -11
  85. exonware_xwlazy-0.1.0.20.dist-info/RECORD +87 -0
  86. exonware_xwlazy-0.1.0.11.dist-info/RECORD +0 -20
  87. xwlazy/__init__.py +0 -34
  88. xwlazy/lazy/__init__.py +0 -301
  89. xwlazy/lazy/bootstrap.py +0 -106
  90. xwlazy/lazy/lazy_base.py +0 -465
  91. xwlazy/lazy/lazy_contracts.py +0 -290
  92. xwlazy/lazy/lazy_core.py +0 -3727
  93. xwlazy/lazy/logging_utils.py +0 -194
  94. xwlazy/version.py +0 -77
  95. {exonware_xwlazy-0.1.0.11.dist-info → exonware_xwlazy-0.1.0.20.dist-info}/WHEEL +0 -0
  96. {exonware_xwlazy-0.1.0.11.dist-info → exonware_xwlazy-0.1.0.20.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,54 @@
1
+ """
2
+ Installer 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 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
+ class LazyInstallerRegistry:
20
+ """Registry to manage separate lazy installer instances per package."""
21
+ _instances: Dict[str, 'LazyInstaller'] = {}
22
+ _lock = threading.RLock()
23
+
24
+ @classmethod
25
+ def get_instance(cls, package_name: str = 'default') -> 'LazyInstaller':
26
+ """
27
+ Get or create a lazy installer instance for a package.
28
+
29
+ Args:
30
+ package_name: Package name for isolation
31
+
32
+ Returns:
33
+ LazyInstaller instance for the package
34
+ """
35
+ with cls._lock:
36
+ if package_name not in cls._instances:
37
+ # Lazy import to avoid circular dependency
38
+ from .lazy_installer import LazyInstaller
39
+ cls._instances[package_name] = LazyInstaller(package_name)
40
+ return cls._instances[package_name]
41
+
42
+ @classmethod
43
+ def get_all_instances(cls) -> Dict[str, 'LazyInstaller']:
44
+ """
45
+ Get all lazy installer instances.
46
+
47
+ Returns:
48
+ Dict mapping package_name -> LazyInstaller
49
+ """
50
+ with cls._lock:
51
+ return cls._instances.copy()
52
+
53
+ __all__ = ['LazyInstallerRegistry']
54
+
@@ -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
+
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,153 @@
1
+ """
2
+ SBOM and Audit Mixin
3
+
4
+ Company: eXonware.com
5
+ Author: Eng. Muhammad AlShehri
6
+ Email: connect@exonware.com
7
+
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
+ class SBOMAuditMixin:
45
+ """Mixin for SBOM generation and vulnerability auditing."""
46
+
47
+ def _run_vulnerability_audit(self, package_name: str) -> None:
48
+ """Run vulnerability audit on installed package using pip-audit."""
49
+ _ensure_logging_initialized()
50
+ try:
51
+ result = subprocess.run(
52
+ [sys.executable, '-m', 'pip_audit', '-r', '-', '--format', 'json'],
53
+ input=package_name,
54
+ capture_output=True,
55
+ text=True,
56
+ timeout=30
57
+ )
58
+
59
+ if result.returncode == 0:
60
+ _log("audit", f"Vulnerability audit passed for {package_name}")
61
+ else:
62
+ try:
63
+ audit_data = json.loads(result.stdout)
64
+ if audit_data.get('vulnerabilities'):
65
+ logger.warning(f"[SECURITY] Vulnerabilities found in {package_name}: {audit_data}")
66
+ print(f"[SECURITY WARNING] Package '{package_name}' has known vulnerabilities")
67
+ print(f"Run 'pip-audit' for details")
68
+ except json.JSONDecodeError:
69
+ logger.warning(f"Could not parse audit results for {package_name}")
70
+ except subprocess.TimeoutExpired:
71
+ logger.warning(f"Vulnerability audit timed out for {package_name}")
72
+ except Exception as e:
73
+ logger.debug(f"Vulnerability audit skipped for {package_name}: {e}")
74
+
75
+ def _update_lockfile(self, package_name: str) -> None:
76
+ """Update lockfile with newly installed package."""
77
+ _ensure_logging_initialized()
78
+ lockfile_path = LazyInstallPolicy.get_lockfile_path(self._package_name) # type: ignore[attr-defined]
79
+ if not lockfile_path:
80
+ return
81
+
82
+ try:
83
+ version = self._get_installed_version(package_name) # type: ignore[attr-defined]
84
+ if not version:
85
+ return
86
+
87
+ lockfile_path = Path(lockfile_path)
88
+ if lockfile_path.exists():
89
+ with open(lockfile_path, 'r', encoding='utf-8') as f:
90
+ lockdata = json.load(f)
91
+ else:
92
+ lockdata = {
93
+ "metadata": {
94
+ "generated_by": f"xwlazy-{self._package_name}", # type: ignore[attr-defined]
95
+ "version": "1.0"
96
+ },
97
+ "packages": {}
98
+ }
99
+
100
+ lockdata["packages"][package_name] = {
101
+ "version": version,
102
+ "installed_at": datetime.now().isoformat(),
103
+ "installer": self._package_name # type: ignore[attr-defined]
104
+ }
105
+
106
+ lockfile_path.parent.mkdir(parents=True, exist_ok=True)
107
+ with open(lockfile_path, 'w', encoding='utf-8') as f:
108
+ json.dump(lockdata, f, indent=2)
109
+
110
+ _log("sbom", f"Updated lockfile: {lockfile_path}")
111
+ except Exception as e:
112
+ logger.warning(f"Failed to update lockfile: {e}")
113
+
114
+ def generate_sbom(self) -> Dict:
115
+ """Generate Software Bill of Materials (SBOM) for installed packages."""
116
+ sbom = {
117
+ "metadata": {
118
+ "format": "xwlazy-sbom",
119
+ "version": "1.0",
120
+ "generated_at": datetime.now().isoformat(),
121
+ "installer_package": self._package_name # type: ignore[attr-defined]
122
+ },
123
+ "packages": []
124
+ }
125
+
126
+ for pkg in self._installed_packages: # type: ignore[attr-defined]
127
+ version = self._get_installed_version(pkg) # type: ignore[attr-defined]
128
+ sbom["packages"].append({
129
+ "name": pkg,
130
+ "version": version or "unknown",
131
+ "installed_by": self._package_name, # type: ignore[attr-defined]
132
+ "source": "pypi"
133
+ })
134
+
135
+ return sbom
136
+
137
+ def export_sbom(self, output_path: str) -> bool:
138
+ """Export SBOM to file."""
139
+ _ensure_logging_initialized()
140
+ try:
141
+ sbom = self.generate_sbom()
142
+ output_path = Path(output_path)
143
+ output_path.parent.mkdir(parents=True, exist_ok=True)
144
+
145
+ with open(output_path, 'w', encoding='utf-8') as f:
146
+ json.dump(sbom, f, indent=2)
147
+
148
+ _log("sbom", f"Exported SBOM to: {output_path}")
149
+ return True
150
+ except Exception as e:
151
+ logger.error(f"Failed to export SBOM: {e}")
152
+ return False
153
+
@@ -0,0 +1,79 @@
1
+ """
2
+ Installation Utilities
3
+
4
+ Company: eXonware.com
5
+ Author: Eng. Muhammad AlShehri
6
+ Email: connect@exonware.com
7
+
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
+ def get_trigger_file() -> Optional[str]:
21
+ """
22
+ Get the file that triggered the import (from call stack).
23
+
24
+ Returns:
25
+ Filename that triggered the import, or None
26
+ """
27
+ try:
28
+ # Walk up the call stack to find the first non-xwlazy file
29
+ # Look for files in xwsystem, xwnode, xwdata, or user code
30
+ for frame_info in inspect.stack():
31
+ filename = frame_info.filename
32
+ # Skip xwlazy internal files and importlib
33
+ if ('xwlazy' not in filename and
34
+ 'importlib' not in filename and
35
+ '<frozen' not in filename and
36
+ filename.endswith('.py')):
37
+ # Return just the filename, not full path
38
+ basename = os.path.basename(filename)
39
+ # If it's a serialization file, use that
40
+ if 'serialization' in filename or 'formats' in filename:
41
+ # Extract the format name (e.g., bson.py -> BsonSerializer)
42
+ if basename.endswith('.py'):
43
+ basename = basename[:-3] # Remove .py
44
+ return f"{basename.capitalize()}Serializer" if basename else None
45
+ return basename
46
+ except Exception:
47
+ pass
48
+ return None
49
+
50
+ def is_externally_managed() -> bool:
51
+ """
52
+ Check if Python environment is externally managed (PEP 668).
53
+
54
+ Returns:
55
+ True if environment is externally managed
56
+ """
57
+ marker_file = Path(sys.prefix) / "EXTERNALLY-MANAGED"
58
+ return marker_file.exists()
59
+
60
+ def check_pip_audit_available() -> bool:
61
+ """
62
+ Check if pip-audit is available for vulnerability scanning.
63
+
64
+ Returns:
65
+ True if pip-audit is available
66
+ """
67
+ try:
68
+ result = subprocess.run(
69
+ [sys.executable, '-m', 'pip', 'list'],
70
+ capture_output=True,
71
+ text=True,
72
+ timeout=5
73
+ )
74
+ return 'pip-audit' in result.stdout
75
+ except Exception:
76
+ return False
77
+
78
+ __all__ = ['get_trigger_file', 'is_externally_managed', 'check_pip_audit_available']
79
+