logger-36 2025.23__py3-none-any.whl → 2025.25__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/{gpu.py → chronos.py} +1 -1
- logger_36/api/memory.py +0 -9
- logger_36/api/storage.py +0 -1
- logger_36/catalog/handler/console.py +1 -1
- logger_36/catalog/handler/console_rich.py +33 -22
- logger_36/catalog/handler/file.py +1 -2
- logger_36/catalog/handler/generic.py +24 -10
- logger_36/catalog/handler/memory.py +31 -6
- logger_36/catalog/logger/chronos.py +10 -2
- logger_36/catalog/logger/memory.py +1 -1
- logger_36/catalog/logger/system.py +6 -5
- logger_36/config/message.py +1 -4
- logger_36/{api/system.py → config/rule.py} +2 -1
- logger_36/constant/chronos.py +50 -0
- logger_36/constant/error.py +2 -0
- logger_36/constant/html.py +2 -0
- logger_36/constant/message.py +7 -7
- logger_36/constant/path.py +8 -7
- logger_36/constant/record.py +2 -0
- logger_36/constant/rule.py +2 -2
- logger_36/{api/time.py → extension/file.py} +18 -2
- logger_36/{task → extension}/inspection.py +15 -29
- logger_36/extension/line.py +1 -1
- logger_36/task/format/memory.py +1 -1
- logger_36/task/format/message.py +7 -5
- logger_36/task/measure/chronos.py +21 -13
- logger_36/task/storage.py +32 -48
- logger_36/type/handler.py +26 -32
- logger_36/type/issue.py +7 -4
- logger_36/type/logger.py +164 -88
- logger_36/version.py +1 -1
- {logger_36-2025.23.dist-info → logger_36-2025.25.dist-info}/METADATA +30 -44
- logger_36-2025.25.dist-info/RECORD +53 -0
- logger_36/extension/html_.py +0 -93
- logger_36-2025.23.dist-info/RECORD +0 -53
- /logger_36/api/{type.py → logger.py} +0 -0
- /logger_36/api/{content.py → message.py} +0 -0
- /logger_36/{constant/generic.py → extension/sentinel.py} +0 -0
- {logger_36-2025.23.dist-info → logger_36-2025.25.dist-info}/WHEEL +0 -0
- {logger_36-2025.23.dist-info → logger_36-2025.25.dist-info}/top_level.txt +0 -0
logger_36/task/format/message.py
CHANGED
@@ -7,8 +7,8 @@ SEE COPYRIGHT NOTICE BELOW
|
|
7
7
|
import difflib as diff
|
8
8
|
import typing as h
|
9
9
|
|
10
|
-
from logger_36.constant.generic import NOT_PASSED
|
11
10
|
from logger_36.constant.message import expected_op_h
|
11
|
+
from logger_36.extension.sentinel import NOT_PASSED
|
12
12
|
|
13
13
|
|
14
14
|
def MessageWithActualExpected(
|
@@ -20,8 +20,10 @@ def MessageWithActualExpected(
|
|
20
20
|
expected_is_choices: bool = False,
|
21
21
|
expected_op: expected_op_h = "=",
|
22
22
|
with_final_dot: bool = True,
|
23
|
-
) -> str:
|
24
|
-
"""
|
23
|
+
) -> tuple[str, bool]:
|
24
|
+
"""
|
25
|
+
Second return: has_actual_expected.
|
26
|
+
"""
|
25
27
|
if actual is NOT_PASSED:
|
26
28
|
if with_final_dot:
|
27
29
|
if message[-1] != ".":
|
@@ -29,7 +31,7 @@ def MessageWithActualExpected(
|
|
29
31
|
elif message[-1] == ".":
|
30
32
|
message = message[:-1]
|
31
33
|
|
32
|
-
return message
|
34
|
+
return message, False
|
33
35
|
|
34
36
|
if message[-1] == ".":
|
35
37
|
message = message[:-1]
|
@@ -44,7 +46,7 @@ def MessageWithActualExpected(
|
|
44
46
|
else:
|
45
47
|
actual = f"{actual}:{type(actual).__name__}"
|
46
48
|
|
47
|
-
return f"{message}: Actual={actual}; {expected}{dot}"
|
49
|
+
return f"{message}: Actual={actual}; {expected}{dot}", True
|
48
50
|
|
49
51
|
|
50
52
|
def _FormattedExpected(
|
@@ -4,34 +4,42 @@ Contributor(s): Eric Debreuve (eric.debreuve@cnrs.fr) since 2023
|
|
4
4
|
SEE COPYRIGHT NOTICE BELOW
|
5
5
|
"""
|
6
6
|
|
7
|
-
import time
|
8
7
|
from datetime import datetime as date_time_t
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
from logger_36.config.message import ELAPSED_TIME_SEPARATOR
|
10
|
+
from logger_36.constant.chronos import START_DATE_TIME
|
11
|
+
from logger_36.constant.message import TIME_LENGTH
|
13
12
|
|
14
13
|
|
15
14
|
def TimeStamp(*, precision: str = "microseconds") -> str:
|
16
|
-
"""
|
15
|
+
"""
|
16
|
+
precision: See documentation of date_time_t.isoformat.
|
17
|
+
"""
|
17
18
|
return (
|
18
19
|
date_time_t.now()
|
19
20
|
.isoformat(timespec=precision)
|
20
|
-
.replace(".", "
|
21
|
+
.replace(".", "_")
|
21
22
|
.replace(":", "-")
|
22
23
|
)
|
23
24
|
|
24
25
|
|
25
|
-
def
|
26
|
+
def FormattedElapsedTime(
|
27
|
+
now: date_time_t, /, *, reference: date_time_t = START_DATE_TIME
|
28
|
+
) -> str:
|
26
29
|
""""""
|
27
|
-
|
28
|
-
|
29
|
-
output
|
30
|
+
output = str(now - reference)
|
31
|
+
|
32
|
+
if output.startswith("0:"):
|
33
|
+
output = output[2:]
|
30
34
|
while output.startswith("00:"):
|
31
|
-
output = output
|
35
|
+
output = output[3:]
|
36
|
+
if output[0] == "0":
|
37
|
+
output = output[1:]
|
38
|
+
|
39
|
+
output = ELAPSED_TIME_SEPARATOR + output
|
32
40
|
|
33
|
-
if
|
34
|
-
return output
|
41
|
+
if output.__len__() > TIME_LENGTH:
|
42
|
+
return output[:TIME_LENGTH]
|
35
43
|
return output
|
36
44
|
|
37
45
|
|
logger_36/task/storage.py
CHANGED
@@ -8,7 +8,14 @@ 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.
|
11
|
+
from logger_36.constant.error import CANNOT_SAVE_RECORDS
|
12
|
+
from logger_36.constant.html import (
|
13
|
+
BODY_PLACEHOLDER,
|
14
|
+
HTML_SUFFIX,
|
15
|
+
MINIMAL_HTML,
|
16
|
+
TITLE_PLACEHOLDER,
|
17
|
+
)
|
18
|
+
from logger_36.extension.file import NewTemporaryFile
|
12
19
|
from logger_36.instance.logger import L
|
13
20
|
from logger_36.type.logger import logger_t
|
14
21
|
|
@@ -19,53 +26,17 @@ def SaveLOGasHTML(
|
|
19
26
|
"""
|
20
27
|
From first console handler found.
|
21
28
|
"""
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
path = path_t(handler.baseFilename).with_suffix(".htm")
|
28
|
-
break
|
29
|
-
else:
|
30
|
-
logger.warning(f"{cannot_save}: No file handler to build a filename from.")
|
31
|
-
return
|
32
|
-
|
33
|
-
if path.exists():
|
34
|
-
logger.warning(
|
35
|
-
f'{cannot_save}: Automatically generated path "{path}" already exists.'
|
36
|
-
)
|
37
|
-
return
|
38
|
-
elif isinstance(path, str):
|
39
|
-
path = path_t(path)
|
40
|
-
|
41
|
-
actual_file = isinstance(path, path_t)
|
42
|
-
|
43
|
-
if actual_file and path.exists():
|
44
|
-
logger.warning(f'{cannot_save}: File "{path}" already exists.')
|
45
|
-
return
|
46
|
-
|
47
|
-
for handler in logger.handlers:
|
48
|
-
records = getattr(handler, "records", None)
|
49
|
-
if isinstance(records, list) and (
|
50
|
-
(records.__len__() == 0)
|
51
|
-
or all(
|
52
|
-
isinstance(_, tuple)
|
53
|
-
and isinstance(_[0], int)
|
54
|
-
and isinstance(_[1], dict | str | l.LogRecord)
|
55
|
-
and isinstance(_[2], bool)
|
56
|
-
for _ in records
|
57
|
-
)
|
58
|
-
):
|
59
|
-
break
|
60
|
-
else:
|
61
|
-
logger.warning(f"{cannot_save}: No handlers with recording capability found.")
|
29
|
+
records = logger_t.Records(logger)
|
30
|
+
if records is None:
|
31
|
+
logger.warning(
|
32
|
+
f"{CANNOT_SAVE_RECORDS}: No handlers with recording capability found"
|
33
|
+
)
|
62
34
|
return
|
63
|
-
|
64
35
|
if records.__len__() == 0:
|
65
36
|
return
|
66
37
|
|
67
38
|
if isinstance(records[0][1], str):
|
68
|
-
records = map(
|
39
|
+
records = map(_HighlightedRecord, records)
|
69
40
|
else:
|
70
41
|
records = map(lambda _: str(_[1]), records)
|
71
42
|
body = "\n".join(records)
|
@@ -73,16 +44,29 @@ def SaveLOGasHTML(
|
|
73
44
|
BODY_PLACEHOLDER, body
|
74
45
|
)
|
75
46
|
|
76
|
-
if
|
77
|
-
|
78
|
-
|
47
|
+
if path is None:
|
48
|
+
path = logger_t.StoragePath(logger, HTML_SUFFIX)
|
49
|
+
logger.info(f'Saving LOG as HTML in "{path}"')
|
50
|
+
elif isinstance(path, str):
|
51
|
+
path = path_t(path)
|
52
|
+
if path.exists():
|
53
|
+
existing = path
|
54
|
+
path = NewTemporaryFile(HTML_SUFFIX)
|
55
|
+
logger.warning(
|
56
|
+
f'File "{existing}" already exists: '
|
57
|
+
f'Saving LOG as HTML in "{path}" instead'
|
58
|
+
)
|
79
59
|
else:
|
80
60
|
path.write(html)
|
61
|
+
return
|
62
|
+
|
63
|
+
with open(path, "w") as accessor:
|
64
|
+
accessor.write(html)
|
81
65
|
|
82
66
|
|
83
|
-
def
|
67
|
+
def _HighlightedRecord(record: tuple[int, str, bool], /) -> str:
|
84
68
|
""""""
|
85
|
-
level, message, is_not_a_rule =
|
69
|
+
level, message, is_not_a_rule = record
|
86
70
|
|
87
71
|
if is_not_a_rule:
|
88
72
|
if level == l.DEBUG:
|
logger_36/type/handler.py
CHANGED
@@ -8,21 +8,16 @@ 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.config.message import
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
11
|
+
from logger_36.config.message import FALLBACK_MESSAGE_WIDTH
|
12
|
+
from logger_36.config.rule import DEFAULT_RULE_LENGTH, RULE_CHARACTER
|
13
|
+
from logger_36.constant.message import (
|
14
|
+
NEXT_LINE_PROLOGUE,
|
15
|
+
TIME_PLACEHOLDER,
|
16
|
+
WHERE_PROLOGUE,
|
17
|
+
CONTEXT_LENGTH_p_1,
|
17
18
|
)
|
18
|
-
from logger_36.constant.message import NEXT_LINE_PROLOGUE
|
19
19
|
from logger_36.constant.record import SHOW_W_RULE_ATTR, WHEN_OR_ELAPSED_ATTR, WHERE_ATTR
|
20
|
-
from logger_36.constant.rule import
|
21
|
-
DEFAULT_RULE,
|
22
|
-
DEFAULT_RULE_LENGTH,
|
23
|
-
MIN_HALF_RULE_LENGTH,
|
24
|
-
RULE_CHARACTER,
|
25
|
-
)
|
20
|
+
from logger_36.constant.rule import DEFAULT_RULE, MIN_HALF_RULE_LENGTH
|
26
21
|
from logger_36.extension.line import WrappedLines
|
27
22
|
|
28
23
|
|
@@ -56,42 +51,41 @@ class extension_t:
|
|
56
51
|
raise NotImplementedError
|
57
52
|
|
58
53
|
def MessageFromRecord(
|
59
|
-
self, record: l.LogRecord, /, *, rule_color: str
|
60
|
-
) -> tuple[str, bool]:
|
54
|
+
self, record: l.LogRecord, /, *, rule_color: str = "black"
|
55
|
+
) -> tuple[str, bool, int | None]:
|
61
56
|
"""
|
62
|
-
|
57
|
+
Arguments from second on: is_not_a_rule, where_location.
|
63
58
|
"""
|
64
59
|
message = record.msg # See logger_36.catalog.handler.README.txt.
|
65
60
|
if self.PreProcessedMessage is not None:
|
66
61
|
message = self.PreProcessedMessage(message)
|
67
62
|
|
68
63
|
if hasattr(record, SHOW_W_RULE_ATTR):
|
69
|
-
return self.Rule(text=message, color=rule_color), False
|
64
|
+
return self.Rule(text=message, color=rule_color), False, None
|
70
65
|
|
71
66
|
if (self.message_width <= 0) or (message.__len__() <= self.message_width):
|
72
67
|
if "\n" in message:
|
73
68
|
message = NEXT_LINE_PROLOGUE.join(message.splitlines())
|
74
69
|
else:
|
75
70
|
if "\n" in message:
|
76
|
-
lines =
|
71
|
+
lines = message.splitlines()
|
77
72
|
else:
|
78
|
-
lines =
|
79
|
-
message = NEXT_LINE_PROLOGUE.join(lines)
|
80
|
-
|
81
|
-
when_or_elapsed = getattr(record, WHEN_OR_ELAPSED_ATTR, None)
|
82
|
-
if when_or_elapsed is None:
|
83
|
-
return message, True
|
73
|
+
lines = [message]
|
74
|
+
message = NEXT_LINE_PROLOGUE.join(WrappedLines(lines, self.message_width))
|
84
75
|
|
76
|
+
when_or_elapsed = getattr(record, WHEN_OR_ELAPSED_ATTR, TIME_PLACEHOLDER)
|
85
77
|
if (where := getattr(record, WHERE_ATTR, None)) is None:
|
78
|
+
where_location = None
|
86
79
|
where = ""
|
87
80
|
else:
|
88
|
-
|
81
|
+
where_location = CONTEXT_LENGTH_p_1 + message.__len__()
|
82
|
+
where = f"{WHERE_PROLOGUE}{where}"
|
89
83
|
|
90
84
|
return (
|
91
|
-
f"{when_or_elapsed}"
|
92
|
-
|
93
|
-
|
94
|
-
)
|
85
|
+
f"{when_or_elapsed}_{record.levelname[0].lower()} {message}{where}",
|
86
|
+
True,
|
87
|
+
where_location,
|
88
|
+
)
|
95
89
|
|
96
90
|
def Rule(self, /, *, text: str | None = None, color: str = "black") -> str | h.Any:
|
97
91
|
"""
|
@@ -122,7 +116,7 @@ class extension_t:
|
|
122
116
|
self.EmitMessage(self.Rule(text=text, color=color))
|
123
117
|
|
124
118
|
|
125
|
-
class
|
119
|
+
class non_file_handler_t(l.Handler, extension_t):
|
126
120
|
def __init__(
|
127
121
|
self,
|
128
122
|
name: str | None,
|
@@ -160,10 +154,10 @@ class file_handler_t(l.FileHandler, extension_t):
|
|
160
154
|
__post_init__(self, level)
|
161
155
|
|
162
156
|
|
163
|
-
|
157
|
+
handler_h = non_file_handler_t | file_handler_t
|
164
158
|
|
165
159
|
|
166
|
-
def __post_init__(handler:
|
160
|
+
def __post_init__(handler: handler_h, level: int) -> None:
|
167
161
|
""""""
|
168
162
|
handler.setLevel(level)
|
169
163
|
|
logger_36/type/issue.py
CHANGED
@@ -8,9 +8,9 @@ import logging as l
|
|
8
8
|
import typing as h
|
9
9
|
|
10
10
|
from logger_36.config.issue import ISSUE_BASE_CONTEXT
|
11
|
-
from logger_36.constant.generic import NOT_PASSED
|
12
11
|
from logger_36.constant.issue import ISSUE_LEVEL_SEPARATOR
|
13
12
|
from logger_36.constant.message import expected_op_h
|
13
|
+
from logger_36.extension.sentinel import NOT_PASSED
|
14
14
|
from logger_36.task.format.message import MessageWithActualExpected
|
15
15
|
|
16
16
|
issue_t = str
|
@@ -28,11 +28,11 @@ def NewIssue(
|
|
28
28
|
expected_is_choices: bool = False,
|
29
29
|
expected_op: expected_op_h = "=",
|
30
30
|
with_final_dot: bool = True,
|
31
|
-
) -> issue_t:
|
31
|
+
) -> tuple[issue_t, bool]:
|
32
32
|
""""""
|
33
33
|
if context.__len__() == 0:
|
34
34
|
context = ISSUE_BASE_CONTEXT
|
35
|
-
message = MessageWithActualExpected(
|
35
|
+
message, has_actual_expected = MessageWithActualExpected(
|
36
36
|
message,
|
37
37
|
actual=actual,
|
38
38
|
expected=expected,
|
@@ -41,7 +41,10 @@ def NewIssue(
|
|
41
41
|
with_final_dot=with_final_dot,
|
42
42
|
)
|
43
43
|
|
44
|
-
return
|
44
|
+
return (
|
45
|
+
f"{level}{ISSUE_LEVEL_SEPARATOR}{context}{separator}{message}",
|
46
|
+
has_actual_expected,
|
47
|
+
)
|
45
48
|
|
46
49
|
|
47
50
|
"""
|