logger-36 2025.12__py3-none-any.whl → 2025.14__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.
@@ -4,12 +4,24 @@ Contributor(s): Eric Debreuve (eric.debreuve@cnrs.fr) since 2023
4
4
  SEE COPYRIGHT NOTICE BELOW
5
5
  """
6
6
 
7
- from logger_36.task.handling import ( # noqa
8
- AddConsoleHandler,
9
- AddFileHandler,
10
- AddGenericHandler,
11
- AddRichConsoleHandler,
12
- )
7
+ TITLE_PLACEHOLDER = "TITLE"
8
+ BODY_PLACEHOLDER = "BODY"
9
+
10
+ MINIMAL_HTML = f"""
11
+ <!DOCTYPE html>
12
+ <html lang="en">
13
+ <head>
14
+ <meta charset="utf-8">
15
+ <title>{TITLE_PLACEHOLDER}</title>
16
+ </head>
17
+ <body>
18
+ <pre>
19
+ {BODY_PLACEHOLDER}
20
+ </pre>
21
+ </body>
22
+ </html>
23
+ """
24
+
13
25
 
14
26
  """
15
27
  COPYRIGHT NOTICE
@@ -79,7 +79,9 @@ def _HandleException(
79
79
  variables = map(
80
80
  lambda _: f"{_:{longest}} = {all_variables[_]}", sorted(found_names)
81
81
  )
82
- variables = 2 * _INDENTATION + f"\n{2*_INDENTATION}".join(variables) + "\n"
82
+ variables = (
83
+ 2 * _INDENTATION + f"\n{2 * _INDENTATION}".join(variables) + "\n"
84
+ )
83
85
  else:
84
86
  variables = ""
85
87
 
@@ -103,7 +105,7 @@ def _HandleException(
103
105
  f"{variables}"
104
106
  f"{message}"
105
107
  f"{_INDENTATION}{REPORT_COLOR}Full report at: file://{document.name}"
106
- f"{MONOCHROME}{OPTIONAL_NEWLINE}",
108
+ f"{MONOCHROME}{OPTIONAL_NEWLINE}"
107
109
  )
108
110
 
109
111
  lines = tcbk.format_exception(exception)
@@ -0,0 +1,83 @@
1
+ """
2
+ Copyright CNRS/Inria/UniCA
3
+ Contributor(s): Eric Debreuve (eric.debreuve@cnrs.fr) since 2023
4
+ SEE COPYRIGHT NOTICE BELOW
5
+ """
6
+
7
+
8
+ def WrappedLines(lines: list[str], message_width: int, /) -> list[str]:
9
+ """"""
10
+ output = []
11
+
12
+ for line in lines:
13
+ while line.__len__() > message_width:
14
+ if all(
15
+ _elm != " " for _elm in line[(message_width - 1) : (message_width + 1)]
16
+ ):
17
+ if line[message_width - 2] == " ":
18
+ piece, line = (
19
+ line[: (message_width - 2)].rstrip(),
20
+ line[(message_width - 1) :],
21
+ )
22
+ else:
23
+ piece, line = (
24
+ line[: (message_width - 1)] + "-",
25
+ line[(message_width - 1) :],
26
+ )
27
+ else:
28
+ piece, line = (
29
+ line[:message_width].rstrip(),
30
+ line[message_width:].lstrip(),
31
+ )
32
+ output.append(piece)
33
+
34
+ output.append(line)
35
+
36
+ return output
37
+
38
+
39
+ """
40
+ COPYRIGHT NOTICE
41
+
42
+ This software is governed by the CeCILL license under French law and
43
+ abiding by the rules of distribution of free software. You can use,
44
+ modify and/ or redistribute the software under the terms of the CeCILL
45
+ license as circulated by CEA, CNRS and INRIA at the following URL
46
+ "http://www.cecill.info".
47
+
48
+ As a counterpart to the access to the source code and rights to copy,
49
+ modify and redistribute granted by the license, users are provided only
50
+ with a limited warranty and the software's author, the holder of the
51
+ economic rights, and the successive licensors have only limited
52
+ liability.
53
+
54
+ In this respect, the user's attention is drawn to the risks associated
55
+ with loading, using, modifying and/or developing or reproducing the
56
+ software by the user in light of its specific status of free software,
57
+ that may mean that it is complicated to manipulate, and that also
58
+ therefore means that it is reserved for developers and experienced
59
+ professionals having in-depth computer knowledge. Users are therefore
60
+ encouraged to load and test the software's suitability as regards their
61
+ requirements in conditions enabling the security of their systems and/or
62
+ data to be ensured and, more generally, to use and operate it in the
63
+ same conditions as regards security.
64
+
65
+ The fact that you are presently reading this means that you have had
66
+ knowledge of the CeCILL license and that you accept its terms.
67
+
68
+ SEE LICENCE NOTICE: file README-LICENCE-utf8.txt at project source root.
69
+
70
+ This software is being developed by Eric Debreuve, a CNRS employee and
71
+ member of team Morpheme.
72
+ Team Morpheme is a joint team between Inria, CNRS, and UniCA.
73
+ It is hosted by the Centre Inria d'Université Côte d'Azur, Laboratory
74
+ I3S, and Laboratory iBV.
75
+
76
+ CNRS: https://www.cnrs.fr/index.php/en
77
+ Inria: https://www.inria.fr/en/
78
+ UniCA: https://univ-cotedazur.eu/
79
+ Centre Inria d'Université Côte d'Azur: https://www.inria.fr/en/centre/sophia/
80
+ I3S: https://www.i3s.unice.fr/en/
81
+ iBV: http://ibv.unice.fr/
82
+ Team Morpheme: https://team.inria.fr/morpheme/
83
+ """
@@ -16,11 +16,7 @@ _BLOCK_FULL = "▉"
16
16
 
17
17
 
18
18
  def FormattedUsage(
19
- usage: int,
20
- /,
21
- *,
22
- unit: storage_units_h | None = "a",
23
- decimals: int | None = None,
19
+ usage: int, /, *, unit: storage_units_h | None = "a", decimals: int | None = None
24
20
  ) -> tuple[int | float, str]:
25
21
  """
26
22
  unit: b or None=bytes, k=kilo, m=mega, g=giga, a=auto
@@ -5,10 +5,69 @@ SEE COPYRIGHT NOTICE BELOW
5
5
  """
6
6
 
7
7
  import difflib as diff
8
+ import logging as l
8
9
  import typing as h
9
10
 
11
+ from logger_36.config.message import (
12
+ LEVEL_CLOSING,
13
+ LEVEL_OPENING,
14
+ MESSAGE_MARKER,
15
+ WHERE_SEPARATOR,
16
+ )
10
17
  from logger_36.constant.generic import NOT_PASSED
11
- from logger_36.constant.message import expected_op_h
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
12
71
 
13
72
 
14
73
  def MessageWithActualExpected(
@@ -7,7 +7,7 @@ SEE COPYRIGHT NOTICE BELOW
7
7
  from logger_36.catalog.config.optional import RICH_IS_AVAILABLE
8
8
 
9
9
 
10
- def RuleAsText(text: str | None, /) -> str:
10
+ def RuleAsText(text: str | None, _: None, /) -> str:
11
11
  """"""
12
12
  if text is None:
13
13
  return "---- ---- ---- ---- ---- ---- ---- ---- ----"
@@ -27,7 +27,8 @@ if RICH_IS_AVAILABLE:
27
27
  return rule_t(title=text_t(text, style=f"bold {color}"), style=color)
28
28
 
29
29
  else:
30
- Rule = lambda _txt, _: RuleAsText(_txt)
30
+ rule_t = text_t = None
31
+ Rule = lambda _, __: RuleAsText(_, None)
31
32
 
32
33
  """
33
34
  COPYRIGHT NOTICE
@@ -51,7 +51,7 @@ def Modules(
51
51
 
52
52
  if formatted:
53
53
  max_length += 4
54
- AlignedInColumns = lambda _str: f"{_str:{max_length}}" if _str != "\n" else "\n"
54
+ AlignedInColumns = lambda _: f"{_:{max_length}}" if _ != "\n" else "\n"
55
55
  output = map(AlignedInColumns, output)
56
56
  output = "".join(output).rstrip()
57
57
 
logger_36/task/storage.py CHANGED
@@ -8,6 +8,7 @@ import logging as l
8
8
  from io import IOBase as io_base_t
9
9
  from pathlib import Path as path_t
10
10
 
11
+ from logger_36.constant.html import BODY_PLACEHOLDER, MINIMAL_HTML, TITLE_PLACEHOLDER
11
12
  from logger_36.instance.logger import L
12
13
 
13
14
 
@@ -35,19 +36,39 @@ def SaveLOGasHTML(path: str | path_t | io_base_t | None = None) -> None:
35
36
  path = path_t(path)
36
37
 
37
38
  actual_file = isinstance(path, path_t)
39
+
38
40
  if actual_file and path.exists():
39
41
  L.warning(f'{cannot_save}: File "{path}" already exists.')
40
42
  return
41
43
 
42
- html = L.past_logs_as_HTML
43
- if html is None:
44
- L.warning(f"{cannot_save}: No handler could provide an HTML output.")
44
+ body = "\n".join(map(_HighlightedEvent, L.recorded))
45
+ html = MINIMAL_HTML.replace(TITLE_PLACEHOLDER, L.name).replace(
46
+ BODY_PLACEHOLDER, body
47
+ )
48
+ if actual_file:
49
+ with open(path, "w") as accessor:
50
+ accessor.write(html)
45
51
  else:
46
- if actual_file:
47
- with open(path, "w") as accessor:
48
- accessor.write(html)
49
- else:
50
- path.write(html)
52
+ path.write(html)
53
+
54
+
55
+ def _HighlightedEvent(event: tuple[int, str], /) -> str:
56
+ """"""
57
+ level, message = event
58
+ if level == l.DEBUG:
59
+ color = "BlueViolet"
60
+ elif level == l.INFO:
61
+ color = "black"
62
+ elif level == l.WARNING:
63
+ color = "gold"
64
+ elif level == l.ERROR:
65
+ color = "orange"
66
+ elif level == l.CRITICAL:
67
+ color = "red"
68
+ else:
69
+ color = "black"
70
+
71
+ return f'<span style="color:{color}">{message}</span>'
51
72
 
52
73
 
53
74
  """
logger_36/type/handler.py CHANGED
@@ -4,57 +4,38 @@ Contributor(s): Eric Debreuve (eric.debreuve@cnrs.fr) since 2023
4
4
  SEE COPYRIGHT NOTICE BELOW
5
5
  """
6
6
 
7
- import dataclasses as d
8
7
  import logging as l
9
8
  import sys as s
10
9
  import typing as h
10
+ from pathlib import Path as path_t
11
11
 
12
- from logger_36.config.message import (
13
- LEVEL_CLOSING,
14
- LEVEL_OPENING,
15
- MESSAGE_MARKER,
16
- WHERE_SEPARATOR,
17
- )
12
+ from logger_36.config.message import FALLBACK_MESSAGE_WIDTH
18
13
  from logger_36.constant.error import MEMORY_MEASURE_ERROR
19
14
  from logger_36.constant.handler import HANDLER_KINDS
20
- from logger_36.constant.message import NEXT_LINE_PROLOGUE
21
- from logger_36.task.format.message import MessageWithActualExpected
15
+ from logger_36.task.format.message import MessageFromRecord, MessageWithActualExpected
22
16
  from logger_36.task.measure.chronos import TimeStamp
23
17
  from logger_36.task.measure.memory import CanCheckUsage as CanCheckMemoryUsage
24
-
25
- MessageFromRecordRaw_h = h.Callable[[l.LogRecord], str]
26
-
27
-
28
- @h.runtime_checkable
29
- class MessageFromRecordPreprocessed_p(h.Protocol):
30
- def __call__(
31
- self,
32
- record: l.LogRecord,
33
- /,
34
- *,
35
- PreProcessed: h.Callable[[str], str] | None = None,
36
- ) -> str: ...
37
-
38
-
39
- MessageFromRecord_h = MessageFromRecordRaw_h | MessageFromRecordPreprocessed_p
18
+ from logger_36.type.message import MessageFromRecord_h, RuleWithText_h
40
19
 
41
20
  _MEMORY_MEASURE_ERROR = MEMORY_MEASURE_ERROR
42
21
 
43
22
 
44
- @d.dataclass(slots=True, repr=False, eq=False)
45
- class handler_extension_t:
46
- name: str | None = None
47
- should_store_memory_usage: bool = False
48
- message_width: int = -1
49
- MessageFromRecord: MessageFromRecord_h = d.field(init=False)
23
+ class _base_t:
24
+ kind: h.ClassVar[str] = "" # See logger_36.constant.handler.handler_codes_h.
50
25
 
51
- handler: d.InitVar[l.Handler | None] = None
52
- level: d.InitVar[int] = l.NOTSET
53
- formatter: d.InitVar[l.Formatter | None] = None
54
-
55
- def __post_init__(
56
- self, handler: l.Handler | None, level: int, formatter: l.Formatter | None
26
+ def __init__(
27
+ self, name: str | None, should_store_memory_usage: bool, message_width: int
57
28
  ) -> None:
29
+ """"""
30
+ self.name = name
31
+ self.should_store_memory_usage = should_store_memory_usage
32
+ self.message_width = message_width
33
+ #
34
+ self.MessageFromRecord: MessageFromRecord_h | None = None
35
+
36
+ self.__post_init__()
37
+
38
+ def __post_init__(self) -> None:
58
39
  """"""
59
40
  global _MEMORY_MEASURE_ERROR
60
41
 
@@ -76,81 +57,95 @@ class handler_extension_t:
76
57
  s.__stderr__.write(_MEMORY_MEASURE_ERROR + "\n")
77
58
  _MEMORY_MEASURE_ERROR = None
78
59
 
79
- handler.setLevel(level)
60
+ if 0 < self.message_width < FALLBACK_MESSAGE_WIDTH:
61
+ self.message_width = FALLBACK_MESSAGE_WIDTH
80
62
 
81
- if 0 < self.message_width < 5:
82
- self.message_width = 5
83
- if formatter is None:
84
- self.MessageFromRecord = self._MessageFromRecord
85
- else:
86
- handler.setFormatter(formatter)
87
- self.MessageFromRecord = handler.formatter.format
63
+ @classmethod
64
+ def New(cls, **kwargs) -> h.Self:
65
+ """
66
+ Interest: default arguments, no prescribed argument order, variable argument list.
67
+ """
68
+ raise NotImplementedError
88
69
 
89
- def _MessageFromRecord(
90
- self,
91
- record: l.LogRecord,
92
- /,
93
- *,
94
- PreProcessed: h.Callable[[str], str] | None = None,
95
- ) -> str:
70
+ def LogAsIs(self, message: str, /) -> None:
96
71
  """
97
- See logger_36.catalog.handler.README.txt.
72
+ See documentation of
73
+ logger_36.catalog.handler.generic.generic_handler_t.LogAsIs.
98
74
  """
99
- message = record.msg
100
-
101
- if PreProcessed is not None:
102
- message = PreProcessed(message)
103
- if (self.message_width <= 0) or (message.__len__() <= self.message_width):
104
- if "\n" in message:
105
- message = NEXT_LINE_PROLOGUE.join(message.splitlines())
106
- else:
107
- if "\n" in message:
108
- lines = _WrappedLines(message.splitlines(), self.message_width)
109
- else:
110
- lines = _WrappedLines([message], self.message_width)
111
- message = NEXT_LINE_PROLOGUE.join(lines)
112
-
113
- if (where := getattr(record, "where", None)) is None:
114
- where = ""
115
- else:
116
- where = f"{NEXT_LINE_PROLOGUE}{WHERE_SEPARATOR} {where}"
117
-
118
- return (
119
- f"{record.when_or_elapsed}"
120
- f"{LEVEL_OPENING}{record.level_first_letter}{LEVEL_CLOSING} "
121
- f"{MESSAGE_MARKER} {message}{where}"
122
- )
123
-
124
-
125
- def _WrappedLines(lines: list[str], message_width: int, /) -> list[str]:
126
- """"""
127
- output = []
128
-
129
- for line in lines:
130
- while line.__len__() > message_width:
131
- if all(
132
- _elm != " " for _elm in line[(message_width - 1) : (message_width + 1)]
133
- ):
134
- if line[message_width - 2] == " ":
135
- piece, line = (
136
- line[: (message_width - 2)].rstrip(),
137
- line[(message_width - 1) :],
138
- )
139
- else:
140
- piece, line = (
141
- line[: (message_width - 1)] + "-",
142
- line[(message_width - 1) :],
143
- )
144
- else:
145
- piece, line = (
146
- line[:message_width].rstrip(),
147
- line[message_width:].lstrip(),
148
- )
149
- output.append(piece)
75
+ raise NotImplementedError
150
76
 
151
- output.append(line)
77
+ def DisplayRule(self, /, *, text: str | None = None, color: str = "black") -> None:
78
+ """"""
79
+ raise NotImplementedError
152
80
 
153
- return output
81
+
82
+ class handler_t(l.Handler, _base_t):
83
+ def __init__(
84
+ self,
85
+ name: str | None,
86
+ should_store_memory_usage: bool,
87
+ message_width: int,
88
+ level: int,
89
+ formatter: l.Formatter | None,
90
+ *_,
91
+ ) -> None:
92
+ """"""
93
+ l.Handler.__init__(self)
94
+ _base_t.__init__(self, name, should_store_memory_usage, message_width)
95
+ __post_init__(self, level, formatter)
96
+
97
+
98
+ class file_handler_t(l.FileHandler, _base_t):
99
+ def __init__(
100
+ self,
101
+ name: str | None,
102
+ should_store_memory_usage: bool,
103
+ message_width: int,
104
+ level: int,
105
+ formatter: l.Formatter | None,
106
+ path: str | path_t | None,
107
+ *_,
108
+ ) -> None:
109
+ """"""
110
+ if path is None:
111
+ raise ValueError("Missing file or folder.")
112
+ if isinstance(path, str):
113
+ path = path_t(path)
114
+ if path.exists():
115
+ raise ValueError(f"File or folder already exists: {path}.")
116
+
117
+ l.FileHandler.__init__(self, path)
118
+ _base_t.__init__(self, name, should_store_memory_usage, message_width)
119
+ __post_init__(self, level, formatter)
120
+
121
+
122
+ any_handler_t = handler_t | file_handler_t
123
+
124
+
125
+ def __post_init__(
126
+ handler: any_handler_t, level: int, formatter: l.Formatter | None
127
+ ) -> None:
128
+ """"""
129
+ handler.setLevel(level)
130
+
131
+ if formatter is None:
132
+ handler.MessageFromRecord = MessageFromRecord
133
+ else:
134
+ handler.setFormatter(formatter)
135
+ _MessageFromRecordRaw = handler.formatter.format
136
+
137
+ def _MessageFromRecord(
138
+ record: l.LogRecord,
139
+ _: RuleWithText_h,
140
+ /,
141
+ *,
142
+ line_width: int = 0,
143
+ PreProcessed: h.Callable[[str], str] | None = None,
144
+ ) -> tuple[str, bool]:
145
+ #
146
+ return _MessageFromRecordRaw(record), False
147
+
148
+ handler.MessageFromRecord = _MessageFromRecord
154
149
 
155
150
 
156
151
  """