bear-utils 0.8.12__py3-none-any.whl → 0.8.13__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.
@@ -1,7 +1,6 @@
1
- from enum import Enum
2
1
  import inspect
3
2
  from types import TracebackType
4
- from typing import Required, TypedDict
3
+ from typing import Literal, Required, TypedDict
5
4
 
6
5
 
7
6
  class ExecValues(TypedDict, total=True):
@@ -10,12 +9,6 @@ class ExecValues(TypedDict, total=True):
10
9
  exc_traceback: Required[TracebackType]
11
10
 
12
11
 
13
- VERBOSE_FORMAT = "%(asctime)s |%(levelname)s| {%(module)s|%(funcName)s|%(lineno)d} %(message)s"
14
- VERBOSE_CONSOLE_FORMAT = "%(asctime)s <[{}]{}[/{}]> %(message)s"
15
- SIMPLE_FORMAT = "%(message)s"
16
- FIVE_MEGABYTES = 1024 * 1024 * 5
17
-
18
-
19
12
  class StackLevelTracker:
20
13
  STACK_LEVEL_OFFSET = 1
21
14
 
@@ -47,18 +40,24 @@ class StackLevelTracker:
47
40
  return self.end_depth - (self.start_depth + self.STACK_LEVEL_OFFSET)
48
41
 
49
42
 
50
- class LogLevel(Enum):
51
- """Enumeration for log levels."""
52
-
53
- VERBOSE = "VERBOSE"
54
- DEBUG = "DEBUG"
55
- INFO = "INFO"
56
- WARNING = "WARNING"
57
- ERROR = "ERROR"
58
-
43
+ VERBOSE_FORMAT = "%(asctime)s |%(levelname)s| {%(module)s|%(funcName)s|%(lineno)d} %(message)s"
44
+ VERBOSE_CONSOLE_FORMAT = "%(asctime)s <[{}]{}[/{}]> %(message)s"
45
+ SIMPLE_FORMAT = "%(message)s"
46
+ FIVE_MEGABYTES = 1024 * 1024 * 5
59
47
 
60
- VERBOSE = LogLevel.VERBOSE
61
- DEBUG = LogLevel.DEBUG
62
- INFO = LogLevel.INFO
63
- WARNING = LogLevel.WARNING
64
- ERROR = LogLevel.ERROR
48
+ VERBOSE: Literal[5] = 5
49
+ SUCCESS: Literal[15] = 15
50
+ FAILURE: Literal[45] = 45
51
+
52
+
53
+ __all__ = [
54
+ "FAILURE",
55
+ "FIVE_MEGABYTES",
56
+ "SIMPLE_FORMAT",
57
+ "SUCCESS",
58
+ "VERBOSE",
59
+ "VERBOSE_CONSOLE_FORMAT",
60
+ "VERBOSE_FORMAT",
61
+ "ExecValues",
62
+ "StackLevelTracker",
63
+ ]
@@ -0,0 +1,126 @@
1
+ from functools import cached_property
2
+ from typing import Any, Literal, overload
3
+
4
+ from pydantic import BaseModel, Field, field_validator
5
+
6
+ from bear_utils.extras.wrappers.add_methods import add_comparison_methods
7
+
8
+ FAILURE: Literal[45] = 45
9
+ ERROR: Literal[40] = 40
10
+ WARNING: Literal[30] = 30
11
+ WARN: Literal[30] = WARNING
12
+ INFO: Literal[20] = 20
13
+ SUCCESS: Literal[15] = 15
14
+ DEBUG: Literal[10] = 10
15
+ VERBOSE: Literal[5] = 5
16
+ NOTSET: Literal[0] = 0
17
+
18
+
19
+ level_to_name = {
20
+ FAILURE: "FAILURE",
21
+ ERROR: "ERROR",
22
+ WARNING: "WARNING",
23
+ INFO: "INFO",
24
+ SUCCESS: "SUCCESS",
25
+ DEBUG: "DEBUG",
26
+ VERBOSE: "VERBOSE",
27
+ NOTSET: "NOTSET",
28
+ }
29
+
30
+ name_to_level = {
31
+ "FAILURE": FAILURE,
32
+ "ERROR": ERROR,
33
+ "WARN": WARNING,
34
+ "WARNING": WARNING,
35
+ "INFO": INFO,
36
+ "SUCCESS": SUCCESS,
37
+ "DEBUG": DEBUG,
38
+ "VERBOSE": VERBOSE,
39
+ "NOTSET": NOTSET,
40
+ }
41
+
42
+
43
+ @add_comparison_methods("value")
44
+ class LogLevel(BaseModel):
45
+ """Model to represent a logging level."""
46
+
47
+ name: str = Field(default="NOTSET", description="Name of the logging level")
48
+ value: int = Field(default=NOTSET, description="Numeric value of the logging level")
49
+
50
+ @field_validator("value")
51
+ @classmethod
52
+ def validate_value(cls, value: int) -> int:
53
+ if value not in level_to_name:
54
+ raise ValueError(f"Invalid logging level value: {value!r}. Valid values are: {list(level_to_name.keys())}")
55
+ return value
56
+
57
+ @field_validator("name")
58
+ @classmethod
59
+ def validate_name(cls, name: str) -> str:
60
+ if name not in name_to_level:
61
+ raise ValueError(f"Invalid logging level name: {name!r}. Valid names are: {list(name_to_level.keys())}")
62
+ return name
63
+
64
+
65
+ class LogLevels(BaseModel):
66
+ """Model to represent a collection of logging levels."""
67
+
68
+ notset: LogLevel = Field(default=LogLevel(name="NOTSET", value=NOTSET))
69
+ verbose: LogLevel = Field(default=LogLevel(name="VERBOSE", value=VERBOSE))
70
+ debug: LogLevel = Field(default=LogLevel(name="DEBUG", value=DEBUG))
71
+ info: LogLevel = Field(default=LogLevel(name="INFO", value=INFO))
72
+ success: LogLevel = Field(default=LogLevel(name="SUCCESS", value=SUCCESS))
73
+ warning: LogLevel = Field(default=LogLevel(name="WARNING", value=WARNING))
74
+ error: LogLevel = Field(default=LogLevel(name="ERROR", value=ERROR))
75
+ failure: LogLevel = Field(default=LogLevel(name="FAILURE", value=FAILURE))
76
+
77
+ model_config = {"arbitrary_types_allowed": True, "extra": "forbid"}
78
+
79
+ @cached_property
80
+ def keys(self) -> list[str]:
81
+ """Get the names of all logging levels."""
82
+ return [key.upper() for key in LogLevels.model_fields]
83
+
84
+ @cached_property
85
+ def levels(self): # noqa: ANN202
86
+ item_dict: dict[str, Any] = {
87
+ level_name.lower(): getattr(self, level_name.lower()).value for level_name in self.keys
88
+ }
89
+ return item_dict.items()
90
+
91
+ def get_int(self, name: str) -> int:
92
+ """Get the integer value of a logging level by name."""
93
+ if not hasattr(self, name):
94
+ raise ValueError(f"Invalid logging level name: {name!r}. Valid names are: {self.keys}")
95
+ return getattr(self, name).value
96
+
97
+ @overload
98
+ def get(self, v: LogLevel) -> LogLevel: ...
99
+
100
+ @overload
101
+ def get(self, v: str) -> LogLevel: ...
102
+
103
+ @overload
104
+ def get(self, v: int) -> LogLevel: ...
105
+
106
+ def get(self, v: int | str | LogLevel) -> LogLevel:
107
+ """Get a logging level by name or value."""
108
+ if isinstance(v, LogLevel):
109
+ return v
110
+ if isinstance(v, str) and v.lower() in self.keys:
111
+ return getattr(self, v.lower())
112
+ if isinstance(v, int):
113
+ for level_name, level_value in self.levels:
114
+ if level_value == v:
115
+ return getattr(self, level_name.lower())
116
+ return self.notset # Default to NOTSET if no match found
117
+
118
+ def get_name(self, value: int) -> str:
119
+ """Get the name of a logging level by its integer value."""
120
+ for level_name, level_value in self.levels:
121
+ if level_value == value:
122
+ return level_name
123
+ raise ValueError(f"Invalid logging level value: {value!r}. Valid values are: {self.keys}")
124
+
125
+
126
+ log_levels = LogLevels()
@@ -1,11 +1,16 @@
1
- from logging import DEBUG, ERROR, INFO, WARNING
2
- from typing import Literal, NotRequired, Required, TypedDict
1
+ from typing import NotRequired, Required, TypedDict
3
2
 
4
3
  from rich.theme import Theme
5
4
 
6
- VERBOSE: Literal[5] = 5
7
- SUCCESS: Literal[15] = 15
8
- FAILURE: Literal[45] = 45
5
+ from bear_utils.logger_manager._log_level import (
6
+ DEBUG,
7
+ ERROR,
8
+ FAILURE,
9
+ INFO,
10
+ SUCCESS,
11
+ VERBOSE,
12
+ WARNING,
13
+ )
9
14
 
10
15
 
11
16
  class LoggerExtraInfo(TypedDict):
@@ -18,6 +18,7 @@ level_to_name = {
18
18
  DEBUG: "DEBUG",
19
19
  NOTSET: "NOTSET",
20
20
  }
21
+
21
22
  name_to_level = {
22
23
  "ERROR": ERROR,
23
24
  "WARN": WARNING,
@@ -0,0 +1,5 @@
1
+ """A basic logger to have around just in case the user doesn't already have one."""
2
+
3
+ from .logger import BasicLogger
4
+
5
+ __all__ = ["BasicLogger"]
@@ -0,0 +1,80 @@
1
+ """Basic logger using the Rich library."""
2
+
3
+ from collections.abc import Callable
4
+
5
+ from rich import inspect
6
+ from rich.console import Console
7
+ from rich.theme import Theme
8
+
9
+ THEME: dict[str, str] = {
10
+ "info": "dim green",
11
+ "debug": "bold blue",
12
+ "warning": "bold yellow",
13
+ "error": "bold red",
14
+ "exception": "bold red",
15
+ "success": "bold green",
16
+ "failure": "bold red underline",
17
+ "verbose": "bold blue",
18
+ }
19
+
20
+
21
+ class BasicLogger:
22
+ """A basic logger that uses the Rich library to print messages to the console."""
23
+
24
+ def __init__(self) -> None:
25
+ """Initialize the BasicLogger with a Rich Console instance."""
26
+ self.console = Console(theme=Theme(THEME))
27
+ for level in THEME:
28
+ method = self.replacement_method(level)
29
+ setattr(self, level, method)
30
+
31
+ def replacement_method(self, level: str) -> Callable:
32
+ """Create a method that logs messages at the specified level."""
33
+
34
+ def method(msg: object, **kwargs) -> None:
35
+ """Log a message at the specified level with the given style.
36
+
37
+ Args:
38
+ msg (object): The message to log.
39
+ **kwargs: Additional keyword arguments for formatting.
40
+ """
41
+ self.log(level, msg, **kwargs)
42
+
43
+ return method
44
+
45
+ def log(self, level: str, msg: object, **kwargs) -> None:
46
+ """Log a message at the specified level.
47
+
48
+ Args:
49
+ level (str): The logging level (e.g., 'info', 'debug', 'warning', etc.).
50
+ msg (object): The message to log.
51
+ **kwargs: Additional keyword arguments for formatting.
52
+ """
53
+ self.console.print(msg, style=level, **kwargs)
54
+
55
+ def print(self, msg: object, **kwargs) -> None:
56
+ """Print a message to the console with the specified style.
57
+
58
+ Args:
59
+ msg (object): The message to print.
60
+ **kwargs: Additional keyword arguments for formatting.
61
+ """
62
+ self.console.print(msg, **kwargs)
63
+
64
+ def inspect(self, obj: object, **kwargs) -> None:
65
+ """Inspect an object and print its details to the console.
66
+
67
+ Args:
68
+ obj (object): The object to inspect.
69
+ **kwargs: Additional keyword arguments for formatting.
70
+ """
71
+ inspect(obj, console=self.console, **kwargs)
72
+
73
+ def print_json(self, data: object, **kwargs) -> None:
74
+ """Print a JSON object to the console.
75
+
76
+ Args:
77
+ data (object): The JSON data to print.
78
+ **kwargs: Additional keyword arguments for formatting.
79
+ """
80
+ self.console.print_json(data=data, **kwargs)
@@ -0,0 +1,19 @@
1
+ from collections.abc import Callable
2
+
3
+ from rich.console import Console
4
+
5
+ class BasicLogger:
6
+ def __init__(self, console: Console | None = None) -> None: ...
7
+ def info(self, msg: object, **kwargs) -> None: ...
8
+ def debug(self, msg: object, **kwargs) -> None: ...
9
+ def warning(self, msg: object, **kwargs) -> None: ...
10
+ def error(self, msg: object, **kwargs) -> None: ...
11
+ def exception(self, msg: object, **kwargs) -> None: ...
12
+ def success(self, msg: object, **kwargs) -> None: ...
13
+ def failure(self, msg: object, **kwargs) -> None: ...
14
+ def verbose(self, msg: object, **kwargs) -> None: ...
15
+ def print(self, msg: object, style: str | None = None, **kwargs) -> None: ...
16
+ def log(self, level: str, msg: object, **kwargs) -> None: ...
17
+ def inspect(self, obj: object, **kwargs) -> None: ...
18
+ def print_json(self, data: object, **kwargs) -> None: ...
19
+ def replacement_method(self, level: str) -> Callable: ...
@@ -6,23 +6,42 @@ import threading
6
6
  from typing import TYPE_CHECKING
7
7
 
8
8
  from fastapi import FastAPI
9
- from httpx import Client
9
+ from httpx import AsyncClient
10
10
  from pydantic import BaseModel
11
11
  import uvicorn
12
12
 
13
13
  from bear_utils.constants import SERVER_OK
14
- from bear_utils.logger_manager._common import DEBUG, ERROR, INFO, VERBOSE, WARNING, LogLevel
14
+ from bear_utils.logger_manager._log_level import LogLevel, log_levels
15
15
  from bear_utils.time import EpochTimestamp
16
16
 
17
17
  if TYPE_CHECKING:
18
18
  from httpx import Response
19
19
 
20
20
 
21
+ VERBOSE: LogLevel = log_levels.get("VERBOSE")
22
+ DEBUG: LogLevel = log_levels.get("DEBUG")
23
+ INFO: LogLevel = log_levels.get("INFO")
24
+ WARNING: LogLevel = log_levels.get("WARNING")
25
+ ERROR: LogLevel = log_levels.get("ERROR")
26
+
27
+
28
+ def get_level(level: str) -> int:
29
+ """Get the numeric value for a given level string."""
30
+ return log_levels.get_int(level)
31
+
32
+
33
+ def get_name(level: int) -> str:
34
+ """Get the name of a logging level by its integer value."""
35
+ return log_levels.get_name(level)
36
+
37
+
21
38
  class LogRequest(BaseModel):
22
39
  """Request model for logging messages."""
23
40
 
24
41
  level: str
25
42
  message: str
43
+ args: list[str] = []
44
+ kwargs: dict[str, str] = {}
26
45
 
27
46
 
28
47
  class LocalLoggingServer:
@@ -33,44 +52,51 @@ class LocalLoggingServer:
33
52
  host: str = "localhost",
34
53
  port: int = 8080,
35
54
  log_file: str = "server.log",
36
- min_level: LogLevel = VERBOSE,
55
+ min_level: LogLevel | int | str = DEBUG,
37
56
  ) -> None:
38
57
  """Initialize the logging server."""
39
58
  self.host: str = host
40
59
  self.port: int = port
41
60
  self.log_file: str = log_file
42
- self.min_level: LogLevel = min_level
61
+ self.min_level: LogLevel = log_levels.get(min_level)
43
62
  self.app = FastAPI()
44
63
  self.server_thread = None
45
64
  self._running = False
46
65
  self._setup_routes()
66
+ self.buffer: list[str] = []
47
67
 
48
68
  def _setup_routes(self) -> None:
49
69
  """Set up the FastAPI routes for logging and health check."""
50
70
 
51
71
  @self.app.post("/log")
52
72
  async def log_message(request: LogRequest) -> dict[str, str]:
53
- self.write_log(request.level, request.message)
73
+ self.write_log(request.level, request.message, *request.args, **request.kwargs)
54
74
  return {"status": "success"}
55
75
 
56
76
  @self.app.get("/health")
57
77
  async def health_check() -> dict[str, str]:
58
78
  return {"status": "healthy"}
59
79
 
60
- def write_log(self, level: str, message: str) -> None:
80
+ def write_log(self, level: str, message: str, *args: str, end: str = "\n", **kwargs: str) -> None:
61
81
  """Write a log entry to the file - same logic as original logger."""
82
+ timestamp: str = EpochTimestamp.now().to_string()
62
83
  try:
63
- log_level = LogLevel(level)
64
- if log_level.value >= self.min_level.value:
65
- timestamp = EpochTimestamp.now().to_string()
66
- log_entry = f"[{timestamp}] {level}: {message}\n"
67
- print(log_entry, file=sys.stderr)
84
+ level_t: LogLevel = log_levels.get(level)
85
+ if level_t.value >= self.min_level.value:
86
+ log_entry: str = f"[{timestamp}] {level}: {message}"
87
+ self.buffer.append(log_entry)
88
+ if args:
89
+ self.buffer.append(f"{end}".join(str(arg) for arg in args))
90
+ if kwargs:
91
+ for key, value in kwargs.items():
92
+ self.buffer.append(f"{key}={value}{end}")
68
93
  with open(self.log_file, "a", encoding="utf-8") as f:
69
- f.write(log_entry)
94
+ f.writelines(self.buffer)
95
+ print(f"{end}".join(self.buffer), file=sys.stderr)
70
96
  except Exception:
71
- # Fallback to stderr like original
72
- timestamp = EpochTimestamp.now().to_string()
73
97
  print(f"[{timestamp}] {level}: {message}", file=sys.stderr)
98
+ finally:
99
+ self.buffer.clear()
74
100
 
75
101
  def start(self) -> None:
76
102
  """Start the logging server in a separate thread."""
@@ -98,61 +124,64 @@ class LocalLoggingServer:
98
124
  class ServerLogger:
99
125
  """Logger that calls HTTP endpoints but behaves like SimpleLogger."""
100
126
 
101
- def __init__(self, server_url: str = "http://localhost:8080", min_level: LogLevel = INFO) -> None:
127
+ def __init__(self, server_url: str = "http://localhost:8080", min_level: LogLevel | int | str = INFO) -> None:
102
128
  """Initialize the ServerLogger."""
103
129
  self.server_url: str = server_url.rstrip("/")
104
- self.min_level: LogLevel = min_level
105
- self.client = Client(timeout=5.0)
130
+ self.min_level: LogLevel = log_levels.get(min_level)
131
+ self.client: AsyncClient = AsyncClient(timeout=5.0)
132
+ self.loop: asyncio.AbstractEventLoop = asyncio.get_event_loop()
106
133
 
107
- def _log(self, level: LogLevel, msg: object, *args, **kwargs) -> None:
134
+ async def _log(self, lvl: LogLevel, msg: object, *args, **kwargs) -> None:
108
135
  """Same interface as SimpleLogger._log but calls HTTP endpoint."""
109
- if isinstance(level, LogLevel) and level.value >= self.min_level.value:
136
+ if lvl.value >= self.min_level.value:
110
137
  try:
111
- response: Response = self.client.post(
138
+ response: Response = await self.client.post(
112
139
  url=f"{self.server_url}/log",
113
140
  json={
114
- "level": level.value,
141
+ "level": lvl.value,
115
142
  "message": msg,
143
+ "args": args,
144
+ "kwargs": kwargs,
116
145
  },
117
146
  )
118
147
  if response.status_code != SERVER_OK:
119
- self._fallback_log(level, msg, *args, **kwargs)
148
+ self._fallback_log(lvl, msg, *args, **kwargs)
120
149
  except Exception:
121
- self._fallback_log(level, msg, *args, **kwargs)
150
+ self._fallback_log(lvl, msg, *args, **kwargs)
122
151
 
123
- def _fallback_log(self, level: LogLevel, msg: object, *args, **kwargs) -> None:
152
+ def _fallback_log(self, lvl: LogLevel, msg: object, *args, **kwargs) -> None:
124
153
  """Fallback - same as original SimpleLogger._log."""
125
154
  timestamp: str = EpochTimestamp.now().to_string()
126
- print(f"[{timestamp}] {level.value}: {msg}", file=sys.stderr)
155
+ print(f"[{timestamp}] {lvl.value}: {msg}", file=sys.stderr)
127
156
  if args:
128
157
  print(" ".join(str(arg) for arg in args), file=sys.stderr)
129
158
  if kwargs:
130
159
  for key, value in kwargs.items():
131
160
  print(f"{key}={value}", file=sys.stderr)
132
161
 
133
- def verbose(self, msg: object, *args, **kwargs) -> None:
162
+ async def verbose(self, msg: object, *args, **kwargs) -> None:
134
163
  """Log a verbose message."""
135
- self._log(VERBOSE, msg, *args, **kwargs)
164
+ await self._log(VERBOSE, msg, *args, **kwargs)
136
165
 
137
- def debug(self, msg: object, *args, **kwargs) -> None:
166
+ async def debug(self, msg: object, *args, **kwargs) -> None:
138
167
  """Log a debug message."""
139
- self._log(DEBUG, msg, *args, **kwargs)
168
+ await self._log(DEBUG, msg, *args, **kwargs)
140
169
 
141
- def info(self, msg: object, *args, **kwargs) -> None:
170
+ async def info(self, msg: object, *args, **kwargs) -> None:
142
171
  """Log an info message."""
143
- self._log(INFO, msg, *args, **kwargs)
172
+ await self._log(INFO, msg, *args, **kwargs)
144
173
 
145
- def warning(self, msg: object, *args, **kwargs) -> None:
174
+ async def warning(self, msg: object, *args, **kwargs) -> None:
146
175
  """Log a warning message."""
147
- self._log(WARNING, msg, *args, **kwargs)
176
+ await self._log(WARNING, msg, *args, **kwargs)
148
177
 
149
- def error(self, msg: object, *args, **kwargs) -> None:
178
+ async def error(self, msg: object, *args, **kwargs) -> None:
150
179
  """Log an error message."""
151
- self._log(ERROR, msg, *args, **kwargs)
180
+ await self._log(ERROR, msg, *args, **kwargs)
152
181
 
153
- def close(self) -> None:
182
+ async def close(self) -> None:
154
183
  """Close the HTTP client."""
155
- self.client.close()
184
+ await self.client.aclose()
156
185
 
157
186
 
158
187
  if __name__ == "__main__":
@@ -3,50 +3,67 @@
3
3
  import sys
4
4
  from typing import TextIO
5
5
 
6
- from bear_utils.logger_manager._common import DEBUG, ERROR, INFO, VERBOSE, WARNING, LogLevel
6
+ from bear_utils.logger_manager._log_level import LogLevel, log_levels
7
7
  from bear_utils.time import EpochTimestamp
8
8
 
9
9
  STDOUT: TextIO = sys.stdout
10
10
  STDERR: TextIO = sys.stderr
11
11
 
12
+ VERBOSE: LogLevel = log_levels.get("VERBOSE")
13
+ DEBUG: LogLevel = log_levels.get("DEBUG")
14
+ INFO: LogLevel = log_levels.get("INFO")
15
+ WARNING: LogLevel = log_levels.get("WARNING")
16
+ ERROR: LogLevel = log_levels.get("ERROR")
17
+
12
18
 
13
19
  class SimpleLogger:
14
20
  """A simple logger that writes messages to stderr (or STDOUT if preferred) with a timestamp."""
15
21
 
16
- def __init__(self, min_level: LogLevel = INFO, redirect: TextIO = STDERR) -> None:
22
+ def __init__(self, min_level: int | str | LogLevel = INFO, redirect: TextIO = STDERR) -> None:
17
23
  """Initialize the logger with a minimum log level."""
18
- self.min_level: LogLevel = min_level
24
+ self.min_level: LogLevel = log_levels.get(min_level)
19
25
  self.redirect: TextIO = redirect
26
+ self.buffer: list[str] = []
27
+
28
+ def _log(self, level: LogLevel, msg: object, end: str = "\n", *args, **kwargs) -> None:
29
+ timestamp: str = EpochTimestamp.now().to_string()
30
+ try:
31
+ self.buffer.append(f"[{timestamp}] {level.value}: {msg}")
32
+ if args:
33
+ self.buffer.append(" ".join(str(arg) for arg in args))
34
+ if kwargs:
35
+ for key, value in kwargs.items():
36
+ self.buffer.append(f"{key}={value}")
37
+ print(f"{end}".join(self.buffer), file=self.redirect)
38
+ except Exception as e:
39
+ print(f"[{timestamp}] {level.value}: {msg} - Error: {e}", file=self.redirect)
40
+ finally:
41
+ self.buffer.clear()
20
42
 
21
- def _log(self, level: LogLevel, msg: object, *args, **kwargs) -> None:
22
- if isinstance(level, LogLevel) and level.value >= self.min_level.value:
23
- timestamp: str = EpochTimestamp.now().to_string()
24
- print(f"[{timestamp}] {level.value}: {msg}", file=self.redirect)
25
- if args:
26
- print(" ".join(str(arg) for arg in args), file=self.redirect)
27
- if kwargs:
28
- for key, value in kwargs.items():
29
- print(f"{key}={value}", file=self.redirect)
43
+ def log(self, level: LogLevel, msg: object, *args, **kwargs) -> None:
44
+ """Log a message at the specified level."""
45
+ if level.value >= self.min_level.value:
46
+ self._log(level, msg, *args, **kwargs)
30
47
 
31
48
  def verbose(self, msg: object, *args, **kwargs) -> None:
32
49
  """Alias for debug level logging."""
33
- self._log(VERBOSE, msg, *args, **kwargs)
50
+ self.log(VERBOSE, msg, *args, **kwargs)
34
51
 
35
52
  def debug(self, msg: object, *args, **kwargs) -> None:
36
53
  """Log a debug message."""
37
- self._log(DEBUG, msg, *args, **kwargs)
54
+ self.log(DEBUG, msg, *args, **kwargs)
38
55
 
39
56
  def info(self, msg: object, *args, **kwargs) -> None:
40
57
  """Log an info message."""
41
- self._log(INFO, msg, *args, **kwargs)
58
+ self.log(INFO, msg, *args, **kwargs)
42
59
 
43
60
  def warning(self, msg: object, *args, **kwargs) -> None:
44
61
  """Log a warning message."""
45
- self._log(WARNING, msg, *args, **kwargs)
62
+ self.log(WARNING, msg, *args, **kwargs)
46
63
 
47
64
  def error(self, msg: object, *args, **kwargs) -> None:
48
65
  """Log an error message."""
49
- self._log(ERROR, msg, *args, **kwargs)
66
+ self.log(ERROR, msg, *args, **kwargs)
50
67
 
51
68
 
52
69
  # Example usage:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bear-utils
3
- Version: 0.8.12
3
+ Version: 0.8.13
4
4
  Summary: Various utilities for Bear programmers, including a rich logging utility, a disk cache, and a SQLite database wrapper amongst other things.
5
5
  Author-email: chaz <bright.lid5647@fastmail.com>
6
6
  Requires-Python: >=3.12
@@ -22,7 +22,7 @@ Requires-Dist: toml>=0.10.2
22
22
  Requires-Dist: uvicorn>=0.35.0
23
23
  Description-Content-Type: text/markdown
24
24
 
25
- # Bear Utils v# Bear Utils v0.8.12
25
+ # Bear Utils v# Bear Utils v0.8.13
26
26
 
27
27
  Personal set of tools and utilities for Python projects, focusing on modularity and ease of use. This library includes components for caching, database management, logging, time handling, file operations, CLI prompts, image processing, clipboard interaction, gradient utilities, event systems, and async helpers.
28
28
 
@@ -62,26 +62,30 @@ bear_utils/gui/gui_tools/qt_color_picker.py,sha256=5NtLiBHk5r9Goma_oiymriH49D_JG
62
62
  bear_utils/gui/gui_tools/qt_file_handler.py,sha256=FgWdS-9WnjVuyGIC8V30ByDCBeJGZKGc8KRTy34SFfI,4404
63
63
  bear_utils/gui/gui_tools/qt_input_dialog.py,sha256=5KaCM9q8kmoy-Fd0j1FbXIVrLlE7W47NEGdhsWtvKwQ,9281
64
64
  bear_utils/logger_manager/__init__.py,sha256=ei8686gFb8vBJ75LTbYizAnJmEIb1zEmowWQkRqfahY,3453
65
- bear_utils/logger_manager/_common.py,sha256=As8Ysq8_mgeR1xRyPshkdHewAPAFkUaF3ZEfIsH-ZiM,1895
65
+ bear_utils/logger_manager/_common.py,sha256=kPTE4e0FKI8IBW1B5X7jJPVJtqgJtKYsX8gy8obg9OQ,1866
66
66
  bear_utils/logger_manager/_console_junk.py,sha256=2fwiYjZZps3GrH5An7aU3Bgvb_aAJiqNzTnKaku6m-0,4916
67
- bear_utils/logger_manager/_styles.py,sha256=3A30TrvPSOm1h2IfHgdDEUc0WP71zWZDGCHrCRofdjc,2523
67
+ bear_utils/logger_manager/_log_level.py,sha256=DBTOQdETazx9jK5rrvsjlEr96ciuB0A1q5GjzKp4UFA,4200
68
+ bear_utils/logger_manager/_styles.py,sha256=1mYHnENhLUWHB2QSpT9CB3_dzgBjhBxM1sh5UGEUULU,2527
68
69
  bear_utils/logger_manager/logger_protocol.py,sha256=v6rGMcyUGmt08dlAKUrWWwqqogtzAeaE4SXmt76Vcf0,629
69
70
  bear_utils/logger_manager/loggers/__init__.py,sha256=ashcnkvQIUQDLbUtU6QILkMjP_fMaeHAN1w7pHLWqQk,67
70
- bear_utils/logger_manager/loggers/_level_sin.py,sha256=Ht39WzNKLfKM0NbY4mM39VKedwdmwA34Hw3w9W4zWq4,1867
71
+ bear_utils/logger_manager/loggers/_level_sin.py,sha256=xykZNZKeHw9UH2ng3UAWgwgzPzA5JIkfkXJQGatsXcI,1868
71
72
  bear_utils/logger_manager/loggers/base_logger.py,sha256=VNb_zj450qdvZ6oVsdPxQ7nwoAW9uOf2Jaaiw0msoVQ,9981
72
73
  bear_utils/logger_manager/loggers/base_logger.pyi,sha256=ApSDj_fH12tvwNQcyZSTtWEWwbOP4FmkJUEZ1ih2qWk,2666
73
74
  bear_utils/logger_manager/loggers/buffer_logger.py,sha256=wF8s4jyajbfandkb3ZcTdMGjEjefn8H9Jxn4b74VYDg,1700
74
75
  bear_utils/logger_manager/loggers/console_logger.py,sha256=wysIThH_sn4QpQQmOTFCFhTwiom0pNGOZp_iXaEJcxo,10082
75
76
  bear_utils/logger_manager/loggers/console_logger.pyi,sha256=SCEn_TCNVM7GyxL0ObjW7PCWcgsiUsXbktrRqts2qLM,1873
76
- bear_utils/logger_manager/loggers/fastapi_logger.py,sha256=K5MiUBypHzoISbO0QmBRKASntmm6ngwc78grFHdtfJM,6197
77
+ bear_utils/logger_manager/loggers/fastapi_logger.py,sha256=n8puzrVIjIpzbv6If1SQVm41rADzMONML5p90chE3SY,7377
77
78
  bear_utils/logger_manager/loggers/file_logger.py,sha256=gZEAybd2Yjhh1Zvetl4doaKv8EKW7_hc5yImlS8M9eA,4943
78
- bear_utils/logger_manager/loggers/simple_logger.py,sha256=qYvUEBPwDxdVf_GHMI5RCM-EXW5AP3ZKt4Ml8Hj31qI,2228
79
+ bear_utils/logger_manager/loggers/simple_logger.py,sha256=SbV2S7KuMsPbIn0K45BwigPH7S9Db8uo3EPPNhObV0c,2888
79
80
  bear_utils/logger_manager/loggers/sub_logger.py,sha256=5C_8b7juSMbqMIvmWaoC1JklL4pDETfqkMtvpQyO9X4,3501
80
81
  bear_utils/logger_manager/loggers/sub_logger.pyi,sha256=yFTRo99ztzsCaRQ5Oz0bPAPwB_dtvmvR2Al7hIs_doY,1001
82
+ bear_utils/logger_manager/loggers/basic_logger/__init__.py,sha256=pEUkCpmt79zUVEE8P_OW55LdSu1hXLtpv6Lo2KsbIVo,144
83
+ bear_utils/logger_manager/loggers/basic_logger/logger.py,sha256=Wytf3QIxZjgCBCJuB3bLqyqQ9ONU7O4WzEHP6DNJT9E,2609
84
+ bear_utils/logger_manager/loggers/basic_logger/logger.pyi,sha256=VN4DcqegS6RTUn66TwI1QKq2TSuRLDz1gf3ttpjkOPo,948
81
85
  bear_utils/monitoring/__init__.py,sha256=9DKNIWTp_voLnaWgiP-wJ-o_N0hYixo-MzjUmg8RUvI,240
82
86
  bear_utils/monitoring/_common.py,sha256=LYQFxgTP9fk0cH71IQTuGwBYYPWCqHP_mMRNecoD76M,657
83
87
  bear_utils/monitoring/host_monitor.py,sha256=e0TYRJw9iDj5Ga6y3ck1TBFEeH42Cax5mQYaNU8yams,13241
84
88
  bear_utils/time/__init__.py,sha256=VctjJG17SyEHAFXytI1sZrOrq7zm3hVenIDOJFdaMN0,1424
85
- bear_utils-0.8.12.dist-info/METADATA,sha256=mc6wyLhigVowKwYH4lZbFSZgNnj2LBOd27FAOHCAtHA,8699
86
- bear_utils-0.8.12.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
87
- bear_utils-0.8.12.dist-info/RECORD,,
89
+ bear_utils-0.8.13.dist-info/METADATA,sha256=Pi8ZiQ0ovpAROUIa_Lece_5-ggSkfRYlKMq8m2H24Qg,8699
90
+ bear_utils-0.8.13.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
91
+ bear_utils-0.8.13.dist-info/RECORD,,