hammad-python 0.0.10__py3-none-any.whl → 0.0.11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hammad/__init__.py +64 -10
- hammad/based/__init__.py +52 -0
- hammad/based/fields.py +546 -0
- hammad/based/model.py +968 -0
- hammad/based/utils.py +455 -0
- hammad/cache/__init__.py +30 -0
- hammad/{cache.py → cache/_cache.py} +83 -12
- hammad/cli/__init__.py +25 -0
- hammad/cli/plugins/__init__.py +786 -0
- hammad/cli/styles/__init__.py +5 -0
- hammad/cli/styles/animations.py +548 -0
- hammad/cli/styles/settings.py +135 -0
- hammad/cli/styles/types.py +358 -0
- hammad/cli/styles/utils.py +480 -0
- hammad/data/__init__.py +51 -0
- hammad/data/collections/__init__.py +32 -0
- hammad/data/collections/base_collection.py +58 -0
- hammad/data/collections/collection.py +227 -0
- hammad/data/collections/searchable_collection.py +556 -0
- hammad/data/collections/vector_collection.py +497 -0
- hammad/data/databases/__init__.py +21 -0
- hammad/data/databases/database.py +551 -0
- hammad/data/types/__init__.py +33 -0
- hammad/data/types/files/__init__.py +1 -0
- hammad/data/types/files/audio.py +81 -0
- hammad/data/types/files/configuration.py +475 -0
- hammad/data/types/files/document.py +195 -0
- hammad/data/types/files/file.py +358 -0
- hammad/data/types/files/image.py +80 -0
- hammad/json/__init__.py +21 -0
- hammad/{utils/json → json}/converters.py +4 -1
- hammad/logging/__init__.py +27 -0
- hammad/logging/decorators.py +432 -0
- hammad/logging/logger.py +534 -0
- hammad/pydantic/__init__.py +43 -0
- hammad/{utils/pydantic → pydantic}/converters.py +2 -1
- hammad/pydantic/models/__init__.py +28 -0
- hammad/pydantic/models/arbitrary_model.py +46 -0
- hammad/pydantic/models/cacheable_model.py +79 -0
- hammad/pydantic/models/fast_model.py +318 -0
- hammad/pydantic/models/function_model.py +176 -0
- hammad/pydantic/models/subscriptable_model.py +63 -0
- hammad/text/__init__.py +37 -0
- hammad/text/text.py +1068 -0
- hammad/text/utils/__init__.py +1 -0
- hammad/{utils/text → text/utils}/converters.py +2 -2
- hammad/text/utils/markdown/__init__.py +1 -0
- hammad/{utils → text/utils}/markdown/converters.py +3 -3
- hammad/{utils → text/utils}/markdown/formatting.py +1 -1
- hammad/{utils/typing/utils.py → typing/__init__.py} +75 -2
- hammad/web/__init__.py +42 -0
- hammad/web/http/__init__.py +1 -0
- hammad/web/http/client.py +944 -0
- hammad/web/openapi/client.py +740 -0
- hammad/web/search/__init__.py +1 -0
- hammad/web/search/client.py +936 -0
- hammad/web/utils.py +463 -0
- hammad/yaml/__init__.py +30 -0
- hammad/yaml/converters.py +19 -0
- {hammad_python-0.0.10.dist-info → hammad_python-0.0.11.dist-info}/METADATA +14 -8
- hammad_python-0.0.11.dist-info/RECORD +65 -0
- hammad/database.py +0 -447
- hammad/logger.py +0 -273
- hammad/types/color.py +0 -951
- hammad/utils/json/__init__.py +0 -0
- hammad/utils/markdown/__init__.py +0 -0
- hammad/utils/pydantic/__init__.py +0 -0
- hammad/utils/text/__init__.py +0 -0
- hammad/utils/typing/__init__.py +0 -0
- hammad_python-0.0.10.dist-info/RECORD +0 -22
- /hammad/{types/__init__.py → py.typed} +0 -0
- /hammad/{utils → web/openapi}/__init__.py +0 -0
- {hammad_python-0.0.10.dist-info → hammad_python-0.0.11.dist-info}/WHEEL +0 -0
- {hammad_python-0.0.10.dist-info → hammad_python-0.0.11.dist-info}/licenses/LICENSE +0 -0
hammad/logging/logger.py
ADDED
@@ -0,0 +1,534 @@
|
|
1
|
+
"""hammad.logging.logger"""
|
2
|
+
|
3
|
+
import logging as _logging
|
4
|
+
import inspect
|
5
|
+
from dataclasses import dataclass, field
|
6
|
+
from typing import (
|
7
|
+
Literal,
|
8
|
+
TypeAlias,
|
9
|
+
NamedTuple,
|
10
|
+
ParamSpec,
|
11
|
+
TypeVar,
|
12
|
+
Dict,
|
13
|
+
Optional,
|
14
|
+
Any,
|
15
|
+
Union,
|
16
|
+
)
|
17
|
+
from typing_extensions import TypedDict
|
18
|
+
|
19
|
+
from rich import get_console as get_rich_console
|
20
|
+
from rich.logging import RichHandler
|
21
|
+
|
22
|
+
from ..cli.styles.types import (
|
23
|
+
CLIStyleType,
|
24
|
+
)
|
25
|
+
from ..cli.styles.settings import CLIStyleRenderableSettings, CLIStyleBackgroundSettings
|
26
|
+
|
27
|
+
__all__ = (
|
28
|
+
"Logger",
|
29
|
+
"create_logger",
|
30
|
+
"create_logger_level",
|
31
|
+
)
|
32
|
+
|
33
|
+
|
34
|
+
# -----------------------------------------------------------------------------
|
35
|
+
# Types
|
36
|
+
# -----------------------------------------------------------------------------
|
37
|
+
|
38
|
+
|
39
|
+
LoggerLevelName: TypeAlias = Literal["debug", "info", "warning", "error", "critical"]
|
40
|
+
"""Literal type helper for logging levels."""
|
41
|
+
|
42
|
+
|
43
|
+
_P = ParamSpec("_P")
|
44
|
+
_R = TypeVar("_R")
|
45
|
+
|
46
|
+
|
47
|
+
class LoggerLevelSettings(TypedDict, total=False):
|
48
|
+
"""Configuration dictionary for the display style of a
|
49
|
+
single logging level."""
|
50
|
+
|
51
|
+
title: CLIStyleType | CLIStyleRenderableSettings
|
52
|
+
"""Either a string tag or style settings for the title output
|
53
|
+
of the messages of this level. This includes module name
|
54
|
+
and level name."""
|
55
|
+
|
56
|
+
message: CLIStyleType | CLIStyleRenderableSettings
|
57
|
+
"""Either a string tag or style settings for the message output
|
58
|
+
of the messages of this level. This includes the message itself."""
|
59
|
+
|
60
|
+
background: CLIStyleType | CLIStyleBackgroundSettings
|
61
|
+
"""Either a string tag or style settings for the background output
|
62
|
+
of the messages of this level. This includes the message itself."""
|
63
|
+
|
64
|
+
|
65
|
+
# -----------------------------------------------------------------------------
|
66
|
+
# Default Level Styles
|
67
|
+
# -----------------------------------------------------------------------------
|
68
|
+
|
69
|
+
DEFAULT_LEVEL_STYLES: Dict[str, LoggerLevelSettings] = {
|
70
|
+
"critical": {
|
71
|
+
"message": "red bold",
|
72
|
+
},
|
73
|
+
"error": {
|
74
|
+
"message": "red italic",
|
75
|
+
},
|
76
|
+
"warning": {
|
77
|
+
"message": "yellow italic",
|
78
|
+
},
|
79
|
+
"info": {
|
80
|
+
"message": "white",
|
81
|
+
},
|
82
|
+
"debug": {
|
83
|
+
"message": "white italic dim",
|
84
|
+
},
|
85
|
+
}
|
86
|
+
|
87
|
+
|
88
|
+
# -----------------------------------------------------------------------------
|
89
|
+
# Logging Filter
|
90
|
+
# -----------------------------------------------------------------------------
|
91
|
+
|
92
|
+
|
93
|
+
class RichLoggerFilter(_logging.Filter):
|
94
|
+
"""Filter for applying rich styling to log messages based on level."""
|
95
|
+
|
96
|
+
def __init__(self, level_styles: Dict[str, LoggerLevelSettings]):
|
97
|
+
super().__init__()
|
98
|
+
self.level_styles = level_styles
|
99
|
+
|
100
|
+
def filter(self, record: _logging.LogRecord) -> bool:
|
101
|
+
# Get the level name
|
102
|
+
level_name = record.levelname.lower()
|
103
|
+
|
104
|
+
# Check if we have custom styling for this level
|
105
|
+
if level_name in self.level_styles:
|
106
|
+
style_config = self.level_styles[level_name]
|
107
|
+
|
108
|
+
# We'll use a special attribute to store style config
|
109
|
+
# The formatter/handler will use this to apply styling
|
110
|
+
record._hammad_style_config = style_config
|
111
|
+
|
112
|
+
return True
|
113
|
+
|
114
|
+
|
115
|
+
# -----------------------------------------------------------------------------
|
116
|
+
# Custom Rich Formatter
|
117
|
+
# -----------------------------------------------------------------------------
|
118
|
+
|
119
|
+
|
120
|
+
class RichLoggerFormatter(_logging.Formatter):
|
121
|
+
"""Custom formatter that applies rich styling."""
|
122
|
+
|
123
|
+
def __init__(self, *args, **kwargs):
|
124
|
+
super().__init__(*args, **kwargs)
|
125
|
+
self.console = get_rich_console()
|
126
|
+
|
127
|
+
def formatMessage(self, record: _logging.LogRecord) -> str:
|
128
|
+
"""Override formatMessage to apply styling to different parts."""
|
129
|
+
# Check if we have style configuration
|
130
|
+
if hasattr(record, "_hammad_style_config"):
|
131
|
+
style_config = record._hammad_style_config
|
132
|
+
|
133
|
+
# Handle title styling (logger name)
|
134
|
+
title_style = style_config.get("title", None)
|
135
|
+
if title_style:
|
136
|
+
if isinstance(title_style, str):
|
137
|
+
# It's a color/style string tag
|
138
|
+
record.name = f"[{title_style}]{record.name}[/{title_style}]"
|
139
|
+
elif isinstance(title_style, dict):
|
140
|
+
# It's a CLIStyleRenderableSettings dict
|
141
|
+
style_str = self._build_renderable_style_string(title_style)
|
142
|
+
if style_str:
|
143
|
+
record.name = f"[{style_str}]{record.name}[/{style_str}]"
|
144
|
+
|
145
|
+
# Handle message styling
|
146
|
+
message_style = style_config.get("message", None)
|
147
|
+
if message_style:
|
148
|
+
if isinstance(message_style, str):
|
149
|
+
# It's a color/style string tag
|
150
|
+
record.message = (
|
151
|
+
f"[{message_style}]{record.getMessage()}[/{message_style}]"
|
152
|
+
)
|
153
|
+
elif isinstance(message_style, dict):
|
154
|
+
# It's a CLIStyleRenderableSettings dict
|
155
|
+
style_str = self._build_renderable_style_string(message_style)
|
156
|
+
if style_str:
|
157
|
+
record.message = (
|
158
|
+
f"[{style_str}]{record.getMessage()}[/{style_str}]"
|
159
|
+
)
|
160
|
+
else:
|
161
|
+
record.message = record.getMessage()
|
162
|
+
else:
|
163
|
+
record.message = record.getMessage()
|
164
|
+
else:
|
165
|
+
record.message = record.getMessage()
|
166
|
+
|
167
|
+
# Now format with the styled values
|
168
|
+
return self._style._fmt.format(**record.__dict__)
|
169
|
+
|
170
|
+
def _build_renderable_style_string(self, style_dict: dict) -> str:
|
171
|
+
"""Build a rich markup style string from a CLIStyleRenderableSettings dictionary."""
|
172
|
+
style_parts = []
|
173
|
+
|
174
|
+
# Handle all the style attributes from CLIStyleRenderableSettings
|
175
|
+
for attr in [
|
176
|
+
"bold",
|
177
|
+
"italic",
|
178
|
+
"dim",
|
179
|
+
"underline",
|
180
|
+
"strike",
|
181
|
+
"blink",
|
182
|
+
"blink2",
|
183
|
+
"reverse",
|
184
|
+
"conceal",
|
185
|
+
"underline2",
|
186
|
+
"frame",
|
187
|
+
"encircle",
|
188
|
+
"overline",
|
189
|
+
]:
|
190
|
+
if style_dict.get(attr):
|
191
|
+
style_parts.append(attr)
|
192
|
+
|
193
|
+
return " ".join(style_parts) if style_parts else ""
|
194
|
+
|
195
|
+
|
196
|
+
# -----------------------------------------------------------------------------
|
197
|
+
# Logger
|
198
|
+
# -----------------------------------------------------------------------------
|
199
|
+
|
200
|
+
|
201
|
+
@dataclass
|
202
|
+
class Logger:
|
203
|
+
"""Flexible logger with rich styling and custom level support."""
|
204
|
+
|
205
|
+
_logger: _logging.Logger = field(init=False)
|
206
|
+
"""The underlying logging.Logger instance."""
|
207
|
+
|
208
|
+
_level_styles: Dict[str, LoggerLevelSettings] = field(init=False)
|
209
|
+
"""Custom level styles."""
|
210
|
+
|
211
|
+
_custom_levels: Dict[str, int] = field(init=False)
|
212
|
+
"""Custom logging levels."""
|
213
|
+
|
214
|
+
_user_level: str = field(init=False)
|
215
|
+
"""User-specified logging level."""
|
216
|
+
|
217
|
+
def __init__(
|
218
|
+
self,
|
219
|
+
name: Optional[str] = None,
|
220
|
+
level: Optional[Union[str, int]] = None,
|
221
|
+
rich: bool = True,
|
222
|
+
display_all: bool = False,
|
223
|
+
level_styles: Optional[Dict[str, LoggerLevelSettings]] = None,
|
224
|
+
) -> None:
|
225
|
+
"""
|
226
|
+
Initialize a new Logger instance.
|
227
|
+
|
228
|
+
Args:
|
229
|
+
name: Name for the logger. If None, defaults to "hammad"
|
230
|
+
level: Logging level. If None, defaults to "debug" if display_all else "warning"
|
231
|
+
rich: Whether to use rich formatting for output
|
232
|
+
display_all: If True, sets effective level to debug to show all messages
|
233
|
+
level_styles: Custom level styles to override defaults
|
234
|
+
"""
|
235
|
+
logger_name = name or "hammad"
|
236
|
+
|
237
|
+
# Initialize custom levels dict
|
238
|
+
self._custom_levels = {}
|
239
|
+
|
240
|
+
# Initialize level styles with defaults
|
241
|
+
self._level_styles = DEFAULT_LEVEL_STYLES.copy()
|
242
|
+
if level_styles:
|
243
|
+
self._level_styles.update(level_styles)
|
244
|
+
|
245
|
+
# Handle integer levels by converting to string names
|
246
|
+
if isinstance(level, int):
|
247
|
+
# Map standard logging levels to their names
|
248
|
+
int_to_name = {
|
249
|
+
_logging.DEBUG: "debug",
|
250
|
+
_logging.INFO: "info",
|
251
|
+
_logging.WARNING: "warning",
|
252
|
+
_logging.ERROR: "error",
|
253
|
+
_logging.CRITICAL: "critical",
|
254
|
+
}
|
255
|
+
level = int_to_name.get(level, "warning")
|
256
|
+
|
257
|
+
self._user_level = level or "warning"
|
258
|
+
|
259
|
+
if display_all:
|
260
|
+
effective_level = "debug"
|
261
|
+
else:
|
262
|
+
effective_level = self._user_level
|
263
|
+
|
264
|
+
# Standard level mapping
|
265
|
+
level_map = {
|
266
|
+
"debug": _logging.DEBUG,
|
267
|
+
"info": _logging.INFO,
|
268
|
+
"warning": _logging.WARNING,
|
269
|
+
"error": _logging.ERROR,
|
270
|
+
"critical": _logging.CRITICAL,
|
271
|
+
}
|
272
|
+
|
273
|
+
# Check if it's a custom level
|
274
|
+
if effective_level.lower() in self._custom_levels:
|
275
|
+
log_level = self._custom_levels[effective_level.lower()]
|
276
|
+
else:
|
277
|
+
log_level = level_map.get(effective_level.lower(), _logging.WARNING)
|
278
|
+
|
279
|
+
# Create logger
|
280
|
+
self._logger = _logging.getLogger(logger_name)
|
281
|
+
|
282
|
+
# Clear any existing handlers
|
283
|
+
if self._logger.hasHandlers():
|
284
|
+
self._logger.handlers.clear()
|
285
|
+
|
286
|
+
# Setup handler based on rich preference
|
287
|
+
if rich:
|
288
|
+
self._setup_rich_handler(log_level)
|
289
|
+
else:
|
290
|
+
self._setup_standard_handler(log_level)
|
291
|
+
|
292
|
+
self._logger.setLevel(log_level)
|
293
|
+
self._logger.propagate = False
|
294
|
+
|
295
|
+
def _setup_rich_handler(self, log_level: int) -> None:
|
296
|
+
"""Setup rich handler for the logger."""
|
297
|
+
console = get_rich_console()
|
298
|
+
|
299
|
+
handler = RichHandler(
|
300
|
+
level=log_level,
|
301
|
+
console=console,
|
302
|
+
rich_tracebacks=True,
|
303
|
+
show_time=False,
|
304
|
+
show_path=False,
|
305
|
+
markup=True,
|
306
|
+
)
|
307
|
+
|
308
|
+
formatter = RichLoggerFormatter(
|
309
|
+
"| [bold]✼ {name}[/bold] - {message}", style="{"
|
310
|
+
)
|
311
|
+
handler.setFormatter(formatter)
|
312
|
+
|
313
|
+
# Add our custom filter
|
314
|
+
handler.addFilter(RichLoggerFilter(self._level_styles))
|
315
|
+
|
316
|
+
self._logger.addHandler(handler)
|
317
|
+
|
318
|
+
def _setup_standard_handler(self, log_level: int) -> None:
|
319
|
+
"""Setup standard handler for the logger."""
|
320
|
+
handler = _logging.StreamHandler()
|
321
|
+
formatter = _logging.Formatter("✼ {name} - {levelname} - {message}", style="{")
|
322
|
+
handler.setFormatter(formatter)
|
323
|
+
handler.setLevel(log_level)
|
324
|
+
|
325
|
+
self._logger.addHandler(handler)
|
326
|
+
|
327
|
+
def add_level(
|
328
|
+
self, name: str, value: int, style: Optional[LoggerLevelSettings] = None
|
329
|
+
) -> None:
|
330
|
+
"""
|
331
|
+
Add a custom logging level.
|
332
|
+
|
333
|
+
Args:
|
334
|
+
name: Name of the custom level
|
335
|
+
value: Numeric value for the level (should be unique)
|
336
|
+
style: Optional style settings for the level
|
337
|
+
"""
|
338
|
+
# Add to Python's logging module
|
339
|
+
_logging.addLevelName(value, name.upper())
|
340
|
+
|
341
|
+
# Store in our custom levels
|
342
|
+
self._custom_levels[name.lower()] = value
|
343
|
+
|
344
|
+
# Add style if provided
|
345
|
+
if style:
|
346
|
+
self._level_styles[name.lower()] = style
|
347
|
+
|
348
|
+
# Update filters if using rich handler
|
349
|
+
for handler in self._logger.handlers:
|
350
|
+
if isinstance(handler, RichHandler):
|
351
|
+
# Remove old filter and add new one with updated styles
|
352
|
+
for f in handler.filters[:]:
|
353
|
+
if isinstance(f, RichLoggerFilter):
|
354
|
+
handler.removeFilter(f)
|
355
|
+
handler.addFilter(RichLoggerFilter(self._level_styles))
|
356
|
+
|
357
|
+
@property
|
358
|
+
def level(self) -> str:
|
359
|
+
"""Get the current logging level."""
|
360
|
+
return self._user_level
|
361
|
+
|
362
|
+
@level.setter
|
363
|
+
def level(self, value: str) -> None:
|
364
|
+
"""Set the logging level."""
|
365
|
+
self._user_level = value
|
366
|
+
|
367
|
+
# Standard level mapping
|
368
|
+
level_map = {
|
369
|
+
"debug": _logging.DEBUG,
|
370
|
+
"info": _logging.INFO,
|
371
|
+
"warning": _logging.WARNING,
|
372
|
+
"error": _logging.ERROR,
|
373
|
+
"critical": _logging.CRITICAL,
|
374
|
+
}
|
375
|
+
|
376
|
+
# Check custom levels
|
377
|
+
if value.lower() in self._custom_levels:
|
378
|
+
log_level = self._custom_levels[value.lower()]
|
379
|
+
else:
|
380
|
+
log_level = level_map.get(value.lower(), _logging.WARNING)
|
381
|
+
|
382
|
+
# Update logger level
|
383
|
+
self._logger.setLevel(log_level)
|
384
|
+
|
385
|
+
# Update handler levels
|
386
|
+
for handler in self._logger.handlers:
|
387
|
+
handler.setLevel(log_level)
|
388
|
+
|
389
|
+
# Convenience methods for standard logging levels
|
390
|
+
def debug(self, message: str, *args: Any, **kwargs: Any) -> None:
|
391
|
+
"""Log a debug message."""
|
392
|
+
self._logger.debug(message, *args, **kwargs)
|
393
|
+
|
394
|
+
def info(self, message: str, *args: Any, **kwargs: Any) -> None:
|
395
|
+
"""Log an info message."""
|
396
|
+
self._logger.info(message, *args, **kwargs)
|
397
|
+
|
398
|
+
def warning(self, message: str, *args: Any, **kwargs: Any) -> None:
|
399
|
+
"""Log a warning message."""
|
400
|
+
self._logger.warning(message, *args, **kwargs)
|
401
|
+
|
402
|
+
def error(self, message: str, *args: Any, **kwargs: Any) -> None:
|
403
|
+
"""Log an error message."""
|
404
|
+
self._logger.error(message, *args, **kwargs)
|
405
|
+
|
406
|
+
def critical(self, message: str, *args: Any, **kwargs: Any) -> None:
|
407
|
+
"""Log a critical message."""
|
408
|
+
self._logger.critical(message, *args, **kwargs)
|
409
|
+
|
410
|
+
def log(
|
411
|
+
self, level: Union[str, int], message: str, *args: Any, **kwargs: Any
|
412
|
+
) -> None:
|
413
|
+
"""
|
414
|
+
Log a message at the specified level.
|
415
|
+
|
416
|
+
Args:
|
417
|
+
level: The level to log at (can be standard or custom)
|
418
|
+
message: The message to log
|
419
|
+
*args: Additional positional arguments for the logger
|
420
|
+
**kwargs: Additional keyword arguments for the logger
|
421
|
+
"""
|
422
|
+
# Standard level mapping
|
423
|
+
level_map = {
|
424
|
+
"debug": _logging.DEBUG,
|
425
|
+
"info": _logging.INFO,
|
426
|
+
"warning": _logging.WARNING,
|
427
|
+
"error": _logging.ERROR,
|
428
|
+
"critical": _logging.CRITICAL,
|
429
|
+
}
|
430
|
+
|
431
|
+
# Handle integer levels
|
432
|
+
if isinstance(level, int):
|
433
|
+
# Use the integer level directly
|
434
|
+
log_level = level
|
435
|
+
else:
|
436
|
+
# Check custom levels first
|
437
|
+
if level.lower() in self._custom_levels:
|
438
|
+
log_level = self._custom_levels[level.lower()]
|
439
|
+
else:
|
440
|
+
log_level = level_map.get(level.lower(), _logging.WARNING)
|
441
|
+
|
442
|
+
self._logger.log(log_level, message, *args, **kwargs)
|
443
|
+
|
444
|
+
@property
|
445
|
+
def name(self) -> str:
|
446
|
+
"""Get the logger name."""
|
447
|
+
return self._logger.name
|
448
|
+
|
449
|
+
@property
|
450
|
+
def handlers(self) -> list[_logging.Handler]:
|
451
|
+
"""Get the logger handlers."""
|
452
|
+
return self._logger.handlers
|
453
|
+
|
454
|
+
def get_logger(self) -> _logging.Logger:
|
455
|
+
"""Get the underlying logging.Logger instance."""
|
456
|
+
return self._logger
|
457
|
+
|
458
|
+
|
459
|
+
# -----------------------------------------------------------------------------
|
460
|
+
# Factory
|
461
|
+
# -----------------------------------------------------------------------------
|
462
|
+
|
463
|
+
|
464
|
+
def create_logger_level(
|
465
|
+
name: str,
|
466
|
+
level: int,
|
467
|
+
color: Optional[str] = None,
|
468
|
+
style: Optional[str] = None,
|
469
|
+
) -> None:
|
470
|
+
"""
|
471
|
+
Create a custom logging level.
|
472
|
+
|
473
|
+
Args:
|
474
|
+
name: The name of the logging level (e.g., "TRACE", "SUCCESS")
|
475
|
+
level: The numeric level value (should be between existing levels)
|
476
|
+
color: Optional color for rich formatting (e.g., "green", "blue")
|
477
|
+
style: Optional style for rich formatting (e.g., "bold", "italic")
|
478
|
+
"""
|
479
|
+
# Convert name to uppercase for consistency
|
480
|
+
level_name = name.upper()
|
481
|
+
|
482
|
+
# Add the level to the logging module
|
483
|
+
_logging.addLevelName(level, level_name)
|
484
|
+
|
485
|
+
# Create a method on the Logger class for this level
|
486
|
+
def log_method(self, message, *args, **kwargs):
|
487
|
+
if self.isEnabledFor(level):
|
488
|
+
self._log(level, message, args, **kwargs)
|
489
|
+
|
490
|
+
# Add the method to the standard logging.Logger class
|
491
|
+
setattr(_logging.Logger, name.lower(), log_method)
|
492
|
+
|
493
|
+
# Store level info for potential rich formatting
|
494
|
+
if hasattr(_logging, "_custom_level_info"):
|
495
|
+
_logging._custom_level_info[level] = {
|
496
|
+
"name": level_name,
|
497
|
+
"color": color,
|
498
|
+
"style": style,
|
499
|
+
}
|
500
|
+
else:
|
501
|
+
_logging._custom_level_info = {
|
502
|
+
level: {"name": level_name, "color": color, "style": style}
|
503
|
+
}
|
504
|
+
|
505
|
+
|
506
|
+
def create_logger(
|
507
|
+
name: Optional[str] = None,
|
508
|
+
level: Optional[Union[str, int]] = None,
|
509
|
+
rich: bool = True,
|
510
|
+
display_all: bool = False,
|
511
|
+
levels: Optional[Dict[LoggerLevelName, LoggerLevelSettings]] = None,
|
512
|
+
) -> Logger:
|
513
|
+
"""
|
514
|
+
Get a logger instance.
|
515
|
+
|
516
|
+
Args:
|
517
|
+
name: Name for the logger. If None, uses caller's function name
|
518
|
+
level: Logging level. If None, defaults to "debug" if display_all else "warning"
|
519
|
+
rich: Whether to use rich formatting for output
|
520
|
+
display_all: If True, sets effective level to debug to show all messages
|
521
|
+
levels: Custom level styles to override defaults. Also can contain
|
522
|
+
custom levels.
|
523
|
+
|
524
|
+
Returns:
|
525
|
+
A Logger instance with the specified configuration.
|
526
|
+
"""
|
527
|
+
if name is None:
|
528
|
+
frame = inspect.currentframe()
|
529
|
+
if frame and frame.f_back:
|
530
|
+
name = frame.f_back.f_code.co_name
|
531
|
+
else:
|
532
|
+
name = "logger"
|
533
|
+
|
534
|
+
return Logger(name, level, rich, display_all, level_styles=levels)
|
@@ -0,0 +1,43 @@
|
|
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 ..based.utils import auto_create_lazy_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_lazy_loader(__all__)
|
39
|
+
|
40
|
+
|
41
|
+
def __dir__() -> list[str]:
|
42
|
+
"""Get the attributes of the pydantic module."""
|
43
|
+
return list(__all__)
|
@@ -0,0 +1,28 @@
|
|
1
|
+
"""hammad.pydantic.models"""
|
2
|
+
|
3
|
+
from typing import TYPE_CHECKING
|
4
|
+
from ...based.utils import auto_create_lazy_loader
|
5
|
+
|
6
|
+
if TYPE_CHECKING:
|
7
|
+
from .arbitrary_model import ArbitraryModel
|
8
|
+
from .cacheable_model import CacheableModel
|
9
|
+
from .fast_model import FastModel
|
10
|
+
from .function_model import FunctionModel
|
11
|
+
from .subscriptable_model import SubscriptableModel
|
12
|
+
|
13
|
+
|
14
|
+
__all__ = (
|
15
|
+
"ArbitraryModel",
|
16
|
+
"CacheableModel",
|
17
|
+
"FastModel",
|
18
|
+
"FunctionModel",
|
19
|
+
"SubscriptableModel",
|
20
|
+
)
|
21
|
+
|
22
|
+
|
23
|
+
__getattr__ = auto_create_lazy_loader(__all__)
|
24
|
+
|
25
|
+
|
26
|
+
def __dir__() -> list[str]:
|
27
|
+
"""Get the attributes of the models module."""
|
28
|
+
return list(__all__)
|
@@ -0,0 +1,46 @@
|
|
1
|
+
"""hammad.pydantic.models.arbitrary_model"""
|
2
|
+
|
3
|
+
from typing import Any, Dict
|
4
|
+
from pydantic import ConfigDict
|
5
|
+
|
6
|
+
from .subscriptable_model import SubscriptableModel
|
7
|
+
|
8
|
+
__all__ = ("ArbitraryModel",)
|
9
|
+
|
10
|
+
|
11
|
+
class ArbitraryModel(SubscriptableModel):
|
12
|
+
"""
|
13
|
+
A model that allows dynamic field assignment and access.
|
14
|
+
Perfect for handling arbitrary JSON data or when schema is unknown at compile time.
|
15
|
+
|
16
|
+
Usage:
|
17
|
+
>>> data = ArbitraryModel()
|
18
|
+
>>> data.name = "John"
|
19
|
+
>>> data.age = 30
|
20
|
+
>>> data.metadata = {"key": "value"}
|
21
|
+
>>> print(data.name) # John
|
22
|
+
>>> print(data["age"]) # 30
|
23
|
+
"""
|
24
|
+
|
25
|
+
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
26
|
+
|
27
|
+
def __init__(self, **data: Any):
|
28
|
+
super().__init__(**data)
|
29
|
+
# Store extra fields for easy access
|
30
|
+
self._arbitrary_fields: Dict[str, Any] = {}
|
31
|
+
|
32
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
33
|
+
if name.startswith("_") or name in self.__class__.model_fields:
|
34
|
+
super().__setattr__(name, value)
|
35
|
+
else:
|
36
|
+
# Store in dynamic fields and set normally
|
37
|
+
if hasattr(self, "_arbitrary_fields"):
|
38
|
+
self._arbitrary_fields[name] = value
|
39
|
+
super().__setattr__(name, value)
|
40
|
+
|
41
|
+
def to_dict(self) -> Dict[str, Any]:
|
42
|
+
"""Convert to dictionary including all dynamic fields."""
|
43
|
+
result = self.model_dump()
|
44
|
+
if hasattr(self, "_arbitrary_fields"):
|
45
|
+
result.update(self._arbitrary_fields)
|
46
|
+
return result
|