exonware-xwlazy 0.1.0.21__tar.gz → 0.1.0.23__tar.gz

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 (97) hide show
  1. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/PKG-INFO +2 -6
  2. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/pyproject.toml +1 -5
  3. exonware_xwlazy-0.1.0.23/src/exonware/__init__.py +42 -0
  4. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/__init__.py +14 -2
  5. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/common/__init__.py +8 -0
  6. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/common/base.py +11 -2
  7. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/common/cache.py +5 -5
  8. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/common/logger.py +5 -5
  9. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/common/services/dependency_mapper.py +31 -13
  10. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/common/services/install_async_utils.py +5 -0
  11. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/common/services/install_cache_utils.py +4 -4
  12. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/common/services/spec_cache.py +2 -2
  13. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/common/services/state_manager.py +4 -4
  14. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/common/strategies/caching_dict.py +2 -2
  15. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/common/strategies/caching_lfu.py +2 -2
  16. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/common/strategies/caching_ttl.py +2 -2
  17. exonware_xwlazy-0.1.0.23/src/exonware/xwlazy/common/utils.py +142 -0
  18. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/config.py +1 -1
  19. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/contracts.py +162 -25
  20. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/defs.py +15 -15
  21. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/facade.py +175 -29
  22. exonware_xwlazy-0.1.0.23/src/exonware/xwlazy/host/__init__.py +8 -0
  23. exonware_xwlazy-0.1.0.23/src/exonware/xwlazy/host/conf.py +16 -0
  24. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/module/base.py +61 -4
  25. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/module/facade.py +1 -1
  26. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/module/importer_engine.py +1017 -170
  27. exonware_xwlazy-0.1.0.23/src/exonware/xwlazy/module/partial_module_detector.py +275 -0
  28. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/module/strategies/module_helper_lazy.py +3 -3
  29. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/base.py +106 -41
  30. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/conf.py +6 -6
  31. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/services/config_manager.py +20 -16
  32. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/services/discovery.py +81 -16
  33. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/services/host_packages.py +41 -6
  34. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/services/install_async.py +16 -2
  35. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/services/install_cache.py +4 -4
  36. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/services/install_policy.py +14 -14
  37. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/services/install_registry.py +3 -3
  38. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/services/install_sbom.py +1 -1
  39. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/services/installer_engine.py +3 -3
  40. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/services/lazy_installer.py +102 -17
  41. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/services/manifest.py +43 -36
  42. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/services/strategy_registry.py +150 -12
  43. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/strategies/package_discovery_file.py +2 -2
  44. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/strategies/package_discovery_hybrid.py +2 -2
  45. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/strategies/package_discovery_manifest.py +2 -2
  46. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/strategies/package_execution_async.py +3 -3
  47. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/strategies/package_execution_cached.py +2 -2
  48. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/strategies/package_execution_pip.py +2 -2
  49. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/strategies/package_execution_wheel.py +2 -2
  50. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/strategies/package_mapping_discovery_first.py +2 -2
  51. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/strategies/package_mapping_hybrid.py +2 -2
  52. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/strategies/package_mapping_manifest_first.py +2 -2
  53. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/strategies/package_policy_allow_list.py +4 -4
  54. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/strategies/package_policy_deny_list.py +4 -4
  55. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/strategies/package_policy_permissive.py +3 -3
  56. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/strategies/package_timing_clean.py +2 -2
  57. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/strategies/package_timing_full.py +2 -2
  58. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/strategies/package_timing_smart.py +2 -2
  59. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/strategies/package_timing_temporary.py +2 -2
  60. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/runtime/adaptive_learner.py +7 -7
  61. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/runtime/base.py +14 -14
  62. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/runtime/facade.py +7 -7
  63. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/runtime/intelligent_selector.py +6 -6
  64. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/runtime/metrics.py +6 -6
  65. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/runtime/performance.py +5 -5
  66. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/version.py +2 -2
  67. exonware_xwlazy-0.1.0.23/src/xwlazy/__init__.py +14 -0
  68. exonware_xwlazy-0.1.0.23/src/xwlazy/lazy.py +30 -0
  69. exonware_xwlazy-0.1.0.21/src/exonware/__init__.py +0 -26
  70. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/.gitignore +0 -0
  71. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/LICENSE +0 -0
  72. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/README.md +0 -0
  73. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/common/services/__init__.py +0 -0
  74. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/common/services/keyword_detection.py +0 -0
  75. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/common/strategies/__init__.py +0 -0
  76. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/common/strategies/caching_installation.py +0 -0
  77. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/common/strategies/caching_lru.py +0 -0
  78. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/common/strategies/caching_multitier.py +0 -0
  79. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/errors.py +0 -0
  80. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/module/__init__.py +0 -0
  81. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/module/data.py +0 -0
  82. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/module/strategies/__init__.py +0 -0
  83. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/module/strategies/module_helper_simple.py +0 -0
  84. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/module/strategies/module_manager_advanced.py +0 -0
  85. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/module/strategies/module_manager_simple.py +0 -0
  86. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/__init__.py +0 -0
  87. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/data.py +0 -0
  88. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/facade.py +0 -0
  89. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/services/__init__.py +0 -0
  90. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/services/async_install_handle.py +0 -0
  91. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/services/install_interactive.py +0 -0
  92. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/services/install_result.py +0 -0
  93. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/services/install_utils.py +0 -0
  94. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/package/strategies/__init__.py +0 -0
  95. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/exonware/xwlazy/runtime/__init__.py +0 -0
  96. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/xwlazy.py +0 -0
  97. {exonware_xwlazy-0.1.0.21 → exonware_xwlazy-0.1.0.23}/src/xwlazy_wrapper.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: exonware-xwlazy
3
- Version: 0.1.0.21
3
+ Version: 0.1.0.23
4
4
  Summary: Marker package that enables lazy install features across the eXonware suite.
5
5
  Project-URL: Homepage, https://exonware.com
6
6
  Project-URL: Repository, https://github.com/exonware/xwlazy
@@ -15,13 +15,9 @@ Classifier: Intended Audience :: Developers
15
15
  Classifier: License :: OSI Approved :: MIT License
16
16
  Classifier: Operating System :: OS Independent
17
17
  Classifier: Programming Language :: Python :: 3
18
- Classifier: Programming Language :: Python :: 3.8
19
- Classifier: Programming Language :: Python :: 3.9
20
- Classifier: Programming Language :: Python :: 3.10
21
- Classifier: Programming Language :: Python :: 3.11
22
18
  Classifier: Programming Language :: Python :: 3.12
23
19
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
- Requires-Python: >=3.8
20
+ Requires-Python: >=3.12
25
21
  Requires-Dist: tomli>=2.0.1; python_version < '3.11'
26
22
  Description-Content-Type: text/markdown
27
23
 
@@ -7,7 +7,7 @@ name = "exonware-xwlazy"
7
7
  dynamic = ["version"]
8
8
  description = "Marker package that enables lazy install features across the eXonware suite."
9
9
  readme = "README.md"
10
- requires-python = ">=3.8"
10
+ requires-python = ">=3.12"
11
11
  keywords = ["lazy", "exonware", "xwlazy"]
12
12
  classifiers = [
13
13
  "Development Status :: 4 - Beta",
@@ -15,10 +15,6 @@ classifiers = [
15
15
  "License :: OSI Approved :: MIT License",
16
16
  "Operating System :: OS Independent",
17
17
  "Programming Language :: Python :: 3",
18
- "Programming Language :: Python :: 3.8",
19
- "Programming Language :: Python :: 3.9",
20
- "Programming Language :: Python :: 3.10",
21
- "Programming Language :: Python :: 3.11",
22
18
  "Programming Language :: Python :: 3.12",
23
19
  "Topic :: Software Development :: Libraries :: Python Modules",
24
20
  ]
@@ -0,0 +1,42 @@
1
+ """
2
+ exonware package - Enterprise-grade Python framework ecosystem
3
+
4
+ Company: eXonware.com
5
+ Author: Eng. Muhammad AlShehri
6
+ Email: connect@exonware.com
7
+ Generation Date: 2025-01-03
8
+
9
+ This is a namespace package allowing multiple exonware subpackages
10
+ to coexist (xwsystem, xwnode, xwdata, xwlazy, etc.)
11
+ """
12
+
13
+ # Make this a namespace package FIRST
14
+ # This allows both exonware.xwsystem and exonware.xwlazy to coexist
15
+ __path__ = __import__('pkgutil').extend_path(__path__, __name__)
16
+
17
+ # Import version from xwlazy - required, no fallback
18
+ from exonware.xwlazy.version import __version__
19
+
20
+ __author__ = 'Eng. Muhammad AlShehri'
21
+ __email__ = 'connect@exonware.com'
22
+ __company__ = 'eXonware.com'
23
+
24
+ # NOW enable lazy mode (after namespace package is set up)
25
+ import sys
26
+ import importlib
27
+ try:
28
+ # Use importlib to import after namespace is ready
29
+ if 'exonware.xwlazy' not in sys.modules:
30
+ xwlazy_module = importlib.import_module('exonware.xwlazy')
31
+ auto_enable_lazy = getattr(xwlazy_module, 'auto_enable_lazy', None)
32
+ if auto_enable_lazy:
33
+ auto_enable_lazy("xwsystem", mode="smart")
34
+ print("✅ Lazy mode enabled for xwsystem")
35
+ else:
36
+ # Module already loaded, use it directly
37
+ from exonware.xwlazy import auto_enable_lazy
38
+ auto_enable_lazy("xwsystem", mode="smart")
39
+ print("✅ Lazy mode enabled for xwsystem")
40
+ except (ImportError, AttributeError):
41
+ print("❌ Lazy mode not enabled for xwsystem (xwlazy not installed)")
42
+ pass # xwlazy not installed - silently continue
@@ -63,6 +63,10 @@ from .facade import (
63
63
  configure_lazy_mode,
64
64
  preload_modules,
65
65
  optimize_lazy_mode,
66
+ # One-line activation API
67
+ auto_enable_lazy,
68
+ # Lazy-loader compatible API
69
+ attach,
66
70
  # Public API functions
67
71
  enable_lazy_install,
68
72
  disable_lazy_install,
@@ -167,6 +171,10 @@ def __getattr__(name: str) -> Any:
167
171
  elif name == "XWRuntimeHelper":
168
172
  from .runtime import XWRuntimeHelper
169
173
  return XWRuntimeHelper
174
+ elif name == "manifest":
175
+ # Import manifest module for lazy access
176
+ from .package.services import manifest
177
+ return manifest
170
178
  raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
171
179
 
172
180
  # Import core classes for advanced usage
@@ -210,7 +218,7 @@ from .module.importer_engine import (
210
218
  _mark_import_started,
211
219
  _mark_import_finished,
212
220
  _lazy_aware_import_module,
213
- _patch_import_module,
221
+ # _patch_import_module removed - deprecated, use sys.meta_path hooks instead
214
222
  _unpatch_import_module,
215
223
  )
216
224
 
@@ -245,6 +253,10 @@ __all__ = [
245
253
  "configure_lazy_mode",
246
254
  "preload_modules",
247
255
  "optimize_lazy_mode",
256
+ # One-line activation API
257
+ "auto_enable_lazy",
258
+ # Lazy-loader compatible API
259
+ "attach",
248
260
  # Public API functions
249
261
  "enable_lazy_install",
250
262
  "disable_lazy_install",
@@ -361,7 +373,7 @@ __all__ = [
361
373
  "_mark_import_started",
362
374
  "_mark_import_finished",
363
375
  "_lazy_aware_import_module",
364
- "_patch_import_module",
376
+ # "_patch_import_module", # Removed - deprecated, use sys.meta_path hooks instead
365
377
  "_unpatch_import_module",
366
378
  ]
367
379
 
@@ -28,6 +28,11 @@ from .cache import (
28
28
  InstallationCache,
29
29
  )
30
30
 
31
+ from .utils import (
32
+ find_project_root,
33
+ find_config_file,
34
+ )
35
+
31
36
  __all__ = [
32
37
  # Logger
33
38
  'get_logger',
@@ -43,5 +48,8 @@ __all__ = [
43
48
  'MultiTierCache',
44
49
  'BytecodeCache',
45
50
  'InstallationCache',
51
+ # Utils
52
+ 'find_project_root',
53
+ 'find_config_file',
46
54
  ]
47
55
 
@@ -12,13 +12,21 @@ Abstract base classes for shared/common strategies.
12
12
 
13
13
  from abc import ABC, abstractmethod
14
14
  from typing import Optional, Any
15
- from ..contracts import ICachingStrategy
15
+ from ..contracts import ICachingStrategy, ICacheStrategy
16
16
 
17
17
  # =============================================================================
18
18
  # ABSTRACT CACHING STRATEGY
19
19
  # =============================================================================
20
20
 
21
21
  class ACachingStrategy(ICachingStrategy, ABC):
22
+ """
23
+ Abstract base class for caching strategies (legacy name).
24
+
25
+ Note: Use ACacheStrategy for new code (ICacheStrategy interface).
26
+ """
27
+ pass
28
+
29
+ class ACacheStrategy(ICacheStrategy, ABC):
22
30
  """
23
31
  Abstract base class for caching strategies.
24
32
 
@@ -51,6 +59,7 @@ class ACachingStrategy(ICachingStrategy, ABC):
51
59
  # =============================================================================
52
60
 
53
61
  __all__ = [
54
- 'ACachingStrategy',
62
+ 'ACachingStrategy', # Legacy name
63
+ 'ACacheStrategy', # New name for ICacheStrategy interface
55
64
  ]
56
65
 
@@ -23,7 +23,7 @@ import importlib
23
23
  import importlib.util
24
24
  import threading
25
25
  from pathlib import Path
26
- from typing import Dict, List, Optional, Tuple, Any, Set
26
+ from typing import Optional, Any
27
27
  from collections import OrderedDict
28
28
  from queue import Queue
29
29
 
@@ -59,7 +59,7 @@ class MultiTierCache:
59
59
  self._l2_dir = l2_dir or Path.home() / ".xwlazy_cache"
60
60
  self._l2_dir.mkdir(parents=True, exist_ok=True)
61
61
  self._enable_l3 = enable_l3
62
- self._l3_patterns: Dict[str, Tuple[int, float]] = {}
62
+ self._l3_patterns: dict[str, tuple[int, float]] = {}
63
63
  self._lock = threading.RLock()
64
64
 
65
65
  self._l2_write_queue: Queue = Queue()
@@ -192,7 +192,7 @@ class MultiTierCache:
192
192
  for old_key, _ in sorted_patterns[:1000]:
193
193
  del self._l3_patterns[old_key]
194
194
 
195
- def get_predictive_keys(self, limit: int = 10) -> List[str]:
195
+ def get_predictive_keys(self, limit: int = 10) -> list[str]:
196
196
  """
197
197
  Get keys likely to be accessed soon (for preloading).
198
198
 
@@ -370,7 +370,7 @@ class InstallationCache:
370
370
 
371
371
  self._cache_file = cache_file
372
372
  self._lock = threading.RLock()
373
- self._cache: Dict[str, Dict[str, Any]] = {}
373
+ self._cache: dict[str, dict[str, Any]] = {}
374
374
  self._dirty = False
375
375
 
376
376
  # Load cache on init
@@ -487,7 +487,7 @@ class InstallationCache:
487
487
  self._dirty = True
488
488
  self._save_cache()
489
489
 
490
- def get_all_installed(self) -> Set[str]:
490
+ def get_all_installed(self) -> set[str]:
491
491
  """
492
492
  Get set of all packages marked as installed.
493
493
 
@@ -17,7 +17,7 @@ import os
17
17
  import sys
18
18
  import logging
19
19
  import io
20
- from typing import Dict, Optional
20
+ from typing import Optional
21
21
  from datetime import datetime
22
22
 
23
23
  # =============================================================================
@@ -37,7 +37,7 @@ _EMOJI_MAP = {
37
37
  }
38
38
 
39
39
  # Default log category states
40
- _CATEGORY_DEFAULTS: Dict[str, bool] = {
40
+ _CATEGORY_DEFAULTS: dict[str, bool] = {
41
41
  "install": True,
42
42
  "hook": False,
43
43
  "enhance": False,
@@ -52,7 +52,7 @@ _CATEGORY_DEFAULTS: Dict[str, bool] = {
52
52
  # =============================================================================
53
53
 
54
54
  _configured = False
55
- _category_overrides: Dict[str, bool] = {}
55
+ _category_overrides: dict[str, bool] = {}
56
56
 
57
57
  # =============================================================================
58
58
  # HELPER FUNCTIONS
@@ -186,7 +186,7 @@ def set_log_category(category: str, enabled: bool) -> None:
186
186
  """
187
187
  _category_overrides[_normalize_category(category)] = bool(enabled)
188
188
 
189
- def set_log_categories(overrides: Dict[str, bool]) -> None:
189
+ def set_log_categories(overrides: dict[str, bool]) -> None:
190
190
  """
191
191
  Bulk update multiple log categories.
192
192
 
@@ -196,7 +196,7 @@ def set_log_categories(overrides: Dict[str, bool]) -> None:
196
196
  for category, enabled in overrides.items():
197
197
  set_log_category(category, enabled)
198
198
 
199
- def get_log_categories() -> Dict[str, bool]:
199
+ def get_log_categories() -> dict[str, bool]:
200
200
  """
201
201
  Return the effective state for each built-in log category.
202
202
 
@@ -5,7 +5,7 @@ This module contains DependencyMapper class extracted from lazy_core.py Section
5
5
  """
6
6
 
7
7
  import threading
8
- from typing import Dict, List, Optional, Set, Tuple
8
+ from typing import Optional
9
9
 
10
10
  # Lazy import to avoid circular dependency
11
11
  def _get_logger():
@@ -59,14 +59,14 @@ class DependencyMapper:
59
59
  def __init__(self, package_name: str = 'default'):
60
60
  """Initialize dependency mapper."""
61
61
  self._discovery = None # Lazy init to avoid circular imports
62
- self._package_import_mapping: Dict[str, List[str]] = {}
63
- self._import_package_mapping: Dict[str, str] = {}
62
+ self._package_import_mapping: dict[str, list[str]] = {}
63
+ self._import_package_mapping: dict[str, str] = {}
64
64
  self._cached = False
65
65
  self._lock = threading.RLock()
66
66
  self._package_name = package_name
67
67
  self._manifest_generation = -1
68
- self._manifest_dependencies: Dict[str, str] = {}
69
- self._manifest_signature: Optional[Tuple[str, float, float]] = None
68
+ self._manifest_dependencies: dict[str, str] = {}
69
+ self._manifest_signature: Optional[tuple[str, float, float]] = None
70
70
  self._manifest_empty = False
71
71
 
72
72
  def set_package_name(self, package_name: str) -> None:
@@ -115,7 +115,7 @@ class DependencyMapper:
115
115
  manifest = loader.get_manifest(self._package_name)
116
116
  current_generation = loader.generation
117
117
 
118
- dependencies: Dict[str, str] = {}
118
+ dependencies: dict[str, str] = {}
119
119
  manifest_empty = True
120
120
  if manifest and manifest.dependencies:
121
121
  dependencies = {
@@ -143,7 +143,7 @@ class DependencyMapper:
143
143
  _cache_spec_if_missing(module_name)
144
144
  return needs_cache
145
145
 
146
- DENY_LIST: Set[str] = {
146
+ DENY_LIST: set[str] = {
147
147
  # POSIX-only modules that don't exist on Windows but try to auto-install
148
148
  "pwd",
149
149
  "grp",
@@ -191,7 +191,9 @@ class DependencyMapper:
191
191
  1. Skip checks (stdlib, deny list)
192
192
  2. Manifest dependencies (explicit user configuration - highest priority)
193
193
  3. Spec cache (module already exists - skip auto-install)
194
- 4. Discovery mappings (automatic discovery)
194
+ 4. Discovery mappings (automatic discovery from project configs)
195
+ 5. Common mappings (quick access list - works without project configs)
196
+ 6. Fallback to import_name itself
195
197
  """
196
198
  if self._should_skip_auto_install(import_name):
197
199
  return None
@@ -206,24 +208,40 @@ class DependencyMapper:
206
208
  if manifest_hit:
207
209
  return manifest_hit
208
210
 
209
- # Check spec cache - if module already exists, skip auto-install
211
+ # Check common mappings BEFORE spec cache (common mappings are reliable)
212
+ # This is important for exonware projects to work immediately
213
+ # Common mappings take precedence over spec cache because spec cache can be stale
214
+ discovery = self._get_discovery()
215
+ common_mappings = getattr(discovery, 'COMMON_MAPPINGS', {})
216
+ common_hit = common_mappings.get(import_name)
217
+ if common_hit:
218
+ return common_hit
219
+
220
+ # Check spec cache - if module already exists AND we don't have a common mapping, skip auto-install
221
+ # Note: We check this AFTER common mappings because spec cache can be stale after uninstallation
210
222
  if _spec_cache_get(import_name):
211
223
  return None
212
224
 
225
+ # Try discovery mappings (from project configs)
213
226
  self._ensure_mappings_cached()
214
- return self._import_package_mapping.get(import_name, import_name)
227
+ discovery_hit = self._import_package_mapping.get(import_name)
228
+ if discovery_hit:
229
+ return discovery_hit
230
+
231
+ # Fallback: assume import name matches package name
232
+ return import_name
215
233
 
216
- def get_import_names(self, package_name: str) -> List[str]:
234
+ def get_import_names(self, package_name: str) -> list[str]:
217
235
  """Get all possible import names for a package."""
218
236
  self._ensure_mappings_cached()
219
237
  return self._package_import_mapping.get(package_name, [package_name])
220
238
 
221
- def get_package_import_mapping(self) -> Dict[str, List[str]]:
239
+ def get_package_import_mapping(self) -> dict[str, list[str]]:
222
240
  """Get complete package to import names mapping."""
223
241
  self._ensure_mappings_cached()
224
242
  return self._package_import_mapping.copy()
225
243
 
226
- def get_import_package_mapping(self) -> Dict[str, str]:
244
+ def get_import_package_mapping(self) -> dict[str, str]:
227
245
  """Get complete import to package name mapping."""
228
246
  self._ensure_mappings_cached()
229
247
  return self._import_package_mapping.copy()
@@ -31,6 +31,11 @@ def _ensure_logging_initialized():
31
31
  global logger
32
32
  if logger is None:
33
33
  logger = _get_logger()
34
+ # Fallback if get_logger returns None (should not happen but safety first)
35
+ if logger is None:
36
+ import logging
37
+ logger = logging.getLogger("xwlazy.install_async_utils.fallback")
38
+ logger.addHandler(logging.NullHandler())
34
39
 
35
40
  async def get_package_size_mb(package_name: str) -> Optional[float]:
36
41
  """
@@ -19,7 +19,7 @@ import tempfile
19
19
  import subprocess
20
20
  import zipfile
21
21
  from pathlib import Path
22
- from typing import Optional, List
22
+ from typing import Optional
23
23
  from contextlib import suppress
24
24
 
25
25
  # Lazy imports
@@ -77,7 +77,7 @@ def get_site_packages_dir() -> Path:
77
77
  path.mkdir(parents=True, exist_ok=True)
78
78
  return path
79
79
 
80
- def pip_install_from_path(wheel_path: Path, policy_args: Optional[List[str]] = None) -> bool:
80
+ def pip_install_from_path(wheel_path: Path, policy_args: Optional[list[str]] = None) -> bool:
81
81
  """Install a wheel file using pip."""
82
82
  try:
83
83
  pip_args = [
@@ -104,7 +104,7 @@ def pip_install_from_path(wheel_path: Path, policy_args: Optional[List[str]] = N
104
104
 
105
105
  def ensure_cached_wheel(
106
106
  package_name: str,
107
- policy_args: Optional[List[str]] = None,
107
+ policy_args: Optional[list[str]] = None,
108
108
  cache_dir: Optional[Path] = None
109
109
  ) -> Optional[Path]:
110
110
  """Ensure a wheel is cached, downloading it if necessary."""
@@ -220,7 +220,7 @@ def has_cached_install_tree(
220
220
 
221
221
  def install_from_cached_wheel(
222
222
  package_name: str,
223
- policy_args: Optional[List[str]] = None,
223
+ policy_args: Optional[list[str]] = None,
224
224
  cache_dir: Optional[Path] = None
225
225
  ) -> bool:
226
226
  """Install from a cached wheel file."""
@@ -21,7 +21,7 @@ import importlib
21
21
  import importlib.machinery
22
22
  import importlib.util
23
23
  from pathlib import Path
24
- from typing import Optional, Tuple
24
+ from typing import Optional
25
25
  from collections import OrderedDict
26
26
  from functools import lru_cache
27
27
 
@@ -31,7 +31,7 @@ _SPEC_CACHE_TTL = float(os.environ.get("XWLAZY_SPEC_CACHE_TTL", "60") or 60.0)
31
31
 
32
32
  # Cache storage
33
33
  _spec_cache_lock = threading.RLock()
34
- _spec_cache: OrderedDict[str, Tuple[importlib.machinery.ModuleSpec, float]] = OrderedDict()
34
+ _spec_cache: OrderedDict[str, tuple[importlib.machinery.ModuleSpec, float]] = OrderedDict()
35
35
 
36
36
  # Multi-level cache: L1 (in-memory) + L2 (disk)
37
37
  _CACHE_L2_DIR = Path(
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import json
4
4
  import os
5
5
  from pathlib import Path
6
- from typing import Dict, Optional
6
+ from typing import Optional
7
7
 
8
8
  def _get_base_config_dir() -> Path:
9
9
  """Determine a cross-platform directory for storing lazy configuration."""
@@ -25,12 +25,12 @@ class LazyStateManager:
25
25
  def __init__(self, package_name: str) -> None:
26
26
  self._package = package_name.lower()
27
27
  self._state_path = _get_base_config_dir() / "state.json"
28
- self._state: Dict[str, Dict[str, bool]] = self._load_state()
28
+ self._state: dict[str, dict[str, bool]] = self._load_state()
29
29
 
30
30
  # --------------------------------------------------------------------- #
31
31
  # Persistence helpers
32
32
  # --------------------------------------------------------------------- #
33
- def _load_state(self) -> Dict[str, Dict[str, bool]]:
33
+ def _load_state(self) -> dict[str, dict[str, bool]]:
34
34
  if not self._state_path.exists():
35
35
  return {}
36
36
  try:
@@ -47,7 +47,7 @@ class LazyStateManager:
47
47
  with self._state_path.open("w", encoding="utf-8") as fh:
48
48
  json.dump(self._state, fh, indent=2, sort_keys=True)
49
49
 
50
- def _ensure_entry(self) -> Dict[str, bool]:
50
+ def _ensure_entry(self) -> dict[str, bool]:
51
51
  return self._state.setdefault(self._package, {})
52
52
 
53
53
  # --------------------------------------------------------------------- #
@@ -11,7 +11,7 @@ Simple dict-based cache implementation.
11
11
  Works with ANY data type (modules, packages, etc.).
12
12
  """
13
13
 
14
- from typing import Dict, Optional, Any
14
+ from typing import Optional, Any
15
15
  from ...common.base import ACachingStrategy
16
16
 
17
17
  class DictCache(ACachingStrategy):
@@ -24,7 +24,7 @@ class DictCache(ACachingStrategy):
24
24
 
25
25
  def __init__(self):
26
26
  """Initialize dict cache."""
27
- self._cache: Dict[str, Any] = {}
27
+ self._cache: dict[str, Any] = {}
28
28
 
29
29
  def get(self, key: str) -> Optional[Any]:
30
30
  """Get value from cache."""
@@ -11,7 +11,7 @@ LFU cache implementation with size limit.
11
11
  Works with ANY data type (modules, packages, etc.).
12
12
  """
13
13
 
14
- from typing import Dict, Optional, Any
14
+ from typing import Optional, Any
15
15
  from collections import Counter
16
16
  from ...common.base import ACachingStrategy
17
17
 
@@ -30,7 +30,7 @@ class LFUCache(ACachingStrategy):
30
30
  Args:
31
31
  max_size: Maximum number of items in cache
32
32
  """
33
- self._cache: Dict[str, Any] = {}
33
+ self._cache: dict[str, Any] = {}
34
34
  self._freq: Counter[str] = Counter()
35
35
  self._max_size = max_size
36
36
 
@@ -12,7 +12,7 @@ Works with ANY data type (modules, packages, etc.).
12
12
  """
13
13
 
14
14
  import time
15
- from typing import Dict, Optional, Any, Tuple
15
+ from typing import Optional, Any
16
16
  from ...common.base import ACachingStrategy
17
17
 
18
18
  class TTLCache(ACachingStrategy):
@@ -30,7 +30,7 @@ class TTLCache(ACachingStrategy):
30
30
  Args:
31
31
  ttl_seconds: Time-to-live in seconds (default: 1 hour)
32
32
  """
33
- self._cache: Dict[str, Tuple[Any, float]] = {} # (value, expiry_time)
33
+ self._cache: dict[str, tuple[Any, float]] = {} # (value, expiry_time)
34
34
  self._ttl = ttl_seconds
35
35
 
36
36
  def get(self, key: str) -> Optional[Any]: