speedy-utils 1.1.24__tar.gz → 1.1.25__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.
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/PKG-INFO +1 -1
- speedy_utils-1.1.25/debug_imemoize.py +66 -0
- speedy_utils-1.1.25/debug_imemoize_flow.py +95 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/pyproject.toml +1 -1
- speedy_utils-1.1.25/simple_test_imemoize.py +64 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/speedy_utils/__init__.py +3 -1
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/speedy_utils/common/utils_cache.py +142 -1
- speedy_utils-1.1.25/test_imemoize.py +116 -0
- speedy_utils-1.1.25/test_imemoize_persistence.py +122 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/.github/copilot-instructions.md +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/.github/workflows/publish.yml +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/.gitignore +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/.pre-commit-config.yaml +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/IMPROVEMENTS.md +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/README.md +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/bumpversion.sh +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/examples/temperature_range_example.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/examples_improved_error_tracing.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/notebooks/llm_utils/llm_as_a_judge.ipynb +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/notebooks/test_multi_thread.ipynb +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/ruff.toml +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/scripts/deploy.sh +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/setup.cfg +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/llm_utils/__init__.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/llm_utils/chat_format/__init__.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/llm_utils/chat_format/display.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/llm_utils/chat_format/transform.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/llm_utils/chat_format/utils.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/llm_utils/group_messages.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/llm_utils/lm/__init__.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/llm_utils/lm/async_lm/__init__.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/llm_utils/lm/async_lm/_utils.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/llm_utils/lm/async_lm/async_llm_task.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/llm_utils/lm/async_lm/async_lm.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/llm_utils/lm/async_lm/async_lm_base.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/llm_utils/lm/async_lm/lm_specific.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/llm_utils/lm/base_prompt_builder.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/llm_utils/lm/llm.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/llm_utils/lm/llm_signature.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/llm_utils/lm/lm_base.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/llm_utils/lm/mixins.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/llm_utils/lm/openai_memoize.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/llm_utils/lm/signature.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/llm_utils/lm/utils.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/llm_utils/scripts/README.md +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/llm_utils/scripts/vllm_load_balancer.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/llm_utils/scripts/vllm_serve.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/llm_utils/vector_cache/__init__.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/llm_utils/vector_cache/cli.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/llm_utils/vector_cache/core.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/llm_utils/vector_cache/types.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/llm_utils/vector_cache/utils.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/speedy_utils/all.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/speedy_utils/common/__init__.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/speedy_utils/common/clock.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/speedy_utils/common/function_decorator.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/speedy_utils/common/logger.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/speedy_utils/common/notebook_utils.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/speedy_utils/common/patcher.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/speedy_utils/common/report_manager.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/speedy_utils/common/utils_io.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/speedy_utils/common/utils_misc.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/speedy_utils/common/utils_print.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/speedy_utils/multi_worker/__init__.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/speedy_utils/multi_worker/process.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/speedy_utils/multi_worker/thread.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/speedy_utils/scripts/__init__.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/speedy_utils/scripts/mpython.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/speedy_utils/scripts/openapi_client_codegen.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/tests/llm_utils/test_llm_mixins.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/tests/sample_objects.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/tests/test.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/tests/test_logger.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/tests/test_logger_format.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/tests/test_memoize_typing.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/tests/test_mpython.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/tests/test_multithread_error_trace.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/tests/test_process.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/tests/test_process_update.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/tests/test_thread.py +0 -0
- {speedy_utils-1.1.24 → speedy_utils-1.1.25}/uv.lock +0 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Debug the imemoize cache key generation
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
import time
|
|
8
|
+
import os.path as osp
|
|
9
|
+
sys.path.insert(0, osp.join(osp.dirname(__file__), 'src'))
|
|
10
|
+
|
|
11
|
+
from speedy_utils.common.utils_cache import imemoize, _GLOBAL_MEMORY_CACHE, get_source, identify, _compute_cache_components
|
|
12
|
+
|
|
13
|
+
def debug_cache_keys():
|
|
14
|
+
"""Debug what cache keys are generated"""
|
|
15
|
+
print("=== Debugging cache key generation ===")
|
|
16
|
+
|
|
17
|
+
# Clear cache
|
|
18
|
+
_GLOBAL_MEMORY_CACHE.clear()
|
|
19
|
+
|
|
20
|
+
# First function
|
|
21
|
+
@imemoize
|
|
22
|
+
def test_func(x):
|
|
23
|
+
return x * x
|
|
24
|
+
|
|
25
|
+
# Get cache components
|
|
26
|
+
func_source1, sub_dir1, key_id1 = _compute_cache_components(
|
|
27
|
+
test_func, (5,), {}, True, None, None
|
|
28
|
+
)
|
|
29
|
+
cache_key1 = identify((func_source1, sub_dir1, key_id1))
|
|
30
|
+
|
|
31
|
+
print(f"Function 1 source length: {len(func_source1)}")
|
|
32
|
+
print(f"Function 1 source: {func_source1[:100]}...")
|
|
33
|
+
print(f"Function 1 cache key: {cache_key1}")
|
|
34
|
+
|
|
35
|
+
# Call function
|
|
36
|
+
result1 = test_func(5)
|
|
37
|
+
print(f"After call 1, cache size: {len(_GLOBAL_MEMORY_CACHE)}")
|
|
38
|
+
|
|
39
|
+
# Redefine same function
|
|
40
|
+
@imemoize
|
|
41
|
+
def test_func(x):
|
|
42
|
+
return x * x
|
|
43
|
+
|
|
44
|
+
# Get cache components for redefined function
|
|
45
|
+
func_source2, sub_dir2, key_id2 = _compute_cache_components(
|
|
46
|
+
test_func, (5,), {}, True, None, None
|
|
47
|
+
)
|
|
48
|
+
cache_key2 = identify((func_source2, sub_dir2, key_id2))
|
|
49
|
+
|
|
50
|
+
print(f"\nFunction 2 source length: {len(func_source2)}")
|
|
51
|
+
print(f"Function 2 source: {func_source2[:100]}...")
|
|
52
|
+
print(f"Function 2 cache key: {cache_key2}")
|
|
53
|
+
|
|
54
|
+
print(f"\nSource code same: {func_source1 == func_source2}")
|
|
55
|
+
print(f"Cache keys same: {cache_key1 == cache_key2}")
|
|
56
|
+
|
|
57
|
+
# Call function again
|
|
58
|
+
result2 = test_func(5)
|
|
59
|
+
print(f"After call 2, cache size: {len(_GLOBAL_MEMORY_CACHE)}")
|
|
60
|
+
|
|
61
|
+
print(f"\nCache contents:")
|
|
62
|
+
for i, (key, value) in enumerate(_GLOBAL_MEMORY_CACHE.items()):
|
|
63
|
+
print(f" {i+1}. {key[:50]}... -> {value}")
|
|
64
|
+
|
|
65
|
+
if __name__ == "__main__":
|
|
66
|
+
debug_cache_keys()
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Debug the imemoize execution flow
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
import time
|
|
8
|
+
import os.path as osp
|
|
9
|
+
sys.path.insert(0, osp.join(osp.dirname(__file__), 'src'))
|
|
10
|
+
|
|
11
|
+
from speedy_utils.common.utils_cache import _GLOBAL_MEMORY_CACHE, identify, _compute_cache_components
|
|
12
|
+
from speedy_utils.common.utils_cache import mem_lock
|
|
13
|
+
|
|
14
|
+
def create_debug_imemoize():
|
|
15
|
+
"""Create a debug version of imemoize with detailed logging"""
|
|
16
|
+
import functools
|
|
17
|
+
import inspect
|
|
18
|
+
|
|
19
|
+
def debug_imemoize(func):
|
|
20
|
+
print(f"Creating imemoize wrapper for {func.__name__}")
|
|
21
|
+
|
|
22
|
+
@functools.wraps(func)
|
|
23
|
+
def wrapper(*args, **kwargs):
|
|
24
|
+
print(f"\n--- Calling {func.__name__}({args}, {kwargs}) ---")
|
|
25
|
+
|
|
26
|
+
# Compute cache key
|
|
27
|
+
func_source, sub_dir, key_id = _compute_cache_components(
|
|
28
|
+
func, args, kwargs, True, None, None
|
|
29
|
+
)
|
|
30
|
+
cache_key = identify((func_source, sub_dir, key_id))
|
|
31
|
+
|
|
32
|
+
print(f"Function source: {func_source[:50]}...")
|
|
33
|
+
print(f"Cache key: {cache_key}")
|
|
34
|
+
print(f"Cache size before lookup: {len(_GLOBAL_MEMORY_CACHE)}")
|
|
35
|
+
|
|
36
|
+
# Check cache
|
|
37
|
+
with mem_lock:
|
|
38
|
+
if cache_key in _GLOBAL_MEMORY_CACHE:
|
|
39
|
+
cached_result = _GLOBAL_MEMORY_CACHE[cache_key]
|
|
40
|
+
print(f"CACHE HIT: Found {cached_result}")
|
|
41
|
+
return cached_result
|
|
42
|
+
else:
|
|
43
|
+
print(f"CACHE MISS: Key not found")
|
|
44
|
+
print(f"Available keys:")
|
|
45
|
+
for i, key in enumerate(_GLOBAL_MEMORY_CACHE.keys()):
|
|
46
|
+
print(f" {i+1}. {key}")
|
|
47
|
+
|
|
48
|
+
# Compute result
|
|
49
|
+
print(f"Computing result...")
|
|
50
|
+
start = time.time()
|
|
51
|
+
result = func(*args, **kwargs)
|
|
52
|
+
end = time.time()
|
|
53
|
+
print(f"Computation took {end - start:.6f}s")
|
|
54
|
+
|
|
55
|
+
# Store in cache
|
|
56
|
+
with mem_lock:
|
|
57
|
+
_GLOBAL_MEMORY_CACHE[cache_key] = result
|
|
58
|
+
print(f"Stored result in cache. Cache size now: {len(_GLOBAL_MEMORY_CACHE)}")
|
|
59
|
+
|
|
60
|
+
return result
|
|
61
|
+
|
|
62
|
+
return wrapper
|
|
63
|
+
|
|
64
|
+
return debug_imemoize
|
|
65
|
+
|
|
66
|
+
def test_debug():
|
|
67
|
+
debug_imemoize = create_debug_imemoize()
|
|
68
|
+
|
|
69
|
+
# Clear cache
|
|
70
|
+
_GLOBAL_MEMORY_CACHE.clear()
|
|
71
|
+
print("Cache cleared")
|
|
72
|
+
|
|
73
|
+
# First function
|
|
74
|
+
@debug_imemoize
|
|
75
|
+
def test_func(x):
|
|
76
|
+
time.sleep(0.05)
|
|
77
|
+
return x * x
|
|
78
|
+
|
|
79
|
+
print("\n=== FIRST CALL ===")
|
|
80
|
+
result1 = test_func(5)
|
|
81
|
+
|
|
82
|
+
print("\n=== SECOND CALL (same function object) ===")
|
|
83
|
+
result2 = test_func(5)
|
|
84
|
+
|
|
85
|
+
# Redefine function
|
|
86
|
+
@debug_imemoize
|
|
87
|
+
def test_func(x):
|
|
88
|
+
time.sleep(0.05)
|
|
89
|
+
return x * x
|
|
90
|
+
|
|
91
|
+
print("\n=== THIRD CALL (new function object, same source) ===")
|
|
92
|
+
result3 = test_func(5)
|
|
93
|
+
|
|
94
|
+
if __name__ == "__main__":
|
|
95
|
+
test_debug()
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Simple test for imemoize without heavy dependencies
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
import time
|
|
8
|
+
import os.path as osp
|
|
9
|
+
sys.path.insert(0, osp.join(osp.dirname(__file__), 'src'))
|
|
10
|
+
|
|
11
|
+
# Test basic imports first
|
|
12
|
+
print("Testing imports...")
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
from speedy_utils.common.utils_cache import imemoize, identify, get_source
|
|
16
|
+
print("✓ Successfully imported imemoize from utils_cache")
|
|
17
|
+
except Exception as e:
|
|
18
|
+
print(f"✗ Failed to import: {e}")
|
|
19
|
+
sys.exit(1)
|
|
20
|
+
|
|
21
|
+
@imemoize
|
|
22
|
+
def simple_computation(x):
|
|
23
|
+
"""Simple computation for testing"""
|
|
24
|
+
print(f"Computing simple_computation({x})")
|
|
25
|
+
time.sleep(0.05) # Small delay to verify caching
|
|
26
|
+
return x * x
|
|
27
|
+
|
|
28
|
+
def test_basic():
|
|
29
|
+
print("\n=== Testing basic imemoize functionality ===")
|
|
30
|
+
|
|
31
|
+
# Test first call
|
|
32
|
+
print("First call:")
|
|
33
|
+
start = time.time()
|
|
34
|
+
result1 = simple_computation(5)
|
|
35
|
+
end = time.time()
|
|
36
|
+
print(f"Result: {result1}, Time: {end - start:.3f}s")
|
|
37
|
+
|
|
38
|
+
# Test second call (should be cached)
|
|
39
|
+
print("Second call (should be cached):")
|
|
40
|
+
start = time.time()
|
|
41
|
+
result2 = simple_computation(5)
|
|
42
|
+
end = time.time()
|
|
43
|
+
print(f"Result: {result2}, Time: {end - start:.3f}s")
|
|
44
|
+
|
|
45
|
+
assert result1 == result2 == 25
|
|
46
|
+
print("✓ Basic caching works!")
|
|
47
|
+
|
|
48
|
+
def test_function_source_hashing():
|
|
49
|
+
print("\n=== Testing function source code detection ===")
|
|
50
|
+
|
|
51
|
+
# Test that get_source works
|
|
52
|
+
source = get_source(simple_computation)
|
|
53
|
+
print(f"Function source hash length: {len(source)}")
|
|
54
|
+
|
|
55
|
+
# Test identity
|
|
56
|
+
identity = identify((source, (5,), {}))
|
|
57
|
+
print(f"Cache key identity: {identity[:50]}...")
|
|
58
|
+
|
|
59
|
+
print("✓ Function source detection works!")
|
|
60
|
+
|
|
61
|
+
if __name__ == "__main__":
|
|
62
|
+
test_basic()
|
|
63
|
+
test_function_source_hashing()
|
|
64
|
+
print("\n🎉 Simple tests passed!")
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
# • timef(func) -> Callable - Function execution time decorator
|
|
17
17
|
# • retry_runtime(sleep_seconds: int, max_retry: int, exceptions) -> Callable
|
|
18
18
|
# • memoize(func) -> Callable - Function result caching decorator
|
|
19
|
+
# • imemoize(func) -> Callable - In-memory caching decorator (global persistent)
|
|
19
20
|
# • identify(obj: Any) -> str - Generate unique object identifier
|
|
20
21
|
# • identify_uuid(obj: Any) -> str - Generate UUID-based object identifier
|
|
21
22
|
# • load_by_ext(fname: Union[str, list[str]]) -> Any - Auto-detect file format loader
|
|
@@ -125,7 +126,7 @@ from .common.notebook_utils import (
|
|
|
125
126
|
)
|
|
126
127
|
|
|
127
128
|
# Cache utilities
|
|
128
|
-
from .common.utils_cache import identify, identify_uuid, memoize
|
|
129
|
+
from .common.utils_cache import identify, identify_uuid, imemoize, memoize
|
|
129
130
|
|
|
130
131
|
# IO utilities
|
|
131
132
|
from .common.utils_io import (
|
|
@@ -224,6 +225,7 @@ __all__ = [
|
|
|
224
225
|
"retry_runtime",
|
|
225
226
|
# Cache utilities
|
|
226
227
|
"memoize",
|
|
228
|
+
"imemoize",
|
|
227
229
|
"identify",
|
|
228
230
|
"identify_uuid",
|
|
229
231
|
# IO utilities
|
|
@@ -44,6 +44,9 @@ _MEM_CACHES: "weakref.WeakKeyDictionary[Callable[..., Any], cachetools.LRUCache]
|
|
|
44
44
|
weakref.WeakKeyDictionary()
|
|
45
45
|
)
|
|
46
46
|
|
|
47
|
+
# Global memory cache for imemoize (persists across IPython reloads)
|
|
48
|
+
_GLOBAL_MEMORY_CACHE: dict[str, Any] = {}
|
|
49
|
+
|
|
47
50
|
# Backward-compat global symbol (internal only; not exported)
|
|
48
51
|
LRU_MEM_CACHE = cachetools.LRUCache(maxsize=256)
|
|
49
52
|
|
|
@@ -680,4 +683,142 @@ def memoize(
|
|
|
680
683
|
return decorator(_func)
|
|
681
684
|
|
|
682
685
|
|
|
683
|
-
|
|
686
|
+
# --------------------------------------------------------------------------------------
|
|
687
|
+
# In-memory memoize with global persistent cache
|
|
688
|
+
# --------------------------------------------------------------------------------------
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
@overload
|
|
692
|
+
def imemoize(
|
|
693
|
+
_func: Callable[P, R],
|
|
694
|
+
*,
|
|
695
|
+
keys: Optional[list[str]] = ...,
|
|
696
|
+
key: Optional[Callable[..., Any]] = ...,
|
|
697
|
+
ignore_self: bool = ...,
|
|
698
|
+
) -> Callable[P, R]: ...
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
@overload
|
|
702
|
+
def imemoize(
|
|
703
|
+
_func: Callable[P, Awaitable[R]],
|
|
704
|
+
*,
|
|
705
|
+
keys: Optional[list[str]] = ...,
|
|
706
|
+
key: Optional[Callable[..., Any]] = ...,
|
|
707
|
+
ignore_self: bool = ...,
|
|
708
|
+
) -> Callable[P, Awaitable[R]]: ...
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
@overload
|
|
712
|
+
def imemoize(
|
|
713
|
+
_func: None = ...,
|
|
714
|
+
*,
|
|
715
|
+
keys: Optional[list[str]] = ...,
|
|
716
|
+
key: Optional[Callable[..., Any]] = ...,
|
|
717
|
+
ignore_self: bool = ...,
|
|
718
|
+
) -> Callable[[Callable[P, R]], Callable[P, R]]: ...
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
@overload
|
|
722
|
+
def imemoize( # type: ignore
|
|
723
|
+
_func: None = ...,
|
|
724
|
+
*,
|
|
725
|
+
keys: Optional[list[str]] = ...,
|
|
726
|
+
key: Optional[Callable[..., Any]] = ...,
|
|
727
|
+
ignore_self: bool = ...,
|
|
728
|
+
) -> Callable[[Callable[P, Awaitable[R]]], Callable[P, Awaitable[R]]]: ...
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
def imemoize(
|
|
732
|
+
_func: Optional[Callable[P, Any]] = None,
|
|
733
|
+
*,
|
|
734
|
+
keys: Optional[list[str]] = None,
|
|
735
|
+
key: Optional[Callable[..., Any]] = None,
|
|
736
|
+
ignore_self: bool = True,
|
|
737
|
+
):
|
|
738
|
+
"""
|
|
739
|
+
In-memory memoization decorator with global persistent cache.
|
|
740
|
+
|
|
741
|
+
Unlike regular memoize, this uses a global memory cache that persists
|
|
742
|
+
across IPython %load executions. The cache key is based on the function's
|
|
743
|
+
source code combined with runtime arguments, making it suitable for
|
|
744
|
+
notebook environments where functions may be reloaded.
|
|
745
|
+
|
|
746
|
+
Args:
|
|
747
|
+
keys: list of argument names to include in key (optional).
|
|
748
|
+
key: custom callable (*args, **kwargs) -> hashable for keying (optional).
|
|
749
|
+
ignore_self: ignore 'self' when building cache key for bound methods.
|
|
750
|
+
|
|
751
|
+
Example:
|
|
752
|
+
@imemoize
|
|
753
|
+
def expensive_computation(x):
|
|
754
|
+
import time
|
|
755
|
+
time.sleep(2)
|
|
756
|
+
return x * x
|
|
757
|
+
|
|
758
|
+
# First call computes and caches
|
|
759
|
+
result1 = expensive_computation(5)
|
|
760
|
+
|
|
761
|
+
# Second call retrieves from memory cache
|
|
762
|
+
result2 = expensive_computation(5)
|
|
763
|
+
|
|
764
|
+
# Even after %load file.py in IPython, the cache persists
|
|
765
|
+
"""
|
|
766
|
+
|
|
767
|
+
def decorator(func: Callable[P, Any]) -> Callable[P, Any]:
|
|
768
|
+
is_async = inspect.iscoroutinefunction(func)
|
|
769
|
+
|
|
770
|
+
if is_async:
|
|
771
|
+
@functools.wraps(func)
|
|
772
|
+
async def async_wrapper(*args: P.args, **kwargs: P.kwargs) -> Any:
|
|
773
|
+
# Compute cache key based on function source + args
|
|
774
|
+
func_source, sub_dir, key_id = _compute_cache_components(
|
|
775
|
+
func, args, kwargs, ignore_self, keys, key
|
|
776
|
+
)
|
|
777
|
+
cache_key = identify((func_source, sub_dir, key_id))
|
|
778
|
+
|
|
779
|
+
# Check global memory cache
|
|
780
|
+
with mem_lock:
|
|
781
|
+
if cache_key in _GLOBAL_MEMORY_CACHE:
|
|
782
|
+
return _GLOBAL_MEMORY_CACHE[cache_key]
|
|
783
|
+
|
|
784
|
+
# Compute result and store in cache
|
|
785
|
+
result = await func(*args, **kwargs)
|
|
786
|
+
|
|
787
|
+
with mem_lock:
|
|
788
|
+
_GLOBAL_MEMORY_CACHE[cache_key] = result
|
|
789
|
+
|
|
790
|
+
return result
|
|
791
|
+
|
|
792
|
+
return async_wrapper
|
|
793
|
+
else:
|
|
794
|
+
@functools.wraps(func)
|
|
795
|
+
def sync_wrapper(*args: P.args, **kwargs: P.kwargs) -> Any:
|
|
796
|
+
# Compute cache key based on function source + args
|
|
797
|
+
func_source, sub_dir, key_id = _compute_cache_components(
|
|
798
|
+
func, args, kwargs, ignore_self, keys, key
|
|
799
|
+
)
|
|
800
|
+
cache_key = identify((func_source, sub_dir, key_id))
|
|
801
|
+
|
|
802
|
+
# Check global memory cache
|
|
803
|
+
with mem_lock:
|
|
804
|
+
if cache_key in _GLOBAL_MEMORY_CACHE:
|
|
805
|
+
return _GLOBAL_MEMORY_CACHE[cache_key]
|
|
806
|
+
|
|
807
|
+
# Compute result and store in cache
|
|
808
|
+
result = func(*args, **kwargs)
|
|
809
|
+
|
|
810
|
+
with mem_lock:
|
|
811
|
+
_GLOBAL_MEMORY_CACHE[cache_key] = result
|
|
812
|
+
|
|
813
|
+
return result
|
|
814
|
+
|
|
815
|
+
return sync_wrapper
|
|
816
|
+
|
|
817
|
+
# Support both @imemoize and @imemoize(...)
|
|
818
|
+
if _func is None:
|
|
819
|
+
return decorator
|
|
820
|
+
else:
|
|
821
|
+
return decorator(_func)
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
__all__ = ["memoize", "imemoize", "identify"]
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Test script for imemoize functionality
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
import time
|
|
8
|
+
sys.path.insert(0, 'src')
|
|
9
|
+
|
|
10
|
+
from speedy_utils import imemoize
|
|
11
|
+
|
|
12
|
+
@imemoize
|
|
13
|
+
def expensive_computation(x, y=1):
|
|
14
|
+
"""Simulate an expensive computation"""
|
|
15
|
+
print(f"Computing expensive_computation({x}, {y})")
|
|
16
|
+
time.sleep(0.1) # Simulate work
|
|
17
|
+
return x * x + y
|
|
18
|
+
|
|
19
|
+
@imemoize
|
|
20
|
+
async def async_expensive_computation(x):
|
|
21
|
+
"""Simulate an expensive async computation"""
|
|
22
|
+
import asyncio
|
|
23
|
+
print(f"Computing async_expensive_computation({x})")
|
|
24
|
+
await asyncio.sleep(0.1) # Simulate work
|
|
25
|
+
return x * x
|
|
26
|
+
|
|
27
|
+
def test_basic_functionality():
|
|
28
|
+
print("=== Testing basic functionality ===")
|
|
29
|
+
|
|
30
|
+
# Test sync function
|
|
31
|
+
print("First call:")
|
|
32
|
+
start = time.time()
|
|
33
|
+
result1 = expensive_computation(5)
|
|
34
|
+
end = time.time()
|
|
35
|
+
print(f"Result: {result1}, Time: {end - start:.3f}s")
|
|
36
|
+
|
|
37
|
+
print("Second call (should be cached):")
|
|
38
|
+
start = time.time()
|
|
39
|
+
result2 = expensive_computation(5)
|
|
40
|
+
end = time.time()
|
|
41
|
+
print(f"Result: {result2}, Time: {end - start:.3f}s")
|
|
42
|
+
|
|
43
|
+
assert result1 == result2
|
|
44
|
+
print("✓ Basic sync caching works")
|
|
45
|
+
|
|
46
|
+
def test_different_arguments():
|
|
47
|
+
print("\n=== Testing different arguments ===")
|
|
48
|
+
|
|
49
|
+
# Different arguments should compute separately
|
|
50
|
+
result1 = expensive_computation(3)
|
|
51
|
+
result2 = expensive_computation(4)
|
|
52
|
+
result3 = expensive_computation(3, y=2) # Different y value
|
|
53
|
+
result4 = expensive_computation(3) # Should be cached
|
|
54
|
+
|
|
55
|
+
print(f"expensive_computation(3) = {result1}")
|
|
56
|
+
print(f"expensive_computation(4) = {result2}")
|
|
57
|
+
print(f"expensive_computation(3, y=2) = {result3}")
|
|
58
|
+
print(f"expensive_computation(3) again = {result4}")
|
|
59
|
+
|
|
60
|
+
assert result1 != result2
|
|
61
|
+
assert result1 != result3
|
|
62
|
+
assert result1 == result4
|
|
63
|
+
print("✓ Different arguments work correctly")
|
|
64
|
+
|
|
65
|
+
async def test_async_functionality():
|
|
66
|
+
print("\n=== Testing async functionality ===")
|
|
67
|
+
|
|
68
|
+
start = time.time()
|
|
69
|
+
result1 = await async_expensive_computation(7)
|
|
70
|
+
end = time.time()
|
|
71
|
+
print(f"First async call: {result1}, Time: {end - start:.3f}s")
|
|
72
|
+
|
|
73
|
+
start = time.time()
|
|
74
|
+
result2 = await async_expensive_computation(7)
|
|
75
|
+
end = time.time()
|
|
76
|
+
print(f"Second async call (cached): {result2}, Time: {end - start:.3f}s")
|
|
77
|
+
|
|
78
|
+
assert result1 == result2
|
|
79
|
+
print("✓ Async caching works")
|
|
80
|
+
|
|
81
|
+
def test_code_change_detection():
|
|
82
|
+
print("\n=== Testing code change detection ===")
|
|
83
|
+
|
|
84
|
+
# Create a function dynamically to simulate code changes
|
|
85
|
+
def create_function(multiplier):
|
|
86
|
+
@imemoize
|
|
87
|
+
def dynamic_func(x):
|
|
88
|
+
print(f"Computing with multiplier {multiplier}")
|
|
89
|
+
return x * multiplier
|
|
90
|
+
return dynamic_func
|
|
91
|
+
|
|
92
|
+
func1 = create_function(2)
|
|
93
|
+
func2 = create_function(3)
|
|
94
|
+
|
|
95
|
+
result1a = func1(5) # Should compute
|
|
96
|
+
result1b = func1(5) # Should be cached
|
|
97
|
+
result2a = func2(5) # Different function, should compute
|
|
98
|
+
|
|
99
|
+
print(f"func1(5) first: {result1a}")
|
|
100
|
+
print(f"func1(5) second: {result1b}")
|
|
101
|
+
print(f"func2(5): {result2a}")
|
|
102
|
+
|
|
103
|
+
assert result1a == result1b == 10
|
|
104
|
+
assert result2a == 15
|
|
105
|
+
print("✓ Code change detection works")
|
|
106
|
+
|
|
107
|
+
if __name__ == "__main__":
|
|
108
|
+
test_basic_functionality()
|
|
109
|
+
test_different_arguments()
|
|
110
|
+
|
|
111
|
+
import asyncio
|
|
112
|
+
asyncio.run(test_async_functionality())
|
|
113
|
+
|
|
114
|
+
test_code_change_detection()
|
|
115
|
+
|
|
116
|
+
print("\n🎉 All tests passed!")
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Test imemoize persistence across function redefinition (simulating IPython reload)
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
import time
|
|
8
|
+
import os.path as osp
|
|
9
|
+
sys.path.insert(0, osp.join(osp.dirname(__file__), 'src'))
|
|
10
|
+
|
|
11
|
+
from speedy_utils.common.utils_cache import imemoize, _GLOBAL_MEMORY_CACHE
|
|
12
|
+
|
|
13
|
+
def test_ipython_reload_simulation():
|
|
14
|
+
"""
|
|
15
|
+
Test that imemoize persists across function redefinition,
|
|
16
|
+
simulating what happens in IPython when %load is used
|
|
17
|
+
"""
|
|
18
|
+
print("=== Testing IPython reload simulation ===")
|
|
19
|
+
|
|
20
|
+
# Clear any existing cache
|
|
21
|
+
_GLOBAL_MEMORY_CACHE.clear()
|
|
22
|
+
|
|
23
|
+
# First definition of the function
|
|
24
|
+
@imemoize
|
|
25
|
+
def computation_v1(x):
|
|
26
|
+
print(f"Computing v1({x})")
|
|
27
|
+
time.sleep(0.05)
|
|
28
|
+
return x * x
|
|
29
|
+
|
|
30
|
+
# Call it first time
|
|
31
|
+
print("1. First call to v1:")
|
|
32
|
+
result1 = computation_v1(10)
|
|
33
|
+
print(f" Result: {result1}")
|
|
34
|
+
|
|
35
|
+
# Call it second time (should be cached)
|
|
36
|
+
print("2. Second call to v1 (should be cached):")
|
|
37
|
+
start = time.time()
|
|
38
|
+
result2 = computation_v1(10)
|
|
39
|
+
end = time.time()
|
|
40
|
+
print(f" Result: {result2}, Time: {end - start:.6f}s")
|
|
41
|
+
|
|
42
|
+
assert result1 == result2
|
|
43
|
+
assert end - start < 0.01 # Should be very fast (cached)
|
|
44
|
+
|
|
45
|
+
# Check cache has content
|
|
46
|
+
print(f"3. Cache now contains {len(_GLOBAL_MEMORY_CACHE)} entries")
|
|
47
|
+
|
|
48
|
+
# Now simulate IPython %load - redefine the SAME function
|
|
49
|
+
@imemoize
|
|
50
|
+
def computation_v1(x): # Same name, same logic
|
|
51
|
+
print(f"Computing v1({x})")
|
|
52
|
+
time.sleep(0.05)
|
|
53
|
+
return x * x
|
|
54
|
+
|
|
55
|
+
# This should still be cached because source code is the same
|
|
56
|
+
print("4. Call after 'reload' with same code (should be cached):")
|
|
57
|
+
start = time.time()
|
|
58
|
+
result3 = computation_v1(10)
|
|
59
|
+
end = time.time()
|
|
60
|
+
print(f" Result: {result3}, Time: {end - start:.6f}s")
|
|
61
|
+
|
|
62
|
+
assert result3 == result1
|
|
63
|
+
assert end - start < 0.01 # Should still be fast (cached)
|
|
64
|
+
|
|
65
|
+
# Now simulate a code change
|
|
66
|
+
@imemoize
|
|
67
|
+
def computation_v1(x): # Same name, different logic
|
|
68
|
+
print(f"Computing v1 modified({x})")
|
|
69
|
+
time.sleep(0.05)
|
|
70
|
+
return x * x * 2 # Different computation!
|
|
71
|
+
|
|
72
|
+
# This should NOT be cached because source code changed
|
|
73
|
+
print("5. Call after code change (should NOT be cached):")
|
|
74
|
+
start = time.time()
|
|
75
|
+
result4 = computation_v1(10)
|
|
76
|
+
end = time.time()
|
|
77
|
+
print(f" Result: {result4}, Time: {end - start:.3f}s")
|
|
78
|
+
|
|
79
|
+
assert result4 == 200 # New computation
|
|
80
|
+
assert result4 != result1 # Different from before
|
|
81
|
+
assert end - start > 0.04 # Should be slow (not cached)
|
|
82
|
+
|
|
83
|
+
# Second call to modified function should now be cached
|
|
84
|
+
print("6. Second call to modified function (should be cached):")
|
|
85
|
+
start = time.time()
|
|
86
|
+
result5 = computation_v1(10)
|
|
87
|
+
end = time.time()
|
|
88
|
+
print(f" Result: {result5}, Time: {end - start:.6f}s")
|
|
89
|
+
|
|
90
|
+
assert result5 == result4
|
|
91
|
+
assert end - start < 0.01 # Should be fast (cached)
|
|
92
|
+
|
|
93
|
+
print("✓ IPython reload simulation works correctly!")
|
|
94
|
+
|
|
95
|
+
def test_cross_function_caching():
|
|
96
|
+
"""Test that different functions have separate cache entries"""
|
|
97
|
+
print("\n=== Testing cross-function caching ===")
|
|
98
|
+
|
|
99
|
+
@imemoize
|
|
100
|
+
def func_a(x):
|
|
101
|
+
print(f"Computing func_a({x})")
|
|
102
|
+
return x + 1
|
|
103
|
+
|
|
104
|
+
@imemoize
|
|
105
|
+
def func_b(x):
|
|
106
|
+
print(f"Computing func_b({x})")
|
|
107
|
+
return x + 1 # Same logic, different function
|
|
108
|
+
|
|
109
|
+
# Both should compute independently
|
|
110
|
+
result_a = func_a(5)
|
|
111
|
+
result_b = func_b(5)
|
|
112
|
+
|
|
113
|
+
print(f"func_a(5) = {result_a}")
|
|
114
|
+
print(f"func_b(5) = {result_b}")
|
|
115
|
+
|
|
116
|
+
assert result_a == result_b == 6
|
|
117
|
+
print("✓ Different functions cache independently!")
|
|
118
|
+
|
|
119
|
+
if __name__ == "__main__":
|
|
120
|
+
test_ipython_reload_simulation()
|
|
121
|
+
test_cross_function_caching()
|
|
122
|
+
print("\n🎉 All persistence tests passed!")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{speedy_utils-1.1.24 → speedy_utils-1.1.25}/src/speedy_utils/scripts/openapi_client_codegen.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|