hammad-python 0.0.29__py3-none-any.whl → 0.0.31__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ham/__init__.py +10 -0
- {hammad_python-0.0.29.dist-info → hammad_python-0.0.31.dist-info}/METADATA +6 -32
- hammad_python-0.0.31.dist-info/RECORD +6 -0
- hammad/__init__.py +0 -84
- hammad/_internal.py +0 -256
- hammad/_main.py +0 -226
- hammad/cache/__init__.py +0 -40
- hammad/cache/base_cache.py +0 -181
- hammad/cache/cache.py +0 -169
- hammad/cache/decorators.py +0 -261
- hammad/cache/file_cache.py +0 -80
- hammad/cache/ttl_cache.py +0 -74
- hammad/cli/__init__.py +0 -33
- hammad/cli/animations.py +0 -573
- hammad/cli/plugins.py +0 -867
- hammad/cli/styles/__init__.py +0 -55
- hammad/cli/styles/settings.py +0 -139
- hammad/cli/styles/types.py +0 -358
- hammad/cli/styles/utils.py +0 -634
- hammad/data/__init__.py +0 -90
- hammad/data/collections/__init__.py +0 -49
- hammad/data/collections/collection.py +0 -326
- hammad/data/collections/indexes/__init__.py +0 -37
- hammad/data/collections/indexes/qdrant/__init__.py +0 -1
- hammad/data/collections/indexes/qdrant/index.py +0 -723
- hammad/data/collections/indexes/qdrant/settings.py +0 -94
- hammad/data/collections/indexes/qdrant/utils.py +0 -210
- hammad/data/collections/indexes/tantivy/__init__.py +0 -1
- hammad/data/collections/indexes/tantivy/index.py +0 -426
- hammad/data/collections/indexes/tantivy/settings.py +0 -40
- hammad/data/collections/indexes/tantivy/utils.py +0 -176
- hammad/data/configurations/__init__.py +0 -35
- hammad/data/configurations/configuration.py +0 -564
- hammad/data/models/__init__.py +0 -50
- hammad/data/models/extensions/__init__.py +0 -4
- hammad/data/models/extensions/pydantic/__init__.py +0 -42
- hammad/data/models/extensions/pydantic/converters.py +0 -759
- hammad/data/models/fields.py +0 -546
- hammad/data/models/model.py +0 -1078
- hammad/data/models/utils.py +0 -280
- hammad/data/sql/__init__.py +0 -24
- hammad/data/sql/database.py +0 -576
- hammad/data/sql/types.py +0 -127
- hammad/data/types/__init__.py +0 -75
- hammad/data/types/file.py +0 -431
- hammad/data/types/multimodal/__init__.py +0 -36
- hammad/data/types/multimodal/audio.py +0 -200
- hammad/data/types/multimodal/image.py +0 -182
- hammad/data/types/text.py +0 -1308
- hammad/formatting/__init__.py +0 -33
- hammad/formatting/json/__init__.py +0 -27
- hammad/formatting/json/converters.py +0 -158
- hammad/formatting/text/__init__.py +0 -63
- hammad/formatting/text/converters.py +0 -723
- hammad/formatting/text/markdown.py +0 -131
- hammad/formatting/yaml/__init__.py +0 -26
- hammad/formatting/yaml/converters.py +0 -5
- hammad/genai/__init__.py +0 -217
- hammad/genai/a2a/__init__.py +0 -32
- hammad/genai/a2a/workers.py +0 -552
- hammad/genai/agents/__init__.py +0 -59
- hammad/genai/agents/agent.py +0 -1973
- hammad/genai/agents/run.py +0 -1024
- hammad/genai/agents/types/__init__.py +0 -42
- hammad/genai/agents/types/agent_context.py +0 -13
- hammad/genai/agents/types/agent_event.py +0 -128
- hammad/genai/agents/types/agent_hooks.py +0 -220
- hammad/genai/agents/types/agent_messages.py +0 -31
- hammad/genai/agents/types/agent_response.py +0 -125
- hammad/genai/agents/types/agent_stream.py +0 -327
- hammad/genai/graphs/__init__.py +0 -125
- hammad/genai/graphs/_utils.py +0 -190
- hammad/genai/graphs/base.py +0 -1828
- hammad/genai/graphs/plugins.py +0 -316
- hammad/genai/graphs/types.py +0 -638
- hammad/genai/models/__init__.py +0 -1
- hammad/genai/models/embeddings/__init__.py +0 -43
- hammad/genai/models/embeddings/model.py +0 -226
- hammad/genai/models/embeddings/run.py +0 -163
- hammad/genai/models/embeddings/types/__init__.py +0 -37
- hammad/genai/models/embeddings/types/embedding_model_name.py +0 -75
- hammad/genai/models/embeddings/types/embedding_model_response.py +0 -76
- hammad/genai/models/embeddings/types/embedding_model_run_params.py +0 -66
- hammad/genai/models/embeddings/types/embedding_model_settings.py +0 -47
- hammad/genai/models/language/__init__.py +0 -57
- hammad/genai/models/language/model.py +0 -1098
- hammad/genai/models/language/run.py +0 -878
- hammad/genai/models/language/types/__init__.py +0 -40
- hammad/genai/models/language/types/language_model_instructor_mode.py +0 -47
- hammad/genai/models/language/types/language_model_messages.py +0 -28
- hammad/genai/models/language/types/language_model_name.py +0 -239
- hammad/genai/models/language/types/language_model_request.py +0 -127
- hammad/genai/models/language/types/language_model_response.py +0 -217
- hammad/genai/models/language/types/language_model_response_chunk.py +0 -56
- hammad/genai/models/language/types/language_model_settings.py +0 -89
- hammad/genai/models/language/types/language_model_stream.py +0 -600
- hammad/genai/models/language/utils/__init__.py +0 -28
- hammad/genai/models/language/utils/requests.py +0 -421
- hammad/genai/models/language/utils/structured_outputs.py +0 -135
- hammad/genai/models/model_provider.py +0 -4
- hammad/genai/models/multimodal.py +0 -47
- hammad/genai/models/reranking.py +0 -26
- hammad/genai/types/__init__.py +0 -1
- hammad/genai/types/base.py +0 -215
- hammad/genai/types/history.py +0 -290
- hammad/genai/types/tools.py +0 -507
- hammad/logging/__init__.py +0 -35
- hammad/logging/decorators.py +0 -834
- hammad/logging/logger.py +0 -1018
- hammad/mcp/__init__.py +0 -53
- hammad/mcp/client/__init__.py +0 -35
- hammad/mcp/client/client.py +0 -624
- hammad/mcp/client/client_service.py +0 -400
- hammad/mcp/client/settings.py +0 -178
- hammad/mcp/servers/__init__.py +0 -26
- hammad/mcp/servers/launcher.py +0 -1161
- hammad/runtime/__init__.py +0 -32
- hammad/runtime/decorators.py +0 -142
- hammad/runtime/run.py +0 -299
- hammad/service/__init__.py +0 -49
- hammad/service/create.py +0 -527
- hammad/service/decorators.py +0 -283
- hammad/types.py +0 -288
- hammad/typing/__init__.py +0 -435
- hammad/web/__init__.py +0 -43
- hammad/web/http/__init__.py +0 -1
- hammad/web/http/client.py +0 -944
- hammad/web/models.py +0 -275
- hammad/web/openapi/__init__.py +0 -1
- hammad/web/openapi/client.py +0 -740
- hammad/web/search/__init__.py +0 -1
- hammad/web/search/client.py +0 -1023
- hammad/web/utils.py +0 -472
- hammad_python-0.0.29.dist-info/RECORD +0 -135
- {hammad → ham}/py.typed +0 -0
- {hammad_python-0.0.29.dist-info → hammad_python-0.0.31.dist-info}/WHEEL +0 -0
- {hammad_python-0.0.29.dist-info → hammad_python-0.0.31.dist-info}/licenses/LICENSE +0 -0
hammad/logging/decorators.py
DELETED
@@ -1,834 +0,0 @@
|
|
1
|
-
"""hammad.logging.decorators"""
|
2
|
-
|
3
|
-
from functools import wraps
|
4
|
-
from typing import (
|
5
|
-
Any,
|
6
|
-
Callable,
|
7
|
-
ParamSpec,
|
8
|
-
TypeVar,
|
9
|
-
List,
|
10
|
-
overload,
|
11
|
-
Union,
|
12
|
-
Type,
|
13
|
-
Optional,
|
14
|
-
Awaitable,
|
15
|
-
)
|
16
|
-
import asyncio
|
17
|
-
|
18
|
-
import logging
|
19
|
-
import time
|
20
|
-
import inspect
|
21
|
-
from .logger import Logger, create_logger, LoggerLevelName
|
22
|
-
from ..cli.styles.types import (
|
23
|
-
CLIStyleType,
|
24
|
-
CLIStyleBackgroundType,
|
25
|
-
)
|
26
|
-
|
27
|
-
|
28
|
-
_P = ParamSpec("_P")
|
29
|
-
_R = TypeVar("_R")
|
30
|
-
|
31
|
-
__all__ = (
|
32
|
-
"trace_function",
|
33
|
-
"trace_cls",
|
34
|
-
"trace",
|
35
|
-
"trace_http",
|
36
|
-
"install_trace_http",
|
37
|
-
)
|
38
|
-
|
39
|
-
|
40
|
-
def trace_function(
|
41
|
-
fn: Optional[Callable[_P, _R]] = None,
|
42
|
-
*,
|
43
|
-
parameters: List[str] = [],
|
44
|
-
logger: Union[logging.Logger, Logger, None] = None,
|
45
|
-
level: Union[LoggerLevelName, str, int] = "debug",
|
46
|
-
rich: bool = True,
|
47
|
-
style: Union[CLIStyleType, str] = "white",
|
48
|
-
bg: Union[CLIStyleBackgroundType, str] = None,
|
49
|
-
) -> Union[Callable[_P, _R], Callable[[Callable[_P, _R]], Callable[_P, _R]]]:
|
50
|
-
"""
|
51
|
-
Tracing decorator that logs the execution of any function, including
|
52
|
-
class methods.
|
53
|
-
|
54
|
-
You can optionally specify specific parameters, that will display
|
55
|
-
'live updates' of the parameter values as they change.
|
56
|
-
|
57
|
-
Args:
|
58
|
-
fn: The function to trace.
|
59
|
-
parameters: The parameters to trace.
|
60
|
-
logger: The logger to use.
|
61
|
-
level: The level to log at.
|
62
|
-
rich: Whether to use rich for the logging.
|
63
|
-
style: The style to use for the logging. This can be a string, or a dictionary
|
64
|
-
of style settings.
|
65
|
-
bg: The background to use for the logging. This can be a string, or a dictionary
|
66
|
-
of background settings.
|
67
|
-
|
68
|
-
Returns:
|
69
|
-
The decorated function or a decorator function.
|
70
|
-
"""
|
71
|
-
|
72
|
-
def decorator(target_fn: Callable[_P, _R]) -> Callable[_P, _R]:
|
73
|
-
@wraps(target_fn)
|
74
|
-
def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _R:
|
75
|
-
# Get or create logger
|
76
|
-
if logger is None:
|
77
|
-
_logger = create_logger(
|
78
|
-
name=f"trace.{target_fn.__module__}.{target_fn.__name__}",
|
79
|
-
level=level,
|
80
|
-
rich=rich,
|
81
|
-
)
|
82
|
-
elif isinstance(logger, Logger):
|
83
|
-
_logger = logger
|
84
|
-
else:
|
85
|
-
# It's a standard logging.Logger, wrap it
|
86
|
-
_logger = create_logger(name=logger.name)
|
87
|
-
_logger._logger = logger
|
88
|
-
|
89
|
-
# Get function signature for parameter tracking
|
90
|
-
sig = inspect.signature(target_fn)
|
91
|
-
bound_args = sig.bind(*args, **kwargs)
|
92
|
-
bound_args.apply_defaults()
|
93
|
-
|
94
|
-
# Build entry message
|
95
|
-
func_name = target_fn.__name__
|
96
|
-
module_name = target_fn.__module__
|
97
|
-
|
98
|
-
if rich and bg:
|
99
|
-
# Create a styled panel for function entry
|
100
|
-
entry_msg = f"[{style}]→ Entering {module_name}.{func_name}()[/{style}]"
|
101
|
-
else:
|
102
|
-
entry_msg = f"→ Entering {module_name}.{func_name}()"
|
103
|
-
|
104
|
-
# Log parameters if requested
|
105
|
-
if parameters:
|
106
|
-
param_info = []
|
107
|
-
for param in parameters:
|
108
|
-
if param in bound_args.arguments:
|
109
|
-
value = bound_args.arguments[param]
|
110
|
-
param_info.append(f"{param}={repr(value)}")
|
111
|
-
|
112
|
-
if param_info:
|
113
|
-
entry_msg += f"\n Parameters: {', '.join(param_info)}"
|
114
|
-
|
115
|
-
# Log function entry
|
116
|
-
_logger.log(level, entry_msg)
|
117
|
-
|
118
|
-
# Track execution time
|
119
|
-
start_time = time.time()
|
120
|
-
|
121
|
-
try:
|
122
|
-
# Execute the function
|
123
|
-
result = target_fn(*args, **kwargs)
|
124
|
-
|
125
|
-
# Calculate execution time
|
126
|
-
exec_time = time.time() - start_time
|
127
|
-
|
128
|
-
# Build exit message
|
129
|
-
if rich and bg:
|
130
|
-
exit_msg = f"[{style}]← Exiting {module_name}.{func_name}() [dim](took {exec_time:.3f}s)[/dim][/{style}]"
|
131
|
-
else:
|
132
|
-
exit_msg = (
|
133
|
-
f"← Exiting {module_name}.{func_name}() (took {exec_time:.3f}s)"
|
134
|
-
)
|
135
|
-
|
136
|
-
# Log the result if it's not None
|
137
|
-
if result is not None:
|
138
|
-
exit_msg += f"\n Result: {repr(result)}"
|
139
|
-
|
140
|
-
_logger.log(level, exit_msg)
|
141
|
-
|
142
|
-
return result
|
143
|
-
|
144
|
-
except Exception as e:
|
145
|
-
# Calculate execution time
|
146
|
-
exec_time = time.time() - start_time
|
147
|
-
|
148
|
-
# Build error message
|
149
|
-
error_style = "bold red" if rich else None
|
150
|
-
if rich:
|
151
|
-
error_msg = f"[{error_style}]✗ Exception in {module_name}.{func_name}() [dim](after {exec_time:.3f}s)[/dim][/{error_style}]"
|
152
|
-
error_msg += f"\n [red]{type(e).__name__}: {str(e)}[/red]"
|
153
|
-
else:
|
154
|
-
error_msg = f"✗ Exception in {module_name}.{func_name}() (after {exec_time:.3f}s)"
|
155
|
-
error_msg += f"\n {type(e).__name__}: {str(e)}"
|
156
|
-
|
157
|
-
# Log at error level for exceptions
|
158
|
-
_logger.error(error_msg)
|
159
|
-
|
160
|
-
# Re-raise the exception
|
161
|
-
raise
|
162
|
-
|
163
|
-
return wrapper
|
164
|
-
|
165
|
-
if fn is None:
|
166
|
-
# Called with parameters: @trace_function(parameters=["x"])
|
167
|
-
return decorator
|
168
|
-
else:
|
169
|
-
# Called directly: @trace_function
|
170
|
-
return decorator(fn)
|
171
|
-
|
172
|
-
|
173
|
-
def trace_cls(
|
174
|
-
cls: Optional[Type[Any]] = None,
|
175
|
-
*,
|
176
|
-
attributes: List[str] = [],
|
177
|
-
functions: List[str] = [],
|
178
|
-
logger: Union[logging.Logger, Logger, None] = None,
|
179
|
-
level: Union[LoggerLevelName, str, int] = "debug",
|
180
|
-
rich: bool = True,
|
181
|
-
style: Union[CLIStyleType, str] = "white",
|
182
|
-
bg: Union[CLIStyleBackgroundType, str] = None,
|
183
|
-
) -> Union[Type[Any], Callable[[Type[Any]], Type[Any]]]:
|
184
|
-
"""
|
185
|
-
Tracing decorator that logs the execution of any class, including
|
186
|
-
class methods.
|
187
|
-
|
188
|
-
Unlike the `trace_function` decorator, this decorator must take
|
189
|
-
in either a list of attributes, or a list of functions to display
|
190
|
-
'live updates' of the attribute values as they change.
|
191
|
-
|
192
|
-
Args:
|
193
|
-
cls: The class to trace.
|
194
|
-
attributes: The attributes to trace.
|
195
|
-
functions: The functions to trace.
|
196
|
-
logger: An optional logger to use.
|
197
|
-
level: An optional level to log at.
|
198
|
-
rich: Whether to use rich for the logging.
|
199
|
-
style: The style to use for the logging. This can be a string, or a dictionary
|
200
|
-
of style settings.
|
201
|
-
bg: The background to use for the logging. This can be a string, or a dictionary
|
202
|
-
of background settings.
|
203
|
-
|
204
|
-
Returns:
|
205
|
-
The traced class or a decorator function.
|
206
|
-
"""
|
207
|
-
|
208
|
-
def decorator(target_cls: Type[Any]) -> Type[Any]:
|
209
|
-
# Get or create logger for the class
|
210
|
-
if logger is None:
|
211
|
-
_logger = create_logger(
|
212
|
-
name=f"trace.{target_cls.__module__}.{target_cls.__name__}",
|
213
|
-
level=level,
|
214
|
-
rich=rich,
|
215
|
-
)
|
216
|
-
elif isinstance(logger, Logger):
|
217
|
-
_logger = logger
|
218
|
-
else:
|
219
|
-
# It's a standard logging.Logger, wrap it
|
220
|
-
_logger = create_logger(name=logger.name)
|
221
|
-
_logger._logger = logger
|
222
|
-
|
223
|
-
# Store original __init__ method
|
224
|
-
original_init = target_cls.__init__
|
225
|
-
|
226
|
-
# Create wrapper for __init__ to log instance creation and track attributes
|
227
|
-
@wraps(original_init)
|
228
|
-
def traced_init(self, *args, **kwargs):
|
229
|
-
# Log instance creation
|
230
|
-
if rich:
|
231
|
-
create_msg = (
|
232
|
-
f"[{style}]🏗 Creating instance of {target_cls.__name__}[/{style}]"
|
233
|
-
)
|
234
|
-
else:
|
235
|
-
create_msg = f"Creating instance of {target_cls.__name__}"
|
236
|
-
|
237
|
-
_logger.log(level, create_msg)
|
238
|
-
|
239
|
-
# Call original __init__
|
240
|
-
original_init(self, *args, **kwargs)
|
241
|
-
|
242
|
-
# Log initial attribute values if requested
|
243
|
-
if attributes:
|
244
|
-
attr_info = []
|
245
|
-
for attr in attributes:
|
246
|
-
if hasattr(self, attr):
|
247
|
-
value = getattr(self, attr)
|
248
|
-
attr_info.append(f"{attr}={repr(value)}")
|
249
|
-
|
250
|
-
if attr_info:
|
251
|
-
if rich:
|
252
|
-
attr_msg = f"[{style}] Initial attributes: {', '.join(attr_info)}[/{style}]"
|
253
|
-
else:
|
254
|
-
attr_msg = f" Initial attributes: {', '.join(attr_info)}"
|
255
|
-
_logger.log(level, attr_msg)
|
256
|
-
|
257
|
-
# Replace __init__ with traced version
|
258
|
-
target_cls.__init__ = traced_init
|
259
|
-
|
260
|
-
# Create wrapper for __setattr__ to track attribute changes
|
261
|
-
if attributes:
|
262
|
-
original_setattr = (
|
263
|
-
target_cls.__setattr__
|
264
|
-
if hasattr(target_cls, "__setattr__")
|
265
|
-
else object.__setattr__
|
266
|
-
)
|
267
|
-
|
268
|
-
def traced_setattr(self, name, value):
|
269
|
-
# Check if this is a tracked attribute
|
270
|
-
if name in attributes:
|
271
|
-
# Get old value if it exists
|
272
|
-
old_value = getattr(self, name, "<not set>")
|
273
|
-
|
274
|
-
# Call original __setattr__
|
275
|
-
if original_setattr == object.__setattr__:
|
276
|
-
object.__setattr__(self, name, value)
|
277
|
-
else:
|
278
|
-
original_setattr(self, name, value)
|
279
|
-
|
280
|
-
# Log the change
|
281
|
-
if rich:
|
282
|
-
change_msg = f"[{style}]{target_cls.__name__}.{name}: {repr(old_value)} → {repr(value)}[/{style}]"
|
283
|
-
else:
|
284
|
-
change_msg = f"{target_cls.__name__}.{name}: {repr(old_value)} → {repr(value)}"
|
285
|
-
|
286
|
-
_logger.log(level, change_msg)
|
287
|
-
else:
|
288
|
-
# Not a tracked attribute, just set it normally
|
289
|
-
if original_setattr == object.__setattr__:
|
290
|
-
object.__setattr__(self, name, value)
|
291
|
-
else:
|
292
|
-
original_setattr(self, name, value)
|
293
|
-
|
294
|
-
target_cls.__setattr__ = traced_setattr
|
295
|
-
|
296
|
-
# Trace specific functions if requested
|
297
|
-
if functions:
|
298
|
-
for func_name in functions:
|
299
|
-
if hasattr(target_cls, func_name):
|
300
|
-
func = getattr(target_cls, func_name)
|
301
|
-
if callable(func) and not isinstance(func, type):
|
302
|
-
# Apply trace_function decorator to this method
|
303
|
-
traced_func = trace_function(
|
304
|
-
func,
|
305
|
-
logger=_logger,
|
306
|
-
level=level,
|
307
|
-
rich=rich,
|
308
|
-
style=style,
|
309
|
-
bg=bg,
|
310
|
-
)
|
311
|
-
setattr(target_cls, func_name, traced_func)
|
312
|
-
|
313
|
-
# Log class decoration
|
314
|
-
if rich:
|
315
|
-
decorate_msg = f"[{style}]✨ Decorated class {target_cls.__name__} with tracing[/{style}]"
|
316
|
-
else:
|
317
|
-
decorate_msg = f"Decorated class {target_cls.__name__} with tracing"
|
318
|
-
|
319
|
-
_logger.log(level, decorate_msg)
|
320
|
-
|
321
|
-
return target_cls
|
322
|
-
|
323
|
-
if cls is None:
|
324
|
-
# Called with parameters: @trace_cls(attributes=["x"])
|
325
|
-
return decorator
|
326
|
-
else:
|
327
|
-
# Called directly: @trace_cls
|
328
|
-
return decorator(cls)
|
329
|
-
|
330
|
-
|
331
|
-
# Decorator overloads for better type hints
|
332
|
-
@overload
|
333
|
-
def trace(
|
334
|
-
func_or_cls: Callable[_P, _R],
|
335
|
-
) -> Callable[_P, _R]:
|
336
|
-
"""Decorator to add log tracing over a function or class."""
|
337
|
-
|
338
|
-
|
339
|
-
@overload
|
340
|
-
def trace(
|
341
|
-
func_or_cls: Type[Any],
|
342
|
-
) -> Type[Any]:
|
343
|
-
"""Decorator to add log tracing over a class."""
|
344
|
-
|
345
|
-
|
346
|
-
@overload
|
347
|
-
def trace(
|
348
|
-
*,
|
349
|
-
parameters: List[str] = [],
|
350
|
-
attributes: List[str] = [],
|
351
|
-
functions: List[str] = [],
|
352
|
-
logger: Union[logging.Logger, Logger, None] = None,
|
353
|
-
level: Union[LoggerLevelName, str, int] = "debug",
|
354
|
-
rich: bool = True,
|
355
|
-
style: Union[CLIStyleType, str] = "white",
|
356
|
-
bg: Union[CLIStyleBackgroundType, str] = None,
|
357
|
-
) -> Callable[[Union[Callable[_P, _R], Type[Any]]], Union[Callable[_P, _R], Type[Any]]]:
|
358
|
-
"""Decorator to add log tracing over a function or class."""
|
359
|
-
|
360
|
-
|
361
|
-
def trace(
|
362
|
-
func_or_cls: Union[Callable[_P, _R], Type[Any], None] = None,
|
363
|
-
*,
|
364
|
-
parameters: List[str] = [],
|
365
|
-
attributes: List[str] = [],
|
366
|
-
functions: List[str] = [],
|
367
|
-
logger: Union[logging.Logger, Logger, None] = None,
|
368
|
-
level: Union[LoggerLevelName, str, int] = "debug",
|
369
|
-
rich: bool = True,
|
370
|
-
style: Union[CLIStyleType, str] = "bold blue",
|
371
|
-
bg: Union[CLIStyleBackgroundType, str] = None,
|
372
|
-
) -> Union[
|
373
|
-
Callable[_P, _R],
|
374
|
-
Type[Any],
|
375
|
-
Callable[[Union[Callable[_P, _R], Type[Any]]], Union[Callable[_P, _R], Type[Any]]],
|
376
|
-
]:
|
377
|
-
"""
|
378
|
-
Universal tracing decorator that can be applied to both functions and classes.
|
379
|
-
|
380
|
-
Can be used in three ways:
|
381
|
-
1. @log (direct decoration)
|
382
|
-
2. @log() (parameterized with defaults)
|
383
|
-
3. @log(parameters=["x"], level="info") (parameterized with custom settings)
|
384
|
-
|
385
|
-
When applied to a function, it logs entry/exit and optionally tracks parameters.
|
386
|
-
When applied to a class, it can track attribute changes and log specific methods.
|
387
|
-
|
388
|
-
Args:
|
389
|
-
func_or_cls: The function or class to log (when used directly)
|
390
|
-
parameters: For functions, the parameters to log
|
391
|
-
attributes: For classes, the attributes to track changes
|
392
|
-
functions: For classes, the methods to log
|
393
|
-
logger: The logger to use (creates one if not provided)
|
394
|
-
level: The logging level
|
395
|
-
rich: Whether to use rich formatting
|
396
|
-
style: The style for rich formatting
|
397
|
-
bg: The background style for rich formatting
|
398
|
-
|
399
|
-
Returns:
|
400
|
-
The decorated function/class or a decorator function
|
401
|
-
"""
|
402
|
-
|
403
|
-
def decorator(
|
404
|
-
target: Union[Callable[_P, _R], Type[Any]],
|
405
|
-
) -> Union[Callable[_P, _R], Type[Any]]:
|
406
|
-
if inspect.isclass(target):
|
407
|
-
# It's a class
|
408
|
-
return trace_cls(
|
409
|
-
target,
|
410
|
-
attributes=attributes,
|
411
|
-
functions=functions,
|
412
|
-
logger=logger,
|
413
|
-
level=level,
|
414
|
-
rich=rich,
|
415
|
-
style=style,
|
416
|
-
bg=bg,
|
417
|
-
)
|
418
|
-
else:
|
419
|
-
# It's a function
|
420
|
-
return trace_function(
|
421
|
-
target,
|
422
|
-
parameters=parameters,
|
423
|
-
logger=logger,
|
424
|
-
level=level,
|
425
|
-
rich=rich,
|
426
|
-
style=style,
|
427
|
-
bg=bg,
|
428
|
-
)
|
429
|
-
|
430
|
-
if func_or_cls is None:
|
431
|
-
# Called with parameters: @log(parameters=["x"])
|
432
|
-
return decorator
|
433
|
-
else:
|
434
|
-
# Called directly: @log
|
435
|
-
return decorator(func_or_cls)
|
436
|
-
|
437
|
-
|
438
|
-
def trace_http(
|
439
|
-
fn_or_call: Union[Callable[_P, _R], Callable[_P, Awaitable[_R]], Any, None] = None,
|
440
|
-
*,
|
441
|
-
show_request: bool = True,
|
442
|
-
show_response: bool = True,
|
443
|
-
request_exclude_none: bool = True,
|
444
|
-
response_exclude_none: bool = False,
|
445
|
-
logger: Union[logging.Logger, Logger, None] = None,
|
446
|
-
level: Union[LoggerLevelName, str, int] = "debug",
|
447
|
-
rich: bool = True,
|
448
|
-
style: Union[CLIStyleType, str] = "white",
|
449
|
-
bg: Union[CLIStyleBackgroundType, str] = None, # noqa: ARG001
|
450
|
-
) -> Any:
|
451
|
-
"""Wraps any function that makes HTTP requests, and displays only the request / response
|
452
|
-
bodies in a pretty panel. Can be used as a decorator or direct function wrapper.
|
453
|
-
|
454
|
-
Usage patterns:
|
455
|
-
1. As decorator: @trace_http
|
456
|
-
2. As decorator with params: @trace_http(show_request=False)
|
457
|
-
3. Direct function call: trace_http(my_function(), show_request=True)
|
458
|
-
4. Direct async function call: await trace_http(my_async_function(), show_request=True)
|
459
|
-
|
460
|
-
Args:
|
461
|
-
fn_or_call: The function to wrap, or the result of a function call.
|
462
|
-
show_request: Whether to show the request body.
|
463
|
-
show_response: Whether to show the response body.
|
464
|
-
request_exclude_none: Whether to exclude None values from request logging.
|
465
|
-
response_exclude_none: Whether to exclude None values from response logging.
|
466
|
-
logger: The logger to use.
|
467
|
-
level: The logging level.
|
468
|
-
rich: Whether to use rich formatting.
|
469
|
-
style: The style to use for the logging.
|
470
|
-
bg: The background to use for the logging (kept for API consistency).
|
471
|
-
|
472
|
-
Returns:
|
473
|
-
The decorated function, a decorator function, or the traced result.
|
474
|
-
"""
|
475
|
-
|
476
|
-
def _get_logger(name: str) -> Logger:
|
477
|
-
"""Get or create a logger."""
|
478
|
-
if logger is None:
|
479
|
-
return create_logger(name=name, level=level, rich=rich)
|
480
|
-
elif isinstance(logger, Logger):
|
481
|
-
return logger
|
482
|
-
else:
|
483
|
-
# It's a standard logging.Logger, wrap it
|
484
|
-
_logger = create_logger(name=logger.name)
|
485
|
-
_logger._logger = logger
|
486
|
-
return _logger
|
487
|
-
|
488
|
-
def _log_request_info(
|
489
|
-
bound_args: inspect.BoundArguments,
|
490
|
-
func_name: str,
|
491
|
-
module_name: str,
|
492
|
-
_logger: Logger,
|
493
|
-
):
|
494
|
-
"""Log HTTP request information."""
|
495
|
-
if not show_request:
|
496
|
-
return
|
497
|
-
|
498
|
-
# Simply log all arguments passed to the function
|
499
|
-
args_info = []
|
500
|
-
for param_name, param_value in bound_args.arguments.items():
|
501
|
-
# Skip None values if requested
|
502
|
-
if request_exclude_none and param_value is None:
|
503
|
-
continue
|
504
|
-
args_info.append(f"{param_name}: {repr(param_value)}")
|
505
|
-
|
506
|
-
if args_info:
|
507
|
-
if rich:
|
508
|
-
request_msg = f"[{style}]🌐 HTTP Request from {module_name}.{func_name}()[/{style}]"
|
509
|
-
request_msg += f"\n " + "\n ".join(args_info)
|
510
|
-
else:
|
511
|
-
request_msg = f"🌐 HTTP Request from {module_name}.{func_name}()"
|
512
|
-
request_msg += f"\n " + "\n ".join(args_info)
|
513
|
-
|
514
|
-
_logger.log(level, request_msg)
|
515
|
-
|
516
|
-
def _log_response_info(
|
517
|
-
result: Any, exec_time: float, func_name: str, module_name: str, _logger: Logger
|
518
|
-
):
|
519
|
-
"""Log HTTP response information."""
|
520
|
-
if not show_response:
|
521
|
-
return
|
522
|
-
|
523
|
-
# Skip None responses if requested
|
524
|
-
if response_exclude_none and result is None:
|
525
|
-
return
|
526
|
-
|
527
|
-
# Simply log the response as a string representation
|
528
|
-
result_str = str(result)
|
529
|
-
truncated_result = result_str[:500] + ("..." if len(result_str) > 500 else "")
|
530
|
-
|
531
|
-
if rich:
|
532
|
-
response_msg = f"[{style}]📥 HTTP Response to {module_name}.{func_name}() [dim](took {exec_time:.3f}s)[/dim][/{style}]"
|
533
|
-
response_msg += f"\n Response: {truncated_result}"
|
534
|
-
else:
|
535
|
-
response_msg = f"📥 HTTP Response to {module_name}.{func_name}() (took {exec_time:.3f}s)"
|
536
|
-
response_msg += f"\n Response: {truncated_result}"
|
537
|
-
|
538
|
-
_logger.log(level, response_msg)
|
539
|
-
|
540
|
-
def _log_error_info(
|
541
|
-
e: Exception,
|
542
|
-
exec_time: float,
|
543
|
-
func_name: str,
|
544
|
-
module_name: str,
|
545
|
-
_logger: Logger,
|
546
|
-
):
|
547
|
-
"""Log HTTP error information."""
|
548
|
-
error_style = "bold red" if rich else None
|
549
|
-
if rich:
|
550
|
-
error_msg = f"[{error_style}]❌ HTTP Error in {module_name}.{func_name}() [dim](after {exec_time:.3f}s)[/dim][/{error_style}]"
|
551
|
-
error_msg += f"\n [red]{type(e).__name__}: {str(e)}[/red]"
|
552
|
-
else:
|
553
|
-
error_msg = (
|
554
|
-
f"❌ HTTP Error in {module_name}.{func_name}() (after {exec_time:.3f}s)"
|
555
|
-
)
|
556
|
-
error_msg += f"\n {type(e).__name__}: {str(e)}"
|
557
|
-
|
558
|
-
_logger.error(error_msg)
|
559
|
-
|
560
|
-
def decorator(
|
561
|
-
target_fn: Union[Callable[_P, _R], Callable[_P, Awaitable[_R]]],
|
562
|
-
) -> Union[Callable[_P, _R], Callable[_P, Awaitable[_R]]]:
|
563
|
-
"""Decorator that wraps sync or async functions."""
|
564
|
-
|
565
|
-
if asyncio.iscoroutinefunction(target_fn):
|
566
|
-
# Async function
|
567
|
-
@wraps(target_fn)
|
568
|
-
async def async_wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _R:
|
569
|
-
_logger = _get_logger(
|
570
|
-
f"http.{target_fn.__module__}.{target_fn.__name__}"
|
571
|
-
)
|
572
|
-
|
573
|
-
# Get function signature for parameter inspection
|
574
|
-
sig = inspect.signature(target_fn)
|
575
|
-
bound_args = sig.bind(*args, **kwargs)
|
576
|
-
bound_args.apply_defaults()
|
577
|
-
|
578
|
-
func_name = target_fn.__name__
|
579
|
-
module_name = target_fn.__module__
|
580
|
-
|
581
|
-
# Log request info
|
582
|
-
_log_request_info(bound_args, func_name, module_name, _logger)
|
583
|
-
|
584
|
-
# Track execution time
|
585
|
-
start_time = time.time()
|
586
|
-
|
587
|
-
try:
|
588
|
-
# Execute the async function
|
589
|
-
result = await target_fn(*args, **kwargs)
|
590
|
-
|
591
|
-
# Calculate execution time
|
592
|
-
exec_time = time.time() - start_time
|
593
|
-
|
594
|
-
# Log response info
|
595
|
-
_log_response_info(
|
596
|
-
result, exec_time, func_name, module_name, _logger
|
597
|
-
)
|
598
|
-
|
599
|
-
return result
|
600
|
-
|
601
|
-
except Exception as e:
|
602
|
-
# Calculate execution time
|
603
|
-
exec_time = time.time() - start_time
|
604
|
-
|
605
|
-
# Log error info
|
606
|
-
_log_error_info(e, exec_time, func_name, module_name, _logger)
|
607
|
-
|
608
|
-
# Re-raise the exception
|
609
|
-
raise
|
610
|
-
|
611
|
-
return async_wrapper
|
612
|
-
else:
|
613
|
-
# Sync function
|
614
|
-
@wraps(target_fn)
|
615
|
-
def sync_wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _R:
|
616
|
-
_logger = _get_logger(
|
617
|
-
f"http.{target_fn.__module__}.{target_fn.__name__}"
|
618
|
-
)
|
619
|
-
|
620
|
-
# Get function signature for parameter inspection
|
621
|
-
sig = inspect.signature(target_fn)
|
622
|
-
bound_args = sig.bind(*args, **kwargs)
|
623
|
-
bound_args.apply_defaults()
|
624
|
-
|
625
|
-
func_name = target_fn.__name__
|
626
|
-
module_name = target_fn.__module__
|
627
|
-
|
628
|
-
# Log request info
|
629
|
-
_log_request_info(bound_args, func_name, module_name, _logger)
|
630
|
-
|
631
|
-
# Track execution time
|
632
|
-
start_time = time.time()
|
633
|
-
|
634
|
-
try:
|
635
|
-
# Execute the function
|
636
|
-
result = target_fn(*args, **kwargs)
|
637
|
-
|
638
|
-
# Calculate execution time
|
639
|
-
exec_time = time.time() - start_time
|
640
|
-
|
641
|
-
# Log response info
|
642
|
-
_log_response_info(
|
643
|
-
result, exec_time, func_name, module_name, _logger
|
644
|
-
)
|
645
|
-
|
646
|
-
return result
|
647
|
-
|
648
|
-
except Exception as e:
|
649
|
-
# Calculate execution time
|
650
|
-
exec_time = time.time() - start_time
|
651
|
-
|
652
|
-
# Log error info
|
653
|
-
_log_error_info(e, exec_time, func_name, module_name, _logger)
|
654
|
-
|
655
|
-
# Re-raise the exception
|
656
|
-
raise
|
657
|
-
|
658
|
-
return sync_wrapper
|
659
|
-
|
660
|
-
# Handle different usage patterns
|
661
|
-
if fn_or_call is None:
|
662
|
-
# Called with parameters: @trace_http(show_request=False)
|
663
|
-
return decorator
|
664
|
-
elif callable(fn_or_call):
|
665
|
-
# Called directly as decorator: @trace_http
|
666
|
-
return decorator(fn_or_call)
|
667
|
-
else:
|
668
|
-
# Called with a function result: trace_http(some_function(), ...)
|
669
|
-
# In this case, we can't trace the function call since it's already executed
|
670
|
-
# But we can still log the response
|
671
|
-
_logger = _get_logger("http.direct_call")
|
672
|
-
|
673
|
-
if show_response and fn_or_call is not None:
|
674
|
-
_log_response_info(fn_or_call, 0.0, "direct_call", "trace_http", _logger)
|
675
|
-
|
676
|
-
return fn_or_call
|
677
|
-
|
678
|
-
|
679
|
-
def install_trace_http(
|
680
|
-
*,
|
681
|
-
show_request: bool = True,
|
682
|
-
show_response: bool = True,
|
683
|
-
request_exclude_none: bool = True,
|
684
|
-
response_exclude_none: bool = False,
|
685
|
-
logger: Union[logging.Logger, Logger, None] = None,
|
686
|
-
level: Union[LoggerLevelName, str, int] = "debug",
|
687
|
-
rich: bool = True,
|
688
|
-
style: Union[CLIStyleType, str] = "white",
|
689
|
-
bg: Union[CLIStyleBackgroundType, str] = None, # noqa: ARG001
|
690
|
-
patch_imports: bool = True,
|
691
|
-
) -> None:
|
692
|
-
"""Install global HTTP tracing for all HTTP-related functions.
|
693
|
-
|
694
|
-
This function patches common HTTP libraries to automatically trace all
|
695
|
-
HTTP requests and responses without needing to manually decorate functions.
|
696
|
-
|
697
|
-
Args:
|
698
|
-
show_request: Whether to show the request body.
|
699
|
-
show_response: Whether to show the response body.
|
700
|
-
request_exclude_none: Whether to exclude None values from request logging.
|
701
|
-
response_exclude_none: Whether to exclude None values from response logging.
|
702
|
-
logger: The logger to use.
|
703
|
-
level: The logging level.
|
704
|
-
rich: Whether to use rich formatting.
|
705
|
-
style: The style to use for the logging.
|
706
|
-
bg: The background to use for the logging (kept for API consistency).
|
707
|
-
patch_imports: Whether to also patch the import mechanism for future imports.
|
708
|
-
"""
|
709
|
-
import sys
|
710
|
-
|
711
|
-
# Create a tracer function with the specified settings
|
712
|
-
def create_tracer(original_func):
|
713
|
-
return trace_http(
|
714
|
-
original_func,
|
715
|
-
show_request=show_request,
|
716
|
-
show_response=show_response,
|
717
|
-
request_exclude_none=request_exclude_none,
|
718
|
-
response_exclude_none=response_exclude_none,
|
719
|
-
logger=logger,
|
720
|
-
level=level,
|
721
|
-
rich=rich,
|
722
|
-
style=style,
|
723
|
-
bg=bg,
|
724
|
-
)
|
725
|
-
|
726
|
-
# List of common HTTP libraries and their functions to patch
|
727
|
-
patches = [
|
728
|
-
# requests library
|
729
|
-
("requests", "get"),
|
730
|
-
("requests", "post"),
|
731
|
-
("requests", "put"),
|
732
|
-
("requests", "delete"),
|
733
|
-
("requests", "patch"),
|
734
|
-
("requests", "head"),
|
735
|
-
("requests", "options"),
|
736
|
-
("requests", "request"),
|
737
|
-
# httpx library
|
738
|
-
("httpx", "get"),
|
739
|
-
("httpx", "post"),
|
740
|
-
("httpx", "put"),
|
741
|
-
("httpx", "delete"),
|
742
|
-
("httpx", "patch"),
|
743
|
-
("httpx", "head"),
|
744
|
-
("httpx", "options"),
|
745
|
-
("httpx", "request"),
|
746
|
-
# urllib3
|
747
|
-
("urllib3", "request"),
|
748
|
-
# aiohttp
|
749
|
-
("aiohttp", "request"),
|
750
|
-
]
|
751
|
-
|
752
|
-
patched_functions = []
|
753
|
-
|
754
|
-
for module_name, func_name in patches:
|
755
|
-
try:
|
756
|
-
# Check if module is already imported
|
757
|
-
if module_name in sys.modules:
|
758
|
-
module = sys.modules[module_name]
|
759
|
-
|
760
|
-
# Handle nested module paths like "openai.chat.completions"
|
761
|
-
if "." in module_name:
|
762
|
-
module_parts = module_name.split(".")
|
763
|
-
for part in module_parts[1:]:
|
764
|
-
if hasattr(module, part):
|
765
|
-
module = getattr(module, part)
|
766
|
-
else:
|
767
|
-
break
|
768
|
-
else:
|
769
|
-
# Successfully navigated to nested module
|
770
|
-
if hasattr(module, func_name):
|
771
|
-
original_func = getattr(module, func_name)
|
772
|
-
traced_func = create_tracer(original_func)
|
773
|
-
setattr(module, func_name, traced_func)
|
774
|
-
patched_functions.append(f"{module_name}.{func_name}")
|
775
|
-
else:
|
776
|
-
# Simple module path
|
777
|
-
if hasattr(module, func_name):
|
778
|
-
original_func = getattr(module, func_name)
|
779
|
-
traced_func = create_tracer(original_func)
|
780
|
-
setattr(module, func_name, traced_func)
|
781
|
-
patched_functions.append(f"{module_name}.{func_name}")
|
782
|
-
|
783
|
-
# Special handling for litellm - also patch litellm.main if available
|
784
|
-
if module_name == "litellm" and "litellm.main" in sys.modules:
|
785
|
-
main_module = sys.modules["litellm.main"]
|
786
|
-
if hasattr(main_module, func_name):
|
787
|
-
setattr(main_module, func_name, traced_func)
|
788
|
-
|
789
|
-
# Also check if the nested module exists in sys.modules for litellm.main case
|
790
|
-
elif "." in module_name and module_name in sys.modules:
|
791
|
-
module = sys.modules[module_name]
|
792
|
-
if hasattr(module, func_name):
|
793
|
-
original_func = getattr(module, func_name)
|
794
|
-
traced_func = create_tracer(original_func)
|
795
|
-
setattr(module, func_name, traced_func)
|
796
|
-
patched_functions.append(f"{module_name}.{func_name}")
|
797
|
-
|
798
|
-
# If this is litellm.main, also patch the main litellm module
|
799
|
-
if module_name == "litellm.main" and "litellm" in sys.modules:
|
800
|
-
main_litellm = sys.modules["litellm"]
|
801
|
-
if hasattr(main_litellm, func_name):
|
802
|
-
setattr(main_litellm, func_name, traced_func)
|
803
|
-
|
804
|
-
except (ImportError, AttributeError):
|
805
|
-
# Module not available or function doesn't exist
|
806
|
-
continue
|
807
|
-
|
808
|
-
# Log what was patched
|
809
|
-
if patched_functions:
|
810
|
-
_logger = (
|
811
|
-
logger
|
812
|
-
if isinstance(logger, Logger)
|
813
|
-
else create_logger(name="http.install_trace", level=level, rich=rich)
|
814
|
-
)
|
815
|
-
|
816
|
-
if rich:
|
817
|
-
install_msg = f"[{style}]✨ Installed HTTP tracing on {len(patched_functions)} functions[/{style}]"
|
818
|
-
install_msg += f"\n " + "\n ".join(patched_functions)
|
819
|
-
else:
|
820
|
-
install_msg = (
|
821
|
-
f"✨ Installed HTTP tracing on {len(patched_functions)} functions"
|
822
|
-
)
|
823
|
-
install_msg += f"\n " + "\n ".join(patched_functions)
|
824
|
-
|
825
|
-
_logger.info(install_msg)
|
826
|
-
else:
|
827
|
-
_logger = (
|
828
|
-
logger
|
829
|
-
if isinstance(logger, Logger)
|
830
|
-
else create_logger(name="http.install_trace", level=level, rich=rich)
|
831
|
-
)
|
832
|
-
_logger.warning(
|
833
|
-
"No HTTP functions found to patch. Import HTTP libraries first."
|
834
|
-
)
|