logger-36 2024.1__py3-none-any.whl → 2025.3__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. logger_36/__init__.py +64 -42
  2. logger_36/api/logger.py +53 -0
  3. logger_36/api/storage.py +53 -0
  4. logger_36/catalog/config/console_rich.py +76 -0
  5. logger_36/catalog/handler/console.py +117 -0
  6. logger_36/catalog/handler/console_rich.py +235 -0
  7. logger_36/catalog/handler/file.py +128 -0
  8. logger_36/catalog/handler/generic.py +228 -0
  9. logger_36/catalog/logger/chronos.py +61 -0
  10. logger_36/catalog/logger/gpu.py +90 -0
  11. logger_36/catalog/logger/memory.py +129 -0
  12. logger_36/catalog/logger/system.py +84 -0
  13. logger_36/config/issue.py +56 -0
  14. logger_36/config/logger.py +103 -0
  15. logger_36/config/memory.py +54 -0
  16. logger_36/config/message.py +66 -0
  17. logger_36/config/system.py +70 -0
  18. logger_36/constant/error.py +70 -0
  19. logger_36/constant/generic.py +58 -0
  20. logger_36/constant/handler.py +58 -0
  21. logger_36/constant/issue.py +58 -0
  22. logger_36/constant/logger.py +67 -0
  23. logger_36/constant/memory.py +58 -0
  24. logger_36/constant/message.py +72 -0
  25. logger_36/constant/record.py +55 -0
  26. logger_36/constant/system.py +60 -0
  27. logger_36/content.py +55 -0
  28. logger_36/exception.py +105 -0
  29. logger_36/gpu.py +53 -0
  30. logger_36/handler.py +209 -0
  31. logger_36/instance/logger.py +55 -0
  32. logger_36/instance/loggers.py +56 -0
  33. logger_36/memory.py +60 -0
  34. logger_36/storage.py +53 -0
  35. logger_36/system.py +53 -0
  36. logger_36/task/format/memory.py +132 -0
  37. logger_36/task/format/message.py +111 -0
  38. logger_36/task/format/rule.py +74 -0
  39. logger_36/task/inspection.py +70 -48
  40. logger_36/task/measure/chronos.py +84 -0
  41. logger_36/task/measure/memory.py +72 -0
  42. logger_36/task/storage.py +127 -46
  43. logger_36/time.py +54 -0
  44. logger_36/type/handler.py +184 -0
  45. logger_36/type/issue.py +91 -0
  46. logger_36/type/logger.py +542 -0
  47. logger_36/type/loggers.py +78 -0
  48. logger_36/version.py +53 -32
  49. logger_36-2025.3.dist-info/METADATA +154 -0
  50. logger_36-2025.3.dist-info/RECORD +52 -0
  51. {logger_36-2024.1.dist-info → logger_36-2025.3.dist-info}/WHEEL +1 -1
  52. logger_36/catalog/gpu.py +0 -56
  53. logger_36/catalog/memory.py +0 -109
  54. logger_36/catalog/system.py +0 -84
  55. logger_36/config.py +0 -48
  56. logger_36/constant.py +0 -52
  57. logger_36/instance.py +0 -34
  58. logger_36/main.py +0 -96
  59. logger_36/measure/chronos.py +0 -55
  60. logger_36/measure/memory.py +0 -50
  61. logger_36/type/console.py +0 -122
  62. logger_36/type/extension.py +0 -122
  63. logger_36/type/file.py +0 -52
  64. logger_36/type/generic.py +0 -115
  65. logger_36-2024.1.dist-info/METADATA +0 -106
  66. logger_36-2024.1.dist-info/RECORD +0 -21
  67. {logger_36-2024.1.dist-info → logger_36-2025.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,542 @@
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
+ import dataclasses as d
8
+ import logging as lggg
9
+ import sys as sstm
10
+ import traceback as tcbk
11
+ import types as t
12
+ import typing as h
13
+ from datetime import date as date_t
14
+ from datetime import datetime as date_time_t
15
+ from os import sep as FOLDER_SEPARATOR
16
+ from pathlib import Path as path_t
17
+ from traceback import TracebackException as traceback_t
18
+
19
+ from logger_36.config.issue import ISSUE_CONTEXT_END, ISSUE_CONTEXT_SEPARATOR
20
+ from logger_36.config.message import (
21
+ DATE_FORMAT,
22
+ ELAPSED_TIME_SEPARATOR,
23
+ LONG_ENOUGH,
24
+ TIME_FORMAT,
25
+ )
26
+ from logger_36.constant.generic import NOT_PASSED
27
+ from logger_36.constant.handler import ANONYMOUS
28
+ from logger_36.constant.issue import ISSUE_LEVEL_SEPARATOR, ORDER, order_h
29
+ from logger_36.constant.logger import (
30
+ LOGGER_NAME,
31
+ WARNING_LOGGER_NAME,
32
+ WARNING_TYPE_COMPILED_PATTERN,
33
+ logger_handle_h,
34
+ )
35
+ from logger_36.constant.memory import UNKNOWN_MEMORY_USAGE
36
+ from logger_36.constant.message import TIME_LENGTH_m_1, expected_op_h
37
+ from logger_36.constant.record import (
38
+ HIDE_WHERE_ATTR,
39
+ SHOW_W_RULE_ATTR,
40
+ STORE_MEMORY_ATTR,
41
+ )
42
+ from logger_36.task.format.message import MessageWithActualExpected
43
+ from logger_36.task.measure.chronos import ElapsedTime
44
+ from logger_36.task.measure.memory import CurrentUsage as CurrentMemoryUsage
45
+ from logger_36.type.issue import NewIssue, issue_t
46
+
47
+ logger_base_t = lggg.Logger
48
+
49
+ _DATE_TIME_ORIGIN = date_time_t.fromtimestamp(1970, None)
50
+ _DATE_ORIGIN = _DATE_TIME_ORIGIN.date()
51
+
52
+
53
+ @d.dataclass(slots=True, repr=False, eq=False)
54
+ class logger_t(logger_base_t):
55
+ """
56
+ intercepted_wrn_handle: When warning interception is on, this stores the original
57
+ "handle" method of the Python warning logger.
58
+ """
59
+ name_: d.InitVar[str] = LOGGER_NAME
60
+ level_: d.InitVar[int] = lggg.NOTSET
61
+ activate_wrn_interceptions: d.InitVar[bool] = True
62
+
63
+ # Must not be False until at least one handler has been added.
64
+ should_hold_messages: bool = True
65
+ exit_on_error: bool = False # Implies exit_on_critical.
66
+ exit_on_critical: bool = False
67
+
68
+ on_hold: list[lggg.LogRecord] = d.field(init=False, default_factory=list)
69
+ events: dict[int, int] = d.field(init=False, default_factory=dict)
70
+ last_message_now: date_time_t = d.field(init=False, default=_DATE_TIME_ORIGIN)
71
+ last_message_date: date_t = d.field(init=False, default=_DATE_ORIGIN)
72
+ any_handler_stores_memory: bool = d.field(init=False, default=False)
73
+ memory_usages: list[tuple[str, int]] = d.field(init=False, default_factory=list)
74
+ context_levels: list[str] = d.field(init=False, default_factory=list)
75
+ staged_issues: list[issue_t] = d.field(init=False, default_factory=list)
76
+ intercepted_wrn_handle: logger_handle_h | None = d.field(init=False, default=None)
77
+ intercepted_log_handles: dict[str, logger_handle_h] = d.field(
78
+ init=False, default_factory=dict
79
+ )
80
+
81
+ def __post_init__(
82
+ self, name_: str, level_: int, activate_wrn_interceptions: bool
83
+ ) -> None:
84
+ """"""
85
+ logger_base_t.__init__(self, name_)
86
+ self.setLevel(level_)
87
+ self.propagate = False # Part of logger_base_t.
88
+
89
+ for level in lggg.getLevelNamesMapping().values():
90
+ self.events[level] = 0
91
+
92
+ if activate_wrn_interceptions:
93
+ self._ActivateWarningInterceptions()
94
+ if self.exit_on_error:
95
+ self.exit_on_critical = True
96
+
97
+ def ResetEventCounts(self) -> None:
98
+ """"""
99
+ for level in self.events:
100
+ self.events[level] = 0
101
+
102
+ def _ActivateWarningInterceptions(self) -> None:
103
+ """
104
+ The log message will not appear if called from __post_init__ since there are no
105
+ handlers yet.
106
+ """
107
+ if self.intercepted_wrn_handle is None:
108
+ logger = lggg.getLogger(WARNING_LOGGER_NAME)
109
+ self.intercepted_wrn_handle = logger.handle
110
+ logger.handle = t.MethodType(_HandleForWarnings(self), logger)
111
+
112
+ lggg.captureWarnings(True)
113
+ self.info("Warning Interception: ON")
114
+
115
+ def _DeactivateWarningInterceptions(self) -> None:
116
+ """"""
117
+ if self.intercepted_wrn_handle is not None:
118
+ logger = lggg.getLogger(WARNING_LOGGER_NAME)
119
+ logger.handle = self.intercepted_wrn_handle
120
+ self.intercepted_wrn_handle = None
121
+
122
+ lggg.captureWarnings(False)
123
+ self.info("Warning Interception: OFF")
124
+
125
+ def ToggleWarningInterceptions(self, state: bool, /) -> None:
126
+ """"""
127
+ if state:
128
+ self._ActivateWarningInterceptions()
129
+ else:
130
+ self._DeactivateWarningInterceptions()
131
+
132
+ def ToggleLogInterceptions(self, state: bool, /) -> None:
133
+ """"""
134
+ if state:
135
+ self.ToggleLogInterceptions(False)
136
+
137
+ all_loggers = [lggg.getLogger()] + [
138
+ lggg.getLogger(_nme)
139
+ for _nme in self.manager.loggerDict
140
+ if _nme not in (self.name, WARNING_LOGGER_NAME)
141
+ ]
142
+ for logger in all_loggers:
143
+ self.intercepted_log_handles[logger.name] = logger.handle
144
+ logger.handle = t.MethodType(
145
+ _HandleForInterceptions(logger, self), logger
146
+ )
147
+
148
+ intercepted = sorted(self.intercepted_log_handles.keys())
149
+ if intercepted.__len__() > 0:
150
+ as_str = ", ".join(intercepted)
151
+ self.info(f"Now Intercepting LOGs from: {as_str}")
152
+ elif self.intercepted_log_handles.__len__() > 0:
153
+ for name, handle in self.intercepted_log_handles.items():
154
+ logger = lggg.getLogger(name)
155
+ logger.handle = handle
156
+ self.intercepted_log_handles.clear()
157
+ self.info("Log Interception: OFF")
158
+
159
+ @property
160
+ def max_memory_usage(self) -> int:
161
+ """"""
162
+ if self.memory_usages.__len__() > 0:
163
+ return max(tuple(zip(*self.memory_usages))[1])
164
+ return UNKNOWN_MEMORY_USAGE
165
+
166
+ @property
167
+ def max_memory_usage_full(self) -> tuple[str, int]:
168
+ """"""
169
+ if self.memory_usages.__len__() > 0:
170
+ where_s, usages = zip(*self.memory_usages)
171
+ max_usage = max(usages)
172
+
173
+ return where_s[usages.index(max_usage)], max_usage
174
+
175
+ return "?", UNKNOWN_MEMORY_USAGE
176
+
177
+ def AddHandler(self, handler: lggg.Handler, should_hold_messages: bool, /) -> None:
178
+ """"""
179
+ self.should_hold_messages = should_hold_messages
180
+ logger_base_t.addHandler(self, handler)
181
+
182
+ extension = getattr(handler, "extension", None)
183
+ if extension is None:
184
+ name = handler.name
185
+ if (name is None) or (name.__len__() == 0):
186
+ name = ANONYMOUS
187
+ else:
188
+ name = getattr(extension, "name", ANONYMOUS)
189
+ if getattr(extension, STORE_MEMORY_ATTR, False):
190
+ self.any_handler_stores_memory = True
191
+
192
+ path = getattr(handler, "baseFilename", "")
193
+ if isinstance(path, path_t) or (path.__len__() > 0):
194
+ path = f"\nPath: {path}"
195
+
196
+ self.info(
197
+ f'New handler "{name}" of type "{type(handler).__name__}" and '
198
+ f"level {handler.level}={lggg.getLevelName(handler.level)}{path}",
199
+ )
200
+
201
+ def handle(self, record: lggg.LogRecord, /) -> None:
202
+ """"""
203
+ elapsed_time, now = ElapsedTime(should_return_now=True)
204
+
205
+ if (self.on_hold.__len__() > 0) and not self.should_hold_messages:
206
+ for held in self.on_hold:
207
+ logger_base_t.handle(self, held)
208
+ self.on_hold.clear()
209
+
210
+ if (date := now.date()) != self.last_message_date:
211
+ self.last_message_date = date
212
+ # levelno: Added for management by logging.Logger.handle.
213
+ date_record = lggg.makeLogRecord(
214
+ {
215
+ "name": self.name,
216
+ "levelno": lggg.INFO,
217
+ "msg": f"DATE: {date.strftime(DATE_FORMAT)}",
218
+ SHOW_W_RULE_ATTR: True,
219
+ }
220
+ )
221
+ if self.should_hold_messages:
222
+ self.on_hold.append(date_record)
223
+ else:
224
+ logger_base_t.handle(self, date_record)
225
+
226
+ # When.
227
+ if now - self.last_message_now > LONG_ENOUGH:
228
+ record.when_or_elapsed = now.strftime(TIME_FORMAT)
229
+ else:
230
+ record.when_or_elapsed = (
231
+ f"{ELAPSED_TIME_SEPARATOR}{elapsed_time:.<{TIME_LENGTH_m_1}}"
232
+ )
233
+ self.last_message_now = now
234
+
235
+ # Where.
236
+ # Memory usage is also stored if there are no handlers yet, just in case.
237
+ should_store_where = self.any_handler_stores_memory or not self.hasHandlers()
238
+ should_show_where = (record.levelno != lggg.INFO) and not hasattr(
239
+ record, HIDE_WHERE_ATTR
240
+ )
241
+ if should_store_where or should_show_where:
242
+ module = path_t(record.pathname)
243
+ for path in sstm.path:
244
+ if module.is_relative_to(path):
245
+ module = module.relative_to(path).with_suffix("")
246
+ module = str(module).replace(FOLDER_SEPARATOR, ".")
247
+ break
248
+ else:
249
+ module = record.module
250
+ where = f"{module}:{record.funcName}:{record.lineno}"
251
+ if should_show_where:
252
+ record.where = where
253
+ else:
254
+ where = None
255
+
256
+ # How.
257
+ record.level_first_letter = record.levelname[0]
258
+
259
+ # What.
260
+ if not isinstance(record.msg, str):
261
+ record.msg = str(record.msg)
262
+
263
+ if self.should_hold_messages:
264
+ self.on_hold.append(record)
265
+ else:
266
+ logger_base_t.handle(self, record)
267
+
268
+ if (self.exit_on_critical and (record.levelno is lggg.CRITICAL)) or (
269
+ self.exit_on_error and (record.levelno is lggg.ERROR)
270
+ ):
271
+ # Also works if self.exit_on_error and record.levelno is lggg.CRITICAL since
272
+ # __post_init__ set self.exit_on_critical if self.exit_on_error.
273
+ sstm.exit(1)
274
+
275
+ self.events[record.levelno] += 1
276
+
277
+ if should_store_where:
278
+ self.memory_usages.append((where, CurrentMemoryUsage()))
279
+
280
+ def Log(
281
+ self,
282
+ message: str,
283
+ /,
284
+ *,
285
+ level: int | str = lggg.ERROR,
286
+ actual: h.Any = NOT_PASSED,
287
+ expected: h.Any | None = None,
288
+ expected_is_choices: bool = False,
289
+ expected_op: expected_op_h = "=",
290
+ with_final_dot: bool = True,
291
+ ) -> None:
292
+ """"""
293
+ if isinstance(level, str):
294
+ level = lggg.getLevelNamesMapping()[level.upper()]
295
+ message = MessageWithActualExpected(
296
+ message,
297
+ actual=actual,
298
+ expected=expected,
299
+ expected_is_choices=expected_is_choices,
300
+ expected_op=expected_op,
301
+ with_final_dot=with_final_dot,
302
+ )
303
+ self.log(level, message)
304
+
305
+ def LogException(
306
+ self,
307
+ exception: Exception,
308
+ /,
309
+ *,
310
+ level: int | str = lggg.ERROR,
311
+ should_remove_caller: bool = False,
312
+ ) -> None:
313
+ """"""
314
+ if isinstance(level, str):
315
+ level = lggg.getLevelNamesMapping()[level.upper()]
316
+ lines = tcbk.format_exception(exception)
317
+ if should_remove_caller:
318
+ message = "\n".join(lines[:1] + lines[2:])
319
+ else:
320
+ # TODO: Explain:
321
+ # - Why it's not: "\n".join(lines)?
322
+ # - Why adding exception name here and not when removing caller?
323
+ formatted = "".join(lines)
324
+ message = f"{type(exception).__name__}:\n{formatted}"
325
+ self.log(level, message)
326
+
327
+ def ShowMessage(self, message: str, /, *, indented: bool = False) -> None:
328
+ """
329
+ See documentation of
330
+ logger_36.catalog.handler.generic.generic_handler_t.ShowMessage.
331
+ """
332
+ for handler in self.handlers:
333
+ ShowMessage = getattr(handler, "ShowMessage", None)
334
+ if ShowMessage is not None:
335
+ ShowMessage(message, indented=indented)
336
+
337
+ def DisplayRule(self, /, *, text: str | None = None, color: str = "white") -> None:
338
+ """"""
339
+ for handler in self.handlers:
340
+ DisplayRule = getattr(handler, "DisplayRule", None)
341
+ if DisplayRule is not None:
342
+ DisplayRule(text=text, color=color)
343
+
344
+ def AddContextLevel(self, new_level: str, /) -> None:
345
+ """"""
346
+ self.context_levels.append(new_level)
347
+
348
+ def AddedContextLevel(self, new_level: str, /) -> h.Self:
349
+ """
350
+ Meant to be used as:
351
+ with self.AddedContextLevel("new level"):
352
+ ...
353
+ """
354
+ self.AddContextLevel(new_level)
355
+ return self
356
+
357
+ def StageIssue(
358
+ self,
359
+ message: str,
360
+ /,
361
+ *,
362
+ level: int = lggg.ERROR,
363
+ actual: h.Any = NOT_PASSED,
364
+ expected: h.Any | None = None,
365
+ expected_is_choices: bool = False,
366
+ expected_op: expected_op_h = "=",
367
+ with_final_dot: bool = False,
368
+ ) -> None:
369
+ """"""
370
+ context = ISSUE_CONTEXT_SEPARATOR.join(self.context_levels)
371
+ issue = NewIssue(
372
+ context,
373
+ ISSUE_CONTEXT_END,
374
+ message,
375
+ level=level,
376
+ actual=actual,
377
+ expected=expected,
378
+ expected_is_choices=expected_is_choices,
379
+ expected_op=expected_op,
380
+ with_final_dot=with_final_dot,
381
+ )
382
+ self.staged_issues.append(issue)
383
+
384
+ @property
385
+ def has_staged_issues(self) -> bool:
386
+ """"""
387
+ return self.staged_issues.__len__() > 0
388
+
389
+ def CommitIssues(
390
+ self,
391
+ /,
392
+ *,
393
+ order: order_h = "when",
394
+ unified: bool = False,
395
+ ) -> None:
396
+ """
397
+ Note that issues after an issue with a level triggering process exit will not be
398
+ logged.
399
+ """
400
+ if not self.has_staged_issues:
401
+ return
402
+
403
+ if order not in ORDER:
404
+ raise ValueError(
405
+ MessageWithActualExpected(
406
+ "Invalid commit order",
407
+ actual=order,
408
+ expected=f"One of {str(ORDER)[1:-1]}",
409
+ )
410
+ )
411
+
412
+ if order == "when":
413
+ issues = self.staged_issues
414
+ else: # order == "context"
415
+ issues = sorted(self.staged_issues, key=lambda _elm: _elm.context)
416
+ """
417
+ Format issues as an exception:
418
+ try:
419
+ raise ValueError("\n" + "\n".join(issues))
420
+ except ValueError as exception:
421
+ lines = ["Traceback (most recent call last):"] + tcbk.format_stack()[:-1]
422
+ lines[-1] = lines[-1][:-1]
423
+ lines.extend(tcbk.format_exception_only(exception))
424
+ formatted = "\n".join(lines)
425
+ """
426
+
427
+ hide_where = {HIDE_WHERE_ATTR: None}
428
+ if unified:
429
+ level, _ = issues[0].split(ISSUE_LEVEL_SEPARATOR, maxsplit=1)
430
+ wo_level = []
431
+ for issue in issues:
432
+ _, issue = issue.split(ISSUE_LEVEL_SEPARATOR, maxsplit=1)
433
+ wo_level.append(issue)
434
+ self.log(int(level), "\n".join(wo_level), stacklevel=2, extra=hide_where)
435
+ else:
436
+ for issue in issues:
437
+ level, issue = issue.split(ISSUE_LEVEL_SEPARATOR, maxsplit=1)
438
+ self.log(int(level), issue, stacklevel=2, extra=hide_where)
439
+ self.staged_issues.clear()
440
+
441
+ def __enter__(self) -> None:
442
+ """"""
443
+ pass
444
+
445
+ def __exit__(
446
+ self,
447
+ exc_type: Exception | None,
448
+ exc_value: str | None,
449
+ traceback: traceback_t | None,
450
+ /,
451
+ ) -> bool:
452
+ """"""
453
+ _ = self.context_levels.pop()
454
+ return False
455
+
456
+
457
+ def _HandleForWarnings(interceptor: logger_base_t, /) -> logger_handle_h:
458
+ """"""
459
+
460
+ def handle_p(_: logger_base_t, record: lggg.LogRecord, /) -> None:
461
+ pieces = WARNING_TYPE_COMPILED_PATTERN.match(record.msg)
462
+ if pieces is None:
463
+ # The warning message does not follow the default format.
464
+ interceptor.handle(record)
465
+ return
466
+
467
+ GetPiece = pieces.group
468
+ path = GetPiece(1)
469
+ line = GetPiece(2)
470
+ kind = GetPiece(3)
471
+ message = GetPiece(4).strip()
472
+
473
+ duplicate = lggg.makeLogRecord(record.__dict__)
474
+ duplicate.msg = f"{kind}: {message}"
475
+ duplicate.pathname = path
476
+ duplicate.module = path_t(path).stem
477
+ duplicate.funcName = "?"
478
+ duplicate.lineno = line
479
+
480
+ interceptor.handle(duplicate)
481
+
482
+ return handle_p
483
+
484
+
485
+ def _HandleForInterceptions(
486
+ intercepted: logger_base_t, interceptor: logger_base_t, /
487
+ ) -> logger_handle_h:
488
+ """"""
489
+
490
+ def handle_p(_: logger_base_t, record: lggg.LogRecord, /) -> None:
491
+ duplicate = lggg.makeLogRecord(record.__dict__)
492
+ duplicate.msg = f"{record.msg} :{intercepted.name}:"
493
+ interceptor.handle(duplicate)
494
+
495
+ return handle_p
496
+
497
+
498
+ """
499
+ COPYRIGHT NOTICE
500
+
501
+ This software is governed by the CeCILL license under French law and
502
+ abiding by the rules of distribution of free software. You can use,
503
+ modify and/ or redistribute the software under the terms of the CeCILL
504
+ license as circulated by CEA, CNRS and INRIA at the following URL
505
+ "http://www.cecill.info".
506
+
507
+ As a counterpart to the access to the source code and rights to copy,
508
+ modify and redistribute granted by the license, users are provided only
509
+ with a limited warranty and the software's author, the holder of the
510
+ economic rights, and the successive licensors have only limited
511
+ liability.
512
+
513
+ In this respect, the user's attention is drawn to the risks associated
514
+ with loading, using, modifying and/or developing or reproducing the
515
+ software by the user in light of its specific status of free software,
516
+ that may mean that it is complicated to manipulate, and that also
517
+ therefore means that it is reserved for developers and experienced
518
+ professionals having in-depth computer knowledge. Users are therefore
519
+ encouraged to load and test the software's suitability as regards their
520
+ requirements in conditions enabling the security of their systems and/or
521
+ data to be ensured and, more generally, to use and operate it in the
522
+ same conditions as regards security.
523
+
524
+ The fact that you are presently reading this means that you have had
525
+ knowledge of the CeCILL license and that you accept its terms.
526
+
527
+ SEE LICENCE NOTICE: file README-LICENCE-utf8.txt at project source root.
528
+
529
+ This software is being developed by Eric Debreuve, a CNRS employee and
530
+ member of team Morpheme.
531
+ Team Morpheme is a joint team between Inria, CNRS, and UniCA.
532
+ It is hosted by the Centre Inria d'Université Côte d'Azur, Laboratory
533
+ I3S, and Laboratory iBV.
534
+
535
+ CNRS: https://www.cnrs.fr/index.php/en
536
+ Inria: https://www.inria.fr/en/
537
+ UniCA: https://univ-cotedazur.eu/
538
+ Centre Inria d'Université Côte d'Azur: https://www.inria.fr/en/centre/sophia/
539
+ I3S: https://www.i3s.unice.fr/en/
540
+ iBV: http://ibv.unice.fr/
541
+ Team Morpheme: https://team.inria.fr/morpheme/
542
+ """
@@ -0,0 +1,78 @@
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
+ import dataclasses as d
8
+ import typing as h
9
+
10
+ from logger_36.type.logger import logger_t
11
+
12
+
13
+ @d.dataclass(slots=True, repr=False, eq=False)
14
+ class loggers_t(dict[h.Hashable, logger_t]):
15
+ active: logger_t | None = d.field(init=False, default=None)
16
+
17
+ def AddNew(self, uid: h.Hashable, /) -> None:
18
+ """"""
19
+ self.Add(uid, logger_t())
20
+
21
+ def Add(self, uid: h.Hashable, logger: logger_t, /) -> None:
22
+ """"""
23
+ if uid in self:
24
+ raise NameError(f"Logger with name/identity {uid} already exists.")
25
+
26
+ self[uid] = logger
27
+ self.active = logger
28
+
29
+ def SetActive(self, uid: h.Hashable, /) -> None:
30
+ """"""
31
+ self.active = self[uid]
32
+
33
+
34
+ """
35
+ COPYRIGHT NOTICE
36
+
37
+ This software is governed by the CeCILL license under French law and
38
+ abiding by the rules of distribution of free software. You can use,
39
+ modify and/ or redistribute the software under the terms of the CeCILL
40
+ license as circulated by CEA, CNRS and INRIA at the following URL
41
+ "http://www.cecill.info".
42
+
43
+ As a counterpart to the access to the source code and rights to copy,
44
+ modify and redistribute granted by the license, users are provided only
45
+ with a limited warranty and the software's author, the holder of the
46
+ economic rights, and the successive licensors have only limited
47
+ liability.
48
+
49
+ In this respect, the user's attention is drawn to the risks associated
50
+ with loading, using, modifying and/or developing or reproducing the
51
+ software by the user in light of its specific status of free software,
52
+ that may mean that it is complicated to manipulate, and that also
53
+ therefore means that it is reserved for developers and experienced
54
+ professionals having in-depth computer knowledge. Users are therefore
55
+ encouraged to load and test the software's suitability as regards their
56
+ requirements in conditions enabling the security of their systems and/or
57
+ data to be ensured and, more generally, to use and operate it in the
58
+ same conditions as regards security.
59
+
60
+ The fact that you are presently reading this means that you have had
61
+ knowledge of the CeCILL license and that you accept its terms.
62
+
63
+ SEE LICENCE NOTICE: file README-LICENCE-utf8.txt at project source root.
64
+
65
+ This software is being developed by Eric Debreuve, a CNRS employee and
66
+ member of team Morpheme.
67
+ Team Morpheme is a joint team between Inria, CNRS, and UniCA.
68
+ It is hosted by the Centre Inria d'Université Côte d'Azur, Laboratory
69
+ I3S, and Laboratory iBV.
70
+
71
+ CNRS: https://www.cnrs.fr/index.php/en
72
+ Inria: https://www.inria.fr/en/
73
+ UniCA: https://univ-cotedazur.eu/
74
+ Centre Inria d'Université Côte d'Azur: https://www.inria.fr/en/centre/sophia/
75
+ I3S: https://www.i3s.unice.fr/en/
76
+ iBV: http://ibv.unice.fr/
77
+ Team Morpheme: https://team.inria.fr/morpheme/
78
+ """