hammad-python 0.0.30__py3-none-any.whl → 0.0.32__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 (137) hide show
  1. ham/__init__.py +200 -0
  2. {hammad_python-0.0.30.dist-info → hammad_python-0.0.32.dist-info}/METADATA +6 -32
  3. hammad_python-0.0.32.dist-info/RECORD +6 -0
  4. hammad/__init__.py +0 -84
  5. hammad/_internal.py +0 -256
  6. hammad/_main.py +0 -226
  7. hammad/cache/__init__.py +0 -40
  8. hammad/cache/base_cache.py +0 -181
  9. hammad/cache/cache.py +0 -169
  10. hammad/cache/decorators.py +0 -261
  11. hammad/cache/file_cache.py +0 -80
  12. hammad/cache/ttl_cache.py +0 -74
  13. hammad/cli/__init__.py +0 -33
  14. hammad/cli/animations.py +0 -573
  15. hammad/cli/plugins.py +0 -867
  16. hammad/cli/styles/__init__.py +0 -55
  17. hammad/cli/styles/settings.py +0 -139
  18. hammad/cli/styles/types.py +0 -358
  19. hammad/cli/styles/utils.py +0 -634
  20. hammad/data/__init__.py +0 -90
  21. hammad/data/collections/__init__.py +0 -49
  22. hammad/data/collections/collection.py +0 -326
  23. hammad/data/collections/indexes/__init__.py +0 -37
  24. hammad/data/collections/indexes/qdrant/__init__.py +0 -1
  25. hammad/data/collections/indexes/qdrant/index.py +0 -723
  26. hammad/data/collections/indexes/qdrant/settings.py +0 -94
  27. hammad/data/collections/indexes/qdrant/utils.py +0 -210
  28. hammad/data/collections/indexes/tantivy/__init__.py +0 -1
  29. hammad/data/collections/indexes/tantivy/index.py +0 -426
  30. hammad/data/collections/indexes/tantivy/settings.py +0 -40
  31. hammad/data/collections/indexes/tantivy/utils.py +0 -176
  32. hammad/data/configurations/__init__.py +0 -35
  33. hammad/data/configurations/configuration.py +0 -564
  34. hammad/data/models/__init__.py +0 -50
  35. hammad/data/models/extensions/__init__.py +0 -4
  36. hammad/data/models/extensions/pydantic/__init__.py +0 -42
  37. hammad/data/models/extensions/pydantic/converters.py +0 -759
  38. hammad/data/models/fields.py +0 -546
  39. hammad/data/models/model.py +0 -1078
  40. hammad/data/models/utils.py +0 -280
  41. hammad/data/sql/__init__.py +0 -24
  42. hammad/data/sql/database.py +0 -576
  43. hammad/data/sql/types.py +0 -127
  44. hammad/data/types/__init__.py +0 -75
  45. hammad/data/types/file.py +0 -431
  46. hammad/data/types/multimodal/__init__.py +0 -36
  47. hammad/data/types/multimodal/audio.py +0 -200
  48. hammad/data/types/multimodal/image.py +0 -182
  49. hammad/data/types/text.py +0 -1308
  50. hammad/formatting/__init__.py +0 -33
  51. hammad/formatting/json/__init__.py +0 -27
  52. hammad/formatting/json/converters.py +0 -158
  53. hammad/formatting/text/__init__.py +0 -63
  54. hammad/formatting/text/converters.py +0 -723
  55. hammad/formatting/text/markdown.py +0 -131
  56. hammad/formatting/yaml/__init__.py +0 -26
  57. hammad/formatting/yaml/converters.py +0 -5
  58. hammad/genai/__init__.py +0 -217
  59. hammad/genai/a2a/__init__.py +0 -32
  60. hammad/genai/a2a/workers.py +0 -552
  61. hammad/genai/agents/__init__.py +0 -59
  62. hammad/genai/agents/agent.py +0 -1973
  63. hammad/genai/agents/run.py +0 -1024
  64. hammad/genai/agents/types/__init__.py +0 -42
  65. hammad/genai/agents/types/agent_context.py +0 -13
  66. hammad/genai/agents/types/agent_event.py +0 -128
  67. hammad/genai/agents/types/agent_hooks.py +0 -220
  68. hammad/genai/agents/types/agent_messages.py +0 -31
  69. hammad/genai/agents/types/agent_response.py +0 -125
  70. hammad/genai/agents/types/agent_stream.py +0 -327
  71. hammad/genai/graphs/__init__.py +0 -125
  72. hammad/genai/graphs/_utils.py +0 -190
  73. hammad/genai/graphs/base.py +0 -1828
  74. hammad/genai/graphs/plugins.py +0 -316
  75. hammad/genai/graphs/types.py +0 -638
  76. hammad/genai/models/__init__.py +0 -1
  77. hammad/genai/models/embeddings/__init__.py +0 -43
  78. hammad/genai/models/embeddings/model.py +0 -226
  79. hammad/genai/models/embeddings/run.py +0 -163
  80. hammad/genai/models/embeddings/types/__init__.py +0 -37
  81. hammad/genai/models/embeddings/types/embedding_model_name.py +0 -75
  82. hammad/genai/models/embeddings/types/embedding_model_response.py +0 -76
  83. hammad/genai/models/embeddings/types/embedding_model_run_params.py +0 -66
  84. hammad/genai/models/embeddings/types/embedding_model_settings.py +0 -47
  85. hammad/genai/models/language/__init__.py +0 -57
  86. hammad/genai/models/language/model.py +0 -1098
  87. hammad/genai/models/language/run.py +0 -878
  88. hammad/genai/models/language/types/__init__.py +0 -40
  89. hammad/genai/models/language/types/language_model_instructor_mode.py +0 -47
  90. hammad/genai/models/language/types/language_model_messages.py +0 -28
  91. hammad/genai/models/language/types/language_model_name.py +0 -239
  92. hammad/genai/models/language/types/language_model_request.py +0 -127
  93. hammad/genai/models/language/types/language_model_response.py +0 -217
  94. hammad/genai/models/language/types/language_model_response_chunk.py +0 -56
  95. hammad/genai/models/language/types/language_model_settings.py +0 -89
  96. hammad/genai/models/language/types/language_model_stream.py +0 -600
  97. hammad/genai/models/language/utils/__init__.py +0 -28
  98. hammad/genai/models/language/utils/requests.py +0 -421
  99. hammad/genai/models/language/utils/structured_outputs.py +0 -135
  100. hammad/genai/models/model_provider.py +0 -4
  101. hammad/genai/models/multimodal.py +0 -47
  102. hammad/genai/models/reranking.py +0 -26
  103. hammad/genai/types/__init__.py +0 -1
  104. hammad/genai/types/base.py +0 -215
  105. hammad/genai/types/history.py +0 -290
  106. hammad/genai/types/tools.py +0 -507
  107. hammad/logging/__init__.py +0 -35
  108. hammad/logging/decorators.py +0 -834
  109. hammad/logging/logger.py +0 -1018
  110. hammad/mcp/__init__.py +0 -53
  111. hammad/mcp/client/__init__.py +0 -35
  112. hammad/mcp/client/client.py +0 -624
  113. hammad/mcp/client/client_service.py +0 -400
  114. hammad/mcp/client/settings.py +0 -178
  115. hammad/mcp/servers/__init__.py +0 -26
  116. hammad/mcp/servers/launcher.py +0 -1161
  117. hammad/runtime/__init__.py +0 -32
  118. hammad/runtime/decorators.py +0 -142
  119. hammad/runtime/run.py +0 -299
  120. hammad/service/__init__.py +0 -49
  121. hammad/service/create.py +0 -527
  122. hammad/service/decorators.py +0 -283
  123. hammad/types.py +0 -288
  124. hammad/typing/__init__.py +0 -435
  125. hammad/web/__init__.py +0 -43
  126. hammad/web/http/__init__.py +0 -1
  127. hammad/web/http/client.py +0 -944
  128. hammad/web/models.py +0 -275
  129. hammad/web/openapi/__init__.py +0 -1
  130. hammad/web/openapi/client.py +0 -740
  131. hammad/web/search/__init__.py +0 -1
  132. hammad/web/search/client.py +0 -1023
  133. hammad/web/utils.py +0 -472
  134. hammad_python-0.0.30.dist-info/RECORD +0 -135
  135. {hammad → ham}/py.typed +0 -0
  136. {hammad_python-0.0.30.dist-info → hammad_python-0.0.32.dist-info}/WHEEL +0 -0
  137. {hammad_python-0.0.30.dist-info → hammad_python-0.0.32.dist-info}/licenses/LICENSE +0 -0
@@ -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