Passed
Push — main ( aaa236...57139e )
by Douglas
03:39 queued 02:06
created

FancyLoguru.remove_paths()   A

Complexity

Conditions 3

Size

Total Lines 7
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 7
nop 1
dl 0
loc 7
rs 10
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
    TextIO,
30
    TypeVar,
31
    Union,
32
    Type,
33
    Sequence,
34
    MutableMapping,
35
)
36
37
# noinspection PyProtectedMember
38
import regex
0 ignored issues
show
introduced by
Unable to import 'regex'
Loading history...
39
from loguru import logger
0 ignored issues
show
introduced by
Unable to import 'loguru'
Loading history...
40
import loguru._defaults as _defaults
0 ignored issues
show
introduced by
Unable to import 'loguru._defaults'
Loading history...
41
42
# noinspection PyProtectedMember
43
from loguru._logger import Logger
0 ignored issues
show
introduced by
Unable to import 'loguru._logger'
Loading history...
44
45
# noinspection PyProtectedMember
46
from loguru._logger import Logger
0 ignored issues
show
Unused Code introduced by
The import Logger was already done on line 43. You should be able to
remove this line.
Loading history...
introduced by
Unable to import 'loguru._logger'
Loading history...
47
48
from pocketutils.core.exceptions import IllegalStateError, XValueError
49
50
Formatter = Union[str, Callable[[Mapping[str, Any]], str]]
51
_FMT = cleandoc(
52
    r"""
53
    <bold>{time:YYYY-MM-DD HH:mm:ss.SSS}</bold> |
54
    <level>{level: <8}</level> |
55
    <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan>
56
    — <level>{message}[EXTRA]</level>
57
    {exception}
58
    """
59
).replace("\n", " ")
60
61
62
class FormatFactory:
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
63
    @classmethod
64
    def with_extras(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
65
        cls,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
66
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
67
        fmt: str = _FMT,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
68
        sep: str = "; ",
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
69
        eq_sign: str = " ",
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
70
    ) -> Callable[[Mapping[str, Any]], str]:
71
        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...
72
            extra = sep.join([e + eq_sign + "{extra[" + e + "]}" for e in record["extra"].keys()])
73
            if len(extra) > 0:
74
                extra = f" [ {extra} ]"
75
            return fmt.replace("[EXTRA]", extra) + os.linesep
76
77
        return FMT
78
79
    @classmethod
80
    def plain(cls, *, fmt: str = _FMT) -> Callable[[Mapping[str, Any]], str]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
81
        def FMT(record: Mapping[str, Any]) -> str:
0 ignored issues
show
Unused Code introduced by
The argument record seems to be unused.
Loading history...
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...
82
            return fmt.replace("[EXTRA]", "") + os.linesep
83
84
        return FMT
85
86
87
class _SENTINEL:
88
    pass
89
90
91
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...
92
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...
93
94
95
_LOGGER_ARG_PATTERN = regex.compile(r"(?:([a-zA-Z]+):)?(.*)", flags=regex.V1)
96
log_compressions = {
97
    ".xz",
98
    ".lzma",
99
    ".gz",
100
    ".zip",
101
    ".bz2",
102
    ".tar",
103
    ".tar.gz",
104
    ".tar.bz2",
105
    ".tar.xz",
106
}
107
valid_log_suffixes = {
108
    *{f".log{c}" for c in log_compressions},
109
    *{f".txt{c}" for c in log_compressions},
110
    *{f".json{c}" for c in log_compressions},
111
}
112
113
114
class FancyLoguruDefaults:
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
115
    levels_built_in = dict(
116
        TRACE=_defaults.LOGURU_TRACE_NO,
117
        DEBUG=_defaults.LOGURU_DEBUG_NO,
118
        INFO=_defaults.LOGURU_INFO_NO,
119
        WARNING=_defaults.LOGURU_WARNING_NO,
120
        ERROR=_defaults.LOGURU_ERROR_NO,
121
        CRITICAL=_defaults.LOGURU_CRITICAL_NO,
122
    )
123
124
    # the levels for caution and notice are DEFINED here
125
    # trace and success must match loguru's
126
    # and the rest must match logging's
127
    # note that most of these alternate between informative and problematic
128
    # i.e. info (ok), caution (bad), success (ok), warning (bad), notice (ok), error (bad)
129
    levels_extended = {
130
        **levels_built_in,
131
        **dict(
132
            CAUTION=23,
133
            SUCCESS=25,
134
            NOTICE=35,
135
        ),
136
    }
137
138
    colors_built_in = dict(
139
        TRACE=_defaults.LOGURU_TRACE_COLOR,
140
        DEBUG=_defaults.LOGURU_DEBUG_COLOR,
141
        INFO=_defaults.LOGURU_INFO_COLOR,
142
        SUCCESS=_defaults.LOGURU_SUCCESS_COLOR,
143
        WARNING=_defaults.LOGURU_WARNING_COLOR,
144
        ERROR=_defaults.LOGURU_ERROR_COLOR,
145
        CRITICAL=_defaults.LOGURU_CRITICAL_COLOR,
146
    )
147
148
    colors_extended = dict(
149
        **colors_built_in,
150
        CAUTION=_defaults.LOGURU_WARNING_COLOR,
151
        NOTICE=_defaults.LOGURU_INFO_COLOR,
152
    )
153
154
    colors_red_green_safe = dict(
155
        TRACE="<dim>",
156
        DEBUG="<dim>",
157
        INFO="<bold>",
158
        CAUTION="<yellow>",
159
        SUCCESS="<blue>",
160
        WARNING="<yellow>",
161
        NOTICE="<blue>",
162
        ERROR="<red>",
163
        CRITICAL="<red>",
164
    )
165
166
    icons_built_in = dict(
167
        TRACE=_defaults.LOGURU_TRACE_ICON,
168
        DEBUG=_defaults.LOGURU_DEBUG_ICON,
169
        INFO=_defaults.LOGURU_INFO_ICON,
170
        SUCCESS=_defaults.LOGURU_SUCCESS_ICON,
171
        WARNING=_defaults.LOGURU_WARNING_ICON,
172
        ERROR=_defaults.LOGURU_ERROR_ICON,
173
        CRITICAL=_defaults.LOGURU_CRITICAL_ICON,
174
    )
175
176
    icons_extended = dict(
177
        **icons_built_in,
178
        CAUTION="⚐",
179
        NOTICE="★",
180
    )
181
182
    level: str = "INFO"
183
184
    fmt_simplified = FormatFactory.with_extras()
185
    fmt_built_in = FormatFactory.plain()
186
    fmt_built_in_raw = _defaults.LOGURU_FORMAT
187
188
    aliases = dict(NONE=None, NO=None, OFF=None, VERBOSE="INFO", QUIET="ERROR")
189
190
191
@dataclass(frozen=True, repr=True, order=True)
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
192
class HandlerInfo:
193
    hid: int
194
    path: Optional[Path]
195
    level: Optional[str]
196
    fmt: Formatter
197
198
199
@dataclass(frozen=False, repr=True)
200
class _HandlerInfo:
201
    hid: int
202
    sink: Any
203
    level: Optional[int]
204
    fmt: Formatter
205
206
207
@dataclass(frozen=True, repr=True, order=True)
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
208
class LogSinkInfo:
209
    path: Path
210
    base: Path
211
    suffix: str
212
    serialize: bool
213
    compression: Optional[str]
214
215
    @classmethod
216
    def guess(cls, path: Union[str, Path]) -> LogSinkInfo:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
217
        path = Path(path)
218
        base, compression = path.name, None
219
        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...
220
            if path.name.endswith(c):
221
                base, compression = path.name[: -len(c)], c
222
        if not [base.endswith(s) for s in [".json", ".log", ".txt"]]:
223
            raise XValueError(
224
                f"Log filename {path.name} is not .json, .log, .txt, or a compressed variant"
225
            )
226
        return LogSinkInfo(
227
            path=path,
228
            base=path.parent / base,
229
            suffix=compression,
230
            serialize=base.endswith(".json"),
231
            compression=compression,
232
        )
233
234
235
class InterceptHandler(logging.Handler):
236
    """
237
    Redirects standard logging to loguru.
238
    """
239
240
    def emit(self, record):
241
        # Get corresponding Loguru level if it exists
242
        try:
243
            level = logger.level(record.levelname).name
244
        except ValueError:
245
            level = record.levelno
246
        # Find caller from where originated the logged message
247
        frame, depth = logging.currentframe(), 2
248
        while frame.f_code.co_filename == logging.__file__:
249
            frame = frame.f_back
250
            depth += 1
251
        logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())
252
253
254
class FancyLoguru(Generic[T]):
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
255
    def __init__(self, log: T = logger):
256
        self._levels = dict(FancyLoguruDefaults.levels_built_in)
257
        self._logger = log
258
        self._main: _HandlerInfo = None
259
        self._paths: MutableMapping[int, _HandlerInfo] = {}
260
        self._aliases = dict(FancyLoguruDefaults.aliases)
261
262
    @staticmethod
263
    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...
264
        return FancyLoguru[Z](logger)
265
266
    @property
267
    def logger(self) -> T:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
268
        return self._logger
269
270
    @property
271
    def levels(self) -> Mapping[str, int]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
272
        return dict(self._levels)
273
274
    @property
275
    def aliases(self) -> Mapping[str, str]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
276
        return self._aliases
277
278
    @property
279
    def main(self) -> Optional[HandlerInfo]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
280
        if self._main is None:
281
            return None
282
        return HandlerInfo(
283
            hid=self._main.hid, level=self._main.level, fmt=self._main.fmt, path=None
284
        )
285
286
    @property
287
    def paths(self) -> AbstractSet[HandlerInfo]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
288
        if self._main is None:
289
            return set()
290
        return {HandlerInfo(hid=h.hid, level=h.level, fmt=h.fmt, path=h.sink) for h in self._paths}
291
292
    def config_levels(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
293
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
294
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
295
        levels: Mapping[str, int] = _SENTINEL,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
296
        colors: Mapping[str, str] = _SENTINEL,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
297
        icons: Mapping[str, str] = _SENTINEL,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
298
        aliases: Mapping[str, str] = _SENTINEL,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
299
        add_methods: bool = True,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
300
    ) -> __qualname__:
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable '__qualname__'
Loading history...
301
        levels = FancyLoguruDefaults.levels_extended if levels is _SENTINEL else levels
302
        colors = FancyLoguruDefaults.colors_extended if colors is _SENTINEL else colors
303
        icons = FancyLoguruDefaults.icons_extended if icons is _SENTINEL else icons
304
        aliases = FancyLoguruDefaults.aliases if aliases is _SENTINEL else aliases
305
        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...
306
            self.config_level(
307
                k,
308
                v,
309
                color=colors.get(k, _SENTINEL),
310
                icon=icons.get(k, _SENTINEL),
311
                add_methods=add_methods,
312
            )
313
        self._aliases = dict(aliases)
314
        return self
315
316
    def init(
317
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
318
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
319
        level: str = FancyLoguruDefaults.level,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
320
        sink=sys.stderr,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
321
        fmt: Formatter = FancyLoguruDefaults.fmt_built_in,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
322
        intercept: bool = True,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
323
    ) -> __qualname__:
324
        """
325
        Sets an initial configuration.
326
        """
327
        if intercept:
328
            # noinspection PyArgumentList
329
            logging.basicConfig(handlers=[InterceptHandler()], level=0, encoding="utf-8")
330
        logger.remove(None)  # get rid of the built-in handler
331
        self.config_main(level=level, sink=sink, fmt=fmt)
332
        return self
333
334
    def config_level(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
335
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
336
        name: str,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
337
        level: int,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
338
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
339
        color: Union[None, str, _SENTINEL] = _SENTINEL,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
340
        icon: Union[None, str, _SENTINEL] = _SENTINEL,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
341
        add_methods: bool = True,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
342
        replace: bool = True,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
343
    ) -> __qualname__:
344
        try:
345
            data = logger.level(name)
346
        except ValueError:
347
            data = None
348
        if data is None:
349
            logger.level(
350
                name,
351
                no=level,
352
                color=None if color is _SENTINEL else color,
353
                icon=None if icon is _SENTINEL else icon,
354
            )
355
        elif replace:
356
            if level != data.no:  # loguru doesn't check whether they're eq; it just errors
357
                raise IllegalStateError(f"Cannot set level={level}!={data.no} for {name}")
358
            logger.level(
359
                name,
360
                color=data.color if color is _SENTINEL else color,
361
                icon=data.icon if icon is _SENTINEL else icon,
362
            )
363
        if add_methods:
364
            self._set_logger_levels([name])
365
        return self
366
367
    def _set_logger_levels(self, levels: Sequence[str]) -> __qualname__:
368
        for level in levels:
369
370
            def _x(__message: str, *args, **kwargs):
371
                logger.log(level, __message, *args, **kwargs)
0 ignored issues
show
introduced by
Cell variable level defined in loop
Loading history...
introduced by
The variable level does not seem to be defined in case the for loop on line 368 is not entered. Are you sure this can never be the case?
Loading history...
372
373
            _x.__name__ = level.lower()
374
            if not hasattr(self._logger, level.lower()):
375
                setattr(self._logger, level.lower(), _x)
376
        return self
377
378
    def config_main(
379
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
380
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
381
        sink: TextIO = _SENTINEL,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
382
        level: Optional[str] = _SENTINEL,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
383
        fmt: Formatter = _SENTINEL,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
384
    ) -> __qualname__:
385
        """
386
        Sets the logging level for the main handler (normally stderr).
387
        """
388
        if level is not None and level is not _SENTINEL:
389
            level = level.upper()
390
        if self._main is None:
391
            self._main = _HandlerInfo(
392
                hid=-1,
393
                sink=sys.stderr,
394
                level=self._levels[FancyLoguruDefaults.level],
395
                fmt=FancyLoguruDefaults.fmt_built_in,
396
            )
397
        else:
398
            try:
399
                logger.remove(self._main.hid)
400
            except ValueError:
401
                logger.error(f"Cannot remove handler {self._main.hid}")
402
        self._main.level = self._main.level if level is _SENTINEL else level
403
        self._main.sink = self._main.sink if sink is _SENTINEL else sink
404
        self._main.fmt = self._main.fmt if fmt is _SENTINEL else fmt
405
        self._main.hid = logger.add(self._main.sink, level=self._main.level, format=self._main.fmt)
406
        return self
407
408
    def remove_paths(self) -> __qualname__:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
409
        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...
410
            h.level = None
411
            try:
412
                logger.remove(k)
413
            except ValueError:
414
                logger.error(f"Cannot remove handler {k} to {h.sink}")
415
416
    def remove_path(self, path: Path) -> __qualname__:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
417
        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...
418
            if h.sink.resolve() == path.resolve():
419
                h.level = None
420
                try:
421
                    logger.remove(k)
422
                except ValueError:
423
                    logger.error(f"Cannot remove handler {k} to {path}")
424
425
    def add_path(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
426
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
427
        path: Path,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
428
        level: str = _SENTINEL,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
429
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
430
        fmt: str = _SENTINEL,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
431
    ) -> __qualname__:
432
        if level is _SENTINEL and self._main is None:
433
            level = FancyLoguruDefaults.level
434
        elif level is _SENTINEL:
435
            level = self._main.level
436
        level = level.upper()
437
        if fmt is _SENTINEL and self._main is None:
438
            fmt = FancyLoguruDefaults.fmt_built_in
439
        elif fmt is _SENTINEL:
440
            fmt = self._main.fmt
441
        if isinstance(fmt, str):
442
            fmt = FormatFactory.with_extras(fmt=fmt)
443
        ell = self._levels[level]
444
        info = LogSinkInfo.guess(path)
445
        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...
446
            str(info.base),
447
            format=fmt,
448
            level=level,
449
            compression=info.compression,
450
            serialize=info.serialize,
451
            backtrace=True,
452
            diagnose=True,
453
            enqueue=True,
454
            encoding="utf-8",
455
        )
456
        self._paths[x] = _HandlerInfo(hid=x, sink=info.base, level=ell, fmt=fmt)
457
        return self
458
459
    def from_cli(
460
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
461
        path: Union[None, str, Path] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
462
        main: Optional[str] = FancyLoguruDefaults.level,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
463
        quiet: bool = False,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
464
    ) -> __qualname__:
465
        """
466
        This function controls logging set via command-line.
467
        Deletes any existing path handlers.
468
469
        Args:
470
            main: The level for stderr
471
            path: If set, the path to a file. Can be prefixed with ``:level:`` to set the level
472
                  (e.g. ``:INFO:mandos-run.log.gz``). Can serialize to JSON if .json is used
473
                  instead of .log or .txt.
474
            quiet: Log with debug about logging changes instead of info
475
        """
476
        lvl = "INFO" if quiet else "DEBUG"
477
        if main is None and self._main is None:
478
            main = FancyLoguruDefaults.level
479
        elif main is None:
480
            main = self._main.level
481
        main = self._aliases.get(main.upper(), main.upper())
482
        if main not in FancyLoguruDefaults.levels_extended:
483
            _permitted = ", ".join(
484
                [*FancyLoguruDefaults.levels_extended, *FancyLoguruDefaults.aliases.keys()]
485
            )
486
            raise XValueError(f"{main.lower()} not a permitted log level (allowed: {_permitted}")
487
        self.config_main(level=main)
488
        self.remove_paths()
489
        if path is not None or len(str(path)) == 0:
490
            match = _LOGGER_ARG_PATTERN.match(str(path))
491
            path_level = "DEBUG" if match.group(1) is None else match.group(1)
492
            path = Path(match.group(2))
493
            self.add_path(path, path_level)
494
            logger.log(lvl, f"Added path handler {path} (level {path_level})")
495
        logger.log(lvl, f"Set main log level to {main}")
496
        return self
497
498
    __call__ = from_cli
499
500
501
class LoggerWithNotice(Logger, metaclass=abc.ABCMeta):
502
    """
503
    A wrapper that has fake methods to trick static analysis.
504
    """
505
506
    def notice(self, __message: str, *args, **kwargs):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
507
        raise NotImplementedError()  # not real
508
509
510
class LoggerWithCaution(Logger, metaclass=abc.ABCMeta):
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
511
    def caution(self, __message: str, *args, **kwargs):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
512
        raise NotImplementedError()  # not real
513
514
515
class LoggerWithCautionAndNotice(LoggerWithNotice, LoggerWithCaution, metaclass=abc.ABCMeta):
516
    """
517
    A wrapper that has fake methods to trick static analysis.
518
    """
519
520
521
class FancyLoguruExtras:
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
522
    @classmethod
523
    def rewire_streams_to_utf8(cls) -> None:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
524
        sys.stderr.reconfigure(encoding="utf-8")
525
        sys.stdout.reconfigure(encoding="utf-8")
526
        sys.stdin.reconfigure(encoding="utf-8")
527
528
    @classmethod
529
    def built_in(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
530
        cls,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
531
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
532
        level: str = FancyLoguruDefaults.level,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
533
        rewire: bool = False,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
534
    ) -> FancyLoguru[Logger]:
535
        if rewire:
536
            cls.rewire_streams_to_utf8()
537
        return FancyLoguru.new(Logger).config_levels().init(level=level)
538
539
    @classmethod
540
    def extended(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
541
        cls,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
542
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
543
        level: str = FancyLoguruDefaults.level,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
544
        simplify_fmt: bool = True,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
545
        red_green_safe: bool = True,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
546
        rewire: bool = False,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
547
    ) -> FancyLoguru[LoggerWithCautionAndNotice]:
548
        if rewire:
549
            cls.rewire_streams_to_utf8()
550
        return (
551
            FancyLoguru.new(LoggerWithCautionAndNotice)
552
            .config_levels(
553
                levels=FancyLoguruDefaults.levels_extended,
554
                colors=(
555
                    FancyLoguruDefaults.colors_red_green_safe
556
                    if red_green_safe
557
                    else FancyLoguruDefaults.colors_extended
558
                ),
559
                icons=FancyLoguruDefaults.icons_extended,
560
            )
561
            .init(
562
                level=level,
563
                fmt=(
564
                    FancyLoguruDefaults.fmt_simplified
565
                    if simplify_fmt
566
                    else FancyLoguruDefaults.fmt_built_in
567
                ),
568
            )
569
        )
570
571
572
if __name__ == "__main__":
573
    _logger = FancyLoguruExtras.extended().logger
574
    _logger.caution("hello")
575
576
577
__all__ = [
578
    "FancyLoguruDefaults",
579
    "FancyLoguru",
580
    "FancyLoguruExtras",
581
    "HandlerInfo",
582
    "Logger",
583
    "LoggerWithCautionAndNotice",
584
]
585