hammad-python 0.0.11__py3-none-any.whl → 0.0.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hammad/__init__.py +169 -56
- hammad/_core/__init__.py +1 -0
- hammad/_core/_utils/__init__.py +4 -0
- hammad/_core/_utils/_import_utils.py +182 -0
- hammad/ai/__init__.py +59 -0
- hammad/ai/_utils.py +142 -0
- hammad/ai/completions/__init__.py +44 -0
- hammad/ai/completions/client.py +729 -0
- hammad/ai/completions/create.py +686 -0
- hammad/ai/completions/types.py +711 -0
- hammad/ai/completions/utils.py +374 -0
- hammad/ai/embeddings/__init__.py +35 -0
- hammad/ai/embeddings/client/__init__.py +1 -0
- hammad/ai/embeddings/client/base_embeddings_client.py +26 -0
- hammad/ai/embeddings/client/fastembed_text_embeddings_client.py +200 -0
- hammad/ai/embeddings/client/litellm_embeddings_client.py +288 -0
- hammad/ai/embeddings/create.py +159 -0
- hammad/ai/embeddings/types.py +69 -0
- hammad/base/__init__.py +35 -0
- hammad/{based → base}/fields.py +23 -23
- hammad/{based → base}/model.py +124 -14
- hammad/base/utils.py +280 -0
- hammad/cache/__init__.py +30 -12
- hammad/cache/base_cache.py +181 -0
- hammad/cache/cache.py +169 -0
- hammad/cache/decorators.py +261 -0
- hammad/cache/file_cache.py +80 -0
- hammad/cache/ttl_cache.py +74 -0
- hammad/cli/__init__.py +10 -2
- hammad/cli/{styles/animations.py → animations.py} +79 -23
- hammad/cli/{plugins/__init__.py → plugins.py} +85 -90
- hammad/cli/styles/__init__.py +50 -0
- hammad/cli/styles/settings.py +4 -0
- hammad/configuration/__init__.py +35 -0
- hammad/{data/types/files → configuration}/configuration.py +96 -7
- hammad/data/__init__.py +14 -26
- hammad/data/collections/__init__.py +4 -2
- hammad/data/collections/collection.py +300 -75
- hammad/data/collections/vector_collection.py +118 -12
- hammad/data/databases/__init__.py +2 -2
- hammad/data/databases/database.py +383 -32
- hammad/json/__init__.py +2 -2
- hammad/logging/__init__.py +13 -5
- hammad/logging/decorators.py +404 -2
- hammad/logging/logger.py +442 -22
- hammad/multimodal/__init__.py +24 -0
- hammad/{data/types/files → multimodal}/audio.py +21 -6
- hammad/{data/types/files → multimodal}/image.py +5 -5
- hammad/multithreading/__init__.py +304 -0
- hammad/pydantic/__init__.py +2 -2
- hammad/pydantic/converters.py +1 -1
- hammad/pydantic/models/__init__.py +2 -2
- hammad/text/__init__.py +59 -14
- hammad/text/converters.py +723 -0
- hammad/text/{utils/markdown/formatting.py → markdown.py} +25 -23
- hammad/text/text.py +12 -14
- hammad/types/__init__.py +11 -0
- hammad/{data/types/files → types}/file.py +18 -18
- hammad/typing/__init__.py +138 -84
- hammad/web/__init__.py +3 -2
- hammad/web/models.py +245 -0
- hammad/web/search/client.py +75 -23
- hammad/web/utils.py +14 -5
- hammad/yaml/__init__.py +2 -2
- hammad/yaml/converters.py +1 -1
- {hammad_python-0.0.11.dist-info → hammad_python-0.0.13.dist-info}/METADATA +4 -1
- hammad_python-0.0.13.dist-info/RECORD +85 -0
- hammad/based/__init__.py +0 -52
- hammad/based/utils.py +0 -455
- hammad/cache/_cache.py +0 -746
- hammad/data/types/__init__.py +0 -33
- hammad/data/types/files/__init__.py +0 -1
- hammad/data/types/files/document.py +0 -195
- hammad/text/utils/__init__.py +0 -1
- hammad/text/utils/converters.py +0 -229
- hammad/text/utils/markdown/__init__.py +0 -1
- hammad/text/utils/markdown/converters.py +0 -506
- hammad_python-0.0.11.dist-info/RECORD +0 -65
- {hammad_python-0.0.11.dist-info → hammad_python-0.0.13.dist-info}/WHEEL +0 -0
- {hammad_python-0.0.11.dist-info → hammad_python-0.0.13.dist-info}/licenses/LICENSE +0 -0
hammad/{based → base}/model.py
RENAMED
@@ -1,14 +1,124 @@
|
|
1
|
-
"""hammad.
|
1
|
+
"""hammad.base.model"""
|
2
2
|
|
3
3
|
import copy
|
4
4
|
from functools import lru_cache
|
5
|
-
from typing import
|
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
|
+
)
|
6
18
|
|
7
19
|
import msgspec
|
8
20
|
from msgspec.json import decode, encode, schema
|
9
21
|
from msgspec.structs import Struct, asdict, fields
|
10
22
|
|
11
|
-
__all__ = ("
|
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
|
12
122
|
|
13
123
|
|
14
124
|
def _get_field_schema(field) -> dict[str, Any]:
|
@@ -83,7 +193,7 @@ def _get_type_schema(type_hint) -> dict[str, Any]:
|
|
83
193
|
return {"type": "object"}
|
84
194
|
|
85
195
|
|
86
|
-
class
|
196
|
+
class Model(Struct):
|
87
197
|
"""Based, as defined by Lil B is:
|
88
198
|
|
89
199
|
```markdown
|
@@ -96,7 +206,7 @@ class BasedModel(Struct):
|
|
96
206
|
does not support the `keys`, `values`, `items`, and `get`
|
97
207
|
methods to allow for usage of those fields as key names.
|
98
208
|
|
99
|
-
These wise words define this model. A `
|
209
|
+
These wise words define this model. A `Model` is
|
100
210
|
interactable both through the dictionary interface, and
|
101
211
|
dot notation, and utilizes `msgspec` to provide an interface
|
102
212
|
identical to a `pydantic.BaseModel`, with the benefit of
|
@@ -423,7 +533,7 @@ class BasedModel(Struct):
|
|
423
533
|
cls,
|
424
534
|
fields: str,
|
425
535
|
schema: Literal[
|
426
|
-
"
|
536
|
+
"base",
|
427
537
|
"dataclass",
|
428
538
|
"pydantic",
|
429
539
|
"msgspec",
|
@@ -431,7 +541,7 @@ class BasedModel(Struct):
|
|
431
541
|
"namedtuple",
|
432
542
|
"attrs",
|
433
543
|
"dict",
|
434
|
-
] = "
|
544
|
+
] = "base",
|
435
545
|
# Simple Override Params To Edit The Final Model
|
436
546
|
# This method always goes field(s) -> model not to field
|
437
547
|
title: Optional[str] = None,
|
@@ -446,7 +556,7 @@ class BasedModel(Struct):
|
|
446
556
|
|
447
557
|
Args:
|
448
558
|
fields: The field to be converted into the model
|
449
|
-
schema: The target schema format to convert to (Defaults to a
|
559
|
+
schema: The target schema format to convert to (Defaults to a Model)
|
450
560
|
title : An optional title or title override for the model (uses the field name if not provided)
|
451
561
|
description : An optional description for the model (uses the field description if not provided)
|
452
562
|
field_name : The name of the field within this new model representing the target field (defaults to "value")
|
@@ -489,8 +599,8 @@ class BasedModel(Struct):
|
|
489
599
|
model_title = title or fields.title()
|
490
600
|
model_description = description or f"Model wrapping field '{fields}'"
|
491
601
|
|
492
|
-
if schema == "
|
493
|
-
from .fields import
|
602
|
+
if schema == "base":
|
603
|
+
from .fields import field
|
494
604
|
|
495
605
|
# Create annotations for the dynamic class
|
496
606
|
annotations = {field_name: field_type}
|
@@ -500,18 +610,18 @@ class BasedModel(Struct):
|
|
500
610
|
|
501
611
|
# Add default if available
|
502
612
|
if field_default is not msgspec.UNSET:
|
503
|
-
class_dict[field_name] =
|
613
|
+
class_dict[field_name] = field(
|
504
614
|
default=field_default,
|
505
615
|
description=field_description,
|
506
616
|
examples=field_examples,
|
507
617
|
)
|
508
618
|
elif field_description or field_examples:
|
509
|
-
class_dict[field_name] =
|
619
|
+
class_dict[field_name] = field(
|
510
620
|
description=field_description, examples=field_examples
|
511
621
|
)
|
512
622
|
|
513
623
|
# Create the dynamic class
|
514
|
-
DynamicModel = type(model_title.replace(" ", ""), (
|
624
|
+
DynamicModel = type(model_title.replace(" ", ""), (Model,), class_dict)
|
515
625
|
|
516
626
|
if init and field_default is not msgspec.UNSET:
|
517
627
|
return DynamicModel(**{field_name: field_default})
|
@@ -817,7 +927,7 @@ class BasedModel(Struct):
|
|
817
927
|
return instance.model_convert(schema, exclude)
|
818
928
|
|
819
929
|
def model_to_pydantic(self):
|
820
|
-
"""Converts the `
|
930
|
+
"""Converts the `Model` to a `pydantic.BaseModel`."""
|
821
931
|
from pydantic import BaseModel, create_model
|
822
932
|
|
823
933
|
# Get the field information from the current instance
|
hammad/base/utils.py
ADDED
@@ -0,0 +1,280 @@
|
|
1
|
+
"""hammad.base.utils"""
|
2
|
+
|
3
|
+
from functools import lru_cache
|
4
|
+
from typing import Any, Callable, Optional, Union, Tuple, Dict
|
5
|
+
|
6
|
+
from msgspec.structs import Struct
|
7
|
+
|
8
|
+
from .fields import FieldInfo, field, Field
|
9
|
+
from .model import Model
|
10
|
+
|
11
|
+
|
12
|
+
__all__ = (
|
13
|
+
"create_model",
|
14
|
+
"get_field_info",
|
15
|
+
"is_field",
|
16
|
+
"is_model",
|
17
|
+
"validator",
|
18
|
+
)
|
19
|
+
|
20
|
+
|
21
|
+
def create_model(
|
22
|
+
__model_name: str,
|
23
|
+
*,
|
24
|
+
__base__: Optional[Union[type, Tuple[type, ...]]] = None,
|
25
|
+
__module__: Optional[str] = None,
|
26
|
+
__qualname__: Optional[str] = None,
|
27
|
+
__doc__: Optional[str] = None,
|
28
|
+
__validators__: Optional[Dict[str, Any]] = None,
|
29
|
+
__config__: Optional[type] = None,
|
30
|
+
**field_definitions: Any,
|
31
|
+
) -> type[Model]:
|
32
|
+
"""Create a Model dynamically with Pydantic-compatible interface.
|
33
|
+
|
34
|
+
This function provides a drop-in replacement for pydantic.create_model()
|
35
|
+
that creates Model classes instead of pydantic BaseModel classes.
|
36
|
+
|
37
|
+
Args:
|
38
|
+
__model_name: Name of the model class to create
|
39
|
+
__base__: Base class(es) to inherit from. If None, uses Model.
|
40
|
+
Can be a single class or tuple of classes.
|
41
|
+
__module__: Module name for the created class
|
42
|
+
__qualname__: Qualified name for the created class
|
43
|
+
__doc__: Docstring for the created class
|
44
|
+
__validators__: Dictionary of validators (for compatibility - not fully implemented)
|
45
|
+
__config__: Configuration class (for compatibility - not fully implemented)
|
46
|
+
**field_definitions: Field definitions as keyword arguments.
|
47
|
+
Each can be:
|
48
|
+
- A type annotation (e.g., str, int)
|
49
|
+
- A tuple of (type, default_value)
|
50
|
+
- A tuple of (type, Field(...))
|
51
|
+
- A Field instance
|
52
|
+
|
53
|
+
Returns:
|
54
|
+
A new Model class with the specified fields
|
55
|
+
|
56
|
+
Examples:
|
57
|
+
# Simple model with basic types
|
58
|
+
User = create_model('User', name=str, age=int)
|
59
|
+
|
60
|
+
# Model with defaults
|
61
|
+
Config = create_model('Config',
|
62
|
+
host=(str, 'localhost'),
|
63
|
+
port=(int, 8080))
|
64
|
+
|
65
|
+
# Model with field constraints
|
66
|
+
Product = create_model('Product',
|
67
|
+
name=str,
|
68
|
+
price=(float, field(gt=0)),
|
69
|
+
tags=(List[str], field(default_factory=list)))
|
70
|
+
|
71
|
+
# Model with custom base class
|
72
|
+
class BaseEntity(Model):
|
73
|
+
id: int
|
74
|
+
created_at: str
|
75
|
+
|
76
|
+
User = create_model('User',
|
77
|
+
name=str,
|
78
|
+
email=str,
|
79
|
+
__base__=BaseEntity)
|
80
|
+
"""
|
81
|
+
# Handle base class specification
|
82
|
+
if __base__ is not None and __config__ is not None:
|
83
|
+
raise ValueError(
|
84
|
+
"Cannot specify both '__base__' and '__config__' - "
|
85
|
+
"use a base class with the desired configuration instead"
|
86
|
+
)
|
87
|
+
|
88
|
+
# Determine base classes
|
89
|
+
if __base__ is None:
|
90
|
+
bases = (Model,)
|
91
|
+
elif isinstance(__base__, tuple):
|
92
|
+
# Ensure all bases are compatible
|
93
|
+
for base in __base__:
|
94
|
+
if not (issubclass(base, Model) or issubclass(base, Struct)):
|
95
|
+
raise ValueError(
|
96
|
+
f"Base class {base} must be a subclass of Model or msgspec.Struct"
|
97
|
+
)
|
98
|
+
bases = __base__
|
99
|
+
else:
|
100
|
+
if not (issubclass(__base__, Model) or issubclass(__base__, Struct)):
|
101
|
+
raise ValueError(
|
102
|
+
f"Base class {__base__} must be a subclass of Model or msgspec.Struct"
|
103
|
+
)
|
104
|
+
bases = (__base__,)
|
105
|
+
|
106
|
+
# Build class dictionary
|
107
|
+
class_dict = {}
|
108
|
+
annotations = {}
|
109
|
+
|
110
|
+
# Set metadata
|
111
|
+
if __doc__ is not None:
|
112
|
+
class_dict["__doc__"] = __doc__
|
113
|
+
if __module__ is not None:
|
114
|
+
class_dict["__module__"] = __module__
|
115
|
+
if __qualname__ is not None:
|
116
|
+
class_dict["__qualname__"] = __qualname__
|
117
|
+
|
118
|
+
# Process field definitions in two passes to ensure proper ordering
|
119
|
+
# First pass: collect required and optional fields separately
|
120
|
+
required_fields = {}
|
121
|
+
optional_fields = {}
|
122
|
+
|
123
|
+
for field_name, field_definition in field_definitions.items():
|
124
|
+
if field_name.startswith("__") and field_name.endswith("__"):
|
125
|
+
# Skip special attributes that were passed as field definitions
|
126
|
+
continue
|
127
|
+
|
128
|
+
# Parse field definition
|
129
|
+
is_optional = False
|
130
|
+
|
131
|
+
if isinstance(field_definition, tuple):
|
132
|
+
if len(field_definition) == 2:
|
133
|
+
field_type, field_value = field_definition
|
134
|
+
annotations[field_name] = field_type
|
135
|
+
|
136
|
+
# Check if field_value is a Field instance or field
|
137
|
+
if hasattr(field_value, "__class__") and (
|
138
|
+
"field" in field_value.__class__.__name__.lower()
|
139
|
+
or hasattr(field_value, "default")
|
140
|
+
or callable(getattr(field_value, "__call__", None))
|
141
|
+
):
|
142
|
+
# It's a field descriptor
|
143
|
+
optional_fields[field_name] = field_value
|
144
|
+
else:
|
145
|
+
# It's a default value - create a field with this default
|
146
|
+
optional_fields[field_name] = field(default=field_value)
|
147
|
+
is_optional = True
|
148
|
+
else:
|
149
|
+
raise ValueError(
|
150
|
+
f"Field definition for '{field_name}' must be a 2-tuple of (type, default/Field)"
|
151
|
+
)
|
152
|
+
elif hasattr(field_definition, "__origin__") or hasattr(
|
153
|
+
field_definition, "__class__"
|
154
|
+
):
|
155
|
+
# It's a type annotation (like str, int, List[str], etc.) - required field
|
156
|
+
annotations[field_name] = field_definition
|
157
|
+
required_fields[field_name] = None
|
158
|
+
else:
|
159
|
+
# It's likely a default value without type annotation
|
160
|
+
# We'll infer the type from the value
|
161
|
+
annotations[field_name] = type(field_definition)
|
162
|
+
optional_fields[field_name] = field(default=field_definition)
|
163
|
+
is_optional = True
|
164
|
+
|
165
|
+
# Second pass: add fields in correct order (required first, then optional)
|
166
|
+
# This ensures msgspec field ordering requirements are met
|
167
|
+
for field_name, field_value in required_fields.items():
|
168
|
+
if field_value is not None:
|
169
|
+
class_dict[field_name] = field_value
|
170
|
+
|
171
|
+
for field_name, field_value in optional_fields.items():
|
172
|
+
class_dict[field_name] = field_value
|
173
|
+
|
174
|
+
# Set annotations in proper order (required fields first, then optional)
|
175
|
+
ordered_annotations = {}
|
176
|
+
|
177
|
+
# Add required field annotations first
|
178
|
+
for field_name in required_fields:
|
179
|
+
if field_name in annotations:
|
180
|
+
ordered_annotations[field_name] = annotations[field_name]
|
181
|
+
|
182
|
+
# Add optional field annotations second
|
183
|
+
for field_name in optional_fields:
|
184
|
+
if field_name in annotations:
|
185
|
+
ordered_annotations[field_name] = annotations[field_name]
|
186
|
+
|
187
|
+
class_dict["__annotations__"] = ordered_annotations
|
188
|
+
|
189
|
+
# Handle validators (basic implementation for compatibility)
|
190
|
+
if __validators__:
|
191
|
+
# Store validators for potential future use
|
192
|
+
class_dict["_validators"] = __validators__
|
193
|
+
# Note: Full validator implementation would require more complex integration
|
194
|
+
|
195
|
+
# Create the dynamic class
|
196
|
+
try:
|
197
|
+
DynamicModel = type(__model_name, bases, class_dict)
|
198
|
+
except Exception as e:
|
199
|
+
raise ValueError(f"Failed to create model '{__model_name}': {e}") from e
|
200
|
+
|
201
|
+
return DynamicModel
|
202
|
+
|
203
|
+
|
204
|
+
@lru_cache(maxsize=None)
|
205
|
+
def get_field_info(field: Any) -> Optional[FieldInfo]:
|
206
|
+
"""Extract FieldInfo from a field descriptor with caching."""
|
207
|
+
if isinstance(field, tuple) and len(field) == 2:
|
208
|
+
_, field_info = field
|
209
|
+
if isinstance(field_info, FieldInfo):
|
210
|
+
return field_info
|
211
|
+
elif hasattr(field, "_field_info"):
|
212
|
+
return field._field_info
|
213
|
+
elif hasattr(field, "field_info"):
|
214
|
+
return field.field_info
|
215
|
+
elif isinstance(field, Field):
|
216
|
+
return field.field_info
|
217
|
+
elif hasattr(field, "__class__") and field.__class__.__name__ == "FieldDescriptor":
|
218
|
+
return field.field_info
|
219
|
+
return None
|
220
|
+
|
221
|
+
|
222
|
+
def is_field(field: Any) -> bool:
|
223
|
+
"""Check if a field is a field."""
|
224
|
+
return get_field_info(field) is not None
|
225
|
+
|
226
|
+
|
227
|
+
def is_model(model: Any) -> bool:
|
228
|
+
"""Check if a model is a model."""
|
229
|
+
# Check if it's an instance of Model
|
230
|
+
if isinstance(model, Model):
|
231
|
+
return True
|
232
|
+
|
233
|
+
# Check if it's a Model class (not instance)
|
234
|
+
if isinstance(model, type) and issubclass(model, Model):
|
235
|
+
return True
|
236
|
+
|
237
|
+
# Check for Model characteristics using duck typing
|
238
|
+
# Look for key Model/msgspec.Struct attributes and methods
|
239
|
+
if hasattr(model, "__struct_fields__") and hasattr(model, "model_dump"):
|
240
|
+
# Check for Model-specific methods
|
241
|
+
if (
|
242
|
+
hasattr(model, "model_copy")
|
243
|
+
and hasattr(model, "model_validate")
|
244
|
+
and hasattr(model, "model_to_pydantic")
|
245
|
+
):
|
246
|
+
return True
|
247
|
+
|
248
|
+
# Check if it's an instance of any msgspec Struct with Model methods
|
249
|
+
try:
|
250
|
+
if isinstance(model, Struct) and hasattr(model, "model_dump"):
|
251
|
+
return True
|
252
|
+
except ImportError:
|
253
|
+
pass
|
254
|
+
|
255
|
+
return False
|
256
|
+
|
257
|
+
|
258
|
+
def validator(
|
259
|
+
*fields: str, pre: bool = False, post: bool = False, always: bool = False
|
260
|
+
):
|
261
|
+
"""Decorator to create a validator for specific fields.
|
262
|
+
|
263
|
+
Args:
|
264
|
+
*fields: Field names to validate
|
265
|
+
pre: Whether this is a pre-validator
|
266
|
+
post: Whether this is a post-validator
|
267
|
+
always: Whether to run even if the value is not set
|
268
|
+
|
269
|
+
Returns:
|
270
|
+
Decorator function
|
271
|
+
"""
|
272
|
+
|
273
|
+
def decorator(func: Callable) -> Callable:
|
274
|
+
func._validator_fields = fields
|
275
|
+
func._validator_pre = pre
|
276
|
+
func._validator_post = post
|
277
|
+
func._validator_always = always
|
278
|
+
return func
|
279
|
+
|
280
|
+
return decorator
|
hammad/cache/__init__.py
CHANGED
@@ -1,30 +1,48 @@
|
|
1
|
-
"""hammad.cache
|
1
|
+
"""hammad.cache
|
2
|
+
|
3
|
+
Contains helpful resources for creating simple cache systems, and
|
4
|
+
decorators that implement "automatic" hashing & caching of function calls.
|
5
|
+
"""
|
2
6
|
|
3
7
|
from typing import TYPE_CHECKING
|
4
|
-
from ..
|
8
|
+
from .._core._utils._import_utils import _auto_create_getattr_loader
|
5
9
|
|
6
10
|
if TYPE_CHECKING:
|
7
|
-
from .
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
11
|
+
from .base_cache import BaseCache, CacheParams, CacheReturn, CacheType
|
12
|
+
from .file_cache import FileCache
|
13
|
+
from .ttl_cache import TTLCache
|
14
|
+
from .cache import Cache, create_cache
|
15
|
+
from .decorators import (
|
12
16
|
cached,
|
13
17
|
auto_cached,
|
14
|
-
|
18
|
+
get_decorator_cache,
|
19
|
+
clear_decorator_cache,
|
15
20
|
)
|
16
21
|
|
17
22
|
|
18
23
|
__all__ = (
|
19
|
-
|
20
|
-
"
|
24
|
+
# hammad.cache.base_cache
|
25
|
+
"BaseCache",
|
26
|
+
"CacheParams",
|
27
|
+
"CacheReturn",
|
28
|
+
"CacheType",
|
29
|
+
# hammad.cache.file_cache
|
30
|
+
"FileCache",
|
31
|
+
# hammad.cache.ttl_cache
|
32
|
+
"TTLCache",
|
33
|
+
# hammad.cache.cache
|
34
|
+
"Cache",
|
21
35
|
"create_cache",
|
36
|
+
# hammad.cache.decorators
|
37
|
+
"cached",
|
38
|
+
"auto_cached",
|
39
|
+
"get_decorator_cache",
|
40
|
+
"clear_decorator_cache",
|
22
41
|
)
|
23
42
|
|
24
43
|
|
25
|
-
__getattr__ =
|
44
|
+
__getattr__ = _auto_create_getattr_loader(__all__)
|
26
45
|
|
27
46
|
|
28
47
|
def __dir__() -> list[str]:
|
29
|
-
"""Get the attributes of the cache module."""
|
30
48
|
return list(__all__)
|