hammad-python 0.0.13__py3-none-any.whl → 0.0.15__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.
- hammad_python-0.0.15.dist-info/METADATA +184 -0
- hammad_python-0.0.15.dist-info/RECORD +4 -0
- hammad/__init__.py +0 -180
- hammad/_core/__init__.py +0 -1
- hammad/_core/_utils/__init__.py +0 -4
- hammad/_core/_utils/_import_utils.py +0 -182
- hammad/ai/__init__.py +0 -59
- hammad/ai/_utils.py +0 -142
- hammad/ai/completions/__init__.py +0 -44
- hammad/ai/completions/client.py +0 -729
- hammad/ai/completions/create.py +0 -686
- hammad/ai/completions/types.py +0 -711
- hammad/ai/completions/utils.py +0 -374
- hammad/ai/embeddings/__init__.py +0 -35
- hammad/ai/embeddings/client/__init__.py +0 -1
- hammad/ai/embeddings/client/base_embeddings_client.py +0 -26
- hammad/ai/embeddings/client/fastembed_text_embeddings_client.py +0 -200
- hammad/ai/embeddings/client/litellm_embeddings_client.py +0 -288
- hammad/ai/embeddings/create.py +0 -159
- hammad/ai/embeddings/types.py +0 -69
- hammad/base/__init__.py +0 -35
- hammad/base/fields.py +0 -546
- hammad/base/model.py +0 -1078
- hammad/base/utils.py +0 -280
- hammad/cache/__init__.py +0 -48
- hammad/cache/base_cache.py +0 -181
- hammad/cache/cache.py +0 -169
- hammad/cache/decorators.py +0 -261
- hammad/cache/file_cache.py +0 -80
- hammad/cache/ttl_cache.py +0 -74
- hammad/cli/__init__.py +0 -33
- hammad/cli/animations.py +0 -604
- hammad/cli/plugins.py +0 -781
- hammad/cli/styles/__init__.py +0 -55
- hammad/cli/styles/settings.py +0 -139
- hammad/cli/styles/types.py +0 -358
- hammad/cli/styles/utils.py +0 -480
- hammad/configuration/__init__.py +0 -35
- hammad/configuration/configuration.py +0 -564
- hammad/data/__init__.py +0 -39
- hammad/data/collections/__init__.py +0 -34
- hammad/data/collections/base_collection.py +0 -58
- hammad/data/collections/collection.py +0 -452
- hammad/data/collections/searchable_collection.py +0 -556
- hammad/data/collections/vector_collection.py +0 -603
- hammad/data/databases/__init__.py +0 -21
- hammad/data/databases/database.py +0 -902
- hammad/json/__init__.py +0 -21
- hammad/json/converters.py +0 -152
- hammad/logging/__init__.py +0 -35
- hammad/logging/decorators.py +0 -834
- hammad/logging/logger.py +0 -954
- hammad/multimodal/__init__.py +0 -24
- hammad/multimodal/audio.py +0 -96
- hammad/multimodal/image.py +0 -80
- hammad/multithreading/__init__.py +0 -304
- hammad/py.typed +0 -0
- hammad/pydantic/__init__.py +0 -43
- hammad/pydantic/converters.py +0 -623
- hammad/pydantic/models/__init__.py +0 -28
- hammad/pydantic/models/arbitrary_model.py +0 -46
- hammad/pydantic/models/cacheable_model.py +0 -79
- hammad/pydantic/models/fast_model.py +0 -318
- hammad/pydantic/models/function_model.py +0 -176
- hammad/pydantic/models/subscriptable_model.py +0 -63
- hammad/text/__init__.py +0 -82
- hammad/text/converters.py +0 -723
- hammad/text/markdown.py +0 -131
- hammad/text/text.py +0 -1066
- hammad/types/__init__.py +0 -11
- hammad/types/file.py +0 -358
- hammad/typing/__init__.py +0 -407
- hammad/web/__init__.py +0 -43
- hammad/web/http/__init__.py +0 -1
- hammad/web/http/client.py +0 -944
- hammad/web/models.py +0 -245
- hammad/web/openapi/__init__.py +0 -0
- hammad/web/openapi/client.py +0 -740
- hammad/web/search/__init__.py +0 -1
- hammad/web/search/client.py +0 -988
- hammad/web/utils.py +0 -472
- hammad/yaml/__init__.py +0 -30
- hammad/yaml/converters.py +0 -19
- hammad_python-0.0.13.dist-info/METADATA +0 -38
- hammad_python-0.0.13.dist-info/RECORD +0 -85
- {hammad_python-0.0.13.dist-info → hammad_python-0.0.15.dist-info}/WHEEL +0 -0
- {hammad_python-0.0.13.dist-info → hammad_python-0.0.15.dist-info}/licenses/LICENSE +0 -0
@@ -1,79 +0,0 @@
|
|
1
|
-
"""hammad.pydantic.models.cacheable_model"""
|
2
|
-
|
3
|
-
from typing import Any, Callable, Dict, Optional
|
4
|
-
from functools import wraps
|
5
|
-
|
6
|
-
from .subscriptable_model import SubscriptableModel
|
7
|
-
|
8
|
-
__all__ = ("CacheableModel",)
|
9
|
-
|
10
|
-
|
11
|
-
class CacheableModel(SubscriptableModel):
|
12
|
-
"""
|
13
|
-
A model with built-in caching for computed properties.
|
14
|
-
Automatically caches expensive computations and invalidates when dependencies change.
|
15
|
-
|
16
|
-
Usage:
|
17
|
-
>>> class MyModel(CacheableModel):
|
18
|
-
... value: int
|
19
|
-
... @CacheableModel.cached_property(dependencies=["value"])
|
20
|
-
... def expensive_computation(self) -> int:
|
21
|
-
... return self.value ** 2
|
22
|
-
>>> model = MyModel(value=10)
|
23
|
-
>>> model.expensive_computation # Computed once
|
24
|
-
>>> model.expensive_computation # Returns cached value
|
25
|
-
"""
|
26
|
-
|
27
|
-
def __init__(self, **data: Any):
|
28
|
-
super().__init__(**data)
|
29
|
-
self._cache: Dict[str, Any] = {}
|
30
|
-
self._cache_dependencies: Dict[str, list] = {}
|
31
|
-
|
32
|
-
@classmethod
|
33
|
-
def cached_property(cls, dependencies: Optional[list] = None):
|
34
|
-
"""Decorator for creating cached properties with optional dependencies."""
|
35
|
-
|
36
|
-
def decorator(func: Callable) -> property:
|
37
|
-
prop_name = func.__name__
|
38
|
-
deps = dependencies or []
|
39
|
-
|
40
|
-
@wraps(func)
|
41
|
-
def wrapper(self) -> Any:
|
42
|
-
# Check if cached and dependencies haven't changed
|
43
|
-
if prop_name in self._cache:
|
44
|
-
if not deps or all(
|
45
|
-
getattr(self, dep) == self._cache.get(f"_{dep}_snapshot")
|
46
|
-
for dep in deps
|
47
|
-
):
|
48
|
-
return self._cache[prop_name]
|
49
|
-
|
50
|
-
# Compute and cache
|
51
|
-
result = func(self)
|
52
|
-
self._cache[prop_name] = result
|
53
|
-
|
54
|
-
# Store dependency snapshots
|
55
|
-
for dep in deps:
|
56
|
-
self._cache[f"_{dep}_snapshot"] = getattr(self, dep)
|
57
|
-
|
58
|
-
return result
|
59
|
-
|
60
|
-
return property(wrapper)
|
61
|
-
|
62
|
-
return decorator
|
63
|
-
|
64
|
-
def clear_cache(self, property_name: Optional[str] = None) -> None:
|
65
|
-
"""Clear cache for specific property or all cached properties."""
|
66
|
-
if property_name:
|
67
|
-
self._cache.pop(property_name, None)
|
68
|
-
else:
|
69
|
-
self._cache.clear()
|
70
|
-
|
71
|
-
def __setattr__(self, name: str, value: Any) -> None:
|
72
|
-
# Invalidate cache when dependencies change
|
73
|
-
if hasattr(self, "_cache") and name in self.__class__.model_fields:
|
74
|
-
# Clear cache for properties that depend on this field
|
75
|
-
for prop_name, deps in getattr(self, "_cache_dependencies", {}).items():
|
76
|
-
if name in deps:
|
77
|
-
self._cache.pop(prop_name, None)
|
78
|
-
|
79
|
-
super().__setattr__(name, value)
|
@@ -1,318 +0,0 @@
|
|
1
|
-
"""hammad.pydantic.models.fast_model"""
|
2
|
-
|
3
|
-
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, TypeVar, Generic
|
4
|
-
from pydantic import BaseModel, ConfigDict
|
5
|
-
from dataclasses import dataclass
|
6
|
-
import copy
|
7
|
-
|
8
|
-
T = TypeVar("T")
|
9
|
-
|
10
|
-
__all__ = ("FastModel",)
|
11
|
-
|
12
|
-
|
13
|
-
class FastModel(BaseModel, Generic[T]):
|
14
|
-
"""
|
15
|
-
FastModel = Pydantic BaseModel with IDE friendly(auto code completion),
|
16
|
-
dot-accessible attributes with extended type hints & utilizing the
|
17
|
-
Pydantic style `model_..` method naming convention to avoid conflicts.
|
18
|
-
|
19
|
-
Combines the power of Pydantic BaseModel with dictionary-like access patterns.
|
20
|
-
|
21
|
-
Examples:
|
22
|
-
|
23
|
-
```python
|
24
|
-
model = FastModel(name="John", age=30)
|
25
|
-
|
26
|
-
print(model.name)
|
27
|
-
print(model.age)
|
28
|
-
|
29
|
-
model.name = "Jane"
|
30
|
-
print(model.name)
|
31
|
-
|
32
|
-
model.age = 25
|
33
|
-
|
34
|
-
# Dictionary-like access
|
35
|
-
print(model["name"])
|
36
|
-
model["age"] = 30
|
37
|
-
```
|
38
|
-
"""
|
39
|
-
|
40
|
-
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
41
|
-
|
42
|
-
def __init__(self: "FastModel[T]", *args: Any, **kwargs: Any) -> None:
|
43
|
-
# Handle dictionary-like initialization
|
44
|
-
if args and len(args) == 1 and isinstance(args[0], dict):
|
45
|
-
kwargs.update(args[0])
|
46
|
-
args = ()
|
47
|
-
|
48
|
-
super().__init__(**kwargs)
|
49
|
-
|
50
|
-
# Set all properties to None for annotated attributes not provided
|
51
|
-
for k, v in self.model_attr_types().items():
|
52
|
-
if k not in kwargs:
|
53
|
-
setattr(self, k, None)
|
54
|
-
|
55
|
-
# Set default values of annotated attributes
|
56
|
-
self.init()
|
57
|
-
|
58
|
-
def init(self: "FastModel[T]") -> None:
|
59
|
-
"""Override this method to set default values."""
|
60
|
-
...
|
61
|
-
|
62
|
-
def __getstate__(self: "FastModel[T]") -> dict[str, Any]:
|
63
|
-
return self.model_dump()
|
64
|
-
|
65
|
-
def __setstate__(self: "FastModel[T]", state: dict[str, Any]) -> "FastModel[T]":
|
66
|
-
return FastModel.model_from_dict(state)
|
67
|
-
|
68
|
-
def __deepcopy__(
|
69
|
-
self: "FastModel[T]", memo: Optional[dict[int, Any]] = None
|
70
|
-
) -> "FastModel[T]":
|
71
|
-
# Get current field values
|
72
|
-
current_data = {}
|
73
|
-
for key in self.keys():
|
74
|
-
current_data[key] = getattr(self, key)
|
75
|
-
|
76
|
-
new = self.model_from_dict(current_data)
|
77
|
-
for key in self.__class__.model_fields.keys():
|
78
|
-
new.model_set_attribute(key, copy.deepcopy(getattr(self, key), memo=memo))
|
79
|
-
return new
|
80
|
-
|
81
|
-
# Dictionary-like access methods
|
82
|
-
def __getitem__(self: "FastModel[T]", key: str) -> Any:
|
83
|
-
"""Get field value using dict-like access."""
|
84
|
-
if hasattr(self, key):
|
85
|
-
return getattr(self, key)
|
86
|
-
raise KeyError(key)
|
87
|
-
|
88
|
-
def __setitem__(self: "FastModel[T]", key: str, value: Any) -> None:
|
89
|
-
"""Set field value using dict-like access."""
|
90
|
-
setattr(self, key, value)
|
91
|
-
|
92
|
-
def __contains__(self: "FastModel[T]", key: str) -> bool:
|
93
|
-
"""Check if field exists using 'in' operator."""
|
94
|
-
return hasattr(self, key) or key in self.__class__.model_fields
|
95
|
-
|
96
|
-
def get(self: "FastModel[T]", key: str, default: Any = None) -> Any:
|
97
|
-
"""Get field value with optional default."""
|
98
|
-
return getattr(self, key, default)
|
99
|
-
|
100
|
-
def keys(self: "FastModel[T]"):
|
101
|
-
"""Return all field names."""
|
102
|
-
return list(self.__class__.model_fields.keys()) + [
|
103
|
-
k for k in self.__dict__.keys() if not k.startswith("_")
|
104
|
-
]
|
105
|
-
|
106
|
-
def items(self: "FastModel[T]"):
|
107
|
-
"""Return all field (name, value) pairs."""
|
108
|
-
for key in self.keys():
|
109
|
-
yield key, getattr(self, key)
|
110
|
-
|
111
|
-
def values(self: "FastModel[T]"):
|
112
|
-
"""Return all field values."""
|
113
|
-
for key in self.keys():
|
114
|
-
yield getattr(self, key)
|
115
|
-
|
116
|
-
def update(self: "FastModel[T]", other: dict) -> None:
|
117
|
-
"""Update multiple fields at once."""
|
118
|
-
for key, value in other.items():
|
119
|
-
setattr(self, key, value)
|
120
|
-
|
121
|
-
@classmethod
|
122
|
-
def model_from_dict(cls: type["FastModel[T]"], d: dict[str, Any]) -> "FastModel[T]":
|
123
|
-
return cls(**d)
|
124
|
-
|
125
|
-
@classmethod
|
126
|
-
def model_attr_has_default_value(cls: type["FastModel[T]"], attr_name: str) -> bool:
|
127
|
-
return hasattr(cls, attr_name) and not callable(getattr(cls, attr_name))
|
128
|
-
|
129
|
-
@classmethod
|
130
|
-
def model_get_attr_default_value(cls: type["FastModel[T]"], attr_name: str) -> Any:
|
131
|
-
if cls.model_attr_has_default_value(attr_name):
|
132
|
-
return getattr(cls, attr_name)
|
133
|
-
else:
|
134
|
-
return None
|
135
|
-
|
136
|
-
@classmethod
|
137
|
-
def model_attr_type(cls: type["FastModel[T]"], attr_name: str) -> type:
|
138
|
-
return cls.model_attr_types()[attr_name]
|
139
|
-
|
140
|
-
@classmethod
|
141
|
-
def model_attr_types(cls: type["FastModel[T]"]) -> dict[str, type]:
|
142
|
-
return cls.__annotations__ if hasattr(cls, "__annotations__") else {}
|
143
|
-
|
144
|
-
@classmethod
|
145
|
-
def model_attr_names(cls: type["FastModel[T]"]) -> List[str]:
|
146
|
-
"""
|
147
|
-
Returns annotated attribute names
|
148
|
-
:return: List[str]
|
149
|
-
"""
|
150
|
-
return [k for k, v in cls.model_attr_types().items()]
|
151
|
-
|
152
|
-
@classmethod
|
153
|
-
def model_has_attr(cls: type["FastModel[T]"], attr_name: str) -> bool:
|
154
|
-
"""
|
155
|
-
Returns True if class have an annotated attribute
|
156
|
-
:param attr_name: Attribute name
|
157
|
-
:return: bool
|
158
|
-
"""
|
159
|
-
return bool(cls.model_attr_types().get(attr_name))
|
160
|
-
|
161
|
-
def model_set_default(self: "FastModel[T]", attr_name: str) -> None:
|
162
|
-
if self.model_attr_has_default_value(attr_name):
|
163
|
-
attr_default_value: Any = self.model_get_attr_default_value(attr_name)
|
164
|
-
setattr(self, attr_name, attr_default_value)
|
165
|
-
|
166
|
-
def model_get_constructor(
|
167
|
-
self: "FastModel[T]", attr_name: str, value: Any
|
168
|
-
) -> Tuple[Optional[Callable[..., Any]], Optional[type]]:
|
169
|
-
"""
|
170
|
-
This method is used for type conversion.
|
171
|
-
FastModel uses this method to get the type of a value, then based on the
|
172
|
-
value, it return a constructor. If the type of a value is 'float' then
|
173
|
-
it returns 'float' since 'float' is also a constructor to build a float
|
174
|
-
value.
|
175
|
-
"""
|
176
|
-
attr_type1: type = self.model_attr_type(attr_name)
|
177
|
-
constructor: Optional[Callable[..., Any]] = None
|
178
|
-
element_type: Optional[type] = None
|
179
|
-
|
180
|
-
if attr_type1 == float:
|
181
|
-
constructor = float
|
182
|
-
elif attr_type1 == str:
|
183
|
-
constructor = str
|
184
|
-
elif attr_type1 == int:
|
185
|
-
constructor = int
|
186
|
-
elif attr_type1 == list:
|
187
|
-
constructor = list
|
188
|
-
elif isinstance(value, FastModel):
|
189
|
-
constructor = attr_type1.model_from_dict
|
190
|
-
elif attr_type1 is Any:
|
191
|
-
constructor = None
|
192
|
-
elif isinstance(value, dict):
|
193
|
-
if attr_type1 == dict:
|
194
|
-
constructor = FastModel.model_from_dict
|
195
|
-
elif issubclass(attr_type1, FastModel):
|
196
|
-
constructor = self.model_attr_type(attr_name).model_from_dict
|
197
|
-
elif attr_type1 is List:
|
198
|
-
constructor = list
|
199
|
-
elif hasattr(attr_type1, "__origin__"):
|
200
|
-
if attr_type1.__dict__["__origin__"] is list:
|
201
|
-
# if the type is 'List[something]'
|
202
|
-
if len(attr_type1.__args__) == 0:
|
203
|
-
constructor = list
|
204
|
-
elif len(attr_type1.__args__) == 1:
|
205
|
-
constructor = List
|
206
|
-
element_type = attr_type1.__args__[0]
|
207
|
-
elif len(attr_type1.__args__) > 1:
|
208
|
-
raise TypeError("Only one dimensional List is supported")
|
209
|
-
elif attr_type1.__dict__["__origin__"] is tuple:
|
210
|
-
# if the type is 'Tuple[something]'
|
211
|
-
constructor = tuple
|
212
|
-
|
213
|
-
return constructor, element_type
|
214
|
-
|
215
|
-
def model_set_attribute(self: "FastModel[T]", attr_name: str, value: Any) -> None:
|
216
|
-
element_type: Optional[type] = None
|
217
|
-
|
218
|
-
# Check for reserved dict keys (restored from original FastModel)
|
219
|
-
dict_reserved_keys = vars(dict).keys()
|
220
|
-
if attr_name in dict_reserved_keys:
|
221
|
-
raise TypeError("You cannot set a reserved name as attribute")
|
222
|
-
|
223
|
-
if self.model_has_attr(attr_name):
|
224
|
-
if value is None:
|
225
|
-
setattr(self, attr_name, None)
|
226
|
-
elif self.model_attr_type(attr_name) == Any:
|
227
|
-
setattr(self, attr_name, value)
|
228
|
-
else:
|
229
|
-
constructor, element_type = self.model_get_constructor(attr_name, value)
|
230
|
-
if constructor is None:
|
231
|
-
setattr(self, attr_name, value)
|
232
|
-
elif constructor == List:
|
233
|
-
# NOTE: fix typing
|
234
|
-
value_list: List[Any] = value
|
235
|
-
new_list: List[Any] = []
|
236
|
-
|
237
|
-
if element_type and issubclass(element_type, FastModel):
|
238
|
-
element_constructor: Callable[[Any], Any] = (
|
239
|
-
element_type.model_from_dict
|
240
|
-
)
|
241
|
-
else:
|
242
|
-
element_constructor = (
|
243
|
-
element_type if element_type else lambda x: x
|
244
|
-
)
|
245
|
-
|
246
|
-
for v in value_list:
|
247
|
-
new_list.append(element_constructor(v))
|
248
|
-
setattr(self, attr_name, new_list)
|
249
|
-
elif constructor == list:
|
250
|
-
setattr(self, attr_name, list(value))
|
251
|
-
else:
|
252
|
-
setattr(self, attr_name, constructor(value))
|
253
|
-
else:
|
254
|
-
if isinstance(value, dict):
|
255
|
-
if isinstance(value, FastModel):
|
256
|
-
constructor: Callable[[Any], Any] = value.model_from_dict
|
257
|
-
else:
|
258
|
-
constructor = FastModel.model_from_dict
|
259
|
-
setattr(self, attr_name, constructor(value))
|
260
|
-
else:
|
261
|
-
setattr(self, attr_name, value)
|
262
|
-
|
263
|
-
def model_set_attributes(self: "FastModel[T]", **d: Any) -> None:
|
264
|
-
for k, v in d.items():
|
265
|
-
self.model_set_attribute(k, v)
|
266
|
-
|
267
|
-
def __getattr__(self: "FastModel[T]", item: str) -> T:
|
268
|
-
"""Handle missing attribute access like the original FastModel."""
|
269
|
-
# Avoid infinite recursion by checking the actual object dict and model fields
|
270
|
-
if item in self.__class__.model_fields or item in self.__dict__:
|
271
|
-
return getattr(self, item)
|
272
|
-
|
273
|
-
raise AttributeError(
|
274
|
-
f"{type(self).__name__!r} object has no attribute {item!r}"
|
275
|
-
)
|
276
|
-
|
277
|
-
def to_dict(
|
278
|
-
self: "FastModel[T]",
|
279
|
-
*args: Any,
|
280
|
-
exclude: Optional[List[str]] = None,
|
281
|
-
is_recursive: bool = False,
|
282
|
-
exclude_none: bool = False,
|
283
|
-
exclude_none_in_lists: bool = False,
|
284
|
-
**kwargs: Any,
|
285
|
-
) -> dict[str, T]:
|
286
|
-
"""Convert to dictionary with various options."""
|
287
|
-
exclude_set: Set[str] = set(exclude) if exclude is not None else set()
|
288
|
-
ret: dict[str, T] = {}
|
289
|
-
|
290
|
-
for k in self.keys():
|
291
|
-
if k in exclude_set:
|
292
|
-
continue
|
293
|
-
|
294
|
-
v = getattr(self, k)
|
295
|
-
|
296
|
-
if exclude_none and v is None:
|
297
|
-
continue
|
298
|
-
|
299
|
-
if is_recursive and isinstance(v, FastModel):
|
300
|
-
ret[k] = v.to_dict(
|
301
|
-
is_recursive=is_recursive,
|
302
|
-
exclude_none=exclude_none,
|
303
|
-
exclude_none_in_lists=exclude_none_in_lists,
|
304
|
-
)
|
305
|
-
elif exclude_none_in_lists and isinstance(v, list):
|
306
|
-
ret[k] = [
|
307
|
-
item.to_dict(exclude_none=True, is_recursive=is_recursive)
|
308
|
-
if isinstance(item, FastModel)
|
309
|
-
else item
|
310
|
-
for item in v
|
311
|
-
]
|
312
|
-
else:
|
313
|
-
ret[k] = v
|
314
|
-
|
315
|
-
return ret
|
316
|
-
|
317
|
-
def model_to_dataclass(self) -> dataclass:
|
318
|
-
return dataclass(**self.model_dump())
|
@@ -1,176 +0,0 @@
|
|
1
|
-
"""hammad.pydantic.models.function_model"""
|
2
|
-
|
3
|
-
from typing import Any, Callable, Dict, List, Optional, TypeVar, Generic, cast
|
4
|
-
from pydantic import BaseModel
|
5
|
-
import inspect
|
6
|
-
from typing import get_type_hints
|
7
|
-
|
8
|
-
try:
|
9
|
-
from typing import ParamSpec
|
10
|
-
except ImportError:
|
11
|
-
from typing_extensions import ParamSpec
|
12
|
-
|
13
|
-
__all__ = ("FunctionModel",)
|
14
|
-
|
15
|
-
P = ParamSpec("P")
|
16
|
-
R = TypeVar("R")
|
17
|
-
|
18
|
-
|
19
|
-
class FunctionModel(BaseModel, Generic[P, R]):
|
20
|
-
"""
|
21
|
-
A specialized pydantic model that acts as a "passthrough" for functions,
|
22
|
-
allowing for partial function application.
|
23
|
-
|
24
|
-
```python
|
25
|
-
from cursives.pydantic.models import FunctionModel
|
26
|
-
|
27
|
-
@FunctionModel(exclude = ["y"])
|
28
|
-
def some_function(x: int, y: str = "1") -> int:
|
29
|
-
return x + len(y)
|
30
|
-
|
31
|
-
print(some_function.call(x=1))
|
32
|
-
>>> 2
|
33
|
-
|
34
|
-
print(some_function.function_schema())
|
35
|
-
# Create OpenAI compatible function schema easily!
|
36
|
-
>>> {
|
37
|
-
... "name": "some_function",
|
38
|
-
... "parameters": {
|
39
|
-
... "x": {"type": "integer"},
|
40
|
-
... "y": {"type": "string", "default": "1"}
|
41
|
-
... }
|
42
|
-
... }
|
43
|
-
|
44
|
-
print(some_function.call_from_dict(...))
|
45
|
-
"""
|
46
|
-
|
47
|
-
def __init__(self, exclude: Optional[List[str]] = None, **kwargs):
|
48
|
-
super().__init__(**kwargs)
|
49
|
-
self._exclude = exclude or []
|
50
|
-
self._original_function: Optional[Callable[P, R]] = None
|
51
|
-
self._function_name: Optional[str] = None
|
52
|
-
self._signature: Optional[inspect.Signature] = None
|
53
|
-
self._type_hints: Optional[Dict[str, Any]] = None
|
54
|
-
|
55
|
-
def __call__(
|
56
|
-
self, func: Callable[P, R], exclude: Optional[List[str]] = None
|
57
|
-
) -> "FunctionModel[P, R]":
|
58
|
-
"""Make this work as a decorator."""
|
59
|
-
self._original_function = func
|
60
|
-
self._function_name = func.__name__
|
61
|
-
self._signature = inspect.signature(func)
|
62
|
-
self._type_hints = get_type_hints(func)
|
63
|
-
|
64
|
-
# Create a new instance that wraps the function
|
65
|
-
# Use exclude parameter if provided, otherwise use self._exclude
|
66
|
-
final_exclude = exclude if exclude is not None else self._exclude
|
67
|
-
wrapped: FunctionModel[P, R] = FunctionModel(exclude=final_exclude)
|
68
|
-
wrapped._original_function = func
|
69
|
-
wrapped._function_name = func.__name__
|
70
|
-
wrapped._signature = self._signature
|
71
|
-
wrapped._type_hints = self._type_hints
|
72
|
-
|
73
|
-
return wrapped
|
74
|
-
|
75
|
-
def call(self, **kwargs) -> R:
|
76
|
-
"""Call the wrapped function with provided arguments."""
|
77
|
-
if not self._original_function:
|
78
|
-
raise ValueError("No function wrapped. Use as decorator first.")
|
79
|
-
|
80
|
-
# Get all parameters from signature
|
81
|
-
sig_params = self._signature.parameters
|
82
|
-
final_kwargs = {}
|
83
|
-
|
84
|
-
# Add provided kwargs (but not excluded ones)
|
85
|
-
for key, value in kwargs.items():
|
86
|
-
if key in sig_params and key not in self._exclude:
|
87
|
-
final_kwargs[key] = value
|
88
|
-
|
89
|
-
# Add defaults for missing parameters (except excluded ones)
|
90
|
-
for param_name, param in sig_params.items():
|
91
|
-
if param_name not in final_kwargs and param_name not in self._exclude:
|
92
|
-
if param.default is not inspect.Parameter.empty:
|
93
|
-
final_kwargs[param_name] = param.default
|
94
|
-
|
95
|
-
return self._original_function(**final_kwargs)
|
96
|
-
|
97
|
-
def call_from_dict(self, data: Dict[str, Any]) -> R:
|
98
|
-
"""Call the function using a dictionary of arguments."""
|
99
|
-
return self.call(**data)
|
100
|
-
|
101
|
-
def function_schema(self) -> Dict[str, Any]:
|
102
|
-
"""Generate OpenAI-compatible function schema."""
|
103
|
-
if not self._original_function:
|
104
|
-
raise ValueError("No function wrapped. Use as decorator first.")
|
105
|
-
|
106
|
-
schema = {
|
107
|
-
"name": self._function_name,
|
108
|
-
"description": self._original_function.__doc__ or "",
|
109
|
-
"parameters": {"type": "object", "properties": {}, "required": []},
|
110
|
-
}
|
111
|
-
|
112
|
-
# Process each parameter
|
113
|
-
for param_name, param in self._signature.parameters.items():
|
114
|
-
if param_name in self._exclude:
|
115
|
-
continue
|
116
|
-
|
117
|
-
param_type = self._type_hints.get(param_name, Any)
|
118
|
-
|
119
|
-
# Convert Python types to JSON schema types
|
120
|
-
json_type = self._python_type_to_json_schema(param_type)
|
121
|
-
|
122
|
-
param_schema = {"type": json_type}
|
123
|
-
|
124
|
-
# Add default if present
|
125
|
-
if param.default is not inspect.Parameter.empty:
|
126
|
-
param_schema["default"] = param.default
|
127
|
-
else:
|
128
|
-
schema["parameters"]["required"].append(param_name)
|
129
|
-
|
130
|
-
schema["parameters"]["properties"][param_name] = param_schema
|
131
|
-
|
132
|
-
return schema
|
133
|
-
|
134
|
-
def _python_type_to_json_schema(self, python_type: Any) -> str:
|
135
|
-
"""Convert Python type to JSON schema type string."""
|
136
|
-
if python_type is int:
|
137
|
-
return "integer"
|
138
|
-
elif python_type is float:
|
139
|
-
return "number"
|
140
|
-
elif python_type is str:
|
141
|
-
return "string"
|
142
|
-
elif python_type is bool:
|
143
|
-
return "boolean"
|
144
|
-
elif python_type is list or python_type is List:
|
145
|
-
return "array"
|
146
|
-
elif python_type is dict or python_type is Dict:
|
147
|
-
return "object"
|
148
|
-
else:
|
149
|
-
return "string" # Default fallback
|
150
|
-
|
151
|
-
def partial(self, **kwargs) -> "FunctionModel":
|
152
|
-
"""Create a new FunctionModel with some arguments pre-filled."""
|
153
|
-
if not self._original_function:
|
154
|
-
raise ValueError("No function wrapped. Use as decorator first.")
|
155
|
-
|
156
|
-
def partial_func(**additional_kwargs):
|
157
|
-
combined_kwargs = {**kwargs, **additional_kwargs}
|
158
|
-
return self.call(**combined_kwargs)
|
159
|
-
|
160
|
-
# Create new wrapped function
|
161
|
-
new_wrapped = FunctionModel(exclude=self._exclude)
|
162
|
-
new_wrapped._original_function = partial_func
|
163
|
-
new_wrapped._function_name = f"{self._function_name}_partial"
|
164
|
-
|
165
|
-
# Update signature to remove pre-filled parameters
|
166
|
-
new_params = []
|
167
|
-
for param_name, param in self._signature.parameters.items():
|
168
|
-
if param_name not in kwargs:
|
169
|
-
new_params.append(param)
|
170
|
-
|
171
|
-
new_wrapped._signature = inspect.Signature(new_params)
|
172
|
-
new_wrapped._type_hints = {
|
173
|
-
k: v for k, v in self._type_hints.items() if k not in kwargs
|
174
|
-
}
|
175
|
-
|
176
|
-
return new_wrapped
|
@@ -1,63 +0,0 @@
|
|
1
|
-
"""hammad.pydantic.models.subscriptable_model"""
|
2
|
-
|
3
|
-
from pydantic import BaseModel
|
4
|
-
from typing import Any
|
5
|
-
|
6
|
-
__all__ = ("SubscriptableModel",)
|
7
|
-
|
8
|
-
|
9
|
-
class SubscriptableModel(BaseModel):
|
10
|
-
"""
|
11
|
-
A pydantic model that allows for dict-like access to its fields.
|
12
|
-
"""
|
13
|
-
|
14
|
-
def __getitem__(self, key: str) -> Any:
|
15
|
-
"""Get field value using dict-like access.
|
16
|
-
|
17
|
-
Usage:
|
18
|
-
>>> msg = Message(role='user')
|
19
|
-
>>> msg['role']
|
20
|
-
'user'
|
21
|
-
"""
|
22
|
-
if hasattr(self, key):
|
23
|
-
return getattr(self, key)
|
24
|
-
raise KeyError(key)
|
25
|
-
|
26
|
-
def __setitem__(self, key: str, value: Any) -> None:
|
27
|
-
"""Set field value using dict-like access.
|
28
|
-
|
29
|
-
Usage:
|
30
|
-
>>> msg = Message(role='user')
|
31
|
-
>>> msg['role'] = 'assistant'
|
32
|
-
>>> msg['role']
|
33
|
-
'assistant'
|
34
|
-
"""
|
35
|
-
setattr(self, key, value)
|
36
|
-
|
37
|
-
def __contains__(self, key: str) -> bool:
|
38
|
-
"""Check if field exists using 'in' operator.
|
39
|
-
|
40
|
-
Usage:
|
41
|
-
>>> msg = Message(role='user')
|
42
|
-
>>> 'role' in msg
|
43
|
-
True
|
44
|
-
>>> 'nonexistent' in msg
|
45
|
-
False
|
46
|
-
"""
|
47
|
-
if hasattr(self, key):
|
48
|
-
return True
|
49
|
-
if value := self.__class__.model_fields.get(key):
|
50
|
-
return value.default is not None
|
51
|
-
return False
|
52
|
-
|
53
|
-
def get(self, key: str, default: Any = None) -> Any:
|
54
|
-
"""Get field value with optional default.
|
55
|
-
|
56
|
-
Usage:
|
57
|
-
>>> msg = Message(role='user')
|
58
|
-
>>> msg.get('role')
|
59
|
-
'user'
|
60
|
-
>>> msg.get('nonexistent', 'default')
|
61
|
-
'default'
|
62
|
-
"""
|
63
|
-
return getattr(self, key) if hasattr(self, key) else default
|