exonware-xwlazy 0.1.0.22__py3-none-any.whl → 1.0.1.2__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 (90) hide show
  1. exonware/__init__.py +86 -16
  2. exonware/xwlazy/version.py +5 -5
  3. exonware/xwlazy.py +2546 -0
  4. exonware/xwlazy_external_libs.toml +716 -0
  5. {exonware_xwlazy-0.1.0.22.dist-info → exonware_xwlazy-1.0.1.2.dist-info}/METADATA +6 -6
  6. exonware_xwlazy-1.0.1.2.dist-info/RECORD +8 -0
  7. exonware/xwlazy/__init__.py +0 -367
  8. exonware/xwlazy/common/__init__.py +0 -47
  9. exonware/xwlazy/common/base.py +0 -56
  10. exonware/xwlazy/common/cache.py +0 -504
  11. exonware/xwlazy/common/logger.py +0 -257
  12. exonware/xwlazy/common/services/__init__.py +0 -72
  13. exonware/xwlazy/common/services/dependency_mapper.py +0 -232
  14. exonware/xwlazy/common/services/install_async_utils.py +0 -165
  15. exonware/xwlazy/common/services/install_cache_utils.py +0 -245
  16. exonware/xwlazy/common/services/keyword_detection.py +0 -283
  17. exonware/xwlazy/common/services/spec_cache.py +0 -165
  18. exonware/xwlazy/common/services/state_manager.py +0 -84
  19. exonware/xwlazy/common/strategies/__init__.py +0 -28
  20. exonware/xwlazy/common/strategies/caching_dict.py +0 -44
  21. exonware/xwlazy/common/strategies/caching_installation.py +0 -88
  22. exonware/xwlazy/common/strategies/caching_lfu.py +0 -66
  23. exonware/xwlazy/common/strategies/caching_lru.py +0 -63
  24. exonware/xwlazy/common/strategies/caching_multitier.py +0 -59
  25. exonware/xwlazy/common/strategies/caching_ttl.py +0 -59
  26. exonware/xwlazy/config.py +0 -193
  27. exonware/xwlazy/contracts.py +0 -1396
  28. exonware/xwlazy/defs.py +0 -378
  29. exonware/xwlazy/errors.py +0 -276
  30. exonware/xwlazy/facade.py +0 -991
  31. exonware/xwlazy/module/__init__.py +0 -18
  32. exonware/xwlazy/module/base.py +0 -565
  33. exonware/xwlazy/module/data.py +0 -17
  34. exonware/xwlazy/module/facade.py +0 -246
  35. exonware/xwlazy/module/importer_engine.py +0 -2117
  36. exonware/xwlazy/module/strategies/__init__.py +0 -22
  37. exonware/xwlazy/module/strategies/module_helper_lazy.py +0 -93
  38. exonware/xwlazy/module/strategies/module_helper_simple.py +0 -65
  39. exonware/xwlazy/module/strategies/module_manager_advanced.py +0 -111
  40. exonware/xwlazy/module/strategies/module_manager_simple.py +0 -95
  41. exonware/xwlazy/package/__init__.py +0 -18
  42. exonware/xwlazy/package/base.py +0 -798
  43. exonware/xwlazy/package/conf.py +0 -324
  44. exonware/xwlazy/package/data.py +0 -17
  45. exonware/xwlazy/package/facade.py +0 -480
  46. exonware/xwlazy/package/services/__init__.py +0 -84
  47. exonware/xwlazy/package/services/async_install_handle.py +0 -87
  48. exonware/xwlazy/package/services/config_manager.py +0 -245
  49. exonware/xwlazy/package/services/discovery.py +0 -370
  50. exonware/xwlazy/package/services/host_packages.py +0 -145
  51. exonware/xwlazy/package/services/install_async.py +0 -277
  52. exonware/xwlazy/package/services/install_cache.py +0 -145
  53. exonware/xwlazy/package/services/install_interactive.py +0 -59
  54. exonware/xwlazy/package/services/install_policy.py +0 -156
  55. exonware/xwlazy/package/services/install_registry.py +0 -54
  56. exonware/xwlazy/package/services/install_result.py +0 -17
  57. exonware/xwlazy/package/services/install_sbom.py +0 -153
  58. exonware/xwlazy/package/services/install_utils.py +0 -79
  59. exonware/xwlazy/package/services/installer_engine.py +0 -406
  60. exonware/xwlazy/package/services/lazy_installer.py +0 -718
  61. exonware/xwlazy/package/services/manifest.py +0 -496
  62. exonware/xwlazy/package/services/strategy_registry.py +0 -186
  63. exonware/xwlazy/package/strategies/__init__.py +0 -57
  64. exonware/xwlazy/package/strategies/package_discovery_file.py +0 -129
  65. exonware/xwlazy/package/strategies/package_discovery_hybrid.py +0 -84
  66. exonware/xwlazy/package/strategies/package_discovery_manifest.py +0 -101
  67. exonware/xwlazy/package/strategies/package_execution_async.py +0 -113
  68. exonware/xwlazy/package/strategies/package_execution_cached.py +0 -90
  69. exonware/xwlazy/package/strategies/package_execution_pip.py +0 -99
  70. exonware/xwlazy/package/strategies/package_execution_wheel.py +0 -106
  71. exonware/xwlazy/package/strategies/package_mapping_discovery_first.py +0 -100
  72. exonware/xwlazy/package/strategies/package_mapping_hybrid.py +0 -105
  73. exonware/xwlazy/package/strategies/package_mapping_manifest_first.py +0 -100
  74. exonware/xwlazy/package/strategies/package_policy_allow_list.py +0 -57
  75. exonware/xwlazy/package/strategies/package_policy_deny_list.py +0 -57
  76. exonware/xwlazy/package/strategies/package_policy_permissive.py +0 -46
  77. exonware/xwlazy/package/strategies/package_timing_clean.py +0 -67
  78. exonware/xwlazy/package/strategies/package_timing_full.py +0 -66
  79. exonware/xwlazy/package/strategies/package_timing_smart.py +0 -68
  80. exonware/xwlazy/package/strategies/package_timing_temporary.py +0 -66
  81. exonware/xwlazy/runtime/__init__.py +0 -18
  82. exonware/xwlazy/runtime/adaptive_learner.py +0 -129
  83. exonware/xwlazy/runtime/base.py +0 -274
  84. exonware/xwlazy/runtime/facade.py +0 -94
  85. exonware/xwlazy/runtime/intelligent_selector.py +0 -170
  86. exonware/xwlazy/runtime/metrics.py +0 -60
  87. exonware/xwlazy/runtime/performance.py +0 -37
  88. exonware_xwlazy-0.1.0.22.dist-info/RECORD +0 -87
  89. {exonware_xwlazy-0.1.0.22.dist-info → exonware_xwlazy-1.0.1.2.dist-info}/WHEEL +0 -0
  90. {exonware_xwlazy-0.1.0.22.dist-info → exonware_xwlazy-1.0.1.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,156 +0,0 @@
1
- """
2
- Install Policy
3
-
4
- Company: eXonware.com
5
- Author: Eng. Muhammad AlShehri
6
- Email: connect@exonware.com
7
-
8
- Generation Date: 15-Nov-2025
9
-
10
- Security and policy configuration for lazy installation.
11
- """
12
-
13
- import threading
14
- from typing import Dict, List, Optional, Set, Tuple
15
-
16
- # Lazy import to avoid circular dependency
17
- def _get_log_event():
18
- """Get log_event function (lazy import to avoid circular dependency)."""
19
- from ...common.logger import log_event
20
- return log_event
21
-
22
- _log = None # Will be initialized on first use
23
-
24
- class LazyInstallPolicy:
25
- """
26
- Security and policy configuration for lazy installation.
27
- Per-package allow/deny lists, index URLs, and security settings.
28
- """
29
- __slots__ = ()
30
-
31
- _allow_lists: Dict[str, Set[str]] = {}
32
- _deny_lists: Dict[str, Set[str]] = {}
33
- _index_urls: Dict[str, str] = {}
34
- _extra_index_urls: Dict[str, List[str]] = {}
35
- _trusted_hosts: Dict[str, List[str]] = {}
36
- _require_hashes: Dict[str, bool] = {}
37
- _verify_ssl: Dict[str, bool] = {}
38
- _lockfile_paths: Dict[str, str] = {}
39
- _lock = threading.RLock()
40
-
41
- @classmethod
42
- def _ensure_logging(cls):
43
- """Ensure logging is initialized."""
44
- global _log
45
- if _log is None:
46
- _log = _get_log_event()
47
-
48
- @classmethod
49
- def set_allow_list(cls, package_name: str, allowed_packages: List[str]) -> None:
50
- """Set allow list for a package (only these can be installed)."""
51
- cls._ensure_logging()
52
- with cls._lock:
53
- cls._allow_lists[package_name] = set(allowed_packages)
54
- _log("config", f"Set allow list for {package_name}: {len(allowed_packages)} packages")
55
-
56
- @classmethod
57
- def set_deny_list(cls, package_name: str, denied_packages: List[str]) -> None:
58
- """Set deny list for a package (these cannot be installed)."""
59
- cls._ensure_logging()
60
- with cls._lock:
61
- cls._deny_lists[package_name] = set(denied_packages)
62
- _log("config", f"Set deny list for {package_name}: {len(denied_packages)} packages")
63
-
64
- @classmethod
65
- def add_to_allow_list(cls, package_name: str, allowed_package: str) -> None:
66
- """Add single package to allow list."""
67
- with cls._lock:
68
- if package_name not in cls._allow_lists:
69
- cls._allow_lists[package_name] = set()
70
- cls._allow_lists[package_name].add(allowed_package)
71
-
72
- @classmethod
73
- def add_to_deny_list(cls, package_name: str, denied_package: str) -> None:
74
- """Add single package to deny list."""
75
- with cls._lock:
76
- if package_name not in cls._deny_lists:
77
- cls._deny_lists[package_name] = set()
78
- cls._deny_lists[package_name].add(denied_package)
79
-
80
- @classmethod
81
- def is_package_allowed(cls, installer_package: str, target_package: str) -> Tuple[bool, str]:
82
- """Check if target_package can be installed by installer_package."""
83
- with cls._lock:
84
- if installer_package in cls._deny_lists:
85
- if target_package in cls._deny_lists[installer_package]:
86
- return False, f"Package '{target_package}' is in deny list"
87
-
88
- if installer_package in cls._allow_lists:
89
- if target_package not in cls._allow_lists[installer_package]:
90
- return False, f"Package '{target_package}' not in allow list"
91
-
92
- return True, "OK"
93
-
94
- @classmethod
95
- def set_index_url(cls, package_name: str, index_url: str) -> None:
96
- """Set PyPI index URL for a package."""
97
- cls._ensure_logging()
98
- with cls._lock:
99
- cls._index_urls[package_name] = index_url
100
- _log("config", f"Set index URL for {package_name}: {index_url}")
101
-
102
- @classmethod
103
- def set_extra_index_urls(cls, package_name: str, urls: List[str]) -> None:
104
- """Set extra index URLs for a package."""
105
- cls._ensure_logging()
106
- with cls._lock:
107
- cls._extra_index_urls[package_name] = urls
108
- _log("config", f"Set {len(urls)} extra index URLs for {package_name}")
109
-
110
- @classmethod
111
- def add_trusted_host(cls, package_name: str, host: str) -> None:
112
- """Add trusted host for a package."""
113
- with cls._lock:
114
- if package_name not in cls._trusted_hosts:
115
- cls._trusted_hosts[package_name] = []
116
- cls._trusted_hosts[package_name].append(host)
117
-
118
- @classmethod
119
- def get_pip_args(cls, package_name: str) -> List[str]:
120
- """Get pip install arguments for a package based on policy."""
121
- args = []
122
-
123
- with cls._lock:
124
- if package_name in cls._index_urls:
125
- args.extend(['--index-url', cls._index_urls[package_name]])
126
-
127
- if package_name in cls._extra_index_urls:
128
- for url in cls._extra_index_urls[package_name]:
129
- args.extend(['--extra-index-url', url])
130
-
131
- if package_name in cls._trusted_hosts:
132
- for host in cls._trusted_hosts[package_name]:
133
- args.extend(['--trusted-host', host])
134
-
135
- if cls._require_hashes.get(package_name, False):
136
- args.append('--require-hashes')
137
-
138
- if not cls._verify_ssl.get(package_name, True):
139
- args.append('--no-verify-ssl')
140
-
141
- return args
142
-
143
- @classmethod
144
- def set_lockfile_path(cls, package_name: str, path: str) -> None:
145
- """Set lockfile path for a package."""
146
- with cls._lock:
147
- cls._lockfile_paths[package_name] = path
148
-
149
- @classmethod
150
- def get_lockfile_path(cls, package_name: str) -> Optional[str]:
151
- """Get lockfile path for a package."""
152
- with cls._lock:
153
- return cls._lockfile_paths.get(package_name)
154
-
155
- __all__ = ['LazyInstallPolicy']
156
-
@@ -1,54 +0,0 @@
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
-
@@ -1,17 +0,0 @@
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
-
@@ -1,153 +0,0 @@
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
-
@@ -1,79 +0,0 @@
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
-