hammad-python 0.0.14__py3-none-any.whl → 0.0.16__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.
Files changed (122) hide show
  1. hammad/__init__.py +177 -0
  2. hammad/{performance/imports.py → _internal.py} +7 -1
  3. hammad/cache/__init__.py +1 -1
  4. hammad/cli/__init__.py +3 -1
  5. hammad/cli/_runner.py +265 -0
  6. hammad/cli/animations.py +1 -1
  7. hammad/cli/plugins.py +133 -78
  8. hammad/cli/styles/__init__.py +1 -1
  9. hammad/cli/styles/utils.py +149 -3
  10. hammad/data/__init__.py +56 -29
  11. hammad/data/collections/__init__.py +27 -17
  12. hammad/data/collections/collection.py +205 -383
  13. hammad/data/collections/indexes/__init__.py +37 -0
  14. hammad/data/collections/indexes/qdrant/__init__.py +1 -0
  15. hammad/data/collections/indexes/qdrant/index.py +735 -0
  16. hammad/data/collections/indexes/qdrant/settings.py +94 -0
  17. hammad/data/collections/indexes/qdrant/utils.py +220 -0
  18. hammad/data/collections/indexes/tantivy/__init__.py +1 -0
  19. hammad/data/collections/indexes/tantivy/index.py +428 -0
  20. hammad/data/collections/indexes/tantivy/settings.py +51 -0
  21. hammad/data/collections/indexes/tantivy/utils.py +200 -0
  22. hammad/data/configurations/__init__.py +2 -2
  23. hammad/data/configurations/configuration.py +2 -2
  24. hammad/data/models/__init__.py +20 -9
  25. hammad/data/models/extensions/__init__.py +4 -0
  26. hammad/data/models/{pydantic → extensions/pydantic}/__init__.py +6 -19
  27. hammad/data/models/{pydantic → extensions/pydantic}/converters.py +143 -16
  28. hammad/data/models/{base/fields.py → fields.py} +1 -1
  29. hammad/data/models/{base/model.py → model.py} +1 -1
  30. hammad/data/models/{base/utils.py → utils.py} +1 -1
  31. hammad/data/sql/__init__.py +23 -0
  32. hammad/data/sql/database.py +578 -0
  33. hammad/data/sql/types.py +141 -0
  34. hammad/data/types/__init__.py +1 -3
  35. hammad/data/types/file.py +3 -3
  36. hammad/data/types/multimodal/__init__.py +2 -2
  37. hammad/data/types/multimodal/audio.py +2 -2
  38. hammad/data/types/multimodal/image.py +2 -2
  39. hammad/formatting/__init__.py +9 -27
  40. hammad/formatting/json/__init__.py +8 -2
  41. hammad/formatting/json/converters.py +7 -1
  42. hammad/formatting/text/__init__.py +1 -1
  43. hammad/formatting/yaml/__init__.py +1 -1
  44. hammad/genai/__init__.py +78 -0
  45. hammad/genai/agents/__init__.py +1 -0
  46. hammad/genai/agents/types/__init__.py +35 -0
  47. hammad/genai/agents/types/history.py +277 -0
  48. hammad/genai/agents/types/tool.py +490 -0
  49. hammad/genai/embedding_models/__init__.py +41 -0
  50. hammad/{ai/embeddings/client/litellm_embeddings_client.py → genai/embedding_models/embedding_model.py} +47 -142
  51. hammad/genai/embedding_models/embedding_model_name.py +77 -0
  52. hammad/genai/embedding_models/embedding_model_request.py +65 -0
  53. hammad/{ai/embeddings/types.py → genai/embedding_models/embedding_model_response.py} +3 -3
  54. hammad/genai/embedding_models/run.py +161 -0
  55. hammad/genai/language_models/__init__.py +35 -0
  56. hammad/genai/language_models/_streaming.py +622 -0
  57. hammad/genai/language_models/_types.py +276 -0
  58. hammad/genai/language_models/_utils/__init__.py +31 -0
  59. hammad/genai/language_models/_utils/_completions.py +131 -0
  60. hammad/genai/language_models/_utils/_messages.py +89 -0
  61. hammad/genai/language_models/_utils/_requests.py +202 -0
  62. hammad/genai/language_models/_utils/_structured_outputs.py +124 -0
  63. hammad/genai/language_models/language_model.py +734 -0
  64. hammad/genai/language_models/language_model_request.py +135 -0
  65. hammad/genai/language_models/language_model_response.py +219 -0
  66. hammad/genai/language_models/language_model_response_chunk.py +53 -0
  67. hammad/genai/language_models/run.py +530 -0
  68. hammad/genai/multimodal_models.py +48 -0
  69. hammad/genai/rerank_models.py +26 -0
  70. hammad/logging/__init__.py +1 -1
  71. hammad/logging/decorators.py +1 -1
  72. hammad/logging/logger.py +2 -2
  73. hammad/mcp/__init__.py +1 -1
  74. hammad/mcp/client/__init__.py +35 -0
  75. hammad/mcp/client/client.py +105 -4
  76. hammad/mcp/client/client_service.py +10 -3
  77. hammad/mcp/servers/__init__.py +24 -0
  78. hammad/{performance/runtime → runtime}/__init__.py +2 -2
  79. hammad/{performance/runtime → runtime}/decorators.py +1 -1
  80. hammad/{performance/runtime → runtime}/run.py +1 -1
  81. hammad/service/__init__.py +1 -1
  82. hammad/service/create.py +3 -8
  83. hammad/service/decorators.py +8 -8
  84. hammad/typing/__init__.py +28 -0
  85. hammad/web/__init__.py +3 -3
  86. hammad/web/http/client.py +1 -1
  87. hammad/web/models.py +53 -21
  88. hammad/web/search/client.py +99 -52
  89. hammad/web/utils.py +13 -13
  90. hammad_python-0.0.16.dist-info/METADATA +191 -0
  91. hammad_python-0.0.16.dist-info/RECORD +110 -0
  92. hammad/ai/__init__.py +0 -1
  93. hammad/ai/_utils.py +0 -142
  94. hammad/ai/completions/__init__.py +0 -45
  95. hammad/ai/completions/client.py +0 -684
  96. hammad/ai/completions/create.py +0 -710
  97. hammad/ai/completions/settings.py +0 -100
  98. hammad/ai/completions/types.py +0 -792
  99. hammad/ai/completions/utils.py +0 -486
  100. hammad/ai/embeddings/__init__.py +0 -35
  101. hammad/ai/embeddings/client/__init__.py +0 -1
  102. hammad/ai/embeddings/client/base_embeddings_client.py +0 -26
  103. hammad/ai/embeddings/client/fastembed_text_embeddings_client.py +0 -200
  104. hammad/ai/embeddings/create.py +0 -159
  105. hammad/data/collections/base_collection.py +0 -58
  106. hammad/data/collections/searchable_collection.py +0 -556
  107. hammad/data/collections/vector_collection.py +0 -596
  108. hammad/data/databases/__init__.py +0 -21
  109. hammad/data/databases/database.py +0 -902
  110. hammad/data/models/base/__init__.py +0 -35
  111. hammad/data/models/pydantic/models/__init__.py +0 -28
  112. hammad/data/models/pydantic/models/arbitrary_model.py +0 -46
  113. hammad/data/models/pydantic/models/cacheable_model.py +0 -79
  114. hammad/data/models/pydantic/models/fast_model.py +0 -318
  115. hammad/data/models/pydantic/models/function_model.py +0 -176
  116. hammad/data/models/pydantic/models/subscriptable_model.py +0 -63
  117. hammad/performance/__init__.py +0 -36
  118. hammad/py.typed +0 -0
  119. hammad_python-0.0.14.dist-info/METADATA +0 -70
  120. hammad_python-0.0.14.dist-info/RECORD +0 -99
  121. {hammad_python-0.0.14.dist-info → hammad_python-0.0.16.dist-info}/WHEEL +0 -0
  122. {hammad_python-0.0.14.dist-info → hammad_python-0.0.16.dist-info}/licenses/LICENSE +0 -0
@@ -1,35 +0,0 @@
1
- """hammad.models.base
2
-
3
- Contains the `Model` and `field` system along with an assortment
4
- of various utilities for interacting and managing these objects.
5
- """
6
-
7
- from typing import TYPE_CHECKING
8
- from ....performance.imports import create_getattr_importer
9
-
10
- if TYPE_CHECKING:
11
- from .model import Model, model_settings
12
- from .fields import field, Field, FieldInfo
13
- from .utils import create_model, validator, is_field, is_model, get_field_info
14
-
15
- __all__ = (
16
- # hammad.models.model
17
- "Model",
18
- "model_settings",
19
- # hammad.models.fields
20
- "field",
21
- "Field",
22
- "FieldInfo",
23
- # hammad.models.utils
24
- "create_model",
25
- "validator",
26
- "is_field",
27
- "is_model",
28
- "get_field_info",
29
- )
30
-
31
- __getattr__ = create_getattr_importer(__all__)
32
-
33
-
34
- def __dir__() -> list[str]:
35
- return list(__all__)
@@ -1,28 +0,0 @@
1
- """hammad.data.models.pydantic.models"""
2
-
3
- from typing import TYPE_CHECKING
4
- from .....performance.imports import create_getattr_importer
5
-
6
- if TYPE_CHECKING:
7
- from .arbitrary_model import ArbitraryModel
8
- from .cacheable_model import CacheableModel
9
- from .fast_model import FastModel
10
- from .function_model import FunctionModel
11
- from .subscriptable_model import SubscriptableModel
12
-
13
-
14
- __all__ = (
15
- "ArbitraryModel",
16
- "CacheableModel",
17
- "FastModel",
18
- "FunctionModel",
19
- "SubscriptableModel",
20
- )
21
-
22
-
23
- __getattr__ = create_getattr_importer(__all__)
24
-
25
-
26
- def __dir__() -> list[str]:
27
- """Get the attributes of the models module."""
28
- return list(__all__)
@@ -1,46 +0,0 @@
1
- """hammad.data.models.pydantic.models.arbitrary_model"""
2
-
3
- from typing import Any, Dict
4
- from pydantic import ConfigDict
5
-
6
- from .subscriptable_model import SubscriptableModel
7
-
8
- __all__ = ("ArbitraryModel",)
9
-
10
-
11
- class ArbitraryModel(SubscriptableModel):
12
- """
13
- A model that allows dynamic field assignment and access.
14
- Perfect for handling arbitrary JSON data or when schema is unknown at compile time.
15
-
16
- Usage:
17
- >>> data = ArbitraryModel()
18
- >>> data.name = "John"
19
- >>> data.age = 30
20
- >>> data.metadata = {"key": "value"}
21
- >>> print(data.name) # John
22
- >>> print(data["age"]) # 30
23
- """
24
-
25
- model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
26
-
27
- def __init__(self, **data: Any):
28
- super().__init__(**data)
29
- # Store extra fields for easy access
30
- self._arbitrary_fields: Dict[str, Any] = {}
31
-
32
- def __setattr__(self, name: str, value: Any) -> None:
33
- if name.startswith("_") or name in self.__class__.model_fields:
34
- super().__setattr__(name, value)
35
- else:
36
- # Store in dynamic fields and set normally
37
- if hasattr(self, "_arbitrary_fields"):
38
- self._arbitrary_fields[name] = value
39
- super().__setattr__(name, value)
40
-
41
- def to_dict(self) -> Dict[str, Any]:
42
- """Convert to dictionary including all dynamic fields."""
43
- result = self.model_dump()
44
- if hasattr(self, "_arbitrary_fields"):
45
- result.update(self._arbitrary_fields)
46
- return result
@@ -1,79 +0,0 @@
1
- """hammad.data.models.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.data.models.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.data.models.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