omlish 0.0.0.dev424__py3-none-any.whl → 0.0.0.dev426__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.
Files changed (126) hide show
  1. omlish/__about__.py +3 -3
  2. omlish/c3.py +4 -1
  3. omlish/configs/processing/flattening.py +1 -1
  4. omlish/configs/processing/merging.py +8 -6
  5. omlish/dataclasses/impl/concerns/doc.py +1 -1
  6. omlish/dataclasses/impl/configs.py +2 -1
  7. omlish/diag/_pycharm/runhack.py +1 -1
  8. omlish/diag/procfs.py +2 -2
  9. omlish/formats/json/stream/lexing.py +69 -14
  10. omlish/formats/json/stream/parsing.py +1 -1
  11. omlish/formats/json/stream/utils.py +3 -0
  12. omlish/formats/json5/streams.py +22 -0
  13. omlish/formats/logfmt.py +8 -2
  14. omlish/funcs/genmachine.py +1 -1
  15. omlish/http/sse.py +1 -1
  16. omlish/inject/impl/injector.py +1 -1
  17. omlish/inject/impl/multis.py +2 -2
  18. omlish/inject/impl/providers.py +0 -4
  19. omlish/inject/impl/proxy.py +0 -2
  20. omlish/inject/scopes.py +0 -4
  21. omlish/io/buffers.py +1 -1
  22. omlish/lang/__init__.py +26 -14
  23. omlish/lang/asyncs.py +12 -0
  24. omlish/lang/{attrs.py → attrstorage.py} +15 -15
  25. omlish/lang/cached/property.py +2 -2
  26. omlish/lang/classes/simple.py +26 -4
  27. omlish/lang/collections.py +1 -1
  28. omlish/lang/functions.py +0 -11
  29. omlish/lang/iterables.py +2 -2
  30. omlish/lang/lazyglobals.py +27 -5
  31. omlish/lang/maysync.py +2 -2
  32. omlish/lifecycles/contextmanagers.py +1 -2
  33. omlish/lifecycles/controller.py +1 -2
  34. omlish/lite/asyncs.py +20 -0
  35. omlish/lite/attrops.py +332 -0
  36. omlish/lite/cached.py +1 -1
  37. omlish/lite/maybes.py +2 -0
  38. omlish/lite/strings.py +0 -7
  39. omlish/lite/timing.py +6 -3
  40. omlish/logs/all.py +25 -32
  41. omlish/logs/base.py +248 -0
  42. omlish/logs/callers.py +21 -15
  43. omlish/logs/infos.py +105 -0
  44. omlish/logs/levels.py +64 -0
  45. omlish/logs/modules.py +10 -0
  46. omlish/logs/protocols.py +31 -0
  47. omlish/logs/standard.py +12 -11
  48. omlish/logs/std/adapters.py +41 -0
  49. omlish/logs/std/configs.py +29 -0
  50. omlish/logs/{filters.py → std/filters.py} +1 -1
  51. omlish/logs/{handlers.py → std/handlers.py} +1 -1
  52. omlish/logs/{json.py → std/json.py} +2 -2
  53. omlish/logs/{proxy.py → std/proxy.py} +3 -3
  54. omlish/logs/std/records.py +286 -0
  55. omlish/logs/times.py +86 -0
  56. omlish/logs/typed/bindings.py +24 -0
  57. omlish/logs/utils.py +60 -4
  58. omlish/logs/warnings.py +8 -0
  59. omlish/manifests/loading.py +1 -1
  60. omlish/os/atomics.py +1 -1
  61. omlish/os/journald.py +3 -3
  62. omlish/reflect/types.py +22 -0
  63. omlish/testing/pytest/plugins/skips.py +0 -4
  64. {omlish-0.0.0.dev424.dist-info → omlish-0.0.0.dev426.dist-info}/METADATA +2 -2
  65. {omlish-0.0.0.dev424.dist-info → omlish-0.0.0.dev426.dist-info}/RECORD +72 -114
  66. omlish/defs.py +0 -216
  67. omlish/dispatch/_dispatch2.py +0 -69
  68. omlish/dispatch/_dispatch3.py +0 -108
  69. omlish/dynamic.py +0 -219
  70. omlish/formats/json/Json.g4 +0 -77
  71. omlish/formats/json/_antlr/JsonLexer.py +0 -109
  72. omlish/formats/json/_antlr/JsonListener.py +0 -61
  73. omlish/formats/json/_antlr/JsonParser.py +0 -457
  74. omlish/formats/json/_antlr/JsonVisitor.py +0 -42
  75. omlish/io/trampoline.py +0 -289
  76. omlish/lite/logs.py +0 -4
  77. omlish/lite/reprs.py +0 -85
  78. omlish/logs/abc.py +0 -319
  79. omlish/logs/color.py +0 -27
  80. omlish/logs/configs.py +0 -29
  81. omlish/logs/protocol.py +0 -218
  82. omlish/logs/timing.py +0 -58
  83. omlish/specs/irc/__init__.py +0 -0
  84. omlish/specs/irc/messages/__init__.py +0 -0
  85. omlish/specs/irc/messages/base.py +0 -49
  86. omlish/specs/irc/messages/formats.py +0 -92
  87. omlish/specs/irc/messages/messages.py +0 -774
  88. omlish/specs/irc/messages/parsing.py +0 -98
  89. omlish/specs/irc/numerics/__init__.py +0 -0
  90. omlish/specs/irc/numerics/formats.py +0 -97
  91. omlish/specs/irc/numerics/numerics.py +0 -865
  92. omlish/specs/irc/numerics/types.py +0 -59
  93. omlish/specs/irc/protocol/LICENSE +0 -11
  94. omlish/specs/irc/protocol/__init__.py +0 -61
  95. omlish/specs/irc/protocol/consts.py +0 -6
  96. omlish/specs/irc/protocol/errors.py +0 -30
  97. omlish/specs/irc/protocol/message.py +0 -21
  98. omlish/specs/irc/protocol/nuh.py +0 -55
  99. omlish/specs/irc/protocol/parsing.py +0 -158
  100. omlish/specs/irc/protocol/rendering.py +0 -153
  101. omlish/specs/irc/protocol/tags.py +0 -102
  102. omlish/specs/irc/protocol/utils.py +0 -30
  103. omlish/specs/proto/Protobuf3.g4 +0 -396
  104. omlish/specs/proto/__init__.py +0 -0
  105. omlish/specs/proto/_antlr/Protobuf3Lexer.py +0 -340
  106. omlish/specs/proto/_antlr/Protobuf3Listener.py +0 -448
  107. omlish/specs/proto/_antlr/Protobuf3Parser.py +0 -3909
  108. omlish/specs/proto/_antlr/Protobuf3Visitor.py +0 -257
  109. omlish/specs/proto/_antlr/__init__.py +0 -0
  110. omlish/specs/proto/nodes.py +0 -54
  111. omlish/specs/proto/parsing.py +0 -97
  112. omlish/sql/parsing/Minisql.g4 +0 -292
  113. omlish/sql/parsing/__init__.py +0 -0
  114. omlish/sql/parsing/_antlr/MinisqlLexer.py +0 -322
  115. omlish/sql/parsing/_antlr/MinisqlListener.py +0 -511
  116. omlish/sql/parsing/_antlr/MinisqlParser.py +0 -3763
  117. omlish/sql/parsing/_antlr/MinisqlVisitor.py +0 -292
  118. omlish/sql/parsing/_antlr/__init__.py +0 -0
  119. omlish/sql/parsing/parsing.py +0 -119
  120. /omlish/{.manifests.json → .omlish-manifests.json} +0 -0
  121. /omlish/{formats/json/_antlr → logs/std}/__init__.py +0 -0
  122. /omlish/logs/{noisy.py → std/noisy.py} +0 -0
  123. {omlish-0.0.0.dev424.dist-info → omlish-0.0.0.dev426.dist-info}/WHEEL +0 -0
  124. {omlish-0.0.0.dev424.dist-info → omlish-0.0.0.dev426.dist-info}/entry_points.txt +0 -0
  125. {omlish-0.0.0.dev424.dist-info → omlish-0.0.0.dev426.dist-info}/licenses/LICENSE +0 -0
  126. {omlish-0.0.0.dev424.dist-info → omlish-0.0.0.dev426.dist-info}/top_level.txt +0 -0
omlish/logs/all.py CHANGED
@@ -5,64 +5,57 @@ from .. import lang as _lang
5
5
  with _lang.auto_proxy_init(globals()):
6
6
  ##
7
7
 
8
- from .callers import ( # noqa
9
- LoggingCaller,
8
+ from .std.filters import ( # noqa
9
+ TidLoggingFilter,
10
10
  )
11
11
 
12
- from .color import ( # noqa
13
- ColorLogFormatter,
12
+ from .std.handlers import ( # noqa
13
+ ListLoggingHandler,
14
14
  )
15
15
 
16
- from .filters import ( # noqa
17
- TidLogFilter,
16
+ from .std.json import ( # noqa
17
+ JsonLoggingFormatter,
18
18
  )
19
19
 
20
- from .handlers import ( # noqa
21
- ListHandler,
20
+ from .std.noisy import ( # noqa
21
+ silence_noisy_loggers,
22
22
  )
23
23
 
24
- from .json import ( # noqa
25
- JsonLogFormatter,
24
+ from .std.proxy import ( # noqa
25
+ ProxyLoggingFilterer,
26
+ ProxyLoggingHandler,
27
+ )
28
+
29
+ from .callers import ( # noqa
30
+ LoggingCaller,
26
31
  )
27
32
 
28
33
  from .levels import ( # noqa
29
34
  LogLevel,
30
- )
31
35
 
32
- from .noisy import ( # noqa
33
- silence_noisy_loggers,
36
+ NamedLogLevel,
34
37
  )
35
38
 
36
- from .protocol import ( # noqa
37
- AnyLogging,
38
- Logging,
39
- AsyncLogging,
40
-
41
- AnyAbstractLogging,
42
- AbstractLogging,
43
- AbstractAsyncLogging,
44
-
45
- AnyNopLogging,
46
- NopLogging,
47
- NopAsyncLogging,
48
-
49
- StdlibLogging,
39
+ from .modules import ( # noqa
40
+ get_module_logger,
50
41
  )
51
42
 
52
- from .proxy import ( # noqa
53
- ProxyLogFilterer,
54
- ProxyLogHandler,
43
+ from .protocols import ( # noqa
44
+ LoggerLike,
55
45
  )
56
46
 
57
47
  from .standard import ( # noqa
58
48
  STANDARD_LOG_FORMAT_PARTS,
59
- StandardLogFormatter,
49
+ StandardLoggingFormatter,
60
50
 
61
- StandardConfiguredLogHandler,
51
+ StandardConfiguredLoggingHandler,
62
52
 
63
53
  configure_standard_logging,
64
54
  )
65
55
 
66
56
  from .utils import ( # noqa
57
+ LogTimingContext,
58
+ log_timing_context,
59
+
67
60
  error_logging,
68
61
  )
omlish/logs/base.py ADDED
@@ -0,0 +1,248 @@
1
+ # ruff: noqa: UP006 UP007 UP045 UP046
2
+ # @omlish-lite
3
+ import abc
4
+ import sys
5
+ import time
6
+ import types
7
+ import typing as ta
8
+
9
+ from ..lite.abstract import Abstract
10
+ from .callers import LoggingCaller
11
+ from .infos import LoggingAsyncioTaskInfo
12
+ from .infos import LoggingMultiprocessingInfo
13
+ from .infos import LoggingProcessInfo
14
+ from .infos import LoggingSourceFileInfo
15
+ from .infos import LoggingThreadInfo
16
+ from .levels import NamedLogLevel
17
+ from .times import LoggingTimeFields
18
+
19
+
20
+ T = ta.TypeVar('T')
21
+
22
+
23
+ LogLevel = int # ta.TypeAlias
24
+
25
+ LoggingExcInfoTuple = ta.Tuple[ta.Type[BaseException], BaseException, ta.Optional[types.TracebackType]] # ta.TypeAlias
26
+ LoggingExcInfo = ta.Union[BaseException, LoggingExcInfoTuple] # ta.TypeAlias
27
+ LoggingExcInfoArg = ta.Union[LoggingExcInfo, bool, None] # ta.TypeAlias
28
+
29
+
30
+ ##
31
+
32
+
33
+ @ta.final
34
+ class LoggingContext:
35
+ level: NamedLogLevel
36
+
37
+ time_ns: int
38
+
39
+ exc_info: ta.Optional[LoggingExcInfo] = None
40
+ exc_info_tuple: ta.Optional[LoggingExcInfoTuple] = None
41
+
42
+ @ta.final
43
+ class NOT_SET: # noqa
44
+ def __new__(cls, *args, **kwargs): # noqa
45
+ raise TypeError
46
+
47
+ #
48
+
49
+ def __init__(
50
+ self,
51
+ level: LogLevel,
52
+ *,
53
+ time_ns: ta.Optional[int] = None,
54
+
55
+ exc_info: LoggingExcInfoArg = False,
56
+
57
+ caller: ta.Union[LoggingCaller, ta.Type[NOT_SET], None] = NOT_SET,
58
+ stack_offset: int = 0,
59
+ stack_info: bool = False,
60
+ ) -> None:
61
+ self.level = level if level.__class__ is NamedLogLevel else NamedLogLevel(level) # type: ignore[assignment]
62
+
63
+ #
64
+
65
+ if time_ns is None:
66
+ time_ns = time.time_ns()
67
+ self.time_ns: int = time_ns
68
+
69
+ #
70
+
71
+ if exc_info is True:
72
+ sys_exc_info = sys.exc_info()
73
+ if sys_exc_info[0] is not None:
74
+ exc_info = sys_exc_info
75
+ else:
76
+ exc_info = None
77
+ elif exc_info is False:
78
+ exc_info = None
79
+
80
+ if exc_info is not None:
81
+ self.exc_info = exc_info
82
+ if isinstance(exc_info, BaseException):
83
+ self.exc_info_tuple = (type(exc_info), exc_info, exc_info.__traceback__)
84
+ else:
85
+ self.exc_info_tuple = exc_info
86
+
87
+ #
88
+
89
+ if caller is not LoggingContext.NOT_SET:
90
+ self._caller = caller # type: ignore[assignment]
91
+ else:
92
+ self._stack_offset = stack_offset
93
+ self._stack_info = stack_info
94
+
95
+ #
96
+
97
+ self.thread = LoggingThreadInfo.build()
98
+ self.process = LoggingProcessInfo.build()
99
+ self.multiprocessing = LoggingMultiprocessingInfo.build()
100
+ self.asyncio_task = LoggingAsyncioTaskInfo.build()
101
+
102
+ #
103
+
104
+ _times: LoggingTimeFields
105
+
106
+ @property
107
+ def times(self) -> LoggingTimeFields:
108
+ try:
109
+ return self._times
110
+ except AttributeError:
111
+ pass
112
+
113
+ times = self._times = LoggingTimeFields.build(self.time_ns)
114
+ return times
115
+
116
+ #
117
+
118
+ def inc_stack_offset(self, ofs: int = 1) -> 'LoggingContext':
119
+ if hasattr(self, '_stack_offset'):
120
+ self._stack_offset += ofs
121
+ return self
122
+
123
+ _caller: ta.Optional[LoggingCaller]
124
+
125
+ def build_caller(self) -> ta.Optional[LoggingCaller]:
126
+ """Must be cooperatively called only from the exact configured _stack_offset."""
127
+
128
+ try:
129
+ return self._caller
130
+ except AttributeError:
131
+ pass
132
+
133
+ caller = self._caller = LoggingCaller.find(
134
+ self._stack_offset + 1,
135
+ stack_info=self._stack_info,
136
+ )
137
+ return caller
138
+
139
+ def caller(self) -> ta.Optional[LoggingCaller]:
140
+ try:
141
+ return self._caller
142
+ except AttributeError:
143
+ return None
144
+
145
+ _source_file: ta.Optional[LoggingSourceFileInfo]
146
+
147
+ def source_file(self) -> ta.Optional[LoggingSourceFileInfo]:
148
+ try:
149
+ return self._source_file
150
+ except AttributeError:
151
+ pass
152
+
153
+ if (caller := self.caller()) is None:
154
+ return None
155
+
156
+ src_file = self._source_file = LoggingSourceFileInfo.build(caller.file_path)
157
+ return src_file
158
+
159
+
160
+ ##
161
+
162
+
163
+ class AnyLogger(Abstract, ta.Generic[T]):
164
+ def is_enabled_for(self, level: LogLevel) -> bool:
165
+ return self.get_effective_level() >= level
166
+
167
+ @abc.abstractmethod
168
+ def get_effective_level(self) -> LogLevel:
169
+ raise NotImplementedError
170
+
171
+ #
172
+
173
+ @ta.final
174
+ def isEnabledFor(self, level: LogLevel) -> bool: # noqa
175
+ return self.is_enabled_for(level)
176
+
177
+ @ta.final
178
+ def getEffectiveLevel(self) -> LogLevel: # noqa
179
+ return self.get_effective_level()
180
+
181
+ #
182
+
183
+ @ta.final
184
+ def debug(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> T:
185
+ return self._log(LoggingContext(NamedLogLevel.DEBUG, stack_offset=1), msg, *args, **kwargs)
186
+
187
+ @ta.final
188
+ def info(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> T:
189
+ return self._log(LoggingContext(NamedLogLevel.INFO, stack_offset=1), msg, *args, **kwargs)
190
+
191
+ @ta.final
192
+ def warning(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> T:
193
+ return self._log(LoggingContext(NamedLogLevel.WARNING, stack_offset=1), msg, *args, **kwargs)
194
+
195
+ @ta.final
196
+ def error(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> T:
197
+ return self._log(LoggingContext(NamedLogLevel.ERROR, stack_offset=1), msg, *args, **kwargs)
198
+
199
+ @ta.final
200
+ def exception(self, msg: str, *args: ta.Any, exc_info: LoggingExcInfoArg = True, **kwargs: ta.Any) -> T:
201
+ return self._log(LoggingContext(NamedLogLevel.ERROR, exc_info=exc_info, stack_offset=1), msg, *args, **kwargs)
202
+
203
+ @ta.final
204
+ def critical(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> T:
205
+ return self._log(LoggingContext(NamedLogLevel.CRITICAL, stack_offset=1), msg, *args, **kwargs)
206
+
207
+ @ta.final
208
+ def log(self, level: LogLevel, msg: str, *args: ta.Any, **kwargs: ta.Any) -> T:
209
+ return self._log(LoggingContext(level, stack_offset=1), msg, *args, **kwargs)
210
+
211
+ #
212
+
213
+ @abc.abstractmethod
214
+ def _log(self, ctx: LoggingContext, msg: str, *args: ta.Any, **kwargs: ta.Any) -> T:
215
+ raise NotImplementedError
216
+
217
+
218
+ class Logger(AnyLogger[None], Abstract):
219
+ @abc.abstractmethod
220
+ def _log(self, ctx: LoggingContext, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None:
221
+ raise NotImplementedError
222
+
223
+
224
+ class AsyncLogger(AnyLogger[ta.Awaitable[None]], Abstract):
225
+ @abc.abstractmethod
226
+ def _log(self, ctx: LoggingContext, msg: str, *args: ta.Any, **kwargs: ta.Any) -> ta.Awaitable[None]:
227
+ raise NotImplementedError
228
+
229
+
230
+ ##
231
+
232
+
233
+ class AnyNopLogger(AnyLogger[T], Abstract):
234
+ @ta.final
235
+ def get_effective_level(self) -> LogLevel:
236
+ return 999
237
+
238
+
239
+ @ta.final
240
+ class NopLogger(AnyNopLogger[None], Logger):
241
+ def _log(self, ctx: LoggingContext, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None:
242
+ pass
243
+
244
+
245
+ @ta.final
246
+ class AsyncNopLogger(AnyNopLogger[ta.Awaitable[None]], AsyncLogger):
247
+ async def _log(self, ctx: LoggingContext, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None:
248
+ pass
omlish/logs/callers.py CHANGED
@@ -12,32 +12,36 @@ import typing as ta
12
12
 
13
13
 
14
14
  class LoggingCaller(ta.NamedTuple):
15
- filename: str
16
- lineno: int
17
- func: str
18
- sinfo: ta.Optional[str]
15
+ file_path: str
16
+ line_no: int
17
+ name: str
18
+ stack_info: ta.Optional[str]
19
19
 
20
20
  @classmethod
21
21
  def is_internal_frame(cls, frame: types.FrameType) -> bool:
22
- filename = os.path.normcase(frame.f_code.co_filename)
22
+ file_path = os.path.normcase(frame.f_code.co_filename)
23
23
 
24
+ # Yes, really.
25
+ # https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L204
24
26
  # https://github.com/python/cpython/commit/5ca6d7469be53960843df39bb900e9c3359f127f
25
- if 'importlib' in filename and '_bootstrap' in filename:
27
+ if 'importlib' in file_path and '_bootstrap' in file_path:
26
28
  return True
27
29
 
28
30
  return False
29
31
 
30
32
  @classmethod
31
- def find_frame(cls, ofs: int = 0) -> types.FrameType:
33
+ def find_frame(cls, ofs: int = 0) -> ta.Optional[types.FrameType]:
32
34
  f: ta.Optional[types.FrameType] = sys._getframe(2 + ofs) # noqa
33
35
 
34
- while f is not None and hasattr(f, 'f_code'):
35
- if f.f_code.co_filename != __file__:
36
+ while f is not None:
37
+ # NOTE: We don't check __file__ like stdlib since we may be running amalgamated - we rely on careful, manual
38
+ # stack_offset management.
39
+ if hasattr(f, 'f_code'):
36
40
  return f
37
41
 
38
42
  f = f.f_back
39
43
 
40
- raise RuntimeError
44
+ return None
41
45
 
42
46
  @classmethod
43
47
  def find(
@@ -45,14 +49,16 @@ class LoggingCaller(ta.NamedTuple):
45
49
  ofs: int = 0,
46
50
  *,
47
51
  stack_info: bool = False,
48
- ) -> 'LoggingCaller':
49
- f = cls.find_frame(ofs + 1)
50
- # TODO: ('(unknown file)', 0, '(unknown function)', None) ?
52
+ ) -> ta.Optional['LoggingCaller']:
53
+ if (f := cls.find_frame(ofs + 1)) is None:
54
+ return None
51
55
 
56
+ # https://github.com/python/cpython/blob/08e9794517063c8cd92c48714071b1d3c60b71bd/Lib/logging/__init__.py#L1616-L1623 # noqa
52
57
  sinfo = None
53
58
  if stack_info:
54
59
  sio = io.StringIO()
55
- sio.write('Stack (most recent call last):\n')
60
+ # In stdlib, but done elsewhere here:
61
+ # sio.write('Stack (most recent call last):\n')
56
62
  traceback.print_stack(f, file=sio)
57
63
  sinfo = sio.getvalue()
58
64
  sio.close()
@@ -61,7 +67,7 @@ class LoggingCaller(ta.NamedTuple):
61
67
 
62
68
  return cls(
63
69
  f.f_code.co_filename,
64
- f.f_lineno,
70
+ f.f_lineno or 0,
65
71
  f.f_code.co_name,
66
72
  sinfo,
67
73
  )
omlish/logs/infos.py ADDED
@@ -0,0 +1,105 @@
1
+ # ruff: noqa: UP045
2
+ # @omlish-lite
3
+ import os.path
4
+ import sys
5
+ import threading
6
+ import typing as ta
7
+
8
+
9
+ ##
10
+
11
+
12
+ @ta.final
13
+ class LoggingSourceFileInfo(ta.NamedTuple):
14
+ file_name: str
15
+ module: str
16
+
17
+ @classmethod
18
+ def build(cls, file_path: ta.Optional[str]) -> ta.Optional['LoggingSourceFileInfo']:
19
+ if file_path is None:
20
+ return None
21
+
22
+ # https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L331-L336 # noqa
23
+ try:
24
+ file_name = os.path.basename(file_path)
25
+ module = os.path.splitext(file_name)[0]
26
+ except (TypeError, ValueError, AttributeError):
27
+ return None
28
+
29
+ return cls(
30
+ file_name,
31
+ module,
32
+ )
33
+
34
+
35
+ ##
36
+
37
+
38
+ @ta.final
39
+ class LoggingThreadInfo(ta.NamedTuple):
40
+ ident: int
41
+ native_id: ta.Optional[int]
42
+ name: str
43
+
44
+ @classmethod
45
+ def build(cls) -> 'LoggingThreadInfo':
46
+ return cls(
47
+ threading.get_ident(),
48
+ threading.get_native_id() if hasattr(threading, 'get_native_id') else None,
49
+ threading.current_thread().name,
50
+ )
51
+
52
+
53
+ ##
54
+
55
+
56
+ @ta.final
57
+ class LoggingProcessInfo(ta.NamedTuple):
58
+ pid: int
59
+
60
+ @classmethod
61
+ def build(cls) -> 'LoggingProcessInfo':
62
+ return cls(
63
+ os.getpid(),
64
+ )
65
+
66
+
67
+ ##
68
+
69
+
70
+ @ta.final
71
+ class LoggingMultiprocessingInfo(ta.NamedTuple):
72
+ process_name: str
73
+
74
+ @classmethod
75
+ def build(cls) -> ta.Optional['LoggingMultiprocessingInfo']:
76
+ # https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L355-L364 # noqa
77
+ if (mp := sys.modules.get('multiprocessing')) is None:
78
+ return None
79
+
80
+ return cls(
81
+ mp.current_process().name,
82
+ )
83
+
84
+
85
+ ##
86
+
87
+
88
+ @ta.final
89
+ class LoggingAsyncioTaskInfo(ta.NamedTuple):
90
+ name: str
91
+
92
+ @classmethod
93
+ def build(cls) -> ta.Optional['LoggingAsyncioTaskInfo']:
94
+ # https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L372-L377 # noqa
95
+ if (asyncio := sys.modules.get('asyncio')) is None:
96
+ return None
97
+
98
+ try:
99
+ task = asyncio.current_task()
100
+ except Exception: # noqa
101
+ return None
102
+
103
+ return cls(
104
+ task.get_name(), # Always non-None
105
+ )
omlish/logs/levels.py CHANGED
@@ -1,7 +1,71 @@
1
+ # ruff: noqa: UP006 UP045
1
2
  # @omlish-lite
3
+ import logging
4
+ import typing as ta
2
5
 
3
6
 
4
7
  LogLevel = int # ta.TypeAlias
5
8
 
6
9
 
7
10
  ##
11
+
12
+
13
+ @ta.final
14
+ class NamedLogLevel(int):
15
+ # logging.getLevelNamesMapping (or, as that is unavailable <3.11, logging._nameToLevel) includes the deprecated
16
+ # aliases.
17
+ _NAMES_BY_INT: ta.ClassVar[ta.Mapping[LogLevel, str]] = dict(sorted(logging._levelToName.items(), key=lambda t: -t[0])) # noqa
18
+
19
+ _INTS_BY_NAME: ta.ClassVar[ta.Mapping[str, LogLevel]] = {v: k for k, v in _NAMES_BY_INT.items()}
20
+
21
+ _NAME_INT_PAIRS: ta.ClassVar[ta.Sequence[ta.Tuple[str, LogLevel]]] = list(_INTS_BY_NAME.items())
22
+
23
+ #
24
+
25
+ @property
26
+ def exact_name(self) -> ta.Optional[str]:
27
+ return self._NAMES_BY_INT.get(self)
28
+
29
+ _effective_name: ta.Optional[str]
30
+
31
+ @property
32
+ def effective_name(self) -> ta.Optional[str]:
33
+ try:
34
+ return self._effective_name
35
+ except AttributeError:
36
+ pass
37
+
38
+ if (n := self.exact_name) is None:
39
+ for n, i in self._NAME_INT_PAIRS: # noqa
40
+ if self >= i:
41
+ break
42
+ else:
43
+ n = None
44
+
45
+ self._effective_name = n
46
+ return n
47
+
48
+ #
49
+
50
+ def __repr__(self) -> str:
51
+ return f'{self.__class__.__name__}({int(self)})'
52
+
53
+ def __str__(self) -> str:
54
+ return self.exact_name or f'{self.effective_name or "INVALID"}:{int(self)}'
55
+
56
+ #
57
+
58
+ CRITICAL: ta.ClassVar['NamedLogLevel']
59
+ ERROR: ta.ClassVar['NamedLogLevel']
60
+ WARNING: ta.ClassVar['NamedLogLevel']
61
+ INFO: ta.ClassVar['NamedLogLevel']
62
+ DEBUG: ta.ClassVar['NamedLogLevel']
63
+ NOTSET: ta.ClassVar['NamedLogLevel']
64
+
65
+
66
+ NamedLogLevel.CRITICAL = NamedLogLevel(logging.CRITICAL)
67
+ NamedLogLevel.ERROR = NamedLogLevel(logging.ERROR)
68
+ NamedLogLevel.WARNING = NamedLogLevel(logging.WARNING)
69
+ NamedLogLevel.INFO = NamedLogLevel(logging.INFO)
70
+ NamedLogLevel.DEBUG = NamedLogLevel(logging.DEBUG)
71
+ NamedLogLevel.NOTSET = NamedLogLevel(logging.NOTSET)
omlish/logs/modules.py ADDED
@@ -0,0 +1,10 @@
1
+ # @omlish-lite
2
+ import logging
3
+ import typing as ta
4
+
5
+
6
+ ##
7
+
8
+
9
+ def get_module_logger(mod_globals: ta.Mapping[str, ta.Any]) -> logging.Logger:
10
+ return logging.getLogger(mod_globals.get('__name__'))
@@ -0,0 +1,31 @@
1
+ # @omlish-lite
2
+ import typing as ta
3
+
4
+ from .levels import LogLevel
5
+
6
+
7
+ ##
8
+
9
+
10
+ class LoggerLike(ta.Protocol):
11
+ """Satisfied by both our Logger and stdlib logging.Logger."""
12
+
13
+ def isEnabledFor(self, level: LogLevel) -> bool: ... # noqa
14
+
15
+ def getEffectiveLevel(self) -> LogLevel: ... # noqa
16
+
17
+ #
18
+
19
+ def debug(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None: ... # noqa
20
+
21
+ def info(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None: ... # noqa
22
+
23
+ def warning(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None: ... # noqa
24
+
25
+ def error(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None: ... # noqa
26
+
27
+ def exception(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None: ... # noqa
28
+
29
+ def critical(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None: ... # noqa
30
+
31
+ def log(self, level: LogLevel, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None: ... # noqa