veris-ai 0.1.0__tar.gz → 0.2.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.

@@ -0,0 +1,43 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ workflow_dispatch:
5
+
6
+ jobs:
7
+ publish:
8
+ runs-on: ubuntu-latest
9
+ permissions:
10
+ contents: write
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ with:
14
+ fetch-depth: 0
15
+
16
+ - name: Set up Python
17
+ uses: actions/setup-python@v4
18
+ with:
19
+ python-version: '3.13'
20
+
21
+ - name: Install dependencies
22
+ run: |
23
+ curl -LsSf https://astral.sh/uv/install.sh | sh
24
+ uv sync
25
+ uv pip install python-semantic-release
26
+
27
+ - name: Configure Git
28
+ run: |
29
+ git config --global user.name "GitHub Actions"
30
+ git config --global user.email "actions@github.com"
31
+
32
+ - name: Version and Release
33
+ env:
34
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
35
+ PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
36
+ run: |
37
+ uv run semantic-release version
38
+ uv run semantic-release publish
39
+
40
+ - name: Push changes
41
+ run: |
42
+ git push
43
+ git push --tags
@@ -0,0 +1,44 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [ master ]
6
+ pull_request:
7
+ branches: [ master ]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.11", "3.12", "3.13"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Set up Python ${{ matrix.python-version }}
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+
24
+ - name: Install uv
25
+ run: |
26
+ curl -LsSf https://astral.sh/uv/install.sh | sh
27
+
28
+ - name: Install dependencies
29
+ run: |
30
+ uv venv
31
+ uv pip install -e .[dev]
32
+
33
+ - name: Run code quality checks with Ruff
34
+ run: |
35
+ uv run ruff check .
36
+ uv run ruff format --check .
37
+
38
+ - name: Run type checks with mypy
39
+ run: |
40
+ uv run mypy src/veris_ai tests
41
+
42
+ - name: Run tests with pytest
43
+ run: |
44
+ uv run pytest tests/ --cov=veris_ai --cov-report=xml --cov-report=term-missing
@@ -0,0 +1,33 @@
1
+ # CHANGELOG
2
+
3
+
4
+ ## v0.2.0 (2025-04-18)
5
+
6
+ ### Chores
7
+
8
+ - Update changelog for 0.1.1
9
+ ([`24d7139`](https://github.com/veris-ai/veris-python-sdk/commit/24d713943737abcea6394258e5129ae0a55cb869))
10
+
11
+ - Update semantic release config
12
+ ([`6f745e0`](https://github.com/veris-ai/veris-python-sdk/commit/6f745e09263703cfe034ba688b96e4da50759889))
13
+
14
+ ### Continuous Integration
15
+
16
+ - Add workflows for release ([#3](https://github.com/veris-ai/veris-python-sdk/pull/3),
17
+ [`3c160e7`](https://github.com/veris-ai/veris-python-sdk/commit/3c160e7e928ed1efb42825b767084df5d451edb5))
18
+
19
+ - Fix publish ci workflow ([#4](https://github.com/veris-ai/veris-python-sdk/pull/4),
20
+ [`47f23e1`](https://github.com/veris-ai/veris-python-sdk/commit/47f23e19cd04b60ab0ae087b6d70b4748350393c))
21
+
22
+ ### Features
23
+
24
+ - Remove dependencies and use linter ([#2](https://github.com/veris-ai/veris-python-sdk/pull/2),
25
+ [`4b8c43b`](https://github.com/veris-ai/veris-python-sdk/commit/4b8c43b551265ff9c994f53d29a7ef185b7e3286))
26
+
27
+
28
+ ## v0.1.1 (2025-04-17)
29
+
30
+ ### Features
31
+
32
+ - Updates to package for publishing ([#1](https://github.com/veris-ai/veris-python-sdk/pull/1),
33
+ [`c6f460e`](https://github.com/veris-ai/veris-python-sdk/commit/c6f460ea6e2f8472c120370a14f67f1d8c28626c))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: veris-ai
3
- Version: 0.1.0
3
+ Version: 0.2.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
@@ -9,8 +9,6 @@ License-Expression: MIT
9
9
  License-File: LICENSE
10
10
  Requires-Python: >=3.11
11
11
  Requires-Dist: httpx>=0.24.0
12
- Requires-Dist: python-dotenv>=1.0.0
13
- Requires-Dist: typing-extensions>=4.0.0
14
12
  Provides-Extra: dev
15
13
  Requires-Dist: black>=23.7.0; extra == 'dev'
16
14
  Requires-Dist: mypy>=1.5.1; extra == 'dev'
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "veris-ai"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "A Python package for Veris AI tools"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -13,9 +13,7 @@ authors = [
13
13
  { name = "Mehdi Jamei", email = "mehdi@veris.ai" }
14
14
  ]
15
15
  dependencies = [
16
- "typing-extensions>=4.0.0",
17
16
  "httpx>=0.24.0",
18
- "python-dotenv>=1.0.0",
19
17
  ]
20
18
 
21
19
  [project.optional-dependencies]
@@ -97,4 +95,36 @@ known-first-party = ["veris_ai"]
97
95
  convention = "google"
98
96
 
99
97
  [tool.ruff.per-file-ignores]
100
- "tests/*" = ["S101", "D103", "ANN401"]
98
+ "tests/*" = [
99
+ "D", # Ignore documentation requirements
100
+ "ANN", # Ignore type annotation requirements
101
+ "S", # Ignore security warnings
102
+ "ARG", # Ignore unused arguments
103
+ "PLR", # Ignore pylint refactor suggestions
104
+ "N", # Ignore naming conventions
105
+ "T201", # Ignore print statements
106
+ "E", # Ignore style errors
107
+ "F", # Ignore pyflakes warnings
108
+ "B", # Ignore bugbear warnings
109
+ "C4", # Ignore comprehension warnings
110
+ "UP", # Ignore upgrade suggestions
111
+ "SLF", # Ignore self warnings
112
+ "ERA", # Ignore eradicate warnings
113
+ "PD", # Ignore pandas warnings
114
+ "PGH", # Ignore pygrep-hooks
115
+ "PL", # Ignore pylint warnings
116
+ "PT" # Ignore pytest warnings
117
+ ]
118
+
119
+ [tool.semantic_release]
120
+ version_variables = ["pyproject.toml:version"]
121
+ branch = "master"
122
+ changelog_file = "CHANGELOG.md"
123
+ build_command = "uv build"
124
+ dist_path = "dist/"
125
+ upload_to_repository = true
126
+ repository_url = "https://upload.pypi.org/legacy/"
127
+ commit_message = "chore: release v{version}"
128
+ commit_parser = "angular"
129
+ major_on_zero = false
130
+ tag_format = "v{version}"
@@ -3,62 +3,81 @@ import json
3
3
  import logging
4
4
  import os
5
5
  from collections.abc import Callable
6
+ from contextlib import suppress
6
7
  from functools import wraps
7
- from typing import Any, Union, get_type_hints
8
+ from typing import Any, TypeVar, Union, get_args, get_origin, get_type_hints
8
9
 
9
10
  import httpx
10
11
 
11
12
  logger = logging.getLogger(__name__)
12
13
 
14
+ T = TypeVar("T") # Generic type for return value
15
+
16
+
13
17
  class ToolMock:
14
18
  """Class for mocking tool calls."""
15
- def __init__(self):
16
- self.endpoint = os.getenv("VERIS_MOCK_ENDPOINT_URL")
17
- if not self.endpoint:
18
- raise ValueError("VERIS_MOCK_ENDPOINT_URL environment variable is not set")
19
- # Default timeout of 30 seconds
20
- self.timeout = float(os.getenv("VERIS_MOCK_TIMEOUT", "30.0"))
21
19
 
22
- def _convert_to_type(self, value: Any, target_type: type) -> Any: # type: ignore
20
+ def __init__(self) -> None:
21
+ """Initialize the ToolMock class."""
22
+
23
+ def _convert_to_type(self, value: object, target_type: type) -> object:
23
24
  """Convert a value to the specified type."""
24
- if target_type == Any:
25
+ if target_type is Any:
25
26
  return value
26
-
27
+
27
28
  # Handle basic types
28
29
  if target_type in (str, int, float, bool):
29
30
  return target_type(value)
30
-
31
+
31
32
  # Handle List types
32
- if hasattr(target_type, "__origin__") and target_type.__origin__ == list:
33
+ origin = get_origin(target_type)
34
+ if origin is list:
33
35
  if not isinstance(value, list):
34
- raise ValueError(f"Expected list but got {type(value)}")
35
- item_type = target_type.__args__[0]
36
+ error_msg = f"Expected list but got {type(value)}"
37
+ raise ValueError(error_msg)
38
+ item_type = get_args(target_type)[0]
36
39
  return [self._convert_to_type(item, item_type) for item in value]
37
-
40
+
38
41
  # Handle Dict types
39
- if hasattr(target_type, "__origin__") and target_type.__origin__ == dict:
42
+ if origin is dict:
40
43
  if not isinstance(value, dict):
41
- raise ValueError(f"Expected dict but got {type(value)}")
42
- key_type, value_type = target_type.__args__
43
- return {self._convert_to_type(k, key_type): self._convert_to_type(v, value_type)
44
- for k, v in value.items()}
45
-
44
+ error_msg = f"Expected dict but got {type(value)}"
45
+ raise ValueError(error_msg)
46
+ key_type, value_type = get_args(target_type)
47
+ return {
48
+ self._convert_to_type(k, key_type): self._convert_to_type(v, value_type)
49
+ for k, v in value.items()
50
+ }
51
+
46
52
  # Handle Union types
47
- if hasattr(target_type, "__origin__") and target_type.__origin__ == Union:
48
- for possible_type in target_type.__args__:
53
+ if origin is Union:
54
+ error_msg = (
55
+ f"Could not convert {value} to any of the union types {get_args(target_type)}"
56
+ )
57
+ for possible_type in get_args(target_type):
49
58
  try:
50
59
  return self._convert_to_type(value, possible_type)
51
60
  except (ValueError, TypeError):
52
61
  continue
53
- raise ValueError(f"Could not convert {value} to any of the union types {target_type.__args__}") # noqa
54
-
62
+ raise ValueError(error_msg)
63
+
55
64
  # For other types, try direct conversion
56
65
  return target_type(value)
57
66
 
58
67
  def mock(self, func: Callable) -> Callable:
59
68
  """Decorator for mocking tool calls."""
69
+ endpoint = os.getenv("VERIS_MOCK_ENDPOINT_URL")
70
+ if not endpoint:
71
+ error_msg = "VERIS_MOCK_ENDPOINT_URL environment variable is not set"
72
+ raise ValueError(error_msg)
73
+ # Default timeout of 30 seconds
74
+ timeout = float(os.getenv("VERIS_MOCK_TIMEOUT", "30.0"))
75
+
60
76
  @wraps(func)
61
- async def wrapper(*args, **kwargs):
77
+ async def wrapper(
78
+ *args: tuple[object, ...],
79
+ **kwargs: dict[str, object],
80
+ ) -> object:
62
81
  # Check if we're in simulation mode
63
82
  env_mode = os.getenv("ENV", "").lower()
64
83
  if env_mode != "simulation":
@@ -68,25 +87,16 @@ class ToolMock:
68
87
  logger.info(f"Simulating function: {func.__name__}")
69
88
  sig = inspect.signature(func)
70
89
  type_hints = get_type_hints(func)
71
-
90
+
72
91
  # Extract return type object (not just the name)
73
92
  return_type_obj = type_hints.pop("return", Any)
74
-
93
+
75
94
  # Create parameter info
76
95
  params_info = {}
77
96
  bound_args = sig.bind(*args, **kwargs)
78
97
  bound_args.apply_defaults()
79
-
80
- for param_name, param_value in bound_args.arguments.items():
81
- params_info[param_name] = {
82
- "value": param_value,
83
- "type": type_hints.get(param_name, Any).__name__,
84
- }
85
-
86
- # Get function docstring
87
- docstring = inspect.getdoc(func) or ""
88
98
 
89
- ctx = bound_args.arguments.pop('ctx', None)
99
+ ctx = bound_args.arguments.pop("ctx", None)
90
100
  session_id = None
91
101
  if ctx:
92
102
  try:
@@ -94,35 +104,41 @@ class ToolMock:
94
104
  except AttributeError:
95
105
  logger.warning("Cannot get session_id from context.")
96
106
 
107
+ for param_name, param_value in bound_args.arguments.items():
108
+ params_info[param_name] = {
109
+ "value": param_value,
110
+ "type": type_hints.get(param_name, Any).__name__,
111
+ }
112
+
113
+ # Get function docstring
114
+ docstring = inspect.getdoc(func) or ""
97
115
  # Prepare payload
98
116
  payload = {
99
117
  "session_id": session_id,
100
118
  "tool_call": {
101
- 'function_name': func.__name__,
102
- 'parameters': params_info,
103
- 'return_type': return_type_obj.__name__,
104
- 'docstring': docstring,
105
- }
119
+ "function_name": func.__name__,
120
+ "parameters": params_info,
121
+ "return_type": return_type_obj.__name__,
122
+ "docstring": docstring,
123
+ },
106
124
  }
107
125
 
108
126
  # Send request to endpoint with timeout
109
- async with httpx.AsyncClient(timeout=self.timeout) as client:
110
- response = await client.post(self.endpoint, json=payload)
127
+ async with httpx.AsyncClient(timeout=timeout) as client:
128
+ response = await client.post(endpoint, json=payload)
111
129
  response.raise_for_status()
112
130
  mock_result = response.json()["result"]
113
131
  logger.info(f"Mock response: {mock_result}")
114
-
132
+
115
133
  # Parse the mock result if it's a string
116
134
  if isinstance(mock_result, str):
117
- try:
135
+ with suppress(json.JSONDecodeError):
118
136
  mock_result = json.loads(mock_result)
119
- except json.JSONDecodeError:
120
- # If it's not valid JSON, treat it as a raw string
121
- pass
122
-
137
+
123
138
  # Convert the mock result to the expected return type
124
139
  return self._convert_to_type(mock_result, return_type_obj)
125
140
 
126
141
  return wrapper
127
142
 
143
+
128
144
  veris = ToolMock()
@@ -0,0 +1 @@
1
+ """Tests for the veris_ai package."""
@@ -0,0 +1,46 @@
1
+ import os
2
+ from unittest.mock import patch
3
+
4
+ import pytest
5
+
6
+
7
+ class MockContext:
8
+ class RequestContext:
9
+ class LifespanContext:
10
+ def __init__(self):
11
+ self.session_id = "test-session"
12
+
13
+ def __init__(self):
14
+ self.lifespan_context = self.LifespanContext()
15
+
16
+ def __init__(self):
17
+ self.request_context = self.RequestContext()
18
+
19
+
20
+ @pytest.fixture
21
+ def mock_context():
22
+ return MockContext()
23
+
24
+
25
+ @pytest.fixture
26
+ def simulation_env():
27
+ with patch.dict(
28
+ os.environ,
29
+ {
30
+ "VERIS_MOCK_ENDPOINT_URL": "http://test-endpoint",
31
+ "ENV": "simulation",
32
+ },
33
+ ):
34
+ yield
35
+
36
+
37
+ @pytest.fixture
38
+ def production_env():
39
+ with patch.dict(
40
+ os.environ,
41
+ {
42
+ "VERIS_MOCK_ENDPOINT_URL": "http://test-endpoint",
43
+ "ENV": "production",
44
+ },
45
+ ):
46
+ yield
@@ -0,0 +1,223 @@
1
+ import os
2
+ from typing import Any, Union
3
+ from unittest.mock import AsyncMock, Mock, patch
4
+
5
+ import httpx
6
+ import pytest
7
+
8
+ from veris_ai import veris
9
+
10
+
11
+ @pytest.fixture
12
+ def tool_mock():
13
+ return veris
14
+
15
+
16
+ # Test type conversion
17
+ @pytest.mark.parametrize(
18
+ "value,target_type,expected",
19
+ [
20
+ ("42", int, 42),
21
+ (42, str, "42"),
22
+ ("3.14", float, 3.14),
23
+ ("true", bool, True),
24
+ ([1, 2, 3], list[int], [1, 2, 3]),
25
+ (["1", "2"], list[int], [1, 2]),
26
+ ({"a": 1}, dict[str, int], {"a": 1}),
27
+ ("42", Union[int, str], 42),
28
+ ("abc", Union[int, str], "abc"),
29
+ ],
30
+ )
31
+ def test_convert_to_type(tool_mock, value, target_type, expected):
32
+ result = tool_mock._convert_to_type(value, target_type)
33
+ assert result == expected
34
+ assert isinstance(result, type(expected))
35
+
36
+
37
+ def test_convert_to_type_invalid(tool_mock):
38
+ with pytest.raises(ValueError):
39
+ tool_mock._convert_to_type("not a list", list[int])
40
+
41
+
42
+ def test_convert_to_type_dict_invalid(tool_mock):
43
+ with pytest.raises(ValueError, match="Expected dict but got <class 'str'>"):
44
+ tool_mock._convert_to_type("not a dict", dict[str, int])
45
+
46
+
47
+ def test_convert_to_type_union_all_fail(tool_mock):
48
+ with pytest.raises(ValueError, match="Could not convert abc to any of the union types"):
49
+ tool_mock._convert_to_type("abc", Union[int, float])
50
+
51
+
52
+ def test_convert_to_type_custom_type(tool_mock):
53
+ class CustomType:
54
+ def __init__(self, value):
55
+ self.value = value
56
+
57
+ result = tool_mock._convert_to_type("test", CustomType)
58
+ assert isinstance(result, CustomType)
59
+ assert result.value == "test"
60
+
61
+
62
+ # Test mock decorator
63
+ @pytest.mark.asyncio
64
+ async def test_mock_decorator_simulation_mode(simulation_env):
65
+ @veris.mock
66
+ async def test_func(param1: str, param2: int) -> dict[str, Any]:
67
+ return {"result": "real"}
68
+
69
+ mock_response = {"result": {"mocked": True}}
70
+
71
+ with patch("httpx.AsyncClient") as mock_client:
72
+ mock_response_obj = Mock()
73
+ mock_response_obj.json.return_value = mock_response
74
+ mock_response_obj.raise_for_status.return_value = None
75
+
76
+ mock_client.return_value.__aenter__.return_value.post = AsyncMock(
77
+ return_value=mock_response_obj,
78
+ )
79
+
80
+ result = await test_func("test", 42)
81
+ assert result == {"mocked": True}
82
+
83
+
84
+ @pytest.mark.asyncio
85
+ async def test_mock_decorator_production_mode(production_env):
86
+ @veris.mock
87
+ async def test_func(param1: str, param2: int) -> dict:
88
+ return {"result": "real"}
89
+
90
+ result = await test_func("test", 42)
91
+ assert result == {"result": "real"}
92
+
93
+
94
+ @pytest.mark.asyncio
95
+ async def test_mock_with_context(simulation_env, mock_context):
96
+ @veris.mock
97
+ async def test_func(ctx) -> dict:
98
+ return {"result": "real"}
99
+
100
+ mock_response = {"result": {"mocked": True}}
101
+
102
+ with patch("veris_ai.tool_mock.httpx.AsyncClient") as mock_client:
103
+ mock_response_obj = Mock()
104
+ mock_response_obj.json.return_value = mock_response
105
+ mock_response_obj.raise_for_status.return_value = None
106
+
107
+ mock_client.return_value.__aenter__.return_value.post = AsyncMock(
108
+ return_value=mock_response_obj,
109
+ )
110
+
111
+ result = await test_func(mock_context)
112
+ first_call = mock_client.return_value.__aenter__.return_value.post.call_args
113
+ assert (
114
+ first_call.kwargs["json"]["session_id"]
115
+ == mock_context.request_context.lifespan_context.session_id
116
+ )
117
+ assert result == {"mocked": True}
118
+
119
+
120
+ @pytest.mark.asyncio
121
+ async def test_mock_without_context(simulation_env):
122
+ @veris.mock
123
+ async def test_func() -> dict:
124
+ return {"result": "real"}
125
+
126
+ mock_response = {"result": {"mocked": True}}
127
+
128
+ with patch("veris_ai.tool_mock.httpx.AsyncClient") as mock_client:
129
+ mock_response_obj = Mock()
130
+ mock_response_obj.json.return_value = mock_response
131
+ mock_response_obj.raise_for_status.return_value = None
132
+
133
+ mock_client.return_value.__aenter__.return_value.post = AsyncMock(
134
+ return_value=mock_response_obj,
135
+ )
136
+
137
+ result = await test_func()
138
+ first_call = mock_client.return_value.__aenter__.return_value.post.call_args
139
+ assert first_call.kwargs["json"]["session_id"] == None
140
+ assert result == {"mocked": True}
141
+
142
+
143
+ @pytest.mark.asyncio
144
+ async def test_mock_with_malformed_context(simulation_env):
145
+ @veris.mock
146
+ async def test_func(ctx) -> dict:
147
+ return {"result": "real"}
148
+
149
+ mock_response = {"result": {"mocked": True}}
150
+
151
+ with patch("veris_ai.tool_mock.httpx.AsyncClient") as mock_client:
152
+ mock_response_obj = Mock()
153
+ mock_response_obj.json.return_value = mock_response
154
+ mock_response_obj.raise_for_status.return_value = None
155
+
156
+ mock_client.return_value.__aenter__.return_value.post = AsyncMock(
157
+ return_value=mock_response_obj,
158
+ )
159
+
160
+ result = await test_func({"unknown": "unknown"})
161
+ first_call = mock_client.return_value.__aenter__.return_value.post.call_args
162
+ assert first_call.kwargs["json"]["session_id"] == None
163
+ assert result == {"mocked": True}
164
+
165
+
166
+ # Test error handling
167
+ @pytest.mark.asyncio
168
+ async def test_mock_http_error(simulation_env):
169
+ @veris.mock
170
+ async def test_func(param1: str) -> dict:
171
+ return {"result": "real"}
172
+
173
+ with patch("httpx.AsyncClient") as mock_client:
174
+ mock_client.return_value.__aenter__.return_value.post = AsyncMock(
175
+ side_effect=httpx.HTTPError("Mock HTTP Error"),
176
+ )
177
+
178
+ with pytest.raises(httpx.HTTPError):
179
+ await test_func("test")
180
+
181
+
182
+ @pytest.mark.asyncio
183
+ async def test_mock_missing_endpoint():
184
+ with (
185
+ patch.dict(os.environ, {"VERIS_MOCK_ENDPOINT_URL": ""}),
186
+ pytest.raises(ValueError, match="VERIS_MOCK_ENDPOINT_URL environment variable is not set"),
187
+ ):
188
+
189
+ @veris.mock
190
+ async def test_func():
191
+ return {"result": "real"}
192
+
193
+
194
+ @pytest.mark.asyncio
195
+ async def test_mock_invalid_endpoint(simulation_env):
196
+ with patch.dict(os.environ, {"VERIS_MOCK_ENDPOINT_URL": ""}), pytest.raises(ValueError):
197
+
198
+ @veris.mock
199
+ async def test_func():
200
+ return {"result": "real"}
201
+
202
+ await test_func()
203
+
204
+
205
+ @pytest.mark.asyncio
206
+ async def test_mock_string_json_response(simulation_env):
207
+ @veris.mock
208
+ async def test_func() -> dict:
209
+ return {"result": "real"}
210
+
211
+ mock_response = {"result": '{"key": "value"}'}
212
+
213
+ with patch("httpx.AsyncClient") as mock_client:
214
+ mock_response_obj = Mock()
215
+ mock_response_obj.json.return_value = mock_response
216
+ mock_response_obj.raise_for_status.return_value = None
217
+
218
+ mock_client.return_value.__aenter__.return_value.post = AsyncMock(
219
+ return_value=mock_response_obj,
220
+ )
221
+
222
+ result = await test_func()
223
+ assert result == {"key": "value"}