schemez 1.2.4__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.

schemez/__init__.py CHANGED
@@ -35,9 +35,11 @@ from schemez.schema_generators import (
35
35
  create_constructor_schema,
36
36
  )
37
37
  from schemez.typedefs import OpenAIFunctionDefinition, OpenAIFunctionTool
38
+ from schemez.code_generation import ToolCodeGenerator, ToolsetCodeGenerator
38
39
 
39
40
  __version__ = version("schemez")
40
41
 
42
+
41
43
  __all__ = [
42
44
  "ExecutableFunction",
43
45
  "FunctionType",
@@ -54,6 +56,8 @@ __all__ = [
54
56
  "SchemaDef",
55
57
  "SchemaField",
56
58
  "TOMLCode",
59
+ "ToolCodeGenerator",
60
+ "ToolsetCodeGenerator",
57
61
  "YAMLCode",
58
62
  "__version__",
59
63
  "create_constructor_schema",
@@ -0,0 +1,6 @@
1
+ """Meta-resource provider that exposes tools through Python execution."""
2
+
3
+ from schemez.code_generation.tool_code_generator import ToolCodeGenerator
4
+ from schemez.code_generation.toolset_code_generator import ToolsetCodeGenerator
5
+
6
+ __all__ = ["ToolCodeGenerator", "ToolsetCodeGenerator"]
@@ -0,0 +1,76 @@
1
+ """Namespace callable wrapper for tools."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ import inspect
7
+ from typing import TYPE_CHECKING, Any
8
+
9
+
10
+ if TYPE_CHECKING:
11
+ from collections.abc import Callable
12
+
13
+ from schemez.code_generation.tool_code_generator import ToolCodeGenerator
14
+
15
+
16
+ @dataclass
17
+ class NamespaceCallable:
18
+ """Wrapper for tool functions with proper repr and call interface."""
19
+
20
+ callable: Callable
21
+ """The callable function to execute."""
22
+
23
+ name_override: str | None = None
24
+ """Override name for the callable, defaults to callable.__name__."""
25
+
26
+ def __post_init__(self) -> None:
27
+ """Set function attributes for introspection."""
28
+ self.__name__ = self.name_override or self.callable.__name__
29
+ self.__doc__ = self.callable.__doc__ or ""
30
+
31
+ @property
32
+ def name(self) -> str:
33
+ """Get the effective name of the callable."""
34
+ return self.name_override or self.callable.__name__
35
+
36
+ @classmethod
37
+ def from_generator(cls, generator: ToolCodeGenerator) -> NamespaceCallable:
38
+ """Create a NamespaceCallable from a ToolCodeGenerator.
39
+
40
+ Args:
41
+ generator: The generator to wrap
42
+
43
+ Returns:
44
+ NamespaceCallable instance
45
+ """
46
+ return cls(generator.callable, generator.name_override)
47
+
48
+ async def __call__(self, *args, **kwargs) -> Any:
49
+ """Execute the wrapped callable."""
50
+ try:
51
+ if inspect.iscoroutinefunction(self.callable):
52
+ result = await self.callable(*args, **kwargs)
53
+
54
+ result = self.callable(*args, **kwargs)
55
+ except Exception as e: # noqa: BLE001
56
+ return f"Error executing {self.name}: {e!s}"
57
+ else:
58
+ return result if result is not None else "Operation completed successfully"
59
+
60
+ def __repr__(self) -> str:
61
+ """Return detailed representation for debugging."""
62
+ return f"NamespaceCallable(name='{self.name}')"
63
+
64
+ def __str__(self) -> str:
65
+ """Return readable string representation."""
66
+ return f"<tool: {self.name}>"
67
+
68
+ @property
69
+ def signature(self) -> str:
70
+ """Get function signature for debugging."""
71
+ try:
72
+ sig = inspect.signature(self.callable)
73
+ except (ValueError, TypeError):
74
+ return f"{self.name}(...)"
75
+ else:
76
+ return f"{self.name}{sig}"
@@ -0,0 +1,126 @@
1
+ """Helper functions for FastAPI route generation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ from schemez.schema import json_schema_to_base_model
8
+
9
+
10
+ if TYPE_CHECKING:
11
+ from collections.abc import Callable
12
+
13
+ from pydantic import BaseModel
14
+ from pydantic.fields import FieldInfo
15
+
16
+
17
+ def create_param_model(parameters_schema: dict[str, Any]) -> type[BaseModel] | None:
18
+ """Create Pydantic model for parameter validation using schemez.
19
+
20
+ Args:
21
+ parameters_schema: JSON schema for tool parameters
22
+
23
+ Returns:
24
+ Pydantic model class or None if no parameters
25
+ """
26
+ if parameters_schema.get("properties"):
27
+ return json_schema_to_base_model(parameters_schema) # type: ignore
28
+ return None
29
+
30
+
31
+ def generate_func_code(model_fields: dict[str, FieldInfo]) -> str:
32
+ """Generate dynamic function code for FastAPI route handler.
33
+
34
+ Args:
35
+ model_fields: Model fields from Pydantic model
36
+
37
+ Returns:
38
+ Generated function code as string
39
+ """
40
+ route_params = []
41
+ for name, field_info in model_fields.items():
42
+ field_type = field_info.annotation
43
+ if field_info.is_required():
44
+ route_params.append(f"{name}: {field_type.__name__}") # type: ignore
45
+ else:
46
+ route_params.append(f"{name}: {field_type.__name__} = None") # type: ignore
47
+
48
+ # Create function signature dynamically
49
+ param_str = ", ".join(route_params)
50
+ return f"""
51
+ async def dynamic_handler({param_str}) -> dict[str, Any]:
52
+ kwargs = {{{", ".join(f'"{name}": {name}' for name in model_fields)}}}
53
+ return await route_handler(**kwargs)
54
+ """
55
+
56
+
57
+ def create_route_handler(tool_callable: Callable, param_cls: type | None) -> Callable:
58
+ """Create FastAPI route handler for a tool.
59
+
60
+ Args:
61
+ tool_callable: The tool function to execute
62
+ param_cls: Pydantic model for parameter validation
63
+
64
+ Returns:
65
+ Async route handler function
66
+ """
67
+
68
+ async def route_handler(*args, **kwargs) -> Any:
69
+ """Route handler for the tool."""
70
+ if param_cls:
71
+ params_instance = param_cls(**kwargs) # Parse and validate parameters
72
+ dct = params_instance.model_dump() # Convert to dict and remove None values
73
+ clean_params = {k: v for k, v in dct.items() if v is not None}
74
+ result = await _execute_tool_function(tool_callable, **clean_params)
75
+ else:
76
+ result = await _execute_tool_function(tool_callable)
77
+ return {"result": result}
78
+
79
+ return route_handler
80
+
81
+
82
+ async def _execute_tool_function(tool_callable: Callable, **kwargs) -> Any:
83
+ """Execute a tool function with the given parameters.
84
+
85
+ Args:
86
+ tool_callable: Tool function to execute
87
+ **kwargs: Tool parameters
88
+
89
+ Returns:
90
+ Tool execution result
91
+ """
92
+ try:
93
+ # For now, just simulate execution
94
+ # In real implementation, this would call the actual tool
95
+ # potentially through sandbox providers
96
+ return f"Executed {tool_callable.__name__} with params: {kwargs}"
97
+ except Exception as e: # noqa: BLE001
98
+ return f"Error executing {tool_callable.__name__}: {e!s}"
99
+
100
+
101
+ if __name__ == "__main__":
102
+ from llmling_agent.tools.base import Tool
103
+
104
+ def greet(name: str, greeting: str = "Hello") -> str:
105
+ """Greet someone."""
106
+ return f"{greeting}, {name}!"
107
+
108
+ # Create a tool and demonstrate helper functions
109
+ tool = Tool.from_callable(greet)
110
+ schema = tool.schema["function"]
111
+ parameters_schema = schema.get("parameters", {})
112
+
113
+ # Create parameter model
114
+ param_cls = create_param_model(dict(parameters_schema))
115
+ print(f"Generated parameter model: {param_cls}")
116
+
117
+ if param_cls:
118
+ print(f"Model fields: {param_cls.model_fields}")
119
+
120
+ # Generate function code
121
+ func_code = generate_func_code(param_cls.model_fields)
122
+ print(f"Generated function code:\n{func_code}")
123
+
124
+ # Create route handler
125
+ handler = create_route_handler(greet, param_cls)
126
+ print(f"Generated route handler: {handler}")
@@ -0,0 +1,300 @@
1
+ """Meta-resource provider that exposes tools through Python execution."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ import inspect
7
+ from typing import TYPE_CHECKING, Any, get_origin
8
+
9
+ from schemez import create_schema
10
+ from schemez.code_generation.route_helpers import (
11
+ create_param_model,
12
+ create_route_handler,
13
+ generate_func_code,
14
+ )
15
+
16
+
17
+ if TYPE_CHECKING:
18
+ from collections.abc import Callable
19
+
20
+ from fastapi import FastAPI
21
+
22
+ from schemez.typedefs import OpenAIFunctionTool, Property
23
+
24
+
25
+ TYPE_MAP = {
26
+ "string": "str",
27
+ "integer": "int",
28
+ "number": "float",
29
+ "boolean": "bool",
30
+ "array": "list",
31
+ "null": "None",
32
+ }
33
+
34
+
35
+ @dataclass
36
+ class ToolCodeGenerator:
37
+ """Generates code artifacts for a single tool."""
38
+
39
+ callable: Callable
40
+ """Tool to generate code for."""
41
+
42
+ schema: OpenAIFunctionTool
43
+ """Schema of the tool."""
44
+
45
+ name_override: str | None = None
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)."""
50
+
51
+ @classmethod
52
+ def from_callable(
53
+ cls,
54
+ fn: Callable,
55
+ exclude_types: list[type] | None = None,
56
+ ) -> ToolCodeGenerator:
57
+ """Create a ToolCodeGenerator from a Tool."""
58
+ schema = create_schema(fn).model_dump_openai()
59
+ schema["function"]["name"] = fn.__name__
60
+ schema["function"]["description"] = fn.__doc__ or ""
61
+ return cls(schema=schema, callable=fn, exclude_types=exclude_types or [])
62
+
63
+ @property
64
+ def name(self) -> str:
65
+ """Name of the tool."""
66
+ return self.name_override or self.callable.__name__
67
+
68
+ def _extract_basic_signature(self, return_type: str = "Any") -> str:
69
+ """Fallback signature extraction from tool schema."""
70
+ schema = self.schema["function"]
71
+ params = schema.get("parameters", {}).get("properties", {})
72
+ required = set(schema.get("parameters", {}).get("required", []))
73
+
74
+ param_strs = []
75
+ for name, param_info in params.items():
76
+ # Skip context parameters that should be hidden from users
77
+ if self._is_context_parameter(name):
78
+ continue
79
+
80
+ type_hint = self._infer_parameter_type(name, param_info)
81
+
82
+ if name not in required:
83
+ # Check for actual default value in schema
84
+ default_value = param_info.get("default")
85
+ if default_value is not None:
86
+ if isinstance(default_value, str):
87
+ param_strs.append(f"{name}: {type_hint} = {default_value!r}")
88
+ else:
89
+ param_strs.append(f"{name}: {type_hint} = {default_value}")
90
+ else:
91
+ param_strs.append(f"{name}: {type_hint} = None")
92
+ else:
93
+ param_strs.append(f"{name}: {type_hint}")
94
+
95
+ return f"{self.name}({', '.join(param_strs)}) -> {return_type}"
96
+
97
+ def _infer_parameter_type(self, param_name: str, param_info: Property) -> str:
98
+ """Infer parameter type from schema and function inspection."""
99
+ schema_type = param_info.get("type", "Any")
100
+
101
+ # If schema has a specific type, use it
102
+ if schema_type != "object":
103
+ return TYPE_MAP.get(schema_type, "Any")
104
+
105
+ # For 'object' type, try to infer from function signature
106
+ try:
107
+ callable_func = self.callable
108
+ # Use wrapped signature if available (for context parameter hiding)
109
+ sig = getattr(callable_func, "__signature__", None) or inspect.signature(
110
+ callable_func
111
+ )
112
+
113
+ if param_name in sig.parameters:
114
+ param = sig.parameters[param_name]
115
+
116
+ # Try annotation first
117
+ if param.annotation != inspect.Parameter.empty:
118
+ if hasattr(param.annotation, "__name__"):
119
+ return param.annotation.__name__
120
+ return str(param.annotation)
121
+
122
+ # Infer from default value
123
+ if param.default != inspect.Parameter.empty:
124
+ default_type = type(param.default).__name__
125
+ # Map common types
126
+ if default_type in ["int", "float", "str", "bool"]:
127
+ return default_type
128
+ # If no default and it's required, assume str for web-like functions
129
+ required = set(
130
+ self.schema.get("function", {})
131
+ .get("parameters", {})
132
+ .get("required", [])
133
+ )
134
+ if param_name in required:
135
+ return "str"
136
+
137
+ except Exception: # noqa: BLE001
138
+ pass
139
+
140
+ # Fallback to Any for unresolved object types
141
+ return "Any"
142
+
143
+ def _get_return_model_name(self) -> str:
144
+ """Get the return model name for a tool."""
145
+ try:
146
+ schema = create_schema(self.callable)
147
+ if schema.returns.get("type") == "object":
148
+ return f"{self.name.title()}Response"
149
+ if schema.returns.get("type") == "array":
150
+ return f"list[{self.name.title()}Item]"
151
+ return TYPE_MAP.get(schema.returns.get("type", "string"), "Any")
152
+ except Exception: # noqa: BLE001
153
+ return "Any"
154
+
155
+ def get_function_signature(self) -> str:
156
+ """Extract function signature using schemez."""
157
+ try:
158
+ return_model_name = self._get_return_model_name()
159
+ return self._extract_basic_signature(return_model_name)
160
+ except Exception: # noqa: BLE001
161
+ return self._extract_basic_signature("Any")
162
+
163
+ def _get_callable_signature(self) -> inspect.Signature:
164
+ """Get signature from callable, respecting wrapped signatures."""
165
+ # Use wrapped signature if available (for context parameter hiding)
166
+ return getattr(self.callable, "__signature__", None) or inspect.signature(
167
+ self.callable
168
+ )
169
+
170
+ def _is_context_parameter(self, param_name: str) -> bool:
171
+ """Check if a parameter is a context parameter that should be hidden."""
172
+ try:
173
+ sig = self._get_callable_signature()
174
+ if param_name not in sig.parameters:
175
+ return False
176
+
177
+ param = sig.parameters[param_name]
178
+ if param.annotation == inspect.Parameter.empty:
179
+ return False
180
+
181
+ annotation = param.annotation
182
+
183
+ for typ in self.exclude_types:
184
+ if self._types_match(annotation, typ):
185
+ return True
186
+ except Exception: # noqa: BLE001
187
+ pass
188
+
189
+ return False
190
+
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:
196
+ return True
197
+
198
+ # Handle generic types - get origin for comparison
199
+ origin_annotation = get_origin(annotation)
200
+ if origin_annotation is exclude_type:
201
+ return True
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
+
224
+ except Exception: # noqa: BLE001
225
+ pass
226
+
227
+ return False
228
+
229
+ def generate_return_model(self) -> str | None:
230
+ """Generate Pydantic model code for the tool's return type."""
231
+ try:
232
+ schema = create_schema(self.callable)
233
+ if schema.returns.get("type") not in {"object", "array"}:
234
+ return None
235
+
236
+ class_name = f"{self.name.title()}Response"
237
+ model_code = schema.to_pydantic_model_code(class_name=class_name)
238
+ return model_code.strip() or None
239
+
240
+ except Exception: # noqa: BLE001
241
+ return None
242
+
243
+ # Route generation methods
244
+ def generate_route_handler(self) -> Callable:
245
+ """Generate FastAPI route handler for this tool.
246
+
247
+ Returns:
248
+ Async route handler function
249
+ """
250
+ # Extract parameter schema
251
+ schema = self.schema["function"]
252
+ parameters_schema = schema.get("parameters", {})
253
+
254
+ # Create parameter model
255
+ param_cls = create_param_model(dict(parameters_schema))
256
+
257
+ # Create route handler
258
+ return create_route_handler(self.callable, param_cls)
259
+
260
+ def add_route_to_app(self, app: FastAPI, path_prefix: str = "/tools") -> None:
261
+ """Add this tool's route to FastAPI app.
262
+
263
+ Args:
264
+ app: FastAPI application instance
265
+ path_prefix: Path prefix for the route
266
+ """
267
+ # Extract parameter schema
268
+ schema = self.schema["function"]
269
+ parameters_schema = schema.get("parameters", {})
270
+
271
+ # Create parameter model
272
+ param_cls = create_param_model(dict(parameters_schema))
273
+
274
+ # Create the route handler
275
+ route_handler = self.generate_route_handler()
276
+
277
+ # Set up the route with proper parameter annotations for FastAPI
278
+ if param_cls:
279
+ # Get field information from the generated model
280
+ model_fields = param_cls.model_fields
281
+ func_code = generate_func_code(model_fields)
282
+ # Execute the dynamic function creation
283
+ namespace = {"route_handler": route_handler, "Any": Any}
284
+ exec(func_code, namespace)
285
+ dynamic_handler: Callable = namespace["dynamic_handler"] # type: ignore
286
+ else:
287
+
288
+ async def dynamic_handler() -> dict[str, Any]:
289
+ return await route_handler()
290
+
291
+ # Add route to FastAPI app
292
+ app.get(f"{path_prefix}/{self.name}")(dynamic_handler)
293
+
294
+
295
+ if __name__ == "__main__":
296
+ import webbrowser
297
+
298
+ generator = ToolCodeGenerator.from_callable(webbrowser.open)
299
+ sig = generator.get_function_signature()
300
+ print(sig)
@@ -0,0 +1,167 @@
1
+ """Orchestrates code generation for multiple tools."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import contextlib
6
+ from dataclasses import dataclass
7
+ import inspect
8
+ from typing import TYPE_CHECKING, Any
9
+
10
+ from schemez.code_generation.namespace_callable import NamespaceCallable
11
+ from schemez.code_generation.tool_code_generator import ToolCodeGenerator
12
+
13
+
14
+ if TYPE_CHECKING:
15
+ from collections.abc import Callable, Sequence
16
+
17
+ from fastapi import FastAPI
18
+
19
+
20
+ USAGE = """\
21
+ Usage notes:
22
+ - Write your code inside an 'async def main():' function
23
+ - All tool functions are async, use 'await'
24
+ - Use 'return' statements to return values from main()
25
+ - Generated model classes are available for type checking
26
+ - Use 'await report_progress(current, total, message)' for long-running operations
27
+ - DO NOT call asyncio.run() or try to run the main function yourself
28
+ - DO NOT import asyncio or other modules - tools are already available
29
+ - Example:
30
+ async def main():
31
+ for i in range(5):
32
+ await report_progress(i, 5, f'Step {i+1} for {name}')
33
+ should_continue = await ask_user('Continue?', 'bool')
34
+ if not should_continue:
35
+ break
36
+ return f'Completed for {name}'
37
+
38
+ """
39
+
40
+
41
+ @dataclass
42
+ class ToolsetCodeGenerator:
43
+ """Generates code artifacts for multiple tools."""
44
+
45
+ generators: Sequence[ToolCodeGenerator]
46
+ """ToolCodeGenerator instances for each tool."""
47
+
48
+ include_signatures: bool = True
49
+ """Include function signatures in documentation."""
50
+
51
+ include_docstrings: bool = True
52
+ """Include function docstrings in documentation."""
53
+
54
+ @classmethod
55
+ def from_callables(
56
+ cls,
57
+ callables: Sequence[Callable],
58
+ include_signatures: bool = True,
59
+ include_docstrings: bool = True,
60
+ exclude_types: list[type] | None = None,
61
+ ) -> ToolsetCodeGenerator:
62
+ """Create a ToolsetCodeGenerator from a sequence of Tools.
63
+
64
+ Args:
65
+ callables: Callables to generate code for
66
+ include_signatures: Include function signatures in documentation
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.
70
+
71
+ Returns:
72
+ ToolsetCodeGenerator instance
73
+ """
74
+ generators = [
75
+ ToolCodeGenerator.from_callable(i, exclude_types=exclude_types)
76
+ for i in callables
77
+ ]
78
+ return cls(generators, include_signatures, include_docstrings)
79
+
80
+ def generate_tool_description(self) -> str:
81
+ """Generate comprehensive tool description with available functions."""
82
+ if not self.generators:
83
+ return "Execute Python code (no tools available)"
84
+
85
+ return_models = self.generate_return_models()
86
+ parts = [
87
+ "Execute Python code with the following tools available as async functions:",
88
+ "",
89
+ ]
90
+
91
+ if return_models:
92
+ parts.extend([
93
+ "# Generated return type models",
94
+ return_models,
95
+ "",
96
+ "# Available functions:",
97
+ "",
98
+ ])
99
+
100
+ for generator in self.generators:
101
+ if self.include_signatures:
102
+ signature = generator.get_function_signature()
103
+ parts.append(f"async def {signature}:")
104
+ else:
105
+ parts.append(f"async def {generator.name}(...):")
106
+
107
+ if self.include_docstrings and generator.callable.__doc__:
108
+ indented_desc = " " + generator.callable.__doc__.replace(
109
+ "\n", "\n "
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
+
118
+ parts.append(f' """{indented_desc}"""')
119
+ parts.append("")
120
+
121
+ parts.append(USAGE)
122
+
123
+ return "\n".join(parts)
124
+
125
+ def generate_execution_namespace(self) -> dict[str, Any]:
126
+ """Build Python namespace with tool functions and generated models."""
127
+ namespace: dict[str, Any] = {"__builtins__": __builtins__, "_result": None}
128
+
129
+ # Add tool functions
130
+ for generator in self.generators:
131
+ namespace[generator.name] = NamespaceCallable.from_generator(generator)
132
+
133
+ # Add generated model classes to namespace
134
+ if models_code := self.generate_return_models():
135
+ with contextlib.suppress(Exception):
136
+ exec(models_code, namespace)
137
+
138
+ return namespace
139
+
140
+ def generate_return_models(self) -> str:
141
+ """Generate Pydantic models for tool return types."""
142
+ model_parts = [
143
+ code for g in self.generators if (code := g.generate_return_model())
144
+ ]
145
+ return "\n\n".join(model_parts) if model_parts else ""
146
+
147
+ def add_all_routes(self, app: FastAPI, path_prefix: str = "/tools") -> None:
148
+ """Add FastAPI routes for all tools.
149
+
150
+ Args:
151
+ app: FastAPI application instance
152
+ path_prefix: Path prefix for routes
153
+ """
154
+ for generator in self.generators:
155
+ generator.add_route_to_app(app, path_prefix)
156
+
157
+
158
+ if __name__ == "__main__":
159
+
160
+ async def no_annotations_func(test):
161
+ pass
162
+
163
+ generator = ToolsetCodeGenerator.from_callables([no_annotations_func])
164
+ models = generator.generate_return_models()
165
+ print(models)
166
+ namespace = generator.generate_execution_namespace()
167
+ print(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
 
schemez/schema.py CHANGED
@@ -2,17 +2,16 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import TYPE_CHECKING, Any, Literal, Self
5
+ from enum import Enum
6
+ from typing import TYPE_CHECKING, Any, Literal, Optional, Self
6
7
 
7
- import anyenv
8
- from pydantic import BaseModel, ConfigDict
8
+ from pydantic import BaseModel, ConfigDict, Field, create_model
9
9
  import upath
10
10
 
11
11
 
12
12
  if TYPE_CHECKING:
13
13
  from collections.abc import Callable
14
14
 
15
- from llmling_agent.agent.agent import AgentType
16
15
  from llmling_agent.models.content import BaseContent
17
16
  from upath.types import JoinablePathLike
18
17
 
@@ -69,41 +68,47 @@ class Schema(BaseModel):
69
68
  return get_function_model(func, name=name)
70
69
 
71
70
  @classmethod
72
- def from_vision_llm_sync(
73
- cls,
74
- file_content: bytes,
75
- source_type: SourceType = "pdf",
76
- model: str = "google-gla:gemini-2.0-flash",
77
- system_prompt: str = DEFAULT_SYSTEM_PROMPT,
78
- user_prompt: str = DEFAULT_USER_PROMPT,
79
- provider: AgentType = "pydantic_ai",
80
- ) -> Self:
81
- """Create a schema model from a document using AI.
71
+ def from_json_schema(cls, json_schema: dict[str, Any]) -> type[Schema]:
72
+ """Create a schema model from a JSON schema.
82
73
 
83
74
  Args:
84
- file_content: The document content to create a schema from
85
- source_type: The type of the document
86
- model: The AI model to use for schema extraction
87
- system_prompt: The system prompt to use for schema extraction
88
- user_prompt: The user prompt to use for schema extraction
89
- provider: The provider to use for schema extraction
75
+ json_schema: The JSON schema to create a schema from
90
76
 
91
77
  Returns:
92
- A new schema model class based on the document
78
+ A new schema model class based on the JSON schema
93
79
  """
94
- from llmling_agent import Agent, ImageBase64Content, PDFBase64Content
80
+ from datamodel_code_generator import DataModelType, PythonVersion
81
+ from datamodel_code_generator.model import get_data_model_types
82
+ from datamodel_code_generator.parser.jsonschema import JsonSchemaParser
83
+ from pydantic.v1.main import ModelMetaclass
95
84
 
96
- if source_type == "pdf":
97
- content: BaseContent = PDFBase64Content.from_bytes(file_content)
98
- else:
99
- content = ImageBase64Content.from_bytes(file_content)
100
- agent = Agent[None]( # type:ignore[var-annotated]
101
- model=model,
102
- system_prompt=system_prompt.format(name=cls.__name__),
103
- provider=provider,
104
- ).to_structured(cls)
105
- chat_message = anyenv.run_sync(agent.run(user_prompt, content))
106
- return chat_message.content
85
+ data_model_types = get_data_model_types(
86
+ DataModelType.PydanticBaseModel, target_python_version=PythonVersion.PY_312
87
+ )
88
+ parser = JsonSchemaParser(
89
+ f"""{json_schema}""",
90
+ data_model_type=data_model_types.data_model,
91
+ data_model_root_type=data_model_types.root_model,
92
+ data_model_field_type=data_model_types.field_model,
93
+ data_type_manager_type=data_model_types.data_type_manager,
94
+ dump_resolve_reference_action=data_model_types.dump_resolve_reference_action,
95
+ )
96
+ code_str = (
97
+ parser.parse()
98
+ .replace("from pydantic ", "from pydantic.v1 ") # type: ignore
99
+ .replace("from __future__ import annotations\n\n", "")
100
+ )
101
+ ex_namespace: dict[str, Any] = {}
102
+ exec(code_str, ex_namespace, ex_namespace)
103
+ model = None
104
+ for v in ex_namespace.values():
105
+ if isinstance(v, ModelMetaclass):
106
+ model = v
107
+ if not model:
108
+ msg = "Class not found in output"
109
+ raise Exception(msg) # noqa: TRY002
110
+ model.__module__ = __name__
111
+ return model # type: ignore
107
112
 
108
113
  @classmethod
109
114
  async def from_vision_llm(
@@ -113,7 +118,6 @@ class Schema(BaseModel):
113
118
  model: str = "google-gla:gemini-2.0-flash",
114
119
  system_prompt: str = DEFAULT_SYSTEM_PROMPT,
115
120
  user_prompt: str = DEFAULT_USER_PROMPT,
116
- provider: AgentType = "pydantic_ai",
117
121
  ) -> Self:
118
122
  """Create a schema model from a document using AI.
119
123
 
@@ -123,7 +127,6 @@ class Schema(BaseModel):
123
127
  model: The AI model to use for schema extraction
124
128
  system_prompt: The system prompt to use for schema extraction
125
129
  user_prompt: The user prompt to use for schema extraction
126
- provider: The provider to use for schema extraction
127
130
 
128
131
  Returns:
129
132
  A new schema model class based on the document
@@ -134,45 +137,11 @@ class Schema(BaseModel):
134
137
  content: BaseContent = PDFBase64Content.from_bytes(file_content)
135
138
  else:
136
139
  content = ImageBase64Content.from_bytes(file_content)
137
- agent = Agent[None]( # type:ignore[var-annotated]
138
- model=model,
139
- system_prompt=system_prompt.format(name=cls.__name__),
140
- provider=provider,
141
- ).to_structured(cls)
140
+ prompt = system_prompt.format(name=cls.__name__)
141
+ agent = Agent(model=model, system_prompt=prompt, output_type=cls)
142
142
  chat_message = await agent.run(user_prompt, content)
143
143
  return chat_message.content
144
144
 
145
- @classmethod
146
- def from_llm_sync(
147
- cls,
148
- text: str,
149
- model: str = "google-gla:gemini-2.0-flash",
150
- system_prompt: str = DEFAULT_SYSTEM_PROMPT,
151
- user_prompt: str = DEFAULT_USER_PROMPT,
152
- provider: AgentType = "pydantic_ai",
153
- ) -> Self:
154
- """Create a schema model from a text snippet using AI.
155
-
156
- Args:
157
- text: The text to create a schema from
158
- model: The AI model to use for schema extraction
159
- system_prompt: The system prompt to use for schema extraction
160
- user_prompt: The user prompt to use for schema extraction
161
- provider: The provider to use for schema extraction
162
-
163
- Returns:
164
- A new schema model class based on the document
165
- """
166
- from llmling_agent import Agent
167
-
168
- agent = Agent[None]( # type:ignore[var-annotated]
169
- model=model,
170
- system_prompt=system_prompt.format(name=cls.__name__),
171
- provider=provider,
172
- ).to_structured(cls)
173
- chat_message = anyenv.run_sync(agent.run(user_prompt, text))
174
- return chat_message.content
175
-
176
145
  @classmethod
177
146
  async def from_llm(
178
147
  cls,
@@ -180,7 +149,6 @@ class Schema(BaseModel):
180
149
  model: str = "google-gla:gemini-2.0-flash",
181
150
  system_prompt: str = DEFAULT_SYSTEM_PROMPT,
182
151
  user_prompt: str = DEFAULT_USER_PROMPT,
183
- provider: AgentType = "pydantic_ai",
184
152
  ) -> Self:
185
153
  """Create a schema model from a text snippet using AI.
186
154
 
@@ -189,18 +157,14 @@ class Schema(BaseModel):
189
157
  model: The AI model to use for schema extraction
190
158
  system_prompt: The system prompt to use for schema extraction
191
159
  user_prompt: The user prompt to use for schema extraction
192
- provider: The provider to use for schema extraction
193
160
 
194
161
  Returns:
195
162
  A new schema model class based on the document
196
163
  """
197
164
  from llmling_agent import Agent
198
165
 
199
- agent = Agent[None]( # type:ignore[var-annotated]
200
- model=model,
201
- system_prompt=system_prompt.format(name=cls.__name__),
202
- provider=provider,
203
- ).to_structured(cls)
166
+ prompt = system_prompt.format(name=cls.__name__)
167
+ agent = Agent(model=model, system_prompt=prompt, output_type=cls)
204
168
  chat_message = await agent.run(user_prompt, text)
205
169
  return chat_message.content
206
170
 
@@ -280,3 +244,87 @@ class Schema(BaseModel):
280
244
  class_name=class_name,
281
245
  target_python_version=target_python_version,
282
246
  )
247
+
248
+
249
+ def json_schema_to_base_model(
250
+ schema: dict[str, Any], model_cls: type[BaseModel] = Schema
251
+ ) -> type[Schema]:
252
+ type_mapping: dict[str, type] = {
253
+ "string": str,
254
+ "integer": int,
255
+ "number": float,
256
+ "boolean": bool,
257
+ "array": list,
258
+ "object": dict,
259
+ }
260
+
261
+ properties = schema.get("properties", {})
262
+ required_fields = schema.get("required", [])
263
+ model_fields = {}
264
+
265
+ def process_field(field_name: str, field_props: dict[str, Any]) -> tuple:
266
+ """Recursively processes a field and returns its type and Field instance."""
267
+ json_type = field_props.get("type", "string")
268
+ enum_values = field_props.get("enum")
269
+
270
+ # Handle Enums
271
+ if enum_values:
272
+ enum_name: str = f"{field_name.capitalize()}Enum"
273
+ field_type: Any = Enum(enum_name, {v: v for v in enum_values})
274
+ # Handle Nested Objects
275
+ elif json_type == "object" and "properties" in field_props:
276
+ # Recursively create submodel
277
+ field_type = json_schema_to_base_model(field_props) # type: ignore
278
+ # Handle Arrays with Nested Objects
279
+ elif json_type == "array" and "items" in field_props:
280
+ item_props = field_props["items"]
281
+ if item_props.get("type") == "object":
282
+ item_type: Any = json_schema_to_base_model(item_props) # pyright: ignore[reportRedeclaration]
283
+ else:
284
+ item_type = type_mapping.get(item_props.get("type"), Any) # pyright: ignore[reportAssignmentType]
285
+ field_type = list[item_type] # type: ignore
286
+ else:
287
+ field_type = type_mapping.get(json_type, Any) # type: ignore
288
+
289
+ # Handle default values and optionality
290
+ default_value = field_props.get("default", ...)
291
+ nullable = field_props.get("nullable", False)
292
+ description = field_props.get("title", "")
293
+
294
+ if nullable:
295
+ field_type = Optional[field_type] # type: ignore # noqa: UP045
296
+
297
+ if field_name not in required_fields:
298
+ default_value = field_props.get("default")
299
+
300
+ return field_type, Field(default_value, description=description)
301
+
302
+ # Process each field
303
+ for field_name, field_props in properties.items():
304
+ model_fields[field_name] = process_field(field_name, field_props)
305
+
306
+ return create_model( # type: ignore
307
+ schema.get("title", "DynamicModel"), **model_fields, __base__=model_cls
308
+ )
309
+
310
+
311
+ if __name__ == "__main__":
312
+ schema = {
313
+ "$id": "https://example.com/person.schema.json",
314
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
315
+ "title": "Person",
316
+ "type": "object",
317
+ "properties": {
318
+ "firstName": {"type": "string", "description": "The person's first name."},
319
+ "lastName": {"type": "string", "description": "The person's last name."},
320
+ "age": {
321
+ "description": "Age in years, must be equal to or greater than zero.",
322
+ "type": "integer",
323
+ "minimum": 0,
324
+ },
325
+ },
326
+ }
327
+ model = Schema.from_json_schema(schema)
328
+ import devtools
329
+
330
+ devtools.debug(model.__fields__)
@@ -33,7 +33,7 @@ class HttpToolExecutor:
33
33
 
34
34
  def __init__(
35
35
  self,
36
- schemas: Sequence[dict[str, Any] | UPath],
36
+ schemas: Sequence[dict[str, Any] | JoinablePathLike],
37
37
  handler: ToolHandler,
38
38
  base_url: str = "http://localhost:8000",
39
39
  ):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: schemez
3
- Version: 1.2.4
3
+ Version: 1.4.4
4
4
  Summary: Pydantic shim for config stuff
5
5
  Keywords:
6
6
  Author: Philipp Temminghoff
@@ -1,25 +1,30 @@
1
- schemez/__init__.py,sha256=KkwJF8pfbzw_4cBxwXuUyXIExqdxS8iumvyAL-JUED4,1669
1
+ schemez/__init__.py,sha256=UwLAA0B5whh63w9sYQvs_b_NNm6TBCm86-zPQiDxj3g,1799
2
2
  schemez/bind_kwargs.py,sha256=ChyArgNa5R8VdwSJmmrQItMH9Ld6hStWBISw-T1wyws,6228
3
3
  schemez/code.py,sha256=usZLov9i5KpK1W2VJxngUzeetgrINtodiooG_AxN-y4,2072
4
+ schemez/code_generation/__init__.py,sha256=KV2ETgN8sKHlAOGnktURAHDJbz8jImBNputaxhdlin8,286
5
+ schemez/code_generation/namespace_callable.py,sha256=LiQHsS3J46snBj4uhKNrI-dboeQNPO2QwdbhSk3-gyE,2382
6
+ schemez/code_generation/route_helpers.py,sha256=YZfD0BUZ6_0iLnSzf2vKa8Fu2kJNfC-8oYzaQ--x8fA,4056
7
+ schemez/code_generation/tool_code_generator.py,sha256=6j6F6dDhXruInaZeu3ReK0x7wA56oAGRfzRCytnlaQk,10766
8
+ schemez/code_generation/toolset_code_generator.py,sha256=0L3Wrqc4XUbtbptHFLWARogBWMy50iVpQzzvYQlzNe0,5806
4
9
  schemez/convert.py,sha256=3sOxOgDaFzV7uiOUSM6_Sy0YlafIlZRSevs5y2vT1Kw,4403
5
10
  schemez/create_type.py,sha256=wrdqdzXtfxZfsgp9IroldoYGTJs_Rdli8TiscqhV2bI,11647
6
11
  schemez/docstrings.py,sha256=kmd660wcomXzKac0SSNYxPRNbVCUovrpmE9jwnVRS6c,4115
7
- schemez/executable.py,sha256=YM4WcmRyJ9CpzKpNgS0A-Ri0Jd7mAzfHmoaXONI-mIs,7134
8
- schemez/functionschema.py,sha256=D5nJIOQYwfOwuNSHLIoqNZtYkA1Y0DIFH18qMI6--ik,26141
12
+ schemez/executable.py,sha256=ZvZJdUMI_EWjTqp-wUTzob3-cZDPmHgA03W756gSzKc,7190
13
+ schemez/functionschema.py,sha256=LCNHn4ZCitUIoiEylfI59i0Em5TmnJ5N0rfxUWWlE38,25803
9
14
  schemez/helpers.py,sha256=YFx7UKYDI_sn5sVGAzzl5rcB26iBvLatvZlEFsQM5rc,7874
10
15
  schemez/log.py,sha256=i0SDbIfWmuC_nfJdQOYAdUYaR0TBk3Mhu-K3M-lnAM4,364
11
16
  schemez/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
17
  schemez/pydantic_types.py,sha256=8vgSl8i2z9n0fB-8AJj-D3TBByEWE5IxItBxQ0XwXFI,1640
13
- schemez/schema.py,sha256=Qt4INzB22jj-Uu61T-QI-USpNKMEeATdp98zE0Q8pL8,9592
18
+ schemez/schema.py,sha256=OUiMNkoJZzsRQ2hTdKLvHFrmeoSZZhtbgnV4EjeIPA4,11552
14
19
  schemez/schema_generators.py,sha256=Gze7S7dQkTsl_1ckeHLXPxx4jQo7RB6hHQM-5fpAsrA,6973
15
20
  schemez/schemadef/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
21
  schemez/schemadef/schemadef.py,sha256=FtD7TOnYxiuYOIfadRHKkkbZn98mWFb0_lKfPsPR-hI,14393
17
22
  schemez/tool_executor/__init__.py,sha256=7wLjhA1NGekTMsiIfWLAv6J7qYhWDlanH9DKU4v1c6c,263
18
- schemez/tool_executor/executor.py,sha256=-uSPfkE5JrpUQWbmD0QdEpAXVBsWiRmCWCcFvDWqtus,10486
23
+ schemez/tool_executor/executor.py,sha256=0VOY9N4Epqdv_MYU-BoDiTKbFiyIwGk_pfe5JcxJq4o,10497
19
24
  schemez/tool_executor/helpers.py,sha256=zxfI9tUkUx8Dy9fNP89-2kqfV8eZwQ3re2Gmd-oekb0,1476
20
25
  schemez/tool_executor/types.py,sha256=l2DxUIEHP9bjLnEaXZ6X428cSviicTDJsc3wfSNqKxg,675
21
26
  schemez/typedefs.py,sha256=3OAUQ1nin9nlsOcTPAO5xrsOqVUfwsH_7_cexQYREus,6091
22
- schemez-1.2.4.dist-info/licenses/LICENSE,sha256=AteGCH9r177TxxrOFEiOARrastASsf7yW6MQxlAHdwA,1078
23
- schemez-1.2.4.dist-info/WHEEL,sha256=ELhySV62sOro8I5wRaLaF3TWxhBpkcDkdZUdAYLy_Hk,78
24
- schemez-1.2.4.dist-info/METADATA,sha256=ycelBanzLf1RK3HpheIamKERJ8lcEMdxmxmecfKotzk,11616
25
- schemez-1.2.4.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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.3
2
+ Generator: uv 0.9.8
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any