vectorwave 0.2.2__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.
Files changed (94) hide show
  1. {vectorwave-0.2.2/src/vectorwave.egg-info → vectorwave-0.2.4}/PKG-INFO +35 -1
  2. {vectorwave-0.2.2 → vectorwave-0.2.4}/Readme.md +34 -0
  3. {vectorwave-0.2.2 → vectorwave-0.2.4}/pyproject.toml +1 -1
  4. vectorwave-0.2.4/src/tests/core/test_auto_injector.py +228 -0
  5. vectorwave-0.2.4/src/tests/core/test_auto_injector_real.py +111 -0
  6. vectorwave-0.2.4/src/tests/utils/test_replay_source.py +140 -0
  7. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/__init__.py +3 -2
  8. vectorwave-0.2.4/src/vectorwave/core/auto_injector.py +100 -0
  9. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/core/decorator.py +9 -4
  10. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/database/db.py +5 -0
  11. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/monitoring/tracer.py +3 -1
  12. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/search/execution_search.py +31 -0
  13. vectorwave-0.2.4/src/vectorwave/utils/context.py +4 -0
  14. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/utils/replayer.py +8 -0
  15. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/utils/replayer_semantic.py +6 -0
  16. {vectorwave-0.2.2 → vectorwave-0.2.4/src/vectorwave.egg-info}/PKG-INFO +35 -1
  17. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave.egg-info/SOURCES.txt +5 -0
  18. {vectorwave-0.2.2 → vectorwave-0.2.4}/LICENSE +0 -0
  19. {vectorwave-0.2.2 → vectorwave-0.2.4}/MANIFEST.in +0 -0
  20. {vectorwave-0.2.2 → vectorwave-0.2.4}/NOTICE +0 -0
  21. {vectorwave-0.2.2 → vectorwave-0.2.4}/setup.cfg +0 -0
  22. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/tests/__init__.py +0 -0
  23. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/tests/batch/__init__.py +0 -0
  24. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/tests/batch/test_batch.py +0 -0
  25. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/tests/conftest.py +0 -0
  26. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/tests/core/__init__.py +0 -0
  27. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/tests/core/llm/__init__.py +0 -0
  28. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/tests/core/llm/test_token_usage.py +0 -0
  29. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/tests/core/test_decorator.py +0 -0
  30. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/tests/core/test_semantic_caching.py +0 -0
  31. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/tests/database/__init__.py +0 -0
  32. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/tests/database/test_archiver.py +0 -0
  33. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/tests/database/test_dataset.py +0 -0
  34. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/tests/database/test_db.py +0 -0
  35. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/tests/database/test_db_search.py +0 -0
  36. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/tests/exception/__init__.py +0 -0
  37. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/tests/models/__init__.py +0 -0
  38. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/tests/models/test_db_config.py +0 -0
  39. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/tests/monitoring/__init__.py +0 -0
  40. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/tests/monitoring/alert/__init__.py +0 -0
  41. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/tests/monitoring/alert/test_alerter.py +0 -0
  42. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/tests/monitoring/test_async_trace.py +0 -0
  43. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/tests/monitoring/test_tracer.py +0 -0
  44. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/tests/prediction/__init__.py +0 -0
  45. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/tests/search/__init__.py +0 -0
  46. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/tests/search/test_execution_search.py +0 -0
  47. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/tests/utils/__init__.py +0 -0
  48. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/tests/utils/test_function_cahe.py +0 -0
  49. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/tests/utils/test_replayer.py +0 -0
  50. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/tests/utils/test_return_caching.py +0 -0
  51. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/tests/utils/test_status.py +0 -0
  52. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/tests/vectorizer/__init__.py +0 -0
  53. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/batch/__init__.py +0 -0
  54. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/batch/batch.py +0 -0
  55. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/core/__init__.py +0 -0
  56. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/core/core.py +0 -0
  57. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/core/generator.py +0 -0
  58. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/core/llm/__init__.py +0 -0
  59. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/core/llm/base.py +0 -0
  60. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/core/llm/factory.py +0 -0
  61. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/core/llm/openai_client.py +0 -0
  62. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/database/__init__.py +0 -0
  63. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/database/archiver.py +0 -0
  64. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/database/dataset.py +0 -0
  65. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/database/db_search.py +0 -0
  66. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/exception/__init__.py +0 -0
  67. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/exception/exceptions.py +0 -0
  68. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/models/__init__.py +0 -0
  69. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/models/db_config.py +0 -0
  70. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/monitoring/__init__.py +0 -0
  71. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/monitoring/alert/__init__.py +0 -0
  72. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/monitoring/alert/base.py +0 -0
  73. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/monitoring/alert/factory.py +0 -0
  74. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/monitoring/alert/null_alerter.py +0 -0
  75. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/monitoring/alert/webhook_alerter.py +0 -0
  76. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/monitoring/monitoring.py +0 -0
  77. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/prediction/__init__.py +0 -0
  78. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/prediction/predictor.py +0 -0
  79. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/search/__init__.py +0 -0
  80. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/search/extended_search.py +0 -0
  81. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/search/rag_search.py +0 -0
  82. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/utils/__init__.py +0 -0
  83. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/utils/function_cache.py +0 -0
  84. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/utils/healer.py +0 -0
  85. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/utils/return_caching_utils.py +0 -0
  86. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/utils/status.py +0 -0
  87. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/vectorizer/__init__.py +0 -0
  88. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/vectorizer/base.py +0 -0
  89. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/vectorizer/factory.py +0 -0
  90. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/vectorizer/huggingface_vectorizer.py +0 -0
  91. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave/vectorizer/openai_vectorizer.py +0 -0
  92. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave.egg-info/dependency_links.txt +0 -0
  93. {vectorwave-0.2.2 → vectorwave-0.2.4}/src/vectorwave.egg-info/requires.txt +0 -0
  94. {vectorwave-0.2.2 → 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.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.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "vectorwave"
7
- version = "0.2.02"
7
+ version = "0.2.04"
8
8
  authors = [
9
9
  { name = "junyeonggim", email = "junyeonggim5@gmail.com" },
10
10
  ]
@@ -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]
@@ -0,0 +1,140 @@
1
+ import pytest
2
+ import sys
3
+ from unittest.mock import MagicMock, patch
4
+ from vectorwave import vectorize, search_executions
5
+ from vectorwave.utils.replayer import VectorWaveReplayer
6
+ from vectorwave.models.db_config import WeaviateSettings
7
+
8
+ # 1. Dummy function for testing
9
+ @vectorize(unique_id="test_replay_tagging")
10
+ def calc_add(a: int, b: int):
11
+ """Simple addition function for testing replay tags."""
12
+ return a + b
13
+
14
+ @pytest.fixture
15
+ def mock_replay_deps(monkeypatch):
16
+ """Mocks dependencies for Replay Source testing."""
17
+ # Mock Settings
18
+ mock_settings = WeaviateSettings(
19
+ EXECUTION_COLLECTION_NAME="VectorWaveExecutions",
20
+ GOLDEN_COLLECTION_NAME="VectorWaveGoldenDataset"
21
+ )
22
+ mock_get_settings = MagicMock(return_value=mock_settings)
23
+
24
+ # Mock Batch Manager (to verify logs)
25
+ mock_batch = MagicMock()
26
+ mock_batch.add_object = MagicMock()
27
+ mock_get_batch = MagicMock(return_value=mock_batch)
28
+
29
+ # Mock DB Client (for Replayer initialization)
30
+ mock_client = MagicMock()
31
+ mock_collection = MagicMock()
32
+ mock_client.collections.get.return_value = mock_collection
33
+ mock_get_client = MagicMock(return_value=mock_client)
34
+
35
+ # --- [수정] Patch dependencies where they are USED, not just defined ---
36
+
37
+ # 1. Patch for Replayer (vectorwave/utils/replayer.py)
38
+ monkeypatch.setattr("vectorwave.utils.replayer.get_cached_client", mock_get_client)
39
+ monkeypatch.setattr("vectorwave.utils.replayer.get_weaviate_settings", mock_get_settings)
40
+
41
+ # 2. Patch for Database (vectorwave/database/db.py) - 안전장치
42
+ monkeypatch.setattr("vectorwave.database.db.get_cached_client", mock_get_client)
43
+
44
+ # 3. Patch for Config (vectorwave/models/db_config.py) - 안전장치
45
+ monkeypatch.setattr("vectorwave.models.db_config.get_weaviate_settings", mock_get_settings)
46
+
47
+ # 4. Patch batch manager for Decorator & Tracer
48
+ monkeypatch.setattr("vectorwave.core.decorator.get_batch_manager", mock_get_batch)
49
+ monkeypatch.setattr("vectorwave.monitoring.tracer.get_batch_manager", mock_get_batch)
50
+
51
+ # 5. Patch search_executions in THIS test file
52
+ mock_search = MagicMock()
53
+ monkeypatch.setattr(sys.modules[__name__], "search_executions", mock_search)
54
+
55
+ return {
56
+ "batch": mock_batch,
57
+ "search": mock_search,
58
+ "client": mock_client
59
+ }
60
+
61
+ def test_execution_source_tagging(mock_replay_deps):
62
+ """
63
+ Verification Scenario:
64
+ 1. Realtime Execution -> Check if saved to DB as 'REALTIME'
65
+ 2. Replay Execution -> Check if saved to DB as 'REPLAY'
66
+ """
67
+ mock_batch = mock_replay_deps["batch"]
68
+
69
+ # ---------------------------------------------------------
70
+ # Case 1: Realtime Execution
71
+ # ---------------------------------------------------------
72
+ print("\n🚀 1. Running Realtime Execution...")
73
+ calc_add(10, 20)
74
+
75
+ # Check if log was sent to BatchManager
76
+ assert mock_batch.add_object.called
77
+
78
+ # Get the latest call args
79
+ args, kwargs = mock_batch.add_object.call_args
80
+ props = kwargs['properties']
81
+
82
+ print(f" [Log Check] Source: {props.get('exec_source')}")
83
+
84
+ # Verify exec_source is 'REALTIME'
85
+ assert props.get("exec_source") == "REALTIME"
86
+ assert props.get("status") == "SUCCESS"
87
+
88
+ # ---------------------------------------------------------
89
+ # Case 2: Replay Execution
90
+ # ---------------------------------------------------------
91
+ print("\n🔄 2. Running Replay...")
92
+
93
+ # Prepare dummy log for Replayer to fetch
94
+ dummy_log_obj = MagicMock()
95
+ dummy_log_obj.uuid = "old-uuid-123"
96
+ dummy_log_obj.properties = {
97
+ "function_name": "calc_add",
98
+ "a": 10,
99
+ "b": 20,
100
+ "return_value": "30",
101
+ "exec_source": "REALTIME"
102
+ }
103
+
104
+ # Replayer uses client.collections.get().query.fetch_objects()
105
+ mock_collection = mock_replay_deps["client"].collections.get.return_value
106
+ mock_collection.query.fetch_objects.return_value.objects = [dummy_log_obj]
107
+
108
+ # Also need to mock import_module since Replayer imports the function dynamically
109
+ with patch("vectorwave.utils.replayer.importlib.import_module") as mock_import:
110
+ mock_module = MagicMock()
111
+ # Set the function on the mock module
112
+ setattr(mock_module, "calc_add", calc_add)
113
+ mock_import.return_value = mock_module
114
+
115
+ replayer = VectorWaveReplayer()
116
+
117
+ # Reset batch mock to capture only replay log
118
+ mock_batch.add_object.reset_mock()
119
+
120
+ # Execute Replay
121
+ result = replayer.replay(
122
+ function_full_name="src.tests.utils.test_replay_source.calc_add",
123
+ limit=1
124
+ )
125
+
126
+ assert result['total'] > 0, "Replay should have executed at least 1 test."
127
+
128
+ # Check if new log was sent to BatchManager
129
+ assert mock_batch.add_object.called
130
+
131
+ # Get the latest call args (from Replay execution)
132
+ args, kwargs = mock_batch.add_object.call_args
133
+ replay_props = kwargs['properties']
134
+
135
+ print(f" [Log Check] Source: {replay_props.get('exec_source')}")
136
+
137
+ # Verify exec_source is 'REPLAY'
138
+ assert replay_props.get("exec_source") == "REPLAY"
139
+
140
+ print("\n✅ All tests passed! Execution Source tagging is working.")
@@ -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
@@ -11,6 +11,7 @@ from ..monitoring.tracer import trace_root, trace_span
11
11
  from ..utils.function_cache import function_cache_manager
12
12
  from ..utils.return_caching_utils import _check_and_return_cached_result
13
13
  from ..vectorizer.factory import get_vectorizer
14
+ from ..utils.context import execution_source_context
14
15
 
15
16
  logger = logging.getLogger(__name__)
16
17
 
@@ -53,7 +54,7 @@ def vectorize(search_description: Optional[str] = None,
53
54
  func_uuid = generate_uuid5(func_identifier)
54
55
 
55
56
  # Prepare attributes to capture
56
- final_attributes = ['function_uuid', 'team', 'priority', 'run_id']
57
+ final_attributes = ['function_uuid', 'team', 'priority', 'run_id', 'exec_source']
57
58
  if attributes_to_capture:
58
59
  for attr in attributes_to_capture:
59
60
  if attr not in final_attributes:
@@ -145,7 +146,7 @@ def vectorize(search_description: Optional[str] = None,
145
146
  async def inner_wrapper(*args, **kwargs):
146
147
  # Remove injected tags from kwargs before calling original func
147
148
  clean_kwargs = {k: v for k, v in kwargs.items() if
148
- k not in valid_execution_tags and k != 'function_uuid'}
149
+ k not in valid_execution_tags and k != 'function_uuid' and k != 'exec_source'}
149
150
  return await func(*args, **clean_kwargs)
150
151
 
151
152
  @wraps(func)
@@ -157,8 +158,10 @@ def vectorize(search_description: Optional[str] = None,
157
158
  full_kwargs = kwargs.copy()
158
159
  full_kwargs.update(valid_execution_tags)
159
160
  full_kwargs['function_uuid'] = func_uuid
161
+ full_kwargs['exec_source'] = execution_source_context.get()
160
162
  return await inner_wrapper(*args, **full_kwargs)
161
163
 
164
+ outer_wrapper._is_vectorized = True
162
165
  return outer_wrapper
163
166
 
164
167
  else: # Sync wrapper
@@ -167,7 +170,7 @@ def vectorize(search_description: Optional[str] = None,
167
170
  @wraps(func)
168
171
  def inner_wrapper(*args, **kwargs):
169
172
  clean_kwargs = {k: v for k, v in kwargs.items() if
170
- k not in valid_execution_tags and k != 'function_uuid'}
173
+ k not in valid_execution_tags and k != 'function_uuid' and k != 'exec_source'}
171
174
  return func(*args, **clean_kwargs)
172
175
 
173
176
  @wraps(func)
@@ -179,8 +182,10 @@ def vectorize(search_description: Optional[str] = None,
179
182
  full_kwargs = kwargs.copy()
180
183
  full_kwargs.update(valid_execution_tags)
181
184
  full_kwargs['function_uuid'] = func_uuid
185
+ full_kwargs['exec_source'] = execution_source_context.get()
182
186
  return inner_wrapper(*args, **full_kwargs)
183
187
 
188
+ outer_wrapper._is_vectorized = True
184
189
  return outer_wrapper
185
190
 
186
- return decorator
191
+ return decorator
@@ -282,6 +282,11 @@ def create_execution_schema(client: weaviate.WeaviateClient, settings: WeaviateS
282
282
  name="return_value",
283
283
  data_type=wvc.DataType.TEXT,
284
284
  description="The JSON-serialized or str() representation of the function's return value"
285
+ ),
286
+ wvc.Property(
287
+ name="exec_source",
288
+ data_type=wvc.DataType.TEXT,
289
+ description="Source of execution: 'REALTIME' (User traffic) or 'REPLAY' (Regression Test)"
285
290
  )
286
291
  ]
287
292
 
@@ -15,6 +15,7 @@ from ..models.db_config import get_weaviate_settings, WeaviateSettings
15
15
  from .alert.factory import get_alerter
16
16
  from ..vectorizer.factory import get_vectorizer
17
17
  from ..database.db_search import check_semantic_drift
18
+ from ..utils.context import execution_source_context
18
19
 
19
20
  # Create module-level logger
20
21
  logger = logging.getLogger(__name__)
@@ -137,7 +138,8 @@ def _create_span_properties(
137
138
  "status": status,
138
139
  "error_message": error_msg,
139
140
  "error_code": error_code,
140
- "return_value": return_value_to_log
141
+ "return_value": return_value_to_log,
142
+ "exec_source": execution_source_context.get()
141
143
  }
142
144
 
143
145
  if tracer.settings.global_custom_values:
@@ -121,3 +121,34 @@ def find_by_trace_id(trace_id: str, limit: int = 100) -> List[Dict[str, Any]]:
121
121
  sort_ascending=True, # Sort chronologically
122
122
  limit=limit
123
123
  )
124
+
125
+
126
+ def find_replay_executions(
127
+ limit: int = 20,
128
+ status: Optional[str] = None,
129
+ function_name: Optional[str] = None
130
+ ) -> List[Dict[str, Any]]:
131
+ """
132
+ Searches specifically for logs generated during a Replay session.
133
+
134
+ Args:
135
+ limit: Max number of logs to retrieve.
136
+ status: Optional filter for 'SUCCESS' or 'ERROR'.
137
+ function_name: Optional filter for specific function name.
138
+ """
139
+ logger.info(f"\n--- Searching for REPLAY Executions (Status: {status}, Func: {function_name}) ---")
140
+
141
+ filters = {"exec_source": "REPLAY"}
142
+
143
+ if status:
144
+ filters["status"] = status
145
+
146
+ if function_name:
147
+ filters["function_name"] = function_name
148
+
149
+ return find_executions(
150
+ filters=filters,
151
+ sort_by="timestamp_utc",
152
+ sort_ascending=False,
153
+ limit=limit
154
+ )
@@ -0,0 +1,4 @@
1
+ from contextvars import ContextVar
2
+
3
+ #default is realtime context
4
+ execution_source_context: ContextVar[str] = ContextVar("execution_source", default="REALTIME")
@@ -13,6 +13,7 @@ import weaviate.classes.query as wvc_query
13
13
  from ..database.db import get_cached_client
14
14
  from ..models.db_config import get_weaviate_settings
15
15
  from ..monitoring.tracer import _mask_and_serialize
16
+ from .context import execution_source_context
16
17
 
17
18
  logger = logging.getLogger(__name__)
18
19
 
@@ -78,7 +79,10 @@ class VectorWaveReplayer:
78
79
  # [FIX] Extract only valid arguments for the target function
79
80
  inputs = self._extract_inputs(raw_inputs, target_func)
80
81
 
82
+ token = None
81
83
  try:
84
+ token = execution_source_context.set("REPLAY")
85
+
82
86
  # 3. Function Re-execution
83
87
  if is_async_func:
84
88
  actual_output = asyncio.run(target_func(**inputs))
@@ -127,6 +131,10 @@ class VectorWaveReplayer:
127
131
  "traceback": traceback.format_exc()
128
132
  })
129
133
 
134
+ finally:
135
+ if token:
136
+ execution_source_context.reset(token)
137
+
130
138
  logger.info(f"Replay Finished. Passed: {results['passed']}, Failed: {results['failed']}")
131
139
  return results
132
140
 
@@ -6,6 +6,8 @@ import math
6
6
  import asyncio
7
7
  import inspect
8
8
  from typing import Any, Dict, List, Optional
9
+
10
+ from .context import execution_source_context
9
11
  from ..core.llm.factory import get_llm_client
10
12
 
11
13
  import weaviate.classes.query as wvc_query
@@ -88,12 +90,16 @@ class SemanticReplayer(VectorWaveReplayer):
88
90
  inputs = self._extract_inputs(raw_inputs, target_func)
89
91
 
90
92
  try:
93
+ token = execution_source_context.set("REPLAY")
91
94
  # 3. Function Re-execution
92
95
  if is_async_func:
93
96
  actual_output = asyncio.run(target_func(**inputs))
94
97
  else:
95
98
  actual_output = target_func(**inputs)
96
99
 
100
+ if token:
101
+ execution_source_context.reset(token)
102
+
97
103
  # 4. Result Validation (Semantic Comparison)
98
104
  is_match, match_reason = self._compare_results_semantic(
99
105
  expected_output,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vectorwave
3
- Version: 0.2.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
@@ -30,6 +32,7 @@ src/tests/search/__init__.py
30
32
  src/tests/search/test_execution_search.py
31
33
  src/tests/utils/__init__.py
32
34
  src/tests/utils/test_function_cahe.py
35
+ src/tests/utils/test_replay_source.py
33
36
  src/tests/utils/test_replayer.py
34
37
  src/tests/utils/test_return_caching.py
35
38
  src/tests/utils/test_status.py
@@ -43,6 +46,7 @@ src/vectorwave.egg-info/top_level.txt
43
46
  src/vectorwave/batch/__init__.py
44
47
  src/vectorwave/batch/batch.py
45
48
  src/vectorwave/core/__init__.py
49
+ src/vectorwave/core/auto_injector.py
46
50
  src/vectorwave/core/core.py
47
51
  src/vectorwave/core/decorator.py
48
52
  src/vectorwave/core/generator.py
@@ -74,6 +78,7 @@ src/vectorwave/search/execution_search.py
74
78
  src/vectorwave/search/extended_search.py
75
79
  src/vectorwave/search/rag_search.py
76
80
  src/vectorwave/utils/__init__.py
81
+ src/vectorwave/utils/context.py
77
82
  src/vectorwave/utils/function_cache.py
78
83
  src/vectorwave/utils/healer.py
79
84
  src/vectorwave/utils/replayer.py
File without changes
File without changes
File without changes
File without changes