veris-ai 1.13.0__tar.gz → 1.14.0__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.

Potentially problematic release.


This version of veris-ai might be problematic. Click here for more details.

Files changed (43) hide show
  1. {veris_ai-1.13.0 → veris_ai-1.14.0}/PKG-INFO +1 -1
  2. {veris_ai-1.13.0 → veris_ai-1.14.0}/pyproject.toml +1 -1
  3. {veris_ai-1.13.0 → veris_ai-1.14.0}/src/veris_ai/tool_mock.py +11 -1
  4. {veris_ai-1.13.0 → veris_ai-1.14.0}/src/veris_ai/utils.py +72 -0
  5. {veris_ai-1.13.0 → veris_ai-1.14.0}/tests/test_side_effects.py +354 -0
  6. {veris_ai-1.13.0 → veris_ai-1.14.0}/uv.lock +1 -1
  7. {veris_ai-1.13.0 → veris_ai-1.14.0}/.cursor/rules/documentation-management.mdc +0 -0
  8. {veris_ai-1.13.0 → veris_ai-1.14.0}/.github/workflows/release.yml +0 -0
  9. {veris_ai-1.13.0 → veris_ai-1.14.0}/.github/workflows/test.yml +0 -0
  10. {veris_ai-1.13.0 → veris_ai-1.14.0}/.gitignore +0 -0
  11. {veris_ai-1.13.0 → veris_ai-1.14.0}/.pre-commit-config.yaml +0 -0
  12. {veris_ai-1.13.0 → veris_ai-1.14.0}/CHANGELOG.md +0 -0
  13. {veris_ai-1.13.0 → veris_ai-1.14.0}/CLAUDE.md +0 -0
  14. {veris_ai-1.13.0 → veris_ai-1.14.0}/LICENSE +0 -0
  15. {veris_ai-1.13.0 → veris_ai-1.14.0}/README.md +0 -0
  16. {veris_ai-1.13.0 → veris_ai-1.14.0}/examples/README.md +0 -0
  17. {veris_ai-1.13.0 → veris_ai-1.14.0}/examples/__init__.py +0 -0
  18. {veris_ai-1.13.0 → veris_ai-1.14.0}/examples/import_options.py +0 -0
  19. {veris_ai-1.13.0 → veris_ai-1.14.0}/examples/openai_agents_example.py +0 -0
  20. {veris_ai-1.13.0 → veris_ai-1.14.0}/src/veris_ai/README.md +0 -0
  21. {veris_ai-1.13.0 → veris_ai-1.14.0}/src/veris_ai/__init__.py +0 -0
  22. {veris_ai-1.13.0 → veris_ai-1.14.0}/src/veris_ai/agents_wrapper.py +0 -0
  23. {veris_ai-1.13.0 → veris_ai-1.14.0}/src/veris_ai/api_client.py +0 -0
  24. {veris_ai-1.13.0 → veris_ai-1.14.0}/src/veris_ai/jaeger_interface/README.md +0 -0
  25. {veris_ai-1.13.0 → veris_ai-1.14.0}/src/veris_ai/jaeger_interface/__init__.py +0 -0
  26. {veris_ai-1.13.0 → veris_ai-1.14.0}/src/veris_ai/jaeger_interface/client.py +0 -0
  27. {veris_ai-1.13.0 → veris_ai-1.14.0}/src/veris_ai/jaeger_interface/models.py +0 -0
  28. {veris_ai-1.13.0 → veris_ai-1.14.0}/src/veris_ai/models.py +0 -0
  29. {veris_ai-1.13.0 → veris_ai-1.14.0}/src/veris_ai/observability.py +0 -0
  30. {veris_ai-1.13.0 → veris_ai-1.14.0}/tests/README.md +0 -0
  31. {veris_ai-1.13.0 → veris_ai-1.14.0}/tests/__init__.py +0 -0
  32. {veris_ai-1.13.0 → veris_ai-1.14.0}/tests/conftest.py +0 -0
  33. {veris_ai-1.13.0 → veris_ai-1.14.0}/tests/fixtures/__init__.py +0 -0
  34. {veris_ai-1.13.0 → veris_ai-1.14.0}/tests/fixtures/http_server.py +0 -0
  35. {veris_ai-1.13.0 → veris_ai-1.14.0}/tests/fixtures/simple_app.py +0 -0
  36. {veris_ai-1.13.0 → veris_ai-1.14.0}/tests/test_agents_wrapper_extract.py +0 -0
  37. {veris_ai-1.13.0 → veris_ai-1.14.0}/tests/test_agents_wrapper_simple.py +0 -0
  38. {veris_ai-1.13.0 → veris_ai-1.14.0}/tests/test_helpers.py +0 -0
  39. {veris_ai-1.13.0 → veris_ai-1.14.0}/tests/test_mcp_protocol_server_mocked.py +0 -0
  40. {veris_ai-1.13.0 → veris_ai-1.14.0}/tests/test_token_decoding.py +0 -0
  41. {veris_ai-1.13.0 → veris_ai-1.14.0}/tests/test_tool_mock.py +0 -0
  42. {veris_ai-1.13.0 → veris_ai-1.14.0}/tests/test_utils.py +0 -0
  43. {veris_ai-1.13.0 → veris_ai-1.14.0}/tests/test_veris_runner_tool_options.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: veris-ai
3
- Version: 1.13.0
3
+ Version: 1.14.0
4
4
  Summary: A Python package for Veris AI tools
5
5
  Project-URL: Homepage, https://github.com/veris-ai/veris-python-sdk
6
6
  Project-URL: Bug Tracker, https://github.com/veris-ai/veris-python-sdk/issues
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "veris-ai"
7
- version = "1.13.0"
7
+ version = "1.14.0"
8
8
  description = "A Python package for Veris AI tools"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -20,10 +20,12 @@ from veris_ai.api_client import get_api_client
20
20
  from veris_ai.utils import (
21
21
  convert_to_type,
22
22
  execute_callback,
23
+ execute_combined_callback,
23
24
  extract_json_schema,
24
25
  get_function_parameters,
25
26
  get_input_parameters,
26
27
  launch_callback_task,
28
+ launch_combined_callback_task,
27
29
  )
28
30
 
29
31
  logger = logging.getLogger(__name__)
@@ -245,13 +247,14 @@ class VerisSDK:
245
247
 
246
248
  return decorator
247
249
 
248
- def mock( # noqa: C901, PLR0915
250
+ def mock( # noqa: C901, PLR0915, PLR0913
249
251
  self,
250
252
  mode: Literal["tool", "function"] = "tool",
251
253
  expects_response: bool | None = None,
252
254
  cache_response: bool | None = None,
253
255
  input_callback: Callable[..., Any] | None = None,
254
256
  output_callback: Callable[[Any], Any] | None = None,
257
+ combined_callback: Callable[..., Any] | None = None,
255
258
  ) -> Callable:
256
259
  """Decorator for mocking tool calls.
257
260
 
@@ -261,6 +264,7 @@ class VerisSDK:
261
264
  cache_response: Whether to cache the response
262
265
  input_callback: Callable that receives input parameters as individual arguments
263
266
  output_callback: Callable that receives the output value
267
+ combined_callback: Callable that receives both input parameters and mock_output
264
268
  """
265
269
  response_expectation = (
266
270
  ResponseExpectation.NONE
@@ -306,6 +310,7 @@ class VerisSDK:
306
310
  input_params = get_input_parameters(func, args, kwargs)
307
311
  launch_callback_task(input_callback, input_params, unpack=True)
308
312
  launch_callback_task(output_callback, result, unpack=False)
313
+ launch_combined_callback_task(combined_callback, input_params, result)
309
314
 
310
315
  return result
311
316
 
@@ -337,6 +342,7 @@ class VerisSDK:
337
342
  input_params = get_input_parameters(func, args, kwargs)
338
343
  execute_callback(input_callback, input_params, unpack=True)
339
344
  execute_callback(output_callback, result, unpack=False)
345
+ execute_combined_callback(combined_callback, input_params, result)
340
346
 
341
347
  return result
342
348
 
@@ -350,6 +356,7 @@ class VerisSDK:
350
356
  return_value: Any, # noqa: ANN401
351
357
  input_callback: Callable[..., Any] | None = None,
352
358
  output_callback: Callable[[Any], Any] | None = None,
359
+ combined_callback: Callable[..., Any] | None = None,
353
360
  ) -> Callable:
354
361
  """Decorator for stubbing tool calls.
355
362
 
@@ -357,6 +364,7 @@ class VerisSDK:
357
364
  return_value: The value to return when the function is stubbed
358
365
  input_callback: Callable that receives input parameters as individual arguments
359
366
  output_callback: Callable that receives the output value
367
+ combined_callback: Callable that receives both input parameters and mock_output
360
368
  """
361
369
 
362
370
  def decorator(func: Callable) -> Callable:
@@ -380,6 +388,7 @@ class VerisSDK:
380
388
  input_params = get_input_parameters(func, args, kwargs)
381
389
  launch_callback_task(input_callback, input_params, unpack=True)
382
390
  launch_callback_task(output_callback, return_value, unpack=False)
391
+ launch_combined_callback_task(combined_callback, input_params, return_value)
383
392
 
384
393
  return return_value
385
394
 
@@ -397,6 +406,7 @@ class VerisSDK:
397
406
  input_params = get_input_parameters(func, args, kwargs)
398
407
  execute_callback(input_callback, input_params, unpack=True)
399
408
  execute_callback(output_callback, return_value, unpack=False)
409
+ execute_combined_callback(combined_callback, input_params, return_value)
400
410
 
401
411
  return return_value
402
412
 
@@ -489,3 +489,75 @@ def launch_callback_task(
489
489
  except RuntimeError:
490
490
  # If no event loop is running, log a warning
491
491
  logger.warning("Cannot launch callback task: no event loop running")
492
+
493
+
494
+ def execute_combined_callback(
495
+ callback: Callable | None,
496
+ input_params: dict[str, Any],
497
+ mock_output: Any, # noqa: ANN401
498
+ ) -> None:
499
+ """Execute a combined callback synchronously with input parameters and mock output.
500
+
501
+ Args:
502
+ callback: The callback callable to execute
503
+ input_params: Dictionary of input parameters
504
+ mock_output: The output from the mock/stub call
505
+
506
+ Note:
507
+ Exceptions in callbacks are caught and logged to prevent breaking the main flow.
508
+ """
509
+ if callback is None:
510
+ return
511
+
512
+ try:
513
+ # Combine input params with mock_output
514
+ combined_data = {**input_params, "mock_output": mock_output}
515
+ # Filter parameters to match callback signature
516
+ filtered_data = filter_callback_parameters(callback, combined_data)
517
+ callback(**filtered_data)
518
+ except Exception as e:
519
+ logger.warning(f"Combined callback execution failed: {e}", exc_info=True)
520
+
521
+
522
+ def launch_combined_callback_task(
523
+ callback: Callable | None,
524
+ input_params: dict[str, Any],
525
+ mock_output: Any, # noqa: ANN401
526
+ ) -> None:
527
+ """Launch a combined callback as a background task (fire-and-forget).
528
+
529
+ Args:
530
+ callback: The callback callable to execute (can be sync or async)
531
+ input_params: Dictionary of input parameters
532
+ mock_output: The output from the mock/stub call
533
+
534
+ Note:
535
+ This launches the callback without blocking. Errors are logged but won't
536
+ affect the main execution flow.
537
+ """
538
+ if callback is None:
539
+ return
540
+
541
+ async def _run_callback() -> None:
542
+ """Wrapper to run combined callback with error handling."""
543
+ try:
544
+ # Combine input params with mock_output
545
+ combined_data = {**input_params, "mock_output": mock_output}
546
+ # Filter parameters to match callback signature
547
+ filtered_data = filter_callback_parameters(callback, combined_data)
548
+
549
+ if inspect.iscoroutinefunction(callback):
550
+ await callback(**filtered_data)
551
+ else:
552
+ result = callback(**filtered_data)
553
+ if inspect.iscoroutine(result):
554
+ await result
555
+ except Exception as e:
556
+ logger.warning(f"Combined callback execution failed: {e}", exc_info=True)
557
+
558
+ # Create task without awaiting (fire-and-forget)
559
+ try:
560
+ asyncio.create_task(_run_callback())
561
+ except RuntimeError:
562
+ # If no event loop is running, log a warning
563
+ logger.warning("Cannot launch combined callback task: no event loop running")
@@ -948,3 +948,357 @@ def test_callback_with_no_matching_parameters(simulation_env):
948
948
  result = some_func(1, "test")
949
949
  assert result == "result"
950
950
  assert len(called) == 1
951
+
952
+
953
+ # Test combined_callback functionality
954
+
955
+
956
+ @pytest.mark.asyncio
957
+ async def test_mock_with_async_combined_callback(simulation_env):
958
+ """Test mock decorator with async combined_callback."""
959
+ captured_results = []
960
+
961
+ async def combined_handler(a: int, b: int, d: int = None, mock_output: int = None):
962
+ """Combined callback that uses both inputs and output."""
963
+ # Compute sum of inputs that are not None, then multiply by mock_output
964
+ total_input = a + b + (d if d is not None else 0)
965
+ result = total_input * (mock_output if mock_output is not None else 1)
966
+ captured_results.append(result)
967
+
968
+ @veris.mock(mode="function", combined_callback=combined_handler)
969
+ async def add(a: int, b: int, c: int = None, d: int = None) -> int:
970
+ """Add numbers together."""
971
+ return a + b + (c if c is not None else 0) + (d if d is not None else 0)
972
+
973
+ mock_response = 6
974
+
975
+ with patch.object(get_api_client(), "post_async", return_value=mock_response):
976
+ result = await add(1, 2, d=4)
977
+ assert result == 6
978
+ # Wait for background tasks to complete
979
+ await asyncio.sleep(0.01)
980
+ assert len(captured_results) == 1
981
+ # (1 + 2 + 4) * 6 = 42
982
+ assert captured_results[0] == 42
983
+
984
+
985
+ @pytest.mark.asyncio
986
+ async def test_mock_with_sync_combined_callback_async_func(simulation_env):
987
+ """Test mock decorator with sync combined_callback on async function."""
988
+ captured_results = []
989
+
990
+ def multiply_by_result(a: int, b: int, mock_output: int = None):
991
+ """Multiply inputs by the mock output."""
992
+ result = (a + b) * (mock_output if mock_output is not None else 1)
993
+ captured_results.append(result)
994
+
995
+ @veris.mock(mode="function", combined_callback=multiply_by_result)
996
+ async def add(a: int, b: int) -> int:
997
+ return a + b
998
+
999
+ mock_response = 10
1000
+
1001
+ with patch.object(get_api_client(), "post_async", return_value=mock_response):
1002
+ result = await add(3, 7)
1003
+ assert result == 10
1004
+ # Wait for background tasks to complete
1005
+ await asyncio.sleep(0.01)
1006
+ assert len(captured_results) == 1
1007
+ # (3 + 7) * 10 = 100
1008
+ assert captured_results[0] == 100
1009
+
1010
+
1011
+ def test_mock_with_sync_combined_callback_sync_func(simulation_env):
1012
+ """Test mock decorator with sync combined_callback on sync function."""
1013
+ captured_results = []
1014
+
1015
+ def process_combined(x: int, y: str, mock_output: dict = None):
1016
+ """Process both input and output."""
1017
+ result = {
1018
+ "input_x": x,
1019
+ "input_y": y,
1020
+ "output": mock_output,
1021
+ "combined": f"{y}:{x}:{mock_output.get('value') if mock_output else 'none'}",
1022
+ }
1023
+ captured_results.append(result)
1024
+
1025
+ @veris.mock(mode="function", combined_callback=process_combined)
1026
+ def test_func(x: int, y: str) -> dict:
1027
+ return {"value": x}
1028
+
1029
+ mock_response = {"value": 999}
1030
+
1031
+ with patch.object(get_api_client(), "post", return_value=mock_response):
1032
+ result = test_func(42, "test")
1033
+ assert result == {"value": 999}
1034
+ assert len(captured_results) == 1
1035
+ assert captured_results[0]["input_x"] == 42
1036
+ assert captured_results[0]["input_y"] == "test"
1037
+ assert captured_results[0]["output"] == {"value": 999}
1038
+ assert captured_results[0]["combined"] == "test:42:999"
1039
+
1040
+
1041
+ @pytest.mark.asyncio
1042
+ async def test_stub_with_async_combined_callback(simulation_env):
1043
+ """Test stub decorator with async combined_callback."""
1044
+ captured_results = []
1045
+
1046
+ async def combined_handler(name: str, count: int, mock_output: str = None):
1047
+ """Combine input and output."""
1048
+ result = f"{name}:{count}:{mock_output}"
1049
+ captured_results.append(result)
1050
+
1051
+ @veris.stub(return_value="stubbed_value", combined_callback=combined_handler)
1052
+ async def process(name: str, count: int) -> str:
1053
+ return f"{name}_{count}"
1054
+
1055
+ result = await process("test", 5)
1056
+ assert result == "stubbed_value"
1057
+ # Wait for background tasks to complete
1058
+ await asyncio.sleep(0.01)
1059
+ assert len(captured_results) == 1
1060
+ assert captured_results[0] == "test:5:stubbed_value"
1061
+
1062
+
1063
+ def test_stub_with_sync_combined_callback_sync_func(simulation_env):
1064
+ """Test stub decorator with sync combined_callback on sync function."""
1065
+ captured_results = []
1066
+
1067
+ def multiply_inputs_by_output(a: int, b: int, mock_output: int = None):
1068
+ """Multiply sum of inputs by output."""
1069
+ result = (a + b) * (mock_output if mock_output is not None else 1)
1070
+ captured_results.append(result)
1071
+
1072
+ @veris.stub(return_value=5, combined_callback=multiply_inputs_by_output)
1073
+ def add(a: int, b: int) -> int:
1074
+ return a + b
1075
+
1076
+ result = add(2, 3)
1077
+ assert result == 5
1078
+ assert len(captured_results) == 1
1079
+ # (2 + 3) * 5 = 25
1080
+ assert captured_results[0] == 25
1081
+
1082
+
1083
+ @pytest.mark.asyncio
1084
+ async def test_combined_callback_with_parameter_filtering(simulation_env):
1085
+ """Test that combined callback only receives parameters it accepts."""
1086
+ captured_results = []
1087
+
1088
+ async def selective_callback(a: int, mock_output: int = None):
1089
+ """Callback that only accepts 'a' and 'mock_output', not 'b' or 'c'."""
1090
+ result = a * (mock_output if mock_output is not None else 1)
1091
+ captured_results.append(result)
1092
+
1093
+ @veris.mock(mode="function", combined_callback=selective_callback)
1094
+ async def complex_func(a: int, b: int, c: str = "default") -> int:
1095
+ return a + b
1096
+
1097
+ mock_response = 7
1098
+
1099
+ with patch.object(get_api_client(), "post_async", return_value=mock_response):
1100
+ result = await complex_func(3, 5, c="ignored")
1101
+ assert result == 7
1102
+ # Wait for background tasks to complete
1103
+ await asyncio.sleep(0.01)
1104
+ assert len(captured_results) == 1
1105
+ # 3 * 7 = 21 (b and c are filtered out)
1106
+ assert captured_results[0] == 21
1107
+
1108
+
1109
+ @pytest.mark.asyncio
1110
+ async def test_combined_callback_in_production_mode(production_env):
1111
+ """Test that combined_callback is not called in production mode."""
1112
+ callback_mock = Mock()
1113
+
1114
+ @veris.mock(mode="function", combined_callback=callback_mock)
1115
+ async def test_func(x: int) -> int:
1116
+ return x * 2
1117
+
1118
+ result = await test_func(21)
1119
+ assert result == 42
1120
+
1121
+ # Combined callback should NOT have been called
1122
+ callback_mock.assert_not_called()
1123
+
1124
+
1125
+ @pytest.mark.asyncio
1126
+ async def test_combined_callback_exception_is_logged(simulation_env):
1127
+ """Test that exceptions in combined callback are caught and logged."""
1128
+
1129
+ async def failing_combined_callback(x: int, mock_output: int = None):
1130
+ raise ValueError("Combined callback error")
1131
+
1132
+ @veris.mock(mode="function", combined_callback=failing_combined_callback)
1133
+ async def test_func(x: int) -> int:
1134
+ return x * 2
1135
+
1136
+ mock_response = 100
1137
+
1138
+ with (
1139
+ patch.object(get_api_client(), "post_async", return_value=mock_response),
1140
+ patch("veris_ai.utils.logger.warning") as mock_logger,
1141
+ ):
1142
+ # Function should still work despite combined callback failure
1143
+ result = await test_func(42)
1144
+ assert result == 100
1145
+
1146
+ # Wait for background tasks to complete
1147
+ await asyncio.sleep(0.01)
1148
+
1149
+ # Error should have been logged
1150
+ mock_logger.assert_called()
1151
+ assert "Combined callback execution failed" in str(mock_logger.call_args)
1152
+
1153
+
1154
+ @pytest.mark.asyncio
1155
+ async def test_mock_with_all_three_callbacks(simulation_env):
1156
+ """Test mock decorator with input, output, and combined callbacks."""
1157
+ captured_inputs = []
1158
+ captured_outputs = []
1159
+ captured_combined = []
1160
+
1161
+ async def capture_input(**kwargs):
1162
+ captured_inputs.append(kwargs)
1163
+
1164
+ async def capture_output(result: int):
1165
+ captured_outputs.append(result)
1166
+
1167
+ async def capture_combined(a: int, b: int, mock_output: int = None):
1168
+ combined = {"sum": a + b, "product": a * b, "mock": mock_output}
1169
+ captured_combined.append(combined)
1170
+
1171
+ @veris.mock(
1172
+ mode="function",
1173
+ input_callback=capture_input,
1174
+ output_callback=capture_output,
1175
+ combined_callback=capture_combined,
1176
+ )
1177
+ async def add(a: int, b: int) -> int:
1178
+ return a + b
1179
+
1180
+ mock_response = 50
1181
+
1182
+ with patch.object(get_api_client(), "post_async", return_value=mock_response):
1183
+ result = await add(10, 20)
1184
+ assert result == 50
1185
+ # Wait for background tasks to complete
1186
+ await asyncio.sleep(0.01)
1187
+
1188
+ # All callbacks should have been called
1189
+ assert len(captured_inputs) == 1
1190
+ assert captured_inputs[0] == {"a": 10, "b": 20}
1191
+
1192
+ assert len(captured_outputs) == 1
1193
+ assert captured_outputs[0] == 50
1194
+
1195
+ assert len(captured_combined) == 1
1196
+ assert captured_combined[0] == {"sum": 30, "product": 200, "mock": 50}
1197
+
1198
+
1199
+ def test_stub_with_all_three_callbacks(simulation_env):
1200
+ """Test stub decorator with input, output, and combined callbacks."""
1201
+ captured_inputs = []
1202
+ captured_outputs = []
1203
+ captured_combined = []
1204
+
1205
+ def capture_input(**kwargs):
1206
+ captured_inputs.append(kwargs)
1207
+
1208
+ def capture_output(result: str):
1209
+ captured_outputs.append(result)
1210
+
1211
+ def capture_combined(name: str, value: int, mock_output: str = None):
1212
+ combined = f"{name}_{value}_{mock_output}"
1213
+ captured_combined.append(combined)
1214
+
1215
+ @veris.stub(
1216
+ return_value="stubbed",
1217
+ input_callback=capture_input,
1218
+ output_callback=capture_output,
1219
+ combined_callback=capture_combined,
1220
+ )
1221
+ def process(name: str, value: int) -> str:
1222
+ return f"{name}:{value}"
1223
+
1224
+ result = process("test", 42)
1225
+ assert result == "stubbed"
1226
+
1227
+ # All callbacks should have been called
1228
+ assert len(captured_inputs) == 1
1229
+ assert captured_inputs[0] == {"name": "test", "value": 42}
1230
+
1231
+ assert len(captured_outputs) == 1
1232
+ assert captured_outputs[0] == "stubbed"
1233
+
1234
+ assert len(captured_combined) == 1
1235
+ assert captured_combined[0] == "test_42_stubbed"
1236
+
1237
+
1238
+ @pytest.mark.asyncio
1239
+ async def test_combined_callback_with_kwargs_accepts_all(simulation_env):
1240
+ """Test combined callback with **kwargs accepts all parameters including mock_output."""
1241
+ captured_data = []
1242
+
1243
+ async def flexible_callback(**kwargs):
1244
+ """Callback that accepts any parameters via **kwargs."""
1245
+ captured_data.append(kwargs)
1246
+
1247
+ @veris.mock(mode="function", combined_callback=flexible_callback)
1248
+ async def multi_param_func(a: int, b: str, c: float = 3.14) -> dict:
1249
+ return {"result": a}
1250
+
1251
+ mock_response = {"result": 999}
1252
+
1253
+ with patch.object(get_api_client(), "post_async", return_value=mock_response):
1254
+ result = await multi_param_func(10, "test", c=2.71)
1255
+ assert result == {"result": 999}
1256
+ # Wait for background tasks to complete
1257
+ await asyncio.sleep(0.01)
1258
+
1259
+ assert len(captured_data) == 1
1260
+ # Should have all input params plus mock_output
1261
+ assert captured_data[0]["a"] == 10
1262
+ assert captured_data[0]["b"] == "test"
1263
+ assert captured_data[0]["c"] == 2.71
1264
+ assert captured_data[0]["mock_output"] == {"result": 999}
1265
+
1266
+
1267
+ def test_combined_callback_user_example(simulation_env):
1268
+ """Test the exact example from the user: add function with multiply_by_result."""
1269
+ multiplication_results = []
1270
+
1271
+ def multiply_by_result(a: int, b: int, d: int = None, mock_output: int = None):
1272
+ """Multiply sum of provided inputs by the mock output."""
1273
+ # Sum the inputs that are not None
1274
+ input_sum = a + b + (d if d is not None else 0)
1275
+ # Multiply by mock output
1276
+ result = input_sum * (mock_output if mock_output is not None else 1)
1277
+ multiplication_results.append(result)
1278
+
1279
+ @veris.mock(mode="function", combined_callback=multiply_by_result)
1280
+ def add(a: int, b: int, c: int = None, d: int = None) -> int:
1281
+ """Add numbers together."""
1282
+ total = a + b
1283
+ if c is not None:
1284
+ total += c
1285
+ if d is not None:
1286
+ total += d
1287
+ return total
1288
+
1289
+ # Mock the API to return 6 as the result
1290
+ mock_response = 6
1291
+
1292
+ with patch.object(get_api_client(), "post", return_value=mock_response):
1293
+ # Call add(1, 2, d=4)
1294
+ # Expected: inputs are 1, 2, 4 (c is None)
1295
+ # Mock returns 6
1296
+ # Combined callback should compute: (1 + 2 + 4) * 6 = 42
1297
+ result = add(1, 2, d=4)
1298
+
1299
+ # The function returns the mocked value
1300
+ assert result == 6
1301
+
1302
+ # The combined callback computed (1+2+4)*6
1303
+ assert len(multiplication_results) == 1
1304
+ assert multiplication_results[0] == 42
@@ -1571,7 +1571,7 @@ wheels = [
1571
1571
 
1572
1572
  [[package]]
1573
1573
  name = "veris-ai"
1574
- version = "1.12.3"
1574
+ version = "1.13.0"
1575
1575
  source = { editable = "." }
1576
1576
  dependencies = [
1577
1577
  { name = "httpx" },
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