exonware-xwlazy 0.1.0.21__py3-none-any.whl → 0.1.0.23__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 -6
- exonware/xwlazy/__init__.py +14 -2
- exonware/xwlazy/common/__init__.py +8 -0
- exonware/xwlazy/common/base.py +11 -2
- exonware/xwlazy/common/cache.py +5 -5
- exonware/xwlazy/common/logger.py +5 -5
- exonware/xwlazy/common/services/dependency_mapper.py +31 -13
- exonware/xwlazy/common/services/install_async_utils.py +5 -0
- exonware/xwlazy/common/services/install_cache_utils.py +4 -4
- exonware/xwlazy/common/services/spec_cache.py +2 -2
- exonware/xwlazy/common/services/state_manager.py +4 -4
- exonware/xwlazy/common/strategies/caching_dict.py +2 -2
- exonware/xwlazy/common/strategies/caching_lfu.py +2 -2
- exonware/xwlazy/common/strategies/caching_ttl.py +2 -2
- exonware/xwlazy/common/utils.py +142 -0
- exonware/xwlazy/config.py +1 -1
- exonware/xwlazy/contracts.py +162 -25
- exonware/xwlazy/defs.py +15 -15
- exonware/xwlazy/facade.py +175 -29
- exonware/xwlazy/host/__init__.py +8 -0
- exonware/xwlazy/host/conf.py +16 -0
- exonware/xwlazy/module/base.py +61 -4
- exonware/xwlazy/module/facade.py +1 -1
- exonware/xwlazy/module/importer_engine.py +1017 -170
- exonware/xwlazy/module/partial_module_detector.py +275 -0
- exonware/xwlazy/module/strategies/module_helper_lazy.py +3 -3
- exonware/xwlazy/package/base.py +106 -41
- exonware/xwlazy/package/conf.py +6 -6
- exonware/xwlazy/package/services/config_manager.py +20 -16
- exonware/xwlazy/package/services/discovery.py +81 -16
- exonware/xwlazy/package/services/host_packages.py +41 -6
- exonware/xwlazy/package/services/install_async.py +16 -2
- exonware/xwlazy/package/services/install_cache.py +4 -4
- exonware/xwlazy/package/services/install_policy.py +14 -14
- exonware/xwlazy/package/services/install_registry.py +3 -3
- exonware/xwlazy/package/services/install_sbom.py +1 -1
- exonware/xwlazy/package/services/installer_engine.py +3 -3
- exonware/xwlazy/package/services/lazy_installer.py +102 -17
- exonware/xwlazy/package/services/manifest.py +43 -36
- exonware/xwlazy/package/services/strategy_registry.py +150 -12
- exonware/xwlazy/package/strategies/package_discovery_file.py +2 -2
- exonware/xwlazy/package/strategies/package_discovery_hybrid.py +2 -2
- exonware/xwlazy/package/strategies/package_discovery_manifest.py +2 -2
- exonware/xwlazy/package/strategies/package_execution_async.py +3 -3
- exonware/xwlazy/package/strategies/package_execution_cached.py +2 -2
- exonware/xwlazy/package/strategies/package_execution_pip.py +2 -2
- exonware/xwlazy/package/strategies/package_execution_wheel.py +2 -2
- exonware/xwlazy/package/strategies/package_mapping_discovery_first.py +2 -2
- exonware/xwlazy/package/strategies/package_mapping_hybrid.py +2 -2
- exonware/xwlazy/package/strategies/package_mapping_manifest_first.py +2 -2
- exonware/xwlazy/package/strategies/package_policy_allow_list.py +4 -4
- exonware/xwlazy/package/strategies/package_policy_deny_list.py +4 -4
- exonware/xwlazy/package/strategies/package_policy_permissive.py +3 -3
- exonware/xwlazy/package/strategies/package_timing_clean.py +2 -2
- exonware/xwlazy/package/strategies/package_timing_full.py +2 -2
- exonware/xwlazy/package/strategies/package_timing_smart.py +2 -2
- exonware/xwlazy/package/strategies/package_timing_temporary.py +2 -2
- exonware/xwlazy/runtime/adaptive_learner.py +7 -7
- exonware/xwlazy/runtime/base.py +14 -14
- exonware/xwlazy/runtime/facade.py +7 -7
- exonware/xwlazy/runtime/intelligent_selector.py +6 -6
- exonware/xwlazy/runtime/metrics.py +6 -6
- exonware/xwlazy/runtime/performance.py +5 -5
- exonware/xwlazy/version.py +2 -2
- {exonware_xwlazy-0.1.0.21.dist-info → exonware_xwlazy-0.1.0.23.dist-info}/METADATA +2 -6
- exonware_xwlazy-0.1.0.23.dist-info/RECORD +93 -0
- xwlazy/__init__.py +14 -0
- xwlazy/lazy.py +30 -0
- exonware_xwlazy-0.1.0.21.dist-info/RECORD +0 -87
- {exonware_xwlazy-0.1.0.21.dist-info → exonware_xwlazy-0.1.0.23.dist-info}/WHEEL +0 -0
- {exonware_xwlazy-0.1.0.21.dist-info → exonware_xwlazy-0.1.0.23.dist-info}/licenses/LICENSE +0 -0
|
@@ -5,7 +5,7 @@ This module contains LazyInstallConfig which manages per-package lazy installati
|
|
|
5
5
|
configuration. Extracted from lazy_core.py Section 5.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from typing import
|
|
8
|
+
from typing import Optional
|
|
9
9
|
from ...common.services import LazyStateManager
|
|
10
10
|
from ...defs import LazyLoadMode, LazyInstallMode, LazyModeConfig
|
|
11
11
|
from ...defs import get_preset_mode
|
|
@@ -47,13 +47,13 @@ _MODE_ENUM_MAP = {
|
|
|
47
47
|
|
|
48
48
|
class LazyInstallConfig:
|
|
49
49
|
"""Global configuration for lazy installation per package."""
|
|
50
|
-
_configs:
|
|
51
|
-
_modes:
|
|
52
|
-
_load_modes:
|
|
53
|
-
_install_modes:
|
|
54
|
-
_mode_configs:
|
|
55
|
-
_initialized:
|
|
56
|
-
_manual_overrides:
|
|
50
|
+
_configs: dict[str, bool] = {}
|
|
51
|
+
_modes: dict[str, str] = {}
|
|
52
|
+
_load_modes: dict[str, LazyLoadMode] = {}
|
|
53
|
+
_install_modes: dict[str, LazyInstallMode] = {}
|
|
54
|
+
_mode_configs: dict[str, LazyModeConfig] = {}
|
|
55
|
+
_initialized: dict[str, bool] = {}
|
|
56
|
+
_manual_overrides: dict[str, bool] = {}
|
|
57
57
|
|
|
58
58
|
@classmethod
|
|
59
59
|
def set(
|
|
@@ -171,15 +171,19 @@ class LazyInstallConfig:
|
|
|
171
171
|
|
|
172
172
|
# Enable async for modes that support it
|
|
173
173
|
installer = LazyInstallerRegistry.get_instance(package_key)
|
|
174
|
-
if installer
|
|
175
|
-
installer
|
|
176
|
-
installer.
|
|
174
|
+
if installer:
|
|
175
|
+
# CRITICAL: Enable the installer (it's disabled by default)
|
|
176
|
+
installer.enable()
|
|
177
177
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
178
|
+
if mode_enum in (LazyInstallMode.SMART, LazyInstallMode.FULL, LazyInstallMode.CLEAN, LazyInstallMode.TEMPORARY):
|
|
179
|
+
installer._async_enabled = True
|
|
180
|
+
installer._ensure_async_loop()
|
|
181
|
+
|
|
182
|
+
# For FULL mode, install all dependencies on start
|
|
183
|
+
if mode_enum == LazyInstallMode.FULL:
|
|
184
|
+
loop = installer._async_loop
|
|
185
|
+
if loop:
|
|
186
|
+
asyncio.run_coroutine_threadsafe(installer.install_all_dependencies(), loop)
|
|
183
187
|
|
|
184
188
|
if install_hook:
|
|
185
189
|
if not is_import_hook_installed(package_key):
|
|
@@ -19,7 +19,7 @@ import subprocess
|
|
|
19
19
|
import sys
|
|
20
20
|
import threading
|
|
21
21
|
from pathlib import Path
|
|
22
|
-
from typing import
|
|
22
|
+
from typing import Optional
|
|
23
23
|
|
|
24
24
|
from ..base import APackageHelper
|
|
25
25
|
from ...defs import DependencyInfo
|
|
@@ -54,32 +54,59 @@ class LazyDiscovery(APackageHelper):
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
# Common import name to package name mappings
|
|
57
|
+
# Quick access list: Works without loading project configs
|
|
58
|
+
# Includes exonware-common packages for instant resolution
|
|
57
59
|
COMMON_MAPPINGS = {
|
|
60
|
+
# Image processing
|
|
58
61
|
'cv2': 'opencv-python',
|
|
59
62
|
'PIL': 'Pillow',
|
|
60
63
|
'Pillow': 'Pillow',
|
|
64
|
+
|
|
65
|
+
# Data formats
|
|
61
66
|
'yaml': 'PyYAML',
|
|
67
|
+
'bson': 'pymongo',
|
|
68
|
+
'msgpack': 'msgpack', # Exonware common: MessagePack
|
|
69
|
+
'cbor2': 'cbor2', # Exonware common: CBOR
|
|
70
|
+
'cbor': 'cbor2',
|
|
71
|
+
|
|
72
|
+
# Machine learning / Data science
|
|
62
73
|
'sklearn': 'scikit-learn',
|
|
63
74
|
'bs4': 'beautifulsoup4',
|
|
64
|
-
'dateutil': 'python-dateutil',
|
|
65
|
-
'requests_oauthlib': 'requests-oauthlib',
|
|
66
|
-
'google': 'google-api-python-client',
|
|
67
|
-
'jwt': 'PyJWT',
|
|
68
|
-
'crypto': 'pycrypto',
|
|
69
|
-
'Crypto': 'pycrypto',
|
|
70
|
-
'MySQLdb': 'mysqlclient',
|
|
71
|
-
'psycopg2': 'psycopg2-binary',
|
|
72
|
-
'bson': 'pymongo',
|
|
73
|
-
'lxml': 'lxml',
|
|
74
75
|
'numpy': 'numpy',
|
|
75
76
|
'pandas': 'pandas',
|
|
76
77
|
'matplotlib': 'matplotlib',
|
|
77
78
|
'seaborn': 'seaborn',
|
|
78
79
|
'plotly': 'plotly',
|
|
80
|
+
'scipy': 'scipy',
|
|
81
|
+
'scikit-image': 'scikit-image',
|
|
82
|
+
|
|
83
|
+
# Web frameworks
|
|
79
84
|
'django': 'Django',
|
|
80
85
|
'flask': 'Flask',
|
|
81
86
|
'fastapi': 'fastapi',
|
|
82
87
|
'uvicorn': 'uvicorn',
|
|
88
|
+
|
|
89
|
+
# Database
|
|
90
|
+
'MySQLdb': 'mysqlclient',
|
|
91
|
+
'psycopg2': 'psycopg2-binary',
|
|
92
|
+
|
|
93
|
+
# Serialization (Exonware common)
|
|
94
|
+
'lxml': 'lxml',
|
|
95
|
+
'xml': 'lxml',
|
|
96
|
+
'fastavro': 'fastavro', # Exonware common: Avro
|
|
97
|
+
'avro': 'fastavro',
|
|
98
|
+
'protobuf': 'protobuf', # Exonware common: Protocol Buffers
|
|
99
|
+
'pyarrow': 'pyarrow', # Exonware common: Parquet/Feather
|
|
100
|
+
'parquet': 'pyarrow',
|
|
101
|
+
'feather': 'pyarrow',
|
|
102
|
+
|
|
103
|
+
# Utilities
|
|
104
|
+
'dateutil': 'python-dateutil',
|
|
105
|
+
'requests_oauthlib': 'requests-oauthlib',
|
|
106
|
+
'google': 'google-api-python-client',
|
|
107
|
+
'jwt': 'PyJWT',
|
|
108
|
+
'crypto': 'pycrypto',
|
|
109
|
+
'Crypto': 'pycrypto',
|
|
83
110
|
'pytest': 'pytest',
|
|
84
111
|
'black': 'black',
|
|
85
112
|
'isort': 'isort',
|
|
@@ -89,20 +116,58 @@ class LazyDiscovery(APackageHelper):
|
|
|
89
116
|
'pytz': 'pytz',
|
|
90
117
|
'aiofiles': 'aiofiles',
|
|
91
118
|
'watchdog': 'watchdog',
|
|
119
|
+
|
|
120
|
+
# Image utilities
|
|
92
121
|
'wand': 'Wand',
|
|
93
122
|
'exifread': 'ExifRead',
|
|
94
123
|
'piexif': 'piexif',
|
|
95
124
|
'rawpy': 'rawpy',
|
|
96
125
|
'imageio': 'imageio',
|
|
97
|
-
|
|
98
|
-
|
|
126
|
+
|
|
127
|
+
# OpenCV
|
|
99
128
|
'opencv-python': 'opencv-python',
|
|
100
129
|
'opencv-contrib-python': 'opencv-contrib-python',
|
|
130
|
+
|
|
131
|
+
# Observability
|
|
101
132
|
'opentelemetry': 'opentelemetry-api',
|
|
102
133
|
'opentelemetry.trace': 'opentelemetry-api',
|
|
103
134
|
'opentelemetry.sdk': 'opentelemetry-sdk',
|
|
104
135
|
}
|
|
105
136
|
|
|
137
|
+
# ========================================================================
|
|
138
|
+
# Stub Implementations for APackageHelper (Installation methods not used by Discovery)
|
|
139
|
+
# ========================================================================
|
|
140
|
+
|
|
141
|
+
def install_package(self, package_name: str, module_name: Optional[str] = None) -> bool:
|
|
142
|
+
return False
|
|
143
|
+
|
|
144
|
+
def _check_security_policy(self, package_name: str) -> tuple[bool, str]:
|
|
145
|
+
return (False, "Discovery only")
|
|
146
|
+
|
|
147
|
+
def _run_pip_install(self, package_name: str, args: list[str]) -> bool:
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
def is_cache_valid(self, key: str) -> bool:
|
|
151
|
+
return False
|
|
152
|
+
|
|
153
|
+
def _check_importability(self, package_name: str) -> bool:
|
|
154
|
+
return False
|
|
155
|
+
|
|
156
|
+
def _check_persistent_cache(self, package_name: str) -> bool:
|
|
157
|
+
return False
|
|
158
|
+
|
|
159
|
+
def _mark_installed_in_persistent_cache(self, package_name: str) -> None:
|
|
160
|
+
pass
|
|
161
|
+
|
|
162
|
+
def _mark_uninstalled_in_persistent_cache(self, package_name: str) -> None:
|
|
163
|
+
pass
|
|
164
|
+
|
|
165
|
+
def _run_install(self, *package_names: str) -> None:
|
|
166
|
+
pass
|
|
167
|
+
|
|
168
|
+
def _run_uninstall(self, *package_names: str) -> None:
|
|
169
|
+
pass
|
|
170
|
+
|
|
106
171
|
def _discover_from_sources(self) -> None:
|
|
107
172
|
"""Discover dependencies from all sources."""
|
|
108
173
|
self._discover_from_pyproject_toml()
|
|
@@ -316,12 +381,12 @@ class LazyDiscovery(APackageHelper):
|
|
|
316
381
|
mapping = self.discover_all_dependencies()
|
|
317
382
|
return mapping.get(import_name)
|
|
318
383
|
|
|
319
|
-
def get_imports_for_package(self, package_name: str) ->
|
|
384
|
+
def get_imports_for_package(self, package_name: str) -> list[str]:
|
|
320
385
|
"""Get all possible import names for a package."""
|
|
321
386
|
mapping = self.get_package_import_mapping()
|
|
322
387
|
return mapping.get(package_name, [package_name])
|
|
323
388
|
|
|
324
|
-
def get_package_import_mapping(self) ->
|
|
389
|
+
def get_package_import_mapping(self) -> dict[str, list[str]]:
|
|
325
390
|
"""Get mapping of package names to their possible import names."""
|
|
326
391
|
self.discover_all_dependencies()
|
|
327
392
|
|
|
@@ -338,7 +403,7 @@ class LazyDiscovery(APackageHelper):
|
|
|
338
403
|
|
|
339
404
|
return package_to_imports
|
|
340
405
|
|
|
341
|
-
def get_import_package_mapping(self) ->
|
|
406
|
+
def get_import_package_mapping(self) -> dict[str, str]:
|
|
342
407
|
"""Get mapping of import names to package names."""
|
|
343
408
|
self.discover_all_dependencies()
|
|
344
409
|
return {import_name: dep_info.package_name for import_name, dep_info in self.discovered_dependencies.items()}
|
|
@@ -4,7 +4,8 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
6
|
import sys
|
|
7
|
-
|
|
7
|
+
import functools
|
|
8
|
+
from typing import Iterable, Sequence
|
|
8
9
|
|
|
9
10
|
# Lazy imports to avoid circular dependency
|
|
10
11
|
def _get_config_package_lazy_install_enabled():
|
|
@@ -40,13 +41,48 @@ class LazyMetaPathFinder:
|
|
|
40
41
|
self.package_name = package_name
|
|
41
42
|
|
|
42
43
|
def _enhance_classes_with_class_methods(self, module):
|
|
43
|
-
"""
|
|
44
|
-
|
|
44
|
+
"""Enhance classes with lazy-aware static/class method behavior."""
|
|
45
|
+
|
|
46
|
+
for name, cls in vars(module).items():
|
|
47
|
+
if not isinstance(cls, type):
|
|
48
|
+
continue
|
|
49
|
+
|
|
50
|
+
# Skip if not a serializer (heuristic)
|
|
51
|
+
if not name.endswith('Serializer'):
|
|
52
|
+
continue
|
|
53
|
+
|
|
54
|
+
# Methods to patch
|
|
55
|
+
for method_name in ['encode', 'decode']:
|
|
56
|
+
if not hasattr(cls, method_name):
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
original_method = getattr(cls, method_name)
|
|
60
|
+
if not callable(original_method):
|
|
61
|
+
continue
|
|
62
|
+
|
|
63
|
+
# Check if already patched to avoid recursion/duplication
|
|
64
|
+
if getattr(original_method, '_is_lazy_wrapper', False):
|
|
65
|
+
continue
|
|
66
|
+
|
|
67
|
+
@functools.wraps(original_method)
|
|
68
|
+
def wrapper(first_arg, *args, **kwargs):
|
|
69
|
+
# Check if first_arg is 'self' (instance of cls)
|
|
70
|
+
if isinstance(first_arg, cls):
|
|
71
|
+
return original_method(first_arg, *args, **kwargs)
|
|
72
|
+
|
|
73
|
+
# Called as class method or static usage: Class.encode(data)
|
|
74
|
+
# first_arg is actually the data
|
|
75
|
+
# Instantiate to trigger lazy loading (__init__)
|
|
76
|
+
instance = cls()
|
|
77
|
+
return original_method(instance, first_arg, *args, **kwargs)
|
|
78
|
+
|
|
79
|
+
wrapper._is_lazy_wrapper = True
|
|
80
|
+
setattr(cls, method_name, wrapper)
|
|
45
81
|
|
|
46
82
|
_TRUTHY = {"1", "true", "yes", "on"}
|
|
47
|
-
_REGISTERED: dict[str, dict[str,
|
|
83
|
+
_REGISTERED: dict[str, dict[str, tuple[str, ...]]] = {}
|
|
48
84
|
|
|
49
|
-
def _normalized(items: Iterable[str]) ->
|
|
85
|
+
def _normalized(items: Iterable[str]) -> tuple[str, ...]:
|
|
50
86
|
seen = []
|
|
51
87
|
for item in items:
|
|
52
88
|
if item not in seen:
|
|
@@ -142,4 +178,3 @@ def _apply_wrappers_for_loaded_modules(
|
|
|
142
178
|
finder._enhance_classes_with_class_methods(module) # type: ignore[attr-defined]
|
|
143
179
|
except Exception:
|
|
144
180
|
continue
|
|
145
|
-
|
|
@@ -14,7 +14,7 @@ import os
|
|
|
14
14
|
import asyncio
|
|
15
15
|
import threading
|
|
16
16
|
import importlib
|
|
17
|
-
from typing import Optional,
|
|
17
|
+
from typing import Optional, Any
|
|
18
18
|
|
|
19
19
|
from .async_install_handle import AsyncInstallHandle
|
|
20
20
|
from .manifest import PackageManifest
|
|
@@ -45,8 +45,17 @@ def _ensure_logging_initialized():
|
|
|
45
45
|
global logger, _log
|
|
46
46
|
if logger is None:
|
|
47
47
|
logger = _get_logger()
|
|
48
|
+
if logger is None:
|
|
49
|
+
import logging
|
|
50
|
+
logger = logging.getLogger("xwlazy.lazy_installer.fallback")
|
|
51
|
+
logger.addHandler(logging.NullHandler())
|
|
48
52
|
if _log is None:
|
|
49
53
|
_log = _get_log_event()
|
|
54
|
+
if _log is None:
|
|
55
|
+
# Simple fallback
|
|
56
|
+
def _fallback_log(event, *args, **kwargs):
|
|
57
|
+
pass
|
|
58
|
+
_log = _fallback_log
|
|
50
59
|
|
|
51
60
|
class AsyncInstallMixin:
|
|
52
61
|
"""Mixin for async installation operations."""
|
|
@@ -84,7 +93,12 @@ class AsyncInstallMixin:
|
|
|
84
93
|
def apply_manifest(self, manifest: Optional[PackageManifest]) -> None:
|
|
85
94
|
"""Apply manifest-driven configuration such as async installs."""
|
|
86
95
|
env_override = _ENV_ASYNC_INSTALL
|
|
87
|
-
|
|
96
|
+
# Force disable async install unless strictly overridden by env var (safety default)
|
|
97
|
+
if not env_override:
|
|
98
|
+
desired_async = False
|
|
99
|
+
else:
|
|
100
|
+
desired_async = True
|
|
101
|
+
# desired_async = bool(env_override or (manifest and manifest.async_installs))
|
|
88
102
|
desired_workers = _ENV_ASYNC_WORKERS or (manifest.async_workers if manifest else 1)
|
|
89
103
|
desired_workers = max(1, desired_workers)
|
|
90
104
|
|
|
@@ -14,7 +14,7 @@ Uses shared utilities from common/services/install_cache_utils.
|
|
|
14
14
|
import os
|
|
15
15
|
import time
|
|
16
16
|
from pathlib import Path
|
|
17
|
-
from typing import Optional
|
|
17
|
+
from typing import Optional
|
|
18
18
|
from collections import OrderedDict
|
|
19
19
|
|
|
20
20
|
# Import shared utilities
|
|
@@ -93,7 +93,7 @@ class InstallCacheMixin:
|
|
|
93
93
|
"""Get the cached wheel file path for a package."""
|
|
94
94
|
return get_wheel_path(package_name, self._async_cache_dir) # type: ignore[attr-defined]
|
|
95
95
|
|
|
96
|
-
def _install_from_cached_wheel(self, package_name: str, policy_args: Optional[
|
|
96
|
+
def _install_from_cached_wheel(self, package_name: str, policy_args: Optional[list[str]] = None) -> bool:
|
|
97
97
|
"""Install from a cached wheel file."""
|
|
98
98
|
return _install_from_cached_wheel_util(
|
|
99
99
|
package_name,
|
|
@@ -101,11 +101,11 @@ class InstallCacheMixin:
|
|
|
101
101
|
self._async_cache_dir # type: ignore[attr-defined]
|
|
102
102
|
)
|
|
103
103
|
|
|
104
|
-
def _pip_install_from_path(self, wheel_path: Path, policy_args: Optional[
|
|
104
|
+
def _pip_install_from_path(self, wheel_path: Path, policy_args: Optional[list[str]] = None) -> bool:
|
|
105
105
|
"""Install a wheel file using pip."""
|
|
106
106
|
return pip_install_from_path(wheel_path, policy_args)
|
|
107
107
|
|
|
108
|
-
def _ensure_cached_wheel(self, package_name: str, policy_args: Optional[
|
|
108
|
+
def _ensure_cached_wheel(self, package_name: str, policy_args: Optional[list[str]] = None) -> Optional[Path]:
|
|
109
109
|
"""Ensure a wheel is cached, downloading it if necessary."""
|
|
110
110
|
return ensure_cached_wheel(
|
|
111
111
|
package_name,
|
|
@@ -11,7 +11,7 @@ Security and policy configuration for lazy installation.
|
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
13
|
import threading
|
|
14
|
-
from typing import
|
|
14
|
+
from typing import Optional
|
|
15
15
|
|
|
16
16
|
# Lazy import to avoid circular dependency
|
|
17
17
|
def _get_log_event():
|
|
@@ -28,14 +28,14 @@ class LazyInstallPolicy:
|
|
|
28
28
|
"""
|
|
29
29
|
__slots__ = ()
|
|
30
30
|
|
|
31
|
-
_allow_lists:
|
|
32
|
-
_deny_lists:
|
|
33
|
-
_index_urls:
|
|
34
|
-
_extra_index_urls:
|
|
35
|
-
_trusted_hosts:
|
|
36
|
-
_require_hashes:
|
|
37
|
-
_verify_ssl:
|
|
38
|
-
_lockfile_paths:
|
|
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
39
|
_lock = threading.RLock()
|
|
40
40
|
|
|
41
41
|
@classmethod
|
|
@@ -46,7 +46,7 @@ class LazyInstallPolicy:
|
|
|
46
46
|
_log = _get_log_event()
|
|
47
47
|
|
|
48
48
|
@classmethod
|
|
49
|
-
def set_allow_list(cls, package_name: str, allowed_packages:
|
|
49
|
+
def set_allow_list(cls, package_name: str, allowed_packages: list[str]) -> None:
|
|
50
50
|
"""Set allow list for a package (only these can be installed)."""
|
|
51
51
|
cls._ensure_logging()
|
|
52
52
|
with cls._lock:
|
|
@@ -54,7 +54,7 @@ class LazyInstallPolicy:
|
|
|
54
54
|
_log("config", f"Set allow list for {package_name}: {len(allowed_packages)} packages")
|
|
55
55
|
|
|
56
56
|
@classmethod
|
|
57
|
-
def set_deny_list(cls, package_name: str, denied_packages:
|
|
57
|
+
def set_deny_list(cls, package_name: str, denied_packages: list[str]) -> None:
|
|
58
58
|
"""Set deny list for a package (these cannot be installed)."""
|
|
59
59
|
cls._ensure_logging()
|
|
60
60
|
with cls._lock:
|
|
@@ -78,7 +78,7 @@ class LazyInstallPolicy:
|
|
|
78
78
|
cls._deny_lists[package_name].add(denied_package)
|
|
79
79
|
|
|
80
80
|
@classmethod
|
|
81
|
-
def is_package_allowed(cls, installer_package: str, target_package: str) ->
|
|
81
|
+
def is_package_allowed(cls, installer_package: str, target_package: str) -> tuple[bool, str]:
|
|
82
82
|
"""Check if target_package can be installed by installer_package."""
|
|
83
83
|
with cls._lock:
|
|
84
84
|
if installer_package in cls._deny_lists:
|
|
@@ -100,7 +100,7 @@ class LazyInstallPolicy:
|
|
|
100
100
|
_log("config", f"Set index URL for {package_name}: {index_url}")
|
|
101
101
|
|
|
102
102
|
@classmethod
|
|
103
|
-
def set_extra_index_urls(cls, package_name: str, urls:
|
|
103
|
+
def set_extra_index_urls(cls, package_name: str, urls: list[str]) -> None:
|
|
104
104
|
"""Set extra index URLs for a package."""
|
|
105
105
|
cls._ensure_logging()
|
|
106
106
|
with cls._lock:
|
|
@@ -116,7 +116,7 @@ class LazyInstallPolicy:
|
|
|
116
116
|
cls._trusted_hosts[package_name].append(host)
|
|
117
117
|
|
|
118
118
|
@classmethod
|
|
119
|
-
def get_pip_args(cls, package_name: str) ->
|
|
119
|
+
def get_pip_args(cls, package_name: str) -> list[str]:
|
|
120
120
|
"""Get pip install arguments for a package based on policy."""
|
|
121
121
|
args = []
|
|
122
122
|
|
|
@@ -11,14 +11,14 @@ Registry to manage separate lazy installer instances per package.
|
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
13
|
import threading
|
|
14
|
-
from typing import
|
|
14
|
+
from typing import TYPE_CHECKING
|
|
15
15
|
|
|
16
16
|
if TYPE_CHECKING:
|
|
17
17
|
from .lazy_installer import LazyInstaller
|
|
18
18
|
|
|
19
19
|
class LazyInstallerRegistry:
|
|
20
20
|
"""Registry to manage separate lazy installer instances per package."""
|
|
21
|
-
_instances:
|
|
21
|
+
_instances: dict[str, 'LazyInstaller'] = {}
|
|
22
22
|
_lock = threading.RLock()
|
|
23
23
|
|
|
24
24
|
@classmethod
|
|
@@ -40,7 +40,7 @@ class LazyInstallerRegistry:
|
|
|
40
40
|
return cls._instances[package_name]
|
|
41
41
|
|
|
42
42
|
@classmethod
|
|
43
|
-
def get_all_instances(cls) ->
|
|
43
|
+
def get_all_instances(cls) -> dict[str, 'LazyInstaller']:
|
|
44
44
|
"""
|
|
45
45
|
Get all lazy installer instances.
|
|
46
46
|
|
|
@@ -15,7 +15,7 @@ import sys
|
|
|
15
15
|
import asyncio
|
|
16
16
|
import threading
|
|
17
17
|
import importlib.metadata
|
|
18
|
-
from typing import
|
|
18
|
+
from typing import Optional, Callable
|
|
19
19
|
|
|
20
20
|
from .install_result import InstallResult, InstallStatus
|
|
21
21
|
from .install_policy import LazyInstallPolicy
|
|
@@ -55,7 +55,7 @@ class InstallerEngine:
|
|
|
55
55
|
self._loop: Optional[asyncio.AbstractEventLoop] = None
|
|
56
56
|
self._loop_thread: Optional[threading.Thread] = None
|
|
57
57
|
self._lock = threading.RLock()
|
|
58
|
-
self._active_installs:
|
|
58
|
+
self._active_installs: set[str] = set() # Track active installs to prevent duplicates
|
|
59
59
|
|
|
60
60
|
# Get install mode from config
|
|
61
61
|
self._mode = LazyInstallConfig.get_install_mode(package_name) or LazyInstallMode.SMART
|
|
@@ -275,7 +275,7 @@ class InstallerEngine:
|
|
|
275
275
|
self,
|
|
276
276
|
*package_names: str,
|
|
277
277
|
callback: Optional[Callable[[str, InstallResult], None]] = None
|
|
278
|
-
) ->
|
|
278
|
+
) -> dict[str, InstallResult]:
|
|
279
279
|
"""
|
|
280
280
|
Install multiple packages in parallel (async), but wait for all to complete.
|
|
281
281
|
|