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.
Files changed (94) hide show
  1. {vectorwave-0.2.3/src/vectorwave.egg-info → vectorwave-0.2.4}/PKG-INFO +35 -1
  2. {vectorwave-0.2.3 → vectorwave-0.2.4}/Readme.md +34 -0
  3. {vectorwave-0.2.3 → 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.3 → vectorwave-0.2.4}/src/vectorwave/__init__.py +3 -2
  7. vectorwave-0.2.4/src/vectorwave/core/auto_injector.py +100 -0
  8. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/core/decorator.py +2 -0
  9. {vectorwave-0.2.3 → vectorwave-0.2.4/src/vectorwave.egg-info}/PKG-INFO +35 -1
  10. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave.egg-info/SOURCES.txt +3 -0
  11. {vectorwave-0.2.3 → vectorwave-0.2.4}/LICENSE +0 -0
  12. {vectorwave-0.2.3 → vectorwave-0.2.4}/MANIFEST.in +0 -0
  13. {vectorwave-0.2.3 → vectorwave-0.2.4}/NOTICE +0 -0
  14. {vectorwave-0.2.3 → vectorwave-0.2.4}/setup.cfg +0 -0
  15. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/__init__.py +0 -0
  16. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/batch/__init__.py +0 -0
  17. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/batch/test_batch.py +0 -0
  18. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/conftest.py +0 -0
  19. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/core/__init__.py +0 -0
  20. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/core/llm/__init__.py +0 -0
  21. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/core/llm/test_token_usage.py +0 -0
  22. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/core/test_decorator.py +0 -0
  23. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/core/test_semantic_caching.py +0 -0
  24. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/database/__init__.py +0 -0
  25. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/database/test_archiver.py +0 -0
  26. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/database/test_dataset.py +0 -0
  27. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/database/test_db.py +0 -0
  28. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/database/test_db_search.py +0 -0
  29. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/exception/__init__.py +0 -0
  30. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/models/__init__.py +0 -0
  31. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/models/test_db_config.py +0 -0
  32. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/monitoring/__init__.py +0 -0
  33. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/monitoring/alert/__init__.py +0 -0
  34. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/monitoring/alert/test_alerter.py +0 -0
  35. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/monitoring/test_async_trace.py +0 -0
  36. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/monitoring/test_tracer.py +0 -0
  37. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/prediction/__init__.py +0 -0
  38. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/search/__init__.py +0 -0
  39. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/search/test_execution_search.py +0 -0
  40. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/utils/__init__.py +0 -0
  41. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/utils/test_function_cahe.py +0 -0
  42. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/utils/test_replay_source.py +0 -0
  43. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/utils/test_replayer.py +0 -0
  44. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/utils/test_return_caching.py +0 -0
  45. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/utils/test_status.py +0 -0
  46. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/tests/vectorizer/__init__.py +0 -0
  47. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/batch/__init__.py +0 -0
  48. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/batch/batch.py +0 -0
  49. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/core/__init__.py +0 -0
  50. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/core/core.py +0 -0
  51. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/core/generator.py +0 -0
  52. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/core/llm/__init__.py +0 -0
  53. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/core/llm/base.py +0 -0
  54. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/core/llm/factory.py +0 -0
  55. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/core/llm/openai_client.py +0 -0
  56. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/database/__init__.py +0 -0
  57. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/database/archiver.py +0 -0
  58. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/database/dataset.py +0 -0
  59. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/database/db.py +0 -0
  60. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/database/db_search.py +0 -0
  61. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/exception/__init__.py +0 -0
  62. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/exception/exceptions.py +0 -0
  63. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/models/__init__.py +0 -0
  64. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/models/db_config.py +0 -0
  65. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/monitoring/__init__.py +0 -0
  66. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/monitoring/alert/__init__.py +0 -0
  67. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/monitoring/alert/base.py +0 -0
  68. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/monitoring/alert/factory.py +0 -0
  69. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/monitoring/alert/null_alerter.py +0 -0
  70. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/monitoring/alert/webhook_alerter.py +0 -0
  71. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/monitoring/monitoring.py +0 -0
  72. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/monitoring/tracer.py +0 -0
  73. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/prediction/__init__.py +0 -0
  74. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/prediction/predictor.py +0 -0
  75. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/search/__init__.py +0 -0
  76. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/search/execution_search.py +0 -0
  77. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/search/extended_search.py +0 -0
  78. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/search/rag_search.py +0 -0
  79. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/utils/__init__.py +0 -0
  80. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/utils/context.py +0 -0
  81. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/utils/function_cache.py +0 -0
  82. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/utils/healer.py +0 -0
  83. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/utils/replayer.py +0 -0
  84. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/utils/replayer_semantic.py +0 -0
  85. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/utils/return_caching_utils.py +0 -0
  86. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/utils/status.py +0 -0
  87. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/vectorizer/__init__.py +0 -0
  88. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/vectorizer/base.py +0 -0
  89. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/vectorizer/factory.py +0 -0
  90. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/vectorizer/huggingface_vectorizer.py +0 -0
  91. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave/vectorizer/openai_vectorizer.py +0 -0
  92. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave.egg-info/dependency_links.txt +0 -0
  93. {vectorwave-0.2.3 → vectorwave-0.2.4}/src/vectorwave.egg-info/requires.txt +0 -0
  94. {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
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.03"
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]
@@ -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
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