wizelit-sdk 0.1.26__py3-none-any.whl → 0.1.28__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.
@@ -5,4 +5,7 @@ from .agent_wrapper import WizelitAgent
5
5
  from .job import Job
6
6
  from .streaming import LogStreamer
7
7
 
8
- __all__ = ["WizelitAgent", "Job", "LogStreamer"]
8
+ # Backward-compatible alias used by sample MCP servers
9
+ WizelitAgentWrapper = WizelitAgent
10
+
11
+ __all__ = ["WizelitAgent", "WizelitAgentWrapper", "Job", "LogStreamer"]
@@ -8,6 +8,11 @@ from contextvars import ContextVar
8
8
  from fastmcp import FastMCP, Context
9
9
  from fastmcp.dependencies import CurrentContext
10
10
  from wizelit_sdk.agent_wrapper.job import Job
11
+ from wizelit_sdk.agent_wrapper.signature_validation import (
12
+ SignatureValidationError,
13
+ bind_and_validate_arguments,
14
+ ensure_type_hints,
15
+ )
11
16
 
12
17
  if TYPE_CHECKING:
13
18
  from wizelit_sdk.database import DatabaseManager
@@ -191,6 +196,35 @@ class WizelitAgent:
191
196
 
192
197
  new_sig = sig.replace(parameters=params_list)
193
198
 
199
+ # Exclude dependency-injected params from validation/schema
200
+ exclude_args = ["ctx"]
201
+ if has_job_param:
202
+ exclude_args.append("job")
203
+
204
+ # Validate that the user function has explicit type hints
205
+ ensure_type_hints(func, exclude_params=exclude_args)
206
+
207
+ # Validate response_handling schema early to catch drift
208
+ if response_handling is not None:
209
+ allowed_keys = {"mode", "extract_path", "template", "content_type"}
210
+ unknown_keys = set(response_handling.keys()) - allowed_keys
211
+ if unknown_keys:
212
+ raise ValueError(
213
+ f"response_handling for {tool_name} has unsupported keys: {sorted(unknown_keys)}"
214
+ )
215
+
216
+ mode = response_handling.get("mode", "default")
217
+ if mode not in {"direct", "formatted", "default"}:
218
+ raise ValueError(
219
+ f"response_handling.mode for {tool_name} must be one of 'direct', 'formatted', 'default'"
220
+ )
221
+
222
+ content_type = response_handling.get("content_type", "text")
223
+ if content_type not in {"text", "json", "auto"}:
224
+ raise ValueError(
225
+ f"response_handling.content_type for {tool_name} must be one of 'text', 'json', 'auto'"
226
+ )
227
+
194
228
  # Create the wrapper function
195
229
  async def tool_wrapper(*args, **kwargs):
196
230
  """MCP-compliant wrapper with streaming."""
@@ -209,31 +243,14 @@ class WizelitAgent:
209
243
  job = job()
210
244
  # If job is still None, _execute_tool will create it
211
245
 
212
- # Bind all arguments (including positional) to the original function signature
213
- # This ensures parameters are correctly passed even if fast-mcp uses positional args
214
- # Create a signature without 'job' since we've already extracted it
215
- func_sig = inspect.signature(func)
216
- if has_job_param and "job" in func_sig.parameters:
217
- # Remove 'job' from signature for binding since we handle it separately
218
- params_without_job = {
219
- name: param
220
- for name, param in func_sig.parameters.items()
221
- if name != "job"
222
- }
223
- func_sig = func_sig.replace(
224
- parameters=list(params_without_job.values())
225
- )
226
-
227
246
  try:
228
- bound_args = func_sig.bind(*args, **kwargs)
229
- bound_args.apply_defaults()
230
- func_kwargs = bound_args.arguments
231
- except TypeError as e:
232
- # Fallback: if binding fails, use kwargs as-is (shouldn't happen normally)
233
- logging.warning(
234
- f"Failed to bind arguments for {tool_name}: {e}. Args: {args}, Kwargs: {kwargs}"
247
+ func_kwargs = bind_and_validate_arguments(
248
+ func, args, kwargs, exclude_params=exclude_args
235
249
  )
236
- func_kwargs = kwargs
250
+ except SignatureValidationError as exc:
251
+ raise ValueError(
252
+ f"Argument validation failed for {tool_name}: {exc}"
253
+ ) from exc
237
254
 
238
255
  return await self._execute_tool(
239
256
  func, ctx, is_async, is_long_running, tool_name, job, **func_kwargs
@@ -258,10 +275,6 @@ class WizelitAgent:
258
275
 
259
276
  # Register with fast-mcp
260
277
  # Exclude ctx and job from schema generation since they're dependency-injected
261
- exclude_args = ["ctx"]
262
- if has_job_param:
263
- exclude_args.append("job")
264
-
265
278
  # Prepare tool kwargs
266
279
  tool_kwargs = {
267
280
  "description": tool_description,
@@ -0,0 +1,118 @@
1
+ """Common helpers for function signature validation.
2
+
3
+ These helpers are used by the Wizelit MCP wrappers to enforce that tool
4
+ functions declare explicit type hints and that incoming arguments match the
5
+ expected signature at runtime.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import inspect
11
+ from typing import Any, Dict, Iterable, Mapping, Sequence, get_type_hints
12
+
13
+ from typeguard import check_type
14
+
15
+
16
+ class SignatureValidationError(TypeError):
17
+ """Raised when a function signature or call arguments are invalid."""
18
+
19
+
20
+ def _clean_excluded(exclude: Iterable[str] | None) -> set[str]:
21
+ return {name for name in (exclude or [])}
22
+
23
+
24
+ def ensure_type_hints(
25
+ func: Any, *, exclude_params: Iterable[str] | None = None
26
+ ) -> Dict[str, Any]:
27
+ """Ensure a function has type hints for all non-excluded parameters.
28
+
29
+ Args:
30
+ func: The target function.
31
+ exclude_params: Parameter names to ignore (e.g., dependency-injected params).
32
+
33
+ Returns:
34
+ The resolved type hints for the function.
35
+
36
+ Raises:
37
+ SignatureValidationError: If any non-excluded parameter lacks a type hint.
38
+ """
39
+
40
+ exclude = _clean_excluded(exclude_params)
41
+ hints = get_type_hints(func, include_extras=True)
42
+ missing = [
43
+ name
44
+ for name, param in inspect.signature(func).parameters.items()
45
+ if name not in exclude and param.kind != inspect.Parameter.VAR_KEYWORD
46
+ and param.kind != inspect.Parameter.VAR_POSITIONAL
47
+ and name not in hints
48
+ ]
49
+
50
+ if missing:
51
+ raise SignatureValidationError(
52
+ f"Function {func.__name__} is missing type hints for: {', '.join(missing)}"
53
+ )
54
+ return hints
55
+
56
+
57
+ def bind_and_validate_arguments(
58
+ func: Any,
59
+ args: Sequence[Any],
60
+ kwargs: Mapping[str, Any],
61
+ *,
62
+ exclude_params: Iterable[str] | None = None,
63
+ ) -> Dict[str, Any]:
64
+ """Bind args/kwargs to a function signature and validate types.
65
+
66
+ This ensures required parameters are present and values match the annotated
67
+ types. Excluded parameters are ignored for both binding and validation.
68
+
69
+ Args:
70
+ func: The target function.
71
+ args: Positional arguments.
72
+ kwargs: Keyword arguments.
73
+ exclude_params: Parameter names to ignore during validation.
74
+
75
+ Returns:
76
+ A dictionary of bound arguments suitable for calling ``func``.
77
+
78
+ Raises:
79
+ SignatureValidationError: If required parameters are missing or types mismatch.
80
+ """
81
+
82
+ exclude = _clean_excluded(exclude_params)
83
+ sig = inspect.signature(func)
84
+
85
+ # Drop excluded parameters from the signature for binding
86
+ filtered_params = [
87
+ param
88
+ for name, param in sig.parameters.items()
89
+ if name not in exclude
90
+ ]
91
+ filtered_sig = sig.replace(parameters=filtered_params)
92
+
93
+ try:
94
+ bound = filtered_sig.bind(*args, **kwargs)
95
+ bound.apply_defaults()
96
+ except TypeError as exc:
97
+ raise SignatureValidationError(
98
+ f"Invalid arguments for {func.__name__}: {exc}"
99
+ ) from exc
100
+
101
+ hints = get_type_hints(func, include_extras=True)
102
+ for name, value in bound.arguments.items():
103
+ if name in exclude:
104
+ continue
105
+ expected = hints.get(name)
106
+ if expected is None:
107
+ continue
108
+ try:
109
+ # typeguard 4.x uses check_type(value, expected_type)
110
+ # typeguard 2.x uses check_type(argname, value, expected_type)
111
+ check_type(value, expected)
112
+ except TypeError as exc:
113
+ raise SignatureValidationError(
114
+ f"Argument '{name}' to {func.__name__} must be {expected!r}, got {type(value)!r}. "
115
+ f"Value: {value!r}. Original error: {exc}"
116
+ ) from exc
117
+
118
+ return bound.arguments
wizelit_sdk/database.py CHANGED
@@ -18,11 +18,11 @@ class DatabaseManager:
18
18
  """Singleton database manager with connection pooling."""
19
19
 
20
20
  DATABASE_URL: str = ""
21
- POOL_SIZE: int = 5
22
- MAX_OVERFLOW: int = 10
23
- POOL_TIMEOUT: int = 30
24
- POOL_RECYCLE: int = 3600
25
- ECHO_SQL: bool = False
21
+ POOL_SIZE: int = int(os.getenv("DB_POOL_SIZE", "5"))
22
+ MAX_OVERFLOW: int = int(os.getenv("DB_MAX_OVERFLOW", "10"))
23
+ POOL_TIMEOUT: int = int(os.getenv("DB_POOL_TIMEOUT", "30"))
24
+ POOL_RECYCLE: int = int(os.getenv("DB_POOL_RECYCLE", "3600"))
25
+ ECHO_SQL: bool = os.getenv("DB_ECHO_SQL", "false").lower() == "true"
26
26
 
27
27
  _instance = None
28
28
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wizelit-sdk
3
- Version: 0.1.26
3
+ Version: 0.1.28
4
4
  Summary: Wizelit Agent Wrapper - Internal utility package
5
5
  Author-email: Your Name <your.email@company.com>
6
6
  Requires-Python: >=3.10
@@ -8,6 +8,7 @@ Requires-Dist: asyncpg>=0.26.0
8
8
  Requires-Dist: fastmcp>=0.1.0
9
9
  Requires-Dist: redis>=4.5.0
10
10
  Requires-Dist: sqlalchemy>=1.4
11
+ Requires-Dist: typeguard>=4.3.0
11
12
  Requires-Dist: typing-extensions>=4.0.0
12
13
  Provides-Extra: dev
13
14
  Requires-Dist: black>=22.0.0; extra == 'dev'
@@ -0,0 +1,13 @@
1
+ wizelit_sdk/__init__.py,sha256=76Dt-WxNbEJwOCPqu2P9AooVSQoGmc8-CH1fxIWJ5Lo,471
2
+ wizelit_sdk/database.py,sha256=6F2Nyt-sr5U5GkAB9OeSCU-iv2byZBDsg7aXqCl-ntk,4822
3
+ wizelit_sdk/agent_wrapper/__init__.py,sha256=srVcHZfvpf3q8m2nzIST6Ofl2V8Vv5GRtcNXudOLiD8,333
4
+ wizelit_sdk/agent_wrapper/agent_wrapper.py,sha256=LiSYpnKF-RTx_9UnVgW9KqyfTWtilK_Gft1yCuxujus,23204
5
+ wizelit_sdk/agent_wrapper/job.py,sha256=Gk2CyLBQFzIosYuGrqUj6_rZ9kDIO6YLYQ7Pxk6ASuM,13776
6
+ wizelit_sdk/agent_wrapper/signature_validation.py,sha256=Njplwofw36jXoWgBooaZCbwhkf72pR-UoRQ3Q6-biro,3729
7
+ wizelit_sdk/agent_wrapper/streaming.py,sha256=f0VV3IzGAlfQY_cw2OHgxWjvM16Hs42_b700EUX2QpY,6633
8
+ wizelit_sdk/models/__init__.py,sha256=UB7nfHoE6hGcjfzy7w8AmuKVmYrTg9wIgGjG8GWziJ0,143
9
+ wizelit_sdk/models/base.py,sha256=aEPGMZVczKnlWz4Ps99e_xI5TcuSV3DxCigRMU5BnhE,704
10
+ wizelit_sdk/models/job.py,sha256=P68Vb1k77Z_Tha4dE0GzrLPa0LJrnMCxyYMENZyD7Kc,2480
11
+ wizelit_sdk-0.1.28.dist-info/METADATA,sha256=wBU5HBbuQBXynPvt_L2blH-JzArvPpDgU2jkRZ2kOWQ,3297
12
+ wizelit_sdk-0.1.28.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
13
+ wizelit_sdk-0.1.28.dist-info/RECORD,,
@@ -1,12 +0,0 @@
1
- wizelit_sdk/__init__.py,sha256=76Dt-WxNbEJwOCPqu2P9AooVSQoGmc8-CH1fxIWJ5Lo,471
2
- wizelit_sdk/database.py,sha256=39Vu5A8CV7u7ItIwRgrkAugD9UqNbG5vQrCsMOgTamc,4631
3
- wizelit_sdk/agent_wrapper/__init__.py,sha256=OAF36jAmI0A3ByppsYMzJDHeDG9X0Ac4LbFzNp0b7jw,219
4
- wizelit_sdk/agent_wrapper/agent_wrapper.py,sha256=e9KCQSpC7hox3q6sqzhOLDIq37btw7IT4ejeuljc6Gk,22742
5
- wizelit_sdk/agent_wrapper/job.py,sha256=Gk2CyLBQFzIosYuGrqUj6_rZ9kDIO6YLYQ7Pxk6ASuM,13776
6
- wizelit_sdk/agent_wrapper/streaming.py,sha256=f0VV3IzGAlfQY_cw2OHgxWjvM16Hs42_b700EUX2QpY,6633
7
- wizelit_sdk/models/__init__.py,sha256=UB7nfHoE6hGcjfzy7w8AmuKVmYrTg9wIgGjG8GWziJ0,143
8
- wizelit_sdk/models/base.py,sha256=aEPGMZVczKnlWz4Ps99e_xI5TcuSV3DxCigRMU5BnhE,704
9
- wizelit_sdk/models/job.py,sha256=P68Vb1k77Z_Tha4dE0GzrLPa0LJrnMCxyYMENZyD7Kc,2480
10
- wizelit_sdk-0.1.26.dist-info/METADATA,sha256=0QpbkJ8Z5Vl-2s7xrYHoTrutSAMUgQol9JNr9fgXkgo,3265
11
- wizelit_sdk-0.1.26.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
12
- wizelit_sdk-0.1.26.dist-info/RECORD,,