schemez 1.2.3__py3-none-any.whl → 1.4.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 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,272 @@
1
+ """Meta-resource provider that exposes tools through Python execution."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ import inspect
7
+ from typing import TYPE_CHECKING, Any
8
+
9
+ from pydantic_ai import RunContext
10
+
11
+ from schemez import create_schema
12
+ from schemez.code_generation.route_helpers import (
13
+ create_param_model,
14
+ create_route_handler,
15
+ generate_func_code,
16
+ )
17
+
18
+
19
+ if TYPE_CHECKING:
20
+ from collections.abc import Callable
21
+
22
+ from fastapi import FastAPI
23
+
24
+ from schemez.typedefs import OpenAIFunctionTool, Property
25
+
26
+
27
+ TYPE_MAP = {
28
+ "string": "str",
29
+ "integer": "int",
30
+ "number": "float",
31
+ "boolean": "bool",
32
+ "array": "list",
33
+ "null": "None",
34
+ }
35
+
36
+
37
+ @dataclass
38
+ class ToolCodeGenerator:
39
+ """Generates code artifacts for a single tool."""
40
+
41
+ schema: OpenAIFunctionTool
42
+ """Schema of the tool."""
43
+
44
+ callable: Callable
45
+ """Tool to generate code for."""
46
+
47
+ name_override: str | None = None
48
+ """Name of the tool."""
49
+
50
+ @classmethod
51
+ def from_callable(cls, fn: Callable) -> ToolCodeGenerator:
52
+ """Create a ToolCodeGenerator from a Tool."""
53
+ schema = create_schema(fn).model_dump_openai()
54
+ schema["function"]["name"] = fn.__name__
55
+ schema["function"]["description"] = fn.__doc__ or ""
56
+ return cls(schema=schema, callable=callable)
57
+
58
+ @property
59
+ def name(self) -> str:
60
+ """Name of the tool."""
61
+ return self.name_override or self.callable.__name__
62
+
63
+ def _extract_basic_signature(self, return_type: str = "Any") -> str:
64
+ """Fallback signature extraction from tool schema."""
65
+ schema = self.schema["function"]
66
+ params = schema.get("parameters", {}).get("properties", {})
67
+ required = set(schema.get("parameters", {}).get("required", []))
68
+
69
+ param_strs = []
70
+ for name, param_info in params.items():
71
+ # Skip context parameters that should be hidden from users
72
+ if self._is_context_parameter(name):
73
+ continue
74
+
75
+ type_hint = self._infer_parameter_type(name, param_info)
76
+
77
+ if name not in required:
78
+ # Check for actual default value in schema
79
+ default_value = param_info.get("default")
80
+ if default_value is not None:
81
+ if isinstance(default_value, str):
82
+ param_strs.append(f"{name}: {type_hint} = {default_value!r}")
83
+ else:
84
+ param_strs.append(f"{name}: {type_hint} = {default_value}")
85
+ else:
86
+ param_strs.append(f"{name}: {type_hint} = None")
87
+ else:
88
+ param_strs.append(f"{name}: {type_hint}")
89
+
90
+ return f"{self.name}({', '.join(param_strs)}) -> {return_type}"
91
+
92
+ def _infer_parameter_type(self, param_name: str, param_info: Property) -> str:
93
+ """Infer parameter type from schema and function inspection."""
94
+ schema_type = param_info.get("type", "Any")
95
+
96
+ # If schema has a specific type, use it
97
+ if schema_type != "object":
98
+ return TYPE_MAP.get(schema_type, "Any")
99
+
100
+ # For 'object' type, try to infer from function signature
101
+ try:
102
+ callable_func = self.callable
103
+ # Use wrapped signature if available (for context parameter hiding)
104
+ sig = getattr(callable_func, "__signature__", None) or inspect.signature(
105
+ callable_func
106
+ )
107
+
108
+ if param_name in sig.parameters:
109
+ param = sig.parameters[param_name]
110
+
111
+ # Try annotation first
112
+ if param.annotation != inspect.Parameter.empty:
113
+ if hasattr(param.annotation, "__name__"):
114
+ return param.annotation.__name__
115
+ return str(param.annotation)
116
+
117
+ # Infer from default value
118
+ if param.default != inspect.Parameter.empty:
119
+ default_type = type(param.default).__name__
120
+ # Map common types
121
+ if default_type in ["int", "float", "str", "bool"]:
122
+ return default_type
123
+ # If no default and it's required, assume str for web-like functions
124
+ required = set(
125
+ self.schema.get("function", {})
126
+ .get("parameters", {})
127
+ .get("required", [])
128
+ )
129
+ if param_name in required:
130
+ return "str"
131
+
132
+ except Exception: # noqa: BLE001
133
+ pass
134
+
135
+ # Fallback to Any for unresolved object types
136
+ return "Any"
137
+
138
+ def _get_return_model_name(self) -> str:
139
+ """Get the return model name for a tool."""
140
+ try:
141
+ schema = create_schema(self.callable)
142
+ if schema.returns.get("type") == "object":
143
+ return f"{self.name.title()}Response"
144
+ if schema.returns.get("type") == "array":
145
+ return f"list[{self.name.title()}Item]"
146
+ return TYPE_MAP.get(schema.returns.get("type", "string"), "Any")
147
+ except Exception: # noqa: BLE001
148
+ return "Any"
149
+
150
+ def get_function_signature(self) -> str:
151
+ """Extract function signature using schemez."""
152
+ try:
153
+ return_model_name = self._get_return_model_name()
154
+ return self._extract_basic_signature(return_model_name)
155
+ except Exception: # noqa: BLE001
156
+ return self._extract_basic_signature("Any")
157
+
158
+ def _get_callable_signature(self) -> inspect.Signature:
159
+ """Get signature from callable, respecting wrapped signatures."""
160
+ # Use wrapped signature if available (for context parameter hiding)
161
+ return getattr(self.callable, "__signature__", None) or inspect.signature(
162
+ self.callable
163
+ )
164
+
165
+ def _is_context_parameter(self, param_name: str) -> bool: # noqa: PLR0911
166
+ """Check if a parameter is a context parameter that should be hidden."""
167
+ try:
168
+ sig = self._get_callable_signature()
169
+ if param_name not in sig.parameters:
170
+ return False
171
+
172
+ param = sig.parameters[param_name]
173
+ if param.annotation == inspect.Parameter.empty:
174
+ return False
175
+
176
+ # Check if parameter is RunContext or AgentContext
177
+ annotation = param.annotation
178
+ annotation_str = str(annotation)
179
+
180
+ # Handle RunContext (including parameterized like RunContext[None])
181
+ if annotation is RunContext:
182
+ return True
183
+
184
+ # Check for parameterized RunContext using string matching
185
+ if "RunContext" in annotation_str:
186
+ return True
187
+
188
+ # Handle AgentContext
189
+ if hasattr(annotation, "__name__") and annotation.__name__ == "AgentContext":
190
+ return True
191
+
192
+ # Check for AgentContext in string representation
193
+ if "AgentContext" in annotation_str:
194
+ return True
195
+
196
+ except Exception: # noqa: BLE001
197
+ pass
198
+
199
+ return False
200
+
201
+ def generate_return_model(self) -> str | None:
202
+ """Generate Pydantic model code for the tool's return type."""
203
+ try:
204
+ schema = create_schema(self.callable)
205
+ if schema.returns.get("type") not in {"object", "array"}:
206
+ return None
207
+
208
+ class_name = f"{self.name.title()}Response"
209
+ model_code = schema.to_pydantic_model_code(class_name=class_name)
210
+ return model_code.strip() or None
211
+
212
+ except Exception: # noqa: BLE001
213
+ return None
214
+
215
+ # Route generation methods
216
+ def generate_route_handler(self) -> Callable:
217
+ """Generate FastAPI route handler for this tool.
218
+
219
+ Returns:
220
+ Async route handler function
221
+ """
222
+ # Extract parameter schema
223
+ schema = self.schema["function"]
224
+ parameters_schema = schema.get("parameters", {})
225
+
226
+ # Create parameter model
227
+ param_cls = create_param_model(dict(parameters_schema))
228
+
229
+ # Create route handler
230
+ return create_route_handler(self.callable, param_cls)
231
+
232
+ def add_route_to_app(self, app: FastAPI, path_prefix: str = "/tools") -> None:
233
+ """Add this tool's route to FastAPI app.
234
+
235
+ Args:
236
+ app: FastAPI application instance
237
+ path_prefix: Path prefix for the route
238
+ """
239
+ # Extract parameter schema
240
+ schema = self.schema["function"]
241
+ parameters_schema = schema.get("parameters", {})
242
+
243
+ # Create parameter model
244
+ param_cls = create_param_model(dict(parameters_schema))
245
+
246
+ # Create the route handler
247
+ route_handler = self.generate_route_handler()
248
+
249
+ # Set up the route with proper parameter annotations for FastAPI
250
+ if param_cls:
251
+ # Get field information from the generated model
252
+ model_fields = param_cls.model_fields
253
+ func_code = generate_func_code(model_fields)
254
+ # Execute the dynamic function creation
255
+ namespace = {"route_handler": route_handler, "Any": Any}
256
+ exec(func_code, namespace)
257
+ dynamic_handler: Callable = namespace["dynamic_handler"] # type: ignore
258
+ else:
259
+
260
+ async def dynamic_handler() -> dict[str, Any]:
261
+ return await route_handler()
262
+
263
+ # Add route to FastAPI app
264
+ app.get(f"{path_prefix}/{self.name}")(dynamic_handler)
265
+
266
+
267
+ if __name__ == "__main__":
268
+ import webbrowser
269
+
270
+ generator = ToolCodeGenerator.from_callable(webbrowser.open)
271
+ sig = generator.get_function_signature()
272
+ print(sig)
@@ -0,0 +1,151 @@
1
+ """Orchestrates code generation for multiple tools."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import contextlib
6
+ from dataclasses import dataclass
7
+ from typing import TYPE_CHECKING, Any
8
+
9
+ from schemez.code_generation.namespace_callable import NamespaceCallable
10
+ from schemez.code_generation.tool_code_generator import ToolCodeGenerator
11
+
12
+
13
+ if TYPE_CHECKING:
14
+ from collections.abc import Callable, Sequence
15
+
16
+ from fastapi import FastAPI
17
+
18
+
19
+ USAGE = """\
20
+ Usage notes:
21
+ - Write your code inside an 'async def main():' function
22
+ - All tool functions are async, use 'await'
23
+ - Use 'return' statements to return values from main()
24
+ - Generated model classes are available for type checking
25
+ - Use 'await report_progress(current, total, message)' for long-running operations
26
+ - DO NOT call asyncio.run() or try to run the main function yourself
27
+ - DO NOT import asyncio or other modules - tools are already available
28
+ - Example:
29
+ async def main():
30
+ for i in range(5):
31
+ await report_progress(i, 5, f'Step {i+1} for {name}')
32
+ should_continue = await ask_user('Continue?', 'bool')
33
+ if not should_continue:
34
+ break
35
+ return f'Completed for {name}'
36
+
37
+ """
38
+
39
+
40
+ @dataclass
41
+ class ToolsetCodeGenerator:
42
+ """Generates code artifacts for multiple tools."""
43
+
44
+ generators: Sequence[ToolCodeGenerator]
45
+ """ToolCodeGenerator instances for each tool."""
46
+
47
+ include_signatures: bool = True
48
+ """Include function signatures in documentation."""
49
+
50
+ include_docstrings: bool = True
51
+ """Include function docstrings in documentation."""
52
+
53
+ @classmethod
54
+ def from_callables(
55
+ cls,
56
+ callables: Sequence[Callable],
57
+ include_signatures: bool = True,
58
+ include_docstrings: bool = True,
59
+ ) -> ToolsetCodeGenerator:
60
+ """Create a ToolsetCodeGenerator from a sequence of Tools.
61
+
62
+ Args:
63
+ callables: Callables to generate code for
64
+ include_signatures: Include function signatures in documentation
65
+ include_docstrings: Include function docstrings in documentation
66
+
67
+ Returns:
68
+ ToolsetCodeGenerator instance
69
+ """
70
+ generators = [ToolCodeGenerator.from_callable(i) for i in callables]
71
+ return cls(generators, include_signatures, include_docstrings)
72
+
73
+ def generate_tool_description(self) -> str:
74
+ """Generate comprehensive tool description with available functions."""
75
+ if not self.generators:
76
+ return "Execute Python code (no tools available)"
77
+
78
+ return_models = self.generate_return_models()
79
+ parts = [
80
+ "Execute Python code with the following tools available as async functions:",
81
+ "",
82
+ ]
83
+
84
+ if return_models:
85
+ parts.extend([
86
+ "# Generated return type models",
87
+ return_models,
88
+ "",
89
+ "# Available functions:",
90
+ "",
91
+ ])
92
+
93
+ for generator in self.generators:
94
+ if self.include_signatures:
95
+ signature = generator.get_function_signature()
96
+ parts.append(f"async def {signature}:")
97
+ else:
98
+ parts.append(f"async def {generator.name}(...):")
99
+
100
+ if self.include_docstrings and generator.callable.__doc__:
101
+ indented_desc = " " + generator.callable.__doc__.replace(
102
+ "\n", "\n "
103
+ )
104
+ parts.append(f' """{indented_desc}"""')
105
+ parts.append("")
106
+
107
+ parts.append(USAGE)
108
+
109
+ return "\n".join(parts)
110
+
111
+ def generate_execution_namespace(self) -> dict[str, Any]:
112
+ """Build Python namespace with tool functions and generated models."""
113
+ namespace: dict[str, Any] = {"__builtins__": __builtins__, "_result": None}
114
+
115
+ # Add tool functions
116
+ for generator in self.generators:
117
+ namespace[generator.name] = NamespaceCallable.from_generator(generator)
118
+
119
+ # Add generated model classes to namespace
120
+ if models_code := self.generate_return_models():
121
+ with contextlib.suppress(Exception):
122
+ exec(models_code, namespace)
123
+
124
+ return namespace
125
+
126
+ def generate_return_models(self) -> str:
127
+ """Generate Pydantic models for tool return types."""
128
+ model_parts = [
129
+ code for g in self.generators if (code := g.generate_return_model())
130
+ ]
131
+ return "\n\n".join(model_parts) if model_parts else ""
132
+
133
+ def add_all_routes(self, app: FastAPI, path_prefix: str = "/tools") -> None:
134
+ """Add FastAPI routes for all tools.
135
+
136
+ Args:
137
+ app: FastAPI application instance
138
+ path_prefix: Path prefix for routes
139
+ """
140
+ for generator in self.generators:
141
+ generator.add_route_to_app(app, path_prefix)
142
+
143
+
144
+ if __name__ == "__main__":
145
+ import webbrowser
146
+
147
+ generator = ToolsetCodeGenerator.from_callables([webbrowser.open])
148
+ models = generator.generate_return_models()
149
+ print(models)
150
+ namespace = generator.generate_execution_namespace()
151
+ print(namespace)
schemez/functionschema.py CHANGED
@@ -12,7 +12,6 @@ import decimal
12
12
  import enum
13
13
  import inspect
14
14
  import ipaddress
15
- import logging
16
15
  from pathlib import Path
17
16
  import re
18
17
  import types
@@ -23,6 +22,7 @@ from uuid import UUID
23
22
  import docstring_parser
24
23
  import pydantic
25
24
 
25
+ from schemez import log
26
26
  from schemez.typedefs import (
27
27
  OpenAIFunctionDefinition,
28
28
  OpenAIFunctionTool,
@@ -34,7 +34,7 @@ if typing.TYPE_CHECKING:
34
34
  from schemez.typedefs import Property
35
35
 
36
36
 
37
- logger = logging.getLogger(__name__)
37
+ logger = log.get_logger(__name__)
38
38
 
39
39
 
40
40
  class FunctionType(enum.StrEnum):
schemez/helpers.py CHANGED
@@ -4,7 +4,6 @@ from __future__ import annotations
4
4
 
5
5
  import asyncio
6
6
  import importlib
7
- import os
8
7
  from pathlib import Path
9
8
  import shutil
10
9
  import subprocess
@@ -16,7 +15,6 @@ from pydantic import BaseModel
16
15
  from pydantic_core import to_json
17
16
 
18
17
 
19
- StrPath = str | os.PathLike[str]
20
18
  PythonVersion = Literal["3.13", "3.14", "3.15"]
21
19
 
22
20
  if TYPE_CHECKING:
schemez/schema.py CHANGED
@@ -2,22 +2,20 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import os
6
- from typing import TYPE_CHECKING, Any, Literal, Self
5
+ from enum import Enum
6
+ from typing import TYPE_CHECKING, Any, Literal, Optional, Self
7
7
 
8
- import anyenv
9
- from pydantic import BaseModel, ConfigDict
8
+ from pydantic import BaseModel, ConfigDict, Field, create_model
10
9
  import upath
11
10
 
12
11
 
13
12
  if TYPE_CHECKING:
14
13
  from collections.abc import Callable
15
14
 
16
- from llmling_agent.agent.agent import AgentType
17
15
  from llmling_agent.models.content import BaseContent
16
+ from upath.types import JoinablePathLike
18
17
 
19
18
 
20
- StrPath = str | os.PathLike[str]
21
19
  SourceType = Literal["pdf", "image"]
22
20
  PythonVersion = Literal["3.13", "3.14", "3.15"]
23
21
 
@@ -43,7 +41,9 @@ class Schema(BaseModel):
43
41
  return merge_models(self, other)
44
42
 
45
43
  @classmethod
46
- def from_yaml(cls, content: str, inherit_path: StrPath | None = None) -> Self:
44
+ def from_yaml(
45
+ cls, content: str, inherit_path: JoinablePathLike | None = None
46
+ ) -> Self:
47
47
  """Create from YAML string."""
48
48
  import yamling
49
49
 
@@ -68,41 +68,47 @@ class Schema(BaseModel):
68
68
  return get_function_model(func, name=name)
69
69
 
70
70
  @classmethod
71
- def from_vision_llm_sync(
72
- cls,
73
- file_content: bytes,
74
- source_type: SourceType = "pdf",
75
- model: str = "google-gla:gemini-2.0-flash",
76
- system_prompt: str = DEFAULT_SYSTEM_PROMPT,
77
- user_prompt: str = DEFAULT_USER_PROMPT,
78
- provider: AgentType = "pydantic_ai",
79
- ) -> Self:
80
- """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.
81
73
 
82
74
  Args:
83
- file_content: The document content to create a schema from
84
- source_type: The type of the document
85
- model: The AI model to use for schema extraction
86
- system_prompt: The system prompt to use for schema extraction
87
- user_prompt: The user prompt to use for schema extraction
88
- provider: The provider to use for schema extraction
75
+ json_schema: The JSON schema to create a schema from
89
76
 
90
77
  Returns:
91
- A new schema model class based on the document
78
+ A new schema model class based on the JSON schema
92
79
  """
93
- 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
94
84
 
95
- if source_type == "pdf":
96
- content: BaseContent = PDFBase64Content.from_bytes(file_content)
97
- else:
98
- content = ImageBase64Content.from_bytes(file_content)
99
- agent = Agent[None]( # type:ignore[var-annotated]
100
- model=model,
101
- system_prompt=system_prompt.format(name=cls.__name__),
102
- provider=provider,
103
- ).to_structured(cls)
104
- chat_message = anyenv.run_sync(agent.run(user_prompt, content))
105
- 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
106
112
 
107
113
  @classmethod
108
114
  async def from_vision_llm(
@@ -112,7 +118,6 @@ class Schema(BaseModel):
112
118
  model: str = "google-gla:gemini-2.0-flash",
113
119
  system_prompt: str = DEFAULT_SYSTEM_PROMPT,
114
120
  user_prompt: str = DEFAULT_USER_PROMPT,
115
- provider: AgentType = "pydantic_ai",
116
121
  ) -> Self:
117
122
  """Create a schema model from a document using AI.
118
123
 
@@ -122,7 +127,6 @@ class Schema(BaseModel):
122
127
  model: The AI model to use for schema extraction
123
128
  system_prompt: The system prompt to use for schema extraction
124
129
  user_prompt: The user prompt to use for schema extraction
125
- provider: The provider to use for schema extraction
126
130
 
127
131
  Returns:
128
132
  A new schema model class based on the document
@@ -133,45 +137,11 @@ class Schema(BaseModel):
133
137
  content: BaseContent = PDFBase64Content.from_bytes(file_content)
134
138
  else:
135
139
  content = ImageBase64Content.from_bytes(file_content)
136
- agent = Agent[None]( # type:ignore[var-annotated]
137
- model=model,
138
- system_prompt=system_prompt.format(name=cls.__name__),
139
- provider=provider,
140
- ).to_structured(cls)
140
+ prompt = system_prompt.format(name=cls.__name__)
141
+ agent = Agent(model=model, system_prompt=prompt, output_type=cls)
141
142
  chat_message = await agent.run(user_prompt, content)
142
143
  return chat_message.content
143
144
 
144
- @classmethod
145
- def from_llm_sync(
146
- cls,
147
- text: str,
148
- model: str = "google-gla:gemini-2.0-flash",
149
- system_prompt: str = DEFAULT_SYSTEM_PROMPT,
150
- user_prompt: str = DEFAULT_USER_PROMPT,
151
- provider: AgentType = "pydantic_ai",
152
- ) -> Self:
153
- """Create a schema model from a text snippet using AI.
154
-
155
- Args:
156
- text: The text to create a schema from
157
- model: The AI model to use for schema extraction
158
- system_prompt: The system prompt to use for schema extraction
159
- user_prompt: The user prompt to use for schema extraction
160
- provider: The provider to use for schema extraction
161
-
162
- Returns:
163
- A new schema model class based on the document
164
- """
165
- from llmling_agent import Agent
166
-
167
- agent = Agent[None]( # type:ignore[var-annotated]
168
- model=model,
169
- system_prompt=system_prompt.format(name=cls.__name__),
170
- provider=provider,
171
- ).to_structured(cls)
172
- chat_message = anyenv.run_sync(agent.run(user_prompt, text))
173
- return chat_message.content
174
-
175
145
  @classmethod
176
146
  async def from_llm(
177
147
  cls,
@@ -179,7 +149,6 @@ class Schema(BaseModel):
179
149
  model: str = "google-gla:gemini-2.0-flash",
180
150
  system_prompt: str = DEFAULT_SYSTEM_PROMPT,
181
151
  user_prompt: str = DEFAULT_USER_PROMPT,
182
- provider: AgentType = "pydantic_ai",
183
152
  ) -> Self:
184
153
  """Create a schema model from a text snippet using AI.
185
154
 
@@ -188,18 +157,14 @@ class Schema(BaseModel):
188
157
  model: The AI model to use for schema extraction
189
158
  system_prompt: The system prompt to use for schema extraction
190
159
  user_prompt: The user prompt to use for schema extraction
191
- provider: The provider to use for schema extraction
192
160
 
193
161
  Returns:
194
162
  A new schema model class based on the document
195
163
  """
196
164
  from llmling_agent import Agent
197
165
 
198
- agent = Agent[None]( # type:ignore[var-annotated]
199
- model=model,
200
- system_prompt=system_prompt.format(name=cls.__name__),
201
- provider=provider,
202
- ).to_structured(cls)
166
+ prompt = system_prompt.format(name=cls.__name__)
167
+ agent = Agent(model=model, system_prompt=prompt, output_type=cls)
203
168
  chat_message = await agent.run(user_prompt, text)
204
169
  return chat_message.content
205
170
 
@@ -233,7 +198,7 @@ class Schema(BaseModel):
233
198
  )
234
199
  return yamling.dump_yaml(text)
235
200
 
236
- def save(self, path: StrPath, overwrite: bool = False) -> None:
201
+ def save(self, path: JoinablePathLike, overwrite: bool = False) -> None:
237
202
  """Save configuration to a YAML file.
238
203
 
239
204
  Args:
@@ -279,3 +244,87 @@ class Schema(BaseModel):
279
244
  class_name=class_name,
280
245
  target_python_version=target_python_version,
281
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__)
@@ -3,7 +3,6 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import asyncio
6
- import logging
7
6
  from pathlib import Path
8
7
  import time
9
8
  from typing import TYPE_CHECKING, Any
@@ -12,6 +11,7 @@ from pydantic import BaseModel
12
11
  from pydantic_core import from_json
13
12
  from upath import UPath
14
13
 
14
+ from schemez import log
15
15
  from schemez.functionschema import FunctionSchema
16
16
  from schemez.tool_executor.helpers import clean_generated_code, generate_input_model
17
17
 
@@ -20,11 +20,12 @@ if TYPE_CHECKING:
20
20
  from collections.abc import Callable, Sequence
21
21
 
22
22
  from fastapi import FastAPI
23
+ from upath.types import JoinablePathLike
23
24
 
24
25
  from schemez.tool_executor.types import ToolHandler
25
26
 
26
27
 
27
- logger = logging.getLogger(__name__)
28
+ logger = log.get_logger(__name__)
28
29
 
29
30
 
30
31
  class HttpToolExecutor:
@@ -32,7 +33,7 @@ class HttpToolExecutor:
32
33
 
33
34
  def __init__(
34
35
  self,
35
- schemas: Sequence[dict[str, Any] | Path],
36
+ schemas: Sequence[dict[str, Any] | JoinablePathLike],
36
37
  handler: ToolHandler,
37
38
  base_url: str = "http://localhost:8000",
38
39
  ):
@@ -61,7 +62,7 @@ class HttpToolExecutor:
61
62
  match schema:
62
63
  case dict():
63
64
  loaded_schemas.append(schema)
64
- case str() | Path():
65
+ case str() | Path() | UPath():
65
66
  text = UPath(schema).read_text("utf-8")
66
67
  loaded_schemas.append(from_json(text))
67
68
  case _:
@@ -264,7 +265,7 @@ from datetime import datetime
264
265
  uvicorn.run(app, host=host, port=port)
265
266
  return None
266
267
 
267
- async def save_to_files(self, output_dir: Path) -> dict[str, Path]:
268
+ async def save_to_files(self, output_dir: JoinablePathLike) -> dict[str, UPath]:
268
269
  """Save generated code to files.
269
270
 
270
271
  Args:
@@ -273,7 +274,7 @@ from datetime import datetime
273
274
  Returns:
274
275
  Dictionary mapping file types to paths
275
276
  """
276
- output_dir = Path(output_dir)
277
+ output_dir = UPath(output_dir)
277
278
  output_dir.mkdir(parents=True, exist_ok=True)
278
279
 
279
280
  saved_files = {}
@@ -2,13 +2,13 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import logging
6
5
  import time
7
6
 
7
+ from schemez import log
8
8
  from schemez.helpers import model_to_python_code
9
9
 
10
10
 
11
- logger = logging.getLogger(__name__)
11
+ logger = log.get_logger(__name__)
12
12
 
13
13
 
14
14
  async def generate_input_model(schema_dict: dict) -> tuple[str, str]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: schemez
3
- Version: 1.2.3
3
+ Version: 1.4.0
4
4
  Summary: Pydantic shim for config stuff
5
5
  Keywords:
6
6
  Author: Philipp Temminghoff
@@ -0,0 +1,30 @@
1
+ schemez/__init__.py,sha256=UwLAA0B5whh63w9sYQvs_b_NNm6TBCm86-zPQiDxj3g,1799
2
+ schemez/bind_kwargs.py,sha256=ChyArgNa5R8VdwSJmmrQItMH9Ld6hStWBISw-T1wyws,6228
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=yM29h4nccZEV21FMSZaKJI-IBdDezxpHld3n4WFWfkM,9627
8
+ schemez/code_generation/toolset_code_generator.py,sha256=Uw4UEjLzzHsm6YzsPFaEPxYHpFgIv_DKaFVP0XePjjI,5083
9
+ schemez/convert.py,sha256=3sOxOgDaFzV7uiOUSM6_Sy0YlafIlZRSevs5y2vT1Kw,4403
10
+ schemez/create_type.py,sha256=wrdqdzXtfxZfsgp9IroldoYGTJs_Rdli8TiscqhV2bI,11647
11
+ schemez/docstrings.py,sha256=kmd660wcomXzKac0SSNYxPRNbVCUovrpmE9jwnVRS6c,4115
12
+ schemez/executable.py,sha256=YM4WcmRyJ9CpzKpNgS0A-Ri0Jd7mAzfHmoaXONI-mIs,7134
13
+ schemez/functionschema.py,sha256=D5nJIOQYwfOwuNSHLIoqNZtYkA1Y0DIFH18qMI6--ik,26141
14
+ schemez/helpers.py,sha256=YFx7UKYDI_sn5sVGAzzl5rcB26iBvLatvZlEFsQM5rc,7874
15
+ schemez/log.py,sha256=i0SDbIfWmuC_nfJdQOYAdUYaR0TBk3Mhu-K3M-lnAM4,364
16
+ schemez/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ schemez/pydantic_types.py,sha256=8vgSl8i2z9n0fB-8AJj-D3TBByEWE5IxItBxQ0XwXFI,1640
18
+ schemez/schema.py,sha256=OUiMNkoJZzsRQ2hTdKLvHFrmeoSZZhtbgnV4EjeIPA4,11552
19
+ schemez/schema_generators.py,sha256=Gze7S7dQkTsl_1ckeHLXPxx4jQo7RB6hHQM-5fpAsrA,6973
20
+ schemez/schemadef/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
+ schemez/schemadef/schemadef.py,sha256=FtD7TOnYxiuYOIfadRHKkkbZn98mWFb0_lKfPsPR-hI,14393
22
+ schemez/tool_executor/__init__.py,sha256=7wLjhA1NGekTMsiIfWLAv6J7qYhWDlanH9DKU4v1c6c,263
23
+ schemez/tool_executor/executor.py,sha256=0VOY9N4Epqdv_MYU-BoDiTKbFiyIwGk_pfe5JcxJq4o,10497
24
+ schemez/tool_executor/helpers.py,sha256=zxfI9tUkUx8Dy9fNP89-2kqfV8eZwQ3re2Gmd-oekb0,1476
25
+ schemez/tool_executor/types.py,sha256=l2DxUIEHP9bjLnEaXZ6X428cSviicTDJsc3wfSNqKxg,675
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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.2
2
+ Generator: uv 0.9.8
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,25 +0,0 @@
1
- schemez/__init__.py,sha256=KkwJF8pfbzw_4cBxwXuUyXIExqdxS8iumvyAL-JUED4,1669
2
- schemez/bind_kwargs.py,sha256=ChyArgNa5R8VdwSJmmrQItMH9Ld6hStWBISw-T1wyws,6228
3
- schemez/code.py,sha256=usZLov9i5KpK1W2VJxngUzeetgrINtodiooG_AxN-y4,2072
4
- schemez/convert.py,sha256=3sOxOgDaFzV7uiOUSM6_Sy0YlafIlZRSevs5y2vT1Kw,4403
5
- schemez/create_type.py,sha256=wrdqdzXtfxZfsgp9IroldoYGTJs_Rdli8TiscqhV2bI,11647
6
- schemez/docstrings.py,sha256=kmd660wcomXzKac0SSNYxPRNbVCUovrpmE9jwnVRS6c,4115
7
- schemez/executable.py,sha256=YM4WcmRyJ9CpzKpNgS0A-Ri0Jd7mAzfHmoaXONI-mIs,7134
8
- schemez/functionschema.py,sha256=NXQsZKeo7GRr7gnhEMHJvnjZhQktn9Kjf1W8xWyWh5Y,26135
9
- schemez/helpers.py,sha256=_4-S8n7m3yajVYdYckSyrG1GsfMqCOfm5O-bGkYI1qc,7917
10
- schemez/log.py,sha256=i0SDbIfWmuC_nfJdQOYAdUYaR0TBk3Mhu-K3M-lnAM4,364
11
- schemez/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- schemez/pydantic_types.py,sha256=8vgSl8i2z9n0fB-8AJj-D3TBByEWE5IxItBxQ0XwXFI,1640
13
- schemez/schema.py,sha256=u6SDhYDtfCjgy2Aa-_MDLLNcUfEXbeye4T-W6s3AED8,9558
14
- schemez/schema_generators.py,sha256=Gze7S7dQkTsl_1ckeHLXPxx4jQo7RB6hHQM-5fpAsrA,6973
15
- schemez/schemadef/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- schemez/schemadef/schemadef.py,sha256=FtD7TOnYxiuYOIfadRHKkkbZn98mWFb0_lKfPsPR-hI,14393
17
- schemez/tool_executor/__init__.py,sha256=7wLjhA1NGekTMsiIfWLAv6J7qYhWDlanH9DKU4v1c6c,263
18
- schemez/tool_executor/executor.py,sha256=lQFnVO5J64nFNEsy82jRS-q52PaU3fWsnuQddaLOo6c,10410
19
- schemez/tool_executor/helpers.py,sha256=jxBv1G_R8rQU3ACHGgfVEecqO0DSZQ6qUIHcn25Ucc4,1470
20
- schemez/tool_executor/types.py,sha256=l2DxUIEHP9bjLnEaXZ6X428cSviicTDJsc3wfSNqKxg,675
21
- schemez/typedefs.py,sha256=3OAUQ1nin9nlsOcTPAO5xrsOqVUfwsH_7_cexQYREus,6091
22
- schemez-1.2.3.dist-info/licenses/LICENSE,sha256=AteGCH9r177TxxrOFEiOARrastASsf7yW6MQxlAHdwA,1078
23
- schemez-1.2.3.dist-info/WHEEL,sha256=X16MKk8bp2DRsAuyteHJ-9qOjzmnY0x1aj0P1ftqqWA,78
24
- schemez-1.2.3.dist-info/METADATA,sha256=VPcDp5yYQYgz_YPcUsmDFhVh3-oeL7JTOdS4cAw0Aio,11616
25
- schemez-1.2.3.dist-info/RECORD,,