hammad-python 0.0.12__py3-none-any.whl → 0.0.14__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 (78) hide show
  1. hammad/__init__.py +1 -180
  2. hammad/ai/__init__.py +0 -58
  3. hammad/ai/completions/__init__.py +3 -2
  4. hammad/ai/completions/client.py +84 -129
  5. hammad/ai/completions/create.py +33 -9
  6. hammad/ai/completions/settings.py +100 -0
  7. hammad/ai/completions/types.py +86 -5
  8. hammad/ai/completions/utils.py +112 -0
  9. hammad/ai/embeddings/__init__.py +2 -2
  10. hammad/ai/embeddings/client/fastembed_text_embeddings_client.py +1 -1
  11. hammad/ai/embeddings/client/litellm_embeddings_client.py +1 -1
  12. hammad/ai/embeddings/types.py +4 -4
  13. hammad/cache/__init__.py +13 -21
  14. hammad/cli/__init__.py +2 -2
  15. hammad/cli/animations.py +8 -39
  16. hammad/cli/styles/__init__.py +2 -2
  17. hammad/data/__init__.py +19 -2
  18. hammad/data/collections/__init__.py +2 -2
  19. hammad/data/collections/vector_collection.py +0 -7
  20. hammad/{configuration → data/configurations}/__init__.py +2 -2
  21. hammad/{configuration → data/configurations}/configuration.py +1 -1
  22. hammad/data/databases/__init__.py +2 -2
  23. hammad/data/models/__init__.py +44 -0
  24. hammad/{base → data/models/base}/__init__.py +3 -3
  25. hammad/{pydantic → data/models/pydantic}/__init__.py +28 -16
  26. hammad/{pydantic → data/models/pydantic}/converters.py +11 -2
  27. hammad/{pydantic → data/models/pydantic}/models/__init__.py +3 -3
  28. hammad/{pydantic → data/models/pydantic}/models/arbitrary_model.py +1 -1
  29. hammad/{pydantic → data/models/pydantic}/models/cacheable_model.py +1 -1
  30. hammad/{pydantic → data/models/pydantic}/models/fast_model.py +1 -1
  31. hammad/{pydantic → data/models/pydantic}/models/function_model.py +1 -1
  32. hammad/{pydantic → data/models/pydantic}/models/subscriptable_model.py +1 -1
  33. hammad/data/types/__init__.py +41 -0
  34. hammad/{types → data/types}/file.py +2 -2
  35. hammad/{multimodal → data/types/multimodal}/__init__.py +2 -2
  36. hammad/{multimodal → data/types/multimodal}/audio.py +2 -2
  37. hammad/{multimodal → data/types/multimodal}/image.py +2 -2
  38. hammad/{text → data/types}/text.py +4 -4
  39. hammad/formatting/__init__.py +38 -0
  40. hammad/{json → formatting/json}/__init__.py +3 -3
  41. hammad/{json → formatting/json}/converters.py +2 -2
  42. hammad/{text → formatting/text}/__init__.py +5 -24
  43. hammad/{text → formatting/text}/converters.py +2 -2
  44. hammad/{text → formatting/text}/markdown.py +1 -1
  45. hammad/{yaml → formatting/yaml}/__init__.py +3 -7
  46. hammad/formatting/yaml/converters.py +5 -0
  47. hammad/logging/__init__.py +2 -2
  48. hammad/mcp/__init__.py +50 -0
  49. hammad/mcp/client/__init__.py +1 -0
  50. hammad/mcp/client/client.py +523 -0
  51. hammad/mcp/client/client_service.py +393 -0
  52. hammad/mcp/client/settings.py +178 -0
  53. hammad/mcp/servers/__init__.py +1 -0
  54. hammad/mcp/servers/launcher.py +1161 -0
  55. hammad/performance/__init__.py +36 -0
  56. hammad/{_core/_utils/_import_utils.py → performance/imports.py} +125 -76
  57. hammad/performance/runtime/__init__.py +32 -0
  58. hammad/performance/runtime/decorators.py +142 -0
  59. hammad/performance/runtime/run.py +299 -0
  60. hammad/service/__init__.py +49 -0
  61. hammad/service/create.py +532 -0
  62. hammad/service/decorators.py +285 -0
  63. hammad/web/__init__.py +2 -2
  64. hammad/web/http/client.py +1 -1
  65. hammad/web/openapi/__init__.py +1 -0
  66. {hammad_python-0.0.12.dist-info → hammad_python-0.0.14.dist-info}/METADATA +35 -3
  67. hammad_python-0.0.14.dist-info/RECORD +99 -0
  68. hammad/_core/__init__.py +0 -1
  69. hammad/_core/_utils/__init__.py +0 -4
  70. hammad/multithreading/__init__.py +0 -304
  71. hammad/types/__init__.py +0 -11
  72. hammad/yaml/converters.py +0 -19
  73. hammad_python-0.0.12.dist-info/RECORD +0 -85
  74. /hammad/{base → data/models/base}/fields.py +0 -0
  75. /hammad/{base → data/models/base}/model.py +0 -0
  76. /hammad/{base → data/models/base}/utils.py +0 -0
  77. {hammad_python-0.0.12.dist-info → hammad_python-0.0.14.dist-info}/WHEEL +0 -0
  78. {hammad_python-0.0.12.dist-info → hammad_python-0.0.14.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,299 @@
1
+ """hammad.performance.runtime.run"""
2
+
3
+ import concurrent.futures
4
+ import itertools
5
+ import functools
6
+ from typing import (
7
+ Callable,
8
+ Iterable,
9
+ List,
10
+ Any,
11
+ TypeVar,
12
+ Tuple,
13
+ Optional,
14
+ Union,
15
+ Type,
16
+ overload,
17
+ )
18
+
19
+ from tenacity import (
20
+ retry,
21
+ stop_after_attempt,
22
+ wait_exponential,
23
+ retry_if_exception_type,
24
+ retry_if_exception,
25
+ )
26
+
27
+
28
+ __all__ = (
29
+ "run_sequentially",
30
+ "run_parallel",
31
+ "run_with_retry",
32
+ )
33
+
34
+
35
+ Parameters = TypeVar("Parameters", bound=dict[str, Any])
36
+ Return = TypeVar("Return")
37
+
38
+ TaskParameters = TypeVar("TaskParameters", bound=dict[str, Any])
39
+
40
+
41
+ def run_sequentially(
42
+ function: Callable[..., Return],
43
+ parameters: Iterable[Parameters],
44
+ raise_on_error: bool = False,
45
+ ) -> List[Return]:
46
+ """Executes a function multiple times sequentially, using a
47
+ list of given parameter dictionary definitions.
48
+
49
+ If the function raised an exception at any point during
50
+ the call, by default the exception will be propogated/ignored
51
+ and the run will continue, unless the `raise_on_error` flag is
52
+ set to `True`.
53
+
54
+ Args:
55
+ function : The function to execute.
56
+ parameters : An iterable of parameter dictionaries to pass to the function.
57
+ raise_on_error : Whether to raise an exception if the function raises an exception.
58
+
59
+ Returns:
60
+ A list of results from the function calls."""
61
+ results: List[Return] = []
62
+
63
+ def execute_single_task(params: Parameters) -> Optional[Return]:
64
+ """Execute a single task with error handling."""
65
+ try:
66
+ if isinstance(params, dict):
67
+ return function(**params)
68
+ else:
69
+ # Handle case where params might be a single argument or tuple
70
+ if isinstance(params, tuple):
71
+ return function(*params)
72
+ else:
73
+ return function(params)
74
+ except Exception as e:
75
+ if raise_on_error:
76
+ raise
77
+ return None
78
+
79
+ for params in itertools.chain(parameters):
80
+ result = execute_single_task(params)
81
+ if result is not None:
82
+ results.append(result)
83
+
84
+ return results
85
+
86
+
87
+ def run_parallel(
88
+ function: Callable[..., Return],
89
+ parameters: Iterable[Parameters],
90
+ max_workers: Optional[int] = None,
91
+ timeout: Optional[float] = None,
92
+ raise_on_error: bool = False,
93
+ ) -> List[Union[Return, Exception]]:
94
+ """Executes a function multiple times in parallel, using a
95
+ list of given parameter dictionary definitions.
96
+
97
+ Uses ThreadPoolExecutor to run tasks concurrently. Results are returned
98
+ in the same order as the input parameters.
99
+
100
+ Args:
101
+ function : The function to execute.
102
+ parameters : An iterable of parameter dictionaries to pass to the function.
103
+ max_workers : The maximum number of worker threads. If None, defaults
104
+ to ThreadPoolExecutor's default (typically based on CPU cores).
105
+ timeout : The maximum number of seconds to wait for each individual task
106
+ to complete. If a task exceeds this timeout, a
107
+ concurrent.futures.TimeoutError will be stored as its result.
108
+ If None, tasks will wait indefinitely for completion.
109
+ raise_on_error : Whether to raise an exception if the function raises an exception.
110
+ If False, exceptions are returned as results instead of being raised.
111
+
112
+ Returns:
113
+ A list where each element corresponds to the respective item in parameters.
114
+ - If a task executed successfully, its return value is stored.
115
+ - If a task raised an exception (including TimeoutError due to timeout),
116
+ the exception object itself is stored (unless raise_on_error is True).
117
+ """
118
+ # Materialize parameters to ensure consistent ordering and count
119
+ materialized_params = list(parameters)
120
+ if not materialized_params:
121
+ return []
122
+
123
+ with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
124
+ futures: List[concurrent.futures.Future] = []
125
+
126
+ # Submit all tasks
127
+ for params in materialized_params:
128
+ if isinstance(params, dict):
129
+ future = executor.submit(function, **params)
130
+ elif isinstance(params, tuple):
131
+ future = executor.submit(function, *params)
132
+ else:
133
+ future = executor.submit(function, params)
134
+ futures.append(future)
135
+
136
+ # Collect results in order
137
+ results: List[Union[Return, Exception]] = [None] * len(futures) # type: ignore
138
+ for i, future in enumerate(futures):
139
+ try:
140
+ results[i] = future.result(timeout=timeout)
141
+ except Exception as e:
142
+ if raise_on_error:
143
+ raise
144
+ results[i] = e
145
+
146
+ return results
147
+
148
+
149
+ @overload
150
+ def run_with_retry(
151
+ func: Callable[..., Return],
152
+ *,
153
+ max_attempts: int = 3,
154
+ initial_delay: float = 1.0,
155
+ max_delay: float = 60.0,
156
+ backoff: float = 2.0,
157
+ jitter: Optional[float] = None,
158
+ exceptions: Optional[Tuple[Type[Exception], ...]] = None,
159
+ reraise: bool = True,
160
+ before_retry: Optional[Callable[[Exception], None]] = None,
161
+ hook: Optional[Callable[[Exception, dict, dict], Tuple[dict, dict]]] = None,
162
+ ) -> Callable[..., Return]: ...
163
+
164
+
165
+ @overload
166
+ def run_with_retry(
167
+ *,
168
+ max_attempts: int = 3,
169
+ initial_delay: float = 1.0,
170
+ max_delay: float = 60.0,
171
+ backoff: float = 2.0,
172
+ jitter: Optional[float] = None,
173
+ exceptions: Optional[Tuple[Type[Exception], ...]] = None,
174
+ reraise: bool = True,
175
+ before_retry: Optional[Callable[[Exception], None]] = None,
176
+ hook: Optional[Callable[[Exception, dict, dict], Tuple[dict, dict]]] = None,
177
+ ) -> Callable[[Callable[..., Return]], Callable[..., Return]]: ...
178
+
179
+
180
+ def run_with_retry(
181
+ func: Optional[Callable[..., Return]] = None,
182
+ *,
183
+ max_attempts: int = 3,
184
+ initial_delay: float = 1.0,
185
+ max_delay: float = 60.0,
186
+ backoff: float = 2.0,
187
+ jitter: Optional[float] = None,
188
+ exceptions: Optional[Tuple[Type[Exception], ...]] = None,
189
+ reraise: bool = True,
190
+ before_retry: Optional[Callable[[Exception], None]] = None,
191
+ hook: Optional[Callable[[Exception, dict, dict], Tuple[dict, dict]]] = None,
192
+ ) -> Union[
193
+ Callable[..., Return], Callable[[Callable[..., Return]], Callable[..., Return]]
194
+ ]:
195
+ """
196
+ Decorator that adds retry logic to functions using tenacity. Essential for robust parallel
197
+ processing when dealing with network calls, database operations, or other
198
+ operations that might fail transiently.
199
+
200
+ Can be used either as a decorator or as a function that takes a function as first argument.
201
+
202
+ Args:
203
+ func: The function to decorate (when used directly rather than as a decorator)
204
+ max_attempts: Maximum number of attempts (including the first try).
205
+ initial_delay: Initial delay between retries in seconds.
206
+ max_delay: Maximum delay between retries in seconds.
207
+ backoff: Multiplier for delay after each failed attempt.
208
+ jitter: If set, adds random jitter to delays between retries.
209
+ exceptions: Tuple of exception types to retry on. If None, retries on all exceptions.
210
+ reraise: Whether to reraise the last exception after all retries fail.
211
+ before_retry: Optional callback function to execute before each retry attempt.
212
+ Takes the exception as argument.
213
+ hook: Optional function to modify args/kwargs before retry.
214
+ Takes (exception, current_args_dict, current_kwargs_dict) and
215
+ returns (new_args_dict, new_kwargs_dict).
216
+
217
+ Example:
218
+ # As a decorator:
219
+ @run_with_retry(
220
+ max_attempts=3,
221
+ initial_delay=0.5,
222
+ max_delay=5.0,
223
+ backoff=2.0,
224
+ exceptions=(ConnectionError, TimeoutError),
225
+ )
226
+ def fetch_data(url: str, timeout: int = 30) -> dict:
227
+ return requests.get(url, timeout=timeout).json()
228
+
229
+ # As a function:
230
+ def fetch_data(url: str, timeout: int = 30) -> dict:
231
+ return requests.get(url, timeout=timeout).json()
232
+
233
+ fetch_with_retry = run_with_retry(fetch_data, max_attempts=3)
234
+ """
235
+
236
+ def decorator(f: Callable[..., Return]) -> Callable[..., Return]:
237
+ # Create retry configuration
238
+ wait_strategy = wait_exponential(
239
+ multiplier=initial_delay,
240
+ exp_base=backoff,
241
+ max=max_delay,
242
+ )
243
+
244
+ # Build retry arguments
245
+ retry_args = {
246
+ "stop": stop_after_attempt(max_attempts),
247
+ "wait": wait_strategy,
248
+ "retry": retry_if_exception_type(exceptions)
249
+ if exceptions
250
+ else retry_if_exception(lambda e: True),
251
+ "reraise": reraise,
252
+ }
253
+
254
+ if before_retry or hook:
255
+ # We need a stateful wrapper to handle callbacks with hooks
256
+ @functools.wraps(f)
257
+ def wrapper(*args, **kwargs) -> Return:
258
+ # Store current args/kwargs that can be modified by hook
259
+ current_args = args
260
+ current_kwargs = kwargs
261
+
262
+ def before_sleep_callback(retry_state):
263
+ nonlocal current_args, current_kwargs
264
+
265
+ # Only process if there was an exception
266
+ if retry_state.outcome and retry_state.outcome.failed:
267
+ exc = retry_state.outcome.exception()
268
+
269
+ if before_retry:
270
+ before_retry(exc)
271
+
272
+ if hook:
273
+ # Convert args to dict for hook
274
+ args_dict = dict(enumerate(current_args))
275
+ # Call hook to potentially modify arguments
276
+ new_args_dict, new_kwargs = hook(
277
+ exc, args_dict, current_kwargs
278
+ )
279
+ # Convert back to args tuple
280
+ current_args = tuple(
281
+ new_args_dict[i] for i in range(len(new_args_dict))
282
+ )
283
+ current_kwargs = new_kwargs
284
+
285
+ # Create a wrapped function that uses the current args/kwargs
286
+ @retry(**retry_args, before_sleep=before_sleep_callback)
287
+ def retryable_func():
288
+ return f(*current_args, **current_kwargs)
289
+
290
+ return retryable_func()
291
+
292
+ return wrapper
293
+ else:
294
+ # Simple case without callbacks - use tenacity's retry decorator directly
295
+ return retry(**retry_args)(f)
296
+
297
+ if func is not None:
298
+ return decorator(func)
299
+ return decorator
@@ -0,0 +1,49 @@
1
+ """hammad.service
2
+
3
+ An optional extension to the `hammad-python` package, installable with:
4
+
5
+ ```bash
6
+ pip install hammad-python[service]
7
+ ```
8
+
9
+ TLDR: FastAPI is already so gosh darn simple, theres no need for server/client
10
+ resources within this submodule. This module contains function/decorators for:
11
+
12
+ - `@serve` - Easily launch functions as a FastAPI endpoint within a quick server.
13
+ - `@serve_mcp` - Serve functions as MCP (Model Context Protocol) server tools.
14
+ - `create_service` - Launch a FastAPI server from:
15
+ - A function
16
+ - A model-like object
17
+ - Pydantic models
18
+ - Dataclasses
19
+ - `hammad.base.model.Model`
20
+ - msgspec.Struct
21
+ """
22
+
23
+ from typing import TYPE_CHECKING
24
+ from ..performance.imports import create_getattr_importer
25
+
26
+ if TYPE_CHECKING:
27
+ from .create import (
28
+ create_service,
29
+ async_create_service,
30
+ )
31
+ from .decorators import serve, serve_mcp
32
+
33
+
34
+ __all__ = (
35
+ # hammad.service.create
36
+ "create_service",
37
+ "async_create_service",
38
+ # hammad.service.decorators
39
+ "serve",
40
+ "serve_mcp",
41
+ )
42
+
43
+
44
+ __getattr__ = create_getattr_importer(__all__)
45
+
46
+
47
+ def __dir__() -> list[str]:
48
+ """Get the attributes of the create and decorators modules."""
49
+ return list(__all__)