Passed
Push — main ( f8490e...e59e89 )
by Douglas
01:39
created

InterceptHandler.emit()   A

Complexity

Conditions 3

Size

Total Lines 12
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 10
nop 2
dl 0
loc 12
rs 9.9
c 0
b 0
f 0
1
"""
2
Loguru logging extension that, configurably:
3
- redirects built-in logging to your loguru logger
4
- remembers the handlers added
5
- auto-detects compression and serialization from filenames
6
- includes extras in non-serializing handlers
7
- has a few alternative (possibly better) choices for colors, icons, and levels
8
- will complain when you do really stupid things
9
- has a convenient notation for configuring from a CLI
10
  (e.g. ``--stderr debug --log :INFO:run.log.gz``)
11
- mandes utf-8
12
"""
13
from __future__ import annotations
14
15
import abc
16
import logging
17
import os
18
import sys
19
from dataclasses import dataclass
20
from inspect import cleandoc
21
from pathlib import Path
22
from typing import (
23
    AbstractSet,
24
    Any,
25
    Callable,
26
    Generic,
27
    Mapping,
28
    Optional,
29
    Sequence,
30
    TextIO,
31
    Type,
32
    TypeVar,
33
    Union,
34
)
35
36
import loguru._defaults as _defaults
0 ignored issues
show
introduced by
Unable to import 'loguru._defaults'
Loading history...
37
38
# noinspection PyProtectedMember
39
import regex
0 ignored issues
show
introduced by
Unable to import 'regex'
Loading history...
40
from loguru import logger
0 ignored issues
show
introduced by
Unable to import 'loguru'
Loading history...
41
42
# noinspection PyProtectedMember
43
# noinspection PyProtectedMember
44
from loguru._logger import Logger
0 ignored issues
show
introduced by
Unable to import 'loguru._logger'
Loading history...
45
46
from pocketutils.core.exceptions import IllegalStateError, XValueError
47
48
Formatter = Union[str, Callable[[Mapping[str, Any]], str]]
49
_FMT = cleandoc(
50
    r"""
51
    <bold>{time:YYYY-MM-DD HH:mm:ss.SSS}</bold> |
52
    <level>{level: <8}</level> |
53
    <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan>
54
    — <level>{message}[EXTRA]</level>
55
    {exception}
56
    """
57
).replace("\n", " ")
58
59
60
class FormatFactory:
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
61
    @classmethod
62
    def with_extras(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
63
        cls,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
64
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
65
        fmt: str = _FMT,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
66
        sep: str = "; ",
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
67
        eq_sign: str = " ",
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
68
    ) -> Callable[[Mapping[str, Any]], str]:
69
        def FMT(record: Mapping[str, Any]) -> str:
0 ignored issues
show
Coding Style Naming introduced by
Function name "FMT" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
70
            extra = sep.join([e + eq_sign + "{extra[" + e + "]}" for e in record["extra"].keys()])
71
            if len(extra) > 0:
72
                extra = f" [ {extra} ]"
73
            return fmt.replace("[EXTRA]", extra) + os.linesep
74
75
        return FMT
76
77
    @classmethod
78
    def plain(cls, *, fmt: str = _FMT) -> Callable[[Mapping[str, Any]], str]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
79
        def FMT(record: Mapping[str, Any]) -> str:
0 ignored issues
show
Coding Style Naming introduced by
Function name "FMT" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Unused Code introduced by
The argument record seems to be unused.
Loading history...
80
            return fmt.replace("[EXTRA]", "") + os.linesep
81
82
        return FMT
83
84
85
class _SENTINEL:
86
    pass
87
88
89
T = TypeVar("T", covariant=True, bound=Logger)
0 ignored issues
show
Coding Style Naming introduced by
Class name "T" doesn't conform to PascalCase naming style ('[^\\W\\da-z][^\\W_]+$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
90
Z = TypeVar("Z", covariant=True, bound=Logger)
0 ignored issues
show
Coding Style Naming introduced by
Class name "Z" doesn't conform to PascalCase naming style ('[^\\W\\da-z][^\\W_]+$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
91
92
93
_LOGGER_ARG_PATTERN = regex.compile(r"(?:([a-zA-Z]+):)?(.*)", flags=regex.V1)
94
log_compressions = {
95
    ".xz",
96
    ".lzma",
97
    ".gz",
98
    ".zip",
99
    ".bz2",
100
    ".tar",
101
    ".tar.gz",
102
    ".tar.bz2",
103
    ".tar.xz",
104
}
105
valid_log_suffixes = {
106
    *{f".log{c}" for c in log_compressions},
107
    *{f".txt{c}" for c in log_compressions},
108
    *{f".json{c}" for c in log_compressions},
109
}
110
111
112
class FancyLoguruDefaults:
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
113
    levels_built_in = dict(
114
        TRACE=_defaults.LOGURU_TRACE_NO,
115
        DEBUG=_defaults.LOGURU_DEBUG_NO,
116
        INFO=_defaults.LOGURU_INFO_NO,
117
        WARNING=_defaults.LOGURU_WARNING_NO,
118
        ERROR=_defaults.LOGURU_ERROR_NO,
119
        CRITICAL=_defaults.LOGURU_CRITICAL_NO,
120
    )
121
122
    # the levels for caution and notice are DEFINED here
123
    # trace and success must match loguru's
124
    # and the rest must match logging's
125
    # note that most of these alternate between informative and problematic
126
    # i.e. info (ok), caution (bad), success (ok), warning (bad), notice (ok), error (bad)
127
    levels_extended = {
128
        **levels_built_in,
129
        **dict(
130
            CAUTION=23,
131
            SUCCESS=25,
132
            NOTICE=35,
133
        ),
134
    }
135
136
    colors_built_in = dict(
137
        TRACE=_defaults.LOGURU_TRACE_COLOR,
138
        DEBUG=_defaults.LOGURU_DEBUG_COLOR,
139
        INFO=_defaults.LOGURU_INFO_COLOR,
140
        SUCCESS=_defaults.LOGURU_SUCCESS_COLOR,
141
        WARNING=_defaults.LOGURU_WARNING_COLOR,
142
        ERROR=_defaults.LOGURU_ERROR_COLOR,
143
        CRITICAL=_defaults.LOGURU_CRITICAL_COLOR,
144
    )
145
146
    colors_extended = dict(
147
        **colors_built_in,
148
        CAUTION=_defaults.LOGURU_WARNING_COLOR,
149
        NOTICE=_defaults.LOGURU_INFO_COLOR,
150
    )
151
152
    colors_red_green_safe = dict(
153
        TRACE="<dim>",
154
        DEBUG="<dim>",
155
        INFO="<bold>",
156
        CAUTION="<yellow>",
157
        SUCCESS="<blue>",
158
        WARNING="<yellow>",
159
        NOTICE="<blue>",
160
        ERROR="<red>",
161
        CRITICAL="<red>",
162
    )
163
164
    icons_built_in = dict(
165
        TRACE=_defaults.LOGURU_TRACE_ICON,
166
        DEBUG=_defaults.LOGURU_DEBUG_ICON,
167
        INFO=_defaults.LOGURU_INFO_ICON,
168
        SUCCESS=_defaults.LOGURU_SUCCESS_ICON,
169
        WARNING=_defaults.LOGURU_WARNING_ICON,
170
        ERROR=_defaults.LOGURU_ERROR_ICON,
171
        CRITICAL=_defaults.LOGURU_CRITICAL_ICON,
172
    )
173
174
    icons_extended = dict(
175
        **icons_built_in,
176
        CAUTION="⚐",
177
        NOTICE="★",
178
    )
179
180
    level: str = "INFO"
181
182
    fmt_simplified = FormatFactory.with_extras()
183
    fmt_built_in = FormatFactory.plain()
184
    fmt_built_in_raw = _defaults.LOGURU_FORMAT
185
186
    aliases = dict(NONE=None, NO=None, OFF=None, VERBOSE="INFO", QUIET="ERROR")
187
188
189
@dataclass(frozen=True, repr=True, order=True)
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
190
class HandlerInfo:
191
    hid: int
192
    path: Optional[Path]
193
    level: Optional[str]
194
    fmt: Formatter
195
196
197
@dataclass(frozen=False, repr=True)
198
class _HandlerInfo:
199
    hid: int
200
    sink: Any
201
    level: Optional[int]
202
    fmt: Formatter
203
204
205
@dataclass(frozen=True, repr=True, order=True)
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
206
class LogSinkInfo:
207
    path: Path
208
    base: Path
209
    suffix: str
210
    serialize: bool
211
    compression: Optional[str]
212
213
    @classmethod
214
    def guess(cls, path: Union[str, Path]) -> LogSinkInfo:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
215
        path = Path(path)
216
        base, compression = path.name, None
217
        for c in log_compressions:
0 ignored issues
show
Coding Style Naming introduced by
Variable name "c" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
218
            if path.name.endswith(c):
219
                base, compression = path.name[: -len(c)], c
220
        if not [base.endswith(s) for s in [".json", ".log", ".txt"]]:
221
            raise XValueError(
222
                f"Log filename {path.name} is not .json, .log, .txt, or a compressed variant"
223
            )
224
        return LogSinkInfo(
225
            path=path,
226
            base=path.parent / base,
227
            suffix=compression,
228
            serialize=base.endswith(".json"),
229
            compression=compression,
230
        )
231
232
233
class InterceptHandler(logging.Handler):
234
    """
235
    Redirects standard logging to loguru.
236
    """
237
238
    def emit(self, record):
239
        # Get corresponding Loguru level if it exists
240
        try:
241
            level = logger.level(record.levelname).name
242
        except ValueError:
243
            level = record.levelno
244
        # Find caller from where originated the logged message
245
        frame, depth = logging.currentframe(), 2
246
        while frame.f_code.co_filename == logging.__file__:
247
            frame = frame.f_back
248
            depth += 1
249
        logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())
250
251
252
class FancyLoguru(Generic[T]):
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
253
    def __init__(self, log: T = logger):
254
        self._levels = dict(FancyLoguruDefaults.levels_built_in)
255
        self._logger = log
256
        self._main: _HandlerInfo = None
257
        self._paths = {}
258
        self._aliases = dict(FancyLoguruDefaults.aliases)
259
260
    @staticmethod
261
    def new(t: Type[Z]) -> FancyLoguru[Z]:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "t" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
introduced by
Missing function or method docstring
Loading history...
Unused Code introduced by
The argument t seems to be unused.
Loading history...
262
        return FancyLoguru[Z](logger)
263
264
    @property
265
    def logger(self) -> T:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
266
        return self._logger
267
268
    @property
269
    def levels(self) -> Mapping[str, int]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
270
        return dict(self._levels)
271
272
    @property
273
    def aliases(self) -> Mapping[str, str]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
274
        return self._aliases
275
276
    @property
277
    def main(self) -> Optional[HandlerInfo]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
278
        if self._main is None:
279
            return None
280
        return HandlerInfo(
281
            hid=self._main.hid, level=self._main.level, fmt=self._main.fmt, path=None
282
        )
283
284
    @property
285
    def paths(self) -> AbstractSet[HandlerInfo]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
286
        if self._main is None:
287
            return set()
288
        return {HandlerInfo(hid=h.hid, level=h.level, fmt=h.fmt, path=h.sink) for h in self._paths}
289
290
    def config_levels(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
291
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
292
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
293
        levels: Mapping[str, int] = _SENTINEL,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
294
        colors: Mapping[str, str] = _SENTINEL,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
295
        icons: Mapping[str, str] = _SENTINEL,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
296
        aliases: Mapping[str, str] = _SENTINEL,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
297
        add_methods: bool = True,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
298
    ) -> __qualname__:
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable '__qualname__'
Loading history...
299
        levels = FancyLoguruDefaults.levels_extended if levels is _SENTINEL else levels
300
        colors = FancyLoguruDefaults.colors_extended if colors is _SENTINEL else colors
301
        icons = FancyLoguruDefaults.icons_extended if icons is _SENTINEL else icons
302
        aliases = FancyLoguruDefaults.aliases if aliases is _SENTINEL else aliases
303
        for k, v in levels.items():
0 ignored issues
show
Coding Style Naming introduced by
Variable name "v" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
304
            self.config_level(
305
                k,
306
                v,
307
                color=colors.get(k, _SENTINEL),
308
                icon=icons.get(k, _SENTINEL),
309
                add_methods=add_methods,
310
            )
311
        self._aliases = dict(aliases)
312
        return self
313
314
    def init(
315
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
316
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
317
        level: str = FancyLoguruDefaults.level,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
318
        sink=sys.stderr,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
319
        fmt: Formatter = FancyLoguruDefaults.fmt_built_in,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
320
        intercept: bool = True,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
321
    ) -> __qualname__:
322
        """
323
        Sets an initial configuration.
324
        """
325
        if intercept:
326
            # noinspection PyArgumentList
327
            logging.basicConfig(handlers=[InterceptHandler()], level=0, encoding="utf-8")
328
        logger.remove(None)  # get rid of the built-in handler
329
        self.config_main(level=level, sink=sink, fmt=fmt)
330
        return self
331
332
    def config_level(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
333
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
334
        name: str,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
335
        level: int,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
336
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
337
        color: Union[None, str, _SENTINEL] = _SENTINEL,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
338
        icon: Union[None, str, _SENTINEL] = _SENTINEL,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
339
        add_methods: bool = True,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
340
        replace: bool = True,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
341
    ) -> __qualname__:
342
        try:
343
            data = logger.level(name)
344
        except ValueError:
345
            data = None
346
        if data is None:
347
            logger.level(
348
                name,
349
                no=level,
350
                color=None if color is _SENTINEL else color,
351
                icon=None if icon is _SENTINEL else icon,
352
            )
353
        elif replace:
354
            if level != data.no:  # loguru doesn't check whether they're eq; it just errors
355
                raise IllegalStateError(f"Cannot set level={level}!={data.no} for {name}")
356
            logger.level(
357
                name,
358
                color=data.color if color is _SENTINEL else color,
359
                icon=data.icon if icon is _SENTINEL else icon,
360
            )
361
        if add_methods:
362
            self._set_logger_levels([name])
363
        return self
364
365
    def _set_logger_levels(self, levels: Sequence[str]) -> __qualname__:
366
        for level in levels:
367
368
            def _x(__message: str, *args, **kwargs):
369
                logger.log(level, __message, *args, **kwargs)
0 ignored issues
show
introduced by
The variable level does not seem to be defined in case the for loop on line 366 is not entered. Are you sure this can never be the case?
Loading history...
introduced by
Cell variable level defined in loop
Loading history...
370
371
            _x.__name__ = level.lower()
372
            if not hasattr(self._logger, level.lower()):
373
                setattr(self._logger, level.lower(), _x)
374
        return self
375
376
    def config_main(
377
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
378
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
379
        sink: TextIO = _SENTINEL,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
380
        level: Optional[str] = _SENTINEL,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
381
        fmt: Formatter = _SENTINEL,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
382
    ) -> __qualname__:
383
        """
384
        Sets the logging level for the main handler (normally stderr).
385
        """
386
        if level is not None and level is not _SENTINEL:
387
            level = level.upper()
388
        if self._main is None:
389
            self._main = _HandlerInfo(
390
                hid=-1,
391
                sink=sys.stderr,
392
                level=self._levels[FancyLoguruDefaults.level],
393
                fmt=FancyLoguruDefaults.fmt_built_in,
394
            )
395
        else:
396
            try:
397
                logger.remove(self._main.hid)
398
            except ValueError:
399
                logger.error(f"Cannot remove handler {self._main.hid}")
400
        self._main.level = self._main.level if level is _SENTINEL else level
401
        self._main.sink = self._main.sink if sink is _SENTINEL else sink
402
        self._main.fmt = self._main.fmt if fmt is _SENTINEL else fmt
403
        self._main.hid = logger.add(self._main.sink, level=self._main.level, format=self._main.fmt)
404
        return self
405
406
    def remove_path(self, path: Path) -> __qualname__:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
407
        for k, h in self._paths.items():
0 ignored issues
show
Coding Style Naming introduced by
Variable name "h" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
408
            if h.path.resolve() == path.resolve():
409
                h.level = None
410
                try:
411
                    logger.remove(k)
412
                except ValueError:
413
                    logger.error(f"Cannot remove handler {k} to {path}")
414
415
    def add_path(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
416
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
417
        path: Path,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
418
        level: str = _SENTINEL,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
419
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
420
        fmt: str = _SENTINEL,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
421
    ) -> __qualname__:
422
        if level is _SENTINEL and self._main is None:
423
            level = FancyLoguruDefaults.level
424
        elif level is _SENTINEL:
425
            level = self._main.level
426
        level = level.upper()
427
        if fmt is _SENTINEL and self._main is None:
428
            fmt = FancyLoguruDefaults.fmt_built_in
429
        elif fmt is _SENTINEL:
430
            fmt = self._main.fmt
431
        if isinstance(fmt, str):
432
            fmt = FormatFactory.with_extras(fmt=fmt)
433
        ell = self._levels[level]
434
        info = LogSinkInfo.guess(path)
435
        x = self._logger.add(
0 ignored issues
show
Coding Style Naming introduced by
Variable name "x" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
436
            str(info.base),
437
            format=fmt,
438
            level=level,
439
            compression=info.compression,
440
            serialize=info.serialize,
441
            backtrace=True,
442
            diagnose=True,
443
            enqueue=True,
444
            encoding="utf-8",
445
        )
446
        self._paths[x] = _HandlerInfo(hid=x, sink=info.base, level=ell, fmt=fmt)
447
        return self
448
449
    def from_cli(
450
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
451
        path: Union[None, str, Path] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
452
        main: Optional[str] = FancyLoguruDefaults.level,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
453
    ) -> __qualname__:
454
        """
455
        This function controls logging set via command-line.
456
457
        Args:
458
            main: The level for stderr
459
            path: If set, the path to a file. Can be prefixed with ``:level:`` to set the level
460
                  (e.g. ``:INFO:mandos-run.log.gz``). Can serialize to JSON if .json is used
461
                  instead of .log or .txt.
462
        """
463
        if main is None:
464
            main = FancyLoguruDefaults.level
465
        main = self._aliases.get(main.upper(), main.upper())
466
        if main not in FancyLoguruDefaults.levels_extended:
467
            _permitted = ", ".join(
468
                [*FancyLoguruDefaults.levels_extended, *FancyLoguruDefaults.aliases.keys()]
469
            )
470
            raise XValueError(f"{main.lower()} not a permitted log level (allowed: {_permitted}")
471
        self.config_main(level=main)
472
        if path is not None or len(str(path)) == 0:
473
            match = _LOGGER_ARG_PATTERN.match(str(path))
474
            path_level = "DEBUG" if match.group(1) is None else match.group(1)
475
            path = Path(match.group(2))
476
            self.add_path(path, path_level)
477
            logger.info(f"Added logger to {path} at level {path_level}")
478
        logger.info(f"Set main log level to {main}")
479
        return self
480
481
    __call__ = from_cli
482
483
484
class LoggerWithNotice(Logger, metaclass=abc.ABCMeta):
485
    """
486
    A wrapper that has fake methods to trick static analysis.
487
    """
488
489
    def notice(self, __message: str, *args, **kwargs):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
490
        raise NotImplementedError()  # not real
491
492
493
class LoggerWithCaution(Logger, metaclass=abc.ABCMeta):
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
494
    def caution(self, __message: str, *args, **kwargs):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
495
        raise NotImplementedError()  # not real
496
497
498
class LoggerWithCautionAndNotice(LoggerWithNotice, LoggerWithCaution, metaclass=abc.ABCMeta):
499
    """
500
    A wrapper that has fake methods to trick static analysis.
501
    """
502
503
504
class FancyLoguruExtras:
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
505
    @classmethod
506
    def rewire_streams_to_utf8(cls) -> None:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
507
        sys.stderr.reconfigure(encoding="utf-8")
508
        sys.stdout.reconfigure(encoding="utf-8")
509
        sys.stdin.reconfigure(encoding="utf-8")
510
511
    @classmethod
512
    def built_in(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
513
        cls,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
514
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
515
        level: str = FancyLoguruDefaults.level,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
516
        rewire: bool = False,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
517
    ) -> FancyLoguru[Logger]:
518
        if rewire:
519
            cls.rewire_streams_to_utf8()
520
        return FancyLoguru.new(Logger).config_levels().init(level=level)
521
522
    @classmethod
523
    def extended(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
524
        cls,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
525
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
526
        level: str = FancyLoguruDefaults.level,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
527
        simplify_fmt: bool = True,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
528
        red_green_safe: bool = True,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
529
        rewire: bool = False,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
530
    ) -> FancyLoguru[LoggerWithCautionAndNotice]:
531
        if rewire:
532
            cls.rewire_streams_to_utf8()
533
        return (
534
            FancyLoguru.new(LoggerWithCautionAndNotice)
535
            .config_levels(
536
                levels=FancyLoguruDefaults.levels_extended,
537
                colors=(
538
                    FancyLoguruDefaults.colors_red_green_safe
539
                    if red_green_safe
540
                    else FancyLoguruDefaults.colors_extended
541
                ),
542
                icons=FancyLoguruDefaults.icons_extended,
543
            )
544
            .init(
545
                level=level,
546
                fmt=(
547
                    FancyLoguruDefaults.fmt_simplified
548
                    if simplify_fmt
549
                    else FancyLoguruDefaults.fmt_built_in
550
                ),
551
            )
552
        )
553
554
555
if __name__ == "__main__":
556
    _logger = FancyLoguruExtras.extended().logger
557
    _logger.caution("hello")
558
559
560
__all__ = [
561
    "FancyLoguruDefaults",
562
    "FancyLoguru",
563
    "FancyLoguruExtras",
564
    "HandlerInfo",
565
    "Logger",
566
    "LoggerWithCautionAndNotice",
567
]
568