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.
Files changed (107) hide show
  1. bear_utils/__init__.py +51 -0
  2. bear_utils/__main__.py +14 -0
  3. bear_utils/_internal/__init__.py +0 -0
  4. bear_utils/_internal/_version.py +1 -0
  5. bear_utils/_internal/cli.py +119 -0
  6. bear_utils/_internal/debug.py +174 -0
  7. bear_utils/ai/__init__.py +30 -0
  8. bear_utils/ai/ai_helpers/__init__.py +136 -0
  9. bear_utils/ai/ai_helpers/_common.py +19 -0
  10. bear_utils/ai/ai_helpers/_config.py +24 -0
  11. bear_utils/ai/ai_helpers/_parsers.py +194 -0
  12. bear_utils/ai/ai_helpers/_types.py +15 -0
  13. bear_utils/cache/__init__.py +131 -0
  14. bear_utils/cli/__init__.py +22 -0
  15. bear_utils/cli/_args.py +12 -0
  16. bear_utils/cli/_get_version.py +207 -0
  17. bear_utils/cli/commands.py +105 -0
  18. bear_utils/cli/prompt_helpers.py +186 -0
  19. bear_utils/cli/shell/__init__.py +1 -0
  20. bear_utils/cli/shell/_base_command.py +81 -0
  21. bear_utils/cli/shell/_base_shell.py +430 -0
  22. bear_utils/cli/shell/_common.py +19 -0
  23. bear_utils/cli/typer_bridge.py +90 -0
  24. bear_utils/config/__init__.py +13 -0
  25. bear_utils/config/config_manager.py +229 -0
  26. bear_utils/config/dir_manager.py +69 -0
  27. bear_utils/config/settings_manager.py +179 -0
  28. bear_utils/constants/__init__.py +90 -0
  29. bear_utils/constants/_exceptions.py +8 -0
  30. bear_utils/constants/_exit_code.py +60 -0
  31. bear_utils/constants/_http_status_code.py +37 -0
  32. bear_utils/constants/_lazy_typing.py +15 -0
  33. bear_utils/constants/_meta.py +196 -0
  34. bear_utils/constants/date_related.py +25 -0
  35. bear_utils/constants/time_related.py +24 -0
  36. bear_utils/database/__init__.py +8 -0
  37. bear_utils/database/_db_manager.py +98 -0
  38. bear_utils/events/__init__.py +18 -0
  39. bear_utils/events/events_class.py +52 -0
  40. bear_utils/events/events_module.py +74 -0
  41. bear_utils/extras/__init__.py +28 -0
  42. bear_utils/extras/_async_helpers.py +67 -0
  43. bear_utils/extras/_tools.py +185 -0
  44. bear_utils/extras/_zapper.py +399 -0
  45. bear_utils/extras/platform_utils.py +57 -0
  46. bear_utils/extras/responses/__init__.py +5 -0
  47. bear_utils/extras/responses/function_response.py +451 -0
  48. bear_utils/extras/wrappers/__init__.py +1 -0
  49. bear_utils/extras/wrappers/add_methods.py +100 -0
  50. bear_utils/extras/wrappers/string_io.py +46 -0
  51. bear_utils/files/__init__.py +6 -0
  52. bear_utils/files/file_handlers/__init__.py +5 -0
  53. bear_utils/files/file_handlers/_base_file_handler.py +107 -0
  54. bear_utils/files/file_handlers/file_handler_factory.py +280 -0
  55. bear_utils/files/file_handlers/json_file_handler.py +71 -0
  56. bear_utils/files/file_handlers/log_file_handler.py +40 -0
  57. bear_utils/files/file_handlers/toml_file_handler.py +76 -0
  58. bear_utils/files/file_handlers/txt_file_handler.py +76 -0
  59. bear_utils/files/file_handlers/yaml_file_handler.py +64 -0
  60. bear_utils/files/ignore_parser.py +293 -0
  61. bear_utils/graphics/__init__.py +6 -0
  62. bear_utils/graphics/bear_gradient.py +145 -0
  63. bear_utils/graphics/font/__init__.py +13 -0
  64. bear_utils/graphics/font/_raw_block_letters.py +463 -0
  65. bear_utils/graphics/font/_theme.py +31 -0
  66. bear_utils/graphics/font/_utils.py +220 -0
  67. bear_utils/graphics/font/block_font.py +192 -0
  68. bear_utils/graphics/font/glitch_font.py +63 -0
  69. bear_utils/graphics/image_helpers.py +45 -0
  70. bear_utils/gui/__init__.py +8 -0
  71. bear_utils/gui/gui_tools/__init__.py +10 -0
  72. bear_utils/gui/gui_tools/_settings.py +36 -0
  73. bear_utils/gui/gui_tools/_types.py +12 -0
  74. bear_utils/gui/gui_tools/qt_app.py +150 -0
  75. bear_utils/gui/gui_tools/qt_color_picker.py +130 -0
  76. bear_utils/gui/gui_tools/qt_file_handler.py +130 -0
  77. bear_utils/gui/gui_tools/qt_input_dialog.py +303 -0
  78. bear_utils/logger_manager/__init__.py +109 -0
  79. bear_utils/logger_manager/_common.py +63 -0
  80. bear_utils/logger_manager/_console_junk.py +135 -0
  81. bear_utils/logger_manager/_log_level.py +50 -0
  82. bear_utils/logger_manager/_styles.py +95 -0
  83. bear_utils/logger_manager/logger_protocol.py +42 -0
  84. bear_utils/logger_manager/loggers/__init__.py +1 -0
  85. bear_utils/logger_manager/loggers/_console.py +223 -0
  86. bear_utils/logger_manager/loggers/_level_sin.py +61 -0
  87. bear_utils/logger_manager/loggers/_logger.py +19 -0
  88. bear_utils/logger_manager/loggers/base_logger.py +244 -0
  89. bear_utils/logger_manager/loggers/base_logger.pyi +51 -0
  90. bear_utils/logger_manager/loggers/basic_logger/__init__.py +5 -0
  91. bear_utils/logger_manager/loggers/basic_logger/logger.py +80 -0
  92. bear_utils/logger_manager/loggers/basic_logger/logger.pyi +19 -0
  93. bear_utils/logger_manager/loggers/buffer_logger.py +57 -0
  94. bear_utils/logger_manager/loggers/console_logger.py +278 -0
  95. bear_utils/logger_manager/loggers/console_logger.pyi +50 -0
  96. bear_utils/logger_manager/loggers/fastapi_logger.py +333 -0
  97. bear_utils/logger_manager/loggers/file_logger.py +151 -0
  98. bear_utils/logger_manager/loggers/simple_logger.py +98 -0
  99. bear_utils/logger_manager/loggers/sub_logger.py +105 -0
  100. bear_utils/logger_manager/loggers/sub_logger.pyi +23 -0
  101. bear_utils/monitoring/__init__.py +13 -0
  102. bear_utils/monitoring/_common.py +28 -0
  103. bear_utils/monitoring/host_monitor.py +346 -0
  104. bear_utils/time/__init__.py +59 -0
  105. bear_utils-0.0.1.dist-info/METADATA +305 -0
  106. bear_utils-0.0.1.dist-info/RECORD +107 -0
  107. 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)