bear-utils 0.0.1__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.
- bear_utils/__init__.py +51 -0
- bear_utils/__main__.py +14 -0
- bear_utils/_internal/__init__.py +0 -0
- bear_utils/_internal/_version.py +1 -0
- bear_utils/_internal/cli.py +119 -0
- bear_utils/_internal/debug.py +174 -0
- bear_utils/ai/__init__.py +30 -0
- bear_utils/ai/ai_helpers/__init__.py +136 -0
- bear_utils/ai/ai_helpers/_common.py +19 -0
- bear_utils/ai/ai_helpers/_config.py +24 -0
- bear_utils/ai/ai_helpers/_parsers.py +194 -0
- bear_utils/ai/ai_helpers/_types.py +15 -0
- bear_utils/cache/__init__.py +131 -0
- bear_utils/cli/__init__.py +22 -0
- bear_utils/cli/_args.py +12 -0
- bear_utils/cli/_get_version.py +207 -0
- bear_utils/cli/commands.py +105 -0
- bear_utils/cli/prompt_helpers.py +186 -0
- bear_utils/cli/shell/__init__.py +1 -0
- bear_utils/cli/shell/_base_command.py +81 -0
- bear_utils/cli/shell/_base_shell.py +430 -0
- bear_utils/cli/shell/_common.py +19 -0
- bear_utils/cli/typer_bridge.py +90 -0
- bear_utils/config/__init__.py +13 -0
- bear_utils/config/config_manager.py +229 -0
- bear_utils/config/dir_manager.py +69 -0
- bear_utils/config/settings_manager.py +179 -0
- bear_utils/constants/__init__.py +90 -0
- bear_utils/constants/_exceptions.py +8 -0
- bear_utils/constants/_exit_code.py +60 -0
- bear_utils/constants/_http_status_code.py +37 -0
- bear_utils/constants/_lazy_typing.py +15 -0
- bear_utils/constants/_meta.py +196 -0
- bear_utils/constants/date_related.py +25 -0
- bear_utils/constants/time_related.py +24 -0
- bear_utils/database/__init__.py +8 -0
- bear_utils/database/_db_manager.py +98 -0
- bear_utils/events/__init__.py +18 -0
- bear_utils/events/events_class.py +52 -0
- bear_utils/events/events_module.py +74 -0
- bear_utils/extras/__init__.py +28 -0
- bear_utils/extras/_async_helpers.py +67 -0
- bear_utils/extras/_tools.py +185 -0
- bear_utils/extras/_zapper.py +399 -0
- bear_utils/extras/platform_utils.py +57 -0
- bear_utils/extras/responses/__init__.py +5 -0
- bear_utils/extras/responses/function_response.py +451 -0
- bear_utils/extras/wrappers/__init__.py +1 -0
- bear_utils/extras/wrappers/add_methods.py +100 -0
- bear_utils/extras/wrappers/string_io.py +46 -0
- bear_utils/files/__init__.py +6 -0
- bear_utils/files/file_handlers/__init__.py +5 -0
- bear_utils/files/file_handlers/_base_file_handler.py +107 -0
- bear_utils/files/file_handlers/file_handler_factory.py +280 -0
- bear_utils/files/file_handlers/json_file_handler.py +71 -0
- bear_utils/files/file_handlers/log_file_handler.py +40 -0
- bear_utils/files/file_handlers/toml_file_handler.py +76 -0
- bear_utils/files/file_handlers/txt_file_handler.py +76 -0
- bear_utils/files/file_handlers/yaml_file_handler.py +64 -0
- bear_utils/files/ignore_parser.py +293 -0
- bear_utils/graphics/__init__.py +6 -0
- bear_utils/graphics/bear_gradient.py +145 -0
- bear_utils/graphics/font/__init__.py +13 -0
- bear_utils/graphics/font/_raw_block_letters.py +463 -0
- bear_utils/graphics/font/_theme.py +31 -0
- bear_utils/graphics/font/_utils.py +220 -0
- bear_utils/graphics/font/block_font.py +192 -0
- bear_utils/graphics/font/glitch_font.py +63 -0
- bear_utils/graphics/image_helpers.py +45 -0
- bear_utils/gui/__init__.py +8 -0
- bear_utils/gui/gui_tools/__init__.py +10 -0
- bear_utils/gui/gui_tools/_settings.py +36 -0
- bear_utils/gui/gui_tools/_types.py +12 -0
- bear_utils/gui/gui_tools/qt_app.py +150 -0
- bear_utils/gui/gui_tools/qt_color_picker.py +130 -0
- bear_utils/gui/gui_tools/qt_file_handler.py +130 -0
- bear_utils/gui/gui_tools/qt_input_dialog.py +303 -0
- bear_utils/logger_manager/__init__.py +109 -0
- bear_utils/logger_manager/_common.py +63 -0
- bear_utils/logger_manager/_console_junk.py +135 -0
- bear_utils/logger_manager/_log_level.py +50 -0
- bear_utils/logger_manager/_styles.py +95 -0
- bear_utils/logger_manager/logger_protocol.py +42 -0
- bear_utils/logger_manager/loggers/__init__.py +1 -0
- bear_utils/logger_manager/loggers/_console.py +223 -0
- bear_utils/logger_manager/loggers/_level_sin.py +61 -0
- bear_utils/logger_manager/loggers/_logger.py +19 -0
- bear_utils/logger_manager/loggers/base_logger.py +244 -0
- bear_utils/logger_manager/loggers/base_logger.pyi +51 -0
- bear_utils/logger_manager/loggers/basic_logger/__init__.py +5 -0
- bear_utils/logger_manager/loggers/basic_logger/logger.py +80 -0
- bear_utils/logger_manager/loggers/basic_logger/logger.pyi +19 -0
- bear_utils/logger_manager/loggers/buffer_logger.py +57 -0
- bear_utils/logger_manager/loggers/console_logger.py +278 -0
- bear_utils/logger_manager/loggers/console_logger.pyi +50 -0
- bear_utils/logger_manager/loggers/fastapi_logger.py +333 -0
- bear_utils/logger_manager/loggers/file_logger.py +151 -0
- bear_utils/logger_manager/loggers/simple_logger.py +98 -0
- bear_utils/logger_manager/loggers/sub_logger.py +105 -0
- bear_utils/logger_manager/loggers/sub_logger.pyi +23 -0
- bear_utils/monitoring/__init__.py +13 -0
- bear_utils/monitoring/_common.py +28 -0
- bear_utils/monitoring/host_monitor.py +346 -0
- bear_utils/time/__init__.py +59 -0
- bear_utils-0.0.1.dist-info/METADATA +305 -0
- bear_utils-0.0.1.dist-info/RECORD +107 -0
- bear_utils-0.0.1.dist-info/WHEEL +4 -0
@@ -0,0 +1,135 @@
|
|
1
|
+
from collections.abc import Callable
|
2
|
+
from io import StringIO
|
3
|
+
from logging import Formatter, Handler, LogRecord
|
4
|
+
from logging.handlers import BufferingHandler
|
5
|
+
import threading
|
6
|
+
from typing import Any
|
7
|
+
|
8
|
+
from prompt_toolkit import print_formatted_text
|
9
|
+
from prompt_toolkit.output.defaults import create_output
|
10
|
+
from rich.text import Text
|
11
|
+
|
12
|
+
from bear_utils.constants.date_related import DATE_TIME_FORMAT
|
13
|
+
|
14
|
+
from ._common import SIMPLE_FORMAT, VERBOSE_CONSOLE_FORMAT, ExecValues
|
15
|
+
from ._styles import LoggerExtraInfo
|
16
|
+
|
17
|
+
|
18
|
+
def get_extra(record: LogRecord) -> LoggerExtraInfo:
|
19
|
+
"""Get extra information from the log record."""
|
20
|
+
extra: LoggerExtraInfo = {
|
21
|
+
"style_name": record.__dict__.get("style_name", ""),
|
22
|
+
"style": record.__dict__.get("style", ""),
|
23
|
+
"log_level": record.__dict__.get("log_level", ""),
|
24
|
+
"log_level_style": record.__dict__.get("log_level_style", ""),
|
25
|
+
"namespace": record.__dict__.get("namespace", ""),
|
26
|
+
}
|
27
|
+
return extra
|
28
|
+
|
29
|
+
|
30
|
+
def extract_exec_info(record: LogRecord) -> dict[str, ExecValues] | None:
|
31
|
+
"""Extract execution info from the log record."""
|
32
|
+
exec_values: dict[str, ExecValues] | None = record.__dict__.get("exec_values", {})
|
33
|
+
if exec_values is not None:
|
34
|
+
return exec_values
|
35
|
+
return None
|
36
|
+
|
37
|
+
|
38
|
+
class ConsoleHandler(Handler):
|
39
|
+
def __init__(self, print_func: Callable, buffer_output: Callable):
|
40
|
+
super().__init__()
|
41
|
+
self.print_func: Callable = print_func
|
42
|
+
self.buffer_func: Callable = buffer_output
|
43
|
+
|
44
|
+
def emit(self, record: LogRecord, return_str: bool = False) -> Any:
|
45
|
+
"""Emit a log record either to console or return as string.
|
46
|
+
|
47
|
+
Args:
|
48
|
+
record: The LogRecord to emit
|
49
|
+
return_str: If True, return formatted string instead of printing
|
50
|
+
|
51
|
+
Returns:
|
52
|
+
str if return_str=True, None otherwise
|
53
|
+
"""
|
54
|
+
formatted_msg: str = self.format(record)
|
55
|
+
extra: LoggerExtraInfo = get_extra(record)
|
56
|
+
exec_values: dict[str, ExecValues] | None = extract_exec_info(record)
|
57
|
+
exc_info: bool = bool(exec_values)
|
58
|
+
style_name = extra.get("style_name", "")
|
59
|
+
|
60
|
+
print_kwargs = {
|
61
|
+
"msg": formatted_msg,
|
62
|
+
"style": style_name,
|
63
|
+
"exc_info": exc_info if exc_info is not None else False,
|
64
|
+
"exec_values": exec_values,
|
65
|
+
"return_str": return_str,
|
66
|
+
}
|
67
|
+
if return_str:
|
68
|
+
return self.buffer_func(**print_kwargs)
|
69
|
+
|
70
|
+
return self.print_func(**print_kwargs)
|
71
|
+
|
72
|
+
|
73
|
+
class ConsoleFormatter(Formatter):
|
74
|
+
def __init__(self, fmt: str = SIMPLE_FORMAT, datefmt: str = DATE_TIME_FORMAT):
|
75
|
+
super().__init__(fmt=fmt, datefmt=datefmt)
|
76
|
+
self.log_format: str = fmt
|
77
|
+
|
78
|
+
def format(self, record: LogRecord) -> str:
|
79
|
+
extra: LoggerExtraInfo = get_extra(record)
|
80
|
+
if self.log_format == VERBOSE_CONSOLE_FORMAT:
|
81
|
+
log_level_color: str = extra["log_level_style"]
|
82
|
+
style_name: str = extra.get("style_name", "")
|
83
|
+
dynamic_format = self.log_format.format(
|
84
|
+
log_level_color,
|
85
|
+
style_name.upper(),
|
86
|
+
log_level_color,
|
87
|
+
)
|
88
|
+
temp_formatter = Formatter(fmt=dynamic_format, datefmt=self.datefmt)
|
89
|
+
return temp_formatter.format(record)
|
90
|
+
|
91
|
+
if self.log_format == SIMPLE_FORMAT:
|
92
|
+
record.msg = f"{record.msg}"
|
93
|
+
|
94
|
+
return super().format(record)
|
95
|
+
|
96
|
+
|
97
|
+
class ConsoleBuffering(BufferingHandler):
|
98
|
+
def __init__(
|
99
|
+
self,
|
100
|
+
capacity: int = 9999,
|
101
|
+
console_handler: ConsoleHandler | None = None,
|
102
|
+
return_auto: bool = False,
|
103
|
+
):
|
104
|
+
super().__init__(capacity=capacity)
|
105
|
+
# should come with a formatter before getting here
|
106
|
+
self.console_handler: ConsoleHandler = console_handler or ConsoleHandler(
|
107
|
+
print_func=lambda **kwargs: None, buffer_output=lambda **kwargs: ""
|
108
|
+
)
|
109
|
+
self._lock = threading.RLock()
|
110
|
+
self.flush_auto = return_auto
|
111
|
+
|
112
|
+
def flush_to_output(self) -> Text:
|
113
|
+
"""Flush all buffered records to the console handler."""
|
114
|
+
with self._lock:
|
115
|
+
output_buffer = StringIO()
|
116
|
+
output = create_output(stdout=output_buffer)
|
117
|
+
for record in self.buffer:
|
118
|
+
formatted_msg = self.console_handler.emit(record, return_str=True)
|
119
|
+
print_formatted_text(formatted_msg, output=output, end="\n")
|
120
|
+
output = output_buffer.getvalue()
|
121
|
+
output_buffer.close()
|
122
|
+
self.buffer.clear()
|
123
|
+
return Text.from_ansi(output)
|
124
|
+
|
125
|
+
def trigger_flush(self) -> None:
|
126
|
+
"""Immediately flush all buffered records to console."""
|
127
|
+
self.flush()
|
128
|
+
|
129
|
+
def flush(self) -> None:
|
130
|
+
"""Flush all buffered records to the console handler."""
|
131
|
+
if self.flush_auto:
|
132
|
+
with self._lock:
|
133
|
+
for record in self.buffer:
|
134
|
+
self.console_handler.emit(record)
|
135
|
+
self.buffer.clear()
|
@@ -0,0 +1,50 @@
|
|
1
|
+
from typing import Literal
|
2
|
+
|
3
|
+
from bear_utils.constants._meta import IntValue as Value, RichIntEnum
|
4
|
+
|
5
|
+
FAILURE: Literal[45] = 45
|
6
|
+
ERROR: Literal[40] = 40
|
7
|
+
WARNING: Literal[30] = 30
|
8
|
+
WARN: Literal[30] = WARNING
|
9
|
+
INFO: Literal[20] = 20
|
10
|
+
SUCCESS: Literal[15] = 15
|
11
|
+
DEBUG: Literal[10] = 10
|
12
|
+
VERBOSE: Literal[5] = 5
|
13
|
+
NOTSET: Literal[0] = 0
|
14
|
+
|
15
|
+
|
16
|
+
class LogLevel(RichIntEnum):
|
17
|
+
"""Enumeration for logging levels."""
|
18
|
+
|
19
|
+
NOTSET = Value(NOTSET, "NOTSET", default=NOTSET)
|
20
|
+
VERBOSE = Value(VERBOSE, "VERBOSE", default=VERBOSE)
|
21
|
+
DEBUG = Value(DEBUG, "DEBUG", default=DEBUG)
|
22
|
+
INFO = Value(INFO, "INFO", default=INFO)
|
23
|
+
WARNING = Value(WARNING, "WARNING", default=WARNING)
|
24
|
+
ERROR = Value(ERROR, "ERROR", default=ERROR)
|
25
|
+
FAILURE = Value(FAILURE, "FAILURE", default=FAILURE)
|
26
|
+
SUCCESS = Value(SUCCESS, "SUCCESS", default=SUCCESS)
|
27
|
+
|
28
|
+
|
29
|
+
level_to_name = {
|
30
|
+
FAILURE: "FAILURE",
|
31
|
+
ERROR: "ERROR",
|
32
|
+
WARNING: "WARNING",
|
33
|
+
INFO: "INFO",
|
34
|
+
SUCCESS: "SUCCESS",
|
35
|
+
DEBUG: "DEBUG",
|
36
|
+
VERBOSE: "VERBOSE",
|
37
|
+
NOTSET: "NOTSET",
|
38
|
+
}
|
39
|
+
|
40
|
+
name_to_level = {
|
41
|
+
"FAILURE": FAILURE,
|
42
|
+
"ERROR": ERROR,
|
43
|
+
"WARN": WARNING,
|
44
|
+
"WARNING": WARNING,
|
45
|
+
"INFO": INFO,
|
46
|
+
"SUCCESS": SUCCESS,
|
47
|
+
"DEBUG": DEBUG,
|
48
|
+
"VERBOSE": VERBOSE,
|
49
|
+
"NOTSET": NOTSET,
|
50
|
+
}
|
@@ -0,0 +1,95 @@
|
|
1
|
+
from typing import NotRequired, Required, TypedDict
|
2
|
+
|
3
|
+
from rich.theme import Theme
|
4
|
+
|
5
|
+
from bear_utils.logger_manager._log_level import (
|
6
|
+
DEBUG,
|
7
|
+
ERROR,
|
8
|
+
FAILURE,
|
9
|
+
INFO,
|
10
|
+
SUCCESS,
|
11
|
+
VERBOSE,
|
12
|
+
WARNING,
|
13
|
+
)
|
14
|
+
|
15
|
+
|
16
|
+
class LoggerExtraInfo(TypedDict):
|
17
|
+
"""Type definition for extra info that can be added to log records."""
|
18
|
+
|
19
|
+
style_name: Required[str]
|
20
|
+
style: Required[str]
|
21
|
+
namespace: NotRequired[str]
|
22
|
+
log_level: Required[int]
|
23
|
+
log_level_style: Required[str]
|
24
|
+
|
25
|
+
|
26
|
+
LOGGER_METHODS: dict[str, LoggerExtraInfo] = {
|
27
|
+
"info": {
|
28
|
+
"style_name": "info",
|
29
|
+
"style": "dim green",
|
30
|
+
"log_level": INFO,
|
31
|
+
"log_level_style": "black on white",
|
32
|
+
},
|
33
|
+
"debug": {
|
34
|
+
"style_name": "debug",
|
35
|
+
"style": "bold blue",
|
36
|
+
"log_level": DEBUG,
|
37
|
+
"log_level_style": "black on blue",
|
38
|
+
},
|
39
|
+
"warning": {
|
40
|
+
"style_name": "warning",
|
41
|
+
"style": "bold yellow",
|
42
|
+
"log_level": WARNING,
|
43
|
+
"log_level_style": "yellow on black",
|
44
|
+
},
|
45
|
+
"error": {
|
46
|
+
"style_name": "error",
|
47
|
+
"style": "bold red",
|
48
|
+
"log_level": ERROR,
|
49
|
+
"log_level_style": "bold white on red",
|
50
|
+
},
|
51
|
+
"exception": {
|
52
|
+
"style_name": "exception",
|
53
|
+
"style": "bold red",
|
54
|
+
"log_level": ERROR,
|
55
|
+
"log_level_style": "bold white on red",
|
56
|
+
},
|
57
|
+
"success": {
|
58
|
+
"style_name": "success",
|
59
|
+
"style": "bold green",
|
60
|
+
"log_level": SUCCESS,
|
61
|
+
"log_level_style": "black on bright_green",
|
62
|
+
},
|
63
|
+
"failure": {
|
64
|
+
"style_name": "failure",
|
65
|
+
"style": "bold red underline",
|
66
|
+
"log_level": FAILURE,
|
67
|
+
"log_level_style": "bold red on white",
|
68
|
+
},
|
69
|
+
"verbose": {
|
70
|
+
"style_name": "verbose",
|
71
|
+
"style": "bold blue",
|
72
|
+
"log_level": VERBOSE,
|
73
|
+
"log_level_style": "black on bright_blue",
|
74
|
+
},
|
75
|
+
}
|
76
|
+
|
77
|
+
|
78
|
+
def get_method(name: str) -> LoggerExtraInfo:
|
79
|
+
"""Get the name info from the logger methods.
|
80
|
+
|
81
|
+
Args:
|
82
|
+
name (str): The name of the logger method.
|
83
|
+
|
84
|
+
Returns:
|
85
|
+
LoggerExtraInfo | dict: The info of the logger method or an empty dict if not found.
|
86
|
+
"""
|
87
|
+
if not LOGGER_METHODS.get(name):
|
88
|
+
raise ValueError(f"Logger method '{name}' does not exist. Available methods: {list(LOGGER_METHODS.keys())}")
|
89
|
+
return LOGGER_METHODS[name]
|
90
|
+
|
91
|
+
|
92
|
+
DEFAULT_STYLES: dict[str, str] = {**{method: info["style"] for method, info in LOGGER_METHODS.items()}}
|
93
|
+
"""Just the styles of the logger methods, used to create the theme."""
|
94
|
+
|
95
|
+
DEFAULT_THEME = Theme(styles=DEFAULT_STYLES)
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# ruff: noqa: D102
|
2
|
+
"""A protocol for logging classes for general use."""
|
3
|
+
|
4
|
+
from typing import Protocol, runtime_checkable
|
5
|
+
|
6
|
+
|
7
|
+
@runtime_checkable
|
8
|
+
class LoggerProtocol(Protocol):
|
9
|
+
"""A protocol for logging classes with extra methods."""
|
10
|
+
|
11
|
+
def debug(self, msg: object, *args, **kwargs) -> None: ...
|
12
|
+
|
13
|
+
def info(self, msg: object, *args, **kwargs) -> None: ...
|
14
|
+
|
15
|
+
def warning(self, msg: object, *args, **kwargs) -> None: ...
|
16
|
+
|
17
|
+
def error(self, msg: object, *args, **kwargs) -> None: ...
|
18
|
+
|
19
|
+
def verbose(self, msg: object, *args, **kwargs) -> None: ...
|
20
|
+
|
21
|
+
def success(self, msg: object, *args, **kwargs) -> None: ...
|
22
|
+
|
23
|
+
def failure(self, msg: object, *args, **kwargs) -> None: ...
|
24
|
+
|
25
|
+
|
26
|
+
@runtime_checkable
|
27
|
+
class AsyncLoggerProtocol(Protocol):
|
28
|
+
"""A protocol for asynchronous logging classes."""
|
29
|
+
|
30
|
+
async def debug(self, msg: object, *args, **kwargs) -> None: ...
|
31
|
+
|
32
|
+
async def info(self, msg: object, *args, **kwargs) -> None: ...
|
33
|
+
|
34
|
+
async def warning(self, msg: object, *args, **kwargs) -> None: ...
|
35
|
+
|
36
|
+
async def error(self, msg: object, *args, **kwargs) -> None: ...
|
37
|
+
|
38
|
+
async def verbose(self, msg: object, *args, **kwargs) -> None: ...
|
39
|
+
|
40
|
+
async def success(self, msg: object, *args, **kwargs) -> None: ...
|
41
|
+
|
42
|
+
async def failure(self, msg: object, *args, **kwargs) -> None: ...
|
@@ -0,0 +1 @@
|
|
1
|
+
"""A module with all of the loggers used in the logger manager."""
|
@@ -0,0 +1,223 @@
|
|
1
|
+
from collections.abc import Callable, Mapping
|
2
|
+
from datetime import datetime
|
3
|
+
import sys
|
4
|
+
import threading
|
5
|
+
from time import monotonic
|
6
|
+
from typing import TYPE_CHECKING, Literal, TextIO, cast
|
7
|
+
|
8
|
+
from rich._log_render import FormatTimeCallable, LogRender
|
9
|
+
from rich._null_file import NULL_FILE
|
10
|
+
from rich.console import COLOR_SYSTEMS, Console, ConsoleThreadLocals, RenderHook, _is_jupyter, detect_legacy_windows
|
11
|
+
from rich.emoji import EmojiVariant
|
12
|
+
from rich.highlighter import NullHighlighter, ReprHighlighter
|
13
|
+
from rich.style import StyleType
|
14
|
+
from rich.text import Text
|
15
|
+
from rich.theme import Theme, ThemeStack
|
16
|
+
from rich.themes import DEFAULT
|
17
|
+
|
18
|
+
from bear_utils.logger_manager._log_level import LogLevel
|
19
|
+
|
20
|
+
if TYPE_CHECKING:
|
21
|
+
from rich.color import ColorSystem
|
22
|
+
from rich.live import Live
|
23
|
+
from rich.segment import Segment
|
24
|
+
|
25
|
+
HighlighterType = Callable[[str, Text], Text] | Text
|
26
|
+
JUPYTER_DEFAULT_COLUMNS = 115
|
27
|
+
JUPYTER_DEFAULT_LINES = 100
|
28
|
+
WINDOWS = sys.platform == "win32"
|
29
|
+
|
30
|
+
_null_highlighter = NullHighlighter()
|
31
|
+
|
32
|
+
|
33
|
+
class LogConsole[T: TextIO](Console):
|
34
|
+
"""A Console from Rich that has added methods named after the logger methods."""
|
35
|
+
|
36
|
+
def __init__(
|
37
|
+
self,
|
38
|
+
*,
|
39
|
+
color_system: Literal["auto", "standard", "256", "truecolor", "windows"] | None = "auto",
|
40
|
+
force_terminal: bool | None = None,
|
41
|
+
force_jupyter: bool | None = None,
|
42
|
+
force_interactive: bool | None = None,
|
43
|
+
soft_wrap: bool = False,
|
44
|
+
theme: Theme | None = None,
|
45
|
+
stderr: bool = False,
|
46
|
+
file: T | None = None,
|
47
|
+
quiet: bool = False,
|
48
|
+
width: int | None = None,
|
49
|
+
height: int | None = None,
|
50
|
+
style: StyleType | None = None,
|
51
|
+
no_color: bool | None = None,
|
52
|
+
tab_size: int = 8,
|
53
|
+
record: bool = False,
|
54
|
+
markup: bool = True,
|
55
|
+
emoji: bool = True,
|
56
|
+
emoji_variant: EmojiVariant | None = None,
|
57
|
+
highlight: bool = True,
|
58
|
+
log_time: bool = True,
|
59
|
+
log_path: bool = True,
|
60
|
+
log_time_format: str | FormatTimeCallable = "[%X]",
|
61
|
+
highlighter: HighlighterType | None = ReprHighlighter(), # type: ignore[assignment] # noqa: B008
|
62
|
+
legacy_windows: bool | None = None,
|
63
|
+
safe_box: bool = True,
|
64
|
+
get_datetime: Callable[[], datetime] | None = None,
|
65
|
+
get_time: Callable[[], float] | None = None,
|
66
|
+
_environ: Mapping[str, str] | None = None,
|
67
|
+
level: str | int | LogLevel = LogLevel.INFO,
|
68
|
+
):
|
69
|
+
# Copy of os.environ allows us to replace it for testing
|
70
|
+
if _environ is not None:
|
71
|
+
self._environ = _environ
|
72
|
+
|
73
|
+
self.is_jupyter = _is_jupyter() if force_jupyter is None else force_jupyter
|
74
|
+
if self.is_jupyter:
|
75
|
+
if width is None:
|
76
|
+
jupyter_columns = self._environ.get("JUPYTER_COLUMNS")
|
77
|
+
if jupyter_columns is not None and jupyter_columns.isdigit():
|
78
|
+
width = int(jupyter_columns)
|
79
|
+
else:
|
80
|
+
width = JUPYTER_DEFAULT_COLUMNS
|
81
|
+
if height is None:
|
82
|
+
jupyter_lines = self._environ.get("JUPYTER_LINES")
|
83
|
+
if jupyter_lines is not None and jupyter_lines.isdigit():
|
84
|
+
height = int(jupyter_lines)
|
85
|
+
else:
|
86
|
+
height = JUPYTER_DEFAULT_LINES
|
87
|
+
|
88
|
+
self.tab_size = tab_size
|
89
|
+
self.record = record
|
90
|
+
self._markup = markup
|
91
|
+
self._emoji = emoji
|
92
|
+
self._emoji_variant: EmojiVariant | None = emoji_variant
|
93
|
+
self._highlight = highlight
|
94
|
+
self.legacy_windows: bool = (
|
95
|
+
(detect_legacy_windows() and not self.is_jupyter) if legacy_windows is None else legacy_windows
|
96
|
+
)
|
97
|
+
|
98
|
+
if width is None:
|
99
|
+
columns = self._environ.get("COLUMNS")
|
100
|
+
if columns is not None and columns.isdigit():
|
101
|
+
width = int(columns) - self.legacy_windows
|
102
|
+
if height is None:
|
103
|
+
lines = self._environ.get("LINES")
|
104
|
+
if lines is not None and lines.isdigit():
|
105
|
+
height = int(lines)
|
106
|
+
|
107
|
+
self.soft_wrap = soft_wrap
|
108
|
+
self._width = width
|
109
|
+
self._height = height
|
110
|
+
|
111
|
+
self._color_system: ColorSystem | None
|
112
|
+
|
113
|
+
self._force_terminal = None
|
114
|
+
if force_terminal is not None:
|
115
|
+
self._force_terminal = force_terminal
|
116
|
+
|
117
|
+
self._file: T | None = file
|
118
|
+
self.quiet = quiet
|
119
|
+
self.stderr = stderr
|
120
|
+
|
121
|
+
if color_system is None:
|
122
|
+
self._color_system = None
|
123
|
+
elif color_system == "auto":
|
124
|
+
self._color_system = self._detect_color_system()
|
125
|
+
else:
|
126
|
+
self._color_system = COLOR_SYSTEMS[color_system]
|
127
|
+
|
128
|
+
self._lock = threading.RLock()
|
129
|
+
self._log_render = LogRender(
|
130
|
+
show_time=log_time,
|
131
|
+
show_path=log_path,
|
132
|
+
time_format=log_time_format,
|
133
|
+
)
|
134
|
+
self.highlighter: HighlighterType = highlighter or _null_highlighter # type: ignore[assignment]
|
135
|
+
self.safe_box = safe_box
|
136
|
+
self.get_datetime = get_datetime or datetime.now
|
137
|
+
self.get_time = get_time or monotonic
|
138
|
+
self.style = style
|
139
|
+
self.no_color = no_color if no_color is not None else self._environ.get("NO_COLOR", "") != ""
|
140
|
+
self.is_interactive = (
|
141
|
+
(self.is_terminal and not self.is_dumb_terminal) if force_interactive is None else force_interactive
|
142
|
+
)
|
143
|
+
|
144
|
+
self._record_buffer_lock = threading.RLock()
|
145
|
+
self._thread_locals = ConsoleThreadLocals(theme_stack=ThemeStack(DEFAULT if theme is None else theme))
|
146
|
+
self._record_buffer: list[Segment] = []
|
147
|
+
self._render_hooks: list[RenderHook] = []
|
148
|
+
self._live: Live | None = None
|
149
|
+
self._is_alt_screen = False
|
150
|
+
self.level: LogLevel = LogLevel.get(level, default=LogLevel.INFO)
|
151
|
+
|
152
|
+
@property
|
153
|
+
def file(self) -> T:
|
154
|
+
"""Get the file object to write to."""
|
155
|
+
file = self._file or (sys.stderr if self.stderr else sys.stdout)
|
156
|
+
file = getattr(file, "rich_proxied_file", file)
|
157
|
+
if file is None:
|
158
|
+
file = NULL_FILE
|
159
|
+
return cast("T", file)
|
160
|
+
|
161
|
+
@file.setter
|
162
|
+
def file(self, new_file: T) -> None: # type: ignore[override]
|
163
|
+
"""Set a new file object."""
|
164
|
+
self._file = new_file
|
165
|
+
|
166
|
+
def _log(self, level: LogLevel, msg: object, *args, **kwargs) -> None:
|
167
|
+
"""Log a message at the specified level.
|
168
|
+
|
169
|
+
Args:
|
170
|
+
level (LogLevel): The log level for the message. We aren't using this parameter in the current implementation,
|
171
|
+
msg (object): The message to log.
|
172
|
+
*args: Additional positional arguments.
|
173
|
+
**kwargs: Additional keyword arguments.
|
174
|
+
"""
|
175
|
+
if level.value >= self.level.value:
|
176
|
+
"""Log a message at the specified level."""
|
177
|
+
with self._lock:
|
178
|
+
if not self.quiet:
|
179
|
+
self.log(msg, *args, **kwargs)
|
180
|
+
|
181
|
+
def info(self, msg: object, *args, **kwargs) -> None:
|
182
|
+
"""Log an informational message to the console."""
|
183
|
+
self._log(LogLevel.INFO, msg, *args, **kwargs)
|
184
|
+
|
185
|
+
def warning(self, msg: object, *args, **kwargs) -> None:
|
186
|
+
"""Log a warning message to the console."""
|
187
|
+
self._log(LogLevel.WARNING, msg, *args, **kwargs)
|
188
|
+
|
189
|
+
def error(self, msg: object, *args, **kwargs) -> None:
|
190
|
+
"""Log an error message to the console."""
|
191
|
+
self._log(LogLevel.ERROR, msg, *args, **kwargs)
|
192
|
+
|
193
|
+
def debug(self, msg: object, *args, **kwargs) -> None:
|
194
|
+
"""Log a debug message to the console."""
|
195
|
+
self._log(LogLevel.DEBUG, msg, *args, **kwargs)
|
196
|
+
|
197
|
+
def verbose(self, msg: object, *args, **kwargs) -> None:
|
198
|
+
"""Log a verbose message to the console."""
|
199
|
+
self._log(LogLevel.VERBOSE, msg, *args, **kwargs)
|
200
|
+
|
201
|
+
def success(self, msg: object, *args, **kwargs) -> None:
|
202
|
+
"""Log a success message to the console."""
|
203
|
+
self._log(LogLevel.SUCCESS, msg, *args, **kwargs)
|
204
|
+
|
205
|
+
def failure(self, msg: object, *args, **kwargs) -> None:
|
206
|
+
"""Log a failure message to the console."""
|
207
|
+
self._log(LogLevel.FAILURE, msg, *args, **kwargs)
|
208
|
+
|
209
|
+
def exception(self, msg: object, *args, **kwargs) -> None:
|
210
|
+
"""Log an exception message to the console."""
|
211
|
+
self._log(LogLevel.ERROR, msg, *args, **kwargs)
|
212
|
+
|
213
|
+
|
214
|
+
if __name__ == "__main__":
|
215
|
+
from io import StringIO
|
216
|
+
|
217
|
+
console = LogConsole(file=StringIO())
|
218
|
+
|
219
|
+
console.info("This is an info message")
|
220
|
+
|
221
|
+
value = console.file
|
222
|
+
|
223
|
+
print(value.getvalue()) # Print the captured log messages from StringIO
|
@@ -0,0 +1,61 @@
|
|
1
|
+
"""This is some terrible sinning that should not exist, don't look at it. It doesn't exist if you don't look at it."""
|
2
|
+
|
3
|
+
from logging import addLevelName
|
4
|
+
import threading
|
5
|
+
from typing import Literal
|
6
|
+
|
7
|
+
ERROR: Literal[40] = 40
|
8
|
+
WARNING: Literal[30] = 30
|
9
|
+
WARN: Literal[30] = WARNING
|
10
|
+
INFO: Literal[20] = 20
|
11
|
+
DEBUG: Literal[10] = 10
|
12
|
+
NOTSET: Literal[0] = 0
|
13
|
+
|
14
|
+
level_to_name = {
|
15
|
+
ERROR: "ERROR",
|
16
|
+
WARNING: "WARNING",
|
17
|
+
INFO: "INFO",
|
18
|
+
DEBUG: "DEBUG",
|
19
|
+
NOTSET: "NOTSET",
|
20
|
+
}
|
21
|
+
|
22
|
+
name_to_level = {
|
23
|
+
"ERROR": ERROR,
|
24
|
+
"WARN": WARNING,
|
25
|
+
"WARNING": WARNING,
|
26
|
+
"INFO": INFO,
|
27
|
+
"DEBUG": DEBUG,
|
28
|
+
"NOTSET": NOTSET,
|
29
|
+
}
|
30
|
+
|
31
|
+
_lock = threading.RLock()
|
32
|
+
|
33
|
+
|
34
|
+
def lvl_exists(level: int | str) -> bool:
|
35
|
+
"""Check if a logging level already exists."""
|
36
|
+
with _lock:
|
37
|
+
level = check_level(level, fail=False)
|
38
|
+
return level in level_to_name
|
39
|
+
|
40
|
+
|
41
|
+
def add_level_name(level: int, name: str) -> None:
|
42
|
+
"""Add a custom logging level name."""
|
43
|
+
with _lock:
|
44
|
+
if level in level_to_name:
|
45
|
+
raise ValueError(f"Level {level} already exists with name {level_to_name[level]}")
|
46
|
+
level_to_name[level] = name.upper()
|
47
|
+
name_to_level[name.upper()] = level
|
48
|
+
addLevelName(level=level, levelName=name)
|
49
|
+
|
50
|
+
|
51
|
+
def check_level(level: int | str | None, fail: bool = True) -> int:
|
52
|
+
"""Validate and normalize logging level to integer."""
|
53
|
+
if isinstance(level, str) and level.upper() in name_to_level:
|
54
|
+
return name_to_level[level.upper()]
|
55
|
+
if isinstance(level, int) and level in level_to_name:
|
56
|
+
return level
|
57
|
+
if fail:
|
58
|
+
if not isinstance(level, (int | str)):
|
59
|
+
raise TypeError(f"Level must be int or str, got {type(level).__name__}: {level!r}")
|
60
|
+
raise ValueError(f"Invalid logging level: {level!r}. Valid levels are: {list(name_to_level.keys())}")
|
61
|
+
return 999 # Return a high value to indicate invalid level
|
@@ -0,0 +1,19 @@
|
|
1
|
+
from logging import Logger
|
2
|
+
|
3
|
+
from bear_utils.logger_manager._log_level import FAILURE, SUCCESS, VERBOSE
|
4
|
+
|
5
|
+
|
6
|
+
class LoggerExtra(Logger):
|
7
|
+
"""A custom logger that just includes a few extra methods."""
|
8
|
+
|
9
|
+
def verbose(self, msg: object, *args, **kwargs) -> None:
|
10
|
+
"""Log a verbose message."""
|
11
|
+
self.log(VERBOSE, msg, *args, **kwargs)
|
12
|
+
|
13
|
+
def success(self, msg: object, *args, **kwargs) -> None:
|
14
|
+
"""Log a success message."""
|
15
|
+
self.log(SUCCESS, msg, *args, **kwargs)
|
16
|
+
|
17
|
+
def failure(self, msg: object, *args, **kwargs) -> None:
|
18
|
+
"""Log a failure message."""
|
19
|
+
self.log(FAILURE, msg, *args, **kwargs)
|