schemez 1.1.0__py3-none-any.whl → 1.2.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.
- schemez/__init__.py +21 -0
- schemez/bind_kwargs.py +193 -0
- schemez/create_type.py +340 -0
- schemez/executable.py +211 -0
- schemez/functionschema.py +772 -0
- schemez/helpers.py +72 -51
- schemez/schema.py +2 -1
- schemez/schema_generators.py +215 -0
- schemez/typedefs.py +205 -0
- {schemez-1.1.0.dist-info → schemez-1.2.0.dist-info}/METADATA +2 -1
- schemez-1.2.0.dist-info/RECORD +19 -0
- schemez-1.1.0.dist-info/RECORD +0 -13
- {schemez-1.1.0.dist-info → schemez-1.2.0.dist-info}/WHEEL +0 -0
schemez/helpers.py
CHANGED
@@ -4,19 +4,19 @@ from __future__ import annotations
|
|
4
4
|
|
5
5
|
import asyncio
|
6
6
|
import importlib
|
7
|
-
import json
|
8
7
|
import os
|
9
8
|
from pathlib import Path
|
10
9
|
import subprocess
|
11
10
|
import sys
|
12
11
|
import tempfile
|
13
|
-
from typing import TYPE_CHECKING, Any
|
12
|
+
from typing import TYPE_CHECKING, Any, Literal
|
14
13
|
|
15
14
|
from pydantic import BaseModel
|
15
|
+
from pydantic_core import to_json
|
16
16
|
|
17
17
|
|
18
18
|
StrPath = str | os.PathLike[str]
|
19
|
-
|
19
|
+
PythonVersion = Literal["3.13", "3.14", "3.15"]
|
20
20
|
|
21
21
|
if TYPE_CHECKING:
|
22
22
|
from collections.abc import Callable
|
@@ -177,11 +177,48 @@ def resolve_type_string(type_string: str, safe: bool = True) -> type:
|
|
177
177
|
raise ValueError(msg) from e
|
178
178
|
|
179
179
|
|
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
|
+
|
180
216
|
async def model_to_python_code(
|
181
217
|
model: type[BaseModel],
|
182
218
|
*,
|
183
219
|
class_name: str | None = None,
|
184
|
-
target_python_version:
|
220
|
+
target_python_version: PythonVersion | None = None,
|
221
|
+
model_type: str = "pydantic.BaseModel",
|
185
222
|
) -> str:
|
186
223
|
"""Convert a BaseModel to Python code asynchronously.
|
187
224
|
|
@@ -190,6 +227,7 @@ async def model_to_python_code(
|
|
190
227
|
class_name: Optional custom class name for the generated code
|
191
228
|
target_python_version: Target Python version for code generation.
|
192
229
|
Defaults to current system Python version.
|
230
|
+
model_type: Type of the generated model. Defaults to "pydantic.BaseModel".
|
193
231
|
|
194
232
|
Returns:
|
195
233
|
Generated Python code as string
|
@@ -198,67 +236,50 @@ async def model_to_python_code(
|
|
198
236
|
RuntimeError: If datamodel-codegen is not available
|
199
237
|
subprocess.CalledProcessError: If code generation fails
|
200
238
|
"""
|
201
|
-
|
202
|
-
# Check if datamodel-codegen is available
|
203
|
-
proc = await asyncio.create_subprocess_exec(
|
204
|
-
"datamodel-codegen",
|
205
|
-
"--version",
|
206
|
-
stdout=asyncio.subprocess.PIPE,
|
207
|
-
stderr=asyncio.subprocess.PIPE,
|
208
|
-
)
|
209
|
-
await proc.communicate()
|
210
|
-
if proc.returncode != 0:
|
211
|
-
raise subprocess.CalledProcessError(
|
212
|
-
proc.returncode or -1, "datamodel-codegen"
|
213
|
-
)
|
214
|
-
except FileNotFoundError as e:
|
215
|
-
msg = "datamodel-codegen not available"
|
216
|
-
raise RuntimeError(msg) from e
|
239
|
+
working_prefix = await _detect_command("datamodel-codegen")
|
217
240
|
|
218
|
-
# Get model schema
|
219
241
|
schema = model.model_json_schema()
|
220
242
|
name = class_name or model.__name__
|
221
|
-
|
222
|
-
target_python_version or f"{sys.version_info.major}.{sys.version_info.minor}"
|
223
|
-
)
|
224
|
-
|
225
|
-
# Create temporary file with schema
|
243
|
+
py = target_python_version or f"{sys.version_info.major}.{sys.version_info.minor}"
|
226
244
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
227
|
-
|
245
|
+
# Use pydantic_core.to_json for proper schema serialization
|
246
|
+
schema_json = to_json(schema, indent=2).decode()
|
247
|
+
f.write(schema_json)
|
228
248
|
schema_file = Path(f.name)
|
229
249
|
|
230
|
-
|
231
|
-
|
250
|
+
args = [
|
251
|
+
"--input",
|
252
|
+
str(schema_file),
|
253
|
+
"--input-file-type",
|
254
|
+
"jsonschema",
|
255
|
+
"--output-model-type",
|
256
|
+
model_type,
|
257
|
+
"--class-name",
|
258
|
+
name,
|
259
|
+
"--disable-timestamp",
|
260
|
+
"--use-union-operator",
|
261
|
+
"--use-schema-description",
|
262
|
+
"--enum-field-as-literal",
|
263
|
+
"all",
|
264
|
+
"--target-python-version",
|
265
|
+
py,
|
266
|
+
]
|
267
|
+
|
268
|
+
try: # Generate model using datamodel-codegen
|
232
269
|
proc = await asyncio.create_subprocess_exec(
|
270
|
+
*working_prefix,
|
233
271
|
"datamodel-codegen",
|
234
|
-
|
235
|
-
str(schema_file),
|
236
|
-
"--input-file-type",
|
237
|
-
"jsonschema",
|
238
|
-
"--output-model-type",
|
239
|
-
"pydantic.BaseModel",
|
240
|
-
"--class-name",
|
241
|
-
name,
|
242
|
-
"--disable-timestamp",
|
243
|
-
"--use-union-operator",
|
244
|
-
"--use-schema-description",
|
245
|
-
"--enum-field-as-literal",
|
246
|
-
"all",
|
247
|
-
"--target-python-version",
|
248
|
-
python_version,
|
272
|
+
*args,
|
249
273
|
stdout=asyncio.subprocess.PIPE,
|
250
274
|
stderr=asyncio.subprocess.PIPE,
|
251
275
|
)
|
252
|
-
stdout,
|
276
|
+
stdout, _stderr = await proc.communicate()
|
253
277
|
|
254
278
|
if proc.returncode != 0:
|
255
|
-
|
256
|
-
raise subprocess.CalledProcessError(
|
257
|
-
proc.returncode or -1, "datamodel-codegen"
|
258
|
-
)
|
279
|
+
code = proc.returncode or -1
|
280
|
+
raise subprocess.CalledProcessError(code, "datamodel-codegen")
|
259
281
|
|
260
282
|
return stdout.decode().strip()
|
261
283
|
|
262
|
-
finally:
|
263
|
-
# Cleanup temp file
|
284
|
+
finally: # Cleanup temp file
|
264
285
|
schema_file.unlink(missing_ok=True)
|
schemez/schema.py
CHANGED
@@ -19,6 +19,7 @@ if TYPE_CHECKING:
|
|
19
19
|
|
20
20
|
StrPath = str | os.PathLike[str]
|
21
21
|
SourceType = Literal["pdf", "image"]
|
22
|
+
PythonVersion = Literal["3.13", "3.14", "3.15"]
|
22
23
|
|
23
24
|
DEFAULT_SYSTEM_PROMPT = "You are a schema extractor for {name} BaseModels."
|
24
25
|
DEFAULT_USER_PROMPT = "Extract information from this document:"
|
@@ -260,7 +261,7 @@ class Schema(BaseModel):
|
|
260
261
|
cls,
|
261
262
|
*,
|
262
263
|
class_name: str | None = None,
|
263
|
-
target_python_version:
|
264
|
+
target_python_version: PythonVersion | None = None,
|
264
265
|
) -> str:
|
265
266
|
"""Convert this model to Python code asynchronously.
|
266
267
|
|
@@ -0,0 +1,215 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from collections.abc import Callable # noqa: TC003
|
4
|
+
import dataclasses
|
5
|
+
import importlib
|
6
|
+
import inspect
|
7
|
+
import types
|
8
|
+
from typing import Any, Literal, get_type_hints
|
9
|
+
|
10
|
+
import pydantic
|
11
|
+
|
12
|
+
from schemez.functionschema import (
|
13
|
+
FunctionSchema,
|
14
|
+
_resolve_type_annotation,
|
15
|
+
create_schema,
|
16
|
+
)
|
17
|
+
from schemez.typedefs import ToolParameters
|
18
|
+
|
19
|
+
|
20
|
+
def create_schemas_from_callables(
|
21
|
+
callables: dict[str, Callable[..., Any]],
|
22
|
+
prefix: str | Literal[False] | None = None,
|
23
|
+
exclude_private: bool = True,
|
24
|
+
) -> dict[str, FunctionSchema]:
|
25
|
+
"""Generate OpenAI function schemas from a dictionary of callables.
|
26
|
+
|
27
|
+
Args:
|
28
|
+
callables: Dictionary mapping names to callable objects
|
29
|
+
prefix: Schema name prefix to prepend to function names.
|
30
|
+
If None, no prefix. If False, use raw name.
|
31
|
+
If string, uses that prefix.
|
32
|
+
exclude_private: Whether to exclude callables starting with underscore
|
33
|
+
|
34
|
+
Returns:
|
35
|
+
Dictionary mapping qualified names to FunctionSchema objects
|
36
|
+
|
37
|
+
Example:
|
38
|
+
>>> def foo(x: int) -> str: ...
|
39
|
+
>>> def bar(y: float) -> int: ...
|
40
|
+
>>> callables = {'foo': foo, 'bar': bar}
|
41
|
+
>>> schemas = create_schemas_from_callables(callables, prefix='math')
|
42
|
+
>>> print(schemas['math.foo'])
|
43
|
+
"""
|
44
|
+
schemas = {}
|
45
|
+
|
46
|
+
for name, callable_obj in callables.items():
|
47
|
+
# Skip private members if requested
|
48
|
+
if exclude_private and name.startswith("_"):
|
49
|
+
continue
|
50
|
+
|
51
|
+
# Generate schema key based on prefix setting
|
52
|
+
key = name if prefix is False else f"{prefix}.{name}" if prefix else name
|
53
|
+
schemas[key] = create_schema(callable_obj)
|
54
|
+
|
55
|
+
return schemas
|
56
|
+
|
57
|
+
|
58
|
+
def create_schemas_from_class(
|
59
|
+
cls: type,
|
60
|
+
prefix: str | Literal[False] | None = None,
|
61
|
+
) -> dict[str, FunctionSchema]:
|
62
|
+
"""Generate OpenAI function schemas for all public methods in a class.
|
63
|
+
|
64
|
+
Args:
|
65
|
+
cls: The class to generate schemas from
|
66
|
+
prefix: Schema name prefix. If None, uses class name.
|
67
|
+
If False, no prefix. If string, uses that prefix.
|
68
|
+
|
69
|
+
Returns:
|
70
|
+
Dictionary mapping qualified method names to FunctionSchema objects
|
71
|
+
|
72
|
+
Example:
|
73
|
+
>>> class MyClass:
|
74
|
+
... def my_method(self, x: int) -> str:
|
75
|
+
... return str(x)
|
76
|
+
>>> schemas = create_schemas_from_class(MyClass)
|
77
|
+
>>> print(schemas['MyClass.my_method'])
|
78
|
+
"""
|
79
|
+
callables: dict[str, Callable[..., Any]] = {}
|
80
|
+
|
81
|
+
# Get all attributes of the class
|
82
|
+
for name, attr in inspect.getmembers(cls):
|
83
|
+
# Handle different method types
|
84
|
+
if inspect.isfunction(attr) or inspect.ismethod(attr):
|
85
|
+
callables[name] = attr
|
86
|
+
elif isinstance(attr, classmethod | staticmethod):
|
87
|
+
callables[name] = attr.__get__(None, cls)
|
88
|
+
|
89
|
+
# Use default prefix of class name if not specified
|
90
|
+
effective_prefix = cls.__name__ if prefix is None else prefix
|
91
|
+
return create_schemas_from_callables(callables, prefix=effective_prefix)
|
92
|
+
|
93
|
+
|
94
|
+
def create_constructor_schema(cls: type) -> FunctionSchema:
|
95
|
+
"""Create OpenAI function schema from class constructor.
|
96
|
+
|
97
|
+
Args:
|
98
|
+
cls: Class to create schema for
|
99
|
+
|
100
|
+
Returns:
|
101
|
+
OpenAI function schema for class constructor
|
102
|
+
"""
|
103
|
+
if isinstance(cls, type) and issubclass(cls, pydantic.BaseModel):
|
104
|
+
properties = {}
|
105
|
+
required = []
|
106
|
+
for name, field in cls.model_fields.items():
|
107
|
+
param_type = field.annotation
|
108
|
+
properties[name] = _resolve_type_annotation(
|
109
|
+
param_type,
|
110
|
+
description=field.description,
|
111
|
+
default=field.default,
|
112
|
+
)
|
113
|
+
if field.is_required():
|
114
|
+
required.append(name)
|
115
|
+
|
116
|
+
# Handle dataclasses
|
117
|
+
elif dataclasses.is_dataclass(cls):
|
118
|
+
properties = {}
|
119
|
+
required = []
|
120
|
+
dc_fields = dataclasses.fields(cls)
|
121
|
+
hints = get_type_hints(cls)
|
122
|
+
|
123
|
+
for dc_field in dc_fields:
|
124
|
+
param_type = hints[dc_field.name]
|
125
|
+
properties[dc_field.name] = _resolve_type_annotation(
|
126
|
+
param_type, default=dc_field.default
|
127
|
+
)
|
128
|
+
if (
|
129
|
+
dc_field.default is dataclasses.MISSING
|
130
|
+
and dc_field.default_factory is dataclasses.MISSING
|
131
|
+
):
|
132
|
+
required.append(dc_field.name)
|
133
|
+
|
134
|
+
# Handle regular classes
|
135
|
+
else:
|
136
|
+
sig = inspect.signature(cls.__init__)
|
137
|
+
hints = get_type_hints(cls.__init__)
|
138
|
+
properties = {}
|
139
|
+
required = []
|
140
|
+
|
141
|
+
for name, param in sig.parameters.items():
|
142
|
+
if name == "self":
|
143
|
+
continue
|
144
|
+
|
145
|
+
param_type = hints.get(name, Any)
|
146
|
+
properties[name] = _resolve_type_annotation(
|
147
|
+
param_type,
|
148
|
+
default=param.default,
|
149
|
+
)
|
150
|
+
|
151
|
+
if param.default is param.empty:
|
152
|
+
required.append(name)
|
153
|
+
|
154
|
+
name = f"create_{cls.__name__}"
|
155
|
+
description = inspect.getdoc(cls) or f"Create {cls.__name__} instance"
|
156
|
+
|
157
|
+
# Create parameters with required list included
|
158
|
+
params = ToolParameters({
|
159
|
+
"type": "object",
|
160
|
+
"properties": properties,
|
161
|
+
"required": required,
|
162
|
+
})
|
163
|
+
|
164
|
+
return FunctionSchema(
|
165
|
+
name=name, description=description, parameters=params, required=required
|
166
|
+
)
|
167
|
+
|
168
|
+
|
169
|
+
def create_schemas_from_module(
|
170
|
+
module: types.ModuleType | str,
|
171
|
+
include_functions: list[str] | None = None,
|
172
|
+
prefix: str | Literal[False] | None = None,
|
173
|
+
) -> dict[str, FunctionSchema]:
|
174
|
+
"""Generate OpenAI function schemas from a Python module's functions.
|
175
|
+
|
176
|
+
Args:
|
177
|
+
module: Either a ModuleType object or string name of module to analyze
|
178
|
+
include_functions: Optional list of function names to specifically include
|
179
|
+
prefix: Schema name prefix. If None, uses module name.
|
180
|
+
If False, no prefix. If string, uses that prefix.
|
181
|
+
|
182
|
+
Returns:
|
183
|
+
Dictionary mapping function names to FunctionSchema objects
|
184
|
+
|
185
|
+
Raises:
|
186
|
+
ImportError: If module string name cannot be imported
|
187
|
+
|
188
|
+
Example:
|
189
|
+
>>> import math
|
190
|
+
>>> schemas = create_schemas_from_module(math, ['sin', 'cos'])
|
191
|
+
>>> print(schemas['math.sin'])
|
192
|
+
"""
|
193
|
+
# Resolve module if string name provided
|
194
|
+
mod = (
|
195
|
+
module
|
196
|
+
if isinstance(module, types.ModuleType)
|
197
|
+
else importlib.import_module(module)
|
198
|
+
)
|
199
|
+
|
200
|
+
# Get all functions from module
|
201
|
+
callables: dict[str, Callable[..., Any]] = {
|
202
|
+
name: func
|
203
|
+
for name, func in inspect.getmembers(mod, predicate=inspect.isfunction)
|
204
|
+
if include_functions is None
|
205
|
+
or (name in include_functions and func.__module__.startswith(mod.__name__))
|
206
|
+
}
|
207
|
+
|
208
|
+
# Use default prefix of module name if not specified
|
209
|
+
effective_prefix = mod.__name__ if prefix is None else prefix
|
210
|
+
return create_schemas_from_callables(callables, prefix=effective_prefix)
|
211
|
+
|
212
|
+
|
213
|
+
if __name__ == "__main__":
|
214
|
+
schemas = create_schemas_from_module(__name__)
|
215
|
+
print(schemas)
|
schemez/typedefs.py
ADDED
@@ -0,0 +1,205 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import inspect
|
4
|
+
from typing import Any, Literal, NotRequired, TypedDict
|
5
|
+
|
6
|
+
|
7
|
+
class PropertyBase(TypedDict, total=False):
|
8
|
+
"""Base schema property with common fields."""
|
9
|
+
|
10
|
+
type: str
|
11
|
+
description: str
|
12
|
+
format: str
|
13
|
+
default: Any
|
14
|
+
enum: NotRequired[list[Any]]
|
15
|
+
|
16
|
+
|
17
|
+
class SimpleProperty(PropertyBase):
|
18
|
+
"""Schema property for primitive types."""
|
19
|
+
|
20
|
+
type: Literal["string", "number", "integer", "boolean"]
|
21
|
+
enum: NotRequired[list[Any]]
|
22
|
+
|
23
|
+
|
24
|
+
class ArrayProperty(PropertyBase):
|
25
|
+
"""Schema property for array types."""
|
26
|
+
|
27
|
+
type: Literal["array"]
|
28
|
+
items: Property
|
29
|
+
|
30
|
+
|
31
|
+
class ObjectProperty(PropertyBase):
|
32
|
+
"""Schema property for nested object types."""
|
33
|
+
|
34
|
+
type: Literal["object"]
|
35
|
+
properties: NotRequired[dict[str, Property]]
|
36
|
+
required: NotRequired[list[str]]
|
37
|
+
additionalProperties: NotRequired[bool]
|
38
|
+
|
39
|
+
|
40
|
+
Property = ArrayProperty | ObjectProperty | SimpleProperty
|
41
|
+
|
42
|
+
|
43
|
+
class ToolParameters(TypedDict):
|
44
|
+
"""Schema for function parameters."""
|
45
|
+
|
46
|
+
type: Literal["object"]
|
47
|
+
properties: dict[str, Property]
|
48
|
+
required: NotRequired[list[str]]
|
49
|
+
|
50
|
+
|
51
|
+
class OpenAIFunctionDefinition(TypedDict):
|
52
|
+
"""Schema for the function definition part of an OpenAI tool.
|
53
|
+
|
54
|
+
This represents the inner "function" object that contains the actual
|
55
|
+
function metadata and parameters.
|
56
|
+
"""
|
57
|
+
|
58
|
+
name: str
|
59
|
+
description: str
|
60
|
+
parameters: ToolParameters
|
61
|
+
|
62
|
+
|
63
|
+
class OpenAIFunctionTool(TypedDict):
|
64
|
+
"""Complete OpenAI tool definition for function calling.
|
65
|
+
|
66
|
+
This represents the top-level tool object that wraps a function definition
|
67
|
+
and identifies it as a function tool type.
|
68
|
+
"""
|
69
|
+
|
70
|
+
type: Literal["function"]
|
71
|
+
function: OpenAIFunctionDefinition
|
72
|
+
|
73
|
+
|
74
|
+
def _create_simple_property(
|
75
|
+
type_str: Literal["string", "number", "integer", "boolean"],
|
76
|
+
description: str | None = None,
|
77
|
+
enum_values: list[Any] | None = None,
|
78
|
+
default: Any = None,
|
79
|
+
fmt: str | None = None,
|
80
|
+
) -> SimpleProperty:
|
81
|
+
"""Create a simple property."""
|
82
|
+
prop: SimpleProperty = {"type": type_str}
|
83
|
+
if description is not None:
|
84
|
+
prop["description"] = description
|
85
|
+
if enum_values is not None:
|
86
|
+
prop["enum"] = enum_values
|
87
|
+
if default is not inspect.Parameter.empty and default is not None:
|
88
|
+
prop["default"] = default
|
89
|
+
if fmt is not None:
|
90
|
+
prop["format"] = fmt
|
91
|
+
return prop
|
92
|
+
|
93
|
+
|
94
|
+
def _create_array_property(
|
95
|
+
items: Property,
|
96
|
+
description: str | None = None,
|
97
|
+
) -> ArrayProperty:
|
98
|
+
"""Create an array property."""
|
99
|
+
prop: ArrayProperty = {
|
100
|
+
"type": "array",
|
101
|
+
"items": items,
|
102
|
+
}
|
103
|
+
if description is not None:
|
104
|
+
prop["description"] = description
|
105
|
+
return prop
|
106
|
+
|
107
|
+
|
108
|
+
def _create_object_property(
|
109
|
+
description: str | None = None,
|
110
|
+
properties: dict[str, Property] | None = None,
|
111
|
+
required: list[str] | None = None,
|
112
|
+
additional_properties: bool | None = None,
|
113
|
+
) -> ObjectProperty:
|
114
|
+
"""Create an object property.
|
115
|
+
|
116
|
+
Args:
|
117
|
+
description: Optional property description
|
118
|
+
properties: Optional dict of property definitions
|
119
|
+
required: Optional list of required property names
|
120
|
+
additional_properties: Whether to allow additional properties
|
121
|
+
|
122
|
+
Returns:
|
123
|
+
Object property definition
|
124
|
+
"""
|
125
|
+
prop: ObjectProperty = {"type": "object"}
|
126
|
+
if description is not None:
|
127
|
+
prop["description"] = description
|
128
|
+
if properties is not None:
|
129
|
+
prop["properties"] = properties
|
130
|
+
if required is not None:
|
131
|
+
prop["required"] = required
|
132
|
+
if additional_properties is not None:
|
133
|
+
prop["additionalProperties"] = additional_properties
|
134
|
+
return prop
|
135
|
+
|
136
|
+
|
137
|
+
def _convert_complex_property(
|
138
|
+
prop: dict[str, Any],
|
139
|
+
description: str | None = None,
|
140
|
+
) -> Property:
|
141
|
+
"""Convert complex schema properties to simple OpenAI-compatible types.
|
142
|
+
|
143
|
+
Args:
|
144
|
+
prop: Complex property definition
|
145
|
+
description: Optional property description
|
146
|
+
|
147
|
+
Returns:
|
148
|
+
Simplified Property compatible with OpenAI
|
149
|
+
"""
|
150
|
+
if "anyOf" in prop or "oneOf" in prop:
|
151
|
+
types = []
|
152
|
+
for subschema in prop.get("anyOf", prop.get("oneOf", [])):
|
153
|
+
if isinstance(subschema, dict):
|
154
|
+
types.append(subschema.get("type")) # noqa: PERF401
|
155
|
+
|
156
|
+
# If null is allowed, treat as optional string
|
157
|
+
if "null" in types:
|
158
|
+
return _create_simple_property(
|
159
|
+
type_str="string",
|
160
|
+
description=description or prop.get("description"),
|
161
|
+
default=None,
|
162
|
+
)
|
163
|
+
|
164
|
+
# Get first non-null type or default to string
|
165
|
+
first_type = next((t for t in types if t != "null"), "string")
|
166
|
+
if first_type not in ("string", "number", "integer", "boolean"):
|
167
|
+
first_type = "string"
|
168
|
+
|
169
|
+
return _create_simple_property(
|
170
|
+
type_str=first_type, # type: ignore # Valid since we checked above
|
171
|
+
description=description or prop.get("description"),
|
172
|
+
default=prop.get("default"),
|
173
|
+
)
|
174
|
+
|
175
|
+
if prop.get("type") == "array":
|
176
|
+
items = prop.get("items", {"type": "string"})
|
177
|
+
if isinstance(items, dict):
|
178
|
+
items = _convert_complex_property(items)
|
179
|
+
return _create_array_property(
|
180
|
+
items=items,
|
181
|
+
description=description or prop.get("description"),
|
182
|
+
)
|
183
|
+
|
184
|
+
if prop.get("type") == "object":
|
185
|
+
sub_props = prop.get("properties", {})
|
186
|
+
cleaned_props = {
|
187
|
+
name: _convert_complex_property(p) for name, p in sub_props.items()
|
188
|
+
}
|
189
|
+
return _create_object_property(
|
190
|
+
description=description or prop.get("description"),
|
191
|
+
properties=cleaned_props,
|
192
|
+
required=prop.get("required"),
|
193
|
+
)
|
194
|
+
|
195
|
+
type_str = prop.get("type", "string")
|
196
|
+
if type_str not in ("string", "number", "integer", "boolean"):
|
197
|
+
type_str = "string"
|
198
|
+
|
199
|
+
return _create_simple_property(
|
200
|
+
type_str=type_str, # type: ignore # Valid since we checked above
|
201
|
+
description=description or prop.get("description"),
|
202
|
+
enum_values=prop.get("enum"),
|
203
|
+
default=prop.get("default"),
|
204
|
+
fmt=prop.get("format"),
|
205
|
+
)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: schemez
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.2.0
|
4
4
|
Summary: Pydantic shim for config stuff
|
5
5
|
Keywords:
|
6
6
|
Author: Philipp Temminghoff
|
@@ -41,6 +41,7 @@ Classifier: Topic :: Documentation
|
|
41
41
|
Classifier: Topic :: Software Development
|
42
42
|
Classifier: Topic :: Utilities
|
43
43
|
Classifier: Typing :: Typed
|
44
|
+
Requires-Dist: docstring-parser>=0.17.0
|
44
45
|
Requires-Dist: griffe>=1.7.3
|
45
46
|
Requires-Dist: pydantic
|
46
47
|
Requires-Dist: universal-pathlib>=0.2.6
|
@@ -0,0 +1,19 @@
|
|
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,,
|
schemez-1.1.0.dist-info/RECORD
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
schemez/__init__.py,sha256=iDo1ZpV07BUOmRmISV6QDA8s3viJR5V1NnrBsdw6eVM,985
|
2
|
-
schemez/code.py,sha256=usZLov9i5KpK1W2VJxngUzeetgrINtodiooG_AxN-y4,2072
|
3
|
-
schemez/convert.py,sha256=b6Sz11lq0HvpXfMREOqnnw8rcVg2XzTKhjjPNc4YIoE,4403
|
4
|
-
schemez/docstrings.py,sha256=kmd660wcomXzKac0SSNYxPRNbVCUovrpmE9jwnVRS6c,4115
|
5
|
-
schemez/helpers.py,sha256=TeYYb0_SghARkDwfXzlnrSErE4bhoGqzmDSCW63eOms,8016
|
6
|
-
schemez/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
7
|
-
schemez/pydantic_types.py,sha256=8vgSl8i2z9n0fB-8AJj-D3TBByEWE5IxItBxQ0XwXFI,1640
|
8
|
-
schemez/schema.py,sha256=tBrEuYuq_vqgjNZMebhdkbcFWXhVqRPTZCuCqL0boVk,9500
|
9
|
-
schemez/schemadef/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
-
schemez/schemadef/schemadef.py,sha256=FtD7TOnYxiuYOIfadRHKkkbZn98mWFb0_lKfPsPR-hI,14393
|
11
|
-
schemez-1.1.0.dist-info/WHEEL,sha256=I8-bO5cg2sb8TH6ZM6EgCP87Y1cV_f9UGgWnfAhVOZI,78
|
12
|
-
schemez-1.1.0.dist-info/METADATA,sha256=BdB7YfOeV3zs62Z1CjDfLSL0qhDZXg-4-s9cXdItdqs,5359
|
13
|
-
schemez-1.1.0.dist-info/RECORD,,
|
File without changes
|