hammad-python 0.0.30__py3-none-any.whl → 0.0.32__py3-none-any.whl

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