docs for muutils v0.6.17
View Source on GitHub

# muutils.logger.headerfuncs

View Source
View on GitHub

     1from __future__ import annotations
     2
     3import json
     4from typing import Any, Mapping, Protocol
     5
     6from muutils.json_serialize import json_serialize
     7
     8# takes message, level, other data, and outputs message with appropriate header
     9# HeaderFunction = Callable[[str, int, Any], str]
    10
    11
    12class HeaderFunction(Protocol):
    13    def __call__(self, msg: Any, lvl: int, **kwargs) -> str: ...
    14
    15
    16def md_header_function(
    17    msg: Any,
    18    lvl: int,
    19    stream: str | None = None,
    20    indent_lvl: str = "  ",
    21    extra_indent: str = "",
    22    **kwargs,
    23) -> str:
    24    """standard header function. will output
    25
    26    - `# {msg}`
    27
    28            for levels in [0, 9]
    29
    30    - `## {msg}`
    31
    32            for levels in [10, 19], and so on
    33
    34    - `[{stream}] # {msg}`
    35
    36            for a non-`None` stream, with level headers as before
    37
    38    - `!WARNING! [{stream}] {msg}`
    39
    40            for level in [-9, -1]
    41
    42    - `!!WARNING!! [{stream}] {msg}`
    43
    44            for level in [-19, -10] and so on
    45
    46    """
    47    stream_prefix: str = ""
    48    if stream is not None:
    49        stream_prefix = f"[{stream}] "
    50
    51    lvl_div_10: int = lvl // 10
    52
    53    msg_processed: str
    54    if isinstance(msg, Mapping):
    55        msg_processed = ", ".join([f"{k}: {json_serialize(v)}" for k, v in msg.items()])
    56    else:
    57        msg_processed = json.dumps(json_serialize(msg))
    58
    59    if lvl >= 0:
    60        return f"{extra_indent}{indent_lvl * (lvl_div_10 - 1)}{stream_prefix}#{'#' * lvl_div_10 if lvl else ''} {msg_processed}"
    61    else:
    62        exclamation_pts: str = "!" * (abs(lvl) // 10)
    63        return f"{extra_indent}{exclamation_pts}WARNING{exclamation_pts} {stream_prefix} {msg_processed}"
    64
    65
    66HEADER_FUNCTIONS: dict[str, HeaderFunction] = {
    67    "md": md_header_function,
    68}

class HeaderFunction(typing.Protocol):
View Source
View on GitHub

    13class HeaderFunction(Protocol):
    14    def __call__(self, msg: Any, lvl: int, **kwargs) -> str: ...

Base class for protocol classes.

Protocol classes are defined as::

    class Proto(Protocol):
        def meth(self) -> int:
            ...

Such classes are primarily used with static type checkers that recognize structural subtyping (static duck-typing).

For example::

    class C:
        def meth(self) -> int:
            return 0

    def func(x: Proto) -> int:
        return x.meth()

    func(C())  # Passes static type check

See PEP 544 for details. Protocol classes decorated with @typing.runtime_checkable act as simple-minded runtime protocols that check only the presence of given attributes, ignoring their type signatures. Protocol classes can be generic, they are defined as::

    class GenProto[T](Protocol):
        def meth(self) -> T:
            ...

HeaderFunction(*args, **kwargs)
View Source
View on GitHub

    1710def _no_init_or_replace_init(self, *args, **kwargs):
    1711    cls = type(self)
    1712
    1713    if cls._is_protocol:
    1714        raise TypeError('Protocols cannot be instantiated')
    1715
    1716    # Already using a custom `__init__`. No need to calculate correct
    1717    # `__init__` to call. This can lead to RecursionError. See bpo-45121.
    1718    if cls.__init__ is not _no_init_or_replace_init:
    1719        return
    1720
    1721    # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`.
    1722    # The first instantiation of the subclass will call `_no_init_or_replace_init` which
    1723    # searches for a proper new `__init__` in the MRO. The new `__init__`
    1724    # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent
    1725    # instantiation of the protocol subclass will thus use the new
    1726    # `__init__` and no longer call `_no_init_or_replace_init`.
    1727    for base in cls.__mro__:
    1728        init = base.__dict__.get('__init__', _no_init_or_replace_init)
    1729        if init is not _no_init_or_replace_init:
    1730            cls.__init__ = init
    1731            break
    1732    else:
    1733        # should not happen
    1734        cls.__init__ = object.__init__
    1735
    1736    cls.__init__(self, *args, **kwargs)

def md_header_function( msg: Any, lvl: int, stream: str | None = None, indent_lvl: str = ' ', extra_indent: str = '', **kwargs) -> str:
View Source
View on GitHub

    17def md_header_function(
    18    msg: Any,
    19    lvl: int,
    20    stream: str | None = None,
    21    indent_lvl: str = "  ",
    22    extra_indent: str = "",
    23    **kwargs,
    24) -> str:
    25    """standard header function. will output
    26
    27    - `# {msg}`
    28
    29            for levels in [0, 9]
    30
    31    - `## {msg}`
    32
    33            for levels in [10, 19], and so on
    34
    35    - `[{stream}] # {msg}`
    36
    37            for a non-`None` stream, with level headers as before
    38
    39    - `!WARNING! [{stream}] {msg}`
    40
    41            for level in [-9, -1]
    42
    43    - `!!WARNING!! [{stream}] {msg}`
    44
    45            for level in [-19, -10] and so on
    46
    47    """
    48    stream_prefix: str = ""
    49    if stream is not None:
    50        stream_prefix = f"[{stream}] "
    51
    52    lvl_div_10: int = lvl // 10
    53
    54    msg_processed: str
    55    if isinstance(msg, Mapping):
    56        msg_processed = ", ".join([f"{k}: {json_serialize(v)}" for k, v in msg.items()])
    57    else:
    58        msg_processed = json.dumps(json_serialize(msg))
    59
    60    if lvl >= 0:
    61        return f"{extra_indent}{indent_lvl * (lvl_div_10 - 1)}{stream_prefix}#{'#' * lvl_div_10 if lvl else ''} {msg_processed}"
    62    else:
    63        exclamation_pts: str = "!" * (abs(lvl) // 10)
    64        return f"{extra_indent}{exclamation_pts}WARNING{exclamation_pts} {stream_prefix} {msg_processed}"

standard header function. will output

- `# {msg}`

      for levels in [0, 9]

- `## {msg}`

      for levels in [10, 19], and so on

- `[{stream}] # {msg}`

      for a non-`None` stream, with level headers as before

- `!WARNING! [{stream}] {msg}`

      for level in [-9, -1]

- `!!WARNING!! [{stream}] {msg}`

      for level in [-19, -10] and so on

HEADER_FUNCTIONS: dict[str, HeaderFunction] = {'md': <function md_header_function>}