hammad-python 0.0.29__py3-none-any.whl → 0.0.31__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.
- ham/__init__.py +10 -0
- {hammad_python-0.0.29.dist-info → hammad_python-0.0.31.dist-info}/METADATA +6 -32
- hammad_python-0.0.31.dist-info/RECORD +6 -0
- hammad/__init__.py +0 -84
- hammad/_internal.py +0 -256
- hammad/_main.py +0 -226
- hammad/cache/__init__.py +0 -40
- 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 -573
- hammad/cli/plugins.py +0 -867
- 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 -634
- hammad/data/__init__.py +0 -90
- hammad/data/collections/__init__.py +0 -49
- hammad/data/collections/collection.py +0 -326
- hammad/data/collections/indexes/__init__.py +0 -37
- hammad/data/collections/indexes/qdrant/__init__.py +0 -1
- hammad/data/collections/indexes/qdrant/index.py +0 -723
- hammad/data/collections/indexes/qdrant/settings.py +0 -94
- hammad/data/collections/indexes/qdrant/utils.py +0 -210
- hammad/data/collections/indexes/tantivy/__init__.py +0 -1
- hammad/data/collections/indexes/tantivy/index.py +0 -426
- hammad/data/collections/indexes/tantivy/settings.py +0 -40
- hammad/data/collections/indexes/tantivy/utils.py +0 -176
- hammad/data/configurations/__init__.py +0 -35
- hammad/data/configurations/configuration.py +0 -564
- hammad/data/models/__init__.py +0 -50
- hammad/data/models/extensions/__init__.py +0 -4
- hammad/data/models/extensions/pydantic/__init__.py +0 -42
- hammad/data/models/extensions/pydantic/converters.py +0 -759
- hammad/data/models/fields.py +0 -546
- hammad/data/models/model.py +0 -1078
- hammad/data/models/utils.py +0 -280
- hammad/data/sql/__init__.py +0 -24
- hammad/data/sql/database.py +0 -576
- hammad/data/sql/types.py +0 -127
- hammad/data/types/__init__.py +0 -75
- hammad/data/types/file.py +0 -431
- hammad/data/types/multimodal/__init__.py +0 -36
- hammad/data/types/multimodal/audio.py +0 -200
- hammad/data/types/multimodal/image.py +0 -182
- hammad/data/types/text.py +0 -1308
- hammad/formatting/__init__.py +0 -33
- hammad/formatting/json/__init__.py +0 -27
- hammad/formatting/json/converters.py +0 -158
- hammad/formatting/text/__init__.py +0 -63
- hammad/formatting/text/converters.py +0 -723
- hammad/formatting/text/markdown.py +0 -131
- hammad/formatting/yaml/__init__.py +0 -26
- hammad/formatting/yaml/converters.py +0 -5
- hammad/genai/__init__.py +0 -217
- hammad/genai/a2a/__init__.py +0 -32
- hammad/genai/a2a/workers.py +0 -552
- hammad/genai/agents/__init__.py +0 -59
- hammad/genai/agents/agent.py +0 -1973
- hammad/genai/agents/run.py +0 -1024
- hammad/genai/agents/types/__init__.py +0 -42
- hammad/genai/agents/types/agent_context.py +0 -13
- hammad/genai/agents/types/agent_event.py +0 -128
- hammad/genai/agents/types/agent_hooks.py +0 -220
- hammad/genai/agents/types/agent_messages.py +0 -31
- hammad/genai/agents/types/agent_response.py +0 -125
- hammad/genai/agents/types/agent_stream.py +0 -327
- hammad/genai/graphs/__init__.py +0 -125
- hammad/genai/graphs/_utils.py +0 -190
- hammad/genai/graphs/base.py +0 -1828
- hammad/genai/graphs/plugins.py +0 -316
- hammad/genai/graphs/types.py +0 -638
- hammad/genai/models/__init__.py +0 -1
- hammad/genai/models/embeddings/__init__.py +0 -43
- hammad/genai/models/embeddings/model.py +0 -226
- hammad/genai/models/embeddings/run.py +0 -163
- hammad/genai/models/embeddings/types/__init__.py +0 -37
- hammad/genai/models/embeddings/types/embedding_model_name.py +0 -75
- hammad/genai/models/embeddings/types/embedding_model_response.py +0 -76
- hammad/genai/models/embeddings/types/embedding_model_run_params.py +0 -66
- hammad/genai/models/embeddings/types/embedding_model_settings.py +0 -47
- hammad/genai/models/language/__init__.py +0 -57
- hammad/genai/models/language/model.py +0 -1098
- hammad/genai/models/language/run.py +0 -878
- hammad/genai/models/language/types/__init__.py +0 -40
- hammad/genai/models/language/types/language_model_instructor_mode.py +0 -47
- hammad/genai/models/language/types/language_model_messages.py +0 -28
- hammad/genai/models/language/types/language_model_name.py +0 -239
- hammad/genai/models/language/types/language_model_request.py +0 -127
- hammad/genai/models/language/types/language_model_response.py +0 -217
- hammad/genai/models/language/types/language_model_response_chunk.py +0 -56
- hammad/genai/models/language/types/language_model_settings.py +0 -89
- hammad/genai/models/language/types/language_model_stream.py +0 -600
- hammad/genai/models/language/utils/__init__.py +0 -28
- hammad/genai/models/language/utils/requests.py +0 -421
- hammad/genai/models/language/utils/structured_outputs.py +0 -135
- hammad/genai/models/model_provider.py +0 -4
- hammad/genai/models/multimodal.py +0 -47
- hammad/genai/models/reranking.py +0 -26
- hammad/genai/types/__init__.py +0 -1
- hammad/genai/types/base.py +0 -215
- hammad/genai/types/history.py +0 -290
- hammad/genai/types/tools.py +0 -507
- hammad/logging/__init__.py +0 -35
- hammad/logging/decorators.py +0 -834
- hammad/logging/logger.py +0 -1018
- hammad/mcp/__init__.py +0 -53
- hammad/mcp/client/__init__.py +0 -35
- hammad/mcp/client/client.py +0 -624
- hammad/mcp/client/client_service.py +0 -400
- hammad/mcp/client/settings.py +0 -178
- hammad/mcp/servers/__init__.py +0 -26
- hammad/mcp/servers/launcher.py +0 -1161
- hammad/runtime/__init__.py +0 -32
- hammad/runtime/decorators.py +0 -142
- hammad/runtime/run.py +0 -299
- hammad/service/__init__.py +0 -49
- hammad/service/create.py +0 -527
- hammad/service/decorators.py +0 -283
- hammad/types.py +0 -288
- hammad/typing/__init__.py +0 -435
- 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 -275
- hammad/web/openapi/__init__.py +0 -1
- hammad/web/openapi/client.py +0 -740
- hammad/web/search/__init__.py +0 -1
- hammad/web/search/client.py +0 -1023
- hammad/web/utils.py +0 -472
- hammad_python-0.0.29.dist-info/RECORD +0 -135
- {hammad → ham}/py.typed +0 -0
- {hammad_python-0.0.29.dist-info → hammad_python-0.0.31.dist-info}/WHEEL +0 -0
- {hammad_python-0.0.29.dist-info → hammad_python-0.0.31.dist-info}/licenses/LICENSE +0 -0
hammad/data/models/model.py
DELETED
@@ -1,1078 +0,0 @@
|
|
1
|
-
"""hammad.data.models.model"""
|
2
|
-
|
3
|
-
import copy
|
4
|
-
from functools import lru_cache
|
5
|
-
from typing import (
|
6
|
-
Any,
|
7
|
-
Callable,
|
8
|
-
Dict,
|
9
|
-
List,
|
10
|
-
Literal,
|
11
|
-
Mapping,
|
12
|
-
Optional,
|
13
|
-
Self,
|
14
|
-
Set,
|
15
|
-
Type,
|
16
|
-
Union,
|
17
|
-
)
|
18
|
-
|
19
|
-
import msgspec
|
20
|
-
from msgspec.json import decode, encode, schema
|
21
|
-
from msgspec.structs import Struct, asdict, fields
|
22
|
-
|
23
|
-
__all__ = ("Model", "model_settings")
|
24
|
-
|
25
|
-
|
26
|
-
def model_settings(
|
27
|
-
*,
|
28
|
-
tag: Union[None, bool, str, int, Callable[[str], Union[str, int]]] = None,
|
29
|
-
tag_field: Union[None, str] = None,
|
30
|
-
rename: Union[
|
31
|
-
None,
|
32
|
-
Literal["lower", "upper", "camel", "pascal", "kebab"],
|
33
|
-
Callable[[str], Optional[str]],
|
34
|
-
Mapping[str, str],
|
35
|
-
] = None,
|
36
|
-
omit_defaults: bool = False,
|
37
|
-
forbid_unknown_fields: bool = False,
|
38
|
-
frozen: bool = False,
|
39
|
-
eq: bool = True,
|
40
|
-
order: bool = False,
|
41
|
-
kw_only: bool = False,
|
42
|
-
repr_omit_defaults: bool = False,
|
43
|
-
array_like: bool = False,
|
44
|
-
gc: bool = True,
|
45
|
-
weakref: bool = False,
|
46
|
-
dict: bool = False,
|
47
|
-
cache_hash: bool = False,
|
48
|
-
) -> Callable[[Type], Type]:
|
49
|
-
"""Decorator to configure msgspec Struct parameters for Model classes.
|
50
|
-
|
51
|
-
This decorator allows you to configure all msgspec Struct parameters
|
52
|
-
while preserving type safety and IDE completion.
|
53
|
-
|
54
|
-
Args:
|
55
|
-
tag: Tag configuration for the struct
|
56
|
-
tag_field: Field to use for tagging
|
57
|
-
rename: Field renaming strategy
|
58
|
-
omit_defaults: Whether to omit default values in serialization
|
59
|
-
forbid_unknown_fields: Whether to forbid unknown fields during deserialization
|
60
|
-
frozen: Whether the struct should be immutable
|
61
|
-
eq: Whether to generate __eq__ method
|
62
|
-
order: Whether to generate ordering methods
|
63
|
-
kw_only: Whether fields should be keyword-only
|
64
|
-
repr_omit_defaults: Whether to omit defaults in repr
|
65
|
-
array_like: Whether to treat the struct as array-like
|
66
|
-
gc: Whether to enable garbage collection
|
67
|
-
weakref: Whether to enable weak references
|
68
|
-
dict: Whether to enable dict-like access
|
69
|
-
cache_hash: Whether to cache hash values
|
70
|
-
|
71
|
-
Returns:
|
72
|
-
Class decorator that configures the Model class
|
73
|
-
|
74
|
-
Example:
|
75
|
-
@model_settings(frozen=True, kw_only=True)
|
76
|
-
class User(Model):
|
77
|
-
name: str
|
78
|
-
age: int = 0
|
79
|
-
"""
|
80
|
-
|
81
|
-
def decorator(cls: Type) -> Type:
|
82
|
-
# Store the configuration parameters
|
83
|
-
config_kwargs = {
|
84
|
-
"tag": tag,
|
85
|
-
"tag_field": tag_field,
|
86
|
-
"rename": rename,
|
87
|
-
"omit_defaults": omit_defaults,
|
88
|
-
"forbid_unknown_fields": forbid_unknown_fields,
|
89
|
-
"frozen": frozen,
|
90
|
-
"eq": eq,
|
91
|
-
"order": order,
|
92
|
-
"kw_only": kw_only,
|
93
|
-
"repr_omit_defaults": repr_omit_defaults,
|
94
|
-
"array_like": array_like,
|
95
|
-
"gc": gc,
|
96
|
-
"weakref": weakref,
|
97
|
-
"dict": dict,
|
98
|
-
"cache_hash": cache_hash,
|
99
|
-
}
|
100
|
-
|
101
|
-
# Filter out None values to avoid passing them to __init_subclass__
|
102
|
-
filtered_kwargs = {k: v for k, v in config_kwargs.items() if v is not None}
|
103
|
-
|
104
|
-
# Create a new class with the same name and bases but with the configuration
|
105
|
-
class ConfiguredModel(cls):
|
106
|
-
def __init_subclass__(cls, **kwargs):
|
107
|
-
# Merge the decorator kwargs with any kwargs passed to __init_subclass__
|
108
|
-
merged_kwargs = {**filtered_kwargs, **kwargs}
|
109
|
-
super().__init_subclass__(**merged_kwargs)
|
110
|
-
|
111
|
-
# Preserve the original class name and module
|
112
|
-
ConfiguredModel.__name__ = cls.__name__
|
113
|
-
ConfiguredModel.__qualname__ = cls.__qualname__
|
114
|
-
ConfiguredModel.__module__ = cls.__module__
|
115
|
-
|
116
|
-
# Apply the configuration by calling __init_subclass__ manually
|
117
|
-
ConfiguredModel.__init_subclass__(**filtered_kwargs)
|
118
|
-
|
119
|
-
return ConfiguredModel
|
120
|
-
|
121
|
-
return decorator
|
122
|
-
|
123
|
-
|
124
|
-
def _get_field_schema(field) -> dict[str, Any]:
|
125
|
-
"""Helper method to generate schema for a single field."""
|
126
|
-
field_type = field.type
|
127
|
-
|
128
|
-
# Handle basic types
|
129
|
-
if field_type == str:
|
130
|
-
return {"type": "string"}
|
131
|
-
elif field_type == int:
|
132
|
-
return {"type": "integer"}
|
133
|
-
elif field_type == float:
|
134
|
-
return {"type": "number"}
|
135
|
-
elif field_type == bool:
|
136
|
-
return {"type": "boolean"}
|
137
|
-
elif field_type == list:
|
138
|
-
return {"type": "array"}
|
139
|
-
elif field_type == dict:
|
140
|
-
return {"type": "object"}
|
141
|
-
|
142
|
-
# Handle Optional types (Union with None)
|
143
|
-
if hasattr(field_type, "__origin__") and field_type.__origin__ is Union:
|
144
|
-
args = field_type.__args__
|
145
|
-
if len(args) == 2 and type(None) in args:
|
146
|
-
# This is Optional[T]
|
147
|
-
non_none_type = args[0] if args[1] is type(None) else args[1]
|
148
|
-
base_schema = _get_type_schema(non_none_type)
|
149
|
-
base_schema["nullable"] = True
|
150
|
-
return base_schema
|
151
|
-
|
152
|
-
# Handle generic types
|
153
|
-
if hasattr(field_type, "__origin__"):
|
154
|
-
origin = field_type.__origin__
|
155
|
-
if origin is list:
|
156
|
-
args = getattr(field_type, "__args__", ())
|
157
|
-
if args:
|
158
|
-
return {"type": "array", "items": _get_type_schema(args[0])}
|
159
|
-
return {"type": "array"}
|
160
|
-
elif origin is dict:
|
161
|
-
return {"type": "object"}
|
162
|
-
elif origin is set:
|
163
|
-
args = getattr(field_type, "__args__", ())
|
164
|
-
if args:
|
165
|
-
return {
|
166
|
-
"type": "array",
|
167
|
-
"items": _get_type_schema(args[0]),
|
168
|
-
"uniqueItems": True,
|
169
|
-
}
|
170
|
-
return {"type": "array", "uniqueItems": True}
|
171
|
-
|
172
|
-
# Default fallback
|
173
|
-
return {"type": "object"}
|
174
|
-
|
175
|
-
|
176
|
-
def _get_type_schema(type_hint) -> dict[str, Any]:
|
177
|
-
"""Helper method to get schema for a type hint."""
|
178
|
-
if type_hint == str:
|
179
|
-
return {"type": "string"}
|
180
|
-
elif type_hint == int:
|
181
|
-
return {"type": "integer"}
|
182
|
-
elif type_hint == float:
|
183
|
-
return {"type": "number"}
|
184
|
-
elif type_hint == bool:
|
185
|
-
return {"type": "boolean"}
|
186
|
-
elif type_hint == list:
|
187
|
-
return {"type": "array"}
|
188
|
-
elif type_hint == dict:
|
189
|
-
return {"type": "object"}
|
190
|
-
elif type_hint == set:
|
191
|
-
return {"type": "array", "uniqueItems": True}
|
192
|
-
else:
|
193
|
-
return {"type": "object"}
|
194
|
-
|
195
|
-
|
196
|
-
class Model(Struct):
|
197
|
-
"""Based, as defined by Lil B is:
|
198
|
-
|
199
|
-
```markdown
|
200
|
-
"Based means being yourself.
|
201
|
-
Not being scared of what people think about you.
|
202
|
-
Not being afraid to do what you wanna do."
|
203
|
-
```
|
204
|
-
|
205
|
-
NOTE: This model does support the dictionary interface, but
|
206
|
-
does not support the `keys`, `values`, `items`, and `get`
|
207
|
-
methods to allow for usage of those fields as key names.
|
208
|
-
|
209
|
-
These wise words define this model. A `Model` is
|
210
|
-
interactable both through the dictionary interface, and
|
211
|
-
dot notation, and utilizes `msgspec` to provide an interface
|
212
|
-
identical to a `pydantic.BaseModel`, with the benefit of
|
213
|
-
`msgspec's` superior performance (5-60x faster for common operations).
|
214
|
-
"""
|
215
|
-
|
216
|
-
def __init_subclass__(cls, **kwargs):
|
217
|
-
"""Called when a class is subclassed to set up proper typing."""
|
218
|
-
super().__init_subclass__(**kwargs)
|
219
|
-
|
220
|
-
# Create dynamic properties for field access to help with IDE completion
|
221
|
-
try:
|
222
|
-
struct_fields = fields(cls)
|
223
|
-
field_names = [f.name for f in struct_fields]
|
224
|
-
|
225
|
-
# Store field names for IDE completion
|
226
|
-
cls._field_names_literal = field_names
|
227
|
-
|
228
|
-
# Create properties for each field to aid IDE completion
|
229
|
-
for field_name in field_names:
|
230
|
-
if not hasattr(cls, f"_get_{field_name}"):
|
231
|
-
|
232
|
-
def make_getter(fname):
|
233
|
-
def getter(self):
|
234
|
-
return getattr(self, fname)
|
235
|
-
|
236
|
-
return getter
|
237
|
-
|
238
|
-
setattr(cls, f"_get_{field_name}", make_getter(field_name))
|
239
|
-
except Exception:
|
240
|
-
# If fields() fails, fallback gracefully
|
241
|
-
pass
|
242
|
-
|
243
|
-
# Remove complex metadata handling - let msgspec handle fields natively
|
244
|
-
|
245
|
-
@classmethod
|
246
|
-
@lru_cache(maxsize=None)
|
247
|
-
def _get_field_names(cls) -> tuple[str, ...]:
|
248
|
-
"""Get all field names as a tuple for type hints."""
|
249
|
-
struct_fields = fields(cls)
|
250
|
-
return tuple(f.name for f in struct_fields)
|
251
|
-
|
252
|
-
@classmethod
|
253
|
-
@lru_cache(maxsize=None)
|
254
|
-
def _get_fields_info(cls) -> Dict[str, Any]:
|
255
|
-
"""Cached method to get field information."""
|
256
|
-
struct_fields = fields(cls)
|
257
|
-
result = {}
|
258
|
-
|
259
|
-
for f in struct_fields:
|
260
|
-
field_info = {
|
261
|
-
"field": f,
|
262
|
-
"type": f.type,
|
263
|
-
"required": f.required,
|
264
|
-
"default": f.default if not f.required else None,
|
265
|
-
}
|
266
|
-
|
267
|
-
result[f.name] = field_info
|
268
|
-
|
269
|
-
return result
|
270
|
-
|
271
|
-
@classmethod
|
272
|
-
def model_json_schema(cls) -> dict[str, Any]:
|
273
|
-
"""Returns the json schema for the object.
|
274
|
-
|
275
|
-
Uses msgspec's native schema generation when possible for better performance.
|
276
|
-
"""
|
277
|
-
try:
|
278
|
-
# Try to use msgspec's native schema generation first
|
279
|
-
return schema(cls)
|
280
|
-
except Exception:
|
281
|
-
# Fallback to manual schema generation
|
282
|
-
schema_dict = {
|
283
|
-
"type": "object",
|
284
|
-
"properties": {},
|
285
|
-
"required": [],
|
286
|
-
"title": cls.__name__,
|
287
|
-
}
|
288
|
-
|
289
|
-
if cls.__doc__:
|
290
|
-
schema_dict["description"] = cls.__doc__.strip()
|
291
|
-
|
292
|
-
# Get field information from the struct
|
293
|
-
fields_info = cls._get_fields_info()
|
294
|
-
|
295
|
-
for field_name, field_info in fields_info.items():
|
296
|
-
field = field_info["field"]
|
297
|
-
field_schema = _get_field_schema(field)
|
298
|
-
schema_dict["properties"][field_name] = field_schema
|
299
|
-
|
300
|
-
# Add to required if field has no default
|
301
|
-
if field.required:
|
302
|
-
schema_dict["required"].append(field_name)
|
303
|
-
|
304
|
-
# Remove empty required array if no required fields
|
305
|
-
if not schema_dict["required"]:
|
306
|
-
del schema_dict["required"]
|
307
|
-
|
308
|
-
return schema_dict
|
309
|
-
|
310
|
-
def model_dump(
|
311
|
-
self,
|
312
|
-
*,
|
313
|
-
mode: Literal["json", "python"] = "python",
|
314
|
-
include: Optional[Union[Set[str], Set[int]]] = None,
|
315
|
-
exclude: Optional[Union[Set[str], Set[int]]] = None,
|
316
|
-
exclude_none: bool = False,
|
317
|
-
exclude_defaults: bool = False,
|
318
|
-
exclude_unset: bool = False,
|
319
|
-
) -> Any:
|
320
|
-
"""Dumps the object into a dictionary, or a json string.
|
321
|
-
|
322
|
-
Note: exclude_unset is included for compatibility but has no effect
|
323
|
-
as msgspec doesn't track unset state.
|
324
|
-
"""
|
325
|
-
# Convert struct to dictionary using msgspec's optimized asdict
|
326
|
-
data = asdict(self)
|
327
|
-
|
328
|
-
# Handle include/exclude filtering
|
329
|
-
if include is not None:
|
330
|
-
if isinstance(include, set) and all(isinstance(k, str) for k in include):
|
331
|
-
data = {k: v for k, v in data.items() if k in include}
|
332
|
-
elif isinstance(include, set) and all(isinstance(k, int) for k in include):
|
333
|
-
# For integer indices, convert to list of items and filter by index
|
334
|
-
items = list(data.items())
|
335
|
-
data = dict(items[i] for i in include if 0 <= i < len(items))
|
336
|
-
|
337
|
-
if exclude is not None:
|
338
|
-
if isinstance(exclude, set) and all(isinstance(k, str) for k in exclude):
|
339
|
-
data = {k: v for k, v in data.items() if k not in exclude}
|
340
|
-
elif isinstance(exclude, set) and all(isinstance(k, int) for k in exclude):
|
341
|
-
# For integer indices, convert to list and exclude by index
|
342
|
-
items = list(data.items())
|
343
|
-
data = dict(items[i] for i in range(len(items)) if i not in exclude)
|
344
|
-
|
345
|
-
# Handle None exclusion
|
346
|
-
if exclude_none:
|
347
|
-
data = {k: v for k, v in data.items() if v is not None}
|
348
|
-
|
349
|
-
# Handle default exclusion
|
350
|
-
if exclude_defaults:
|
351
|
-
fields_info = self._get_fields_info()
|
352
|
-
data = {
|
353
|
-
k: v
|
354
|
-
for k, v in data.items()
|
355
|
-
if k not in fields_info
|
356
|
-
or fields_info[k]["required"]
|
357
|
-
or v != fields_info[k]["default"]
|
358
|
-
}
|
359
|
-
|
360
|
-
# Return based on mode
|
361
|
-
if mode == "python":
|
362
|
-
return data
|
363
|
-
elif mode == "json":
|
364
|
-
return encode(data).decode("utf-8")
|
365
|
-
else:
|
366
|
-
raise ValueError(f"Invalid mode: {mode}. Must be 'json' or 'python'")
|
367
|
-
|
368
|
-
def model_dump_json(
|
369
|
-
self,
|
370
|
-
*,
|
371
|
-
indent: Optional[int] = None,
|
372
|
-
include: Optional[Union[Set[str], Set[int]]] = None,
|
373
|
-
exclude: Optional[Union[Set[str], Set[int]]] = None,
|
374
|
-
exclude_none: bool = False,
|
375
|
-
exclude_defaults: bool = False,
|
376
|
-
) -> str:
|
377
|
-
"""Generate a JSON representation of the model."""
|
378
|
-
data = self.model_dump(
|
379
|
-
mode="python",
|
380
|
-
include=include,
|
381
|
-
exclude=exclude,
|
382
|
-
exclude_none=exclude_none,
|
383
|
-
exclude_defaults=exclude_defaults,
|
384
|
-
)
|
385
|
-
# msgspec's encode is faster than json.dumps
|
386
|
-
return encode(data).decode("utf-8")
|
387
|
-
|
388
|
-
def model_copy(
|
389
|
-
self,
|
390
|
-
*,
|
391
|
-
update: Optional[Dict[str, Any]] = None,
|
392
|
-
deep: bool = False,
|
393
|
-
exclude: Optional[Union[Set[str], Set[int]]] = None,
|
394
|
-
) -> Self:
|
395
|
-
"""Create a copy of the struct, optionally updating fields."""
|
396
|
-
if update is None:
|
397
|
-
update = {}
|
398
|
-
|
399
|
-
# Get current data as dict using msgspec's optimized asdict
|
400
|
-
current_data = asdict(self)
|
401
|
-
|
402
|
-
# Handle exclude filtering
|
403
|
-
if exclude is not None:
|
404
|
-
if isinstance(exclude, set) and all(isinstance(k, str) for k in exclude):
|
405
|
-
current_data = {
|
406
|
-
k: v for k, v in current_data.items() if k not in exclude
|
407
|
-
}
|
408
|
-
elif isinstance(exclude, set) and all(isinstance(k, int) for k in exclude):
|
409
|
-
items = list(current_data.items())
|
410
|
-
current_data = dict(
|
411
|
-
items[i] for i in range(len(items)) if i not in exclude
|
412
|
-
)
|
413
|
-
|
414
|
-
# Update with new values
|
415
|
-
current_data.update(update)
|
416
|
-
|
417
|
-
# Create new instance
|
418
|
-
new_instance = self.__class__(**current_data)
|
419
|
-
|
420
|
-
if deep:
|
421
|
-
# For deep copy, we need to recursively copy nested structures
|
422
|
-
return copy.deepcopy(new_instance)
|
423
|
-
|
424
|
-
return new_instance
|
425
|
-
|
426
|
-
@classmethod
|
427
|
-
def model_validate(cls, obj: Any) -> Self:
|
428
|
-
"""Validate and create an instance from various input types."""
|
429
|
-
if isinstance(obj, cls):
|
430
|
-
return obj
|
431
|
-
elif isinstance(obj, dict):
|
432
|
-
return cls(**obj)
|
433
|
-
elif hasattr(obj, "__dict__"):
|
434
|
-
return cls(**obj.__dict__)
|
435
|
-
else:
|
436
|
-
# Try to decode if it's a string/bytes
|
437
|
-
try:
|
438
|
-
if isinstance(obj, (str, bytes)):
|
439
|
-
decoded = decode(obj, type=cls)
|
440
|
-
return decoded
|
441
|
-
except Exception:
|
442
|
-
pass
|
443
|
-
raise ValueError(f"Cannot validate {type(obj)} as {cls.__name__}")
|
444
|
-
|
445
|
-
@classmethod
|
446
|
-
def model_validate_json(cls, json_data: Union[str, bytes]) -> Self:
|
447
|
-
"""Create an instance from JSON string or bytes.
|
448
|
-
|
449
|
-
Uses msgspec's optimized JSON decoder.
|
450
|
-
"""
|
451
|
-
return decode(json_data, type=cls)
|
452
|
-
|
453
|
-
@classmethod
|
454
|
-
def model_fields(cls) -> Dict[str, Any]:
|
455
|
-
"""Get information about the struct's fields."""
|
456
|
-
return cls._get_fields_info()
|
457
|
-
|
458
|
-
@classmethod
|
459
|
-
def model_load_from_model(
|
460
|
-
cls,
|
461
|
-
model: Any,
|
462
|
-
title: Optional[str] = None,
|
463
|
-
description: Optional[str] = None,
|
464
|
-
init: bool = False,
|
465
|
-
exclude: Optional[Union[Set[str], Set[int]]] = None,
|
466
|
-
) -> Self:
|
467
|
-
"""Load a model from another model.
|
468
|
-
|
469
|
-
Args:
|
470
|
-
model : The model to load from
|
471
|
-
title : An optional title or title override for the model
|
472
|
-
description : An optional description for the model
|
473
|
-
init : Whether to initialize the model with the field value
|
474
|
-
exclude : Fields to exclude from the conversion
|
475
|
-
"""
|
476
|
-
# Extract data from the source model
|
477
|
-
if hasattr(model, "model_dump"):
|
478
|
-
# It's a pydantic-like model
|
479
|
-
source_data = model.model_dump()
|
480
|
-
elif hasattr(model, "__dict__"):
|
481
|
-
# It's a regular object with attributes
|
482
|
-
source_data = model.__dict__.copy()
|
483
|
-
elif isinstance(model, dict):
|
484
|
-
# It's already a dictionary
|
485
|
-
source_data = model.copy()
|
486
|
-
elif hasattr(model, "_asdict"):
|
487
|
-
# It's a namedtuple
|
488
|
-
source_data = model._asdict()
|
489
|
-
else:
|
490
|
-
# Try to use msgspec's asdict for msgspec structs
|
491
|
-
try:
|
492
|
-
source_data = asdict(model)
|
493
|
-
except Exception:
|
494
|
-
# Last resort - try to convert to dict
|
495
|
-
try:
|
496
|
-
source_data = dict(model)
|
497
|
-
except Exception:
|
498
|
-
raise ValueError(
|
499
|
-
f"Cannot extract data from model of type {type(model)}"
|
500
|
-
)
|
501
|
-
|
502
|
-
# Apply exclusions if specified
|
503
|
-
if exclude is not None:
|
504
|
-
if isinstance(exclude, set) and all(isinstance(k, str) for k in exclude):
|
505
|
-
source_data = {k: v for k, v in source_data.items() if k not in exclude}
|
506
|
-
elif isinstance(exclude, set) and all(isinstance(k, int) for k in exclude):
|
507
|
-
items = list(source_data.items())
|
508
|
-
source_data = dict(
|
509
|
-
items[i] for i in range(len(items)) if i not in exclude
|
510
|
-
)
|
511
|
-
|
512
|
-
# Get the fields of the target class to filter compatible fields
|
513
|
-
try:
|
514
|
-
target_fields = cls._get_field_names()
|
515
|
-
# Only include fields that exist in the target model
|
516
|
-
filtered_data = {k: v for k, v in source_data.items() if k in target_fields}
|
517
|
-
except Exception:
|
518
|
-
# If we can't get field names, use all data
|
519
|
-
filtered_data = source_data
|
520
|
-
|
521
|
-
if init:
|
522
|
-
# Create and return an instance
|
523
|
-
return cls(**filtered_data)
|
524
|
-
else:
|
525
|
-
# Return the class type - this doesn't make much sense for the method signature
|
526
|
-
# but following the parameter description, we'll return an uninitialized version
|
527
|
-
# In practice, this would typically return the class itself or raise an error
|
528
|
-
# For now, let's create an instance anyway since that's most useful
|
529
|
-
return cls(**filtered_data)
|
530
|
-
|
531
|
-
@classmethod
|
532
|
-
def model_field_to_model(
|
533
|
-
cls,
|
534
|
-
fields: str,
|
535
|
-
schema: Literal[
|
536
|
-
"base",
|
537
|
-
"dataclass",
|
538
|
-
"pydantic",
|
539
|
-
"msgspec",
|
540
|
-
"typeddict",
|
541
|
-
"namedtuple",
|
542
|
-
"attrs",
|
543
|
-
"dict",
|
544
|
-
] = "base",
|
545
|
-
# Simple Override Params To Edit The Final Model
|
546
|
-
# This method always goes field(s) -> model not to field
|
547
|
-
title: Optional[str] = None,
|
548
|
-
description: Optional[str] = None,
|
549
|
-
field_name: str = "value",
|
550
|
-
field_description: Optional[str] = None,
|
551
|
-
field_examples: Optional[List[Any]] = None,
|
552
|
-
init: bool = False,
|
553
|
-
) -> Any:
|
554
|
-
"""Convert a single field to a new model of any
|
555
|
-
type.
|
556
|
-
|
557
|
-
Args:
|
558
|
-
fields: The field to be converted into the model
|
559
|
-
schema: The target schema format to convert to (Defaults to a Model)
|
560
|
-
title : An optional title or title override for the model (uses the field name if not provided)
|
561
|
-
description : An optional description for the model (uses the field description if not provided)
|
562
|
-
field_name : The name of the field within this new model representing the target field (defaults to "value")
|
563
|
-
field_description : An optional description for the field within this new model (defaults to None)
|
564
|
-
field_examples : An optional list of examples for the field within this new model (defaults to None)
|
565
|
-
init : Whether to initialize the model with the field value (defaults to False)
|
566
|
-
"""
|
567
|
-
# Get field information from the class
|
568
|
-
fields_info = cls._get_fields_info()
|
569
|
-
|
570
|
-
if fields not in fields_info:
|
571
|
-
raise ValueError(f"Field '{fields}' not found in {cls.__name__}")
|
572
|
-
|
573
|
-
field_info = fields_info[fields]
|
574
|
-
field_type = field_info["type"]
|
575
|
-
|
576
|
-
# Handle default values properly, including default_factory
|
577
|
-
if not field_info["required"]:
|
578
|
-
field_default = field_info["default"]
|
579
|
-
# Check for default_factory in the msgspec field
|
580
|
-
msgspec_field = field_info["field"]
|
581
|
-
if (
|
582
|
-
hasattr(msgspec_field, "default_factory")
|
583
|
-
and msgspec_field.default_factory is not msgspec.UNSET
|
584
|
-
and msgspec_field.default_factory is not msgspec.NODEFAULT
|
585
|
-
):
|
586
|
-
# It has a default_factory, call it to get the actual default
|
587
|
-
try:
|
588
|
-
field_default = msgspec_field.default_factory()
|
589
|
-
except Exception:
|
590
|
-
# If calling fails, use UNSET
|
591
|
-
field_default = msgspec.UNSET
|
592
|
-
# If field_default is NODEFAULT but no default_factory, keep as UNSET
|
593
|
-
elif field_default is msgspec.NODEFAULT:
|
594
|
-
field_default = msgspec.UNSET
|
595
|
-
else:
|
596
|
-
field_default = msgspec.UNSET
|
597
|
-
|
598
|
-
# Use provided title or default to field name
|
599
|
-
model_title = title or fields.title()
|
600
|
-
model_description = description or f"Model wrapping field '{fields}'"
|
601
|
-
|
602
|
-
if schema == "base":
|
603
|
-
from .fields import field
|
604
|
-
|
605
|
-
# Create annotations for the dynamic class
|
606
|
-
annotations = {field_name: field_type}
|
607
|
-
|
608
|
-
# Create field definition
|
609
|
-
class_dict = {"__annotations__": annotations}
|
610
|
-
|
611
|
-
# Add default if available
|
612
|
-
if field_default is not msgspec.UNSET:
|
613
|
-
class_dict[field_name] = field(
|
614
|
-
default=field_default,
|
615
|
-
description=field_description,
|
616
|
-
examples=field_examples,
|
617
|
-
)
|
618
|
-
elif field_description or field_examples:
|
619
|
-
class_dict[field_name] = field(
|
620
|
-
description=field_description, examples=field_examples
|
621
|
-
)
|
622
|
-
|
623
|
-
# Create the dynamic class
|
624
|
-
DynamicModel = type(model_title.replace(" ", ""), (Model,), class_dict)
|
625
|
-
|
626
|
-
if init and field_default is not msgspec.UNSET:
|
627
|
-
return DynamicModel(**{field_name: field_default})
|
628
|
-
elif init:
|
629
|
-
# Need a value to initialize with
|
630
|
-
raise ValueError("Cannot initialize model without a default value")
|
631
|
-
else:
|
632
|
-
return DynamicModel
|
633
|
-
|
634
|
-
elif schema == "dataclass":
|
635
|
-
from dataclasses import make_dataclass, field as dc_field
|
636
|
-
|
637
|
-
if field_default is not msgspec.UNSET:
|
638
|
-
fields_list = [
|
639
|
-
(field_name, field_type, dc_field(default=field_default))
|
640
|
-
]
|
641
|
-
else:
|
642
|
-
fields_list = [(field_name, field_type)]
|
643
|
-
|
644
|
-
DynamicDataclass = make_dataclass(model_title.replace(" ", ""), fields_list)
|
645
|
-
|
646
|
-
if init and field_default is not msgspec.UNSET:
|
647
|
-
return DynamicDataclass(**{field_name: field_default})
|
648
|
-
elif init:
|
649
|
-
raise ValueError("Cannot initialize dataclass without a default value")
|
650
|
-
else:
|
651
|
-
return DynamicDataclass
|
652
|
-
|
653
|
-
elif schema == "pydantic":
|
654
|
-
from pydantic import BaseModel, create_model
|
655
|
-
|
656
|
-
pydantic_fields = {}
|
657
|
-
if field_default is not msgspec.UNSET:
|
658
|
-
pydantic_fields[field_name] = (field_type, field_default)
|
659
|
-
else:
|
660
|
-
pydantic_fields[field_name] = (field_type, ...)
|
661
|
-
|
662
|
-
PydanticModel = create_model(
|
663
|
-
model_title.replace(" ", ""), **pydantic_fields
|
664
|
-
)
|
665
|
-
|
666
|
-
if init and field_default is not msgspec.UNSET:
|
667
|
-
return PydanticModel(**{field_name: field_default})
|
668
|
-
elif init:
|
669
|
-
raise ValueError(
|
670
|
-
"Cannot initialize pydantic model without a default value"
|
671
|
-
)
|
672
|
-
else:
|
673
|
-
return PydanticModel
|
674
|
-
|
675
|
-
elif schema == "msgspec":
|
676
|
-
# Create a msgspec Struct dynamically
|
677
|
-
struct_fields = {field_name: field_type}
|
678
|
-
if field_default is not msgspec.UNSET:
|
679
|
-
struct_fields[field_name] = msgspec_field(default=field_default)
|
680
|
-
|
681
|
-
DynamicStruct = type(
|
682
|
-
model_title.replace(" ", ""),
|
683
|
-
(Struct,),
|
684
|
-
{"__annotations__": {field_name: field_type}},
|
685
|
-
)
|
686
|
-
|
687
|
-
if init and field_default is not msgspec.UNSET:
|
688
|
-
return DynamicStruct(**{field_name: field_default})
|
689
|
-
elif init:
|
690
|
-
raise ValueError(
|
691
|
-
"Cannot initialize msgspec struct without a default value"
|
692
|
-
)
|
693
|
-
else:
|
694
|
-
return DynamicStruct
|
695
|
-
|
696
|
-
elif schema == "typeddict":
|
697
|
-
from typing import TypedDict
|
698
|
-
|
699
|
-
# TypedDict can't be created dynamically in the same way
|
700
|
-
# Return a dictionary with type information
|
701
|
-
if init and field_default is not msgspec.UNSET:
|
702
|
-
return {field_name: field_default}
|
703
|
-
elif init:
|
704
|
-
raise ValueError("Cannot initialize TypedDict without a default value")
|
705
|
-
else:
|
706
|
-
# Return a TypedDict class (though this is limited)
|
707
|
-
return TypedDict(model_title.replace(" ", ""), {field_name: field_type})
|
708
|
-
|
709
|
-
elif schema == "namedtuple":
|
710
|
-
from collections import namedtuple
|
711
|
-
|
712
|
-
DynamicNamedTuple = namedtuple(model_title.replace(" ", ""), [field_name])
|
713
|
-
|
714
|
-
if init and field_default is not msgspec.UNSET:
|
715
|
-
return DynamicNamedTuple(**{field_name: field_default})
|
716
|
-
elif init:
|
717
|
-
raise ValueError("Cannot initialize namedtuple without a default value")
|
718
|
-
else:
|
719
|
-
return DynamicNamedTuple
|
720
|
-
|
721
|
-
elif schema == "attrs":
|
722
|
-
try:
|
723
|
-
import attrs
|
724
|
-
|
725
|
-
if field_default is not msgspec.UNSET:
|
726
|
-
field_attr = attrs.field(default=field_default)
|
727
|
-
else:
|
728
|
-
field_attr = attrs.field()
|
729
|
-
|
730
|
-
@attrs.define
|
731
|
-
class DynamicAttrs:
|
732
|
-
pass
|
733
|
-
|
734
|
-
# Set the field dynamically
|
735
|
-
setattr(DynamicAttrs, field_name, field_attr)
|
736
|
-
DynamicAttrs.__annotations__ = {field_name: field_type}
|
737
|
-
|
738
|
-
if init and field_default is not msgspec.UNSET:
|
739
|
-
return DynamicAttrs(**{field_name: field_default})
|
740
|
-
elif init:
|
741
|
-
raise ValueError(
|
742
|
-
"Cannot initialize attrs class without a default value"
|
743
|
-
)
|
744
|
-
else:
|
745
|
-
return DynamicAttrs
|
746
|
-
|
747
|
-
except ImportError:
|
748
|
-
raise ImportError("attrs library is required for attrs conversion")
|
749
|
-
|
750
|
-
elif schema == "dict":
|
751
|
-
if init and field_default is not msgspec.UNSET:
|
752
|
-
return {field_name: field_default}
|
753
|
-
elif init:
|
754
|
-
raise ValueError("Cannot initialize dict without a default value")
|
755
|
-
else:
|
756
|
-
return {field_name: field_type}
|
757
|
-
|
758
|
-
else:
|
759
|
-
raise ValueError(f"Unsupported schema format: {schema}")
|
760
|
-
|
761
|
-
def model_convert(
|
762
|
-
self,
|
763
|
-
schema: Literal[
|
764
|
-
"dataclass",
|
765
|
-
"pydantic",
|
766
|
-
"msgspec",
|
767
|
-
"typeddict",
|
768
|
-
"namedtuple",
|
769
|
-
"attrs",
|
770
|
-
"dict",
|
771
|
-
],
|
772
|
-
exclude: Optional[Union[Set[str], Set[int]]] = None,
|
773
|
-
) -> Any:
|
774
|
-
"""Convert the model to different schema formats using adaptix.
|
775
|
-
|
776
|
-
Args:
|
777
|
-
schema: The target schema format to convert to
|
778
|
-
exclude: Fields to exclude from the conversion
|
779
|
-
|
780
|
-
Returns:
|
781
|
-
The converted model in the specified format
|
782
|
-
"""
|
783
|
-
# Get current model data
|
784
|
-
current_data = asdict(self)
|
785
|
-
|
786
|
-
# Apply exclusions if specified
|
787
|
-
if exclude is not None:
|
788
|
-
if isinstance(exclude, set) and all(isinstance(k, str) for k in exclude):
|
789
|
-
current_data = {
|
790
|
-
k: v for k, v in current_data.items() if k not in exclude
|
791
|
-
}
|
792
|
-
elif isinstance(exclude, set) and all(isinstance(k, int) for k in exclude):
|
793
|
-
items = list(current_data.items())
|
794
|
-
current_data = dict(
|
795
|
-
items[i] for i in range(len(items)) if i not in exclude
|
796
|
-
)
|
797
|
-
|
798
|
-
if schema == "dataclass":
|
799
|
-
# Create a dynamic dataclass using make_dataclass
|
800
|
-
from dataclasses import make_dataclass, field
|
801
|
-
|
802
|
-
field_info = self._get_fields_info()
|
803
|
-
fields_list = []
|
804
|
-
|
805
|
-
for field_name, info in field_info.items():
|
806
|
-
if field_name not in current_data:
|
807
|
-
continue
|
808
|
-
field_type = info["type"]
|
809
|
-
if info["required"]:
|
810
|
-
fields_list.append((field_name, field_type))
|
811
|
-
else:
|
812
|
-
fields_list.append(
|
813
|
-
(field_name, field_type, field(default=info["default"]))
|
814
|
-
)
|
815
|
-
|
816
|
-
DynamicDataclass = make_dataclass(
|
817
|
-
f"Dynamic{self.__class__.__name__}", fields_list
|
818
|
-
)
|
819
|
-
|
820
|
-
return DynamicDataclass(**current_data)
|
821
|
-
|
822
|
-
elif schema == "pydantic":
|
823
|
-
from pydantic import BaseModel, create_model
|
824
|
-
|
825
|
-
field_info = self._get_fields_info()
|
826
|
-
pydantic_fields = {}
|
827
|
-
|
828
|
-
for field_name, info in field_info.items():
|
829
|
-
if field_name not in current_data:
|
830
|
-
continue
|
831
|
-
field_type = info["type"]
|
832
|
-
if info["required"]:
|
833
|
-
pydantic_fields[field_name] = (field_type, ...)
|
834
|
-
else:
|
835
|
-
pydantic_fields[field_name] = (field_type, info["default"])
|
836
|
-
|
837
|
-
PydanticModel = create_model(
|
838
|
-
f"Pydantic{self.__class__.__name__}", **pydantic_fields
|
839
|
-
)
|
840
|
-
return PydanticModel(**current_data)
|
841
|
-
|
842
|
-
elif schema == "msgspec":
|
843
|
-
# Return as msgspec Struct (already is one)
|
844
|
-
return self.__class__(**current_data)
|
845
|
-
|
846
|
-
elif schema == "typeddict":
|
847
|
-
# TypedDict doesn't have constructor, just return the dict with type info
|
848
|
-
return current_data
|
849
|
-
|
850
|
-
elif schema == "namedtuple":
|
851
|
-
from collections import namedtuple
|
852
|
-
|
853
|
-
field_names = list(current_data.keys())
|
854
|
-
DynamicNamedTuple = namedtuple(
|
855
|
-
f"Dynamic{self.__class__.__name__}", field_names
|
856
|
-
)
|
857
|
-
return DynamicNamedTuple(**current_data)
|
858
|
-
|
859
|
-
elif schema == "attrs":
|
860
|
-
try:
|
861
|
-
import attrs
|
862
|
-
|
863
|
-
field_info = self._get_fields_info()
|
864
|
-
attrs_fields = []
|
865
|
-
|
866
|
-
for field_name, info in field_info.items():
|
867
|
-
if field_name not in current_data:
|
868
|
-
continue
|
869
|
-
if info["required"]:
|
870
|
-
attrs_fields.append(attrs.field())
|
871
|
-
else:
|
872
|
-
attrs_fields.append(attrs.field(default=info["default"]))
|
873
|
-
|
874
|
-
@attrs.define
|
875
|
-
class DynamicAttrs:
|
876
|
-
pass
|
877
|
-
|
878
|
-
# Set fields dynamically
|
879
|
-
for i, field_name in enumerate(current_data.keys()):
|
880
|
-
setattr(DynamicAttrs, field_name, attrs_fields[i])
|
881
|
-
|
882
|
-
return DynamicAttrs(**current_data)
|
883
|
-
|
884
|
-
except ImportError:
|
885
|
-
raise ImportError("attrs library is required for attrs conversion")
|
886
|
-
|
887
|
-
elif schema == "dict":
|
888
|
-
return current_data
|
889
|
-
|
890
|
-
else:
|
891
|
-
raise ValueError(f"Unsupported schema format: {schema}")
|
892
|
-
|
893
|
-
@classmethod
|
894
|
-
def convert_from_data(
|
895
|
-
cls,
|
896
|
-
data: Any,
|
897
|
-
schema: Literal[
|
898
|
-
"dataclass",
|
899
|
-
"pydantic",
|
900
|
-
"msgspec",
|
901
|
-
"typeddict",
|
902
|
-
"namedtuple",
|
903
|
-
"attrs",
|
904
|
-
"dict",
|
905
|
-
],
|
906
|
-
exclude: Optional[Union[Set[str], Set[int]]] = None,
|
907
|
-
) -> Any:
|
908
|
-
"""Class method to convert data to different schema formats.
|
909
|
-
|
910
|
-
Args:
|
911
|
-
data: Input data to convert (dict, object, etc.)
|
912
|
-
schema: The target schema format to convert to
|
913
|
-
exclude: Fields to exclude from the conversion
|
914
|
-
|
915
|
-
Returns:
|
916
|
-
The converted model in the specified format
|
917
|
-
"""
|
918
|
-
# First create an instance from the data
|
919
|
-
if isinstance(data, dict):
|
920
|
-
instance = cls(**data)
|
921
|
-
elif hasattr(data, "__dict__"):
|
922
|
-
instance = cls(**data.__dict__)
|
923
|
-
else:
|
924
|
-
instance = cls.model_validate(data)
|
925
|
-
|
926
|
-
# Then use the instance method to convert
|
927
|
-
return instance.model_convert(schema, exclude)
|
928
|
-
|
929
|
-
def model_to_pydantic(self):
|
930
|
-
"""Converts the `Model` to a `pydantic.BaseModel`."""
|
931
|
-
from pydantic import BaseModel, create_model
|
932
|
-
|
933
|
-
# Get the field information from the current instance
|
934
|
-
fields_info = self._get_fields_info()
|
935
|
-
|
936
|
-
# Create a dictionary for pydantic model fields
|
937
|
-
pydantic_fields = {}
|
938
|
-
for field_name, field_info in fields_info.items():
|
939
|
-
field_type = field_info["type"]
|
940
|
-
default = field_info["default"]
|
941
|
-
required = field_info["required"]
|
942
|
-
|
943
|
-
if required:
|
944
|
-
pydantic_fields[field_name] = (field_type, ...)
|
945
|
-
else:
|
946
|
-
pydantic_fields[field_name] = (field_type, default)
|
947
|
-
|
948
|
-
# Create a dynamic pydantic model class
|
949
|
-
PydanticModel = create_model(
|
950
|
-
f"Pydantic{self.__class__.__name__}", **pydantic_fields
|
951
|
-
)
|
952
|
-
|
953
|
-
# Create an instance with the current data
|
954
|
-
current_data = asdict(self)
|
955
|
-
return PydanticModel(**current_data)
|
956
|
-
|
957
|
-
def __str__(self) -> str:
|
958
|
-
"""String representation of the struct."""
|
959
|
-
return f"{self.__class__.__name__}({', '.join(f'{k}={repr(v)}' for k, v in asdict(self).items())})"
|
960
|
-
|
961
|
-
def __repr__(self) -> str:
|
962
|
-
"""Detailed string representation of the struct."""
|
963
|
-
return self.__str__()
|
964
|
-
|
965
|
-
# Dictionary access methods for compatibility
|
966
|
-
def __getitem__(self, key: str) -> Any:
|
967
|
-
"""Get an item from the struct with IDE field completion support."""
|
968
|
-
if not hasattr(self, key):
|
969
|
-
raise KeyError(f"'{key}' not found in {self.__class__.__name__}")
|
970
|
-
return getattr(self, key)
|
971
|
-
|
972
|
-
def get_field(self, field_name: str) -> Any:
|
973
|
-
"""Get a field value with better IDE completion. Use: model.get_field('field_name')"""
|
974
|
-
if not hasattr(self, field_name):
|
975
|
-
raise KeyError(f"'{field_name}' not found in {self.__class__.__name__}")
|
976
|
-
return getattr(self, field_name)
|
977
|
-
|
978
|
-
@property
|
979
|
-
def field_keys(self) -> tuple[str, ...]:
|
980
|
-
"""Get all available field names as a tuple for IDE completion."""
|
981
|
-
return tuple(self.__struct_fields__)
|
982
|
-
|
983
|
-
def fields(self):
|
984
|
-
"""Returns an accessor object with all fields for IDE completion."""
|
985
|
-
|
986
|
-
class FieldAccessor:
|
987
|
-
def __init__(self, instance):
|
988
|
-
self._instance = instance
|
989
|
-
# Dynamically set all field names as properties for IDE completion
|
990
|
-
struct_fields = list(instance.__struct_fields__)
|
991
|
-
self.__dict__.update(
|
992
|
-
{name: getattr(instance, name) for name in struct_fields}
|
993
|
-
)
|
994
|
-
|
995
|
-
def __getitem__(self, field_key: str) -> Any:
|
996
|
-
if not hasattr(self._instance, field_key):
|
997
|
-
raise KeyError(
|
998
|
-
f"'{field_key}' not found in {self._instance.__class__.__name__}"
|
999
|
-
)
|
1000
|
-
return getattr(self._instance, field_key)
|
1001
|
-
|
1002
|
-
def __dir__(self):
|
1003
|
-
return list(self._instance.__struct_fields__)
|
1004
|
-
|
1005
|
-
def keys(self):
|
1006
|
-
"""Get all field names."""
|
1007
|
-
return list(self._instance.__struct_fields__)
|
1008
|
-
|
1009
|
-
def values(self):
|
1010
|
-
"""Get all field values."""
|
1011
|
-
return [
|
1012
|
-
getattr(self._instance, name)
|
1013
|
-
for name in self._instance.__struct_fields__
|
1014
|
-
]
|
1015
|
-
|
1016
|
-
def items(self):
|
1017
|
-
"""Get all field name-value pairs."""
|
1018
|
-
return [
|
1019
|
-
(name, getattr(self._instance, name))
|
1020
|
-
for name in self._instance.__struct_fields__
|
1021
|
-
]
|
1022
|
-
|
1023
|
-
return FieldAccessor(self)
|
1024
|
-
|
1025
|
-
def __setitem__(self, key: str, value: Any) -> None:
|
1026
|
-
"""Set an item in the struct."""
|
1027
|
-
if key not in self.__struct_fields__:
|
1028
|
-
raise KeyError(
|
1029
|
-
f"'{key}' is not a valid field for {self.__class__.__name__}"
|
1030
|
-
)
|
1031
|
-
setattr(self, key, value)
|
1032
|
-
|
1033
|
-
def __delitem__(self, key: str) -> None:
|
1034
|
-
"""Delete an item from the struct.
|
1035
|
-
|
1036
|
-
Note: This will raise an error as struct fields cannot be deleted.
|
1037
|
-
"""
|
1038
|
-
raise TypeError(f"Cannot delete field '{key}' from immutable struct")
|
1039
|
-
|
1040
|
-
def __contains__(self, key: str) -> bool:
|
1041
|
-
"""Check if the struct contains a field."""
|
1042
|
-
return key in self.__struct_fields__
|
1043
|
-
|
1044
|
-
def __iter__(self):
|
1045
|
-
"""Iterate over field names."""
|
1046
|
-
return iter(self.__struct_fields__)
|
1047
|
-
|
1048
|
-
def __dir__(self) -> list[str]:
|
1049
|
-
"""Allows for IDE autocompletion of the model's fields
|
1050
|
-
when accessing through the dictionary interface."""
|
1051
|
-
# Include both parent attributes and field names
|
1052
|
-
base_attrs = super().__dir__()
|
1053
|
-
field_names = list(self.__struct_fields__)
|
1054
|
-
|
1055
|
-
# Add some useful methods and properties
|
1056
|
-
additional_attrs = [
|
1057
|
-
"model_dump",
|
1058
|
-
"model_dump_json",
|
1059
|
-
"model_copy",
|
1060
|
-
"model_validate",
|
1061
|
-
"model_validate_json",
|
1062
|
-
"model_fields",
|
1063
|
-
"model_json_schema",
|
1064
|
-
"model_to_pydantic",
|
1065
|
-
"model_convert",
|
1066
|
-
"fields",
|
1067
|
-
]
|
1068
|
-
|
1069
|
-
return list(set(base_attrs + field_names + additional_attrs))
|
1070
|
-
|
1071
|
-
# Support for __post_init__ if needed (from msgspec 0.18.0+)
|
1072
|
-
def __post_init__(self) -> None:
|
1073
|
-
"""Called after struct initialization.
|
1074
|
-
|
1075
|
-
Override this method in subclasses to add post-initialization logic.
|
1076
|
-
This is called automatically by msgspec after the struct is created.
|
1077
|
-
"""
|
1078
|
-
pass
|