hammad-python 0.0.13__py3-none-any.whl → 0.0.15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hammad_python-0.0.15.dist-info/METADATA +184 -0
- hammad_python-0.0.15.dist-info/RECORD +4 -0
- hammad/__init__.py +0 -180
- hammad/_core/__init__.py +0 -1
- hammad/_core/_utils/__init__.py +0 -4
- hammad/_core/_utils/_import_utils.py +0 -182
- hammad/ai/__init__.py +0 -59
- hammad/ai/_utils.py +0 -142
- hammad/ai/completions/__init__.py +0 -44
- hammad/ai/completions/client.py +0 -729
- hammad/ai/completions/create.py +0 -686
- hammad/ai/completions/types.py +0 -711
- hammad/ai/completions/utils.py +0 -374
- hammad/ai/embeddings/__init__.py +0 -35
- hammad/ai/embeddings/client/__init__.py +0 -1
- hammad/ai/embeddings/client/base_embeddings_client.py +0 -26
- hammad/ai/embeddings/client/fastembed_text_embeddings_client.py +0 -200
- hammad/ai/embeddings/client/litellm_embeddings_client.py +0 -288
- hammad/ai/embeddings/create.py +0 -159
- hammad/ai/embeddings/types.py +0 -69
- hammad/base/__init__.py +0 -35
- hammad/base/fields.py +0 -546
- hammad/base/model.py +0 -1078
- hammad/base/utils.py +0 -280
- hammad/cache/__init__.py +0 -48
- hammad/cache/base_cache.py +0 -181
- hammad/cache/cache.py +0 -169
- hammad/cache/decorators.py +0 -261
- hammad/cache/file_cache.py +0 -80
- hammad/cache/ttl_cache.py +0 -74
- hammad/cli/__init__.py +0 -33
- hammad/cli/animations.py +0 -604
- hammad/cli/plugins.py +0 -781
- hammad/cli/styles/__init__.py +0 -55
- hammad/cli/styles/settings.py +0 -139
- hammad/cli/styles/types.py +0 -358
- hammad/cli/styles/utils.py +0 -480
- hammad/configuration/__init__.py +0 -35
- hammad/configuration/configuration.py +0 -564
- hammad/data/__init__.py +0 -39
- hammad/data/collections/__init__.py +0 -34
- hammad/data/collections/base_collection.py +0 -58
- hammad/data/collections/collection.py +0 -452
- hammad/data/collections/searchable_collection.py +0 -556
- hammad/data/collections/vector_collection.py +0 -603
- hammad/data/databases/__init__.py +0 -21
- hammad/data/databases/database.py +0 -902
- hammad/json/__init__.py +0 -21
- hammad/json/converters.py +0 -152
- hammad/logging/__init__.py +0 -35
- hammad/logging/decorators.py +0 -834
- hammad/logging/logger.py +0 -954
- hammad/multimodal/__init__.py +0 -24
- hammad/multimodal/audio.py +0 -96
- hammad/multimodal/image.py +0 -80
- hammad/multithreading/__init__.py +0 -304
- hammad/py.typed +0 -0
- hammad/pydantic/__init__.py +0 -43
- hammad/pydantic/converters.py +0 -623
- hammad/pydantic/models/__init__.py +0 -28
- hammad/pydantic/models/arbitrary_model.py +0 -46
- hammad/pydantic/models/cacheable_model.py +0 -79
- hammad/pydantic/models/fast_model.py +0 -318
- hammad/pydantic/models/function_model.py +0 -176
- hammad/pydantic/models/subscriptable_model.py +0 -63
- hammad/text/__init__.py +0 -82
- hammad/text/converters.py +0 -723
- hammad/text/markdown.py +0 -131
- hammad/text/text.py +0 -1066
- hammad/types/__init__.py +0 -11
- hammad/types/file.py +0 -358
- hammad/typing/__init__.py +0 -407
- hammad/web/__init__.py +0 -43
- hammad/web/http/__init__.py +0 -1
- hammad/web/http/client.py +0 -944
- hammad/web/models.py +0 -245
- hammad/web/openapi/__init__.py +0 -0
- hammad/web/openapi/client.py +0 -740
- hammad/web/search/__init__.py +0 -1
- hammad/web/search/client.py +0 -988
- hammad/web/utils.py +0 -472
- hammad/yaml/__init__.py +0 -30
- hammad/yaml/converters.py +0 -19
- hammad_python-0.0.13.dist-info/METADATA +0 -38
- hammad_python-0.0.13.dist-info/RECORD +0 -85
- {hammad_python-0.0.13.dist-info → hammad_python-0.0.15.dist-info}/WHEEL +0 -0
- {hammad_python-0.0.13.dist-info → hammad_python-0.0.15.dist-info}/licenses/LICENSE +0 -0
hammad/multimodal/__init__.py
DELETED
@@ -1,24 +0,0 @@
|
|
1
|
-
"""hammad.multimodal
|
2
|
-
|
3
|
-
Contains types and model like objects for working with various
|
4
|
-
types of multimodal data."""
|
5
|
-
|
6
|
-
from typing import TYPE_CHECKING
|
7
|
-
from .._core._utils._import_utils import _auto_create_getattr_loader
|
8
|
-
|
9
|
-
if TYPE_CHECKING:
|
10
|
-
from .image import Image
|
11
|
-
from .audio import Audio
|
12
|
-
|
13
|
-
|
14
|
-
__all__ = (
|
15
|
-
"Image",
|
16
|
-
"Audio",
|
17
|
-
)
|
18
|
-
|
19
|
-
|
20
|
-
__getattr__ = _auto_create_getattr_loader(__all__)
|
21
|
-
|
22
|
-
|
23
|
-
def __dir__() -> list[str]:
|
24
|
-
return list(__all__)
|
hammad/multimodal/audio.py
DELETED
@@ -1,96 +0,0 @@
|
|
1
|
-
"""hammad.data.types.files.audio"""
|
2
|
-
|
3
|
-
import httpx
|
4
|
-
from typing import Self
|
5
|
-
|
6
|
-
from ..types.file import File, FileSource
|
7
|
-
from ..base.fields import field
|
8
|
-
|
9
|
-
__all__ = ("Audio",)
|
10
|
-
|
11
|
-
|
12
|
-
class Audio(File):
|
13
|
-
"""A representation of an audio file, that is loadable from both a URL, file path
|
14
|
-
or bytes."""
|
15
|
-
|
16
|
-
# Audio-specific metadata
|
17
|
-
_duration: float | None = field(default=None)
|
18
|
-
_sample_rate: int | None = field(default=None)
|
19
|
-
_channels: int | None = field(default=None)
|
20
|
-
_format: str | None = field(default=None)
|
21
|
-
|
22
|
-
@property
|
23
|
-
def is_valid_audio(self) -> bool:
|
24
|
-
"""Check if this is a valid audio file based on MIME type."""
|
25
|
-
return self.type is not None and self.type.startswith("audio/")
|
26
|
-
|
27
|
-
@property
|
28
|
-
def format(self) -> str | None:
|
29
|
-
"""Get the audio format from MIME type."""
|
30
|
-
if self._format is None and self.type:
|
31
|
-
# Extract format from MIME type (e.g., 'audio/mp3' -> 'mp3')
|
32
|
-
self._format = self.type.split("/")[-1].upper()
|
33
|
-
return self._format
|
34
|
-
|
35
|
-
@property
|
36
|
-
def duration(self) -> float | None:
|
37
|
-
"""Get the duration of the audio file in seconds."""
|
38
|
-
return self._duration
|
39
|
-
|
40
|
-
@property
|
41
|
-
def sample_rate(self) -> int | None:
|
42
|
-
"""Get the sample rate of the audio file in Hz."""
|
43
|
-
return self._sample_rate
|
44
|
-
|
45
|
-
@property
|
46
|
-
def channels(self) -> int | None:
|
47
|
-
"""Get the number of channels in the audio file."""
|
48
|
-
return self._channels
|
49
|
-
|
50
|
-
@classmethod
|
51
|
-
def from_url(
|
52
|
-
cls,
|
53
|
-
url: str,
|
54
|
-
*,
|
55
|
-
lazy: bool = True,
|
56
|
-
timeout: float = 30.0,
|
57
|
-
) -> Self:
|
58
|
-
"""Download and create an audio file from a URL.
|
59
|
-
|
60
|
-
Args:
|
61
|
-
url: The URL to download from.
|
62
|
-
lazy: If True, defer loading content until needed.
|
63
|
-
timeout: Request timeout in seconds.
|
64
|
-
|
65
|
-
Returns:
|
66
|
-
A new Audio instance.
|
67
|
-
"""
|
68
|
-
data = None
|
69
|
-
size = None
|
70
|
-
type = None
|
71
|
-
|
72
|
-
if not lazy:
|
73
|
-
with httpx.Client(timeout=timeout) as client:
|
74
|
-
response = client.get(url)
|
75
|
-
response.raise_for_status()
|
76
|
-
|
77
|
-
data = response.content
|
78
|
-
size = len(data)
|
79
|
-
|
80
|
-
# Get content type
|
81
|
-
content_type = response.headers.get("content-type", "")
|
82
|
-
type = content_type.split(";")[0] if content_type else None
|
83
|
-
|
84
|
-
# Validate it's audio
|
85
|
-
if type and not type.startswith("audio/"):
|
86
|
-
raise ValueError(f"URL does not point to an audio file: {type}")
|
87
|
-
|
88
|
-
return cls(
|
89
|
-
data=data,
|
90
|
-
type=type,
|
91
|
-
source=FileSource(
|
92
|
-
is_url=True,
|
93
|
-
url=url,
|
94
|
-
size=size,
|
95
|
-
),
|
96
|
-
)
|
hammad/multimodal/image.py
DELETED
@@ -1,80 +0,0 @@
|
|
1
|
-
"""hammad.data.types.files.image"""
|
2
|
-
|
3
|
-
import httpx
|
4
|
-
from typing import Self
|
5
|
-
|
6
|
-
from ..types.file import File, FileSource
|
7
|
-
from ..base.fields import field
|
8
|
-
|
9
|
-
__all__ = ("Image",)
|
10
|
-
|
11
|
-
|
12
|
-
class Image(File):
|
13
|
-
"""A representation of an image, that is loadable from both a URL, file path
|
14
|
-
or bytes."""
|
15
|
-
|
16
|
-
# Image-specific metadata
|
17
|
-
_width: int | None = field(default=None)
|
18
|
-
_height: int | None = field(default=None)
|
19
|
-
_format: str | None = field(default=None)
|
20
|
-
|
21
|
-
@property
|
22
|
-
def is_valid_image(self) -> bool:
|
23
|
-
"""Check if this is a valid image based on MIME type."""
|
24
|
-
return self.type is not None and self.type.startswith("image/")
|
25
|
-
|
26
|
-
@property
|
27
|
-
def format(self) -> str | None:
|
28
|
-
"""Get the image format from MIME type."""
|
29
|
-
if self._format is None and self.type:
|
30
|
-
# Extract format from MIME type (e.g., 'image/png' -> 'png')
|
31
|
-
self._format = self.type.split("/")[-1].upper()
|
32
|
-
return self._format
|
33
|
-
|
34
|
-
@classmethod
|
35
|
-
def from_url(
|
36
|
-
cls,
|
37
|
-
url: str,
|
38
|
-
*,
|
39
|
-
lazy: bool = True,
|
40
|
-
timeout: float = 30.0,
|
41
|
-
) -> Self:
|
42
|
-
"""Download and create an image from a URL.
|
43
|
-
|
44
|
-
Args:
|
45
|
-
url: The URL to download from.
|
46
|
-
lazy: If True, defer loading content until needed.
|
47
|
-
timeout: Request timeout in seconds.
|
48
|
-
|
49
|
-
Returns:
|
50
|
-
A new Image instance.
|
51
|
-
"""
|
52
|
-
data = None
|
53
|
-
size = None
|
54
|
-
type = None
|
55
|
-
|
56
|
-
if not lazy:
|
57
|
-
with httpx.Client(timeout=timeout) as client:
|
58
|
-
response = client.get(url)
|
59
|
-
response.raise_for_status()
|
60
|
-
|
61
|
-
data = response.content
|
62
|
-
size = len(data)
|
63
|
-
|
64
|
-
# Get content type
|
65
|
-
content_type = response.headers.get("content-type", "")
|
66
|
-
type = content_type.split(";")[0] if content_type else None
|
67
|
-
|
68
|
-
# Validate it's an image
|
69
|
-
if type and not type.startswith("image/"):
|
70
|
-
raise ValueError(f"URL does not point to an image: {type}")
|
71
|
-
|
72
|
-
return cls(
|
73
|
-
data=data,
|
74
|
-
type=type,
|
75
|
-
source=FileSource(
|
76
|
-
is_url=True,
|
77
|
-
url=url,
|
78
|
-
size=size,
|
79
|
-
),
|
80
|
-
)
|
@@ -1,304 +0,0 @@
|
|
1
|
-
"""hammad.multithreading"""
|
2
|
-
|
3
|
-
import concurrent.futures
|
4
|
-
import functools
|
5
|
-
import time
|
6
|
-
from typing import (
|
7
|
-
Callable,
|
8
|
-
Iterable,
|
9
|
-
List,
|
10
|
-
Any,
|
11
|
-
TypeVar,
|
12
|
-
Tuple,
|
13
|
-
Optional,
|
14
|
-
Union,
|
15
|
-
Type,
|
16
|
-
cast,
|
17
|
-
overload,
|
18
|
-
)
|
19
|
-
from tenacity import (
|
20
|
-
retry,
|
21
|
-
wait_exponential,
|
22
|
-
stop_after_attempt,
|
23
|
-
retry_if_exception_type,
|
24
|
-
retry_if_exception_message,
|
25
|
-
retry_if_exception_type,
|
26
|
-
)
|
27
|
-
|
28
|
-
T_Arg = TypeVar("T_Arg")
|
29
|
-
R_Out = TypeVar("R_Out")
|
30
|
-
|
31
|
-
SingleTaskArgs = Union[T_Arg, Tuple[Any, ...]]
|
32
|
-
|
33
|
-
|
34
|
-
__all__ = (
|
35
|
-
"run_sequentially",
|
36
|
-
"run_parallel",
|
37
|
-
"sequentialize",
|
38
|
-
"parallelize",
|
39
|
-
"typed_batch",
|
40
|
-
"run_with_retry",
|
41
|
-
"retry",
|
42
|
-
"wait_exponential",
|
43
|
-
"stop_after_attempt",
|
44
|
-
"retry_if_exception_type",
|
45
|
-
"retry_if_exception_message",
|
46
|
-
"retry_if_exception_type",
|
47
|
-
)
|
48
|
-
|
49
|
-
|
50
|
-
def run_sequentially(
|
51
|
-
func: Callable[..., R_Out], args_list: Iterable[SingleTaskArgs]
|
52
|
-
) -> List[R_Out]:
|
53
|
-
"""
|
54
|
-
Executes a function sequentially for each set of arguments in args_list.
|
55
|
-
If the function raises an exception during any call, the execution stops
|
56
|
-
and the exception is propagated.
|
57
|
-
|
58
|
-
Args:
|
59
|
-
func: The function to execute.
|
60
|
-
args_list: An iterable of arguments (or argument tuples) to pass to func.
|
61
|
-
- If func takes multiple arguments (e.g., func(a, b)),
|
62
|
-
each item in args_list should be a tuple (e.g., [(val1_a, val1_b), (val2_a, val2_b)]).
|
63
|
-
- If func takes one argument (e.g., func(a)),
|
64
|
-
each item can be the argument itself (e.g., [val1, val2]).
|
65
|
-
- If func takes no arguments (e.g., func()),
|
66
|
-
each item should be an empty tuple (e.g., [(), ()]).
|
67
|
-
|
68
|
-
Returns:
|
69
|
-
A list of results from the sequential execution.
|
70
|
-
"""
|
71
|
-
results: List[R_Out] = []
|
72
|
-
for args_item in args_list:
|
73
|
-
if isinstance(args_item, tuple):
|
74
|
-
results.append(func(*args_item))
|
75
|
-
else:
|
76
|
-
# This branch handles single arguments.
|
77
|
-
# If func expects no arguments, args_item should be `()` and caught by `isinstance(tuple)`.
|
78
|
-
# If func expects one argument, args_item is that argument.
|
79
|
-
results.append(func(args_item))
|
80
|
-
return results
|
81
|
-
|
82
|
-
|
83
|
-
def run_parallel(
|
84
|
-
func: Callable[..., R_Out],
|
85
|
-
args_list: Iterable[SingleTaskArgs],
|
86
|
-
max_workers: Optional[int] = None,
|
87
|
-
task_timeout: Optional[float] = None,
|
88
|
-
) -> List[Union[R_Out, Exception]]:
|
89
|
-
"""
|
90
|
-
Executes a function in parallel for each set of arguments in args_list
|
91
|
-
using a ThreadPoolExecutor. Results are returned in the same order as the input args_list.
|
92
|
-
|
93
|
-
Args:
|
94
|
-
func: The function to execute.
|
95
|
-
args_list: An iterable of arguments (or argument tuples) to pass to func.
|
96
|
-
(See `run_sequentially` for formatting details).
|
97
|
-
max_workers: The maximum number of worker threads. If None, it defaults
|
98
|
-
to ThreadPoolExecutor's default (typically based on CPU cores).
|
99
|
-
task_timeout: The maximum number of seconds to wait for each individual task
|
100
|
-
to complete. If a task exceeds this timeout, a
|
101
|
-
concurrent.futures.TimeoutError will be stored as its result.
|
102
|
-
If None, tasks will wait indefinitely for completion.
|
103
|
-
|
104
|
-
Returns:
|
105
|
-
A list where each element corresponds to the respective item in args_list.
|
106
|
-
- If a task executed successfully, its return value (R_Out) is stored.
|
107
|
-
- If a task raised an exception (including TimeoutError due to task_timeout),
|
108
|
-
the exception object itself is stored.
|
109
|
-
"""
|
110
|
-
# Materialize args_list to ensure consistent ordering and count, especially if it's a generator.
|
111
|
-
materialized_args_list = list(args_list)
|
112
|
-
if not materialized_args_list:
|
113
|
-
return []
|
114
|
-
|
115
|
-
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
|
116
|
-
futures: List[concurrent.futures.Future] = []
|
117
|
-
for args_item in materialized_args_list:
|
118
|
-
if isinstance(args_item, tuple):
|
119
|
-
future = executor.submit(func, *args_item)
|
120
|
-
else:
|
121
|
-
future = executor.submit(func, args_item)
|
122
|
-
futures.append(future)
|
123
|
-
|
124
|
-
# Initialize results list. Type ignore is used because None is a placeholder.
|
125
|
-
results: List[Union[R_Out, Exception]] = [None] * len(futures) # type: ignore
|
126
|
-
for i, future in enumerate(futures):
|
127
|
-
try:
|
128
|
-
results[i] = future.result(timeout=task_timeout)
|
129
|
-
except Exception as e: # Catches TimeoutError from future.result and any exception from func
|
130
|
-
results[i] = e
|
131
|
-
return results
|
132
|
-
|
133
|
-
|
134
|
-
def sequentialize():
|
135
|
-
"""
|
136
|
-
Decorator to make a function that processes a single item (or argument set)
|
137
|
-
able to process an iterable of items (or argument sets) sequentially.
|
138
|
-
|
139
|
-
The decorated function will expect an iterable of argument sets as its
|
140
|
-
primary argument and will return a list of results. If the underlying
|
141
|
-
function raises an error, execution stops and the error propagates.
|
142
|
-
|
143
|
-
Example:
|
144
|
-
@sequentialize()
|
145
|
-
def process_single(data, factor):
|
146
|
-
return data * factor
|
147
|
-
|
148
|
-
# Now call it with a list of argument tuples
|
149
|
-
results = process_single([(1, 2), (3, 4)])
|
150
|
-
# results will be [2, 12]
|
151
|
-
"""
|
152
|
-
|
153
|
-
def decorator(
|
154
|
-
func_to_process_single_item: Callable[..., R_Out],
|
155
|
-
) -> Callable[[Iterable[SingleTaskArgs]], List[R_Out]]:
|
156
|
-
@functools.wraps(func_to_process_single_item)
|
157
|
-
def wrapper(args_list_for_func: Iterable[SingleTaskArgs]) -> List[R_Out]:
|
158
|
-
return run_sequentially(func_to_process_single_item, args_list_for_func)
|
159
|
-
|
160
|
-
return wrapper
|
161
|
-
|
162
|
-
return decorator
|
163
|
-
|
164
|
-
|
165
|
-
def parallelize(
|
166
|
-
max_workers: Optional[int] = None, task_timeout: Optional[float] = None
|
167
|
-
):
|
168
|
-
"""
|
169
|
-
Decorator to make a function that processes a single item (or argument set)
|
170
|
-
able to process an iterable of items (or argument sets) in parallel.
|
171
|
-
|
172
|
-
The decorated function will expect an iterable of argument sets as its
|
173
|
-
primary argument and will return a list of results or exceptions,
|
174
|
-
maintaining the original order.
|
175
|
-
|
176
|
-
Args:
|
177
|
-
max_workers (Optional[int]): Max worker threads for parallel execution.
|
178
|
-
task_timeout (Optional[float]): Timeout for each individual task.
|
179
|
-
|
180
|
-
Example:
|
181
|
-
@parallelize(max_workers=4, task_timeout=5.0)
|
182
|
-
def fetch_url_content(url: str) -> str:
|
183
|
-
# ... implementation to fetch url ...
|
184
|
-
return "content"
|
185
|
-
|
186
|
-
# Now call it with a list of URLs
|
187
|
-
results = fetch_url_content(["http://example.com", "http://example.org"])
|
188
|
-
# results will be a list of contents or Exception objects.
|
189
|
-
"""
|
190
|
-
|
191
|
-
def decorator(
|
192
|
-
func_to_process_single_item: Callable[..., R_Out],
|
193
|
-
) -> Callable[[Iterable[SingleTaskArgs]], List[Union[R_Out, Exception]]]:
|
194
|
-
@functools.wraps(func_to_process_single_item)
|
195
|
-
def wrapper(
|
196
|
-
args_list_for_func: Iterable[SingleTaskArgs],
|
197
|
-
) -> List[Union[R_Out, Exception]]:
|
198
|
-
return run_parallel(
|
199
|
-
func_to_process_single_item,
|
200
|
-
args_list_for_func,
|
201
|
-
max_workers=max_workers,
|
202
|
-
task_timeout=task_timeout,
|
203
|
-
)
|
204
|
-
|
205
|
-
return wrapper
|
206
|
-
|
207
|
-
return decorator
|
208
|
-
|
209
|
-
|
210
|
-
def typed_batch():
|
211
|
-
"""
|
212
|
-
Decorator that provides better IDE type hinting for functions converted from
|
213
|
-
single-item to batch processing. This helps IDEs understand the transformation
|
214
|
-
and provide accurate autocomplete and type checking.
|
215
|
-
|
216
|
-
The decorated function maintains proper type information showing it transforms
|
217
|
-
from Callable[[T], R] to Callable[[Iterable[T]], List[R]].
|
218
|
-
|
219
|
-
Example:
|
220
|
-
@typed_batch()
|
221
|
-
def process_url(url: str) -> dict:
|
222
|
-
return {"url": url, "status": "ok"}
|
223
|
-
|
224
|
-
# IDE will now correctly understand:
|
225
|
-
# process_url: (Iterable[str]) -> List[dict]
|
226
|
-
results = process_url(["http://example.com", "http://test.com"])
|
227
|
-
"""
|
228
|
-
|
229
|
-
def decorator(
|
230
|
-
func: Callable[..., R_Out],
|
231
|
-
) -> Callable[[Iterable[SingleTaskArgs]], List[R_Out]]:
|
232
|
-
@functools.wraps(func)
|
233
|
-
def wrapper(args_list: Iterable[SingleTaskArgs]) -> List[R_Out]:
|
234
|
-
return run_sequentially(func, args_list)
|
235
|
-
|
236
|
-
# Preserve original function's type info while updating signature
|
237
|
-
wrapper.__annotations__ = {
|
238
|
-
"args_list": Iterable[SingleTaskArgs],
|
239
|
-
"return": List[R_Out],
|
240
|
-
}
|
241
|
-
|
242
|
-
return cast(Callable[[Iterable[SingleTaskArgs]], List[R_Out]], wrapper)
|
243
|
-
|
244
|
-
return decorator
|
245
|
-
|
246
|
-
|
247
|
-
def run_with_retry(
|
248
|
-
max_attempts: int = 3,
|
249
|
-
delay: float = 1.0,
|
250
|
-
backoff: float = 2.0,
|
251
|
-
exceptions: Optional[Tuple[Type[Exception], ...]] = None,
|
252
|
-
):
|
253
|
-
"""
|
254
|
-
Decorator that adds retry logic to functions. Essential for robust parallel
|
255
|
-
processing when dealing with network calls, database operations, or other
|
256
|
-
operations that might fail transiently.
|
257
|
-
|
258
|
-
Args:
|
259
|
-
max_attempts: Maximum number of attempts (including the first try).
|
260
|
-
delay: Initial delay between retries in seconds.
|
261
|
-
backoff: Multiplier for delay after each failed attempt.
|
262
|
-
exceptions: Tuple of exception types to retry on. If None, retries on all exceptions.
|
263
|
-
|
264
|
-
Example:
|
265
|
-
@with_retry(max_attempts=3, delay=0.5, backoff=2.0, exceptions=(ConnectionError, TimeoutError))
|
266
|
-
def fetch_data(url: str) -> dict:
|
267
|
-
# This will retry up to 3 times with exponential backoff
|
268
|
-
# only for ConnectionError and TimeoutError
|
269
|
-
return requests.get(url).json()
|
270
|
-
|
271
|
-
@parallelize(max_workers=10)
|
272
|
-
@with_retry(max_attempts=2)
|
273
|
-
def robust_fetch(url: str) -> str:
|
274
|
-
return fetch_url_content(url)
|
275
|
-
"""
|
276
|
-
if exceptions is None:
|
277
|
-
exceptions = (Exception,)
|
278
|
-
|
279
|
-
def decorator(func: Callable[..., R_Out]) -> Callable[..., R_Out]:
|
280
|
-
@functools.wraps(func)
|
281
|
-
def wrapper(*args, **kwargs) -> R_Out:
|
282
|
-
last_exception = None
|
283
|
-
current_delay = delay
|
284
|
-
|
285
|
-
for attempt in range(max_attempts):
|
286
|
-
try:
|
287
|
-
return func(*args, **kwargs)
|
288
|
-
except exceptions as e:
|
289
|
-
last_exception = e
|
290
|
-
if attempt == max_attempts - 1: # Last attempt
|
291
|
-
break
|
292
|
-
|
293
|
-
print(
|
294
|
-
f"Attempt {attempt + 1} failed for {func.__name__}: {e}. Retrying in {current_delay:.2f}s..."
|
295
|
-
)
|
296
|
-
time.sleep(current_delay)
|
297
|
-
current_delay *= backoff
|
298
|
-
|
299
|
-
# If we get here, all attempts failed
|
300
|
-
raise last_exception
|
301
|
-
|
302
|
-
return wrapper
|
303
|
-
|
304
|
-
return decorator
|
hammad/py.typed
DELETED
File without changes
|
hammad/pydantic/__init__.py
DELETED
@@ -1,43 +0,0 @@
|
|
1
|
-
"""hammad.pydantic
|
2
|
-
|
3
|
-
Contains both models and pydantic **specific** utiltiies / resources
|
4
|
-
meant for general case usage."""
|
5
|
-
|
6
|
-
from typing import TYPE_CHECKING
|
7
|
-
from .._core._utils._import_utils import _auto_create_getattr_loader
|
8
|
-
|
9
|
-
if TYPE_CHECKING:
|
10
|
-
from .converters import (
|
11
|
-
convert_to_pydantic_model,
|
12
|
-
convert_to_pydantic_field,
|
13
|
-
create_confirmation_pydantic_model,
|
14
|
-
create_selection_pydantic_model,
|
15
|
-
)
|
16
|
-
from .models import (
|
17
|
-
FastModel,
|
18
|
-
FunctionModel,
|
19
|
-
ArbitraryModel,
|
20
|
-
CacheableModel,
|
21
|
-
SubscriptableModel,
|
22
|
-
)
|
23
|
-
|
24
|
-
|
25
|
-
__all__ = (
|
26
|
-
"convert_to_pydantic_model",
|
27
|
-
"convert_to_pydantic_field",
|
28
|
-
"create_confirmation_pydantic_model",
|
29
|
-
"create_selection_pydantic_model",
|
30
|
-
"FastModel",
|
31
|
-
"FunctionModel",
|
32
|
-
"ArbitraryModel",
|
33
|
-
"CacheableModel",
|
34
|
-
"SubscriptableModel",
|
35
|
-
)
|
36
|
-
|
37
|
-
|
38
|
-
__getattr__ = _auto_create_getattr_loader(__all__)
|
39
|
-
|
40
|
-
|
41
|
-
def __dir__() -> list[str]:
|
42
|
-
"""Get the attributes of the pydantic module."""
|
43
|
-
return list(__all__)
|