veris-ai 0.2.0__py3-none-any.whl → 1.0.0__py3-none-any.whl

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.

veris_ai/tool_mock.py CHANGED
@@ -4,65 +4,91 @@ import logging
4
4
  import os
5
5
  from collections.abc import Callable
6
6
  from contextlib import suppress
7
+ from contextvars import ContextVar
7
8
  from functools import wraps
8
- from typing import Any, TypeVar, Union, get_args, get_origin, get_type_hints
9
+ from typing import (
10
+ Any,
11
+ TypeVar,
12
+ get_type_hints,
13
+ )
9
14
 
10
15
  import httpx
11
16
 
17
+ from veris_ai.utils import convert_to_type
18
+
12
19
  logger = logging.getLogger(__name__)
13
20
 
14
- T = TypeVar("T") # Generic type for return value
21
+ T = TypeVar("T")
22
+
23
+ # Context variable to store session_id for each call
24
+ _session_id_context: ContextVar[str | None] = ContextVar("veris_session_id", default=None)
15
25
 
16
26
 
17
- class ToolMock:
27
+ class VerisSDK:
18
28
  """Class for mocking tool calls."""
19
29
 
20
30
  def __init__(self) -> None:
21
31
  """Initialize the ToolMock class."""
22
-
23
- def _convert_to_type(self, value: object, target_type: type) -> object:
24
- """Convert a value to the specified type."""
25
- if target_type is Any:
26
- return value
27
-
28
- # Handle basic types
29
- if target_type in (str, int, float, bool):
30
- return target_type(value)
31
-
32
- # Handle List types
33
- origin = get_origin(target_type)
34
- if origin is list:
35
- if not isinstance(value, list):
36
- error_msg = f"Expected list but got {type(value)}"
37
- raise ValueError(error_msg)
38
- item_type = get_args(target_type)[0]
39
- return [self._convert_to_type(item, item_type) for item in value]
40
-
41
- # Handle Dict types
42
- if origin is dict:
43
- if not isinstance(value, dict):
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
-
52
- # Handle Union types
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):
58
- try:
59
- return self._convert_to_type(value, possible_type)
60
- except (ValueError, TypeError):
61
- continue
62
- raise ValueError(error_msg)
63
-
64
- # For other types, try direct conversion
65
- return target_type(value)
32
+ self._mcp = None
33
+
34
+ @property
35
+ def session_id(self) -> str | None:
36
+ """Get the session_id from context variable."""
37
+ return _session_id_context.get()
38
+
39
+ def set_session_id(self, session_id: str) -> None:
40
+ """Set the session_id in context variable."""
41
+ _session_id_context.set(session_id)
42
+ logger.info(f"Session ID set to {session_id}")
43
+
44
+ def clear_session_id(self) -> None:
45
+ """Clear the session_id from context variable."""
46
+ _session_id_context.set(None)
47
+ logger.info("Session ID cleared")
48
+
49
+ @property
50
+ def fastapi_mcp(self) -> Any | None: # noqa: ANN401
51
+ """Get the FastAPI MCP server."""
52
+ return self._mcp
53
+
54
+ def set_fastapi_mcp(self, **params_dict: Any) -> None: # noqa: ANN401
55
+ """Set the FastAPI MCP server."""
56
+ from fastapi import Depends, Request # noqa: PLC0415
57
+ from fastapi.security import OAuth2PasswordBearer # noqa: PLC0415
58
+ from fastapi_mcp import ( # type: ignore[import-untyped] # noqa: PLC0415
59
+ AuthConfig,
60
+ FastApiMCP,
61
+ )
62
+
63
+ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
64
+
65
+ async def authenticate_request(
66
+ _: Request,
67
+ token: str = Depends(oauth2_scheme), # noqa: ARG001
68
+ ) -> None:
69
+ self.set_session_id(token)
70
+
71
+ # Create auth config with dependencies
72
+ auth_config = AuthConfig(
73
+ dependencies=[Depends(authenticate_request)],
74
+ )
75
+
76
+ # Merge the provided params with our auth config
77
+ if "auth_config" in params_dict:
78
+ # Merge the provided auth config with our dependencies
79
+ provided_auth_config = params_dict.pop("auth_config")
80
+ if provided_auth_config.dependencies:
81
+ auth_config.dependencies.extend(provided_auth_config.dependencies)
82
+ # Copy other auth config properties if they exist
83
+ for field, value in provided_auth_config.model_dump(exclude_none=True).items():
84
+ if field != "dependencies" and hasattr(auth_config, field):
85
+ setattr(auth_config, field, value)
86
+
87
+ # Create the FastApiMCP instance with merged parameters
88
+ self._mcp = FastApiMCP(
89
+ auth_config=auth_config,
90
+ **params_dict,
91
+ )
66
92
 
67
93
  def mock(self, func: Callable) -> Callable:
68
94
  """Decorator for mocking tool calls."""
@@ -83,7 +109,6 @@ class ToolMock:
83
109
  if env_mode != "simulation":
84
110
  # If not in simulation mode, execute the original function
85
111
  return await func(*args, **kwargs)
86
-
87
112
  logger.info(f"Simulating function: {func.__name__}")
88
113
  sig = inspect.signature(func)
89
114
  type_hints = get_type_hints(func)
@@ -96,14 +121,6 @@ class ToolMock:
96
121
  bound_args = sig.bind(*args, **kwargs)
97
122
  bound_args.apply_defaults()
98
123
 
99
- ctx = bound_args.arguments.pop("ctx", None)
100
- session_id = None
101
- if ctx:
102
- try:
103
- session_id = ctx.request_context.lifespan_context.session_id
104
- except AttributeError:
105
- logger.warning("Cannot get session_id from context.")
106
-
107
124
  for param_name, param_value in bound_args.arguments.items():
108
125
  params_info[param_name] = {
109
126
  "value": param_value,
@@ -114,7 +131,7 @@ class ToolMock:
114
131
  docstring = inspect.getdoc(func) or ""
115
132
  # Prepare payload
116
133
  payload = {
117
- "session_id": session_id,
134
+ "session_id": self.session_id,
118
135
  "tool_call": {
119
136
  "function_name": func.__name__,
120
137
  "parameters": params_info,
@@ -130,15 +147,15 @@ class ToolMock:
130
147
  mock_result = response.json()["result"]
131
148
  logger.info(f"Mock response: {mock_result}")
132
149
 
133
- # Parse the mock result if it's a string
134
- if isinstance(mock_result, str):
135
- with suppress(json.JSONDecodeError):
136
- mock_result = json.loads(mock_result)
150
+ # Parse the mock result if it's a string
151
+ if isinstance(mock_result, str):
152
+ with suppress(json.JSONDecodeError):
153
+ mock_result = json.loads(mock_result)
137
154
 
138
- # Convert the mock result to the expected return type
139
- return self._convert_to_type(mock_result, return_type_obj)
155
+ # Convert the mock result to the expected return type
156
+ return convert_to_type(mock_result, return_type_obj)
140
157
 
141
158
  return wrapper
142
159
 
143
160
 
144
- veris = ToolMock()
161
+ veris = VerisSDK()
veris_ai/utils.py ADDED
@@ -0,0 +1,71 @@
1
+ from contextlib import suppress
2
+ from typing import Any, Union, get_args, get_origin
3
+
4
+
5
+ def convert_to_type(value: object, target_type: type) -> object:
6
+ """Convert a value to the specified type."""
7
+ # Special case: Any type returns value as-is
8
+ if target_type is Any:
9
+ return value
10
+
11
+ origin = get_origin(target_type)
12
+
13
+ # Define conversion strategies for different type origins
14
+ converters = {
15
+ list: _convert_list,
16
+ dict: _convert_dict,
17
+ Union: _convert_union,
18
+ }
19
+
20
+ # Use appropriate converter based on origin
21
+ if origin in converters:
22
+ return converters[origin](value, target_type)
23
+
24
+ # Handle primitives and custom types
25
+ return _convert_simple_type(value, target_type)
26
+
27
+
28
+ def _convert_list(value: object, target_type: type) -> list:
29
+ """Convert a value to a typed list."""
30
+ if not isinstance(value, list):
31
+ error_msg = f"Expected list but got {type(value)}"
32
+ raise ValueError(error_msg)
33
+
34
+ item_type = get_args(target_type)[0]
35
+ return [convert_to_type(item, item_type) for item in value]
36
+
37
+
38
+ def _convert_dict(value: object, target_type: type) -> dict:
39
+ """Convert a value to a typed dict."""
40
+ if not isinstance(value, dict):
41
+ error_msg = f"Expected dict but got {type(value)}"
42
+ raise ValueError(error_msg)
43
+
44
+ key_type, value_type = get_args(target_type)
45
+ return {convert_to_type(k, key_type): convert_to_type(v, value_type) for k, v in value.items()}
46
+
47
+
48
+ def _convert_union(value: object, target_type: type) -> object:
49
+ """Try to convert value to one of the union types."""
50
+ union_types = get_args(target_type)
51
+
52
+ for possible_type in union_types:
53
+ with suppress(ValueError, TypeError):
54
+ return convert_to_type(value, possible_type)
55
+
56
+ error_msg = f"Could not convert {value} to any of the union types {union_types}"
57
+ raise ValueError(error_msg)
58
+
59
+
60
+ def _convert_simple_type(value: object, target_type: type) -> object:
61
+ """Convert to primitive or custom types."""
62
+ # Primitive types
63
+ if target_type in (str, int, float, bool):
64
+ return target_type(value)
65
+
66
+ # Custom types - try kwargs for dicts, then direct instantiation
67
+ if isinstance(value, dict):
68
+ with suppress(TypeError):
69
+ return target_type(**value)
70
+
71
+ return target_type(value)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: veris-ai
3
- Version: 0.2.0
3
+ Version: 1.0.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
@@ -17,11 +17,14 @@ Requires-Dist: pytest-asyncio>=0.21.1; extra == 'dev'
17
17
  Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
18
18
  Requires-Dist: pytest>=7.4.0; extra == 'dev'
19
19
  Requires-Dist: ruff>=0.11.4; extra == 'dev'
20
+ Provides-Extra: fastapi
21
+ Requires-Dist: fastapi; extra == 'fastapi'
22
+ Requires-Dist: fastapi-mcp; extra == 'fastapi'
20
23
  Description-Content-Type: text/markdown
21
24
 
22
25
  # Veris AI Python SDK
23
26
 
24
- A Python package for Veris AI tools with simulation capabilities.
27
+ A Python package for Veris AI tools with simulation capabilities and FastAPI MCP (Model Context Protocol) integration.
25
28
 
26
29
  ## Installation
27
30
 
@@ -33,6 +36,9 @@ uv add veris-ai
33
36
 
34
37
  # Install with development dependencies
35
38
  uv add "veris-ai[dev]"
39
+
40
+ # Install with FastAPI MCP integration
41
+ uv add "veris-ai[fastapi]"
36
42
  ```
37
43
 
38
44
  ## Environment Setup
@@ -93,6 +99,78 @@ When `ENV=simulation` is set, the decorator will:
93
99
 
94
100
  When not in simulation mode, the original function will be executed normally.
95
101
 
102
+ ## FastAPI MCP Integration
103
+
104
+ The SDK provides integration with FastAPI applications through the Model Context Protocol (MCP). This allows your FastAPI endpoints to be exposed as MCP tools that can be used by AI agents.
105
+
106
+ ```python
107
+ from fastapi import FastAPI
108
+ from veris_ai import veris
109
+
110
+ app = FastAPI()
111
+
112
+ # Set up FastAPI MCP with automatic session handling
113
+ veris.set_fastapi_mcp(
114
+ fastapi=app,
115
+ name="My API Server",
116
+ description="My FastAPI application exposed as MCP tools",
117
+ describe_all_responses=True,
118
+ include_operations=["get_users", "create_user"],
119
+ exclude_tags=["internal"]
120
+ )
121
+
122
+ # The MCP server is now available at veris.fastapi_mcp
123
+ mcp_server = veris.fastapi_mcp
124
+ ```
125
+
126
+ ### Configuration Options
127
+
128
+ The `set_fastapi_mcp()` method accepts the following parameters:
129
+
130
+ - **fastapi** (required): Your FastAPI application instance
131
+ - **name**: Custom name for the MCP server (defaults to app.title)
132
+ - **description**: Custom description (defaults to app.description)
133
+ - **describe_all_responses**: Whether to include all response schemas in tool descriptions
134
+ - **describe_full_response_schema**: Whether to include full JSON schema for responses
135
+ - **http_client**: Optional custom httpx.AsyncClient instance
136
+ - **include_operations**: List of operation IDs to include as MCP tools
137
+ - **exclude_operations**: List of operation IDs to exclude (can't use with include_operations)
138
+ - **include_tags**: List of tags to include as MCP tools
139
+ - **exclude_tags**: List of tags to exclude (can't use with include_tags)
140
+ - **auth_config**: Optional FastAPI MCP AuthConfig for custom authentication
141
+
142
+ ### Session Management
143
+
144
+ The SDK automatically handles session management through OAuth2 authentication:
145
+ - Session IDs are extracted from bearer tokens
146
+ - Context is maintained across API calls
147
+ - Sessions can be managed programmatically:
148
+
149
+ ```python
150
+ # Get current session ID
151
+ session_id = veris.session_id
152
+
153
+ # Set a new session ID
154
+ veris.set_session_id("new-session-123")
155
+
156
+ # Clear the session
157
+ veris.clear_session_id()
158
+ ```
159
+
160
+ ## Project Structure
161
+
162
+ ```
163
+ src/veris_ai/
164
+ ├── __init__.py # Main entry point, exports the veris instance
165
+ ├── tool_mock.py # VerisSDK class with @veris.mock() decorator
166
+ └── utils.py # Type conversion utilities
167
+
168
+ tests/
169
+ ├── conftest.py # Pytest fixtures
170
+ ├── test_tool_mock.py # Tests for VerisSDK functionality
171
+ └── test_utils.py # Tests for type conversion utilities
172
+ ```
173
+
96
174
  ## Development
97
175
 
98
176
  This project uses `pyproject.toml` for dependency management and `uv` for package installation.
@@ -121,16 +199,40 @@ This project uses [Ruff](https://github.com/charliermarsh/ruff) for linting and
121
199
  To run Ruff:
122
200
 
123
201
  ```bash
202
+ # Lint code
124
203
  ruff check .
204
+
205
+ # Auto-fix linting issues
206
+ ruff check --fix .
207
+
208
+ # Format code
209
+ ruff format .
210
+
211
+ # Check formatting only
212
+ ruff format --check .
125
213
  ```
126
214
 
127
- To automatically fix issues:
215
+ The Ruff configuration is defined in `pyproject.toml` under the `[tool.ruff]` section.
216
+
217
+ ### Running Tests
128
218
 
129
219
  ```bash
130
- ruff check --fix .
220
+ # Run all tests with coverage
221
+ pytest tests/ --cov=veris_ai --cov-report=xml --cov-report=term-missing
222
+
223
+ # Run specific test file
224
+ pytest tests/test_tool_mock.py
225
+
226
+ # Run tests with verbose output
227
+ pytest -v tests/
131
228
  ```
132
229
 
133
- The Ruff configuration is defined in `pyproject.toml` under the `[tool.ruff]` section.
230
+ ### Type Checking
231
+
232
+ ```bash
233
+ # Run static type checking
234
+ mypy src/veris_ai tests
235
+ ```
134
236
 
135
237
  ## License
136
238
 
@@ -0,0 +1,7 @@
1
+ veris_ai/__init__.py,sha256=7mEfVmqxQCn5FZN_1ua3iykCB1NUeJ4RfVXaAzdp4Hw,101
2
+ veris_ai/tool_mock.py,sha256=Yr702kl9ggdRUnR_iL37UXFjz-drvQqVpp7JdT_qlb4,5665
3
+ veris_ai/utils.py,sha256=-r4FZajHfrcjB0rhluQl2KDS68ER9pqHp8g_pmB1qAE,2322
4
+ veris_ai-1.0.0.dist-info/METADATA,sha256=mnB4E-ZMjyfrxiVVM2BQ27Ie7SDd-AZK-LuVjHj0cjo,6503
5
+ veris_ai-1.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
6
+ veris_ai-1.0.0.dist-info/licenses/LICENSE,sha256=2g4i20atAgtD5einaKzhQrIB-JrPhyQgD3bC0wkHcCI,1065
7
+ veris_ai-1.0.0.dist-info/RECORD,,
@@ -1,6 +0,0 @@
1
- veris_ai/__init__.py,sha256=7mEfVmqxQCn5FZN_1ua3iykCB1NUeJ4RfVXaAzdp4Hw,101
2
- veris_ai/tool_mock.py,sha256=7qFM35lz10k5i2X-XQVYcuFNF0bczEnHTr0vtineQrM,5172
3
- veris_ai-0.2.0.dist-info/METADATA,sha256=rD-TyXPbDZfJcQCtNuUUA4KGYD6PAFXQIhB5SL1lyNg,3419
4
- veris_ai-0.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
5
- veris_ai-0.2.0.dist-info/licenses/LICENSE,sha256=2g4i20atAgtD5einaKzhQrIB-JrPhyQgD3bC0wkHcCI,1065
6
- veris_ai-0.2.0.dist-info/RECORD,,