python-utils 2.5.6__py3-none-any.whl → 4.0.0__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.
- python_utils/__about__.py +35 -7
- python_utils/__init__.py +241 -0
- python_utils/_aliases.py +53 -0
- python_utils/aio.py +133 -0
- python_utils/containers.py +637 -0
- python_utils/converters.py +265 -85
- python_utils/decorators.py +216 -6
- python_utils/exceptions.py +47 -0
- python_utils/formatters.py +72 -16
- python_utils/generators.py +126 -0
- python_utils/import_.py +64 -26
- python_utils/logger.py +352 -29
- python_utils/loguru.py +53 -0
- python_utils/terminal.py +127 -67
- python_utils/time.py +371 -18
- python_utils/types.py +179 -0
- python_utils-4.0.0.dist-info/METADATA +389 -0
- python_utils-4.0.0.dist-info/RECORD +21 -0
- {python_utils-2.5.6.dist-info → python_utils-4.0.0.dist-info}/WHEEL +1 -3
- python_utils-2.5.6.dist-info/METADATA +0 -122
- python_utils-2.5.6.dist-info/RECORD +0 -15
- python_utils-2.5.6.dist-info/top_level.txt +0 -1
- /python_utils/{compat.py → py.typed} +0 -0
- {python_utils-2.5.6.dist-info → python_utils-4.0.0.dist-info/licenses}/LICENSE +0 -0
python_utils/logger.py
CHANGED
|
@@ -1,18 +1,165 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module provides a base class `LoggerBase` and a derived class `Logged`
|
|
3
|
+
for adding logging capabilities to classes. The `LoggerBase` class expects
|
|
4
|
+
a `logger` attribute to be a `logging.Logger` or compatible instance and
|
|
5
|
+
provides methods for logging at various levels. The `Logged` class
|
|
6
|
+
automatically adds a named logger to the class.
|
|
7
|
+
|
|
8
|
+
Classes:
|
|
9
|
+
LoggerBase:
|
|
10
|
+
A base class that adds logging utilities to a class.
|
|
11
|
+
Logged:
|
|
12
|
+
A derived class that automatically adds a named logger to a class.
|
|
13
|
+
|
|
14
|
+
Example:
|
|
15
|
+
>>> class MyClass(Logged):
|
|
16
|
+
... def __init__(self):
|
|
17
|
+
... Logged.__init__(self)
|
|
18
|
+
|
|
19
|
+
>>> my_class = MyClass()
|
|
20
|
+
>>> my_class.debug('debug')
|
|
21
|
+
>>> my_class.info('info')
|
|
22
|
+
>>> my_class.warning('warning')
|
|
23
|
+
>>> my_class.error('error')
|
|
24
|
+
>>> my_class.exception('exception')
|
|
25
|
+
>>> my_class.log(0, 'log')
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import abc
|
|
31
|
+
import collections.abc
|
|
1
32
|
import logging
|
|
2
|
-
import
|
|
33
|
+
import types
|
|
34
|
+
import typing
|
|
35
|
+
|
|
36
|
+
if typing.TYPE_CHECKING:
|
|
37
|
+
import typing_extensions
|
|
38
|
+
|
|
39
|
+
from . import decorators
|
|
3
40
|
|
|
4
41
|
__all__ = ['Logged']
|
|
5
42
|
|
|
43
|
+
#: Accepted values for the logging ``exc_info`` parameter: a bool, an
|
|
44
|
+
#: ``(type, value, traceback)`` triple, a bare exception instance, or ``None``.
|
|
45
|
+
# From the logging typeshed, converted to be compatible with Python 3.8
|
|
46
|
+
# https://github.com/python/typeshed/blob/main/stdlib/logging/__init__.pyi
|
|
47
|
+
_ExcInfoType: typing.TypeAlias = (
|
|
48
|
+
bool
|
|
49
|
+
| tuple[
|
|
50
|
+
type[BaseException],
|
|
51
|
+
BaseException,
|
|
52
|
+
types.TracebackType | None,
|
|
53
|
+
]
|
|
54
|
+
| tuple[None, None, None]
|
|
55
|
+
| BaseException
|
|
56
|
+
| None
|
|
57
|
+
)
|
|
58
|
+
#: Parameter specification capturing a wrapped logger method's arguments.
|
|
59
|
+
_P = typing.ParamSpec('_P')
|
|
60
|
+
#: Covariant return-type variable for wrapped logger methods.
|
|
61
|
+
_T = typing.TypeVar('_T', covariant=True)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class LoggerProtocol(typing.Protocol):
|
|
65
|
+
"""Structural type for any ``logging.Logger``-compatible logger.
|
|
66
|
+
|
|
67
|
+
Objects that provide these methods (such as a ``logging.Logger`` or a
|
|
68
|
+
``loguru`` logger) can be used as the ``logger`` attribute of a
|
|
69
|
+
``LoggerBase`` subclass. Only the shape matters; no inheritance required.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def debug(
|
|
73
|
+
self,
|
|
74
|
+
msg: object,
|
|
75
|
+
*args: object,
|
|
76
|
+
exc_info: _ExcInfoType = None,
|
|
77
|
+
stack_info: bool = False,
|
|
78
|
+
stacklevel: int = 1,
|
|
79
|
+
extra: collections.abc.Mapping[str, object] | None = None,
|
|
80
|
+
) -> None:
|
|
81
|
+
"""Log ``msg`` at ``DEBUG`` level."""
|
|
82
|
+
|
|
83
|
+
def info(
|
|
84
|
+
self,
|
|
85
|
+
msg: object,
|
|
86
|
+
*args: object,
|
|
87
|
+
exc_info: _ExcInfoType = None,
|
|
88
|
+
stack_info: bool = False,
|
|
89
|
+
stacklevel: int = 1,
|
|
90
|
+
extra: collections.abc.Mapping[str, object] | None = None,
|
|
91
|
+
) -> None:
|
|
92
|
+
"""Log ``msg`` at ``INFO`` level."""
|
|
6
93
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
94
|
+
def warning(
|
|
95
|
+
self,
|
|
96
|
+
msg: object,
|
|
97
|
+
*args: object,
|
|
98
|
+
exc_info: _ExcInfoType = None,
|
|
99
|
+
stack_info: bool = False,
|
|
100
|
+
stacklevel: int = 1,
|
|
101
|
+
extra: collections.abc.Mapping[str, object] | None = None,
|
|
102
|
+
) -> None:
|
|
103
|
+
"""Log ``msg`` at ``WARNING`` level."""
|
|
104
|
+
|
|
105
|
+
def error(
|
|
106
|
+
self,
|
|
107
|
+
msg: object,
|
|
108
|
+
*args: object,
|
|
109
|
+
exc_info: _ExcInfoType = None,
|
|
110
|
+
stack_info: bool = False,
|
|
111
|
+
stacklevel: int = 1,
|
|
112
|
+
extra: collections.abc.Mapping[str, object] | None = None,
|
|
113
|
+
) -> None:
|
|
114
|
+
"""Log ``msg`` at ``ERROR`` level."""
|
|
115
|
+
|
|
116
|
+
def critical(
|
|
117
|
+
self,
|
|
118
|
+
msg: object,
|
|
119
|
+
*args: object,
|
|
120
|
+
exc_info: _ExcInfoType = None,
|
|
121
|
+
stack_info: bool = False,
|
|
122
|
+
stacklevel: int = 1,
|
|
123
|
+
extra: collections.abc.Mapping[str, object] | None = None,
|
|
124
|
+
) -> None:
|
|
125
|
+
"""Log ``msg`` at ``CRITICAL`` level."""
|
|
126
|
+
|
|
127
|
+
def exception(
|
|
128
|
+
self,
|
|
129
|
+
msg: object,
|
|
130
|
+
*args: object,
|
|
131
|
+
exc_info: _ExcInfoType = None,
|
|
132
|
+
stack_info: bool = False,
|
|
133
|
+
stacklevel: int = 1,
|
|
134
|
+
extra: collections.abc.Mapping[str, object] | None = None,
|
|
135
|
+
) -> None:
|
|
136
|
+
"""Log ``msg`` at ``ERROR`` level, including exception information."""
|
|
137
|
+
|
|
138
|
+
def log(
|
|
139
|
+
self,
|
|
140
|
+
level: int,
|
|
141
|
+
msg: object,
|
|
142
|
+
*args: object,
|
|
143
|
+
exc_info: _ExcInfoType = None,
|
|
144
|
+
stack_info: bool = False,
|
|
145
|
+
stacklevel: int = 1,
|
|
146
|
+
extra: collections.abc.Mapping[str, object] | None = None,
|
|
147
|
+
) -> None:
|
|
148
|
+
"""Log ``msg`` at the integer ``level``."""
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class LoggerBase(abc.ABC):
|
|
152
|
+
"""Class which automatically adds logging utilities to your class when
|
|
153
|
+
inheriting. Expects `logger` to be a logging.Logger or compatible instance.
|
|
10
154
|
|
|
11
155
|
Adds easy access to debug, info, warning, error, exception and log methods
|
|
12
156
|
|
|
13
|
-
>>> class MyClass(
|
|
157
|
+
>>> class MyClass(LoggerBase):
|
|
158
|
+
... logger = logging.getLogger(__name__)
|
|
159
|
+
...
|
|
14
160
|
... def __init__(self):
|
|
15
161
|
... Logged.__init__(self)
|
|
162
|
+
|
|
16
163
|
>>> my_class = MyClass()
|
|
17
164
|
>>> my_class.debug('debug')
|
|
18
165
|
>>> my_class.info('info')
|
|
@@ -20,43 +167,219 @@ class Logged(object):
|
|
|
20
167
|
>>> my_class.error('error')
|
|
21
168
|
>>> my_class.exception('exception')
|
|
22
169
|
>>> my_class.log(0, 'log')
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
# I've tried using a protocol to properly type the logger but it gave all
|
|
173
|
+
# sorts of issues with mypy so we're using the lazy solution for now. The
|
|
174
|
+
# actual classes define the correct type anyway
|
|
175
|
+
logger: typing.Any
|
|
176
|
+
# logger: LoggerProtocol
|
|
28
177
|
|
|
29
178
|
@classmethod
|
|
30
|
-
def __get_name(
|
|
179
|
+
def __get_name( # pyright: ignore[reportUnusedFunction]
|
|
180
|
+
cls, *name_parts: str
|
|
181
|
+
) -> str:
|
|
182
|
+
"""Join the non-empty, stripped ``name_parts`` into a dotted name."""
|
|
31
183
|
return '.'.join(n.strip() for n in name_parts if n.strip())
|
|
32
184
|
|
|
185
|
+
@decorators.wraps_classmethod(logging.Logger.debug)
|
|
186
|
+
@classmethod
|
|
187
|
+
def debug(
|
|
188
|
+
cls,
|
|
189
|
+
msg: object,
|
|
190
|
+
*args: object,
|
|
191
|
+
exc_info: _ExcInfoType = None,
|
|
192
|
+
stack_info: bool = False,
|
|
193
|
+
stacklevel: int = 1,
|
|
194
|
+
extra: collections.abc.Mapping[str, object] | None = None,
|
|
195
|
+
) -> None:
|
|
196
|
+
"""Log ``msg`` at ``DEBUG`` level on the class logger."""
|
|
197
|
+
return cls.logger.debug( # type: ignore[no-any-return]
|
|
198
|
+
msg,
|
|
199
|
+
*args,
|
|
200
|
+
exc_info=exc_info,
|
|
201
|
+
stack_info=stack_info,
|
|
202
|
+
stacklevel=stacklevel,
|
|
203
|
+
extra=extra,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
@decorators.wraps_classmethod(logging.Logger.info)
|
|
33
207
|
@classmethod
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
208
|
+
def info(
|
|
209
|
+
cls,
|
|
210
|
+
msg: object,
|
|
211
|
+
*args: object,
|
|
212
|
+
exc_info: _ExcInfoType = None,
|
|
213
|
+
stack_info: bool = False,
|
|
214
|
+
stacklevel: int = 1,
|
|
215
|
+
extra: collections.abc.Mapping[str, object] | None = None,
|
|
216
|
+
) -> None:
|
|
217
|
+
"""Log ``msg`` at ``INFO`` level on the class logger."""
|
|
218
|
+
return cls.logger.info( # type: ignore[no-any-return]
|
|
219
|
+
msg,
|
|
220
|
+
*args,
|
|
221
|
+
exc_info=exc_info,
|
|
222
|
+
stack_info=stack_info,
|
|
223
|
+
stacklevel=stacklevel,
|
|
224
|
+
extra=extra,
|
|
225
|
+
)
|
|
37
226
|
|
|
227
|
+
@decorators.wraps_classmethod(logging.Logger.warning)
|
|
38
228
|
@classmethod
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
229
|
+
def warning(
|
|
230
|
+
cls,
|
|
231
|
+
msg: object,
|
|
232
|
+
*args: object,
|
|
233
|
+
exc_info: _ExcInfoType = None,
|
|
234
|
+
stack_info: bool = False,
|
|
235
|
+
stacklevel: int = 1,
|
|
236
|
+
extra: collections.abc.Mapping[str, object] | None = None,
|
|
237
|
+
) -> None:
|
|
238
|
+
"""Log ``msg`` at ``WARNING`` level on the class logger."""
|
|
239
|
+
return cls.logger.warning( # type: ignore[no-any-return]
|
|
240
|
+
msg,
|
|
241
|
+
*args,
|
|
242
|
+
exc_info=exc_info,
|
|
243
|
+
stack_info=stack_info,
|
|
244
|
+
stacklevel=stacklevel,
|
|
245
|
+
extra=extra,
|
|
246
|
+
)
|
|
42
247
|
|
|
248
|
+
@decorators.wraps_classmethod(logging.Logger.error)
|
|
43
249
|
@classmethod
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
250
|
+
def error(
|
|
251
|
+
cls,
|
|
252
|
+
msg: object,
|
|
253
|
+
*args: object,
|
|
254
|
+
exc_info: _ExcInfoType = None,
|
|
255
|
+
stack_info: bool = False,
|
|
256
|
+
stacklevel: int = 1,
|
|
257
|
+
extra: collections.abc.Mapping[str, object] | None = None,
|
|
258
|
+
) -> None:
|
|
259
|
+
"""Log ``msg`` at ``ERROR`` level on the class logger."""
|
|
260
|
+
return cls.logger.error( # type: ignore[no-any-return]
|
|
261
|
+
msg,
|
|
262
|
+
*args,
|
|
263
|
+
exc_info=exc_info,
|
|
264
|
+
stack_info=stack_info,
|
|
265
|
+
stacklevel=stacklevel,
|
|
266
|
+
extra=extra,
|
|
267
|
+
)
|
|
47
268
|
|
|
269
|
+
@decorators.wraps_classmethod(logging.Logger.critical)
|
|
48
270
|
@classmethod
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
271
|
+
def critical(
|
|
272
|
+
cls,
|
|
273
|
+
msg: object,
|
|
274
|
+
*args: object,
|
|
275
|
+
exc_info: _ExcInfoType = None,
|
|
276
|
+
stack_info: bool = False,
|
|
277
|
+
stacklevel: int = 1,
|
|
278
|
+
extra: collections.abc.Mapping[str, object] | None = None,
|
|
279
|
+
) -> None:
|
|
280
|
+
"""Log ``msg`` at ``CRITICAL`` level on the class logger."""
|
|
281
|
+
return cls.logger.critical( # type: ignore[no-any-return]
|
|
282
|
+
msg,
|
|
283
|
+
*args,
|
|
284
|
+
exc_info=exc_info,
|
|
285
|
+
stack_info=stack_info,
|
|
286
|
+
stacklevel=stacklevel,
|
|
287
|
+
extra=extra,
|
|
288
|
+
)
|
|
52
289
|
|
|
290
|
+
@decorators.wraps_classmethod(logging.Logger.exception)
|
|
53
291
|
@classmethod
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
292
|
+
def exception(
|
|
293
|
+
cls,
|
|
294
|
+
msg: object,
|
|
295
|
+
*args: object,
|
|
296
|
+
exc_info: _ExcInfoType = None,
|
|
297
|
+
stack_info: bool = False,
|
|
298
|
+
stacklevel: int = 1,
|
|
299
|
+
extra: collections.abc.Mapping[str, object] | None = None,
|
|
300
|
+
) -> None:
|
|
301
|
+
"""Log ``msg`` at ``ERROR`` level with exception info attached."""
|
|
302
|
+
return cls.logger.exception( # type: ignore[no-any-return]
|
|
303
|
+
msg,
|
|
304
|
+
*args,
|
|
305
|
+
exc_info=exc_info,
|
|
306
|
+
stack_info=stack_info,
|
|
307
|
+
stacklevel=stacklevel,
|
|
308
|
+
extra=extra,
|
|
309
|
+
)
|
|
57
310
|
|
|
311
|
+
@decorators.wraps_classmethod(logging.Logger.log)
|
|
58
312
|
@classmethod
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
313
|
+
def log(
|
|
314
|
+
cls,
|
|
315
|
+
level: int,
|
|
316
|
+
msg: object,
|
|
317
|
+
*args: object,
|
|
318
|
+
exc_info: _ExcInfoType = None,
|
|
319
|
+
stack_info: bool = False,
|
|
320
|
+
stacklevel: int = 1,
|
|
321
|
+
extra: collections.abc.Mapping[str, object] | None = None,
|
|
322
|
+
) -> None:
|
|
323
|
+
"""Log ``msg`` at the integer ``level`` on the class logger."""
|
|
324
|
+
return cls.logger.log( # type: ignore[no-any-return]
|
|
325
|
+
level,
|
|
326
|
+
msg,
|
|
327
|
+
*args,
|
|
328
|
+
exc_info=exc_info,
|
|
329
|
+
stack_info=stack_info,
|
|
330
|
+
stacklevel=stacklevel,
|
|
331
|
+
extra=extra,
|
|
332
|
+
)
|
|
333
|
+
|
|
62
334
|
|
|
335
|
+
class Logged(LoggerBase):
|
|
336
|
+
"""Class which automatically adds a named logger to your class when
|
|
337
|
+
inheriting.
|
|
338
|
+
|
|
339
|
+
Adds easy access to debug, info, warning, error, exception and log methods
|
|
340
|
+
|
|
341
|
+
>>> class MyClass(Logged):
|
|
342
|
+
... def __init__(self):
|
|
343
|
+
... Logged.__init__(self)
|
|
344
|
+
|
|
345
|
+
>>> my_class = MyClass()
|
|
346
|
+
>>> my_class.debug('debug')
|
|
347
|
+
>>> my_class.info('info')
|
|
348
|
+
>>> my_class.warning('warning')
|
|
349
|
+
>>> my_class.error('error')
|
|
350
|
+
>>> my_class.exception('exception')
|
|
351
|
+
>>> my_class.log(0, 'log')
|
|
352
|
+
|
|
353
|
+
>>> my_class._Logged__get_name('spam')
|
|
354
|
+
'spam'
|
|
355
|
+
"""
|
|
356
|
+
|
|
357
|
+
logger: logging.Logger # pragma: no cover
|
|
358
|
+
|
|
359
|
+
@classmethod
|
|
360
|
+
def __get_name(cls, *name_parts: str) -> str:
|
|
361
|
+
"""Build the dotted logger name via ``LoggerBase``'s joiner."""
|
|
362
|
+
return typing.cast(
|
|
363
|
+
str,
|
|
364
|
+
LoggerBase._LoggerBase__get_name(*name_parts), # type: ignore[attr-defined] # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType, reportAttributeAccessIssue]
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
def __new__(
|
|
368
|
+
cls, *args: typing.Any, **kwargs: typing.Any
|
|
369
|
+
) -> typing_extensions.Self:
|
|
370
|
+
"""
|
|
371
|
+
Create a new instance of the class and initialize the logger.
|
|
372
|
+
|
|
373
|
+
The logger is named using the module and class name.
|
|
374
|
+
|
|
375
|
+
Args:
|
|
376
|
+
*args: Variable length argument list.
|
|
377
|
+
**kwargs: Arbitrary keyword arguments.
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
An instance of the class.
|
|
381
|
+
"""
|
|
382
|
+
cls.logger = logging.getLogger(
|
|
383
|
+
cls.__get_name(cls.__module__, cls.__name__)
|
|
384
|
+
)
|
|
385
|
+
return super().__new__(cls)
|
python_utils/loguru.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module provides a `Logurud` class that integrates the `loguru` logger
|
|
3
|
+
with the base logging functionality defined in `logger_module.LoggerBase`.
|
|
4
|
+
|
|
5
|
+
Classes:
|
|
6
|
+
Logurud: A class that extends `LoggerBase` and uses `loguru` for logging.
|
|
7
|
+
|
|
8
|
+
Usage example:
|
|
9
|
+
>>> from python_utils.loguru import Logurud
|
|
10
|
+
>>> class MyClass(Logurud):
|
|
11
|
+
... def __init__(self):
|
|
12
|
+
... Logurud.__init__(self)
|
|
13
|
+
>>> my_class = MyClass()
|
|
14
|
+
>>> my_class.logger.info('This is an info message')
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import typing
|
|
20
|
+
|
|
21
|
+
import loguru
|
|
22
|
+
|
|
23
|
+
from . import logger as logger_module
|
|
24
|
+
|
|
25
|
+
__all__ = ['Logurud']
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Logurud(logger_module.LoggerBase):
|
|
29
|
+
"""
|
|
30
|
+
A class that extends `LoggerBase` and uses `loguru` for logging.
|
|
31
|
+
|
|
32
|
+
Attributes:
|
|
33
|
+
logger (loguru.Logger): The `loguru` logger instance.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
logger: loguru.Logger
|
|
37
|
+
|
|
38
|
+
def __new__(cls, *args: typing.Any, **kwargs: typing.Any) -> Logurud:
|
|
39
|
+
"""
|
|
40
|
+
Creates a new instance of `Logurud` and initializes the `loguru`
|
|
41
|
+
logger.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
*args (typing.Any): Variable length argument list.
|
|
45
|
+
**kwargs (typing.Any): Arbitrary keyword arguments.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Logurud: A new instance of `Logurud`.
|
|
49
|
+
"""
|
|
50
|
+
# `logger` is already declared at class scope; assign without
|
|
51
|
+
# re-annotating to avoid an obscured-declaration error.
|
|
52
|
+
cls.logger = loguru.logger.opt(depth=1)
|
|
53
|
+
return super().__new__(cls)
|