logger-36 2024.22__py3-none-any.whl → 2024.24__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.
@@ -31,7 +31,6 @@ from logger_36.task.format.rule import Rule
31
31
  from logger_36.type.handler import handler_extension_t
32
32
  from rich.console import Console as console_t
33
33
  from rich.console import RenderableType as renderable_t
34
- from rich.markup import escape as EscapedForRich
35
34
  from rich.text import Text as text_t
36
35
  from rich.traceback import install as InstallTracebackHandler
37
36
 
@@ -140,7 +139,7 @@ class console_rich_handler_t(lggg.Handler):
140
139
  if hasattr(record, SHOW_W_RULE_ATTR):
141
140
  richer = Rule(record.msg, DATE_TIME_COLOR)
142
141
  else:
143
- first, next_s = self.FormattedLines(record, PreProcessed=EscapedForRich)
142
+ first, next_s = self.FormattedLines(record)
144
143
  should_highlight_back = self.alternating_lines == 1
145
144
  if self.alternating_lines >= 0:
146
145
  self.alternating_lines = (self.alternating_lines + 1) % 2
@@ -173,7 +172,9 @@ def HighlightedVersion(
173
172
  background_is_light: bool = True,
174
173
  ) -> renderable_t:
175
174
  """"""
176
- output = text_t(first_line)
175
+ # TODO: Is there a way to use html or something to enable message styling,
176
+ # regardless of the handler (would require styling conversion; html->rich here).
177
+ output = text_t.from_markup(first_line)
177
178
 
178
179
  # Used instead of _CONTEXT_LENGTH which might include \t, thus creating a
179
180
  # mismatch between character length and length when displayed in console.
@@ -193,7 +194,7 @@ def HighlightedVersion(
193
194
  output.stylize(ELAPSED_TIME_COLOR, start=elapsed_time_separator)
194
195
 
195
196
  if next_lines is not None:
196
- output.append(next_lines)
197
+ output.append(text_t.from_markup(next_lines))
197
198
 
198
199
  _ = output.highlight_regex(ACTUAL_PATTERNS, style=ACTUAL_COLOR)
199
200
  _ = output.highlight_regex(EXPECTED_PATTERNS, style=EXPECTED_COLOR)
@@ -19,7 +19,7 @@ LOG_LEVEL_LENGTH = 1 + LEVEL_OPENING.__len__() + LEVEL_CLOSING.__len__()
19
19
  CONTEXT_LENGTH = TIME_LENGTH + LOG_LEVEL_LENGTH
20
20
  NEXT_LINE_PROLOGUE = "\n" + (CONTEXT_LENGTH + MESSAGE_MARKER.__len__() + 1) * " "
21
21
 
22
- expected_op_h = h.Literal[": ", "=", "!=", ">=", "<="]
22
+ expected_op_h = h.Literal[":", ": ", "=", "!=", ">=", "<="]
23
23
  EXPECTED_OP: tuple[str, ...] = h.get_args(expected_op_h)
24
24
 
25
25
  """
@@ -4,6 +4,7 @@ Contributor(s): Eric Debreuve (eric.debreuve@cnrs.fr) since 2023
4
4
  SEE COPYRIGHT NOTICE BELOW
5
5
  """
6
6
 
7
+ import difflib as diff
7
8
  import typing as h
8
9
 
9
10
  from logger_36.config.message import (
@@ -14,7 +15,7 @@ from logger_36.config.message import (
14
15
  MESSAGE_MARKER,
15
16
  )
16
17
  from logger_36.constant.generic import NOT_PASSED
17
- from logger_36.constant.message import EXPECTED_OP, expected_op_h
18
+ from logger_36.constant.message import expected_op_h
18
19
 
19
20
 
20
21
  def MessageFormat(with_where: bool, with_memory_usage: bool, /) -> str:
@@ -40,19 +41,11 @@ def FormattedMessage(
40
41
  *,
41
42
  actual: h.Any = NOT_PASSED,
42
43
  expected: h.Any | None = None,
44
+ expected_is_choices: bool = False,
43
45
  expected_op: expected_op_h = "=",
44
46
  with_final_dot: bool = True,
45
47
  ) -> str:
46
48
  """"""
47
- if expected_op not in EXPECTED_OP:
48
- raise ValueError(
49
- FormattedMessage(
50
- 'Invalid "expected" section operator',
51
- actual=expected_op,
52
- expected=f"One of {str(EXPECTED_OP)[1:-1]}",
53
- )
54
- )
55
-
56
49
  if actual is NOT_PASSED:
57
50
  if with_final_dot:
58
51
  if message[-1] != ".":
@@ -64,27 +57,35 @@ def FormattedMessage(
64
57
 
65
58
  if message[-1] == ".":
66
59
  message = message[:-1]
67
- actual = _FormattedValue(actual)
68
- expected = _FormattedValue(expected)
69
-
60
+ expected = _FormattedExpected(expected_op, expected, expected_is_choices, actual)
70
61
  if with_final_dot:
71
62
  dot = "."
72
63
  else:
73
64
  dot = ""
74
- return f"{message}: Actual={actual}; Expected{expected_op}{expected}{dot}"
75
65
 
66
+ return f"{message}: Actual={actual}:{type(actual).__name__}; {expected}{dot}"
76
67
 
77
- def _FormattedValue(value: h.Any, /, *, should_format_str: bool = True) -> str:
78
- """"""
79
- if value is None:
80
- return "None"
81
-
82
- if isinstance(value, str):
83
- if should_format_str:
84
- return f'"{value}"'
85
- return value
86
68
 
87
- return str(value)
69
+ def _FormattedExpected(
70
+ operator: str, expected: h.Any, expected_is_choices: bool, actual: h.Any, /
71
+ ) -> str:
72
+ """"""
73
+ if isinstance(expected, h.Sequence) and expected_is_choices:
74
+ close_matches = diff.get_close_matches(actual, expected)
75
+ if close_matches.__len__() > 0:
76
+ close_matches = ", ".join(close_matches)
77
+ return f"Close matche(s): {close_matches}"
78
+ else:
79
+ expected = ", ".join(map(str, expected))
80
+ return f"Valid values: {expected}"
81
+ else:
82
+ if operator == "=":
83
+ stripe = f":{type(expected).__name__}"
84
+ else:
85
+ stripe = ""
86
+ if operator == ":":
87
+ operator = ": "
88
+ return f"Expected{operator}{expected}{stripe}"
88
89
 
89
90
 
90
91
  """
@@ -5,18 +5,24 @@ SEE COPYRIGHT NOTICE BELOW
5
5
  """
6
6
 
7
7
 
8
- def RuleAsText(text: str, /) -> str:
8
+ def RuleAsText(text: str | None, /) -> str:
9
9
  """"""
10
- return f"---- ---- ---- ---- {text} ---- ---- ---- ----"
10
+ if text is None:
11
+ return "---- ---- ---- ---- ---- ---- ---- ---- ----"
12
+ else:
13
+ return f"---- ---- ---- ---- {text} ---- ---- ---- ----"
11
14
 
12
15
 
13
16
  try:
14
17
  from rich.rule import Rule as rule_t
15
18
  from rich.text import Text as text_t
16
19
 
17
- def Rule(text: str, color: str, /) -> rule_t:
20
+ def Rule(text: str | None, color: str, /) -> rule_t | str:
18
21
  """"""
19
- return rule_t(title=text_t(text, style=f"bold {color}"), style=color)
22
+ if text is None:
23
+ return rule_t(style=color)
24
+ else:
25
+ return rule_t(title=text_t(text, style=f"bold {color}"), style=color)
20
26
 
21
27
  except ModuleNotFoundError:
22
28
  Rule = lambda _txt, _: RuleAsText(_txt)
logger_36/type/issue.py CHANGED
@@ -25,6 +25,7 @@ def NewIssue(
25
25
  level: int = lggg.ERROR,
26
26
  actual: h.Any = NOT_PASSED,
27
27
  expected: h.Any | None = None,
28
+ expected_is_choices: bool = False,
28
29
  expected_op: expected_op_h = "=",
29
30
  with_final_dot: bool = True,
30
31
  ) -> issue_t:
@@ -35,6 +36,7 @@ def NewIssue(
35
36
  message,
36
37
  actual=actual,
37
38
  expected=expected,
39
+ expected_is_choices=expected_is_choices,
38
40
  expected_op=expected_op,
39
41
  with_final_dot=with_final_dot,
40
42
  )
logger_36/type/logger.py CHANGED
@@ -32,6 +32,7 @@ from logger_36.task.format.memory import (
32
32
  FormattedUsageWithAutoUnit as FormattedMemoryUsage,
33
33
  )
34
34
  from logger_36.task.format.message import FormattedMessage
35
+ from logger_36.task.format.rule import Rule
35
36
  from logger_36.task.measure.chronos import ElapsedTime
36
37
  from logger_36.task.measure.memory import CurrentUsage as CurrentMemoryUsage
37
38
  from logger_36.type.issue import NewIssue, issue_t
@@ -42,12 +43,14 @@ class logger_t(lggg.Logger):
42
43
  name_: d.InitVar[str] = LOGGER_NAME
43
44
  level_: d.InitVar[int] = lggg.NOTSET
44
45
  activate_wrn_interceptions: d.InitVar[bool] = True
45
- exit_on_error: bool = False # Implies exit_on_critical.
46
- exit_on_critical: bool = False
46
+
47
47
  # Must not be False until at least one handler has been added.
48
48
  should_hold_messages: bool = True
49
+ exit_on_error: bool = False # Implies exit_on_critical.
50
+ exit_on_critical: bool = False
49
51
 
50
52
  on_hold: list[lggg.LogRecord] = d.field(init=False, default_factory=list)
53
+ events: dict[int, int] = d.field(init=False, default_factory=dict)
51
54
  last_message_date: str = d.field(init=False, default="")
52
55
  any_handler_shows_memory: bool = d.field(init=False, default=False)
53
56
  memory_usages: list[tuple[str, int]] = d.field(init=False, default_factory=list)
@@ -66,11 +69,19 @@ class logger_t(lggg.Logger):
66
69
  self.setLevel(level_)
67
70
  self.propagate = False # Part of lggg.Logger.
68
71
 
72
+ for level in lggg.getLevelNamesMapping().values():
73
+ self.events[level] = 0
74
+
69
75
  if activate_wrn_interceptions:
70
76
  self._ActivateWarningInterceptions()
71
77
  if self.exit_on_error:
72
78
  self.exit_on_critical = True
73
79
 
80
+ def ResetEventCounts(self) -> None:
81
+ """"""
82
+ for level in self.events:
83
+ self.events[level] = 0
84
+
74
85
  def _ActivateWarningInterceptions(self) -> None:
75
86
  """
76
87
  The log message will not appear if called from __post_init__ since there are no
@@ -214,6 +225,7 @@ class logger_t(lggg.Logger):
214
225
  else:
215
226
  lggg.Logger.handle(self, record)
216
227
 
228
+ self.events[record.levelno] += 1
217
229
  if (self.exit_on_critical and (record.levelno is lggg.CRITICAL)) or (
218
230
  self.exit_on_error and (record.levelno is lggg.ERROR)
219
231
  ):
@@ -221,7 +233,30 @@ class logger_t(lggg.Logger):
221
233
  # __post_init__ set self.exit_on_critical if self.exit_on_error.
222
234
  sstm.exit(1)
223
235
 
224
- def log_exception(
236
+ def Log(
237
+ self,
238
+ message: str,
239
+ /,
240
+ *,
241
+ level: int = lggg.ERROR,
242
+ actual: h.Any = NOT_PASSED,
243
+ expected: h.Any | None = None,
244
+ expected_is_choices: bool = False,
245
+ expected_op: expected_op_h = "=",
246
+ with_final_dot: bool = True,
247
+ ) -> None:
248
+ """"""
249
+ message = FormattedMessage(
250
+ message,
251
+ actual=actual,
252
+ expected=expected,
253
+ expected_is_choices=expected_is_choices,
254
+ expected_op=expected_op,
255
+ with_final_dot=with_final_dot,
256
+ )
257
+ self.log(level, message)
258
+
259
+ def LogException(
225
260
  self,
226
261
  exception: Exception,
227
262
  /,
@@ -236,11 +271,25 @@ class logger_t(lggg.Logger):
236
271
  else:
237
272
  # TODO: Explain:
238
273
  # - Why it's not: "\n".join(lines)?
239
- # - Why adding excpetion name here and not when removing caller?
274
+ # - Why adding exception name here and not when removing caller?
240
275
  formatted = "".join(lines)
241
276
  message = f"{type(exception).__name__}:\n{formatted}"
242
277
  self.log(level, message)
243
278
 
279
+ def ShowMessage(self, message: str, /) -> None:
280
+ """
281
+ See documentation of
282
+ logger_36.catalog.handler.generic.generic_handler_t.ShowMessage.
283
+ """
284
+ for handler in self.handlers:
285
+ ShowMessage = getattr(handler, "ShowMessage", None)
286
+ if ShowMessage is not None:
287
+ ShowMessage(message)
288
+
289
+ def DisplayRule(self, /, *, text: str | None = None, color: str = "white") -> None:
290
+ """"""
291
+ self.ShowMessage(Rule(text, color))
292
+
244
293
  def AddContextLevel(self, new_level: str, /) -> None:
245
294
  """"""
246
295
  self.context_levels.append(new_level)
@@ -262,6 +311,7 @@ class logger_t(lggg.Logger):
262
311
  level: int = lggg.ERROR,
263
312
  actual: h.Any = NOT_PASSED,
264
313
  expected: h.Any | None = None,
314
+ expected_is_choices: bool = False,
265
315
  expected_op: expected_op_h = "=",
266
316
  with_final_dot: bool = False,
267
317
  ) -> None:
@@ -274,6 +324,7 @@ class logger_t(lggg.Logger):
274
324
  level=level,
275
325
  actual=actual,
276
326
  expected=expected,
327
+ expected_is_choices=expected_is_choices,
277
328
  expected_op=expected_op,
278
329
  with_final_dot=with_final_dot,
279
330
  )
@@ -335,16 +386,6 @@ class logger_t(lggg.Logger):
335
386
  self.log(int(level), issue, stacklevel=2)
336
387
  self.staged_issues.clear()
337
388
 
338
- def ShowMessage(self, message: str, /) -> None:
339
- """
340
- See documentation of
341
- logger_36.catalog.handler.generic.generic_handler_t.ShowMessage.
342
- """
343
- for handler in self.handlers:
344
- ShowMessage = getattr(handler, "ShowMessage", None)
345
- if ShowMessage is not None:
346
- ShowMessage(message)
347
-
348
389
  def __enter__(self) -> None:
349
390
  """"""
350
391
  pass
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__ = "2024.22"
7
+ __version__ = "2024.24"
8
8
 
9
9
  """
10
10
  COPYRIGHT NOTICE
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: logger-36
3
- Version: 2024.22
3
+ Version: 2024.24
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
@@ -5,10 +5,10 @@ logger_36/logger.py,sha256=7LJtdT7TmfFsn6r34iTr6OGvEjXlU6hKXEO2c5Lm2zY,2386
5
5
  logger_36/logger_gpu.py,sha256=YYFk6aYQrBDJfxQaDm-ar16T6SlOSL6jJWTOgvpF4EU,2244
6
6
  logger_36/measure.py,sha256=P507VNbVKAf4jYGnGX-3rlDrVbrYP0ZD3nxFmAFvhyI,2404
7
7
  logger_36/storage.py,sha256=O8pDmiL0B3LJpKrhi8a9IMBXs6MwW6r1bMUn_cSDAaY,2246
8
- logger_36/version.py,sha256=JPQbndmeGc-ytruoejhakNRfDt3ogZPJwu9ZkQINvwA,2206
8
+ logger_36/version.py,sha256=R2K8-57Lahk2FjSCqX05d_TyRALChV-T1s0nHQifns8,2206
9
9
  logger_36/catalog/config/console_rich.py,sha256=QDkgSs3I7ZULvkd1q4J1hdvgyB857JJcJWxM9fdL51Y,2883
10
10
  logger_36/catalog/handler/console.py,sha256=SF9S3CUoEPp5dh7RrqotywDJjMgRp0rD9sO3eLVXnkA,4004
11
- logger_36/catalog/handler/console_rich.py,sha256=Ti1k2E1ox4egzicghTb9Wv30xiWaBbWwe8ouopJsujY,8792
11
+ logger_36/catalog/handler/console_rich.py,sha256=uLZzVURQ6OIUuQEGMc142vJkEyxBbE0-HJJGQ_zOJcI,8914
12
12
  logger_36/catalog/handler/file.py,sha256=z5ovaOxemh61pbWDCK2sMMlbd1TKwGjMiQhgoicilm4,4456
13
13
  logger_36/catalog/handler/generic.py,sha256=wG6Z1-lHj_9o6cPurEVpPctFlec3BFeqx2mZU_krJt8,8379
14
14
  logger_36/catalog/logger/chronos.py,sha256=eLqQw8N9vaGO23OCf5RrYDPbUeu7epUvDt9rH-dN7i0,2522
@@ -26,7 +26,7 @@ logger_36/constant/handler.py,sha256=HM8qCSEMGNMCzddjUUNBPGL-3d0qU-EmG5eW4ZQHW6A
26
26
  logger_36/constant/issue.py,sha256=01l8itRPWGS5F6gXtsXUJgGR-4lS1Eu3_YeKC-khKLw,2315
27
27
  logger_36/constant/logger.py,sha256=0GhemAQ_YBiRO5WQBuNTczuejyVu2IYCsgqPRIbL8es,2780
28
28
  logger_36/constant/memory.py,sha256=ZL1MwbdtNsrCrOwzEyfTsfOoOsRBTJtbbf3otHGnxXo,2343
29
- logger_36/constant/message.py,sha256=RKQL-YmEDds5q7HuHTeDebz7_h3zWDX0PNxu-RTwL2I,2714
29
+ logger_36/constant/message.py,sha256=JMnCmW4j-oa-Cs1iZCJ5yAG6V4BzjLCGRIvEw6pQTtU,2719
30
30
  logger_36/constant/record.py,sha256=zebZYR4buX1lGfc7IyuvEh8zOpk7hx0aS4pJ12H0flI,2311
31
31
  logger_36/constant/system.py,sha256=G2mzBTxRXoJMxb53TnmBaceMJC_q3WonoCG7y6nC_R8,2430
32
32
  logger_36/instance/logger.py,sha256=ttKjl9MD7FUjqCWjv5w2hmmpDYxgaORcYf9NaaE9W_M,2246
@@ -34,15 +34,15 @@ logger_36/instance/loggers.py,sha256=RCWpC1NPAf6vXnFc9NqsSALv-x-FEzcH6k_OlxTxeQk
34
34
  logger_36/task/inspection.py,sha256=f9VkVrwMJ_ixV9rFu3XUNpmCbEgoo1tssqd2nMeGYLI,5028
35
35
  logger_36/task/storage.py,sha256=XaSeu-iBCa0N8HNpwCV7cLprj-lbOJocpTIKUgSOvsc,5668
36
36
  logger_36/task/format/memory.py,sha256=ECOdHjdxIqXivOwtcmwpLDMYUrutIeOTCn1L4d3-U8k,4241
37
- logger_36/task/format/message.py,sha256=X9qtXPxhXgCIjnRYBJn93vj4rW4I-7dJP6LaXD5Qu2o,4142
38
- logger_36/task/format/rule.py,sha256=YEe8wG_QLy9vRZqmT2bWlvKT-Dxp4pGaZVmEuwwODyE,2598
37
+ logger_36/task/format/message.py,sha256=HuV8we1mqh7Xcd4vbwoC0mJ8f_PjtP4sX3WFM6W7Oew,4420
38
+ logger_36/task/format/rule.py,sha256=M4a8uW7FEvMI9f4s32A9-DoP0WVlLkyXamGnqbzZ65A,2797
39
39
  logger_36/task/measure/chronos.py,sha256=t-y0bVm1SmF-3wI9pR9Bp6-qzVlsE94fZTZr5a_hZUA,2884
40
40
  logger_36/task/measure/memory.py,sha256=eVw5WOYLyn8o4O4mMArdX2MzsVuhhNDovjYEkk-MIaU,2504
41
41
  logger_36/type/handler.py,sha256=BXpevZhLq5V_IdUfi_LZA4czzlH2SGLpgvbqUBe5X10,8311
42
- logger_36/type/issue.py,sha256=5NCcDX8uTcp4Zd7HoRTYB2_f3RVjqHeDEavkssr6bg0,3114
43
- logger_36/type/logger.py,sha256=sH-eEEt3_kdYSTS4C3sLXA_pp-2MH-D_Bf6EDlJNY1E,15935
42
+ logger_36/type/issue.py,sha256=Y7OCLCzVt6Yvkecwj8HXLdZjg33oMxexc9XkYHzUhh4,3202
43
+ logger_36/type/logger.py,sha256=GClqeLtTCiItaIM0G29NNEETw765vA23npa4klOoydQ,17178
44
44
  logger_36/type/loggers.py,sha256=znqxWBnfQxvkg3VUfbTUvt3S6Kq0DAzWWepxQDt9suI,2871
45
- logger_36-2024.22.dist-info/METADATA,sha256=K5YZ0rA-ldUEqGnM3lMh09FzH9fmEY7gTZC7eWyTpcE,6276
46
- logger_36-2024.22.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
47
- logger_36-2024.22.dist-info/top_level.txt,sha256=sM95BTMWmslEEgR_1pzwZsOeSp8C_QBiu8ImbFr0XLc,10
48
- logger_36-2024.22.dist-info/RECORD,,
45
+ logger_36-2024.24.dist-info/METADATA,sha256=B8cCu3tIwEJIFQ4nKmP-lP4AzaeBJCau_ns-bguXr-8,6276
46
+ logger_36-2024.24.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
47
+ logger_36-2024.24.dist-info/top_level.txt,sha256=sM95BTMWmslEEgR_1pzwZsOeSp8C_QBiu8ImbFr0XLc,10
48
+ logger_36-2024.24.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (74.1.2)
2
+ Generator: setuptools (75.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5