schemez 1.4.0__py3-none-any.whl → 1.4.4__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 schemez might be problematic. Click here for more details.

@@ -2,11 +2,9 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from dataclasses import dataclass
5
+ from dataclasses import dataclass, field
6
6
  import inspect
7
- from typing import TYPE_CHECKING, Any
8
-
9
- from pydantic_ai import RunContext
7
+ from typing import TYPE_CHECKING, Any, get_origin
10
8
 
11
9
  from schemez import create_schema
12
10
  from schemez.code_generation.route_helpers import (
@@ -38,22 +36,29 @@ TYPE_MAP = {
38
36
  class ToolCodeGenerator:
39
37
  """Generates code artifacts for a single tool."""
40
38
 
41
- schema: OpenAIFunctionTool
42
- """Schema of the tool."""
43
-
44
39
  callable: Callable
45
40
  """Tool to generate code for."""
46
41
 
42
+ schema: OpenAIFunctionTool
43
+ """Schema of the tool."""
44
+
47
45
  name_override: str | None = None
48
- """Name of the tool."""
46
+ """Name override for the function to generate code for."""
47
+
48
+ exclude_types: list[type] = field(default_factory=list)
49
+ """Exclude parameters from generated code (like context types)."""
49
50
 
50
51
  @classmethod
51
- def from_callable(cls, fn: Callable) -> ToolCodeGenerator:
52
+ def from_callable(
53
+ cls,
54
+ fn: Callable,
55
+ exclude_types: list[type] | None = None,
56
+ ) -> ToolCodeGenerator:
52
57
  """Create a ToolCodeGenerator from a Tool."""
53
58
  schema = create_schema(fn).model_dump_openai()
54
59
  schema["function"]["name"] = fn.__name__
55
60
  schema["function"]["description"] = fn.__doc__ or ""
56
- return cls(schema=schema, callable=callable)
61
+ return cls(schema=schema, callable=fn, exclude_types=exclude_types or [])
57
62
 
58
63
  @property
59
64
  def name(self) -> str:
@@ -162,7 +167,7 @@ class ToolCodeGenerator:
162
167
  self.callable
163
168
  )
164
169
 
165
- def _is_context_parameter(self, param_name: str) -> bool: # noqa: PLR0911
170
+ def _is_context_parameter(self, param_name: str) -> bool:
166
171
  """Check if a parameter is a context parameter that should be hidden."""
167
172
  try:
168
173
  sig = self._get_callable_signature()
@@ -173,26 +178,49 @@ class ToolCodeGenerator:
173
178
  if param.annotation == inspect.Parameter.empty:
174
179
  return False
175
180
 
176
- # Check if parameter is RunContext or AgentContext
177
181
  annotation = param.annotation
178
- annotation_str = str(annotation)
179
182
 
180
- # Handle RunContext (including parameterized like RunContext[None])
181
- if annotation is RunContext:
182
- return True
183
+ for typ in self.exclude_types:
184
+ if self._types_match(annotation, typ):
185
+ return True
186
+ except Exception: # noqa: BLE001
187
+ pass
183
188
 
184
- # Check for parameterized RunContext using string matching
185
- if "RunContext" in annotation_str:
186
- return True
189
+ return False
187
190
 
188
- # Handle AgentContext
189
- if hasattr(annotation, "__name__") and annotation.__name__ == "AgentContext":
191
+ def _types_match(self, annotation: Any, exclude_type: type) -> bool:
192
+ """Check if annotation matches exclude_type using various strategies."""
193
+ try:
194
+ # Direct type match
195
+ if annotation is exclude_type:
190
196
  return True
191
197
 
192
- # Check for AgentContext in string representation
193
- if "AgentContext" in annotation_str:
198
+ # Handle generic types - get origin for comparison
199
+ origin_annotation = get_origin(annotation)
200
+ if origin_annotation is exclude_type:
194
201
  return True
195
202
 
203
+ # String-based comparison for forward references and __future__.annotations
204
+ annotation_str = str(annotation)
205
+ exclude_type_name = exclude_type.__name__
206
+ exclude_type_full_name = f"{exclude_type.__module__}.{exclude_type.__name__}"
207
+
208
+ # Check various string representations
209
+ if (
210
+ exclude_type_name in annotation_str
211
+ or exclude_type_full_name in annotation_str
212
+ ):
213
+ # Be more specific to avoid false positives
214
+ # Check if it's the exact type name, not just a substring
215
+ import re
216
+
217
+ patterns = [
218
+ rf"\b{re.escape(exclude_type_name)}\b",
219
+ rf"\b{re.escape(exclude_type_full_name)}\b",
220
+ ]
221
+ if any(re.search(pattern, annotation_str) for pattern in patterns):
222
+ return True
223
+
196
224
  except Exception: # noqa: BLE001
197
225
  pass
198
226
 
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import contextlib
6
6
  from dataclasses import dataclass
7
+ import inspect
7
8
  from typing import TYPE_CHECKING, Any
8
9
 
9
10
  from schemez.code_generation.namespace_callable import NamespaceCallable
@@ -56,6 +57,7 @@ class ToolsetCodeGenerator:
56
57
  callables: Sequence[Callable],
57
58
  include_signatures: bool = True,
58
59
  include_docstrings: bool = True,
60
+ exclude_types: list[type] | None = None,
59
61
  ) -> ToolsetCodeGenerator:
60
62
  """Create a ToolsetCodeGenerator from a sequence of Tools.
61
63
 
@@ -63,11 +65,16 @@ class ToolsetCodeGenerator:
63
65
  callables: Callables to generate code for
64
66
  include_signatures: Include function signatures in documentation
65
67
  include_docstrings: Include function docstrings in documentation
68
+ exclude_types: Parameter Types to exclude from the generated code
69
+ Often used for context parameters.
66
70
 
67
71
  Returns:
68
72
  ToolsetCodeGenerator instance
69
73
  """
70
- generators = [ToolCodeGenerator.from_callable(i) for i in callables]
74
+ generators = [
75
+ ToolCodeGenerator.from_callable(i, exclude_types=exclude_types)
76
+ for i in callables
77
+ ]
71
78
  return cls(generators, include_signatures, include_docstrings)
72
79
 
73
80
  def generate_tool_description(self) -> str:
@@ -101,6 +108,13 @@ class ToolsetCodeGenerator:
101
108
  indented_desc = " " + generator.callable.__doc__.replace(
102
109
  "\n", "\n "
103
110
  )
111
+
112
+ # Add warning for async functions without proper return type hints
113
+ if inspect.iscoroutinefunction(generator.callable):
114
+ sig = inspect.signature(generator.callable)
115
+ if sig.return_annotation == inspect.Signature.empty:
116
+ indented_desc += "\n \n Note: This async function should explicitly return a value." # noqa: E501
117
+
104
118
  parts.append(f' """{indented_desc}"""')
105
119
  parts.append("")
106
120
 
@@ -142,9 +156,11 @@ class ToolsetCodeGenerator:
142
156
 
143
157
 
144
158
  if __name__ == "__main__":
145
- import webbrowser
146
159
 
147
- generator = ToolsetCodeGenerator.from_callables([webbrowser.open])
160
+ async def no_annotations_func(test):
161
+ pass
162
+
163
+ generator = ToolsetCodeGenerator.from_callables([no_annotations_func])
148
164
  models = generator.generate_return_models()
149
165
  print(models)
150
166
  namespace = generator.generate_execution_namespace()
schemez/executable.py CHANGED
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  from collections.abc import AsyncIterator, Callable # noqa: TC003
5
- from typing import TYPE_CHECKING, Any, TypeVar, overload
5
+ from typing import TYPE_CHECKING, Any, TypeVar, assert_never, overload
6
6
 
7
7
  from schemez.functionschema import FunctionType, create_schema
8
8
 
@@ -34,8 +34,11 @@ class ExecutableFunction[T_co]:
34
34
  schema: OpenAI function schema
35
35
  func: The actual function to execute
36
36
  """
37
+ from schemez.functionschema import _determine_function_type
38
+
37
39
  self.schema = schema
38
40
  self.func = func
41
+ self.function_type = _determine_function_type(self.func)
39
42
 
40
43
  def run(self, *args: Any, **kwargs: Any) -> T_co | list[T_co]: # noqa: PLR0911
41
44
  """Run the function synchronously.
@@ -47,7 +50,7 @@ class ExecutableFunction[T_co]:
47
50
  Returns:
48
51
  Either a single result or list of results for generators
49
52
  """
50
- match self.schema.function_type:
53
+ match self.function_type:
51
54
  case FunctionType.SYNC:
52
55
  return self.func(*args, **kwargs) # type: ignore
53
56
  case FunctionType.ASYNC:
@@ -86,9 +89,8 @@ class ExecutableFunction[T_co]:
86
89
  return loop.run_until_complete(
87
90
  self._collect_async_gen(*args, **kwargs),
88
91
  )
89
- case _:
90
- msg = f"Unknown function type: {self.schema.function_type}"
91
- raise ValueError(msg)
92
+ case _ as unreachable:
93
+ assert_never(unreachable)
92
94
 
93
95
  async def _collect_async_gen(self, *args: Any, **kwargs: Any) -> list[T_co]:
94
96
  """Collect async generator results into a list.
@@ -115,7 +117,7 @@ class ExecutableFunction[T_co]:
115
117
  Raises:
116
118
  ValueError: If the function type is unknown
117
119
  """
118
- match self.schema.function_type:
120
+ match self.function_type:
119
121
  case FunctionType.SYNC:
120
122
  return self.func(*args, **kwargs) # type: ignore
121
123
  case FunctionType.ASYNC:
@@ -125,7 +127,7 @@ class ExecutableFunction[T_co]:
125
127
  case FunctionType.ASYNC_GENERATOR:
126
128
  return [x async for x in self.func(*args, **kwargs)] # type: ignore
127
129
  case _:
128
- msg = f"Unknown function type: {self.schema.function_type}"
130
+ msg = f"Unknown function type: {self.function_type}"
129
131
  raise ValueError(msg)
130
132
 
131
133
  async def astream(self, *args: Any, **kwargs: Any) -> AsyncIterator[T_co]:
@@ -141,7 +143,7 @@ class ExecutableFunction[T_co]:
141
143
  Raises:
142
144
  ValueError: If the function type is unknown
143
145
  """
144
- match self.schema.function_type:
146
+ match self.function_type:
145
147
  case FunctionType.SYNC_GENERATOR:
146
148
  for x in self.func(*args, **kwargs): # type: ignore
147
149
  yield x
@@ -153,7 +155,7 @@ class ExecutableFunction[T_co]:
153
155
  case FunctionType.ASYNC:
154
156
  yield await self.func(*args, **kwargs) # type: ignore
155
157
  case _:
156
- msg = f"Unknown function type: {self.schema.function_type}"
158
+ msg = f"Unknown function type: {self.function_type}"
157
159
  raise ValueError(msg)
158
160
 
159
161
 
schemez/functionschema.py CHANGED
@@ -23,11 +23,7 @@ import docstring_parser
23
23
  import pydantic
24
24
 
25
25
  from schemez import log
26
- from schemez.typedefs import (
27
- OpenAIFunctionDefinition,
28
- OpenAIFunctionTool,
29
- ToolParameters,
30
- )
26
+ from schemez.typedefs import OpenAIFunctionDefinition, OpenAIFunctionTool, ToolParameters
31
27
 
32
28
 
33
29
  if typing.TYPE_CHECKING:
@@ -77,38 +73,22 @@ class FunctionSchema(pydantic.BaseModel):
77
73
  """The name of the function as it will be presented to the OpenAI API."""
78
74
 
79
75
  description: str | None = None
80
- """
81
- Optional description of what the function does. This helps the AI understand
82
- when and how to use the function.
83
- """
76
+ """Optional description of what the function does."""
84
77
 
85
78
  parameters: ToolParameters = pydantic.Field(
86
79
  default_factory=lambda: ToolParameters(type="object", properties={}),
87
80
  )
88
- """
89
- JSON Schema object describing the function's parameters. Contains type information,
90
- descriptions, and constraints for each parameter.
91
- """
81
+ """JSON Schema object describing the function's parameters."""
92
82
 
93
83
  required: list[str] = pydantic.Field(default_factory=list)
94
84
  """
95
85
  List of parameter names that are required (do not have default values).
96
- These parameters must be provided when calling the function.
97
86
  """
98
87
 
99
88
  returns: dict[str, Any] = pydantic.Field(
100
89
  default_factory=lambda: {"type": "object"},
101
90
  )
102
- """
103
- JSON Schema object describing the function's return type. Used for type checking
104
- and documentation purposes.
105
- """
106
-
107
- function_type: FunctionType = FunctionType.SYNC
108
- """
109
- The execution pattern of the function (sync, async, generator, or async generator).
110
- Used to determine how to properly invoke the function.
111
- """
91
+ """JSON Schema object describing the function's return type."""
112
92
 
113
93
  model_config = pydantic.ConfigDict(frozen=True)
114
94
 
@@ -377,7 +357,6 @@ class FunctionSchema(pydantic.BaseModel):
377
357
  parameters=parameters,
378
358
  required=required,
379
359
  returns={"type": "object"},
380
- function_type=FunctionType.SYNC,
381
360
  )
382
361
 
383
362
 
@@ -647,22 +626,26 @@ def _determine_function_type(func: Callable[..., Any]) -> FunctionType:
647
626
  def create_schema(
648
627
  func: Callable[..., Any],
649
628
  name_override: str | None = None,
629
+ description_override: str | None = None,
650
630
  ) -> FunctionSchema:
651
631
  """Create an OpenAI function schema from a Python function.
652
632
 
633
+ If an iterator is passed, the schema return type is a list of the iterator's
634
+ element type.
635
+ Variable arguments (*args) and keyword arguments (**kwargs) are not
636
+ supported in OpenAI function schemas and will be ignored with a warning.
637
+
653
638
  Args:
654
639
  func: Function to create schema for
655
640
  name_override: Optional name override (otherwise the function name)
641
+ description_override: Optional description override
642
+ (otherwise the function docstring)
656
643
 
657
644
  Returns:
658
645
  Schema representing the function
659
646
 
660
647
  Raises:
661
648
  TypeError: If input is not callable
662
-
663
- Note:
664
- Variable arguments (*args) and keyword arguments (**kwargs) are not
665
- supported in OpenAI function schemas and will be ignored with a warning.
666
649
  """
667
650
  if not callable(func):
668
651
  msg = f"Expected callable, got {type(func)}"
@@ -737,11 +720,10 @@ def create_schema(
737
720
 
738
721
  return FunctionSchema(
739
722
  name=name_override or getattr(func, "__name__", "unknown") or "unknown",
740
- description=docstring.short_description,
723
+ description=description_override or docstring.short_description,
741
724
  parameters=parameters, # Now includes required fields
742
725
  required=required,
743
726
  returns=returns_dct,
744
- function_type=function_type,
745
727
  )
746
728
 
747
729
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: schemez
3
- Version: 1.4.0
3
+ Version: 1.4.4
4
4
  Summary: Pydantic shim for config stuff
5
5
  Keywords:
6
6
  Author: Philipp Temminghoff
@@ -4,13 +4,13 @@ schemez/code.py,sha256=usZLov9i5KpK1W2VJxngUzeetgrINtodiooG_AxN-y4,2072
4
4
  schemez/code_generation/__init__.py,sha256=KV2ETgN8sKHlAOGnktURAHDJbz8jImBNputaxhdlin8,286
5
5
  schemez/code_generation/namespace_callable.py,sha256=LiQHsS3J46snBj4uhKNrI-dboeQNPO2QwdbhSk3-gyE,2382
6
6
  schemez/code_generation/route_helpers.py,sha256=YZfD0BUZ6_0iLnSzf2vKa8Fu2kJNfC-8oYzaQ--x8fA,4056
7
- schemez/code_generation/tool_code_generator.py,sha256=yM29h4nccZEV21FMSZaKJI-IBdDezxpHld3n4WFWfkM,9627
8
- schemez/code_generation/toolset_code_generator.py,sha256=Uw4UEjLzzHsm6YzsPFaEPxYHpFgIv_DKaFVP0XePjjI,5083
7
+ schemez/code_generation/tool_code_generator.py,sha256=6j6F6dDhXruInaZeu3ReK0x7wA56oAGRfzRCytnlaQk,10766
8
+ schemez/code_generation/toolset_code_generator.py,sha256=0L3Wrqc4XUbtbptHFLWARogBWMy50iVpQzzvYQlzNe0,5806
9
9
  schemez/convert.py,sha256=3sOxOgDaFzV7uiOUSM6_Sy0YlafIlZRSevs5y2vT1Kw,4403
10
10
  schemez/create_type.py,sha256=wrdqdzXtfxZfsgp9IroldoYGTJs_Rdli8TiscqhV2bI,11647
11
11
  schemez/docstrings.py,sha256=kmd660wcomXzKac0SSNYxPRNbVCUovrpmE9jwnVRS6c,4115
12
- schemez/executable.py,sha256=YM4WcmRyJ9CpzKpNgS0A-Ri0Jd7mAzfHmoaXONI-mIs,7134
13
- schemez/functionschema.py,sha256=D5nJIOQYwfOwuNSHLIoqNZtYkA1Y0DIFH18qMI6--ik,26141
12
+ schemez/executable.py,sha256=ZvZJdUMI_EWjTqp-wUTzob3-cZDPmHgA03W756gSzKc,7190
13
+ schemez/functionschema.py,sha256=LCNHn4ZCitUIoiEylfI59i0Em5TmnJ5N0rfxUWWlE38,25803
14
14
  schemez/helpers.py,sha256=YFx7UKYDI_sn5sVGAzzl5rcB26iBvLatvZlEFsQM5rc,7874
15
15
  schemez/log.py,sha256=i0SDbIfWmuC_nfJdQOYAdUYaR0TBk3Mhu-K3M-lnAM4,364
16
16
  schemez/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -24,7 +24,7 @@ schemez/tool_executor/executor.py,sha256=0VOY9N4Epqdv_MYU-BoDiTKbFiyIwGk_pfe5Jcx
24
24
  schemez/tool_executor/helpers.py,sha256=zxfI9tUkUx8Dy9fNP89-2kqfV8eZwQ3re2Gmd-oekb0,1476
25
25
  schemez/tool_executor/types.py,sha256=l2DxUIEHP9bjLnEaXZ6X428cSviicTDJsc3wfSNqKxg,675
26
26
  schemez/typedefs.py,sha256=3OAUQ1nin9nlsOcTPAO5xrsOqVUfwsH_7_cexQYREus,6091
27
- schemez-1.4.0.dist-info/licenses/LICENSE,sha256=AteGCH9r177TxxrOFEiOARrastASsf7yW6MQxlAHdwA,1078
28
- schemez-1.4.0.dist-info/WHEEL,sha256=DpNsHFUm_gffZe1FgzmqwuqiuPC6Y-uBCzibcJcdupM,78
29
- schemez-1.4.0.dist-info/METADATA,sha256=PyncybCr23T22f_d0yl76k-YwHfXv2XHHGAGeuEgdw4,11616
30
- schemez-1.4.0.dist-info/RECORD,,
27
+ schemez-1.4.4.dist-info/licenses/LICENSE,sha256=AteGCH9r177TxxrOFEiOARrastASsf7yW6MQxlAHdwA,1078
28
+ schemez-1.4.4.dist-info/WHEEL,sha256=DpNsHFUm_gffZe1FgzmqwuqiuPC6Y-uBCzibcJcdupM,78
29
+ schemez-1.4.4.dist-info/METADATA,sha256=3iBXsicqp4iXZ3nwDFvwHU_akqL17f0_zt5jqyCbNOE,11616
30
+ schemez-1.4.4.dist-info/RECORD,,