exonware-xwlazy 0.1.0.1__py3-none-any.whl → 0.1.0.9__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/xwlazy/__init__.py +0 -0
- exonware/xwlazy/version.py +2 -2
- exonware_xwlazy-0.1.0.9.dist-info/METADATA +0 -0
- exonware_xwlazy-0.1.0.9.dist-info/RECORD +6 -0
- exonware/__init__.py +0 -42
- exonware/xwlazy/common/__init__.py +0 -55
- exonware/xwlazy/common/base.py +0 -65
- exonware/xwlazy/common/cache.py +0 -504
- exonware/xwlazy/common/logger.py +0 -257
- exonware/xwlazy/common/services/__init__.py +0 -72
- exonware/xwlazy/common/services/dependency_mapper.py +0 -250
- exonware/xwlazy/common/services/install_async_utils.py +0 -170
- exonware/xwlazy/common/services/install_cache_utils.py +0 -245
- exonware/xwlazy/common/services/keyword_detection.py +0 -283
- exonware/xwlazy/common/services/spec_cache.py +0 -165
- exonware/xwlazy/common/services/state_manager.py +0 -84
- exonware/xwlazy/common/strategies/__init__.py +0 -28
- exonware/xwlazy/common/strategies/caching_dict.py +0 -44
- exonware/xwlazy/common/strategies/caching_installation.py +0 -88
- exonware/xwlazy/common/strategies/caching_lfu.py +0 -66
- exonware/xwlazy/common/strategies/caching_lru.py +0 -63
- exonware/xwlazy/common/strategies/caching_multitier.py +0 -59
- exonware/xwlazy/common/strategies/caching_ttl.py +0 -59
- exonware/xwlazy/common/utils.py +0 -142
- exonware/xwlazy/config.py +0 -193
- exonware/xwlazy/contracts.py +0 -1533
- exonware/xwlazy/defs.py +0 -378
- exonware/xwlazy/errors.py +0 -276
- exonware/xwlazy/facade.py +0 -1137
- exonware/xwlazy/host/__init__.py +0 -8
- exonware/xwlazy/host/conf.py +0 -16
- exonware/xwlazy/module/__init__.py +0 -18
- exonware/xwlazy/module/base.py +0 -643
- exonware/xwlazy/module/data.py +0 -17
- exonware/xwlazy/module/facade.py +0 -246
- exonware/xwlazy/module/importer_engine.py +0 -2964
- exonware/xwlazy/module/partial_module_detector.py +0 -275
- exonware/xwlazy/module/strategies/__init__.py +0 -22
- exonware/xwlazy/module/strategies/module_helper_lazy.py +0 -93
- exonware/xwlazy/module/strategies/module_helper_simple.py +0 -65
- exonware/xwlazy/module/strategies/module_manager_advanced.py +0 -111
- exonware/xwlazy/module/strategies/module_manager_simple.py +0 -95
- exonware/xwlazy/package/__init__.py +0 -18
- exonware/xwlazy/package/base.py +0 -877
- exonware/xwlazy/package/conf.py +0 -324
- exonware/xwlazy/package/data.py +0 -17
- exonware/xwlazy/package/facade.py +0 -480
- exonware/xwlazy/package/services/__init__.py +0 -84
- exonware/xwlazy/package/services/async_install_handle.py +0 -87
- exonware/xwlazy/package/services/config_manager.py +0 -249
- exonware/xwlazy/package/services/discovery.py +0 -435
- exonware/xwlazy/package/services/host_packages.py +0 -180
- exonware/xwlazy/package/services/install_async.py +0 -291
- exonware/xwlazy/package/services/install_cache.py +0 -145
- exonware/xwlazy/package/services/install_interactive.py +0 -59
- exonware/xwlazy/package/services/install_policy.py +0 -156
- exonware/xwlazy/package/services/install_registry.py +0 -54
- exonware/xwlazy/package/services/install_result.py +0 -17
- exonware/xwlazy/package/services/install_sbom.py +0 -153
- exonware/xwlazy/package/services/install_utils.py +0 -79
- exonware/xwlazy/package/services/installer_engine.py +0 -406
- exonware/xwlazy/package/services/lazy_installer.py +0 -803
- exonware/xwlazy/package/services/manifest.py +0 -503
- exonware/xwlazy/package/services/strategy_registry.py +0 -324
- exonware/xwlazy/package/strategies/__init__.py +0 -57
- exonware/xwlazy/package/strategies/package_discovery_file.py +0 -129
- exonware/xwlazy/package/strategies/package_discovery_hybrid.py +0 -84
- exonware/xwlazy/package/strategies/package_discovery_manifest.py +0 -101
- exonware/xwlazy/package/strategies/package_execution_async.py +0 -113
- exonware/xwlazy/package/strategies/package_execution_cached.py +0 -90
- exonware/xwlazy/package/strategies/package_execution_pip.py +0 -99
- exonware/xwlazy/package/strategies/package_execution_wheel.py +0 -106
- exonware/xwlazy/package/strategies/package_mapping_discovery_first.py +0 -100
- exonware/xwlazy/package/strategies/package_mapping_hybrid.py +0 -105
- exonware/xwlazy/package/strategies/package_mapping_manifest_first.py +0 -100
- exonware/xwlazy/package/strategies/package_policy_allow_list.py +0 -57
- exonware/xwlazy/package/strategies/package_policy_deny_list.py +0 -57
- exonware/xwlazy/package/strategies/package_policy_permissive.py +0 -46
- exonware/xwlazy/package/strategies/package_timing_clean.py +0 -67
- exonware/xwlazy/package/strategies/package_timing_full.py +0 -66
- exonware/xwlazy/package/strategies/package_timing_smart.py +0 -68
- exonware/xwlazy/package/strategies/package_timing_temporary.py +0 -66
- exonware/xwlazy/runtime/__init__.py +0 -18
- exonware/xwlazy/runtime/adaptive_learner.py +0 -129
- exonware/xwlazy/runtime/base.py +0 -274
- exonware/xwlazy/runtime/facade.py +0 -94
- exonware/xwlazy/runtime/intelligent_selector.py +0 -170
- exonware/xwlazy/runtime/metrics.py +0 -60
- exonware/xwlazy/runtime/performance.py +0 -37
- exonware_xwlazy-0.1.0.1.dist-info/METADATA +0 -454
- exonware_xwlazy-0.1.0.1.dist-info/RECORD +0 -93
- xwlazy/__init__.py +0 -14
- xwlazy/lazy.py +0 -30
- {exonware_xwlazy-0.1.0.1.dist-info → exonware_xwlazy-0.1.0.9.dist-info}/WHEEL +0 -0
- {exonware_xwlazy-0.1.0.1.dist-info → exonware_xwlazy-0.1.0.9.dist-info}/licenses/LICENSE +0 -0
exonware/xwlazy/__init__.py
CHANGED
|
Binary file
|
exonware/xwlazy/version.py
CHANGED
|
@@ -14,13 +14,13 @@ All version references should import from this module to ensure consistency.
|
|
|
14
14
|
# =============================================================================
|
|
15
15
|
|
|
16
16
|
# Main version - update this to change version across entire project
|
|
17
|
-
__version__ = "0.1.0.
|
|
17
|
+
__version__ = "0.1.0.9"
|
|
18
18
|
|
|
19
19
|
# Version components for programmatic access
|
|
20
20
|
VERSION_MAJOR = 0
|
|
21
21
|
VERSION_MINOR = 1
|
|
22
22
|
VERSION_PATCH = 0
|
|
23
|
-
VERSION_BUILD =
|
|
23
|
+
VERSION_BUILD = 9 # Set to None for releases, or build number for dev builds
|
|
24
24
|
|
|
25
25
|
# Version metadata
|
|
26
26
|
VERSION_SUFFIX = "" # e.g., "dev", "alpha", "beta", "rc1"
|
|
Binary file
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
exonware/xwlazy/__init__.py,sha256=ZXpGiNGjj5KRB6q4nDas863XgPcfOa89X8YTC2M_W2o,448
|
|
2
|
+
exonware/xwlazy/version.py,sha256=5NutoLHVDnPmWSl88r-na_F7RH1HOpJgwEyavYK4fBg,2349
|
|
3
|
+
exonware_xwlazy-0.1.0.9.dist-info/METADATA,sha256=54aU__J_zsmCyNPDU56kcI2Hw3suY1nU3Los3C6cluM,1098
|
|
4
|
+
exonware_xwlazy-0.1.0.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
5
|
+
exonware_xwlazy-0.1.0.9.dist-info/licenses/LICENSE,sha256=w42ohoEUfhyT0NgiivAL4fWg2AMRLGnfXPMAR4EO-MU,1094
|
|
6
|
+
exonware_xwlazy-0.1.0.9.dist-info/RECORD,,
|
exonware/__init__.py
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
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
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
#exonware/xwlazy/src/exonware/xwlazy/common/__init__.py
|
|
3
|
-
|
|
4
|
-
Common utilities shared across package, module, and runtime.
|
|
5
|
-
|
|
6
|
-
Company: eXonware.com
|
|
7
|
-
Author: Eng. Muhammad AlShehri
|
|
8
|
-
Email: connect@exonware.com
|
|
9
|
-
|
|
10
|
-
Generation Date: 15-Nov-2025
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
from .logger import (
|
|
14
|
-
get_logger,
|
|
15
|
-
log_event,
|
|
16
|
-
print_formatted,
|
|
17
|
-
format_message,
|
|
18
|
-
is_log_category_enabled,
|
|
19
|
-
set_log_category,
|
|
20
|
-
set_log_categories,
|
|
21
|
-
get_log_categories,
|
|
22
|
-
XWLazyFormatter,
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
from .cache import (
|
|
26
|
-
MultiTierCache,
|
|
27
|
-
BytecodeCache,
|
|
28
|
-
InstallationCache,
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
from .utils import (
|
|
32
|
-
find_project_root,
|
|
33
|
-
find_config_file,
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
__all__ = [
|
|
37
|
-
# Logger
|
|
38
|
-
'get_logger',
|
|
39
|
-
'log_event',
|
|
40
|
-
'print_formatted',
|
|
41
|
-
'format_message',
|
|
42
|
-
'is_log_category_enabled',
|
|
43
|
-
'set_log_category',
|
|
44
|
-
'set_log_categories',
|
|
45
|
-
'get_log_categories',
|
|
46
|
-
'XWLazyFormatter',
|
|
47
|
-
# Cache
|
|
48
|
-
'MultiTierCache',
|
|
49
|
-
'BytecodeCache',
|
|
50
|
-
'InstallationCache',
|
|
51
|
-
# Utils
|
|
52
|
-
'find_project_root',
|
|
53
|
-
'find_config_file',
|
|
54
|
-
]
|
|
55
|
-
|
exonware/xwlazy/common/base.py
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Common Abstract Base Classes
|
|
3
|
-
|
|
4
|
-
Company: eXonware.com
|
|
5
|
-
Author: Eng. Muhammad AlShehri
|
|
6
|
-
Email: connect@exonware.com
|
|
7
|
-
|
|
8
|
-
Generation Date: 15-Nov-2025
|
|
9
|
-
|
|
10
|
-
Abstract base classes for shared/common strategies.
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
from abc import ABC, abstractmethod
|
|
14
|
-
from typing import Optional, Any
|
|
15
|
-
from ..contracts import ICachingStrategy, ICacheStrategy
|
|
16
|
-
|
|
17
|
-
# =============================================================================
|
|
18
|
-
# ABSTRACT CACHING STRATEGY
|
|
19
|
-
# =============================================================================
|
|
20
|
-
|
|
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):
|
|
30
|
-
"""
|
|
31
|
-
Abstract base class for caching strategies.
|
|
32
|
-
|
|
33
|
-
Works with ANY data type (modules, packages, etc.).
|
|
34
|
-
All caching strategies must extend this class.
|
|
35
|
-
"""
|
|
36
|
-
|
|
37
|
-
@abstractmethod
|
|
38
|
-
def get(self, key: str) -> Optional[Any]:
|
|
39
|
-
"""Get value from cache."""
|
|
40
|
-
...
|
|
41
|
-
|
|
42
|
-
@abstractmethod
|
|
43
|
-
def set(self, key: str, value: Any) -> None:
|
|
44
|
-
"""Set value in cache."""
|
|
45
|
-
...
|
|
46
|
-
|
|
47
|
-
@abstractmethod
|
|
48
|
-
def invalidate(self, key: str) -> None:
|
|
49
|
-
"""Invalidate cached value."""
|
|
50
|
-
...
|
|
51
|
-
|
|
52
|
-
@abstractmethod
|
|
53
|
-
def clear(self) -> None:
|
|
54
|
-
"""Clear all cached values."""
|
|
55
|
-
...
|
|
56
|
-
|
|
57
|
-
# =============================================================================
|
|
58
|
-
# EXPORT ALL
|
|
59
|
-
# =============================================================================
|
|
60
|
-
|
|
61
|
-
__all__ = [
|
|
62
|
-
'ACachingStrategy', # Legacy name
|
|
63
|
-
'ACacheStrategy', # New name for ICacheStrategy interface
|
|
64
|
-
]
|
|
65
|
-
|
exonware/xwlazy/common/cache.py
DELETED
|
@@ -1,504 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
#exonware/xwlazy/src/exonware/xwlazy/common/cache.py
|
|
3
|
-
|
|
4
|
-
Cache utilities for xwlazy - shared across package, module, and runtime.
|
|
5
|
-
|
|
6
|
-
Company: eXonware.com
|
|
7
|
-
Author: Eng. Muhammad AlShehri
|
|
8
|
-
Email: connect@exonware.com
|
|
9
|
-
|
|
10
|
-
Generation Date: 15-Nov-2025
|
|
11
|
-
|
|
12
|
-
This module provides unified caching functionality for all xwlazy components.
|
|
13
|
-
All cache code is centralized here to avoid duplication.
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
import os
|
|
17
|
-
import sys
|
|
18
|
-
import json
|
|
19
|
-
import time
|
|
20
|
-
import pickle
|
|
21
|
-
import struct
|
|
22
|
-
import importlib
|
|
23
|
-
import importlib.util
|
|
24
|
-
import threading
|
|
25
|
-
from pathlib import Path
|
|
26
|
-
from typing import Optional, Any
|
|
27
|
-
from collections import OrderedDict
|
|
28
|
-
from queue import Queue
|
|
29
|
-
|
|
30
|
-
from .logger import get_logger
|
|
31
|
-
|
|
32
|
-
logger = get_logger("xwlazy.cache")
|
|
33
|
-
|
|
34
|
-
# =============================================================================
|
|
35
|
-
# MULTI-TIER CACHE
|
|
36
|
-
# =============================================================================
|
|
37
|
-
|
|
38
|
-
class MultiTierCache:
|
|
39
|
-
"""
|
|
40
|
-
Multi-tier cache with L1 (memory), L2 (disk), L3 (predictive).
|
|
41
|
-
|
|
42
|
-
Used by package, module, and runtime for caching:
|
|
43
|
-
- Package installation status
|
|
44
|
-
- Module imports
|
|
45
|
-
- Runtime metrics and performance data
|
|
46
|
-
"""
|
|
47
|
-
|
|
48
|
-
def __init__(self, l1_size: int = 1000, l2_dir: Optional[Path] = None, enable_l3: bool = True):
|
|
49
|
-
"""
|
|
50
|
-
Initialize multi-tier cache.
|
|
51
|
-
|
|
52
|
-
Args:
|
|
53
|
-
l1_size: Maximum size of L1 (memory) cache
|
|
54
|
-
l2_dir: Directory for L2 (disk) cache (defaults to ~/.xwlazy_cache)
|
|
55
|
-
enable_l3: Enable L3 (predictive) cache
|
|
56
|
-
"""
|
|
57
|
-
self._l1_cache: OrderedDict[str, Any] = OrderedDict()
|
|
58
|
-
self._l1_max_size = l1_size
|
|
59
|
-
self._l2_dir = l2_dir or Path.home() / ".xwlazy_cache"
|
|
60
|
-
self._l2_dir.mkdir(parents=True, exist_ok=True)
|
|
61
|
-
self._enable_l3 = enable_l3
|
|
62
|
-
self._l3_patterns: dict[str, tuple[int, float]] = {}
|
|
63
|
-
self._lock = threading.RLock()
|
|
64
|
-
|
|
65
|
-
self._l2_write_queue: Queue = Queue()
|
|
66
|
-
self._l2_write_thread: Optional[threading.Thread] = None
|
|
67
|
-
self._l2_write_stop = threading.Event()
|
|
68
|
-
self._start_l2_writer()
|
|
69
|
-
|
|
70
|
-
def get(self, key: str) -> Optional[Any]:
|
|
71
|
-
"""
|
|
72
|
-
Get value from cache (L1 -> L2 -> L3).
|
|
73
|
-
|
|
74
|
-
Args:
|
|
75
|
-
key: Cache key
|
|
76
|
-
|
|
77
|
-
Returns:
|
|
78
|
-
Cached value or None if not found
|
|
79
|
-
"""
|
|
80
|
-
# Check L1 (memory) cache first
|
|
81
|
-
if key in self._l1_cache:
|
|
82
|
-
with self._lock:
|
|
83
|
-
if key in self._l1_cache:
|
|
84
|
-
value = self._l1_cache.pop(key)
|
|
85
|
-
self._l1_cache[key] = value # Move to end (LRU)
|
|
86
|
-
self._update_l3_pattern(key)
|
|
87
|
-
return value
|
|
88
|
-
|
|
89
|
-
# Check L2 (disk) cache
|
|
90
|
-
l2_path = self._l2_dir / f"{hash(key) % (2**31)}.cache"
|
|
91
|
-
if l2_path.exists():
|
|
92
|
-
try:
|
|
93
|
-
with open(l2_path, 'rb') as f:
|
|
94
|
-
value = pickle.load(f)
|
|
95
|
-
with self._lock:
|
|
96
|
-
self._set_l1(key, value) # Promote to L1
|
|
97
|
-
self._update_l3_pattern(key)
|
|
98
|
-
return value
|
|
99
|
-
except Exception as e:
|
|
100
|
-
logger.debug(f"Failed to load L2 cache for {key}: {e}")
|
|
101
|
-
|
|
102
|
-
# L3 (predictive) - just logs, doesn't return
|
|
103
|
-
if self._enable_l3 and key in self._l3_patterns:
|
|
104
|
-
freq, _ = self._l3_patterns[key]
|
|
105
|
-
if freq > 5:
|
|
106
|
-
logger.debug(f"L3 pattern detected for {key} (freq: {freq})")
|
|
107
|
-
|
|
108
|
-
return None
|
|
109
|
-
|
|
110
|
-
def set(self, key: str, value: Any) -> None:
|
|
111
|
-
"""
|
|
112
|
-
Set value in cache (L1 + L2 batched).
|
|
113
|
-
|
|
114
|
-
Args:
|
|
115
|
-
key: Cache key
|
|
116
|
-
value: Value to cache
|
|
117
|
-
"""
|
|
118
|
-
with self._lock:
|
|
119
|
-
self._set_l1(key, value)
|
|
120
|
-
self._update_l3_pattern(key)
|
|
121
|
-
|
|
122
|
-
# Queue for L2 write (batched)
|
|
123
|
-
self._l2_write_queue.put((key, value))
|
|
124
|
-
|
|
125
|
-
def _set_l1(self, key: str, value: Any) -> None:
|
|
126
|
-
"""Set value in L1 cache (internal, called with lock held)."""
|
|
127
|
-
if key in self._l1_cache:
|
|
128
|
-
self._l1_cache.move_to_end(key)
|
|
129
|
-
else:
|
|
130
|
-
self._l1_cache[key] = value
|
|
131
|
-
if len(self._l1_cache) > self._l1_max_size:
|
|
132
|
-
self._l1_cache.popitem(last=False) # Remove oldest (LRU)
|
|
133
|
-
|
|
134
|
-
def _set_l2(self, key: str, value: Any) -> None:
|
|
135
|
-
"""Set value in L2 cache (internal, called by writer thread)."""
|
|
136
|
-
try:
|
|
137
|
-
l2_path = self._l2_dir / f"{hash(key) % (2**31)}.cache"
|
|
138
|
-
with open(l2_path, 'wb') as f:
|
|
139
|
-
pickle.dump(value, f)
|
|
140
|
-
except Exception as e:
|
|
141
|
-
logger.debug(f"Failed to save L2 cache for {key}: {e}")
|
|
142
|
-
|
|
143
|
-
def _start_l2_writer(self) -> None:
|
|
144
|
-
"""Start background thread for batched L2 writes."""
|
|
145
|
-
def _l2_writer():
|
|
146
|
-
batch = []
|
|
147
|
-
batch_size = 10
|
|
148
|
-
batch_timeout = 0.1
|
|
149
|
-
|
|
150
|
-
while not self._l2_write_stop.is_set():
|
|
151
|
-
try:
|
|
152
|
-
try:
|
|
153
|
-
key, value = self._l2_write_queue.get(timeout=batch_timeout)
|
|
154
|
-
batch.append((key, value))
|
|
155
|
-
|
|
156
|
-
# Collect batch
|
|
157
|
-
for _ in range(batch_size - 1):
|
|
158
|
-
try:
|
|
159
|
-
key, value = self._l2_write_queue.get_nowait()
|
|
160
|
-
batch.append((key, value))
|
|
161
|
-
except:
|
|
162
|
-
break
|
|
163
|
-
except:
|
|
164
|
-
pass
|
|
165
|
-
|
|
166
|
-
# Write batch
|
|
167
|
-
if batch:
|
|
168
|
-
for key, value in batch:
|
|
169
|
-
self._set_l2(key, value)
|
|
170
|
-
batch.clear()
|
|
171
|
-
except Exception as e:
|
|
172
|
-
logger.debug(f"L2 writer error: {e}")
|
|
173
|
-
|
|
174
|
-
self._l2_write_thread = threading.Thread(target=_l2_writer, daemon=True, name="xwlazy-l2-writer")
|
|
175
|
-
self._l2_write_thread.start()
|
|
176
|
-
|
|
177
|
-
def shutdown(self) -> None:
|
|
178
|
-
"""Shutdown L2 writer thread."""
|
|
179
|
-
self._l2_write_stop.set()
|
|
180
|
-
if self._l2_write_thread:
|
|
181
|
-
self._l2_write_thread.join(timeout=1.0)
|
|
182
|
-
|
|
183
|
-
def _update_l3_pattern(self, key: str) -> None:
|
|
184
|
-
"""Update L3 access patterns (called with lock held)."""
|
|
185
|
-
if self._enable_l3:
|
|
186
|
-
freq, _ = self._l3_patterns.get(key, (0, 0.0))
|
|
187
|
-
self._l3_patterns[key] = (freq + 1, time.time())
|
|
188
|
-
|
|
189
|
-
# Prune old patterns if too many
|
|
190
|
-
if len(self._l3_patterns) > 10000:
|
|
191
|
-
sorted_patterns = sorted(self._l3_patterns.items(), key=lambda x: x[1][1])
|
|
192
|
-
for old_key, _ in sorted_patterns[:1000]:
|
|
193
|
-
del self._l3_patterns[old_key]
|
|
194
|
-
|
|
195
|
-
def get_predictive_keys(self, limit: int = 10) -> list[str]:
|
|
196
|
-
"""
|
|
197
|
-
Get keys likely to be accessed soon (for preloading).
|
|
198
|
-
|
|
199
|
-
Args:
|
|
200
|
-
limit: Maximum number of keys to return
|
|
201
|
-
|
|
202
|
-
Returns:
|
|
203
|
-
List of keys sorted by access likelihood
|
|
204
|
-
"""
|
|
205
|
-
with self._lock:
|
|
206
|
-
if not self._enable_l3:
|
|
207
|
-
return []
|
|
208
|
-
|
|
209
|
-
scored = [
|
|
210
|
-
(key, freq * (1.0 / (time.time() - last + 1.0)))
|
|
211
|
-
for key, (freq, last) in self._l3_patterns.items()
|
|
212
|
-
]
|
|
213
|
-
scored.sort(key=lambda x: x[1], reverse=True)
|
|
214
|
-
return [key for key, _ in scored[:limit]]
|
|
215
|
-
|
|
216
|
-
def clear(self) -> None:
|
|
217
|
-
"""Clear all cache tiers."""
|
|
218
|
-
with self._lock:
|
|
219
|
-
self._l1_cache.clear()
|
|
220
|
-
self._l3_patterns.clear()
|
|
221
|
-
|
|
222
|
-
# Clear L2 directory
|
|
223
|
-
try:
|
|
224
|
-
for cache_file in self._l2_dir.glob("*.cache"):
|
|
225
|
-
cache_file.unlink()
|
|
226
|
-
except Exception as e:
|
|
227
|
-
logger.debug(f"Failed to clear L2 cache: {e}")
|
|
228
|
-
|
|
229
|
-
# =============================================================================
|
|
230
|
-
# BYTECODE CACHE
|
|
231
|
-
# =============================================================================
|
|
232
|
-
|
|
233
|
-
class BytecodeCache:
|
|
234
|
-
"""
|
|
235
|
-
Bytecode caching for faster module loading.
|
|
236
|
-
|
|
237
|
-
Caches compiled Python bytecode to avoid recompilation on subsequent imports.
|
|
238
|
-
Used by module loading for performance optimization.
|
|
239
|
-
"""
|
|
240
|
-
|
|
241
|
-
def __init__(self, cache_dir: Optional[Path] = None):
|
|
242
|
-
"""
|
|
243
|
-
Initialize bytecode cache.
|
|
244
|
-
|
|
245
|
-
Args:
|
|
246
|
-
cache_dir: Directory for bytecode cache (defaults to ~/.xwlazy_bytecode)
|
|
247
|
-
"""
|
|
248
|
-
self._cache_dir = cache_dir or Path.home() / ".xwlazy_bytecode"
|
|
249
|
-
self._cache_dir.mkdir(parents=True, exist_ok=True)
|
|
250
|
-
self._lock = threading.RLock()
|
|
251
|
-
|
|
252
|
-
def get_bytecode_path(self, module_path: str) -> Path:
|
|
253
|
-
"""
|
|
254
|
-
Get bytecode cache path for module.
|
|
255
|
-
|
|
256
|
-
Args:
|
|
257
|
-
module_path: Module path (e.g., "exonware.xwdata")
|
|
258
|
-
|
|
259
|
-
Returns:
|
|
260
|
-
Path to bytecode cache file
|
|
261
|
-
"""
|
|
262
|
-
cache_name = f"{hash(module_path) % (2**31)}.pyc"
|
|
263
|
-
return self._cache_dir / cache_name
|
|
264
|
-
|
|
265
|
-
def get_cached_bytecode(self, module_path: str) -> Optional[bytes]:
|
|
266
|
-
"""
|
|
267
|
-
Get cached bytecode if available and valid.
|
|
268
|
-
|
|
269
|
-
Args:
|
|
270
|
-
module_path: Module path
|
|
271
|
-
|
|
272
|
-
Returns:
|
|
273
|
-
Cached bytecode or None if not available/invalid
|
|
274
|
-
"""
|
|
275
|
-
with self._lock:
|
|
276
|
-
cache_path = self.get_bytecode_path(module_path)
|
|
277
|
-
if not cache_path.exists():
|
|
278
|
-
return None
|
|
279
|
-
|
|
280
|
-
try:
|
|
281
|
-
# Check if source is newer than cache
|
|
282
|
-
source_path = self._find_source_path(module_path)
|
|
283
|
-
if source_path and source_path.exists():
|
|
284
|
-
source_mtime = source_path.stat().st_mtime
|
|
285
|
-
cache_mtime = cache_path.stat().st_mtime
|
|
286
|
-
if source_mtime > cache_mtime:
|
|
287
|
-
return None # Source is newer, cache invalid
|
|
288
|
-
|
|
289
|
-
# Read bytecode (skip 16-byte header)
|
|
290
|
-
with open(cache_path, 'rb') as f:
|
|
291
|
-
f.seek(16)
|
|
292
|
-
return f.read()
|
|
293
|
-
except Exception as e:
|
|
294
|
-
logger.debug(f"Failed to load bytecode cache for {module_path}: {e}")
|
|
295
|
-
return None
|
|
296
|
-
|
|
297
|
-
def cache_bytecode(self, module_path: str, bytecode: bytes) -> None:
|
|
298
|
-
"""
|
|
299
|
-
Cache compiled bytecode.
|
|
300
|
-
|
|
301
|
-
Args:
|
|
302
|
-
module_path: Module path
|
|
303
|
-
bytecode: Compiled bytecode to cache
|
|
304
|
-
"""
|
|
305
|
-
with self._lock:
|
|
306
|
-
try:
|
|
307
|
-
cache_path = self.get_bytecode_path(module_path)
|
|
308
|
-
with open(cache_path, 'wb') as f:
|
|
309
|
-
# Write Python bytecode header
|
|
310
|
-
f.write(importlib.util.MAGIC_NUMBER)
|
|
311
|
-
f.write(struct.pack('<I', int(time.time()))) # Timestamp
|
|
312
|
-
f.write(struct.pack('<I', 0)) # Size (0 = unknown)
|
|
313
|
-
f.write(bytecode)
|
|
314
|
-
except Exception as e:
|
|
315
|
-
logger.debug(f"Failed to cache bytecode for {module_path}: {e}")
|
|
316
|
-
|
|
317
|
-
def _find_source_path(self, module_path: str) -> Optional[Path]:
|
|
318
|
-
"""
|
|
319
|
-
Find source file path for module.
|
|
320
|
-
|
|
321
|
-
Args:
|
|
322
|
-
module_path: Module path
|
|
323
|
-
|
|
324
|
-
Returns:
|
|
325
|
-
Path to source file or None if not found
|
|
326
|
-
"""
|
|
327
|
-
try:
|
|
328
|
-
spec = importlib.util.find_spec(module_path)
|
|
329
|
-
if spec and spec.origin:
|
|
330
|
-
return Path(spec.origin)
|
|
331
|
-
except Exception:
|
|
332
|
-
pass
|
|
333
|
-
return None
|
|
334
|
-
|
|
335
|
-
def clear(self) -> None:
|
|
336
|
-
"""Clear bytecode cache."""
|
|
337
|
-
with self._lock:
|
|
338
|
-
try:
|
|
339
|
-
for cache_file in self._cache_dir.glob("*.pyc"):
|
|
340
|
-
cache_file.unlink()
|
|
341
|
-
except Exception as e:
|
|
342
|
-
logger.debug(f"Failed to clear bytecode cache: {e}")
|
|
343
|
-
|
|
344
|
-
# =============================================================================
|
|
345
|
-
# INSTALLATION CACHE
|
|
346
|
-
# =============================================================================
|
|
347
|
-
|
|
348
|
-
class InstallationCache:
|
|
349
|
-
"""
|
|
350
|
-
Persistent file-based cache for tracking installed packages.
|
|
351
|
-
|
|
352
|
-
Cache format: {package_name: {installed: bool, version: str, timestamp: float}}
|
|
353
|
-
Cache location: ~/.xwlazy/installed_packages.json
|
|
354
|
-
|
|
355
|
-
Used by package installer to track which packages are installed,
|
|
356
|
-
avoiding expensive importability checks on subsequent runs.
|
|
357
|
-
"""
|
|
358
|
-
|
|
359
|
-
def __init__(self, cache_file: Optional[Path] = None):
|
|
360
|
-
"""
|
|
361
|
-
Initialize installation cache.
|
|
362
|
-
|
|
363
|
-
Args:
|
|
364
|
-
cache_file: Optional path to cache file. Defaults to ~/.xwlazy/installed_packages.json
|
|
365
|
-
"""
|
|
366
|
-
if cache_file is None:
|
|
367
|
-
cache_dir = Path.home() / ".xwlazy"
|
|
368
|
-
cache_dir.mkdir(parents=True, exist_ok=True)
|
|
369
|
-
cache_file = cache_dir / "installed_packages.json"
|
|
370
|
-
|
|
371
|
-
self._cache_file = cache_file
|
|
372
|
-
self._lock = threading.RLock()
|
|
373
|
-
self._cache: dict[str, dict[str, Any]] = {}
|
|
374
|
-
self._dirty = False
|
|
375
|
-
|
|
376
|
-
# Load cache on init
|
|
377
|
-
self._load_cache()
|
|
378
|
-
|
|
379
|
-
def _load_cache(self) -> None:
|
|
380
|
-
"""Load cache from disk."""
|
|
381
|
-
if not self._cache_file.exists():
|
|
382
|
-
self._cache = {}
|
|
383
|
-
return
|
|
384
|
-
|
|
385
|
-
try:
|
|
386
|
-
with self._lock:
|
|
387
|
-
with open(self._cache_file, 'r', encoding='utf-8') as f:
|
|
388
|
-
data = json.load(f)
|
|
389
|
-
# Validate format
|
|
390
|
-
if isinstance(data, dict):
|
|
391
|
-
self._cache = {k: v for k, v in data.items()
|
|
392
|
-
if isinstance(v, dict) and 'installed' in v}
|
|
393
|
-
else:
|
|
394
|
-
self._cache = {}
|
|
395
|
-
except (json.JSONDecodeError, IOError, OSError) as e:
|
|
396
|
-
logger.debug(f"Failed to load installation cache: {e}")
|
|
397
|
-
self._cache = {}
|
|
398
|
-
|
|
399
|
-
def _save_cache(self) -> None:
|
|
400
|
-
"""Save cache to disk."""
|
|
401
|
-
if not self._dirty:
|
|
402
|
-
return
|
|
403
|
-
|
|
404
|
-
try:
|
|
405
|
-
with self._lock:
|
|
406
|
-
# Create parent directory if needed
|
|
407
|
-
self._cache_file.parent.mkdir(parents=True, exist_ok=True)
|
|
408
|
-
|
|
409
|
-
# Write atomically using temp file
|
|
410
|
-
temp_file = self._cache_file.with_suffix('.tmp')
|
|
411
|
-
with open(temp_file, 'w', encoding='utf-8') as f:
|
|
412
|
-
json.dump(self._cache, f, indent=2, sort_keys=True)
|
|
413
|
-
|
|
414
|
-
# Atomic rename
|
|
415
|
-
temp_file.replace(self._cache_file)
|
|
416
|
-
self._dirty = False
|
|
417
|
-
except (IOError, OSError) as e:
|
|
418
|
-
logger.warning(f"Failed to save installation cache: {e}")
|
|
419
|
-
|
|
420
|
-
def is_installed(self, package_name: str) -> bool:
|
|
421
|
-
"""
|
|
422
|
-
Check if package is marked as installed in cache.
|
|
423
|
-
|
|
424
|
-
Args:
|
|
425
|
-
package_name: Name of the package to check
|
|
426
|
-
|
|
427
|
-
Returns:
|
|
428
|
-
True if package is in cache and marked as installed, False otherwise
|
|
429
|
-
"""
|
|
430
|
-
with self._lock:
|
|
431
|
-
entry = self._cache.get(package_name)
|
|
432
|
-
if entry is None:
|
|
433
|
-
return False
|
|
434
|
-
return entry.get('installed', False)
|
|
435
|
-
|
|
436
|
-
def mark_installed(self, package_name: str, version: Optional[str] = None) -> None:
|
|
437
|
-
"""
|
|
438
|
-
Mark package as installed in cache.
|
|
439
|
-
|
|
440
|
-
Args:
|
|
441
|
-
package_name: Name of the package
|
|
442
|
-
version: Optional version string
|
|
443
|
-
"""
|
|
444
|
-
with self._lock:
|
|
445
|
-
self._cache[package_name] = {
|
|
446
|
-
'installed': True,
|
|
447
|
-
'version': version or 'unknown',
|
|
448
|
-
'timestamp': time.time()
|
|
449
|
-
}
|
|
450
|
-
self._dirty = True
|
|
451
|
-
self._save_cache()
|
|
452
|
-
|
|
453
|
-
def mark_uninstalled(self, package_name: str) -> None:
|
|
454
|
-
"""
|
|
455
|
-
Mark package as uninstalled in cache.
|
|
456
|
-
|
|
457
|
-
Args:
|
|
458
|
-
package_name: Name of the package
|
|
459
|
-
"""
|
|
460
|
-
with self._lock:
|
|
461
|
-
if package_name in self._cache:
|
|
462
|
-
self._cache[package_name]['installed'] = False
|
|
463
|
-
self._cache[package_name]['timestamp'] = time.time()
|
|
464
|
-
self._dirty = True
|
|
465
|
-
self._save_cache()
|
|
466
|
-
|
|
467
|
-
def get_version(self, package_name: str) -> Optional[str]:
|
|
468
|
-
"""
|
|
469
|
-
Get cached version of package.
|
|
470
|
-
|
|
471
|
-
Args:
|
|
472
|
-
package_name: Name of the package
|
|
473
|
-
|
|
474
|
-
Returns:
|
|
475
|
-
Version string if available, None otherwise
|
|
476
|
-
"""
|
|
477
|
-
with self._lock:
|
|
478
|
-
entry = self._cache.get(package_name)
|
|
479
|
-
if entry and entry.get('installed', False):
|
|
480
|
-
return entry.get('version')
|
|
481
|
-
return None
|
|
482
|
-
|
|
483
|
-
def clear(self) -> None:
|
|
484
|
-
"""Clear all cache entries."""
|
|
485
|
-
with self._lock:
|
|
486
|
-
self._cache.clear()
|
|
487
|
-
self._dirty = True
|
|
488
|
-
self._save_cache()
|
|
489
|
-
|
|
490
|
-
def get_all_installed(self) -> set[str]:
|
|
491
|
-
"""
|
|
492
|
-
Get set of all packages marked as installed.
|
|
493
|
-
|
|
494
|
-
Returns:
|
|
495
|
-
Set of package names that are marked as installed
|
|
496
|
-
"""
|
|
497
|
-
with self._lock:
|
|
498
|
-
return {name for name, entry in self._cache.items()
|
|
499
|
-
if entry.get('installed', False)}
|
|
500
|
-
|
|
501
|
-
def __len__(self) -> int:
|
|
502
|
-
"""Return number of cached packages."""
|
|
503
|
-
return len(self._cache)
|
|
504
|
-
|