schemez 1.2.0__py3-none-any.whl → 1.2.3__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.
- schemez/convert.py +1 -1
- schemez/functionschema.py +18 -20
- schemez/helpers.py +15 -44
- schemez/log.py +17 -0
- schemez/tool_executor/__init__.py +8 -0
- schemez/tool_executor/executor.py +322 -0
- schemez/tool_executor/helpers.py +46 -0
- schemez/tool_executor/types.py +28 -0
- schemez/typedefs.py +2 -2
- schemez-1.2.3.dist-info/METADATA +340 -0
- schemez-1.2.3.dist-info/RECORD +25 -0
- {schemez-1.2.0.dist-info → schemez-1.2.3.dist-info}/WHEEL +1 -1
- schemez-1.2.3.dist-info/licenses/LICENSE +22 -0
- schemez-1.2.0.dist-info/METADATA +0 -86
- schemez-1.2.0.dist-info/RECORD +0 -19
schemez/convert.py
CHANGED
@@ -64,7 +64,7 @@ def get_function_model(func: AnyCallable, *, name: str | None = None) -> type[Sc
|
|
64
64
|
|
65
65
|
for param_name, param in sig.parameters.items():
|
66
66
|
# Skip self/cls for methods
|
67
|
-
if param_name in
|
67
|
+
if param_name in {"self", "cls"}:
|
68
68
|
continue
|
69
69
|
|
70
70
|
type_hint = hints.get(param_name, Any)
|
schemez/functionschema.py
CHANGED
@@ -37,7 +37,7 @@ if typing.TYPE_CHECKING:
|
|
37
37
|
logger = logging.getLogger(__name__)
|
38
38
|
|
39
39
|
|
40
|
-
class FunctionType(
|
40
|
+
class FunctionType(enum.StrEnum):
|
41
41
|
"""Enum representing different function types."""
|
42
42
|
|
43
43
|
SYNC = "sync"
|
@@ -119,6 +119,9 @@ class FunctionSchema(pydantic.BaseModel):
|
|
119
119
|
required = self.parameters.get("required", self.required)
|
120
120
|
|
121
121
|
for name, details in properties.items():
|
122
|
+
if name.startswith("_"): # TODO: kwarg for renaming instead perhaps?
|
123
|
+
logger.debug("Skipping parameter %s due to leading underscore", name)
|
124
|
+
continue
|
122
125
|
# Get base type
|
123
126
|
if "enum" in details:
|
124
127
|
values = tuple(details["enum"]) # type: ignore
|
@@ -246,19 +249,14 @@ class FunctionSchema(pydantic.BaseModel):
|
|
246
249
|
RuntimeError: If datamodel-codegen is not available
|
247
250
|
subprocess.CalledProcessError: If code generation fails
|
248
251
|
"""
|
252
|
+
import shutil
|
249
253
|
import subprocess
|
250
254
|
import tempfile
|
251
255
|
|
252
|
-
|
253
|
-
|
254
|
-
subprocess.run(
|
255
|
-
["datamodel-codegen", "--version"],
|
256
|
-
check=True,
|
257
|
-
capture_output=True,
|
258
|
-
)
|
259
|
-
except (subprocess.CalledProcessError, FileNotFoundError) as e:
|
256
|
+
# Check if datamodel-codegen is available
|
257
|
+
if not shutil.which("datamodel-codegen"):
|
260
258
|
msg = "datamodel-codegen not available"
|
261
|
-
raise RuntimeError(msg)
|
259
|
+
raise RuntimeError(msg)
|
262
260
|
|
263
261
|
name = class_name or f"{self.name.title()}Response"
|
264
262
|
|
@@ -393,7 +391,7 @@ def _is_optional_type(typ: type) -> TypeGuard[type]:
|
|
393
391
|
True if the type is Optional, False otherwise
|
394
392
|
"""
|
395
393
|
origin = typing.get_origin(typ)
|
396
|
-
if origin not in
|
394
|
+
if origin not in {typing.Union, types.UnionType}: # pyright: ignore
|
397
395
|
return False
|
398
396
|
args = typing.get_args(typ)
|
399
397
|
# Check if any of the union members is None or NoneType
|
@@ -448,7 +446,7 @@ def _resolve_type_annotation(
|
|
448
446
|
args = typing.get_args(typ)
|
449
447
|
|
450
448
|
# Handle Union types (including Optional)
|
451
|
-
if origin in
|
449
|
+
if origin in {typing.Union, types.UnionType}: # pyright: ignore
|
452
450
|
# For Optional (union with None), filter out None type
|
453
451
|
non_none_types = [t for t in args if t is not type(None)]
|
454
452
|
if non_none_types:
|
@@ -495,7 +493,7 @@ def _resolve_type_annotation(
|
|
495
493
|
schema["required"] = required
|
496
494
|
# Handle mappings - updated check
|
497
495
|
elif (
|
498
|
-
origin in
|
496
|
+
origin in {dict, typing.Dict} # noqa: UP006
|
499
497
|
or (origin is not None and isinstance(origin, type) and issubclass(origin, dict))
|
500
498
|
):
|
501
499
|
schema["type"] = "object"
|
@@ -503,14 +501,14 @@ def _resolve_type_annotation(
|
|
503
501
|
schema["additionalProperties"] = True
|
504
502
|
|
505
503
|
# Handle sequences
|
506
|
-
elif origin in
|
504
|
+
elif origin in {
|
507
505
|
list,
|
508
506
|
set,
|
509
507
|
tuple,
|
510
508
|
frozenset,
|
511
509
|
typing.List, # noqa: UP006 # pyright: ignore
|
512
510
|
typing.Set, # noqa: UP006 # pyright: ignore
|
513
|
-
|
511
|
+
} or (
|
514
512
|
origin is not None
|
515
513
|
and origin.__module__ == "collections.abc"
|
516
514
|
and origin.__name__ in {"Sequence", "MutableSequence", "Collection"}
|
@@ -534,11 +532,11 @@ def _resolve_type_annotation(
|
|
534
532
|
schema["enum"] = [e.value for e in typ]
|
535
533
|
|
536
534
|
# Basic types
|
537
|
-
elif typ in
|
535
|
+
elif typ in {str, Path, UUID, re.Pattern}:
|
538
536
|
schema["type"] = "string"
|
539
537
|
elif typ is int:
|
540
538
|
schema["type"] = "integer"
|
541
|
-
elif typ in
|
539
|
+
elif typ in {float, decimal.Decimal}:
|
542
540
|
schema["type"] = "number"
|
543
541
|
elif typ is bool:
|
544
542
|
schema["type"] = "boolean"
|
@@ -696,10 +694,10 @@ def create_schema(
|
|
696
694
|
# Skip the first parameter for bound methods
|
697
695
|
if skip_first and i == 0:
|
698
696
|
continue
|
699
|
-
if param.kind in
|
697
|
+
if param.kind in {
|
700
698
|
inspect.Parameter.VAR_POSITIONAL,
|
701
699
|
inspect.Parameter.VAR_KEYWORD,
|
702
|
-
|
700
|
+
}:
|
703
701
|
continue
|
704
702
|
|
705
703
|
param_doc = next(
|
@@ -726,7 +724,7 @@ def create_schema(
|
|
726
724
|
function_type = _determine_function_type(func)
|
727
725
|
return_hint = hints.get("return", Any)
|
728
726
|
|
729
|
-
if function_type in
|
727
|
+
if function_type in {FunctionType.SYNC_GENERATOR, FunctionType.ASYNC_GENERATOR}:
|
730
728
|
element_type = next(
|
731
729
|
(t for t in typing.get_args(return_hint) if t is not type(None)),
|
732
730
|
Any,
|
schemez/helpers.py
CHANGED
@@ -6,6 +6,7 @@ import asyncio
|
|
6
6
|
import importlib
|
7
7
|
import os
|
8
8
|
from pathlib import Path
|
9
|
+
import shutil
|
9
10
|
import subprocess
|
10
11
|
import sys
|
11
12
|
import tempfile
|
@@ -177,53 +178,17 @@ def resolve_type_string(type_string: str, safe: bool = True) -> type:
|
|
177
178
|
raise ValueError(msg) from e
|
178
179
|
|
179
180
|
|
180
|
-
async def _detect_command(command: str, *, test_flag: str = "--version") -> list[str]:
|
181
|
-
"""Detect the correct command prefix for running a command.
|
182
|
-
|
183
|
-
Tries 'uv run' first, then falls back to direct execution.
|
184
|
-
|
185
|
-
Args:
|
186
|
-
command: The command to detect
|
187
|
-
test_flag: Flag to test command availability with
|
188
|
-
|
189
|
-
Returns:
|
190
|
-
Command prefix list (empty for direct execution)
|
191
|
-
|
192
|
-
Raises:
|
193
|
-
RuntimeError: If command is not available
|
194
|
-
"""
|
195
|
-
cmd_prefixes = [["uv", "run"], []]
|
196
|
-
|
197
|
-
for prefix in cmd_prefixes:
|
198
|
-
try:
|
199
|
-
proc = await asyncio.create_subprocess_exec(
|
200
|
-
*prefix,
|
201
|
-
command,
|
202
|
-
test_flag,
|
203
|
-
stdout=asyncio.subprocess.PIPE,
|
204
|
-
stderr=asyncio.subprocess.PIPE,
|
205
|
-
)
|
206
|
-
await proc.communicate()
|
207
|
-
if proc.returncode == 0:
|
208
|
-
return prefix
|
209
|
-
except FileNotFoundError:
|
210
|
-
continue
|
211
|
-
|
212
|
-
msg = f"{command} not available (tried both 'uv run' and direct execution)"
|
213
|
-
raise RuntimeError(msg)
|
214
|
-
|
215
|
-
|
216
181
|
async def model_to_python_code(
|
217
|
-
model: type[BaseModel],
|
182
|
+
model: type[BaseModel] | dict[str, Any],
|
218
183
|
*,
|
219
184
|
class_name: str | None = None,
|
220
185
|
target_python_version: PythonVersion | None = None,
|
221
186
|
model_type: str = "pydantic.BaseModel",
|
222
187
|
) -> str:
|
223
|
-
"""Convert a BaseModel to Python code asynchronously.
|
188
|
+
"""Convert a BaseModel or schema dict to Python code asynchronously.
|
224
189
|
|
225
190
|
Args:
|
226
|
-
model: The BaseModel class to convert
|
191
|
+
model: The BaseModel class or schema dictionary to convert
|
227
192
|
class_name: Optional custom class name for the generated code
|
228
193
|
target_python_version: Target Python version for code generation.
|
229
194
|
Defaults to current system Python version.
|
@@ -236,10 +201,17 @@ async def model_to_python_code(
|
|
236
201
|
RuntimeError: If datamodel-codegen is not available
|
237
202
|
subprocess.CalledProcessError: If code generation fails
|
238
203
|
"""
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
204
|
+
# Check if datamodel-codegen is available
|
205
|
+
if not shutil.which("datamodel-codegen"):
|
206
|
+
msg = "datamodel-codegen not available"
|
207
|
+
raise RuntimeError(msg)
|
208
|
+
|
209
|
+
if isinstance(model, dict):
|
210
|
+
schema = model
|
211
|
+
name = class_name or "GeneratedModel"
|
212
|
+
else:
|
213
|
+
schema = model.model_json_schema()
|
214
|
+
name = class_name or model.__name__
|
243
215
|
py = target_python_version or f"{sys.version_info.major}.{sys.version_info.minor}"
|
244
216
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
245
217
|
# Use pydantic_core.to_json for proper schema serialization
|
@@ -267,7 +239,6 @@ async def model_to_python_code(
|
|
267
239
|
|
268
240
|
try: # Generate model using datamodel-codegen
|
269
241
|
proc = await asyncio.create_subprocess_exec(
|
270
|
-
*working_prefix,
|
271
242
|
"datamodel-codegen",
|
272
243
|
*args,
|
273
244
|
stdout=asyncio.subprocess.PIPE,
|
schemez/log.py
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
"""Logging configuration for schemez."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
import logging
|
6
|
+
|
7
|
+
|
8
|
+
def get_logger(name: str) -> logging.Logger:
|
9
|
+
"""Get a logger for the given name.
|
10
|
+
|
11
|
+
Args:
|
12
|
+
name: The name of the logger, will be prefixed with 'llmling_agent.'
|
13
|
+
|
14
|
+
Returns:
|
15
|
+
A logger instance
|
16
|
+
"""
|
17
|
+
return logging.getLogger(f"schemez.{name}")
|
@@ -0,0 +1,8 @@
|
|
1
|
+
"""Tool executor module for HTTP tool generation and execution."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
from schemez.tool_executor.executor import HttpToolExecutor
|
6
|
+
from schemez.tool_executor.types import ToolHandler
|
7
|
+
|
8
|
+
__all__ = ["HttpToolExecutor", "ToolHandler"]
|
@@ -0,0 +1,322 @@
|
|
1
|
+
"""HTTP tool executor for managing tool generation and execution."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
import asyncio
|
6
|
+
import logging
|
7
|
+
from pathlib import Path
|
8
|
+
import time
|
9
|
+
from typing import TYPE_CHECKING, Any
|
10
|
+
|
11
|
+
from pydantic import BaseModel
|
12
|
+
from pydantic_core import from_json
|
13
|
+
from upath import UPath
|
14
|
+
|
15
|
+
from schemez.functionschema import FunctionSchema
|
16
|
+
from schemez.tool_executor.helpers import clean_generated_code, generate_input_model
|
17
|
+
|
18
|
+
|
19
|
+
if TYPE_CHECKING:
|
20
|
+
from collections.abc import Callable, Sequence
|
21
|
+
|
22
|
+
from fastapi import FastAPI
|
23
|
+
|
24
|
+
from schemez.tool_executor.types import ToolHandler
|
25
|
+
|
26
|
+
|
27
|
+
logger = logging.getLogger(__name__)
|
28
|
+
|
29
|
+
|
30
|
+
class HttpToolExecutor:
|
31
|
+
"""Manages HTTP tool generation and execution."""
|
32
|
+
|
33
|
+
def __init__(
|
34
|
+
self,
|
35
|
+
schemas: Sequence[dict[str, Any] | Path],
|
36
|
+
handler: ToolHandler,
|
37
|
+
base_url: str = "http://localhost:8000",
|
38
|
+
):
|
39
|
+
"""Initialize the tool executor.
|
40
|
+
|
41
|
+
Args:
|
42
|
+
schemas: List of tool schema dictionaries or file paths
|
43
|
+
handler: User-provided tool handler function
|
44
|
+
base_url: Base URL for the tool server
|
45
|
+
"""
|
46
|
+
self.schemas = schemas
|
47
|
+
self.handler = handler
|
48
|
+
self.base_url = base_url
|
49
|
+
|
50
|
+
# Cached artifacts
|
51
|
+
self._tool_mappings: dict[str, str] | None = None
|
52
|
+
self._tools_code: str | None = None
|
53
|
+
self._server_app: FastAPI | None = None
|
54
|
+
self._tool_functions: dict[str, Callable] | None = None
|
55
|
+
|
56
|
+
async def _load_schemas(self) -> list[dict]:
|
57
|
+
"""Load and normalize schemas from various sources."""
|
58
|
+
loaded_schemas = []
|
59
|
+
|
60
|
+
for schema in self.schemas:
|
61
|
+
match schema:
|
62
|
+
case dict():
|
63
|
+
loaded_schemas.append(schema)
|
64
|
+
case str() | Path():
|
65
|
+
text = UPath(schema).read_text("utf-8")
|
66
|
+
loaded_schemas.append(from_json(text))
|
67
|
+
case _:
|
68
|
+
msg = f"Invalid schema type: {type(schema)}"
|
69
|
+
raise TypeError(msg)
|
70
|
+
|
71
|
+
return loaded_schemas
|
72
|
+
|
73
|
+
async def _get_tool_mappings(self) -> dict[str, str]:
|
74
|
+
"""Get tool name to input class mappings."""
|
75
|
+
if self._tool_mappings is None:
|
76
|
+
self._tool_mappings = {}
|
77
|
+
schemas = await self._load_schemas()
|
78
|
+
|
79
|
+
for schema_dict in schemas:
|
80
|
+
function_schema = FunctionSchema.from_dict(schema_dict)
|
81
|
+
name = "".join(word.title() for word in function_schema.name.split("_"))
|
82
|
+
input_class_name = f"{name}Input"
|
83
|
+
self._tool_mappings[function_schema.name] = input_class_name
|
84
|
+
|
85
|
+
return self._tool_mappings
|
86
|
+
|
87
|
+
async def _generate_http_wrapper(
|
88
|
+
self, schema_dict: dict, input_class_name: str
|
89
|
+
) -> str:
|
90
|
+
"""Generate HTTP wrapper function."""
|
91
|
+
name = schema_dict["name"]
|
92
|
+
description = schema_dict.get("description", "")
|
93
|
+
|
94
|
+
return f'''
|
95
|
+
async def {name}(input: {input_class_name}) -> str:
|
96
|
+
"""{description}
|
97
|
+
|
98
|
+
Args:
|
99
|
+
input: Function parameters
|
100
|
+
|
101
|
+
Returns:
|
102
|
+
String response from the tool server
|
103
|
+
"""
|
104
|
+
import httpx
|
105
|
+
async with httpx.AsyncClient() as client:
|
106
|
+
response = await client.post(
|
107
|
+
"{self.base_url}/tools/{name}",
|
108
|
+
json=input.model_dump(),
|
109
|
+
timeout=30.0
|
110
|
+
)
|
111
|
+
response.raise_for_status()
|
112
|
+
return response.text
|
113
|
+
'''
|
114
|
+
|
115
|
+
async def generate_tools_code(self) -> str:
|
116
|
+
"""Generate HTTP wrapper tools as Python code."""
|
117
|
+
if self._tools_code is not None:
|
118
|
+
return self._tools_code
|
119
|
+
|
120
|
+
start_time = time.time()
|
121
|
+
logger.info("Starting tools code generation")
|
122
|
+
|
123
|
+
schemas = await self._load_schemas()
|
124
|
+
code_parts: list[str] = []
|
125
|
+
await self._get_tool_mappings()
|
126
|
+
|
127
|
+
# Module header
|
128
|
+
header = '''"""Generated HTTP wrapper tools."""
|
129
|
+
|
130
|
+
from __future__ import annotations
|
131
|
+
|
132
|
+
from pydantic import BaseModel, Field
|
133
|
+
from typing import Literal, List, Any
|
134
|
+
from datetime import datetime
|
135
|
+
|
136
|
+
'''
|
137
|
+
code_parts.append(header)
|
138
|
+
|
139
|
+
# Generate models and wrappers for each tool
|
140
|
+
all_exports = []
|
141
|
+
for schema_dict in schemas:
|
142
|
+
function_schema = FunctionSchema.from_dict(schema_dict)
|
143
|
+
|
144
|
+
schema_data = {
|
145
|
+
"name": function_schema.name,
|
146
|
+
"description": function_schema.description,
|
147
|
+
"parameters": function_schema.parameters,
|
148
|
+
}
|
149
|
+
|
150
|
+
# Generate input model (strip future imports from generated code)
|
151
|
+
input_code, input_class_name = await generate_input_model(schema_data)
|
152
|
+
# Remove future imports and datamodel-codegen header from individual models
|
153
|
+
cleaned_input_code = clean_generated_code(input_code)
|
154
|
+
code_parts.append(cleaned_input_code)
|
155
|
+
wrapper_code = await self._generate_http_wrapper(
|
156
|
+
schema_data, input_class_name
|
157
|
+
)
|
158
|
+
code_parts.append(wrapper_code)
|
159
|
+
|
160
|
+
all_exports.extend([input_class_name, function_schema.name])
|
161
|
+
|
162
|
+
# Add exports
|
163
|
+
code_parts.append(f"\n__all__ = {all_exports}\n")
|
164
|
+
|
165
|
+
self._tools_code = "\n".join(code_parts)
|
166
|
+
elapsed = time.time() - start_time
|
167
|
+
logger.info(f"Tools code generation completed in {elapsed:.2f}s") # noqa: G004
|
168
|
+
return self._tools_code
|
169
|
+
|
170
|
+
async def generate_server_app(self) -> FastAPI:
|
171
|
+
"""Create configured FastAPI server."""
|
172
|
+
from fastapi import FastAPI, HTTPException
|
173
|
+
|
174
|
+
if self._server_app is not None:
|
175
|
+
return self._server_app
|
176
|
+
|
177
|
+
tool_mappings = await self._get_tool_mappings()
|
178
|
+
app = FastAPI(title="Tool Server", version="1.0.0")
|
179
|
+
|
180
|
+
@app.post("/tools/{tool_name}")
|
181
|
+
async def handle_tool_call(tool_name: str, input_data: dict) -> str:
|
182
|
+
"""Generic endpoint that routes all tool calls to user handler."""
|
183
|
+
# Validate tool exists
|
184
|
+
if tool_name not in tool_mappings:
|
185
|
+
tools = list(tool_mappings.keys())
|
186
|
+
detail = f"Tool '{tool_name}' not found. Available: {tools}"
|
187
|
+
raise HTTPException(status_code=404, detail=detail)
|
188
|
+
|
189
|
+
# Create a simple BaseModel for validation
|
190
|
+
class DynamicInput(BaseModel):
|
191
|
+
pass
|
192
|
+
|
193
|
+
# Add fields dynamically (basic validation only)
|
194
|
+
dynamic_input = DynamicInput(**input_data)
|
195
|
+
|
196
|
+
# Call user's handler
|
197
|
+
try:
|
198
|
+
return await self.handler(tool_name, dynamic_input)
|
199
|
+
except Exception as e:
|
200
|
+
msg = f"Tool execution failed: {e}"
|
201
|
+
raise HTTPException(status_code=500, detail=msg) from e
|
202
|
+
|
203
|
+
self._server_app = app
|
204
|
+
return app
|
205
|
+
|
206
|
+
async def get_tool_functions(self) -> dict[str, Callable]:
|
207
|
+
"""Get ready-to-use tool functions."""
|
208
|
+
if self._tool_functions is not None:
|
209
|
+
return self._tool_functions
|
210
|
+
|
211
|
+
start_time = time.time()
|
212
|
+
logger.info("Starting tool functions generation")
|
213
|
+
|
214
|
+
# Generate and execute the tools code
|
215
|
+
tools_code = await self.generate_tools_code()
|
216
|
+
logger.debug("Generated %s characters of code", len(tools_code))
|
217
|
+
|
218
|
+
# Create namespace and execute
|
219
|
+
namespace = {
|
220
|
+
"BaseModel": BaseModel,
|
221
|
+
"Field": BaseModel.model_fields_set,
|
222
|
+
"Literal": Any,
|
223
|
+
"List": list,
|
224
|
+
"datetime": __import__("datetime").datetime,
|
225
|
+
}
|
226
|
+
|
227
|
+
logger.debug("Executing generated tools code...")
|
228
|
+
exec_start = time.time()
|
229
|
+
exec(tools_code, namespace)
|
230
|
+
exec_elapsed = time.time() - exec_start
|
231
|
+
logger.debug("Code execution completed in %.2fs", exec_elapsed)
|
232
|
+
|
233
|
+
# Extract tool functions
|
234
|
+
tool_mappings = await self._get_tool_mappings()
|
235
|
+
self._tool_functions = {
|
236
|
+
tool_name: namespace[tool_name]
|
237
|
+
for tool_name in tool_mappings
|
238
|
+
if tool_name in namespace
|
239
|
+
}
|
240
|
+
|
241
|
+
elapsed = time.time() - start_time
|
242
|
+
logger.info(f"Tool functions generation completed in {elapsed:.2f}s") # noqa: G004
|
243
|
+
return self._tool_functions
|
244
|
+
|
245
|
+
async def start_server(
|
246
|
+
self, host: str = "0.0.0.0", port: int = 8000, background: bool = False
|
247
|
+
) -> None | asyncio.Task:
|
248
|
+
"""Start the FastAPI server.
|
249
|
+
|
250
|
+
Args:
|
251
|
+
host: Host to bind to
|
252
|
+
port: Port to bind to
|
253
|
+
background: If True, run server in background task
|
254
|
+
"""
|
255
|
+
import uvicorn
|
256
|
+
|
257
|
+
app = await self.generate_server_app()
|
258
|
+
|
259
|
+
if background:
|
260
|
+
config = uvicorn.Config(app, host=host, port=port)
|
261
|
+
server = uvicorn.Server(config)
|
262
|
+
return asyncio.create_task(server.serve())
|
263
|
+
|
264
|
+
uvicorn.run(app, host=host, port=port)
|
265
|
+
return None
|
266
|
+
|
267
|
+
async def save_to_files(self, output_dir: Path) -> dict[str, Path]:
|
268
|
+
"""Save generated code to files.
|
269
|
+
|
270
|
+
Args:
|
271
|
+
output_dir: Directory to save files to
|
272
|
+
|
273
|
+
Returns:
|
274
|
+
Dictionary mapping file types to paths
|
275
|
+
"""
|
276
|
+
output_dir = Path(output_dir)
|
277
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
278
|
+
|
279
|
+
saved_files = {}
|
280
|
+
|
281
|
+
# Save tools module
|
282
|
+
tools_code = await self.generate_tools_code()
|
283
|
+
tools_file = output_dir / "generated_tools.py"
|
284
|
+
tools_file.write_text(tools_code)
|
285
|
+
saved_files["tools"] = tools_file
|
286
|
+
|
287
|
+
# Save server code (as template/example)
|
288
|
+
server_template = f'''"""FastAPI server using HttpToolExecutor."""
|
289
|
+
|
290
|
+
import asyncio
|
291
|
+
from pathlib import Path
|
292
|
+
|
293
|
+
from schemez.tool_executor import HttpToolExecutor, ToolHandler
|
294
|
+
from pydantic import BaseModel
|
295
|
+
|
296
|
+
|
297
|
+
async def my_tool_handler(method_name: str, input_props: BaseModel) -> str:
|
298
|
+
"""Implement your tool logic here."""
|
299
|
+
match method_name:
|
300
|
+
case _:
|
301
|
+
return f"Mock result for {{method_name}}: {{input_props}}"
|
302
|
+
|
303
|
+
|
304
|
+
async def main():
|
305
|
+
"""Start the server with your handler."""
|
306
|
+
executor = HttpToolExecutor(
|
307
|
+
schemas=[], # Add your schema files/dicts here
|
308
|
+
handler=my_tool_handler,
|
309
|
+
base_url="{self.base_url}"
|
310
|
+
)
|
311
|
+
|
312
|
+
await executor.start_server()
|
313
|
+
|
314
|
+
|
315
|
+
if __name__ == "__main__":
|
316
|
+
asyncio.run(main())
|
317
|
+
'''
|
318
|
+
|
319
|
+
server_file = output_dir / "server_example.py"
|
320
|
+
server_file.write_text(server_template)
|
321
|
+
saved_files["server_example"] = server_file
|
322
|
+
return saved_files
|
@@ -0,0 +1,46 @@
|
|
1
|
+
"""Helper functions for the tool executor."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
import logging
|
6
|
+
import time
|
7
|
+
|
8
|
+
from schemez.helpers import model_to_python_code
|
9
|
+
|
10
|
+
|
11
|
+
logger = logging.getLogger(__name__)
|
12
|
+
|
13
|
+
|
14
|
+
async def generate_input_model(schema_dict: dict) -> tuple[str, str]:
|
15
|
+
"""Generate input model code from schema."""
|
16
|
+
start_time = time.time()
|
17
|
+
logger.debug("Generating input model for %s", schema_dict["name"])
|
18
|
+
|
19
|
+
words = [word.title() for word in schema_dict["name"].split("_")]
|
20
|
+
cls_name = f"{''.join(words)}Input"
|
21
|
+
code = await model_to_python_code(schema_dict["parameters"], class_name=cls_name)
|
22
|
+
elapsed = time.time() - start_time
|
23
|
+
logger.debug("Generated input model for %s in %.2fs", schema_dict["name"], elapsed)
|
24
|
+
return code, cls_name
|
25
|
+
|
26
|
+
|
27
|
+
def clean_generated_code(code: str) -> str:
|
28
|
+
"""Clean generated code by removing future imports and headers."""
|
29
|
+
lines = code.split("\n")
|
30
|
+
cleaned_lines = []
|
31
|
+
skip_until_class = True
|
32
|
+
|
33
|
+
for line in lines:
|
34
|
+
# Skip lines until we find a class or other meaningful content
|
35
|
+
if skip_until_class:
|
36
|
+
if line.strip().startswith("class ") or (
|
37
|
+
line.strip()
|
38
|
+
and not line.startswith("#")
|
39
|
+
and not line.startswith("from __future__")
|
40
|
+
):
|
41
|
+
skip_until_class = False
|
42
|
+
cleaned_lines.append(line)
|
43
|
+
continue
|
44
|
+
cleaned_lines.append(line)
|
45
|
+
|
46
|
+
return "\n".join(cleaned_lines)
|
@@ -0,0 +1,28 @@
|
|
1
|
+
"""Type definitions for tool executor."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
from typing import TYPE_CHECKING, Protocol
|
6
|
+
|
7
|
+
|
8
|
+
if TYPE_CHECKING:
|
9
|
+
from pydantic import BaseModel
|
10
|
+
|
11
|
+
|
12
|
+
class ToolHandler(Protocol):
|
13
|
+
"""Protocol for user-provided tool handler."""
|
14
|
+
|
15
|
+
async def __call__(self, method_name: str, input_props: BaseModel) -> str:
|
16
|
+
"""Process a tool call.
|
17
|
+
|
18
|
+
Args:
|
19
|
+
method_name: Name of the tool being called (e.g., "get_weather")
|
20
|
+
input_props: Validated input model instance
|
21
|
+
|
22
|
+
Returns:
|
23
|
+
String result from the tool execution
|
24
|
+
|
25
|
+
Raises:
|
26
|
+
Exception: If tool execution fails
|
27
|
+
"""
|
28
|
+
...
|
schemez/typedefs.py
CHANGED
@@ -163,7 +163,7 @@ def _convert_complex_property(
|
|
163
163
|
|
164
164
|
# Get first non-null type or default to string
|
165
165
|
first_type = next((t for t in types if t != "null"), "string")
|
166
|
-
if first_type not in
|
166
|
+
if first_type not in {"string", "number", "integer", "boolean"}:
|
167
167
|
first_type = "string"
|
168
168
|
|
169
169
|
return _create_simple_property(
|
@@ -193,7 +193,7 @@ def _convert_complex_property(
|
|
193
193
|
)
|
194
194
|
|
195
195
|
type_str = prop.get("type", "string")
|
196
|
-
if type_str not in
|
196
|
+
if type_str not in {"string", "number", "integer", "boolean"}:
|
197
197
|
type_str = "string"
|
198
198
|
|
199
199
|
return _create_simple_property(
|
@@ -0,0 +1,340 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: schemez
|
3
|
+
Version: 1.2.3
|
4
|
+
Summary: Pydantic shim for config stuff
|
5
|
+
Keywords:
|
6
|
+
Author: Philipp Temminghoff
|
7
|
+
Author-email: Philipp Temminghoff <philipptemminghoff@googlemail.com>
|
8
|
+
License-Expression: MIT
|
9
|
+
License-File: LICENSE
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
11
|
+
Classifier: Framework :: Pydantic
|
12
|
+
Classifier: Framework :: Pydantic :: 2
|
13
|
+
Classifier: Intended Audience :: Developers
|
14
|
+
Classifier: Operating System :: OS Independent
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
20
|
+
Classifier: Topic :: Documentation
|
21
|
+
Classifier: Topic :: Software Development
|
22
|
+
Classifier: Topic :: Utilities
|
23
|
+
Classifier: Typing :: Typed
|
24
|
+
Requires-Dist: docstring-parser>=0.17.0
|
25
|
+
Requires-Dist: griffe>=1.7.3
|
26
|
+
Requires-Dist: pydantic
|
27
|
+
Requires-Dist: universal-pathlib>=0.2.6
|
28
|
+
Requires-Dist: llmling-agent ; extra == 'ai'
|
29
|
+
Requires-Dist: anyenv>=0.4.14 ; extra == 'ai'
|
30
|
+
Requires-Dist: datamodel-code-generator ; extra == 'codegen'
|
31
|
+
Requires-Dist: fastapi>=0.118.2 ; extra == 'tool-execution'
|
32
|
+
Requires-Dist: yamling ; extra == 'yaml'
|
33
|
+
Requires-Python: >=3.13
|
34
|
+
Project-URL: Code coverage, https://app.codecov.io/gh/phil65/schemez
|
35
|
+
Project-URL: Discussions, https://github.com/phil65/schemez/discussions
|
36
|
+
Project-URL: Documentation, https://phil65.github.io/schemez/
|
37
|
+
Project-URL: Issues, https://github.com/phil65/schemez/issues
|
38
|
+
Project-URL: Source, https://github.com/phil65/schemez
|
39
|
+
Provides-Extra: ai
|
40
|
+
Provides-Extra: codegen
|
41
|
+
Provides-Extra: tool-execution
|
42
|
+
Provides-Extra: yaml
|
43
|
+
Description-Content-Type: text/markdown
|
44
|
+
|
45
|
+
# Schemez
|
46
|
+
|
47
|
+
[](https://pypi.org/project/schemez/)
|
48
|
+
[](https://pypi.org/project/schemez/)
|
49
|
+
[](https://pypi.org/project/schemez/)
|
50
|
+
[](https://pypi.org/project/schemez/)
|
51
|
+
[](https://pypi.org/project/schemez/)
|
52
|
+
[](https://pypi.org/project/schemez/)
|
53
|
+
[](https://pypi.org/project/schemez/)
|
54
|
+
[](https://github.com/phil65/schemez/releases)
|
55
|
+
[](https://github.com/phil65/schemez/graphs/contributors)
|
56
|
+
[](https://github.com/phil65/schemez/discussions)
|
57
|
+
[](https://github.com/phil65/schemez/forks)
|
58
|
+
[](https://github.com/phil65/schemez/issues)
|
59
|
+
[](https://github.com/phil65/schemez/pulls)
|
60
|
+
[](https://github.com/phil65/schemez/watchers)
|
61
|
+
[](https://github.com/phil65/schemez/stars)
|
62
|
+
[](https://github.com/phil65/schemez)
|
63
|
+
[](https://github.com/phil65/schemez/commits)
|
64
|
+
[](https://github.com/phil65/schemez/releases)
|
65
|
+
[](https://github.com/phil65/schemez)
|
66
|
+
[](https://github.com/phil65/schemez)
|
67
|
+
[](https://codecov.io/gh/phil65/schemez/)
|
68
|
+
[](https://pyup.io/repos/github/phil65/schemez/)
|
69
|
+
|
70
|
+
[Read the documentation!](https://phil65.github.io/schemez/)
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
# OpenAI Function Schema Generator
|
75
|
+
|
76
|
+
Convert Python functions to OpenAI-compatible function schemas automatically.
|
77
|
+
|
78
|
+
## Installation
|
79
|
+
|
80
|
+
```bash
|
81
|
+
pip install schemez
|
82
|
+
```
|
83
|
+
|
84
|
+
## Basic Usage
|
85
|
+
|
86
|
+
```python
|
87
|
+
from schemez import create_schema
|
88
|
+
from typing import Literal
|
89
|
+
|
90
|
+
def get_weather(
|
91
|
+
location: str,
|
92
|
+
unit: Literal["C", "F"] = "C",
|
93
|
+
detailed: bool = False,
|
94
|
+
) -> dict[str, str | float]:
|
95
|
+
"""Get the weather for a location.
|
96
|
+
|
97
|
+
Args:
|
98
|
+
location: City or address to get weather for
|
99
|
+
unit: Temperature unit (Celsius or Fahrenheit)
|
100
|
+
detailed: Include extended forecast
|
101
|
+
"""
|
102
|
+
return {"temp": 22.5, "conditions": "sunny"}
|
103
|
+
|
104
|
+
# Create schema
|
105
|
+
schema = create_schema(get_weather)
|
106
|
+
|
107
|
+
# The schema.model_dump_openai() returns a TypedDict with the complete OpenAI tool definition:
|
108
|
+
# OpenAIFunctionTool = TypedDict({
|
109
|
+
# "type": Literal["function"],
|
110
|
+
# "function": OpenAIFunctionDefinition
|
111
|
+
# })
|
112
|
+
|
113
|
+
# Use with OpenAI
|
114
|
+
from openai import OpenAI
|
115
|
+
|
116
|
+
client = OpenAI()
|
117
|
+
response = client.chat.completions.create(
|
118
|
+
model="gpt-4",
|
119
|
+
messages=[{"role": "user", "content": "What's the weather in London?"}],
|
120
|
+
tools=[schema.model_dump_openai()], # Schema includes the type: "function" wrapper
|
121
|
+
tool_choice="auto"
|
122
|
+
)
|
123
|
+
```
|
124
|
+
|
125
|
+
> **Note**: This library supports the OpenAI API v1 format (openai>=1.0.0). For older
|
126
|
+
> versions of the OpenAI package that use the legacy functions API, you'll need to
|
127
|
+
> unwrap the function definition using `schema.model_dump_openai()["function"]`.
|
128
|
+
```
|
129
|
+
|
130
|
+
## Supported Types
|
131
|
+
|
132
|
+
### Basic Types
|
133
|
+
```python
|
134
|
+
def func(
|
135
|
+
text: str, # -> "type": "string"
|
136
|
+
number: int, # -> "type": "integer"
|
137
|
+
amount: float, # -> "type": "number"
|
138
|
+
enabled: bool, # -> "type": "boolean"
|
139
|
+
anything: Any, # -> "type": "string"
|
140
|
+
) -> None: ...
|
141
|
+
```
|
142
|
+
|
143
|
+
### Container Types
|
144
|
+
```python
|
145
|
+
def func(
|
146
|
+
items: list[str], # -> "type": "array", "items": {"type": "string"}
|
147
|
+
numbers: set[int], # -> same as list
|
148
|
+
mapping: dict[str, Any], # -> "type": "object", "additionalProperties": true
|
149
|
+
nested: list[dict[str, int]], # -> nested array/object types
|
150
|
+
sequence: Sequence[str], # -> "type": "array"
|
151
|
+
collection: Collection[int], # -> "type": "array"
|
152
|
+
) -> None: ...
|
153
|
+
```
|
154
|
+
|
155
|
+
### Enums and Literals
|
156
|
+
```python
|
157
|
+
class Color(Enum):
|
158
|
+
RED = "red"
|
159
|
+
BLUE = "blue"
|
160
|
+
|
161
|
+
def func(
|
162
|
+
color: Color, # -> "type": "string", "enum": ["red", "blue"]
|
163
|
+
mode: Literal["fast", "slow"], # -> "type": "string", "enum": ["fast", "slow"]
|
164
|
+
) -> None: ...
|
165
|
+
```
|
166
|
+
|
167
|
+
### Optional and Union Types
|
168
|
+
```python
|
169
|
+
def func(
|
170
|
+
opt1: str | None, # -> "type": "string"
|
171
|
+
opt2: int | None, # -> "type": "integer"
|
172
|
+
union: str | int, # -> "type": "string" (first type)
|
173
|
+
) -> None: ...
|
174
|
+
```
|
175
|
+
|
176
|
+
### Custom Types
|
177
|
+
```python
|
178
|
+
@dataclass
|
179
|
+
class User:
|
180
|
+
name: str
|
181
|
+
age: int
|
182
|
+
|
183
|
+
def func(
|
184
|
+
user: User, # -> "type": "object"
|
185
|
+
data: JsonDict, # -> "type": "object"
|
186
|
+
) -> None: ...
|
187
|
+
```
|
188
|
+
|
189
|
+
### Type Aliases
|
190
|
+
```python
|
191
|
+
JsonValue = dict[str, Any] | list[Any] | str | int | float | bool | None
|
192
|
+
JsonDict = dict[str, JsonValue]
|
193
|
+
|
194
|
+
def func(
|
195
|
+
data: JsonDict, # -> "type": "object"
|
196
|
+
values: list[JsonValue], # -> "type": "array"
|
197
|
+
) -> None: ...
|
198
|
+
```
|
199
|
+
|
200
|
+
### Recursive Types
|
201
|
+
```python
|
202
|
+
def func(
|
203
|
+
tree: dict[str, "dict[str, Any] | str"], # -> "type": "object"
|
204
|
+
nested: dict[str, list["dict[str, Any]"]], # -> "type": "object"
|
205
|
+
) -> None: ...
|
206
|
+
```
|
207
|
+
|
208
|
+
## Generated Schema Example
|
209
|
+
|
210
|
+
```python
|
211
|
+
{
|
212
|
+
"type": "function",
|
213
|
+
"function": {
|
214
|
+
"name": "get_weather",
|
215
|
+
"description": "Get the weather for a location.",
|
216
|
+
"parameters": {
|
217
|
+
"type": "object",
|
218
|
+
"properties": {
|
219
|
+
"location": {
|
220
|
+
"type": "string",
|
221
|
+
"description": "City or address to get weather for"
|
222
|
+
},
|
223
|
+
"unit": {
|
224
|
+
"type": "string",
|
225
|
+
"enum": ["C", "F"],
|
226
|
+
"description": "Temperature unit (Celsius or Fahrenheit)",
|
227
|
+
"default": "C"
|
228
|
+
},
|
229
|
+
"detailed": {
|
230
|
+
"type": "boolean",
|
231
|
+
"description": "Include extended forecast",
|
232
|
+
"default": false
|
233
|
+
}
|
234
|
+
},
|
235
|
+
"required": ["location"]
|
236
|
+
}
|
237
|
+
}
|
238
|
+
}
|
239
|
+
```
|
240
|
+
|
241
|
+
## Schema Generators
|
242
|
+
|
243
|
+
### Module Schemas
|
244
|
+
|
245
|
+
You can generate schemas for all public functions in a module using `create_schemas_from_module`:
|
246
|
+
|
247
|
+
```python
|
248
|
+
from schemez import create_schemas_from_module
|
249
|
+
import math
|
250
|
+
|
251
|
+
# Generate schemas for all public functions
|
252
|
+
schemas = create_schemas_from_module(math)
|
253
|
+
|
254
|
+
# Generate schemas for specific functions only
|
255
|
+
schemas = create_schemas_from_module(math, include_functions=['sin', 'cos'])
|
256
|
+
|
257
|
+
# Import module by string name
|
258
|
+
schemas = create_schemas_from_module('math')
|
259
|
+
```
|
260
|
+
|
261
|
+
### Class Schemas
|
262
|
+
|
263
|
+
Generate schemas for all public methods in a class using `create_schemas_from_class`:
|
264
|
+
|
265
|
+
```python
|
266
|
+
from schemez import create_schemas_from_class
|
267
|
+
|
268
|
+
class Calculator:
|
269
|
+
def add(self, x: int, y: int) -> int:
|
270
|
+
"""Add two numbers.
|
271
|
+
|
272
|
+
Args:
|
273
|
+
x: First number
|
274
|
+
y: Second number
|
275
|
+
|
276
|
+
Returns:
|
277
|
+
Sum of x and y
|
278
|
+
"""
|
279
|
+
return x + y
|
280
|
+
|
281
|
+
@classmethod
|
282
|
+
def multiply(cls, x: int, y: int) -> int:
|
283
|
+
"""Multiply two numbers.
|
284
|
+
|
285
|
+
Args:
|
286
|
+
x: First number
|
287
|
+
y: Second number
|
288
|
+
|
289
|
+
Returns:
|
290
|
+
Product of x and y
|
291
|
+
"""
|
292
|
+
return x * y
|
293
|
+
|
294
|
+
@staticmethod
|
295
|
+
def divide(x: float, y: float) -> float:
|
296
|
+
"""Divide two numbers.
|
297
|
+
|
298
|
+
Args:
|
299
|
+
x: Numerator
|
300
|
+
y: Denominator
|
301
|
+
|
302
|
+
Returns:
|
303
|
+
Result of x divided by y
|
304
|
+
"""
|
305
|
+
return x / y
|
306
|
+
|
307
|
+
# Generate schemas for all public methods
|
308
|
+
schemas = create_schemas_from_class(Calculator)
|
309
|
+
|
310
|
+
# Access individual method schemas
|
311
|
+
add_schema = schemas['Calculator.add']
|
312
|
+
multiply_schema = schemas['Calculator.multiply']
|
313
|
+
divide_schema = schemas['Calculator.divide']
|
314
|
+
```
|
315
|
+
|
316
|
+
The schema generators support:
|
317
|
+
|
318
|
+
- Regular functions
|
319
|
+
- Regular instance methods (bound and unbound)
|
320
|
+
- Class methods
|
321
|
+
- Static methods
|
322
|
+
- Decorated functions / methods
|
323
|
+
- Async functions / methods
|
324
|
+
- Property methods
|
325
|
+
- Basically all stdlib typing features as well as many stdlib types
|
326
|
+
- Method docstrings for descriptions
|
327
|
+
- Default values
|
328
|
+
- Return type hints
|
329
|
+
|
330
|
+
|
331
|
+
## Diferences to pydantic schema generation
|
332
|
+
|
333
|
+
While Pydantics schema generation preserves detailed type information, `schema.model_dump_openai()`
|
334
|
+
simplifies types to match OpenAI's function calling format. Most special types
|
335
|
+
(datetime, UUID, Path, etc.) are handled similarly by both (we only strip unused information), but we handle enums
|
336
|
+
differently: Instead of preserving enum class information, we extract just the values
|
337
|
+
as a string enum. Union types and Optionals are also handled differently - we typically
|
338
|
+
pick the first type to keep the schema simple and practical for AI interaction.
|
339
|
+
This ensures compatibility with OpenAI's function calling API while maintaining enough
|
340
|
+
type information for the AI to understand the function signature.
|
@@ -0,0 +1,25 @@
|
|
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,,
|
@@ -0,0 +1,22 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2024, Philipp Temminghoff
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
22
|
+
|
schemez-1.2.0.dist-info/METADATA
DELETED
@@ -1,86 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.3
|
2
|
-
Name: schemez
|
3
|
-
Version: 1.2.0
|
4
|
-
Summary: Pydantic shim for config stuff
|
5
|
-
Keywords:
|
6
|
-
Author: Philipp Temminghoff
|
7
|
-
Author-email: Philipp Temminghoff <philipptemminghoff@googlemail.com>
|
8
|
-
License: MIT License
|
9
|
-
|
10
|
-
Copyright (c) 2024, Philipp Temminghoff
|
11
|
-
|
12
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
13
|
-
of this software and associated documentation files (the "Software"), to deal
|
14
|
-
in the Software without restriction, including without limitation the rights
|
15
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
16
|
-
copies of the Software, and to permit persons to whom the Software is
|
17
|
-
furnished to do so, subject to the following conditions:
|
18
|
-
|
19
|
-
The above copyright notice and this permission notice shall be included in all
|
20
|
-
copies or substantial portions of the Software.
|
21
|
-
|
22
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
23
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
24
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
25
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
26
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
27
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
28
|
-
SOFTWARE.
|
29
|
-
|
30
|
-
Classifier: Development Status :: 4 - Beta
|
31
|
-
Classifier: Framework :: Pydantic
|
32
|
-
Classifier: Framework :: Pydantic :: 2
|
33
|
-
Classifier: Intended Audience :: Developers
|
34
|
-
Classifier: Operating System :: OS Independent
|
35
|
-
Classifier: Programming Language :: Python :: 3
|
36
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
37
|
-
Classifier: Programming Language :: Python :: 3.12
|
38
|
-
Classifier: Programming Language :: Python :: 3.13
|
39
|
-
Classifier: Programming Language :: Python :: 3.14
|
40
|
-
Classifier: Topic :: Documentation
|
41
|
-
Classifier: Topic :: Software Development
|
42
|
-
Classifier: Topic :: Utilities
|
43
|
-
Classifier: Typing :: Typed
|
44
|
-
Requires-Dist: docstring-parser>=0.17.0
|
45
|
-
Requires-Dist: griffe>=1.7.3
|
46
|
-
Requires-Dist: pydantic
|
47
|
-
Requires-Dist: universal-pathlib>=0.2.6
|
48
|
-
Requires-Dist: llmling-agent ; extra == 'ai'
|
49
|
-
Requires-Dist: anyenv>=0.4.14 ; extra == 'ai'
|
50
|
-
Requires-Dist: yamling ; extra == 'yaml'
|
51
|
-
Requires-Python: >=3.13
|
52
|
-
Project-URL: Code coverage, https://app.codecov.io/gh/phil65/schemez
|
53
|
-
Project-URL: Discussions, https://github.com/phil65/schemez/discussions
|
54
|
-
Project-URL: Documentation, https://phil65.github.io/schemez/
|
55
|
-
Project-URL: Issues, https://github.com/phil65/schemez/issues
|
56
|
-
Project-URL: Source, https://github.com/phil65/schemez
|
57
|
-
Provides-Extra: ai
|
58
|
-
Provides-Extra: yaml
|
59
|
-
Description-Content-Type: text/markdown
|
60
|
-
|
61
|
-
# Schemez
|
62
|
-
|
63
|
-
[](https://pypi.org/project/schemez/)
|
64
|
-
[](https://pypi.org/project/schemez/)
|
65
|
-
[](https://pypi.org/project/schemez/)
|
66
|
-
[](https://pypi.org/project/schemez/)
|
67
|
-
[](https://pypi.org/project/schemez/)
|
68
|
-
[](https://pypi.org/project/schemez/)
|
69
|
-
[](https://pypi.org/project/schemez/)
|
70
|
-
[](https://github.com/phil65/schemez/releases)
|
71
|
-
[](https://github.com/phil65/schemez/graphs/contributors)
|
72
|
-
[](https://github.com/phil65/schemez/discussions)
|
73
|
-
[](https://github.com/phil65/schemez/forks)
|
74
|
-
[](https://github.com/phil65/schemez/issues)
|
75
|
-
[](https://github.com/phil65/schemez/pulls)
|
76
|
-
[](https://github.com/phil65/schemez/watchers)
|
77
|
-
[](https://github.com/phil65/schemez/stars)
|
78
|
-
[](https://github.com/phil65/schemez)
|
79
|
-
[](https://github.com/phil65/schemez/commits)
|
80
|
-
[](https://github.com/phil65/schemez/releases)
|
81
|
-
[](https://github.com/phil65/schemez)
|
82
|
-
[](https://github.com/phil65/schemez)
|
83
|
-
[](https://codecov.io/gh/phil65/schemez/)
|
84
|
-
[](https://pyup.io/repos/github/phil65/schemez/)
|
85
|
-
|
86
|
-
[Read the documentation!](https://phil65.github.io/schemez/)
|
schemez-1.2.0.dist-info/RECORD
DELETED
@@ -1,19 +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=b6Sz11lq0HvpXfMREOqnnw8rcVg2XzTKhjjPNc4YIoE,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=Z8kL1ckU6pEoRhOsFY6NziLhVudELHyylsFVXrCUcwU,26127
|
9
|
-
schemez/helpers.py,sha256=IVuoFbzPJs3eqJBrr0BA6SIHncoU6BFJGP41zTijzXM,8716
|
10
|
-
schemez/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
|
-
schemez/pydantic_types.py,sha256=8vgSl8i2z9n0fB-8AJj-D3TBByEWE5IxItBxQ0XwXFI,1640
|
12
|
-
schemez/schema.py,sha256=u6SDhYDtfCjgy2Aa-_MDLLNcUfEXbeye4T-W6s3AED8,9558
|
13
|
-
schemez/schema_generators.py,sha256=Gze7S7dQkTsl_1ckeHLXPxx4jQo7RB6hHQM-5fpAsrA,6973
|
14
|
-
schemez/schemadef/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
|
-
schemez/schemadef/schemadef.py,sha256=FtD7TOnYxiuYOIfadRHKkkbZn98mWFb0_lKfPsPR-hI,14393
|
16
|
-
schemez/typedefs.py,sha256=kxh-Vc5eKvCShdznJrg5N4_Fr8g1kYlkadEAdzgQ43w,6091
|
17
|
-
schemez-1.2.0.dist-info/WHEEL,sha256=I8-bO5cg2sb8TH6ZM6EgCP87Y1cV_f9UGgWnfAhVOZI,78
|
18
|
-
schemez-1.2.0.dist-info/METADATA,sha256=Nao7AGsQ0CGUiHDXT2nSE0AMOnwqlMybd8KTQSAaIB0,5399
|
19
|
-
schemez-1.2.0.dist-info/RECORD,,
|