omlish 0.0.0.dev426__py3-none-any.whl → 0.0.0.dev428__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.
omlish/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev426'
2
- __revision__ = '94f5fd2fe7124da5d9b5af104cba12b1bbdb3286'
1
+ __version__ = '0.0.0.dev428'
2
+ __revision__ = '302f3506177c6fb44a3b8786e8125ee75d322b0e'
3
3
 
4
4
 
5
5
  #
@@ -4,12 +4,12 @@ import asyncio.base_subprocess
4
4
  import asyncio.subprocess
5
5
  import contextlib
6
6
  import functools
7
- import logging
8
7
  import subprocess
9
8
  import typing as ta
10
9
 
11
10
  from ...lite.check import check
12
11
  from ...lite.timeouts import TimeoutLike
12
+ from ...logs.protocols import LoggerLike
13
13
  from ...subprocesses.asyncs import AbstractAsyncSubprocesses
14
14
  from ...subprocesses.run import SubprocessRun
15
15
  from ...subprocesses.run import SubprocessRunOutput
@@ -28,7 +28,7 @@ class AsyncioProcessCommunicator:
28
28
  proc: asyncio.subprocess.Process,
29
29
  loop: ta.Optional[ta.Any] = None,
30
30
  *,
31
- log: ta.Optional[logging.Logger] = None,
31
+ log: ta.Optional[LoggerLike] = None,
32
32
  ) -> None:
33
33
  super().__init__()
34
34
 
@@ -39,6 +39,7 @@ import types
39
39
  import typing as ta
40
40
  import weakref
41
41
 
42
+ from ...logs.protocols import LoggerLike
42
43
  from .core import BlueletCoro
43
44
  from .core import BlueletExcInfo
44
45
  from .core import CoreBlueletEvent
@@ -84,7 +85,7 @@ class BlueletCoroException(Exception): # noqa
84
85
  def _bluelet_event_select(
85
86
  events: ta.Iterable[BlueletEvent],
86
87
  *,
87
- log: ta.Optional[logging.Logger] = None,
88
+ log: ta.Optional[LoggerLike] = None,
88
89
  ) -> ta.Set[WaitableBlueletEvent]:
89
90
  """
90
91
  Perform a select() over all the Events provided, returning the ones ready to be fired. Only WaitableEvents
@@ -182,7 +183,7 @@ class _BlueletRunner:
182
183
  self,
183
184
  root_coro: BlueletCoro,
184
185
  *,
185
- log: ta.Optional[logging.Logger] = None,
186
+ log: ta.Optional[LoggerLike] = None,
186
187
  ) -> None:
187
188
  super().__init__()
188
189
 
omlish/formats/dotenv.py CHANGED
@@ -27,7 +27,6 @@ import abc
27
27
  import codecs
28
28
  import contextlib
29
29
  import io
30
- import logging
31
30
  import os
32
31
  import pathlib
33
32
  import re
@@ -36,6 +35,7 @@ import tempfile
36
35
  import typing as ta
37
36
 
38
37
  from ..lite.abstract import Abstract
38
+ from ..logs.protocols import LoggerLike
39
39
 
40
40
 
41
41
  ##
@@ -312,7 +312,7 @@ def parse_dotenv_stream(stream: ta.IO[str]) -> ta.Iterator[DotenvBinding]:
312
312
 
313
313
  def _dotenv_with_warn_for_invalid_lines(
314
314
  mappings: ta.Iterator[DotenvBinding],
315
- log: ta.Optional[logging.Logger] = None,
315
+ log: ta.Optional[LoggerLike] = None,
316
316
  ) -> ta.Iterator[DotenvBinding]:
317
317
  for mapping in mappings:
318
318
  if mapping.error:
@@ -337,7 +337,7 @@ class Dotenv:
337
337
  interpolate: bool = True,
338
338
  override: bool = True,
339
339
  env: ta.Optional[ta.Mapping[str, str]] = None,
340
- log: ta.Optional[logging.Logger] = None,
340
+ log: ta.Optional[LoggerLike] = None,
341
341
  ) -> None:
342
342
  super().__init__()
343
343
 
@@ -415,7 +415,7 @@ def dotenv_get_key(
415
415
  key_to_get: str,
416
416
  *,
417
417
  encoding: ta.Optional[str] = 'utf-8',
418
- log: ta.Optional[logging.Logger] = None,
418
+ log: ta.Optional[LoggerLike] = None,
419
419
  ) -> ta.Optional[str]:
420
420
  """
421
421
  Get the value of a given key from the given .env.
@@ -461,7 +461,7 @@ def dotenv_set_key(
461
461
  quote_mode: str = 'always',
462
462
  export: bool = False,
463
463
  encoding: ta.Optional[str] = 'utf-8',
464
- log: ta.Optional[logging.Logger] = None,
464
+ log: ta.Optional[LoggerLike] = None,
465
465
  ) -> ta.Tuple[ta.Optional[bool], str, str]:
466
466
  """
467
467
  Adds or Updates a key/value to the given .env
@@ -511,7 +511,7 @@ def dotenv_unset_key(
511
511
  *,
512
512
  quote_mode: str = 'always',
513
513
  encoding: ta.Optional[str] = 'utf-8',
514
- log: ta.Optional[logging.Logger] = None,
514
+ log: ta.Optional[LoggerLike] = None,
515
515
  ) -> ta.Tuple[ta.Optional[bool], str]:
516
516
  """
517
517
  Removes a given key from the given `.env` file.
@@ -575,7 +575,7 @@ def dotenv_values(
575
575
  interpolate: bool = True,
576
576
  encoding: ta.Optional[str] = 'utf-8',
577
577
  env: ta.Optional[ta.Mapping[str, str]] = None,
578
- log: ta.Optional[logging.Logger] = None,
578
+ log: ta.Optional[LoggerLike] = None,
579
579
  ) -> ta.Dict[str, ta.Optional[str]]:
580
580
  """
581
581
  Parse a .env file and return its content as a dict.
omlish/http/handlers.py CHANGED
@@ -7,6 +7,7 @@ import logging
7
7
  import typing as ta
8
8
 
9
9
  from ..lite.abstract import Abstract
10
+ from ..logs.protocols import LoggerLike
10
11
  from ..sockets.addresses import SocketAddress
11
12
  from .parsing import HttpHeaders
12
13
 
@@ -70,7 +71,7 @@ class HttpHandler_(Abstract): # noqa
70
71
  @dc.dataclass(frozen=True)
71
72
  class LoggingHttpHandler(HttpHandler_):
72
73
  handler: HttpHandler
73
- log: logging.Logger
74
+ log: LoggerLike
74
75
  level: int = logging.DEBUG
75
76
 
76
77
  def __call__(self, req: HttpHandlerRequest) -> HttpHandlerResponse:
@@ -83,7 +84,7 @@ class LoggingHttpHandler(HttpHandler_):
83
84
  @dc.dataclass(frozen=True)
84
85
  class ExceptionLoggingHttpHandler(HttpHandler_):
85
86
  handler: HttpHandler
86
- log: logging.Logger
87
+ log: LoggerLike
87
88
  message: ta.Union[str, ta.Callable[[HttpHandlerRequest, BaseException], str]] = 'Error in http handler'
88
89
 
89
90
  def __call__(self, req: HttpHandlerRequest) -> HttpHandlerResponse:
omlish/logs/base.py CHANGED
@@ -1,229 +1,215 @@
1
1
  # ruff: noqa: UP006 UP007 UP045 UP046
2
2
  # @omlish-lite
3
3
  import abc
4
- import sys
5
- import time
6
- import types
7
4
  import typing as ta
8
5
 
9
6
  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
7
+ from .contexts import CaptureLoggingContext
8
+ from .contexts import CaptureLoggingContextImpl
9
+ from .contexts import LoggingExcInfoArg
10
+ from .levels import LogLevel
16
11
  from .levels import NamedLogLevel
17
- from .times import LoggingTimeFields
18
12
 
19
13
 
20
14
  T = ta.TypeVar('T')
21
15
 
22
16
 
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
17
+ LoggingMsgFn = ta.Callable[[], ta.Union[str, tuple]] # ta.TypeAlias
28
18
 
29
19
 
30
20
  ##
31
21
 
32
22
 
33
- @ta.final
34
- class LoggingContext:
35
- level: NamedLogLevel
23
+ class AnyLogger(Abstract, ta.Generic[T]):
24
+ def is_enabled_for(self, level: LogLevel) -> bool:
25
+ return self.get_effective_level() >= level
36
26
 
37
- time_ns: int
27
+ @abc.abstractmethod
28
+ def get_effective_level(self) -> LogLevel:
29
+ raise NotImplementedError
38
30
 
39
- exc_info: ta.Optional[LoggingExcInfo] = None
40
- exc_info_tuple: ta.Optional[LoggingExcInfoTuple] = None
31
+ #
41
32
 
42
33
  @ta.final
43
- class NOT_SET: # noqa
44
- def __new__(cls, *args, **kwargs): # noqa
45
- raise TypeError
46
-
47
- #
34
+ def isEnabledFor(self, level: LogLevel) -> bool: # noqa
35
+ return self.is_enabled_for(level)
48
36
 
49
- def __init__(
50
- self,
51
- level: LogLevel,
52
- *,
53
- time_ns: ta.Optional[int] = None,
37
+ @ta.final
38
+ def getEffectiveLevel(self) -> LogLevel: # noqa
39
+ return self.get_effective_level()
54
40
 
55
- exc_info: LoggingExcInfoArg = False,
41
+ ##
56
42
 
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]
43
+ @ta.overload
44
+ def log(self, level: LogLevel, msg: str, *args: ta.Any, **kwargs: ta.Any) -> T:
45
+ ...
62
46
 
63
- #
47
+ @ta.overload
48
+ def log(self, level: LogLevel, msg: ta.Tuple[ta.Any, ...], **kwargs: ta.Any) -> T:
49
+ ...
64
50
 
65
- if time_ns is None:
66
- time_ns = time.time_ns()
67
- self.time_ns: int = time_ns
51
+ @ta.overload
52
+ def log(self, level: LogLevel, msg_fn: LoggingMsgFn, **kwargs: ta.Any) -> T:
53
+ ...
68
54
 
69
- #
55
+ @ta.final
56
+ def log(self, level: LogLevel, *args, **kwargs):
57
+ return self._log(CaptureLoggingContextImpl(level, stack_offset=1), *args, **kwargs)
70
58
 
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
59
+ #
86
60
 
87
- #
61
+ @ta.overload
62
+ def debug(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> T:
63
+ ...
88
64
 
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
65
+ @ta.overload
66
+ def debug(self, msg: ta.Tuple[ta.Any, ...], **kwargs: ta.Any) -> T:
67
+ ...
94
68
 
95
- #
69
+ @ta.overload
70
+ def debug(self, msg_fn: LoggingMsgFn, **kwargs: ta.Any) -> T:
71
+ ...
96
72
 
97
- self.thread = LoggingThreadInfo.build()
98
- self.process = LoggingProcessInfo.build()
99
- self.multiprocessing = LoggingMultiprocessingInfo.build()
100
- self.asyncio_task = LoggingAsyncioTaskInfo.build()
73
+ @ta.final
74
+ def debug(self, *args, **kwargs):
75
+ return self._log(CaptureLoggingContextImpl(NamedLogLevel.DEBUG, stack_offset=1), *args, **kwargs)
101
76
 
102
77
  #
103
78
 
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
- #
79
+ @ta.overload
80
+ def info(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> T:
81
+ ...
117
82
 
118
- def inc_stack_offset(self, ofs: int = 1) -> 'LoggingContext':
119
- if hasattr(self, '_stack_offset'):
120
- self._stack_offset += ofs
121
- return self
83
+ @ta.overload
84
+ def info(self, msg: ta.Tuple[ta.Any, ...], **kwargs: ta.Any) -> T:
85
+ ...
122
86
 
123
- _caller: ta.Optional[LoggingCaller]
87
+ @ta.overload
88
+ def info(self, msg_fn: LoggingMsgFn, **kwargs: ta.Any) -> T:
89
+ ...
124
90
 
125
- def build_caller(self) -> ta.Optional[LoggingCaller]:
126
- """Must be cooperatively called only from the exact configured _stack_offset."""
91
+ @ta.final
92
+ def info(self, *args, **kwargs):
93
+ return self._log(CaptureLoggingContextImpl(NamedLogLevel.INFO, stack_offset=1), *args, **kwargs)
127
94
 
128
- try:
129
- return self._caller
130
- except AttributeError:
131
- pass
95
+ #
132
96
 
133
- caller = self._caller = LoggingCaller.find(
134
- self._stack_offset + 1,
135
- stack_info=self._stack_info,
136
- )
137
- return caller
97
+ @ta.overload
98
+ def warning(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> T:
99
+ ...
138
100
 
139
- def caller(self) -> ta.Optional[LoggingCaller]:
140
- try:
141
- return self._caller
142
- except AttributeError:
143
- return None
101
+ @ta.overload
102
+ def warning(self, msg: ta.Tuple[ta.Any, ...], **kwargs: ta.Any) -> T:
103
+ ...
144
104
 
145
- _source_file: ta.Optional[LoggingSourceFileInfo]
105
+ @ta.overload
106
+ def warning(self, msg_fn: LoggingMsgFn, **kwargs: ta.Any) -> T:
107
+ ...
146
108
 
147
- def source_file(self) -> ta.Optional[LoggingSourceFileInfo]:
148
- try:
149
- return self._source_file
150
- except AttributeError:
151
- pass
109
+ @ta.final
110
+ def warning(self, *args, **kwargs):
111
+ return self._log(CaptureLoggingContextImpl(NamedLogLevel.WARNING, stack_offset=1), *args, **kwargs)
152
112
 
153
- if (caller := self.caller()) is None:
154
- return None
113
+ #
155
114
 
156
- src_file = self._source_file = LoggingSourceFileInfo.build(caller.file_path)
157
- return src_file
115
+ @ta.overload
116
+ def error(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> T:
117
+ ...
158
118
 
119
+ @ta.overload
120
+ def error(self, msg: ta.Tuple[ta.Any, ...], **kwargs: ta.Any) -> T:
121
+ ...
159
122
 
160
- ##
123
+ @ta.overload
124
+ def error(self, msg_fn: LoggingMsgFn, **kwargs: ta.Any) -> T:
125
+ ...
161
126
 
127
+ @ta.final
128
+ def error(self, *args, **kwargs):
129
+ return self._log(CaptureLoggingContextImpl(NamedLogLevel.ERROR, stack_offset=1), *args, **kwargs)
162
130
 
163
- class AnyLogger(Abstract, ta.Generic[T]):
164
- def is_enabled_for(self, level: LogLevel) -> bool:
165
- return self.get_effective_level() >= level
131
+ #
166
132
 
167
- @abc.abstractmethod
168
- def get_effective_level(self) -> LogLevel:
169
- raise NotImplementedError
133
+ @ta.overload
134
+ def exception(self, msg: str, *args: ta.Any, exc_info: LoggingExcInfoArg = True, **kwargs: ta.Any) -> T:
135
+ ...
170
136
 
171
- #
137
+ @ta.overload
138
+ def exception(self, msg: ta.Tuple[ta.Any, ...], *, exc_info: LoggingExcInfoArg = True, **kwargs: ta.Any) -> T:
139
+ ...
172
140
 
173
- @ta.final
174
- def isEnabledFor(self, level: LogLevel) -> bool: # noqa
175
- return self.is_enabled_for(level)
141
+ @ta.overload
142
+ def exception(self, msg_fn: LoggingMsgFn, *, exc_info: LoggingExcInfoArg = True, **kwargs: ta.Any) -> T:
143
+ ...
176
144
 
177
145
  @ta.final
178
- def getEffectiveLevel(self) -> LogLevel: # noqa
179
- return self.get_effective_level()
146
+ def exception(self, *args, exc_info: LoggingExcInfoArg = True, **kwargs):
147
+ return self._log(CaptureLoggingContextImpl(NamedLogLevel.ERROR, exc_info=exc_info, stack_offset=1), *args, **kwargs) # noqa
180
148
 
181
149
  #
182
150
 
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)
151
+ @ta.overload
152
+ def critical(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> T:
153
+ ...
186
154
 
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)
155
+ @ta.overload
156
+ def critical(self, msg: ta.Tuple[ta.Any, ...], **kwargs: ta.Any) -> T:
157
+ ...
190
158
 
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)
159
+ @ta.overload
160
+ def critical(self, msg_fn: LoggingMsgFn, **kwargs: ta.Any) -> T:
161
+ ...
194
162
 
195
163
  @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)
164
+ def critical(self, *args, **kwargs):
165
+ return self._log(CaptureLoggingContextImpl(NamedLogLevel.CRITICAL, stack_offset=1), *args, **kwargs)
166
+
167
+ ##
168
+
169
+ @classmethod
170
+ def _prepare_msg_args(cls, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any) -> ta.Tuple[str, tuple]:
171
+ if callable(msg):
172
+ if args:
173
+ raise TypeError(f'Must not provide both a message function and args: {msg=} {args=}')
174
+ x = msg()
175
+ if isinstance(x, str):
176
+ return x, ()
177
+ elif isinstance(x, tuple):
178
+ if x:
179
+ return x[0], x[1:]
180
+ else:
181
+ return '', ()
182
+ else:
183
+ raise TypeError(x)
202
184
 
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)
185
+ elif isinstance(msg, tuple):
186
+ if args:
187
+ raise TypeError(f'Must not provide both a tuple message and args: {msg=} {args=}')
188
+ if msg:
189
+ return msg[0], msg[1:]
190
+ else:
191
+ return '', ()
206
192
 
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)
193
+ elif isinstance(msg, str):
194
+ return msg, args
210
195
 
211
- #
196
+ else:
197
+ raise TypeError(msg)
212
198
 
213
199
  @abc.abstractmethod
214
- def _log(self, ctx: LoggingContext, msg: str, *args: ta.Any, **kwargs: ta.Any) -> T:
200
+ def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any, **kwargs: ta.Any) -> T: # noqa
215
201
  raise NotImplementedError
216
202
 
217
203
 
218
204
  class Logger(AnyLogger[None], Abstract):
219
205
  @abc.abstractmethod
220
- def _log(self, ctx: LoggingContext, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None:
206
+ def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any, **kwargs: ta.Any) -> None: # noqa
221
207
  raise NotImplementedError
222
208
 
223
209
 
224
210
  class AsyncLogger(AnyLogger[ta.Awaitable[None]], Abstract):
225
211
  @abc.abstractmethod
226
- def _log(self, ctx: LoggingContext, msg: str, *args: ta.Any, **kwargs: ta.Any) -> ta.Awaitable[None]:
212
+ def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any, **kwargs: ta.Any) -> ta.Awaitable[None]: # noqa
227
213
  raise NotImplementedError
228
214
 
229
215
 
@@ -238,11 +224,11 @@ class AnyNopLogger(AnyLogger[T], Abstract):
238
224
 
239
225
  @ta.final
240
226
  class NopLogger(AnyNopLogger[None], Logger):
241
- def _log(self, ctx: LoggingContext, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None:
227
+ def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any, **kwargs: ta.Any) -> None: # noqa
242
228
  pass
243
229
 
244
230
 
245
231
  @ta.final
246
232
  class AsyncNopLogger(AnyNopLogger[ta.Awaitable[None]], AsyncLogger):
247
- async def _log(self, ctx: LoggingContext, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None:
233
+ async def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any, **kwargs: ta.Any) -> None: # noqa
248
234
  pass
omlish/logs/callers.py CHANGED
@@ -7,11 +7,13 @@ import traceback
7
7
  import types
8
8
  import typing as ta
9
9
 
10
+ from .infos import LoggingContextInfo
11
+
10
12
 
11
13
  ##
12
14
 
13
15
 
14
- class LoggingCaller(ta.NamedTuple):
16
+ class LoggingCaller(LoggingContextInfo, ta.NamedTuple): # type: ignore[misc]
15
17
  file_path: str
16
18
  line_no: int
17
19
  name: str
@@ -57,8 +59,6 @@ class LoggingCaller(ta.NamedTuple):
57
59
  sinfo = None
58
60
  if stack_info:
59
61
  sio = io.StringIO()
60
- # In stdlib, but done elsewhere here:
61
- # sio.write('Stack (most recent call last):\n')
62
62
  traceback.print_stack(f, file=sio)
63
63
  sinfo = sio.getvalue()
64
64
  sio.close()