vectorwave 0.2.3__tar.gz → 0.2.4__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.
- {vectorwave-0.2.3/src/vectorwave.egg-info → vectorwave-0.2.4}/PKG-INFO +35 -1
- {vectorwave-0.2.3 → vectorwave-0.2.4}/Readme.md +34 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/pyproject.toml +1 -1
- vectorwave-0.2.4/src/tests/core/test_auto_injector.py +228 -0
- vectorwave-0.2.4/src/tests/core/test_auto_injector_real.py +111 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/__init__.py +3 -2
- vectorwave-0.2.4/src/vectorwave/core/auto_injector.py +100 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/core/decorator.py +2 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4/src/vectorwave.egg-info}/PKG-INFO +35 -1
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave.egg-info/SOURCES.txt +3 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/LICENSE +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/MANIFEST.in +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/NOTICE +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/setup.cfg +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/__init__.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/batch/__init__.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/batch/test_batch.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/conftest.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/core/__init__.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/core/llm/__init__.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/core/llm/test_token_usage.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/core/test_decorator.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/core/test_semantic_caching.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/database/__init__.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/database/test_archiver.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/database/test_dataset.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/database/test_db.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/database/test_db_search.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/exception/__init__.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/models/__init__.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/models/test_db_config.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/monitoring/__init__.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/monitoring/alert/__init__.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/monitoring/alert/test_alerter.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/monitoring/test_async_trace.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/monitoring/test_tracer.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/prediction/__init__.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/search/__init__.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/search/test_execution_search.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/utils/__init__.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/utils/test_function_cahe.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/utils/test_replay_source.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/utils/test_replayer.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/utils/test_return_caching.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/utils/test_status.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/vectorizer/__init__.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/batch/__init__.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/batch/batch.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/core/__init__.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/core/core.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/core/generator.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/core/llm/__init__.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/core/llm/base.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/core/llm/factory.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/core/llm/openai_client.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/database/__init__.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/database/archiver.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/database/dataset.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/database/db.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/database/db_search.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/exception/__init__.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/exception/exceptions.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/models/__init__.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/models/db_config.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/monitoring/__init__.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/monitoring/alert/__init__.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/monitoring/alert/base.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/monitoring/alert/factory.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/monitoring/alert/null_alerter.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/monitoring/alert/webhook_alerter.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/monitoring/monitoring.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/monitoring/tracer.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/prediction/__init__.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/prediction/predictor.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/search/__init__.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/search/execution_search.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/search/extended_search.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/search/rag_search.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/utils/__init__.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/utils/context.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/utils/function_cache.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/utils/healer.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/utils/replayer.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/utils/replayer_semantic.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/utils/return_caching_utils.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/utils/status.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/vectorizer/__init__.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/vectorizer/base.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/vectorizer/factory.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/vectorizer/huggingface_vectorizer.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/vectorizer/openai_vectorizer.py +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave.egg-info/dependency_links.txt +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave.egg-info/requires.txt +0 -0
- {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vectorwave
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: VectorWave: Seamless Auto-Vectorization Framework
|
|
5
5
|
Author-email: junyeonggim <junyeonggim5@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -618,6 +618,40 @@ archiver.export_and_clear(
|
|
|
618
618
|
{"messages": [{"role": "user", "content": "{\"price\": 100, \"tax\": 0.1}"}, {"role": "assistant", "content": "110.0"}]}
|
|
619
619
|
```
|
|
620
620
|
|
|
621
|
+
## 🌊 Auto-Injection (Zero-Code Change Integration)
|
|
622
|
+
|
|
623
|
+
You don't need to modify your business logic to use VectorWave. Use `VectorWaveAutoInjector` to inject functionality externally.
|
|
624
|
+
|
|
625
|
+
### How to use
|
|
626
|
+
|
|
627
|
+
1. **Configure Global Settings**: Set default values like `team`, `priority`, or `auto` (pending mode).
|
|
628
|
+
2. **Inject Modules**: Specify the target module path string.
|
|
629
|
+
|
|
630
|
+
```python
|
|
631
|
+
from vectorwave import initialize_database, VectorWaveAutoInjector, generate_and_register_metadata
|
|
632
|
+
|
|
633
|
+
# 1. Initialize DB
|
|
634
|
+
initialize_database()
|
|
635
|
+
|
|
636
|
+
# 2. Configure AutoInjector (Global Settings)
|
|
637
|
+
VectorWaveAutoInjector.configure(
|
|
638
|
+
team="billing-team",
|
|
639
|
+
priority=1,
|
|
640
|
+
auto=True # True: Collect metadata in memory (Pending), False: Save immediately to DB
|
|
641
|
+
)
|
|
642
|
+
|
|
643
|
+
# 3. Inject VectorWave into your module
|
|
644
|
+
# (No need to add @vectorize decorators in 'my_service.payment' code!)
|
|
645
|
+
VectorWaveAutoInjector.inject("my_service.payment")
|
|
646
|
+
|
|
647
|
+
# 4. Register Metadata (If auto=True)
|
|
648
|
+
generate_and_register_metadata()
|
|
649
|
+
|
|
650
|
+
# 5. Run your business logic
|
|
651
|
+
import my_service.payment
|
|
652
|
+
my_service.payment.process_transaction()
|
|
653
|
+
```
|
|
654
|
+
|
|
621
655
|
## 🤝 Contributing
|
|
622
656
|
|
|
623
657
|
We welcome all forms of contributions, including bug reports, feature requests, and code contributions. Please refer to [CONTRIBUTING.md](https://www.google.com/search?q=httpsS://www.google.com/search%3Fq%3DCONTRIBUTING.md) for details.
|
|
@@ -592,6 +592,40 @@ archiver.export_and_clear(
|
|
|
592
592
|
{"messages": [{"role": "user", "content": "{\"price\": 100, \"tax\": 0.1}"}, {"role": "assistant", "content": "110.0"}]}
|
|
593
593
|
```
|
|
594
594
|
|
|
595
|
+
## 🌊 Auto-Injection (Zero-Code Change Integration)
|
|
596
|
+
|
|
597
|
+
You don't need to modify your business logic to use VectorWave. Use `VectorWaveAutoInjector` to inject functionality externally.
|
|
598
|
+
|
|
599
|
+
### How to use
|
|
600
|
+
|
|
601
|
+
1. **Configure Global Settings**: Set default values like `team`, `priority`, or `auto` (pending mode).
|
|
602
|
+
2. **Inject Modules**: Specify the target module path string.
|
|
603
|
+
|
|
604
|
+
```python
|
|
605
|
+
from vectorwave import initialize_database, VectorWaveAutoInjector, generate_and_register_metadata
|
|
606
|
+
|
|
607
|
+
# 1. Initialize DB
|
|
608
|
+
initialize_database()
|
|
609
|
+
|
|
610
|
+
# 2. Configure AutoInjector (Global Settings)
|
|
611
|
+
VectorWaveAutoInjector.configure(
|
|
612
|
+
team="billing-team",
|
|
613
|
+
priority=1,
|
|
614
|
+
auto=True # True: Collect metadata in memory (Pending), False: Save immediately to DB
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
# 3. Inject VectorWave into your module
|
|
618
|
+
# (No need to add @vectorize decorators in 'my_service.payment' code!)
|
|
619
|
+
VectorWaveAutoInjector.inject("my_service.payment")
|
|
620
|
+
|
|
621
|
+
# 4. Register Metadata (If auto=True)
|
|
622
|
+
generate_and_register_metadata()
|
|
623
|
+
|
|
624
|
+
# 5. Run your business logic
|
|
625
|
+
import my_service.payment
|
|
626
|
+
my_service.payment.process_transaction()
|
|
627
|
+
```
|
|
628
|
+
|
|
595
629
|
## 🤝 Contributing
|
|
596
630
|
|
|
597
631
|
We welcome all forms of contributions, including bug reports, feature requests, and code contributions. Please refer to [CONTRIBUTING.md](https://www.google.com/search?q=httpsS://www.google.com/search%3Fq%3DCONTRIBUTING.md) for details.
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import sys
|
|
3
|
+
import types
|
|
4
|
+
from unittest.mock import MagicMock, ANY
|
|
5
|
+
from vectorwave.core.auto_injector import VectorWaveAutoInjector, create_smart_wrapper
|
|
6
|
+
|
|
7
|
+
# --- Fixtures ---
|
|
8
|
+
|
|
9
|
+
@pytest.fixture
|
|
10
|
+
def mock_target_module():
|
|
11
|
+
"""Dynamically creates a fake module for testing."""
|
|
12
|
+
module_name = "fake_business_logic"
|
|
13
|
+
mod = types.ModuleType(module_name)
|
|
14
|
+
|
|
15
|
+
# Define functions for testing
|
|
16
|
+
def func_a(x): return x + 1
|
|
17
|
+
def func_b(y): return y * 2
|
|
18
|
+
|
|
19
|
+
# Set __module__ attribute so the Injector recognizes them as 'functions of this module'
|
|
20
|
+
func_a.__module__ = module_name
|
|
21
|
+
func_b.__module__ = module_name
|
|
22
|
+
|
|
23
|
+
mod.func_a = func_a
|
|
24
|
+
mod.func_b = func_b
|
|
25
|
+
|
|
26
|
+
# Register to sys.modules
|
|
27
|
+
sys.modules[module_name] = mod
|
|
28
|
+
yield mod
|
|
29
|
+
# Cleanup
|
|
30
|
+
del sys.modules[module_name]
|
|
31
|
+
|
|
32
|
+
@pytest.fixture
|
|
33
|
+
def mock_deps(monkeypatch):
|
|
34
|
+
"""
|
|
35
|
+
Mocks vectorize, trace_span, and contextvar that auto_injector depends on.
|
|
36
|
+
"""
|
|
37
|
+
mock_vectorize = MagicMock()
|
|
38
|
+
mock_trace_span = MagicMock()
|
|
39
|
+
mock_tracer_var = MagicMock()
|
|
40
|
+
|
|
41
|
+
# Mock vectorize: @vectorize(...) -> decorator -> returns root_wrapper
|
|
42
|
+
def vectorize_side_effect(**kwargs):
|
|
43
|
+
def decorator(func):
|
|
44
|
+
# Return a mock acting as root_wrapper (Default behavior: execute original function)
|
|
45
|
+
root_wrapper = MagicMock(side_effect=func)
|
|
46
|
+
return root_wrapper
|
|
47
|
+
return decorator
|
|
48
|
+
|
|
49
|
+
# [Modified] Mock trace_span: Modified to accept positional arguments (*args)
|
|
50
|
+
def trace_span_side_effect(*args, **kwargs):
|
|
51
|
+
# Case 1: Called as trace_span(func, capture_return_value=True)
|
|
52
|
+
if args and callable(args[0]):
|
|
53
|
+
func = args[0]
|
|
54
|
+
def wrapper(*w_args, **w_kwargs):
|
|
55
|
+
return func(*w_args, **w_kwargs)
|
|
56
|
+
return wrapper
|
|
57
|
+
|
|
58
|
+
# Case 2: Called as a decorator factory @trace_span(...)
|
|
59
|
+
def decorator(func):
|
|
60
|
+
def wrapper(*w_args, **w_kwargs):
|
|
61
|
+
return func(*w_args, **w_kwargs)
|
|
62
|
+
return wrapper
|
|
63
|
+
return decorator
|
|
64
|
+
|
|
65
|
+
mock_vectorize.side_effect = vectorize_side_effect
|
|
66
|
+
mock_trace_span.side_effect = trace_span_side_effect
|
|
67
|
+
|
|
68
|
+
# Patch according to the module path
|
|
69
|
+
monkeypatch.setattr("vectorwave.core.auto_injector.vectorize", mock_vectorize)
|
|
70
|
+
monkeypatch.setattr("vectorwave.core.auto_injector.trace_span", mock_trace_span)
|
|
71
|
+
monkeypatch.setattr("vectorwave.core.auto_injector.current_tracer_var", mock_tracer_var)
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
"vectorize": mock_vectorize,
|
|
75
|
+
"trace_span": mock_trace_span,
|
|
76
|
+
"tracer_var": mock_tracer_var
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@pytest.fixture
|
|
80
|
+
def mock_multiple_modules():
|
|
81
|
+
"""Dynamically creates two different fake modules (Payment, Auth)."""
|
|
82
|
+
name_a = "service_payment"
|
|
83
|
+
name_b = "service_auth"
|
|
84
|
+
|
|
85
|
+
mod_a = types.ModuleType(name_a)
|
|
86
|
+
mod_b = types.ModuleType(name_b)
|
|
87
|
+
|
|
88
|
+
def pay(x): return x
|
|
89
|
+
def login(x): return x
|
|
90
|
+
|
|
91
|
+
pay.__module__ = name_a
|
|
92
|
+
login.__module__ = name_b
|
|
93
|
+
|
|
94
|
+
mod_a.process_payment = pay
|
|
95
|
+
mod_b.login_user = login
|
|
96
|
+
|
|
97
|
+
sys.modules[name_a] = mod_a
|
|
98
|
+
sys.modules[name_b] = mod_b
|
|
99
|
+
|
|
100
|
+
yield mod_a, mod_b
|
|
101
|
+
|
|
102
|
+
del sys.modules[name_a]
|
|
103
|
+
del sys.modules[name_b]
|
|
104
|
+
|
|
105
|
+
# --- Test Cases ---
|
|
106
|
+
|
|
107
|
+
def test_inject_calls_vectorize_immediately(mock_target_module, mock_deps):
|
|
108
|
+
"""
|
|
109
|
+
Case 1: Verify that vectorize is called immediately at injection time to register metadata.
|
|
110
|
+
"""
|
|
111
|
+
# Act
|
|
112
|
+
VectorWaveAutoInjector.inject("fake_business_logic", team="test-team")
|
|
113
|
+
|
|
114
|
+
# Assert
|
|
115
|
+
# 1. vectorize should have been called (Immediate registration)
|
|
116
|
+
mock_deps["vectorize"].assert_called()
|
|
117
|
+
|
|
118
|
+
# 2. Verify passed configuration
|
|
119
|
+
call_kwargs = mock_deps["vectorize"].call_args.kwargs
|
|
120
|
+
assert call_kwargs["team"] == "test-team"
|
|
121
|
+
|
|
122
|
+
# 3. Verify the function is wrapped
|
|
123
|
+
assert getattr(mock_target_module.func_a, "_is_vectorized") is True
|
|
124
|
+
|
|
125
|
+
def test_smart_wrapper_root_execution(mock_target_module, mock_deps):
|
|
126
|
+
"""
|
|
127
|
+
Case 2: Verify that the pre-created root_wrapper is executed when there is no tracer (Root).
|
|
128
|
+
"""
|
|
129
|
+
# Arrange
|
|
130
|
+
mock_deps["tracer_var"].get.return_value = None # No parent trace
|
|
131
|
+
|
|
132
|
+
# [Modified] Remove fixture's side_effect so return_value setting takes effect.
|
|
133
|
+
mock_deps["vectorize"].side_effect = None
|
|
134
|
+
|
|
135
|
+
# vectorize(...) -> decorator_mock returns -> decorator_mock(func) -> root_wrapper_mock returns
|
|
136
|
+
decorator_mock = MagicMock()
|
|
137
|
+
root_wrapper_mock = MagicMock(return_value=11)
|
|
138
|
+
|
|
139
|
+
mock_deps["vectorize"].return_value = decorator_mock
|
|
140
|
+
decorator_mock.return_value = root_wrapper_mock
|
|
141
|
+
|
|
142
|
+
VectorWaveAutoInjector.inject("fake_business_logic")
|
|
143
|
+
|
|
144
|
+
# Act
|
|
145
|
+
result = mock_target_module.func_a(10)
|
|
146
|
+
|
|
147
|
+
# Assert
|
|
148
|
+
assert result == 11
|
|
149
|
+
# root_wrapper should have been called
|
|
150
|
+
root_wrapper_mock.assert_called_once()
|
|
151
|
+
# trace_span should not be called (logic is already inside root_wrapper)
|
|
152
|
+
mock_deps["trace_span"].assert_not_called()
|
|
153
|
+
|
|
154
|
+
def test_smart_wrapper_child_execution(mock_target_module, mock_deps):
|
|
155
|
+
"""
|
|
156
|
+
Case 3: Verify that trace_span is dynamically called when there is a tracer (Child).
|
|
157
|
+
"""
|
|
158
|
+
# Arrange
|
|
159
|
+
mock_deps["tracer_var"].get.return_value = MagicMock() # Parent trace exists
|
|
160
|
+
|
|
161
|
+
# [Modified] Remove side_effect and set Mock
|
|
162
|
+
mock_deps["vectorize"].side_effect = None
|
|
163
|
+
|
|
164
|
+
decorator_mock = MagicMock()
|
|
165
|
+
root_wrapper_mock = MagicMock(return_value=999) # Should not be called
|
|
166
|
+
|
|
167
|
+
mock_deps["vectorize"].return_value = decorator_mock
|
|
168
|
+
decorator_mock.return_value = root_wrapper_mock
|
|
169
|
+
|
|
170
|
+
VectorWaveAutoInjector.inject("fake_business_logic")
|
|
171
|
+
|
|
172
|
+
# Act
|
|
173
|
+
# trace_span mock is configured to call the original function (see fixture)
|
|
174
|
+
# func_b: y * 2 -> 5 * 2 = 10
|
|
175
|
+
result = mock_target_module.func_b(5)
|
|
176
|
+
|
|
177
|
+
# Assert
|
|
178
|
+
assert result == 10
|
|
179
|
+
# trace_span should have been called
|
|
180
|
+
mock_deps["trace_span"].assert_called()
|
|
181
|
+
# root_wrapper should not have been called
|
|
182
|
+
root_wrapper_mock.assert_not_called()
|
|
183
|
+
|
|
184
|
+
def test_prevent_double_injection(mock_target_module, mock_deps):
|
|
185
|
+
"""
|
|
186
|
+
Case 4: Verify that calling inject again on an already injected function does not result in double wrapping.
|
|
187
|
+
"""
|
|
188
|
+
# 1. First Injection
|
|
189
|
+
VectorWaveAutoInjector.inject("fake_business_logic")
|
|
190
|
+
first_wrapped_func = mock_target_module.func_a
|
|
191
|
+
call_count_first = mock_deps["vectorize"].call_count
|
|
192
|
+
|
|
193
|
+
# 2. Second Injection
|
|
194
|
+
VectorWaveAutoInjector.inject("fake_business_logic")
|
|
195
|
+
second_wrapped_func = mock_target_module.func_a
|
|
196
|
+
call_count_second = mock_deps["vectorize"].call_count
|
|
197
|
+
|
|
198
|
+
# Assert
|
|
199
|
+
# Object addresses must be identical
|
|
200
|
+
assert first_wrapped_func is second_wrapped_func
|
|
201
|
+
# vectorize call count should not increase (Prevent duplicate registration)
|
|
202
|
+
assert call_count_first == call_count_second
|
|
203
|
+
|
|
204
|
+
def test_inject_using_config_dictionary_pattern(mock_multiple_modules, mock_deps):
|
|
205
|
+
"""
|
|
206
|
+
Case 5: Verify that configurations are correctly applied when injecting multiple modules in a batch.
|
|
207
|
+
"""
|
|
208
|
+
mod_a, mod_b = mock_multiple_modules
|
|
209
|
+
|
|
210
|
+
# Injection
|
|
211
|
+
VectorWaveAutoInjector.configure(team="default") # Global config example
|
|
212
|
+
|
|
213
|
+
VectorWaveAutoInjector.inject("service_payment", priority=1)
|
|
214
|
+
VectorWaveAutoInjector.inject("service_auth", team="security")
|
|
215
|
+
|
|
216
|
+
# Verify: Check arguments passed to vectorize calls
|
|
217
|
+
calls = mock_deps["vectorize"].call_args_list
|
|
218
|
+
# Collect kwargs
|
|
219
|
+
configs = [c.kwargs for c in calls]
|
|
220
|
+
|
|
221
|
+
# service_payment: default team + priority 1
|
|
222
|
+
config_a = next((c for c in configs if c.get("priority") == 1), None)
|
|
223
|
+
assert config_a is not None
|
|
224
|
+
assert config_a["team"] == "default"
|
|
225
|
+
|
|
226
|
+
# service_auth: security team (override)
|
|
227
|
+
config_b = next((c for c in configs if c.get("team") == "security"), None)
|
|
228
|
+
assert config_b is not None
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import sys
|
|
3
|
+
import types
|
|
4
|
+
from unittest.mock import patch, MagicMock
|
|
5
|
+
|
|
6
|
+
# Import actual modules
|
|
7
|
+
from vectorwave import vectorize, VectorWaveAutoInjector
|
|
8
|
+
|
|
9
|
+
# --- Fixtures ---
|
|
10
|
+
|
|
11
|
+
@pytest.fixture
|
|
12
|
+
def mock_db_dependencies():
|
|
13
|
+
"""
|
|
14
|
+
Mocks heavy dependencies to prevent @vectorize decorator from internally calling
|
|
15
|
+
DB or AI models.
|
|
16
|
+
(The wrapping logic itself runs for 'real')
|
|
17
|
+
"""
|
|
18
|
+
with patch("vectorwave.core.decorator.get_batch_manager") as mock_batch, \
|
|
19
|
+
patch("vectorwave.core.decorator.get_vectorizer") as mock_vec, \
|
|
20
|
+
patch("vectorwave.core.decorator.function_cache_manager"), \
|
|
21
|
+
patch("vectorwave.core.decorator.get_weaviate_settings"), \
|
|
22
|
+
patch("vectorwave.core.decorator.generate_uuid5", return_value="mock-uuid"):
|
|
23
|
+
|
|
24
|
+
# Return fake DB settings (prevent errors)
|
|
25
|
+
mock_settings = MagicMock()
|
|
26
|
+
mock_settings.custom_properties = {}
|
|
27
|
+
mock_settings.COLLECTION_NAME = "VectorWaveFunction"
|
|
28
|
+
patch("vectorwave.core.decorator.get_weaviate_settings", return_value=mock_settings).start()
|
|
29
|
+
|
|
30
|
+
yield
|
|
31
|
+
|
|
32
|
+
# --- Real Function Integration Test ---
|
|
33
|
+
|
|
34
|
+
def test_real_function_mixed_usage(mock_db_dependencies):
|
|
35
|
+
"""
|
|
36
|
+
[Integration Test] Verifies that double wrapping is prevented when mixing
|
|
37
|
+
actual @vectorize decorator and AutoInjector using real function objects.
|
|
38
|
+
"""
|
|
39
|
+
# 1. Create a fake module (acts like a real Python module)
|
|
40
|
+
module_name = "my_real_test_module"
|
|
41
|
+
mod = types.ModuleType(module_name)
|
|
42
|
+
|
|
43
|
+
# -------------------------------------------------------
|
|
44
|
+
# (A) Manually vectorized function (Using Real Decorator)
|
|
45
|
+
# -------------------------------------------------------
|
|
46
|
+
@vectorize(team="manual_team", auto=True)
|
|
47
|
+
def manual_func(x):
|
|
48
|
+
return x + 1
|
|
49
|
+
|
|
50
|
+
# -------------------------------------------------------
|
|
51
|
+
# (B) Plain function without any decorator
|
|
52
|
+
# -------------------------------------------------------
|
|
53
|
+
def plain_func(y):
|
|
54
|
+
return y * 2
|
|
55
|
+
|
|
56
|
+
# [Important] Set __module__ so Injector recognizes them as internal functions
|
|
57
|
+
manual_func.__module__ = module_name
|
|
58
|
+
plain_func.__module__ = module_name
|
|
59
|
+
|
|
60
|
+
# Register functions to the module
|
|
61
|
+
mod.manual_func = manual_func
|
|
62
|
+
mod.plain_func = plain_func
|
|
63
|
+
|
|
64
|
+
# Register to sys.modules so importlib can find it
|
|
65
|
+
sys.modules[module_name] = mod
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
# 2. Check state before injection
|
|
69
|
+
print("\n[Before Injection]")
|
|
70
|
+
print(f"manual_func vectorized? {getattr(mod.manual_func, '_is_vectorized', False)}")
|
|
71
|
+
print(f"plain_func vectorized? {getattr(mod.plain_func, '_is_vectorized', False)}")
|
|
72
|
+
|
|
73
|
+
# manual_func should already have the decorator applied
|
|
74
|
+
assert getattr(mod.manual_func, "_is_vectorized", False) is True
|
|
75
|
+
# plain_func should be a pure function
|
|
76
|
+
assert getattr(mod.plain_func, "_is_vectorized", False) is False
|
|
77
|
+
|
|
78
|
+
# Save object ID of manual_func before injection (for comparison)
|
|
79
|
+
original_manual_func_id = id(mod.manual_func)
|
|
80
|
+
|
|
81
|
+
# -------------------------------------------------------
|
|
82
|
+
# 3. Execute AutoInjector (Real Injection)
|
|
83
|
+
# -------------------------------------------------------
|
|
84
|
+
print("\n🌊 Injecting VectorWave...")
|
|
85
|
+
VectorWaveAutoInjector.inject(module_name, team="auto_team")
|
|
86
|
+
|
|
87
|
+
# 4. Verify results
|
|
88
|
+
print("\n[After Injection]")
|
|
89
|
+
|
|
90
|
+
# (A) Verify manual_func: Prevent double wrapping
|
|
91
|
+
# If AutoInjector skipped it, the object ID should remain unchanged.
|
|
92
|
+
current_manual_func_id = id(mod.manual_func)
|
|
93
|
+
|
|
94
|
+
if current_manual_func_id == original_manual_func_id:
|
|
95
|
+
print("✅ manual_func: SKIPPED (Identity preserved)")
|
|
96
|
+
else:
|
|
97
|
+
print("❌ manual_func: WRAPPED AGAIN (Identity changed!)")
|
|
98
|
+
|
|
99
|
+
assert current_manual_func_id == original_manual_func_id, \
|
|
100
|
+
"Manually configured function was double-wrapped by AutoInjector!"
|
|
101
|
+
|
|
102
|
+
# (B) Verify plain_func: Auto-wrapping success
|
|
103
|
+
# Since it's newly wrapped, _is_vectorized should become True.
|
|
104
|
+
is_vectorized = getattr(mod.plain_func, "_is_vectorized", False)
|
|
105
|
+
print(f"plain_func vectorized? {is_vectorized}")
|
|
106
|
+
|
|
107
|
+
assert is_vectorized is True, "Plain function was not auto-injected!"
|
|
108
|
+
|
|
109
|
+
finally:
|
|
110
|
+
# Cleanup module after test
|
|
111
|
+
del sys.modules[module_name]
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from .core.decorator import vectorize
|
|
2
|
-
|
|
3
2
|
from .database.db import initialize_database
|
|
4
3
|
from .database.db_search import search_functions, search_executions, search_errors_by_message, search_functions_hybrid
|
|
5
4
|
from .monitoring.tracer import trace_span
|
|
@@ -9,6 +8,7 @@ from .utils.healer import VectorWaveHealer
|
|
|
9
8
|
from .utils.replayer import VectorWaveReplayer
|
|
10
9
|
from .utils.replayer_semantic import SemanticReplayer
|
|
11
10
|
from .database.dataset import VectorWaveDatasetManager
|
|
11
|
+
from .core.auto_injector import VectorWaveAutoInjector
|
|
12
12
|
|
|
13
13
|
__all__ = [
|
|
14
14
|
'vectorize',
|
|
@@ -24,5 +24,6 @@ __all__ = [
|
|
|
24
24
|
'VectorWaveHealer',
|
|
25
25
|
'VectorWaveReplayer',
|
|
26
26
|
'SemanticReplayer',
|
|
27
|
-
'VectorWaveDatasetManager'
|
|
27
|
+
'VectorWaveDatasetManager',
|
|
28
|
+
'VectorWaveAutoInjector'
|
|
28
29
|
]
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import importlib
|
|
3
|
+
import logging
|
|
4
|
+
from functools import wraps
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
# VectorWave Core Modules
|
|
8
|
+
from .decorator import vectorize
|
|
9
|
+
from ..monitoring.tracer import trace_span, current_tracer_var
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
def create_smart_wrapper(original_func, root_wrapper):
|
|
14
|
+
"""
|
|
15
|
+
Creates a wrapper that switches behavior based on the Tracing Context.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
original_func: The original function (used when acting as a Child Span).
|
|
19
|
+
root_wrapper: The wrapper generated by vectorize (used when acting as a Root Span, includes metadata).
|
|
20
|
+
"""
|
|
21
|
+
@wraps(original_func)
|
|
22
|
+
def wrapper(*args, **kwargs):
|
|
23
|
+
# Check for existing tracer at runtime
|
|
24
|
+
tracer = current_tracer_var.get()
|
|
25
|
+
|
|
26
|
+
if tracer:
|
|
27
|
+
# [Case A] Parent trace exists -> Act as Child Span
|
|
28
|
+
# (Wrap original function with trace_span and execute)
|
|
29
|
+
return trace_span(original_func, capture_return_value=True)(*args, **kwargs)
|
|
30
|
+
else:
|
|
31
|
+
# [Case B] No parent trace -> Act as Root Span
|
|
32
|
+
# (Delegate to the already configured root_wrapper)
|
|
33
|
+
return root_wrapper(*args, **kwargs)
|
|
34
|
+
|
|
35
|
+
# Mark to prevent double injection
|
|
36
|
+
wrapper._is_vectorized = True
|
|
37
|
+
return wrapper
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class VectorWaveAutoInjector:
|
|
41
|
+
"""
|
|
42
|
+
Scans specified modules or packages to automatically inject (Monkey Patch) VectorWave functionality.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
# Global default configuration
|
|
46
|
+
_default_config = {}
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def configure(cls, **kwargs):
|
|
50
|
+
"""
|
|
51
|
+
Sets the default configuration to be applied to all inject calls.
|
|
52
|
+
"""
|
|
53
|
+
cls._default_config.update(kwargs)
|
|
54
|
+
logger.info(f"⚙️ VectorWave AutoInjector Configured: {cls._default_config}")
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def inject(cls, target_module_path: str, recursive: bool = False, **config):
|
|
58
|
+
"""
|
|
59
|
+
Finds functions within a module and injects VectorWave functionality.
|
|
60
|
+
**Metadata registration (DB save or Pending) is performed immediately at this point.**
|
|
61
|
+
"""
|
|
62
|
+
try:
|
|
63
|
+
module = importlib.import_module(target_module_path)
|
|
64
|
+
except ImportError as e:
|
|
65
|
+
logger.error(f"Failed to import module '{target_module_path}': {e}")
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
# Merge configurations (Defaults < Individual Config)
|
|
69
|
+
final_config = cls._default_config.copy()
|
|
70
|
+
final_config.update(config)
|
|
71
|
+
|
|
72
|
+
logger.info(f"🌊 [AutoInjector] Injecting VectorWave into: {module.__name__}")
|
|
73
|
+
|
|
74
|
+
patched_count = 0
|
|
75
|
+
|
|
76
|
+
for name, obj in inspect.getmembers(module):
|
|
77
|
+
# Target only functions defined within the module
|
|
78
|
+
if inspect.isfunction(obj) and obj.__module__ == module.__name__:
|
|
79
|
+
|
|
80
|
+
# Skip if already processed
|
|
81
|
+
if getattr(obj, "_is_vectorized", False):
|
|
82
|
+
continue
|
|
83
|
+
|
|
84
|
+
logger.info(f" └─ Auto-wiring: {name}()")
|
|
85
|
+
|
|
86
|
+
# [Key Change] Call vectorize immediately at injection time to perform metadata registration!
|
|
87
|
+
# - If auto=False: Immediate DB save
|
|
88
|
+
# - If auto=True: Add to PENDING list
|
|
89
|
+
# The returned root_wrapper contains the execution logic (Root Span)
|
|
90
|
+
root_wrapper = vectorize(**final_config)(obj)
|
|
91
|
+
|
|
92
|
+
# Create smart wrapper to judge Context at runtime
|
|
93
|
+
smart_wrapper = create_smart_wrapper(obj, root_wrapper)
|
|
94
|
+
|
|
95
|
+
# Replace (Monkey Patching)
|
|
96
|
+
setattr(module, name, smart_wrapper)
|
|
97
|
+
patched_count += 1
|
|
98
|
+
|
|
99
|
+
logger.info(f"✨ Injection Complete. {patched_count} functions registered & auto-wired.")
|
|
100
|
+
return module
|
|
@@ -161,6 +161,7 @@ def vectorize(search_description: Optional[str] = None,
|
|
|
161
161
|
full_kwargs['exec_source'] = execution_source_context.get()
|
|
162
162
|
return await inner_wrapper(*args, **full_kwargs)
|
|
163
163
|
|
|
164
|
+
outer_wrapper._is_vectorized = True
|
|
164
165
|
return outer_wrapper
|
|
165
166
|
|
|
166
167
|
else: # Sync wrapper
|
|
@@ -184,6 +185,7 @@ def vectorize(search_description: Optional[str] = None,
|
|
|
184
185
|
full_kwargs['exec_source'] = execution_source_context.get()
|
|
185
186
|
return inner_wrapper(*args, **full_kwargs)
|
|
186
187
|
|
|
188
|
+
outer_wrapper._is_vectorized = True
|
|
187
189
|
return outer_wrapper
|
|
188
190
|
|
|
189
191
|
return decorator
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vectorwave
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: VectorWave: Seamless Auto-Vectorization Framework
|
|
5
5
|
Author-email: junyeonggim <junyeonggim5@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -618,6 +618,40 @@ archiver.export_and_clear(
|
|
|
618
618
|
{"messages": [{"role": "user", "content": "{\"price\": 100, \"tax\": 0.1}"}, {"role": "assistant", "content": "110.0"}]}
|
|
619
619
|
```
|
|
620
620
|
|
|
621
|
+
## 🌊 Auto-Injection (Zero-Code Change Integration)
|
|
622
|
+
|
|
623
|
+
You don't need to modify your business logic to use VectorWave. Use `VectorWaveAutoInjector` to inject functionality externally.
|
|
624
|
+
|
|
625
|
+
### How to use
|
|
626
|
+
|
|
627
|
+
1. **Configure Global Settings**: Set default values like `team`, `priority`, or `auto` (pending mode).
|
|
628
|
+
2. **Inject Modules**: Specify the target module path string.
|
|
629
|
+
|
|
630
|
+
```python
|
|
631
|
+
from vectorwave import initialize_database, VectorWaveAutoInjector, generate_and_register_metadata
|
|
632
|
+
|
|
633
|
+
# 1. Initialize DB
|
|
634
|
+
initialize_database()
|
|
635
|
+
|
|
636
|
+
# 2. Configure AutoInjector (Global Settings)
|
|
637
|
+
VectorWaveAutoInjector.configure(
|
|
638
|
+
team="billing-team",
|
|
639
|
+
priority=1,
|
|
640
|
+
auto=True # True: Collect metadata in memory (Pending), False: Save immediately to DB
|
|
641
|
+
)
|
|
642
|
+
|
|
643
|
+
# 3. Inject VectorWave into your module
|
|
644
|
+
# (No need to add @vectorize decorators in 'my_service.payment' code!)
|
|
645
|
+
VectorWaveAutoInjector.inject("my_service.payment")
|
|
646
|
+
|
|
647
|
+
# 4. Register Metadata (If auto=True)
|
|
648
|
+
generate_and_register_metadata()
|
|
649
|
+
|
|
650
|
+
# 5. Run your business logic
|
|
651
|
+
import my_service.payment
|
|
652
|
+
my_service.payment.process_transaction()
|
|
653
|
+
```
|
|
654
|
+
|
|
621
655
|
## 🤝 Contributing
|
|
622
656
|
|
|
623
657
|
We welcome all forms of contributions, including bug reports, feature requests, and code contributions. Please refer to [CONTRIBUTING.md](https://www.google.com/search?q=httpsS://www.google.com/search%3Fq%3DCONTRIBUTING.md) for details.
|
|
@@ -8,6 +8,8 @@ src/tests/conftest.py
|
|
|
8
8
|
src/tests/batch/__init__.py
|
|
9
9
|
src/tests/batch/test_batch.py
|
|
10
10
|
src/tests/core/__init__.py
|
|
11
|
+
src/tests/core/test_auto_injector.py
|
|
12
|
+
src/tests/core/test_auto_injector_real.py
|
|
11
13
|
src/tests/core/test_decorator.py
|
|
12
14
|
src/tests/core/test_semantic_caching.py
|
|
13
15
|
src/tests/core/llm/__init__.py
|
|
@@ -44,6 +46,7 @@ src/vectorwave.egg-info/top_level.txt
|
|
|
44
46
|
src/vectorwave/batch/__init__.py
|
|
45
47
|
src/vectorwave/batch/batch.py
|
|
46
48
|
src/vectorwave/core/__init__.py
|
|
49
|
+
src/vectorwave/core/auto_injector.py
|
|
47
50
|
src/vectorwave/core/core.py
|
|
48
51
|
src/vectorwave/core/decorator.py
|
|
49
52
|
src/vectorwave/core/generator.py
|
|
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
|
|
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
|