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.
Files changed (96) hide show
  1. exonware/__init__.py +22 -0
  2. exonware/xwlazy/__init__.py +0 -0
  3. exonware/xwlazy/common/__init__.py +47 -0
  4. exonware/xwlazy/common/base.py +58 -0
  5. exonware/xwlazy/common/cache.py +506 -0
  6. exonware/xwlazy/common/logger.py +268 -0
  7. exonware/xwlazy/common/services/__init__.py +72 -0
  8. exonware/xwlazy/common/services/dependency_mapper.py +234 -0
  9. exonware/xwlazy/common/services/install_async_utils.py +169 -0
  10. exonware/xwlazy/common/services/install_cache_utils.py +257 -0
  11. exonware/xwlazy/common/services/keyword_detection.py +292 -0
  12. exonware/xwlazy/common/services/spec_cache.py +173 -0
  13. exonware/xwlazy/common/strategies/__init__.py +28 -0
  14. exonware/xwlazy/common/strategies/caching_dict.py +45 -0
  15. exonware/xwlazy/common/strategies/caching_installation.py +89 -0
  16. exonware/xwlazy/common/strategies/caching_lfu.py +67 -0
  17. exonware/xwlazy/common/strategies/caching_lru.py +64 -0
  18. exonware/xwlazy/common/strategies/caching_multitier.py +60 -0
  19. exonware/xwlazy/common/strategies/caching_ttl.py +60 -0
  20. {xwlazy/lazy → exonware/xwlazy}/config.py +52 -20
  21. exonware/xwlazy/contracts.py +1410 -0
  22. exonware/xwlazy/defs.py +397 -0
  23. xwlazy/lazy/lazy_errors.py → exonware/xwlazy/errors.py +21 -8
  24. exonware/xwlazy/facade.py +1049 -0
  25. exonware/xwlazy/module/__init__.py +18 -0
  26. exonware/xwlazy/module/base.py +569 -0
  27. exonware/xwlazy/module/data.py +17 -0
  28. exonware/xwlazy/module/facade.py +247 -0
  29. exonware/xwlazy/module/importer_engine.py +2161 -0
  30. exonware/xwlazy/module/strategies/__init__.py +22 -0
  31. exonware/xwlazy/module/strategies/module_helper_lazy.py +94 -0
  32. exonware/xwlazy/module/strategies/module_helper_simple.py +66 -0
  33. exonware/xwlazy/module/strategies/module_manager_advanced.py +112 -0
  34. exonware/xwlazy/module/strategies/module_manager_simple.py +96 -0
  35. exonware/xwlazy/package/__init__.py +18 -0
  36. exonware/xwlazy/package/base.py +807 -0
  37. xwlazy/lazy/host_conf.py → exonware/xwlazy/package/conf.py +62 -10
  38. exonware/xwlazy/package/data.py +17 -0
  39. exonware/xwlazy/package/facade.py +481 -0
  40. exonware/xwlazy/package/services/__init__.py +84 -0
  41. exonware/xwlazy/package/services/async_install_handle.py +89 -0
  42. exonware/xwlazy/package/services/config_manager.py +246 -0
  43. exonware/xwlazy/package/services/discovery.py +374 -0
  44. {xwlazy/lazy → exonware/xwlazy/package/services}/host_packages.py +43 -16
  45. exonware/xwlazy/package/services/install_async.py +278 -0
  46. exonware/xwlazy/package/services/install_cache.py +146 -0
  47. exonware/xwlazy/package/services/install_interactive.py +60 -0
  48. exonware/xwlazy/package/services/install_policy.py +158 -0
  49. exonware/xwlazy/package/services/install_registry.py +56 -0
  50. exonware/xwlazy/package/services/install_result.py +17 -0
  51. exonware/xwlazy/package/services/install_sbom.py +154 -0
  52. exonware/xwlazy/package/services/install_utils.py +83 -0
  53. exonware/xwlazy/package/services/installer_engine.py +408 -0
  54. exonware/xwlazy/package/services/lazy_installer.py +720 -0
  55. {xwlazy/lazy → exonware/xwlazy/package/services}/manifest.py +42 -25
  56. exonware/xwlazy/package/services/strategy_registry.py +188 -0
  57. exonware/xwlazy/package/strategies/__init__.py +57 -0
  58. exonware/xwlazy/package/strategies/package_discovery_file.py +130 -0
  59. exonware/xwlazy/package/strategies/package_discovery_hybrid.py +85 -0
  60. exonware/xwlazy/package/strategies/package_discovery_manifest.py +102 -0
  61. exonware/xwlazy/package/strategies/package_execution_async.py +114 -0
  62. exonware/xwlazy/package/strategies/package_execution_cached.py +91 -0
  63. exonware/xwlazy/package/strategies/package_execution_pip.py +100 -0
  64. exonware/xwlazy/package/strategies/package_execution_wheel.py +107 -0
  65. exonware/xwlazy/package/strategies/package_mapping_discovery_first.py +101 -0
  66. exonware/xwlazy/package/strategies/package_mapping_hybrid.py +106 -0
  67. exonware/xwlazy/package/strategies/package_mapping_manifest_first.py +101 -0
  68. exonware/xwlazy/package/strategies/package_policy_allow_list.py +58 -0
  69. exonware/xwlazy/package/strategies/package_policy_deny_list.py +58 -0
  70. exonware/xwlazy/package/strategies/package_policy_permissive.py +47 -0
  71. exonware/xwlazy/package/strategies/package_timing_clean.py +68 -0
  72. exonware/xwlazy/package/strategies/package_timing_full.py +67 -0
  73. exonware/xwlazy/package/strategies/package_timing_smart.py +69 -0
  74. exonware/xwlazy/package/strategies/package_timing_temporary.py +67 -0
  75. exonware/xwlazy/runtime/__init__.py +18 -0
  76. exonware/xwlazy/runtime/adaptive_learner.py +131 -0
  77. exonware/xwlazy/runtime/base.py +276 -0
  78. exonware/xwlazy/runtime/facade.py +95 -0
  79. exonware/xwlazy/runtime/intelligent_selector.py +173 -0
  80. exonware/xwlazy/runtime/metrics.py +64 -0
  81. exonware/xwlazy/runtime/performance.py +39 -0
  82. exonware/xwlazy/version.py +2 -2
  83. {exonware_xwlazy-0.1.0.11.dist-info → exonware_xwlazy-0.1.0.19.dist-info}/METADATA +86 -10
  84. exonware_xwlazy-0.1.0.19.dist-info/RECORD +87 -0
  85. exonware_xwlazy-0.1.0.11.dist-info/RECORD +0 -20
  86. xwlazy/__init__.py +0 -34
  87. xwlazy/lazy/__init__.py +0 -301
  88. xwlazy/lazy/bootstrap.py +0 -106
  89. xwlazy/lazy/lazy_base.py +0 -465
  90. xwlazy/lazy/lazy_contracts.py +0 -290
  91. xwlazy/lazy/lazy_core.py +0 -3727
  92. xwlazy/lazy/logging_utils.py +0 -194
  93. xwlazy/version.py +0 -77
  94. /xwlazy/lazy/lazy_state.py → /exonware/xwlazy/common/services/state_manager.py +0 -0
  95. {exonware_xwlazy-0.1.0.11.dist-info → exonware_xwlazy-0.1.0.19.dist-info}/WHEEL +0 -0
  96. {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
+