logger-36 2025.18__py3-none-any.whl → 2025.19__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.
logger_36/api/content.py CHANGED
@@ -6,7 +6,6 @@ SEE COPYRIGHT NOTICE BELOW
6
6
 
7
7
  from logger_36.constant.message import LINE_INDENT # noqa
8
8
  from logger_36.task.format.message import MessageWithActualExpected # noqa
9
- from logger_36.task.format.rule import Rule, RuleAsText # noqa
10
9
 
11
10
  """
12
11
  COPYRIGHT NOTICE
@@ -24,7 +24,7 @@ LEVEL_COLOR: dict[int, str | style_t] = {
24
24
  }
25
25
  ACTUAL_COLOR = LEVEL_COLOR[l.CRITICAL]
26
26
  EXPECTED_COLOR = "green3"
27
- DATE_TIME_COLOR = "sky_blue3"
27
+ RULE_COLOR = "sky_blue3"
28
28
 
29
29
  ALTERNATIVE_BACKGROUND_FOR_LIGHT = style_t(bgcolor=color_t.from_rgb(230, 230, 230))
30
30
  ALTERNATIVE_BACKGROUND_FOR_DARK = style_t(bgcolor=color_t.from_rgb(25, 25, 25))
@@ -8,7 +8,6 @@ import logging as l
8
8
  import sys as s
9
9
  import typing as h
10
10
 
11
- from logger_36.task.format.rule import RuleAsText
12
11
  from logger_36.type.handler import handler_t as base_t
13
12
 
14
13
 
@@ -21,18 +20,18 @@ class console_handler_t(base_t):
21
20
  name: str | None = None,
22
21
  message_width: int = -1,
23
22
  level: int = l.NOTSET,
24
- formatter: l.Formatter | None = None,
25
23
  **_,
26
24
  ) -> h.Self:
27
25
  """"""
28
- return cls(name, message_width, level, formatter)
26
+ return cls(name, message_width, None, level)
29
27
 
30
28
  def emit(self, record: l.LogRecord, /) -> None:
31
29
  """"""
32
- output = self.MessageFromRecord(
33
- record, RuleAsText, line_width=self.message_width
34
- )
35
- s.__stdout__.write(output[0] + "\n")
30
+ s.__stdout__.write(self.MessageFromRecord(record)[0] + "\n")
31
+
32
+ def EmitAsIs(self, message: str, /) -> None:
33
+ """"""
34
+ s.__stdout__.write(message + "\n")
36
35
 
37
36
 
38
37
  """
@@ -10,6 +10,7 @@ import typing as h
10
10
  from rich.console import Console as console_t # noqa
11
11
  from rich.console import RenderableType as renderable_t # noqa
12
12
  from rich.markup import escape as EscapedVersion # noqa
13
+ from rich.rule import Rule as rule_t # noqa
13
14
  from rich.text import Text as text_t # noqa
14
15
  from rich.traceback import install as InstallTracebackHandler # noqa
15
16
 
@@ -17,15 +18,14 @@ from logger_36.catalog.config.console_rich import (
17
18
  ACTUAL_COLOR,
18
19
  ALTERNATIVE_BACKGROUND_FOR_DARK,
19
20
  ALTERNATIVE_BACKGROUND_FOR_LIGHT,
20
- DATE_TIME_COLOR,
21
21
  EXPECTED_COLOR,
22
22
  GRAY_COLOR,
23
23
  LEVEL_COLOR,
24
+ RULE_COLOR,
24
25
  WHITE_COLOR,
25
26
  )
26
27
  from logger_36.config.message import ACTUAL_PATTERNS, EXPECTED_PATTERNS, WHERE_SEPARATOR
27
28
  from logger_36.constant.message import CONTEXT_LENGTH
28
- from logger_36.task.format.rule import Rule
29
29
  from logger_36.type.handler import handler_t as base_t
30
30
 
31
31
  _COMMON_TRACEBACK_ARGUMENTS = ("theme", "width")
@@ -51,12 +51,7 @@ class console_rich_handler_t(base_t):
51
51
  """
52
52
 
53
53
  def __init__(
54
- self,
55
- name: str | None,
56
- message_width: int,
57
- level: int,
58
- formatter: l.Formatter | None,
59
- kwargs,
54
+ self, name: str | None, message_width: int, level: int, kwargs
60
55
  ) -> None:
61
56
  """"""
62
57
  alternating_logs = kwargs.pop("alternating_logs", 0)
@@ -64,7 +59,7 @@ class console_rich_handler_t(base_t):
64
59
 
65
60
  assert alternating_logs in (0, 1, 2)
66
61
 
67
- base_t.__init__(self, name, message_width, level, formatter, kwargs)
62
+ base_t.__init__(self, name, message_width, EscapedVersion, level, kwargs)
68
63
 
69
64
  self.console = None # console_t | None.
70
65
  self.alternating_logs = alternating_logs
@@ -96,21 +91,20 @@ class console_rich_handler_t(base_t):
96
91
  name: str | None = None,
97
92
  message_width: int = -1,
98
93
  level: int = l.NOTSET,
99
- formatter: l.Formatter | None = None,
100
94
  **kwargs,
101
95
  ) -> h.Self:
102
96
  """"""
103
- return cls(name, message_width, level, formatter, kwargs)
97
+ return cls(name, message_width, level, kwargs)
98
+
99
+ def Rule(self, text: str | None, /, *, color: str = "black") -> str | rule_t:
100
+ """"""
101
+ if text is None:
102
+ return rule_t(style=color)
103
+ return rule_t(title=text_t(text, style=f"bold {color}"), style=color)
104
104
 
105
105
  def emit(self, record: l.LogRecord, /) -> None:
106
106
  """"""
107
- message, is_not_a_rule = self.MessageFromRecord(
108
- record,
109
- Rule,
110
- line_width=self.message_width,
111
- color=DATE_TIME_COLOR,
112
- PreProcessed=EscapedVersion,
113
- )
107
+ message, is_not_a_rule = self.MessageFromRecord(record, rule_color=RULE_COLOR)
114
108
  if is_not_a_rule:
115
109
  message = HighlightedVersion(
116
110
  self.console,
@@ -126,10 +120,6 @@ class console_rich_handler_t(base_t):
126
120
  """"""
127
121
  self.console.print(message, crop=False, overflow="ignore")
128
122
 
129
- def EmitRule(self, /, *, text: str | None = None, color: str = "black") -> None:
130
- """"""
131
- self.EmitAsIs(Rule(text, color))
132
-
133
123
 
134
124
  def HighlightedVersion(
135
125
  _: console_t,
@@ -8,7 +8,6 @@ import logging as l
8
8
  import typing as h
9
9
  from pathlib import Path as path_t
10
10
 
11
- from logger_36.task.format.rule import RuleAsText
12
11
  from logger_36.type.handler import file_handler_t as base_t
13
12
 
14
13
 
@@ -21,18 +20,15 @@ class file_handler_t(base_t):
21
20
  name: str | None = None,
22
21
  message_width: int = -1,
23
22
  level: int = l.NOTSET,
24
- formatter: l.Formatter | None = None,
25
23
  path: str | path_t | None = None,
26
24
  **_,
27
25
  ) -> h.Self:
28
26
  """"""
29
- return cls(name, message_width, level, formatter, path)
27
+ return cls(name, message_width, None, level, path)
30
28
 
31
29
  def emit(self, record: l.LogRecord, /) -> None:
32
30
  """"""
33
- output = self.MessageFromRecord(
34
- record, RuleAsText, line_width=self.message_width
35
- )
31
+ output = self.MessageFromRecord(record)
36
32
  self.stream.write(output[0] + "\n")
37
33
  self.stream.flush()
38
34
 
@@ -10,28 +10,21 @@ import typing as h
10
10
  from logger_36.catalog.config.optional import RICH_IS_AVAILABLE
11
11
 
12
12
  if RICH_IS_AVAILABLE:
13
- from rich.console import Console as console_t # noqa
14
- from rich.console import ConsoleOptions as console_options_t # noqa
15
- from rich.markup import escape as EscapedForRich # noqa
16
- from rich.terminal_theme import DEFAULT_TERMINAL_THEME # noqa
13
+ from rich.console import Console as console_t
14
+ from rich.markup import escape as EscapedForRich
15
+ from rich.rule import Rule as rule_t
16
+ from rich.terminal_theme import DEFAULT_TERMINAL_THEME
17
+ from rich.text import Text as text_t
17
18
 
18
- from logger_36.catalog.config.console_rich import DATE_TIME_COLOR
19
+ from logger_36.catalog.config.console_rich import RULE_COLOR
19
20
  from logger_36.catalog.handler.console_rich import HighlightedVersion
20
21
  else:
21
- DATE_TIME_COLOR = HighlightedVersion = console_t = console_options_t = (
22
- EscapedForRich
23
- ) = DEFAULT_TERMINAL_THEME = None
22
+ console_t = EscapedForRich = rule_t = DEFAULT_TERMINAL_THEME = text_t = (
23
+ RULE_COLOR
24
+ ) = HighlightedVersion = None
24
25
 
25
- from logger_36.task.format.rule import Rule, RuleAsText
26
26
  from logger_36.type.handler import handler_t as base_t
27
27
 
28
- LogAsIs_h = h.Callable[[str | h.Any], None]
29
-
30
-
31
- @h.runtime_checkable
32
- class EmitRule_p(h.Protocol):
33
- def __call__(self, /, *, text: str | None = None, color: str = "white") -> None: ...
34
-
35
28
 
36
29
  class generic_handler_t(base_t):
37
30
  """
@@ -42,12 +35,7 @@ class generic_handler_t(base_t):
42
35
  """
43
36
 
44
37
  def __init__(
45
- self,
46
- name: str | None,
47
- message_width: int,
48
- level: int,
49
- formatter: l.Formatter | None,
50
- kwargs,
38
+ self, name: str | None, message_width: int, level: int, kwargs
51
39
  ) -> None:
52
40
  """
53
41
  EmitAsIs: By definition, the generic handler does not know how to output
@@ -59,12 +47,13 @@ class generic_handler_t(base_t):
59
47
 
60
48
  assert alternating_logs in (0, 1, 2)
61
49
 
62
- base_t.__init__(self, name, message_width, level, formatter, kwargs)
50
+ base_t.__init__(self, name, message_width, None, level, kwargs)
63
51
 
64
52
  if EmitAsIs is not None:
65
53
  self.EmitAsIs = EmitAsIs
54
+ self.is_rich = False
66
55
  self.console = None # console_t | None.
67
- self.console_options = None # console_options_t | None.
56
+ self.console_options = None # rich.console.ConsoleOptions | None.
68
57
  self.alternating_logs = alternating_logs
69
58
  self._log_parity = False
70
59
 
@@ -73,11 +62,12 @@ class generic_handler_t(base_t):
73
62
  def __post_init_local__(self, supports_html: bool) -> None:
74
63
  """"""
75
64
  if supports_html and (console_t is not None):
65
+ self.PreProcessedMessage = EscapedForRich
66
+ self.is_rich = True
76
67
  self.console = console_t(highlight=False, force_terminal=True)
77
68
  self.console_options = self.console.options.update(
78
69
  overflow="ignore", no_wrap=True
79
70
  )
80
- self.EmitRule = self._EmitRichRule
81
71
 
82
72
  @classmethod
83
73
  def New(
@@ -87,25 +77,25 @@ class generic_handler_t(base_t):
87
77
  name: str | None = None,
88
78
  message_width: int = -1,
89
79
  level: int = l.NOTSET,
90
- formatter: l.Formatter | None = None,
91
80
  **kwargs,
92
81
  ) -> h.Self:
93
82
  """"""
94
- return cls(name, message_width, level, formatter, kwargs)
83
+ return cls(name, message_width, level, kwargs)
84
+
85
+ def Rule(self, text: str | None, /, *, color: str = "black") -> str | rule_t:
86
+ """"""
87
+ if self.is_rich:
88
+ if text is None:
89
+ return rule_t(style=color)
90
+ return rule_t(title=text_t(text, style=f"bold {color}"), style=color)
91
+
92
+ return base_t.Rule(self, text, color=color)
95
93
 
96
94
  def emit(self, record: l.LogRecord, /) -> None:
97
95
  """"""
98
- if self.console is None:
99
- message = self.MessageFromRecord(
100
- record, RuleAsText, line_width=self.message_width
101
- )[0]
102
- else:
96
+ if self.is_rich:
103
97
  message, is_not_a_rule = self.MessageFromRecord(
104
- record,
105
- Rule,
106
- line_width=self.message_width,
107
- color=DATE_TIME_COLOR,
108
- PreProcessed=EscapedForRich,
98
+ record, rule_color=RULE_COLOR
109
99
  )
110
100
  if is_not_a_rule:
111
101
  message = HighlightedVersion(
@@ -138,16 +128,12 @@ class generic_handler_t(base_t):
138
128
  message = (
139
129
  "<pre style='margin-bottom:0px'>" + "".join(html_segments) + "</pre>"
140
130
  )
131
+ else:
132
+ message = self.MessageFromRecord(record)[0]
141
133
 
142
134
  self.EmitAsIs(message)
143
135
  self._log_parity = not self._log_parity
144
136
 
145
- def _EmitRichRule(
146
- self, /, *, text: str | None = None, color: str = "black"
147
- ) -> None:
148
- """"""
149
- self.EmitAsIs(Rule(text, color))
150
-
151
137
 
152
138
  """
153
139
  COPYRIGHT NOTICE
@@ -4,31 +4,10 @@ Contributor(s): Eric Debreuve (eric.debreuve@cnrs.fr) since 2023
4
4
  SEE COPYRIGHT NOTICE BELOW
5
5
  """
6
6
 
7
- from logger_36.catalog.config.optional import RICH_IS_AVAILABLE
8
-
9
-
10
- def RuleAsText(text: str | None, _: None, /) -> str:
11
- """"""
12
- if text is None:
13
- return "---- ---- ---- ---- ---- ---- ---- ---- ----"
14
- else:
15
- return f"---- ---- ---- ---- {text} ---- ---- ---- ----"
16
-
17
-
18
- if RICH_IS_AVAILABLE:
19
- from rich.rule import Rule as rule_t # noqa
20
- from rich.text import Text as text_t # noqa
21
-
22
- def Rule(text: str | None, color: str, /) -> rule_t | str:
23
- """"""
24
- if text is None:
25
- return rule_t(style=color)
26
- else:
27
- return rule_t(title=text_t(text, style=f"bold {color}"), style=color)
28
-
29
- else:
30
- rule_t = text_t = None
31
- Rule = lambda _, __: RuleAsText(_, None)
7
+ RULE_CHARACTER = "-"
8
+ MIN_HALF_RULE_LENGTH = 4
9
+ DEFAULT_RULE_LENGTH = 50
10
+ DEFAULT_RULE = DEFAULT_RULE_LENGTH * RULE_CHARACTER
32
11
 
33
12
  """
34
13
  COPYRIGHT NOTICE
@@ -5,69 +5,10 @@ SEE COPYRIGHT NOTICE BELOW
5
5
  """
6
6
 
7
7
  import difflib as diff
8
- import logging as l
9
8
  import typing as h
10
9
 
11
- from logger_36.config.message import (
12
- LEVEL_CLOSING,
13
- LEVEL_OPENING,
14
- MESSAGE_MARKER,
15
- WHERE_SEPARATOR,
16
- )
17
10
  from logger_36.constant.generic import NOT_PASSED
18
- from logger_36.constant.message import NEXT_LINE_PROLOGUE, expected_op_h
19
- from logger_36.constant.record import SHOW_W_RULE_ATTR
20
- from logger_36.extension.line import WrappedLines
21
- from logger_36.type.message import RuleWithText_h
22
-
23
-
24
- def MessageFromRecord(
25
- record: l.LogRecord,
26
- RuleWithText: RuleWithText_h,
27
- /,
28
- *,
29
- line_width: int = 0,
30
- color: str | None = None,
31
- PreProcessed: h.Callable[[str], str] | None = None,
32
- ) -> tuple[str, bool]:
33
- """
34
- See logger_36.catalog.handler.README.txt.
35
-
36
- The second returned value is is_not_a_rule.
37
- """
38
- message = record.msg
39
-
40
- if hasattr(record, SHOW_W_RULE_ATTR):
41
- return RuleWithText(message, color), False
42
-
43
- if PreProcessed is not None:
44
- message = PreProcessed(message)
45
- if (line_width <= 0) or (message.__len__() <= line_width):
46
- if "\n" in message:
47
- message = NEXT_LINE_PROLOGUE.join(message.splitlines())
48
- else:
49
- if "\n" in message:
50
- lines = WrappedLines(message.splitlines(), line_width)
51
- else:
52
- lines = WrappedLines([message], line_width)
53
- message = NEXT_LINE_PROLOGUE.join(lines)
54
-
55
- when_or_elapsed = getattr(record, "when_or_elapsed", None)
56
- if when_or_elapsed is None:
57
- return message, True
58
-
59
- level_first_letter = getattr(record, "level_first_letter", "")
60
-
61
- if (where := getattr(record, "where", None)) is None:
62
- where = ""
63
- else:
64
- where = f"{NEXT_LINE_PROLOGUE}{WHERE_SEPARATOR} {where}"
65
-
66
- return (
67
- f"{when_or_elapsed}"
68
- f"{LEVEL_OPENING}{level_first_letter}{LEVEL_CLOSING} "
69
- f"{MESSAGE_MARKER} {message}{where}"
70
- ), True
11
+ from logger_36.constant.message import expected_op_h
71
12
 
72
13
 
73
14
  def MessageWithActualExpected(
logger_36/type/handler.py CHANGED
@@ -5,23 +5,38 @@ SEE COPYRIGHT NOTICE BELOW
5
5
  """
6
6
 
7
7
  import logging as l
8
- import sys as s
9
8
  import typing as h
10
9
  from pathlib import Path as path_t
11
10
 
12
- from logger_36.config.message import FALLBACK_MESSAGE_WIDTH
13
- from logger_36.task.format.message import MessageFromRecord
14
- from logger_36.task.format.rule import RuleAsText
15
- from logger_36.type.message import MessageFromRecord_h, RuleWithText_h
16
-
17
-
18
- class _base_t:
19
- def __init__(self, name: str | None, message_width: int) -> None:
11
+ from logger_36.config.message import (
12
+ FALLBACK_MESSAGE_WIDTH,
13
+ LEVEL_CLOSING,
14
+ LEVEL_OPENING,
15
+ MESSAGE_MARKER,
16
+ WHERE_SEPARATOR,
17
+ )
18
+ from logger_36.constant.message import NEXT_LINE_PROLOGUE
19
+ from logger_36.constant.record import SHOW_W_RULE_ATTR
20
+ from logger_36.constant.rule import (
21
+ DEFAULT_RULE,
22
+ DEFAULT_RULE_LENGTH,
23
+ MIN_HALF_RULE_LENGTH,
24
+ RULE_CHARACTER,
25
+ )
26
+ from logger_36.extension.line import WrappedLines
27
+
28
+
29
+ class extension_t:
30
+ def __init__(
31
+ self,
32
+ name: str | None,
33
+ message_width: int,
34
+ PreProcessedMessage: h.Callable[[str], str] | None,
35
+ ) -> None:
20
36
  """"""
21
37
  self.name = name
22
38
  self.message_width = message_width
23
- #
24
- self.MessageFromRecord: MessageFromRecord_h | None = None
39
+ self.PreProcessedMessage = PreProcessedMessage
25
40
 
26
41
  self.__post_init__()
27
42
 
@@ -40,37 +55,97 @@ class _base_t:
40
55
  """
41
56
  raise NotImplementedError
42
57
 
58
+ def MessageFromRecord(
59
+ self, record: l.LogRecord, /, *, rule_color: str | None = None
60
+ ) -> tuple[str, bool]:
61
+ """
62
+ The second returned value is is_not_a_rule.
63
+ """
64
+ message = record.msg # See logger_36.catalog.handler.README.txt.
65
+ if self.PreProcessedMessage is not None:
66
+ message = self.PreProcessedMessage(message)
67
+
68
+ if hasattr(record, SHOW_W_RULE_ATTR):
69
+ return self.Rule(message, color=rule_color), False
70
+
71
+ if (self.message_width <= 0) or (message.__len__() <= self.message_width):
72
+ if "\n" in message:
73
+ message = NEXT_LINE_PROLOGUE.join(message.splitlines())
74
+ else:
75
+ if "\n" in message:
76
+ lines = WrappedLines(message.splitlines(), self.message_width)
77
+ else:
78
+ lines = WrappedLines([message], self.message_width)
79
+ message = NEXT_LINE_PROLOGUE.join(lines)
80
+
81
+ when_or_elapsed = getattr(record, "when_or_elapsed", None)
82
+ if when_or_elapsed is None:
83
+ return message, True
84
+
85
+ level_first_letter = getattr(record, "level_first_letter", "")
86
+
87
+ if (where := getattr(record, "where", None)) is None:
88
+ where = ""
89
+ else:
90
+ where = f"{NEXT_LINE_PROLOGUE}{WHERE_SEPARATOR} {where}"
91
+
92
+ return (
93
+ f"{when_or_elapsed}"
94
+ f"{LEVEL_OPENING}{level_first_letter}{LEVEL_CLOSING} "
95
+ f"{MESSAGE_MARKER} {message}{where}"
96
+ ), True
97
+
98
+ def Rule(self, text: str | None, /, *, color: str = "black") -> str | h.Any:
99
+ """
100
+ Return type hint h.Any: For Rich, for example.
101
+ """
102
+ if text is None:
103
+ if self.message_width > 0:
104
+ return self.message_width * RULE_CHARACTER
105
+ return DEFAULT_RULE
106
+
107
+ if self.message_width > 0:
108
+ target_width = self.message_width
109
+ else:
110
+ target_width = DEFAULT_RULE_LENGTH
111
+ half_rule_length = max(
112
+ (target_width - text.__len__() - 2) // 2, MIN_HALF_RULE_LENGTH
113
+ )
114
+ half_rule = half_rule_length * RULE_CHARACTER
115
+
116
+ return f"{half_rule} {text} {half_rule}"
117
+
43
118
  def EmitAsIs(self, message: str, /) -> None:
44
119
  """"""
45
- s.__stdout__.write(message + "\n")
120
+ raise NotImplementedError
46
121
 
47
122
  def EmitRule(self, /, *, text: str | None = None, color: str = "black") -> None:
48
123
  """"""
49
- self.EmitAsIs(RuleAsText(text, None))
124
+ self.EmitAsIs(self.Rule(text, color=color))
50
125
 
51
126
 
52
- class handler_t(l.Handler, _base_t):
127
+ class handler_t(l.Handler, extension_t):
53
128
  def __init__(
54
129
  self,
55
130
  name: str | None,
56
131
  message_width: int,
132
+ PreProcessedMessage: h.Callable[[str], str] | None,
57
133
  level: int,
58
- formatter: l.Formatter | None,
59
134
  *_,
60
135
  ) -> None:
61
136
  """"""
62
137
  l.Handler.__init__(self)
63
- _base_t.__init__(self, name, message_width)
64
- __post_init__(self, level, formatter)
138
+ extension_t.__init__(self, name, message_width, PreProcessedMessage)
139
+ __post_init__(self, level)
65
140
 
66
141
 
67
- class file_handler_t(l.FileHandler, _base_t):
142
+ class file_handler_t(l.FileHandler, extension_t):
68
143
  def __init__(
69
144
  self,
70
145
  name: str | None,
71
146
  message_width: int,
147
+ PreProcessedMessage: h.Callable[[str], str] | None,
72
148
  level: int,
73
- formatter: l.Formatter | None,
74
149
  path: str | path_t | None,
75
150
  *_,
76
151
  ) -> None:
@@ -83,38 +158,17 @@ class file_handler_t(l.FileHandler, _base_t):
83
158
  raise ValueError(f"File or folder already exists: {path}.")
84
159
 
85
160
  l.FileHandler.__init__(self, path)
86
- _base_t.__init__(self, name, message_width)
87
- __post_init__(self, level, formatter)
161
+ extension_t.__init__(self, name, message_width, PreProcessedMessage)
162
+ __post_init__(self, level)
88
163
 
89
164
 
90
165
  any_handler_t = handler_t | file_handler_t
91
166
 
92
167
 
93
- def __post_init__(
94
- handler: any_handler_t, level: int, formatter: l.Formatter | None
95
- ) -> None:
168
+ def __post_init__(handler: any_handler_t, level: int) -> None:
96
169
  """"""
97
170
  handler.setLevel(level)
98
171
 
99
- if formatter is None:
100
- handler.MessageFromRecord = MessageFromRecord
101
- else:
102
- handler.setFormatter(formatter)
103
- _MessageFromRecordRaw = handler.formatter.format
104
-
105
- def _MessageFromRecord(
106
- record: l.LogRecord,
107
- _: RuleWithText_h,
108
- /,
109
- *,
110
- line_width: int = 0,
111
- PreProcessed: h.Callable[[str], str] | None = None,
112
- ) -> tuple[str, bool]:
113
- #
114
- return _MessageFromRecordRaw(record), False
115
-
116
- handler.MessageFromRecord = _MessageFromRecord
117
-
118
172
 
119
173
  """
120
174
  COPYRIGHT NOTICE
logger_36/type/logger.py CHANGED
@@ -38,12 +38,18 @@ from logger_36.constant.memory import UNKNOWN_MEMORY_USAGE
38
38
  from logger_36.constant.message import LINE_INDENT, TIME_LENGTH_m_1, expected_op_h
39
39
  from logger_36.constant.path import PROJECT_FILE_RELATIVE, USER_FOLDER
40
40
  from logger_36.constant.record import SHOW_W_RULE_ATTR, SHOW_WHERE_ATTR
41
- from logger_36.task.format.message import MessageFromRecord, MessageWithActualExpected
42
- from logger_36.task.format.rule import RuleAsText
41
+ from logger_36.constant.rule import (
42
+ DEFAULT_RULE,
43
+ DEFAULT_RULE_LENGTH,
44
+ MIN_HALF_RULE_LENGTH,
45
+ RULE_CHARACTER,
46
+ )
47
+ from logger_36.task.format.message import MessageWithActualExpected
43
48
  from logger_36.task.measure.chronos import ElapsedTime
44
49
  from logger_36.task.measure.memory import CanCheckUsage as CanCheckMemoryUsage
45
50
  from logger_36.task.measure.memory import CurrentUsage as CurrentMemoryUsage
46
51
  from logger_36.type.handler import any_handler_t as base_handler_t
52
+ from logger_36.type.handler import extension_t as handler_extension_t
47
53
  from logger_36.type.issue import NewIssue, issue_t
48
54
 
49
55
  if RICH_IS_AVAILABLE:
@@ -98,9 +104,10 @@ class logger_t(base_t):
98
104
  init=False, default_factory=dict
99
105
  )
100
106
  intercepts_exceptions: bool = d.field(init=False, default=False)
101
- _on_hold: list[l.LogRecord] = d.field(init=False, default_factory=list)
102
107
  _should_hold_messages: bool = d.field(init=False, default=True)
103
108
  _should_activate_log_interceptions: bool = d.field(init=False, default=False)
109
+ _on_hold: list[l.LogRecord] = d.field(init=False, default_factory=list)
110
+ _recording_handler: handler_extension_t | None = d.field(init=False, default=None)
104
111
 
105
112
  name_: d.InitVar[str | None] = None
106
113
  level_: d.InitVar[int] = l.NOTSET
@@ -164,8 +171,8 @@ class logger_t(base_t):
164
171
  self.setLevel(level_)
165
172
  self.propagate = False # Part of base_t.
166
173
 
167
- for level in l.getLevelNamesMapping().values():
168
- self.events[level] = 0
174
+ for level_id in l.getLevelNamesMapping().values():
175
+ self.events[level_id] = 0
169
176
 
170
177
  if activate_wrn_interceptions:
171
178
  self.ToggleWarningInterceptions(True)
@@ -177,6 +184,9 @@ class logger_t(base_t):
177
184
  if self.exit_on_error:
178
185
  self.exit_on_critical = True
179
186
 
187
+ if self.should_record_messages:
188
+ self.ActivateMessageRecording()
189
+
180
190
  if self.should_watch_memory_usage and not CanCheckMemoryUsage():
181
191
  self.should_watch_memory_usage = False
182
192
  if _MEMORY_MEASURE_ERROR is not None:
@@ -193,7 +203,10 @@ class logger_t(base_t):
193
203
  for held in self._on_hold:
194
204
  if self.should_record_messages:
195
205
  self.recorded.append(
196
- (held.levelno, MessageFromRecord(held, RuleAsText)[0])
206
+ (
207
+ held.levelno,
208
+ self._recording_handler.MessageFromRecord(held)[0],
209
+ )
197
210
  )
198
211
  base_t.handle(self, held)
199
212
  self._on_hold.clear()
@@ -215,7 +228,7 @@ class logger_t(base_t):
215
228
  self.recorded.append(
216
229
  (
217
230
  date_record.levelno,
218
- MessageFromRecord(date_record, RuleAsText)[0],
231
+ self._recording_handler.MessageFromRecord(date_record)[0],
219
232
  )
220
233
  )
221
234
  base_t.handle(self, date_record)
@@ -258,7 +271,10 @@ class logger_t(base_t):
258
271
  else:
259
272
  if self.should_record_messages:
260
273
  self.recorded.append(
261
- (record.levelno, MessageFromRecord(record, RuleAsText)[0])
274
+ (
275
+ record.levelno,
276
+ self._recording_handler.MessageFromRecord(record)[0],
277
+ )
262
278
  )
263
279
  base_t.handle(self, record)
264
280
 
@@ -274,6 +290,12 @@ class logger_t(base_t):
274
290
  if self.should_watch_memory_usage:
275
291
  self.memory_usages.append((where, CurrentMemoryUsage()))
276
292
 
293
+ def ActivateMessageRecording(self) -> None:
294
+ """"""
295
+ if self._recording_handler is None:
296
+ self._recording_handler = handler_extension_t("recording_handler", 0, None)
297
+ self.should_record_messages = True # Useless if called from __post_init__.
298
+
277
299
  def MakeMonochrome(self) -> None:
278
300
  """"""
279
301
  self.AddHandler(console_handler_t)
@@ -295,8 +317,8 @@ class logger_t(base_t):
295
317
 
296
318
  def ResetEventCounts(self) -> None:
297
319
  """"""
298
- for level in self.events:
299
- self.events[level] = 0
320
+ for level_id in self.events:
321
+ self.events[level_id] = 0
300
322
 
301
323
  def ToggleWarningInterceptions(self, state: bool, /) -> None:
302
324
  """"""
@@ -387,13 +409,15 @@ class logger_t(base_t):
387
409
 
388
410
  def AddHandler(
389
411
  self,
390
- handler_t: type[base_handler_t],
412
+ handler_t_or_handler: type[base_handler_t]
413
+ | base_handler_t
414
+ | l.Handler
415
+ | l.FileHandler,
391
416
  /,
392
417
  *,
393
418
  name: str | None = None,
394
419
  level: int = l.INFO,
395
420
  message_width: int = -1,
396
- formatter: l.Formatter | None = None,
397
421
  should_still_hold_messages: bool = False,
398
422
  **kwargs,
399
423
  ) -> None:
@@ -404,13 +428,12 @@ class logger_t(base_t):
404
428
 
405
429
  self._should_hold_messages = should_still_hold_messages
406
430
 
407
- handler = handler_t.New(
408
- name=name,
409
- message_width=message_width,
410
- level=level,
411
- formatter=formatter,
412
- **kwargs,
413
- )
431
+ if isinstance(handler_t_or_handler, type):
432
+ handler = handler_t_or_handler.New(
433
+ name=name, message_width=message_width, level=level, **kwargs
434
+ )
435
+ else:
436
+ handler = handler_t_or_handler
414
437
  base_t.addHandler(self, handler)
415
438
 
416
439
  path = getattr(handler, "baseFilename", "")
@@ -489,7 +512,8 @@ class logger_t(base_t):
489
512
  message = text.indent(message, LINE_INDENT)
490
513
 
491
514
  for handler in self.handlers:
492
- handler.EmitAsIs(message)
515
+ if (EmitAsIs := getattr(handler, "EmitAsIs", None)) is not None:
516
+ EmitAsIs(message)
493
517
 
494
518
  info_raw = LogAsIs # To follow the convention of the logging methods info, error...
495
519
 
@@ -498,7 +522,8 @@ class logger_t(base_t):
498
522
  ) -> None:
499
523
  """"""
500
524
  for handler in self.handlers:
501
- handler.EmitRule(text=message, color=color)
525
+ if (EmitRule := getattr(handler, "EmitRule", None)) is not None:
526
+ EmitRule(text=message, color=color)
502
527
 
503
528
  def AddContextLevel(self, new_level: str, /) -> None:
504
529
  """"""
logger_36/version.py CHANGED
@@ -4,7 +4,7 @@ Contributor(s): Eric Debreuve (eric.debreuve@cnrs.fr) since 2023
4
4
  SEE COPYRIGHT NOTICE BELOW
5
5
  """
6
6
 
7
- __version__ = "2025.18"
7
+ __version__ = "2025.19"
8
8
 
9
9
  """
10
10
  COPYRIGHT NOTICE
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: logger-36
3
- Version: 2025.18
3
+ Version: 2025.19
4
4
  Summary: Simple logger with a catalog of handlers
5
5
  Home-page: https://src.koda.cnrs.fr/eric.debreuve/logger-36/
6
6
  Author: Eric Debreuve
@@ -1,18 +1,18 @@
1
1
  logger_36/__init__.py,sha256=mK6AD0eWI2Sk42oxleTvsxzYJ28FbHK5WNkpLgAhnNE,2129
2
- logger_36/version.py,sha256=IGK2AtFu27rKPgT-azGp5zuAMQXCLFqA56YY96_pPQA,1680
3
- logger_36/api/content.py,sha256=0pKjxSG1hmxAkf4tBvXXOI_qrtbSx7_4Vyz-9BF_-MQ,1855
2
+ logger_36/version.py,sha256=DefgP6TAlC9ZItUKr6xILYjfpSID0x0a7pe5gfQoQ1s,1680
3
+ logger_36/api/content.py,sha256=DuT4UX4r_1DTXzuuRD-tvsTZk5X-Nj11loBKhuWOMw0,1791
4
4
  logger_36/api/gpu.py,sha256=NNs1IvQ7bh8Dppm8O8K2YLWbm4rogc3Ie_-D6xzkX3g,1726
5
5
  logger_36/api/memory.py,sha256=vOY4cTTrC3u7L0OrKXdPNlsCahYjCrY4h7iqpGZv9kU,2217
6
6
  logger_36/api/storage.py,sha256=RYjn2TD-E1zfNTMgm4b2mbYNVtTwsCUMbuPlMbuvgP0,1774
7
7
  logger_36/api/system.py,sha256=h-3GfhZPwawv0UKBWKkT1LzxSCZwpA2VIsy03lLYi6w,1725
8
8
  logger_36/api/time.py,sha256=JG0vgzPSRZ7UWQyoihnVu4sjPC-okFIKA3ZyNh2GaZo,1798
9
9
  logger_36/api/type.py,sha256=eLZ2yuH-sYeh4Z2KnAwTRJEbmkmgzBPMncdqXfFUTG8,1760
10
- logger_36/catalog/config/console_rich.py,sha256=tHUM1hUg-RTBXamjK_EkNO1Woob9ML2R_o6WBkzwe4A,2335
10
+ logger_36/catalog/config/console_rich.py,sha256=t9p9-AkSgPiLAsm1evAdbz77g7JcVLePhUJ1FzNi3cY,2330
11
11
  logger_36/catalog/config/optional.py,sha256=5vabOlEQIFxoT_y4AjP19rpOjBuUJplpuBkLoCIKImA,1872
12
- logger_36/catalog/handler/console.py,sha256=fJt9zAjEh0XbORO-81mWX4QjPu0bJaf9sCbgTCtcFkY,2390
13
- logger_36/catalog/handler/console_rich.py,sha256=nGpt3iayFBkOVHqs_oa02W8__QmBykNDZb-QsK_Rrok,6347
14
- logger_36/catalog/handler/file.py,sha256=pezjcFCaA5zdV3GtHLaU_pfsp_YopvKYGHJAOZ69Vzs,2621
15
- logger_36/catalog/handler/generic.py,sha256=Gye1wS7VialSK6TbZi1g2yk67tG1bVIk5kWtw__BUlk,6683
12
+ logger_36/catalog/handler/console.py,sha256=OKtRG3PseT06_UwZBtbragNIKvCqa7ZsLphYEsgeq2c,2308
13
+ logger_36/catalog/handler/console_rich.py,sha256=b3B_pQefv0IeQFEj-tZ5-tH8RrcessZI8sI4Tyf17bg,6180
14
+ logger_36/catalog/handler/file.py,sha256=JReN8KruN8eKhBfG9sj72kkHfukHK4-8mdSxNpgFhLU,2455
15
+ logger_36/catalog/handler/generic.py,sha256=0Z4Vm-AAc92szghYEzihkm9CBRzCvqgdUTXH_p98S4o,6314
16
16
  logger_36/catalog/logger/chronos.py,sha256=S4m9TMPQy_Ju500mpE1jNzu2gZG-QKdVuvX9RVRKHR8,1911
17
17
  logger_36/catalog/logger/gpu.py,sha256=Py5YY0nD_pqJzJsEKQYoOGHcPqyNVJ3J2noOS3hDL6g,2890
18
18
  logger_36/catalog/logger/memory.py,sha256=J0ZGKO7j1FZA_aDGxpABtvzDy1RjCDiDmWYh4U98fEI,4253
@@ -30,6 +30,7 @@ logger_36/constant/memory.py,sha256=Q_E5tTWa-cGaNwrE_xmKa3BxQG6oJO6DHczrxc_M4sE,
30
30
  logger_36/constant/message.py,sha256=YJOEzdI0ZjUOdHo3CsiS56FVPhrfNoQYvXuUkprH61g,2312
31
31
  logger_36/constant/path.py,sha256=OfLh70Jyc8po9Ls34nQh_bRr3PXyQ3kF9ciR9QPhiqI,2213
32
32
  logger_36/constant/record.py,sha256=gQCGLxq8Vs789Ty_qaRNKy18mqlyMT_4kyN-T9r_rGE,1734
33
+ logger_36/constant/rule.py,sha256=tBKQgPTt6G_p5eInDdWoEEAvQFz4WMSt5THsS5jvk14,1779
33
34
  logger_36/constant/system.py,sha256=pLlLXG5sepQlSUOo3TphaGrHg8xzJBp-GxpL2NPP47k,1904
34
35
  logger_36/extension/html_.py,sha256=W9SyiYsaaYHUrHLGAAN2wiJGXUlwOBJ5gzdjmEcnF18,3342
35
36
  logger_36/extension/line.py,sha256=joeojQX1bZJM53333mOEU3s-YC5ExGOrN9Cu9Lh5-FU,2617
@@ -38,16 +39,14 @@ logger_36/instance/loggers.py,sha256=inBk4KKrQ-z3szaopQ29-qQwh1iSc842sWo5J6zJoiM
38
39
  logger_36/task/inspection.py,sha256=ZgPcrPo3h_kEnM-UpHDLg7PAFfB2fqsLFdfmi6hlPVA,4484
39
40
  logger_36/task/storage.py,sha256=KAILmJlF5IULxEX9QRCyXCwcalp5mpunWVh1oXuLvSs,3516
40
41
  logger_36/task/format/memory.py,sha256=J1Oy3jw8wjSp2kuiRUm_VFpzXOHX2FOc7nuRrCyrskw,3723
41
- logger_36/task/format/message.py,sha256=SqVtiWzcuMl5xPI2kmz2wkQP05amX3wKy0C0qdfXB3I,5245
42
- logger_36/task/format/rule.py,sha256=4wQpeBBbCeF13rXl3Q9UEvvYh4sJOFfk6hOvJHDXYvY,2384
42
+ logger_36/task/format/message.py,sha256=Rm6zymVEEGcgKfmxMPXP7q3PtwZJKlXGhqZ5tnvlwxA,3502
43
43
  logger_36/task/measure/chronos.py,sha256=7ijMZgP4EP18HbLV2yCxpNpRS9724Wyk523f-nkbhUM,2529
44
44
  logger_36/task/measure/memory.py,sha256=kkPHEIUTUhkCOLrAt01eLJLnsnkl0nFPNhFZdIB_JAw,1991
45
- logger_36/type/handler.py,sha256=JxUGjFSHFnX-6xM5f5vD00e1OQMPlPlLvopR32qd1kY,4763
45
+ logger_36/type/handler.py,sha256=LmdiBI1NLzpgI_Mzr6nVBsj9Nu_5iZFGKNKTqGAmdSM,6673
46
46
  logger_36/type/issue.py,sha256=QHAYf7QgrjJUtF2D46z6X630qTgeP_0FE5hIwf54RsE,2688
47
- logger_36/type/logger.py,sha256=UuZt8A4AtWU78k9ryCY6gFe-Q7xzFrDvqbTEX10EgXs,25012
47
+ logger_36/type/logger.py,sha256=oQy5VrJor6aJYH3KMIRMpbWx9YoR9oWFeSZWYzle2n4,26041
48
48
  logger_36/type/loggers.py,sha256=7EX7Sg_RlduBjdfFlNZmUfNeDloH1xU30Rdkg_-rXh8,3172
49
- logger_36/type/message.py,sha256=pv9yJKd_GXuctUFWIjvcFoekNrBu0hDg0fqj_KAFwjI,2537
50
- logger_36-2025.18.dist-info/METADATA,sha256=uUcAg2M2fBGUYwHYNmRvCpM-ShfH-VxsCNbL3oheDK0,6529
51
- logger_36-2025.18.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
52
- logger_36-2025.18.dist-info/top_level.txt,sha256=sM95BTMWmslEEgR_1pzwZsOeSp8C_QBiu8ImbFr0XLc,10
53
- logger_36-2025.18.dist-info/RECORD,,
49
+ logger_36-2025.19.dist-info/METADATA,sha256=o3JDxhFujB13yngrFl9lTrpRoSvLYMev6u9pY06nB_E,6529
50
+ logger_36-2025.19.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
51
+ logger_36-2025.19.dist-info/top_level.txt,sha256=sM95BTMWmslEEgR_1pzwZsOeSp8C_QBiu8ImbFr0XLc,10
52
+ logger_36-2025.19.dist-info/RECORD,,
logger_36/type/message.py DELETED
@@ -1,76 +0,0 @@
1
- """
2
- Copyright CNRS (https://www.cnrs.fr/index.php/en)
3
- Contributor(s): Eric Debreuve (eric.debreuve@cnrs.fr) since 2023
4
- SEE COPYRIGHT NOTICE BELOW
5
- """
6
-
7
- import logging as l
8
- import typing as h
9
-
10
- from logger_36.catalog.config.optional import RICH_IS_AVAILABLE
11
-
12
-
13
- @h.runtime_checkable
14
- class _RuleAsText_p(h.Protocol):
15
- def __call__(self, text: str | None, color: None, /) -> str: ...
16
-
17
-
18
- if RICH_IS_AVAILABLE:
19
- from rich.rule import Rule as rule_t # noqa
20
-
21
- @h.runtime_checkable
22
- class _Rule_p(h.Protocol):
23
- def __call__(self, text: str | None, color: str, /) -> rule_t | str: ...
24
- else:
25
- _Rule_p = None
26
-
27
- RuleWithText_h = _RuleAsText_p | _Rule_p
28
-
29
-
30
- @h.runtime_checkable
31
- class _MessageFromRecordPreprocessed_p(h.Protocol):
32
- def __call__(
33
- self,
34
- record: l.LogRecord,
35
- RuleWithText: RuleWithText_h,
36
- /,
37
- *,
38
- line_width: int = 0,
39
- PreProcessed: h.Callable[[str], str] | None = None,
40
- ) -> tuple[str, bool]: ...
41
-
42
-
43
- MessageFromRecord_h = _MessageFromRecordPreprocessed_p
44
-
45
-
46
- """
47
- COPYRIGHT NOTICE
48
-
49
- This software is governed by the CeCILL license under French law and
50
- abiding by the rules of distribution of free software. You can use,
51
- modify and/ or redistribute the software under the terms of the CeCILL
52
- license as circulated by CEA, CNRS and INRIA at the following URL
53
- "http://www.cecill.info".
54
-
55
- As a counterpart to the access to the source code and rights to copy,
56
- modify and redistribute granted by the license, users are provided only
57
- with a limited warranty and the software's author, the holder of the
58
- economic rights, and the successive licensors have only limited
59
- liability.
60
-
61
- In this respect, the user's attention is drawn to the risks associated
62
- with loading, using, modifying and/or developing or reproducing the
63
- software by the user in light of its specific status of free software,
64
- that may mean that it is complicated to manipulate, and that also
65
- therefore means that it is reserved for developers and experienced
66
- professionals having in-depth computer knowledge. Users are therefore
67
- encouraged to load and test the software's suitability as regards their
68
- requirements in conditions enabling the security of their systems and/or
69
- data to be ensured and, more generally, to use and operate it in the
70
- same conditions as regards security.
71
-
72
- The fact that you are presently reading this means that you have had
73
- knowledge of the CeCILL license and that you accept its terms.
74
-
75
- SEE LICENCE NOTICE: file README-LICENCE-utf8.txt at project source root.
76
- """