schemez 1.1.1__py3-none-any.whl → 1.2.2__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/executable.py ADDED
@@ -0,0 +1,211 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from collections.abc import AsyncIterator, Callable # noqa: TC003
5
+ from typing import TYPE_CHECKING, Any, TypeVar, overload
6
+
7
+ from schemez.functionschema import FunctionType, create_schema
8
+
9
+
10
+ if TYPE_CHECKING:
11
+ from collections.abc import AsyncGenerator, Generator
12
+
13
+ from schemez.functionschema import FunctionSchema
14
+
15
+ T_co = TypeVar("T_co", covariant=True)
16
+
17
+
18
+ class ExecutableFunction[T_co]:
19
+ """Wrapper for executing functions with different calling patterns."""
20
+
21
+ def __init__(
22
+ self,
23
+ schema: FunctionSchema,
24
+ func: (
25
+ Callable[..., T_co]
26
+ | Callable[..., Generator[T_co]]
27
+ | Callable[..., AsyncGenerator[T_co]]
28
+ | Callable[..., AsyncIterator[T_co]]
29
+ ),
30
+ ) -> None:
31
+ """Initialize with schema and function.
32
+
33
+ Args:
34
+ schema: OpenAI function schema
35
+ func: The actual function to execute
36
+ """
37
+ self.schema = schema
38
+ self.func = func
39
+
40
+ def run(self, *args: Any, **kwargs: Any) -> T_co | list[T_co]: # noqa: PLR0911
41
+ """Run the function synchronously.
42
+
43
+ Args:
44
+ *args: Positional arguments to pass to the function
45
+ **kwargs: Keyword arguments to pass to the function
46
+
47
+ Returns:
48
+ Either a single result or list of results for generators
49
+ """
50
+ match self.schema.function_type:
51
+ case FunctionType.SYNC:
52
+ return self.func(*args, **kwargs) # type: ignore
53
+ case FunctionType.ASYNC:
54
+ try:
55
+ loop = asyncio.get_running_loop()
56
+ except RuntimeError:
57
+ return asyncio.run(self.func(*args, **kwargs)) # type: ignore
58
+ else:
59
+ if loop.is_running():
60
+ new_loop = asyncio.new_event_loop()
61
+ try:
62
+ return new_loop.run_until_complete(
63
+ self.func(*args, **kwargs), # type: ignore
64
+ )
65
+ finally:
66
+ new_loop.close()
67
+ return loop.run_until_complete(
68
+ self.func(*args, **kwargs), # type: ignore
69
+ )
70
+ case FunctionType.SYNC_GENERATOR:
71
+ return list(self.func(*args, **kwargs)) # type: ignore
72
+ case FunctionType.ASYNC_GENERATOR:
73
+ try:
74
+ loop = asyncio.get_running_loop()
75
+ except RuntimeError:
76
+ return asyncio.run(self._collect_async_gen(*args, **kwargs))
77
+ else:
78
+ if loop.is_running():
79
+ new_loop = asyncio.new_event_loop()
80
+ try:
81
+ return new_loop.run_until_complete(
82
+ self._collect_async_gen(*args, **kwargs),
83
+ )
84
+ finally:
85
+ new_loop.close()
86
+ return loop.run_until_complete(
87
+ self._collect_async_gen(*args, **kwargs),
88
+ )
89
+ case _:
90
+ msg = f"Unknown function type: {self.schema.function_type}"
91
+ raise ValueError(msg)
92
+
93
+ async def _collect_async_gen(self, *args: Any, **kwargs: Any) -> list[T_co]:
94
+ """Collect async generator results into a list.
95
+
96
+ Args:
97
+ *args: Positional arguments to pass to the function
98
+ **kwargs: Keyword arguments to pass to the function
99
+
100
+ Returns:
101
+ List of collected results
102
+ """
103
+ return [x async for x in self.func(*args, **kwargs)] # type: ignore
104
+
105
+ async def arun(self, *args: Any, **kwargs: Any) -> T_co | list[T_co]:
106
+ """Run the function asynchronously.
107
+
108
+ Args:
109
+ *args: Positional arguments to pass to the function
110
+ **kwargs: Keyword arguments to pass to the function
111
+
112
+ Returns:
113
+ Function result or list of results for generators
114
+
115
+ Raises:
116
+ ValueError: If the function type is unknown
117
+ """
118
+ match self.schema.function_type:
119
+ case FunctionType.SYNC:
120
+ return self.func(*args, **kwargs) # type: ignore
121
+ case FunctionType.ASYNC:
122
+ return await self.func(*args, **kwargs) # type: ignore
123
+ case FunctionType.SYNC_GENERATOR:
124
+ return list(self.func(*args, **kwargs)) # type: ignore
125
+ case FunctionType.ASYNC_GENERATOR:
126
+ return [x async for x in self.func(*args, **kwargs)] # type: ignore
127
+ case _:
128
+ msg = f"Unknown function type: {self.schema.function_type}"
129
+ raise ValueError(msg)
130
+
131
+ async def astream(self, *args: Any, **kwargs: Any) -> AsyncIterator[T_co]:
132
+ """Stream results from the function.
133
+
134
+ Args:
135
+ *args: Positional arguments to pass to the function
136
+ **kwargs: Keyword arguments to pass to the function
137
+
138
+ Yields:
139
+ Individual results as they become available
140
+
141
+ Raises:
142
+ ValueError: If the function type is unknown
143
+ """
144
+ match self.schema.function_type:
145
+ case FunctionType.SYNC_GENERATOR:
146
+ for x in self.func(*args, **kwargs): # type: ignore
147
+ yield x
148
+ case FunctionType.ASYNC_GENERATOR:
149
+ async for x in self.func(*args, **kwargs): # type: ignore
150
+ yield x
151
+ case FunctionType.SYNC:
152
+ yield self.func(*args, **kwargs) # type: ignore
153
+ case FunctionType.ASYNC:
154
+ yield await self.func(*args, **kwargs) # type: ignore
155
+ case _:
156
+ msg = f"Unknown function type: {self.schema.function_type}"
157
+ raise ValueError(msg)
158
+
159
+
160
+ @overload
161
+ def create_executable[T_co](
162
+ func: Callable[..., T_co],
163
+ ) -> ExecutableFunction[T_co]: ...
164
+
165
+
166
+ @overload
167
+ def create_executable[T_co](
168
+ func: Callable[..., Generator[T_co]],
169
+ ) -> ExecutableFunction[T_co]: ...
170
+
171
+
172
+ @overload
173
+ def create_executable[T_co](
174
+ func: Callable[..., AsyncGenerator[T_co]],
175
+ ) -> ExecutableFunction[T_co]: ...
176
+
177
+
178
+ def create_executable(
179
+ func: (
180
+ Callable[..., T_co]
181
+ | Callable[..., Generator[T_co]]
182
+ | Callable[..., AsyncGenerator[T_co]]
183
+ ),
184
+ ) -> ExecutableFunction[T_co]:
185
+ """Create an executable function wrapper with schema.
186
+
187
+ Args:
188
+ func: Function to wrap
189
+
190
+ Returns:
191
+ Executable wrapper with schema
192
+ """
193
+ schema = create_schema(func)
194
+ return ExecutableFunction(schema, func)
195
+
196
+
197
+ if __name__ == "__main__":
198
+ from typing import Literal
199
+
200
+ def get_weather(
201
+ location: str,
202
+ unit: Literal["C", "F"] = "C",
203
+ detailed: bool = False,
204
+ ) -> dict[str, str | float]:
205
+ return {"temp": 22.5, "conditions": "sunny"}
206
+
207
+ exe = create_executable(get_weather)
208
+ # Execute the function
209
+ result = exe.run("London", unit="C")
210
+ print("\nFunction Result:")
211
+ print(result)