exonware-xwlazy 0.1.0.22__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.
Files changed (71) hide show
  1. exonware/__init__.py +20 -1
  2. exonware/xwlazy/__init__.py +14 -2
  3. exonware/xwlazy/common/__init__.py +8 -0
  4. exonware/xwlazy/common/base.py +11 -2
  5. exonware/xwlazy/common/cache.py +5 -5
  6. exonware/xwlazy/common/logger.py +5 -5
  7. exonware/xwlazy/common/services/dependency_mapper.py +31 -13
  8. exonware/xwlazy/common/services/install_async_utils.py +5 -0
  9. exonware/xwlazy/common/services/install_cache_utils.py +4 -4
  10. exonware/xwlazy/common/services/spec_cache.py +2 -2
  11. exonware/xwlazy/common/services/state_manager.py +4 -4
  12. exonware/xwlazy/common/strategies/caching_dict.py +2 -2
  13. exonware/xwlazy/common/strategies/caching_lfu.py +2 -2
  14. exonware/xwlazy/common/strategies/caching_ttl.py +2 -2
  15. exonware/xwlazy/common/utils.py +142 -0
  16. exonware/xwlazy/config.py +1 -1
  17. exonware/xwlazy/contracts.py +162 -25
  18. exonware/xwlazy/defs.py +15 -15
  19. exonware/xwlazy/facade.py +175 -29
  20. exonware/xwlazy/host/__init__.py +8 -0
  21. exonware/xwlazy/host/conf.py +16 -0
  22. exonware/xwlazy/module/base.py +61 -4
  23. exonware/xwlazy/module/facade.py +1 -1
  24. exonware/xwlazy/module/importer_engine.py +1017 -170
  25. exonware/xwlazy/module/partial_module_detector.py +275 -0
  26. exonware/xwlazy/module/strategies/module_helper_lazy.py +3 -3
  27. exonware/xwlazy/package/base.py +106 -41
  28. exonware/xwlazy/package/conf.py +6 -6
  29. exonware/xwlazy/package/services/config_manager.py +20 -16
  30. exonware/xwlazy/package/services/discovery.py +81 -16
  31. exonware/xwlazy/package/services/host_packages.py +41 -6
  32. exonware/xwlazy/package/services/install_async.py +16 -2
  33. exonware/xwlazy/package/services/install_cache.py +4 -4
  34. exonware/xwlazy/package/services/install_policy.py +14 -14
  35. exonware/xwlazy/package/services/install_registry.py +3 -3
  36. exonware/xwlazy/package/services/install_sbom.py +1 -1
  37. exonware/xwlazy/package/services/installer_engine.py +3 -3
  38. exonware/xwlazy/package/services/lazy_installer.py +102 -17
  39. exonware/xwlazy/package/services/manifest.py +43 -36
  40. exonware/xwlazy/package/services/strategy_registry.py +150 -12
  41. exonware/xwlazy/package/strategies/package_discovery_file.py +2 -2
  42. exonware/xwlazy/package/strategies/package_discovery_hybrid.py +2 -2
  43. exonware/xwlazy/package/strategies/package_discovery_manifest.py +2 -2
  44. exonware/xwlazy/package/strategies/package_execution_async.py +3 -3
  45. exonware/xwlazy/package/strategies/package_execution_cached.py +2 -2
  46. exonware/xwlazy/package/strategies/package_execution_pip.py +2 -2
  47. exonware/xwlazy/package/strategies/package_execution_wheel.py +2 -2
  48. exonware/xwlazy/package/strategies/package_mapping_discovery_first.py +2 -2
  49. exonware/xwlazy/package/strategies/package_mapping_hybrid.py +2 -2
  50. exonware/xwlazy/package/strategies/package_mapping_manifest_first.py +2 -2
  51. exonware/xwlazy/package/strategies/package_policy_allow_list.py +4 -4
  52. exonware/xwlazy/package/strategies/package_policy_deny_list.py +4 -4
  53. exonware/xwlazy/package/strategies/package_policy_permissive.py +3 -3
  54. exonware/xwlazy/package/strategies/package_timing_clean.py +2 -2
  55. exonware/xwlazy/package/strategies/package_timing_full.py +2 -2
  56. exonware/xwlazy/package/strategies/package_timing_smart.py +2 -2
  57. exonware/xwlazy/package/strategies/package_timing_temporary.py +2 -2
  58. exonware/xwlazy/runtime/adaptive_learner.py +7 -7
  59. exonware/xwlazy/runtime/base.py +14 -14
  60. exonware/xwlazy/runtime/facade.py +7 -7
  61. exonware/xwlazy/runtime/intelligent_selector.py +6 -6
  62. exonware/xwlazy/runtime/metrics.py +6 -6
  63. exonware/xwlazy/runtime/performance.py +5 -5
  64. exonware/xwlazy/version.py +2 -2
  65. {exonware_xwlazy-0.1.0.22.dist-info → exonware_xwlazy-0.1.0.23.dist-info}/METADATA +2 -6
  66. exonware_xwlazy-0.1.0.23.dist-info/RECORD +93 -0
  67. xwlazy/__init__.py +14 -0
  68. xwlazy/lazy.py +30 -0
  69. exonware_xwlazy-0.1.0.22.dist-info/RECORD +0 -87
  70. {exonware_xwlazy-0.1.0.22.dist-info → exonware_xwlazy-0.1.0.23.dist-info}/WHEEL +0 -0
  71. {exonware_xwlazy-0.1.0.22.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 Dict, Optional
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: 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] = {}
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 and mode_enum in (LazyInstallMode.SMART, LazyInstallMode.FULL, LazyInstallMode.CLEAN, LazyInstallMode.TEMPORARY):
175
- installer._async_enabled = True
176
- installer._ensure_async_loop()
174
+ if installer:
175
+ # CRITICAL: Enable the installer (it's disabled by default)
176
+ installer.enable()
177
177
 
178
- # For FULL mode, install all dependencies on start
179
- if mode_enum == LazyInstallMode.FULL:
180
- loop = installer._async_loop
181
- if loop:
182
- asyncio.run_coroutine_threadsafe(installer.install_all_dependencies(), loop)
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 Dict, List, Optional
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
- 'scipy': 'scipy',
98
- 'scikit-image': 'scikit-image',
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) -> List[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) -> Dict[str, List[str]]:
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) -> Dict[str, str]:
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
- from typing import Iterable, Sequence, Tuple
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
- """Placeholder method."""
44
- pass
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, Tuple[str, ...]]] = {}
83
+ _REGISTERED: dict[str, dict[str, tuple[str, ...]]] = {}
48
84
 
49
- def _normalized(items: Iterable[str]) -> Tuple[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, Dict, Any
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
- desired_async = bool(env_override or (manifest and manifest.async_installs))
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, List
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[List[str]] = None) -> bool:
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[List[str]] = None) -> bool:
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[List[str]] = None) -> Optional[Path]:
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 Dict, List, Optional, Set, Tuple
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: 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] = {}
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: List[str]) -> None:
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: List[str]) -> None:
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) -> Tuple[bool, 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: List[str]) -> None:
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) -> List[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 Dict, TYPE_CHECKING
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: Dict[str, 'LazyInstaller'] = {}
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) -> Dict[str, 'LazyInstaller']:
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 subprocess
16
16
  from datetime import datetime
17
17
  from pathlib import Path
18
- from typing import Dict
18
+ from typing import
19
19
 
20
20
  from .install_policy import LazyInstallPolicy
21
21
 
@@ -15,7 +15,7 @@ import sys
15
15
  import asyncio
16
16
  import threading
17
17
  import importlib.metadata
18
- from typing import Dict, List, Optional, Set, Callable
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: Set[str] = set() # Track active installs to prevent duplicates
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
- ) -> Dict[str, InstallResult]:
278
+ ) -> dict[str, InstallResult]:
279
279
  """
280
280
  Install multiple packages in parallel (async), but wait for all to complete.
281
281