omlish 0.0.0.dev420__py3-none-any.whl → 0.0.0.dev422__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 (99) hide show
  1. omlish/__about__.py +2 -2
  2. omlish/asyncs/bluelet/core.py +2 -2
  3. omlish/asyncs/bluelet/events.py +4 -3
  4. omlish/asyncs/bluelet/files.py +2 -2
  5. omlish/asyncs/bluelet/sockets.py +2 -2
  6. omlish/collections/frozen.py +1 -1
  7. omlish/collections/kv/base.py +1 -1
  8. omlish/collections/multimaps.py +1 -1
  9. omlish/collections/persistent/treapmap.py +2 -1
  10. omlish/concurrent/threadlets.py +2 -2
  11. omlish/configs/formats.py +5 -4
  12. omlish/configs/processing/flattening.py +2 -1
  13. omlish/configs/processing/rewriting.py +2 -2
  14. omlish/configs/shadow.py +3 -2
  15. omlish/daemons/spawning.py +2 -2
  16. omlish/daemons/targets.py +1 -1
  17. omlish/daemons/waiting.py +2 -1
  18. omlish/dataclasses/impl/generation/base.py +3 -2
  19. omlish/dataclasses/impl/generation/compilation.py +2 -1
  20. omlish/dataclasses/impl/generation/ops.py +2 -2
  21. omlish/dataclasses/impl/generation/processor.py +1 -1
  22. omlish/dataclasses/metaclass/meta.py +1 -0
  23. omlish/dataclasses/tools/static.py +5 -1
  24. omlish/formats/dotenv.py +3 -1
  25. omlish/formats/logfmt.py +111 -0
  26. omlish/formats/yaml.py +1 -1
  27. omlish/funcs/builders.py +2 -1
  28. omlish/funcs/match.py +1 -1
  29. omlish/funcs/pairs.py +41 -23
  30. omlish/funcs/pipes.py +3 -1
  31. omlish/http/asgi.py +2 -1
  32. omlish/http/coro/client/io.py +3 -2
  33. omlish/http/coro/server/server.py +2 -2
  34. omlish/http/handlers.py +2 -1
  35. omlish/http/jwt.py +1 -1
  36. omlish/http/parsing.py +2 -2
  37. omlish/io/compress/base.py +3 -2
  38. omlish/io/coro/readers.py +4 -3
  39. omlish/io/fdio/handlers.py +3 -2
  40. omlish/io/fdio/pollers.py +3 -1
  41. omlish/lang/__init__.py +20 -8
  42. omlish/lang/attrs.py +3 -1
  43. omlish/lang/casing.py +3 -1
  44. omlish/lang/classes/abstract.py +35 -96
  45. omlish/lang/classes/virtual.py +2 -2
  46. omlish/lang/contextmanagers.py +6 -4
  47. omlish/lang/generators.py +2 -2
  48. omlish/lang/iterables.py +6 -6
  49. omlish/lang/objects.py +0 -58
  50. omlish/lang/outcomes.py +3 -1
  51. omlish/lang/typing.py +5 -24
  52. omlish/lite/abstract.py +119 -0
  53. omlish/lite/contextmanagers.py +4 -1
  54. omlish/lite/inject.py +9 -9
  55. omlish/lite/marshal.py +230 -114
  56. omlish/lite/maybes.py +3 -1
  57. omlish/lite/maysync.py +4 -2
  58. omlish/lite/objects.py +81 -0
  59. omlish/lite/reflect.py +0 -15
  60. omlish/lite/timeouts.py +3 -1
  61. omlish/lite/wrappers.py +23 -0
  62. omlish/logs/abc.py +21 -5
  63. omlish/logs/all.py +14 -3
  64. omlish/logs/callers.py +23 -4
  65. omlish/logs/configs.py +13 -10
  66. omlish/logs/levels.py +7 -0
  67. omlish/logs/protocol.py +84 -42
  68. omlish/logs/typed/__init__.py +0 -0
  69. omlish/logs/typed/bindings.py +537 -0
  70. omlish/logs/typed/contexts.py +138 -0
  71. omlish/logs/typed/types.py +484 -0
  72. omlish/logs/typed/values.py +114 -0
  73. omlish/marshal/__init__.py +1 -0
  74. omlish/os/atomics.py +3 -2
  75. omlish/os/deathpacts/base.py +4 -2
  76. omlish/os/forkhooks.py +2 -2
  77. omlish/os/pidfiles/pinning.py +2 -1
  78. omlish/reflect/types.py +4 -3
  79. omlish/secrets/crypto.py +1 -1
  80. omlish/sockets/bind.py +2 -1
  81. omlish/sockets/handlers.py +3 -2
  82. omlish/sockets/server/handlers.py +2 -1
  83. omlish/sockets/server/server.py +4 -3
  84. omlish/sql/api/base.py +2 -2
  85. omlish/subprocesses/asyncs.py +2 -1
  86. omlish/subprocesses/base.py +2 -2
  87. omlish/subprocesses/maysync.py +1 -2
  88. omlish/subprocesses/run.py +2 -1
  89. omlish/subprocesses/sync.py +2 -1
  90. omlish/term/coloring.py +3 -1
  91. omlish/testing/pytest/plugins/asyncs/backends/base.py +3 -1
  92. omlish/testing/unittest/loading.py +2 -2
  93. omlish/text/asdl.py +4 -3
  94. {omlish-0.0.0.dev420.dist-info → omlish-0.0.0.dev422.dist-info}/METADATA +1 -1
  95. {omlish-0.0.0.dev420.dist-info → omlish-0.0.0.dev422.dist-info}/RECORD +99 -89
  96. {omlish-0.0.0.dev420.dist-info → omlish-0.0.0.dev422.dist-info}/WHEEL +0 -0
  97. {omlish-0.0.0.dev420.dist-info → omlish-0.0.0.dev422.dist-info}/entry_points.txt +0 -0
  98. {omlish-0.0.0.dev420.dist-info → omlish-0.0.0.dev422.dist-info}/licenses/LICENSE +0 -0
  99. {omlish-0.0.0.dev420.dist-info → omlish-0.0.0.dev422.dist-info}/top_level.txt +0 -0
omlish/lite/reflect.py CHANGED
@@ -91,18 +91,3 @@ def is_literal_type(spec: ta.Any) -> bool:
91
91
 
92
92
  def get_literal_type_args(spec: ta.Any) -> ta.Iterable[ta.Any]:
93
93
  return spec.__args__
94
-
95
-
96
- ##
97
-
98
-
99
- def deep_subclasses(cls: ta.Type[T]) -> ta.Iterator[ta.Type[T]]:
100
- seen = set()
101
- todo = list(reversed(cls.__subclasses__()))
102
- while todo:
103
- cur = todo.pop()
104
- if cur in seen:
105
- continue
106
- seen.add(cur)
107
- yield cur
108
- todo.extend(reversed(cur.__subclasses__()))
omlish/lite/timeouts.py CHANGED
@@ -7,6 +7,8 @@ import abc
7
7
  import time
8
8
  import typing as ta
9
9
 
10
+ from .abstract import Abstract
11
+
10
12
 
11
13
  TimeoutLike = ta.Union['Timeout', ta.Type['Timeout.DEFAULT'], ta.Iterable['TimeoutLike'], float, None] # ta.TypeAlias
12
14
 
@@ -14,7 +16,7 @@ TimeoutLike = ta.Union['Timeout', ta.Type['Timeout.DEFAULT'], ta.Iterable['Timeo
14
16
  ##
15
17
 
16
18
 
17
- class Timeout(abc.ABC):
19
+ class Timeout(Abstract):
18
20
  @property
19
21
  @abc.abstractmethod
20
22
  def can_expire(self) -> bool:
@@ -0,0 +1,23 @@
1
+ # ruff: noqa: UP006
2
+ import functools
3
+ import typing as ta
4
+
5
+
6
+ ##
7
+
8
+
9
+ _ANNOTATION_ATTRS: ta.FrozenSet[str] = frozenset([
10
+ '__annotations__',
11
+
12
+ '__annotate__',
13
+ '__annotate_func__',
14
+
15
+ '__annotations_cache__',
16
+ ])
17
+
18
+ _UPDATE_WRAPPER_ASSIGNMENTS_NO_ANNOTATIONS: ta.Sequence[str] = list(frozenset(functools.WRAPPER_ASSIGNMENTS) - _ANNOTATION_ATTRS) # noqa
19
+
20
+
21
+ def update_wrapper_no_annotations(wrapper, wrapped):
22
+ functools.update_wrapper(wrapper, wrapped, assigned=_UPDATE_WRAPPER_ASSIGNMENTS_NO_ANNOTATIONS)
23
+ return wrapper
omlish/logs/abc.py CHANGED
@@ -1,6 +1,4 @@
1
- # ruff: noqa: A002
2
- # ruff: noqa: N802
3
- # ruff: noqa: N815
1
+ # ruff: noqa: A002 N802 N815
4
2
  import types
5
3
  import typing as ta
6
4
 
@@ -94,6 +92,8 @@ class LogRecord:
94
92
 
95
93
 
96
94
  class Formatter(ta.Protocol):
95
+ """https://docs.python.org/3/library/logging.html#formatter-objects"""
96
+
97
97
  default_time_format: ta.ClassVar[str]
98
98
  default_msec_format: ta.ClassVar[str]
99
99
 
@@ -111,6 +111,8 @@ class Formatter(ta.Protocol):
111
111
 
112
112
 
113
113
  class BufferingFormatter(ta.Protocol):
114
+ """https://docs.python.org/3/library/logging.html#logging.BufferingFormatter"""
115
+
114
116
  def formatHeader(self, records: ta.Sequence[LogRecord]) -> str: ...
115
117
 
116
118
  def formatFooter(self, records: ta.Sequence[LogRecord]) -> str: ...
@@ -122,10 +124,14 @@ class BufferingFormatter(ta.Protocol):
122
124
 
123
125
 
124
126
  class Filter(ta.Protocol):
127
+ """https://docs.python.org/3/library/logging.html#filter-objects"""
128
+
125
129
  def filter(self, record: LogRecord) -> bool: ...
126
130
 
127
131
 
128
132
  class Filterer(ta.Protocol):
133
+ """A base class for loggers and handlers which allows them to share common code."""
134
+
129
135
  def addFilter(self, filter: Filter) -> None: ...
130
136
 
131
137
  def removeFilter(self, filter: Filter) -> None: ...
@@ -136,7 +142,9 @@ class Filterer(ta.Protocol):
136
142
  ##
137
143
 
138
144
 
139
- class Handler(ta.Protocol):
145
+ class Handler(Filterer, ta.Protocol):
146
+ """https://docs.python.org/3/library/logging.html#handler-objects"""
147
+
140
148
  level: Level
141
149
 
142
150
  def get_name(self) -> str: ...
@@ -176,7 +184,9 @@ class Stream(ta.Protocol):
176
184
  def close(self) -> None: ... # OPTIONAL METHOD
177
185
 
178
186
 
179
- class StreamHandler(Handler):
187
+ class StreamHandler(Handler, ta.Protocol):
188
+ """https://docs.python.org/3/library/logging.handlers.html#logging.StreamHandler"""
189
+
180
190
  terminator: ta.ClassVar[str]
181
191
 
182
192
  stream: Stream
@@ -192,6 +202,8 @@ class StreamHandler(Handler):
192
202
 
193
203
 
194
204
  class Manager(ta.Protocol):
205
+ """There is [under normal circumstances] just one Manager instance, which holds the hierarchy of loggers."""
206
+
195
207
  root: 'Logger'
196
208
 
197
209
  disable: Level
@@ -215,6 +227,8 @@ Caller: ta.TypeAlias = tuple[
215
227
 
216
228
 
217
229
  class Logger(Filterer, ta.Protocol):
230
+ """https://docs.python.org/3/library/logging.html#logger-objects"""
231
+
218
232
  name: str
219
233
  level: Level
220
234
  parent: ta.Optional['Logger']
@@ -274,6 +288,8 @@ class Logger(Filterer, ta.Protocol):
274
288
 
275
289
 
276
290
  class LoggerAdapter(ta.Protocol):
291
+ """https://docs.python.org/3/library/logging.html#loggeradapter-objects"""
292
+
277
293
  logger: Logger
278
294
  extra: ta.Mapping[str, ta.Any]
279
295
 
omlish/logs/all.py CHANGED
@@ -25,16 +25,27 @@ with _lang.auto_proxy_init(globals()):
25
25
  JsonLogFormatter,
26
26
  )
27
27
 
28
+ from .levels import ( # noqa
29
+ LogLevel,
30
+ )
31
+
28
32
  from .noisy import ( # noqa
29
33
  silence_noisy_loggers,
30
34
  )
31
35
 
32
36
  from .protocol import ( # noqa
33
- LogLevel,
34
-
37
+ AnyLogging,
35
38
  Logging,
36
- NopLogging,
39
+ AsyncLogging,
40
+
41
+ AnyAbstractLogging,
37
42
  AbstractLogging,
43
+ AbstractAsyncLogging,
44
+
45
+ AnyNopLogging,
46
+ NopLogging,
47
+ NopAsyncLogging,
48
+
38
49
  StdlibLogging,
39
50
  )
40
51
 
omlish/logs/callers.py CHANGED
@@ -1,6 +1,7 @@
1
1
  # ruff: noqa: UP006 UP007 UP045
2
2
  # @omlish-lite
3
3
  import io
4
+ import os.path
4
5
  import sys
5
6
  import traceback
6
7
  import types
@@ -16,18 +17,36 @@ class LoggingCaller(ta.NamedTuple):
16
17
  func: str
17
18
  sinfo: ta.Optional[str]
18
19
 
20
+ @classmethod
21
+ def is_internal_frame(cls, frame: types.FrameType) -> bool:
22
+ filename = os.path.normcase(frame.f_code.co_filename)
23
+
24
+ # https://github.com/python/cpython/commit/5ca6d7469be53960843df39bb900e9c3359f127f
25
+ if 'importlib' in filename and '_bootstrap' in filename:
26
+ return True
27
+
28
+ return False
29
+
19
30
  @classmethod
20
31
  def find_frame(cls, ofs: int = 0) -> types.FrameType:
21
- f: ta.Any = sys._getframe(2 + ofs) # noqa
22
- while hasattr(f, 'f_code'):
32
+ f: ta.Optional[types.FrameType] = sys._getframe(2 + ofs) # noqa
33
+
34
+ while f is not None and hasattr(f, 'f_code'):
23
35
  if f.f_code.co_filename != __file__:
24
36
  return f
37
+
25
38
  f = f.f_back
39
+
26
40
  raise RuntimeError
27
41
 
28
42
  @classmethod
29
- def find(cls, stack_info: bool = False) -> 'LoggingCaller':
30
- f = cls.find_frame(1)
43
+ def find(
44
+ cls,
45
+ ofs: int = 0,
46
+ *,
47
+ stack_info: bool = False,
48
+ ) -> 'LoggingCaller':
49
+ f = cls.find_frame(ofs + 1)
31
50
  # TODO: ('(unknown file)', 0, '(unknown function)', None) ?
32
51
 
33
52
  sinfo = None
omlish/logs/configs.py CHANGED
@@ -1,17 +1,20 @@
1
+ # ruff: noqa: UP006 UP045
2
+ # @omlish-lite
1
3
  """
2
4
  https://docs.python.org/3/howto/logging.html#configuring-logging
5
+ https://docs.python.org/3/library/logging.config.html#logging-config-dictschema
3
6
  """
4
7
  import dataclasses as dc
5
8
  import typing as ta
6
9
 
7
10
 
8
- ##
11
+ FilterConfig = ta.Dict[str, ta.Any] # ta.TypeAlias
12
+ FormatterConfig = ta.Dict[str, ta.Any] # ta.TypeAlias
13
+ HandlerConfig = ta.Dict[str, ta.Any] # ta.TypeAlias
14
+ LoggerConfig = ta.Dict[str, ta.Any] # ta.TypeAlias
9
15
 
10
16
 
11
- FilterConfig: ta.TypeAlias = dict[str, ta.Any]
12
- FormatterConfig: ta.TypeAlias = dict[str, ta.Any]
13
- HandlerConfig: ta.TypeAlias = dict[str, ta.Any]
14
- LoggerConfig: ta.TypeAlias = dict[str, ta.Any]
17
+ ##
15
18
 
16
19
 
17
20
  @dc.dataclass()
@@ -19,8 +22,8 @@ class DictConfig:
19
22
  version: int = 1
20
23
  incremental: bool = False
21
24
  disable_existing_loggers: bool = False
22
- filters: dict[str, FilterConfig] = dc.field(default_factory=dict)
23
- formatters: dict[str, FormatterConfig] = dc.field(default_factory=dict)
24
- handlers: dict[str, HandlerConfig] = dc.field(default_factory=dict)
25
- loggers: dict[str, LoggerConfig] = dc.field(default_factory=dict)
26
- root: LoggerConfig | None = None
25
+ filters: ta.Dict[str, FilterConfig] = dc.field(default_factory=dict)
26
+ formatters: ta.Dict[str, FormatterConfig] = dc.field(default_factory=dict)
27
+ handlers: ta.Dict[str, HandlerConfig] = dc.field(default_factory=dict)
28
+ loggers: ta.Dict[str, LoggerConfig] = dc.field(default_factory=dict)
29
+ root: ta.Optional[LoggerConfig] = None
omlish/logs/levels.py ADDED
@@ -0,0 +1,7 @@
1
+ # @omlish-lite
2
+
3
+
4
+ LogLevel = int # ta.TypeAlias
5
+
6
+
7
+ ##
omlish/logs/protocol.py CHANGED
@@ -6,49 +6,50 @@ import sys
6
6
  import typing as ta
7
7
 
8
8
  from .callers import LoggingCaller
9
+ from .levels import LogLevel
9
10
 
10
11
 
11
- LogLevel = int # ta.TypeAlias
12
+ T = ta.TypeVar('T')
13
+ T_co = ta.TypeVar('T_co', covariant=True)
12
14
 
13
15
 
14
16
  ##
15
17
 
16
18
 
17
- class Logging(ta.Protocol):
18
- def isEnabledFor(self, level: LogLevel) -> bool: # noqa
19
- ...
19
+ class AnyLogging(ta.Protocol[T_co]):
20
+ def isEnabledFor(self, level: LogLevel) -> bool: ... # noqa
20
21
 
21
- def getEffectiveLevel(self) -> LogLevel: # noqa
22
- ...
22
+ def getEffectiveLevel(self) -> LogLevel: ... # noqa
23
23
 
24
24
  #
25
25
 
26
- def debug(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None:
27
- ...
26
+ def debug(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> T_co: ...
27
+
28
+ def info(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> T_co: ...
29
+
30
+ def warning(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> T_co: ...
31
+
32
+ def error(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> T_co: ...
28
33
 
29
- def info(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None:
30
- ...
34
+ def exception(self, msg: str, *args: ta.Any, exc_info: bool = True, **kwargs: ta.Any) -> T_co: ...
31
35
 
32
- def warning(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None:
33
- ...
36
+ def critical(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> T_co: ...
34
37
 
35
- def error(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None:
36
- ...
38
+ def log(self, level: LogLevel, msg: str, *args: ta.Any, **kwargs: ta.Any) -> T_co: ...
37
39
 
38
- def exception(self, msg: str, *args: ta.Any, exc_info: bool = True, **kwargs) -> None:
39
- ...
40
40
 
41
- def critical(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None:
42
- ...
41
+ class Logging(AnyLogging[None], ta.Protocol):
42
+ pass
43
43
 
44
- def log(self, level: LogLevel, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None:
45
- ...
44
+
45
+ class AsyncLogging(AnyLogging[ta.Awaitable[None]], ta.Protocol):
46
+ pass
46
47
 
47
48
 
48
49
  ##
49
50
 
50
51
 
51
- class AbstractLogging(abc.ABC):
52
+ class AnyAbstractLogging(abc.ABC, ta.Generic[T]):
52
53
  @ta.final
53
54
  def isEnabledFor(self, level: LogLevel) -> bool: # noqa
54
55
  return self.is_enabled_for(level)
@@ -66,34 +67,44 @@ class AbstractLogging(abc.ABC):
66
67
 
67
68
  #
68
69
 
69
- def debug(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None:
70
- if self.is_enabled_for(logging.DEBUG):
71
- self.log(logging.DEBUG, msg, args, **kwargs)
70
+ def debug(self, msg: str, *args: ta.Any, _logging_stack_offset: int = 0, **kwargs: ta.Any) -> T:
71
+ return self.log(logging.DEBUG, msg, *args, _logging_stack_offset=_logging_stack_offset + 1, **kwargs)
72
72
 
73
- def info(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None:
74
- if self.is_enabled_for(logging.INFO):
75
- self.log(logging.INFO, msg, args, **kwargs)
73
+ def info(self, msg: str, *args: ta.Any, _logging_stack_offset: int = 0, **kwargs: ta.Any) -> T:
74
+ return self.log(logging.INFO, msg, *args, _logging_stack_offset=_logging_stack_offset + 1, **kwargs)
76
75
 
77
- def warning(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None:
78
- if self.is_enabled_for(logging.WARNING):
79
- self.log(logging.WARNING, msg, args, **kwargs)
76
+ def warning(self, msg: str, *args: ta.Any, _logging_stack_offset: int = 0, **kwargs: ta.Any) -> T:
77
+ return self.log(logging.WARNING, msg, *args, _logging_stack_offset=_logging_stack_offset + 1, **kwargs)
80
78
 
81
- def error(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None:
82
- if self.is_enabled_for(logging.ERROR):
83
- self.log(logging.ERROR, msg, args, **kwargs)
79
+ def error(self, msg: str, *args: ta.Any, _logging_stack_offset: int = 0, **kwargs: ta.Any) -> T:
80
+ return self.log(logging.ERROR, msg, *args, _logging_stack_offset=_logging_stack_offset + 1, **kwargs)
84
81
 
85
- def exception(self, msg: str, *args: ta.Any, exc_info: bool = True, **kwargs: ta.Any) -> None:
86
- self.error(msg, *args, exc_info=exc_info, **kwargs)
82
+ def exception(self, msg: str, *args: ta.Any, exc_info: bool = True, _logging_stack_offset: int = 0, **kwargs: ta.Any) -> T: # noqa
83
+ return self.error(msg, *args, exc_info=exc_info, _logging_stack_offset=_logging_stack_offset + 1, **kwargs) # noqa
87
84
 
88
- def critical(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None:
89
- if self.is_enabled_for(logging.CRITICAL):
90
- self.log(logging.CRITICAL, msg, args, **kwargs)
85
+ def critical(self, msg: str, *args: ta.Any, _logging_stack_offset: int = 0, **kwargs: ta.Any) -> T:
86
+ return self.log(logging.CRITICAL, msg, *args, _logging_stack_offset=_logging_stack_offset + 1, **kwargs)
91
87
 
92
- def log(self, level: LogLevel, msg: str, *args: ta.Any, **kwargs: ta.Any) -> None:
88
+ @abc.abstractmethod
89
+ def log(
90
+ self,
91
+ level: int,
92
+ msg: str,
93
+ *args: ta.Any,
94
+ exc_info: ta.Any = None,
95
+ extra: ta.Any = None,
96
+ stack_info: bool = False,
97
+ _logging_stack_offset: int = 0,
98
+ ) -> T:
99
+ raise NotImplementedError
100
+
101
+
102
+ class AbstractLogging(AnyAbstractLogging[None], abc.ABC):
103
+ def log(self, level: LogLevel, msg: str, *args: ta.Any, _logging_stack_offset: int = 0, **kwargs: ta.Any) -> None:
93
104
  if not isinstance(level, int):
94
105
  raise TypeError('Level must be an integer.')
95
106
  if self.is_enabled_for(level):
96
- self._log(level, msg, args, **kwargs)
107
+ self._log(level, msg, args, _logging_stack_offset=_logging_stack_offset + 1, **kwargs)
97
108
 
98
109
  @abc.abstractmethod
99
110
  def _log(
@@ -105,21 +116,51 @@ class AbstractLogging(abc.ABC):
105
116
  exc_info: ta.Any = None,
106
117
  extra: ta.Any = None,
107
118
  stack_info: bool = False,
119
+ _logging_stack_offset: int = 0,
108
120
  ) -> None:
109
121
  raise NotImplementedError
110
122
 
111
123
 
124
+ class AbstractAsyncLogging(AnyAbstractLogging[ta.Awaitable[None]], abc.ABC):
125
+ async def log(self, level: LogLevel, msg: str, *args: ta.Any, _logging_stack_offset: int = 0, **kwargs: ta.Any) -> None: # noqa
126
+ if not isinstance(level, int):
127
+ raise TypeError('Level must be an integer.')
128
+ if self.is_enabled_for(level):
129
+ await self._log(level, msg, args, _logging_stack_offset=_logging_stack_offset + 1, **kwargs)
130
+
131
+ @abc.abstractmethod
132
+ def _log(
133
+ self,
134
+ level: int,
135
+ msg: str,
136
+ args: ta.Any,
137
+ *,
138
+ exc_info: ta.Any = None,
139
+ extra: ta.Any = None,
140
+ stack_info: bool = False,
141
+ _logging_stack_offset: int = 0,
142
+ ) -> ta.Awaitable[None]:
143
+ raise NotImplementedError
144
+
145
+
112
146
  ##
113
147
 
114
148
 
115
- class NopLogging(AbstractLogging):
149
+ class AnyNopLogging(AnyAbstractLogging[T], abc.ABC):
116
150
  def get_effective_level(self) -> LogLevel:
117
151
  return logging.CRITICAL + 1
118
152
 
153
+
154
+ class NopLogging(AnyNopLogging[None], AbstractLogging):
119
155
  def _log(self, *args: ta.Any, **kwargs: ta.Any) -> None:
120
156
  pass
121
157
 
122
158
 
159
+ class NopAsyncLogging(AnyNopLogging[ta.Awaitable[None]], AbstractAsyncLogging):
160
+ async def _log(self, *args: ta.Any, **kwargs: ta.Any) -> None:
161
+ pass
162
+
163
+
123
164
  ##
124
165
 
125
166
 
@@ -151,8 +192,9 @@ class StdlibLogging(AbstractLogging):
151
192
  exc_info: ta.Any = None,
152
193
  extra: ta.Any = None,
153
194
  stack_info: bool = False,
195
+ _logging_stack_offset: int = 0,
154
196
  ) -> None:
155
- caller = LoggingCaller.find(stack_info)
197
+ caller = LoggingCaller.find(_logging_stack_offset, stack_info=stack_info)
156
198
 
157
199
  if exc_info:
158
200
  if isinstance(exc_info, BaseException):
File without changes