Skip to content

Commit

Permalink
Update loguru
Browse files Browse the repository at this point in the history
  • Loading branch information
FastestMolasses committed Sep 24, 2023
1 parent 56fcdfb commit 72bf265
Show file tree
Hide file tree
Showing 13 changed files with 253 additions and 174 deletions.
2 changes: 1 addition & 1 deletion loguru/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from ._logger import Core as _Core
from ._logger import Logger as _Logger

__version__ = "0.7.0"
__version__ = "0.7.2"

__all__ = ["logger"]

Expand Down
62 changes: 23 additions & 39 deletions loguru/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -114,25 +114,9 @@ from typing import (
overload,
)

if sys.version_info >= (3, 5, 3):
from typing import Awaitable
else:
from typing_extensions import Awaitable

if sys.version_info >= (3, 6):
from os import PathLike
from typing import ContextManager

PathLikeStr = PathLike[str]
else:
from pathlib import PurePath as PathLikeStr

from typing_extensions import ContextManager

if sys.version_info >= (3, 8):
from typing import Protocol, TypedDict
else:
from typing_extensions import Protocol, TypedDict
from typing import Awaitable, Protocol, TypedDict, ContextManager
from os import PathLike
PathLikeStr = PathLike[str]

_T = TypeVar("_T")
_F = TypeVar("_F", bound=Callable[..., Any])
Expand Down Expand Up @@ -320,8 +304,8 @@ class Logger:
depth: int = ...,
ansi: bool = ...
) -> Logger: ...
def bind(__self, **kwargs: Any) -> Logger: ...
def contextualize(__self, **kwargs: Any) -> Contextualizer: ...
def bind(__self, **kwargs: Any) -> Logger: ... # noqa: N805
def contextualize(__self, **kwargs: Any) -> Contextualizer: ... # noqa: N805
def patch(self, patcher: PatcherFunction) -> Logger: ...
@overload
def level(self, name: str) -> Level: ...
Expand Down Expand Up @@ -371,43 +355,43 @@ class Logger:
chunk: int = ...
) -> Generator[Dict[str, Any], None, None]: ...
@overload
def trace(__self, __message: str, *args: Any, **kwargs: Any) -> None: ...
def trace(__self, __message: str, *args: Any, **kwargs: Any) -> None: ... # noqa: N805
@overload
def trace(__self, __message: Any) -> None: ...
def trace(__self, __message: Any) -> None: ... # noqa: N805
@overload
def debug(__self, __message: str, *args: Any, **kwargs: Any) -> None: ...
def debug(__self, __message: str, *args: Any, **kwargs: Any) -> None: ... # noqa: N805
@overload
def debug(__self, __message: Any) -> None: ...
def debug(__self, __message: Any) -> None: ... # noqa: N805
@overload
def info(__self, __message: str, *args: Any, **kwargs: Any) -> None: ...
def info(__self, __message: str, *args: Any, **kwargs: Any) -> None: ... # noqa: N805
@overload
def info(__self, __message: Any) -> None: ...
def info(__self, __message: Any) -> None: ... # noqa: N805
@overload
def success(__self, __message: str, *args: Any, **kwargs: Any) -> None: ...
def success(__self, __message: str, *args: Any, **kwargs: Any) -> None: ... # noqa: N805
@overload
def success(__self, __message: Any) -> None: ...
def success(__self, __message: Any) -> None: ... # noqa: N805
@overload
def warning(__self, __message: str, *args: Any, **kwargs: Any) -> None: ...
def warning(__self, __message: str, *args: Any, **kwargs: Any) -> None: ... # noqa: N805
@overload
def warning(__self, __message: Any) -> None: ...
def warning(__self, __message: Any) -> None: ... # noqa: N805
@overload
def error(__self, __message: str, *args: Any, **kwargs: Any) -> None: ...
def error(__self, __message: str, *args: Any, **kwargs: Any) -> None: ... # noqa: N805
@overload
def error(__self, __message: Any) -> None: ...
def error(__self, __message: Any) -> None: ... # noqa: N805
@overload
def critical(__self, __message: str, *args: Any, **kwargs: Any) -> None: ...
def critical(__self, __message: str, *args: Any, **kwargs: Any) -> None: ... # noqa: N805
@overload
def critical(__self, __message: Any) -> None: ...
def critical(__self, __message: Any) -> None: ... # noqa: N805
@overload
def exception(__self, __message: str, *args: Any, **kwargs: Any) -> None: ...
def exception(__self, __message: str, *args: Any, **kwargs: Any) -> None: ... # noqa: N805
@overload
def exception(__self, __message: Any) -> None: ...
def exception(__self, __message: Any) -> None: ... # noqa: N805
@overload
def log(
__self, __level: Union[int, str], __message: str, *args: Any, **kwargs: Any
__self, __level: Union[int, str], __message: str, *args: Any, **kwargs: Any # noqa: N805
) -> None: ...
@overload
def log(__self, __level: Union[int, str], __message: Any) -> None: ...
def log(__self, __level: Union[int, str], __message: Any) -> None: ... # noqa: N805
def start(self, *args: Any, **kwargs: Any) -> int: ...
def stop(self, *args: Any, **kwargs: Any) -> None: ...

Expand Down
21 changes: 3 additions & 18 deletions loguru/_asyncio_loop.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,11 @@
import asyncio
import sys


def load_loop_functions():
if sys.version_info >= (3, 7):

def get_task_loop(task):
return task.get_loop()

get_running_loop = asyncio.get_running_loop

else:

def get_task_loop(task):
return task._loop

def get_running_loop():
loop = asyncio.get_event_loop()
if not loop.is_running():
raise RuntimeError("There is no running event loop")
return loop
def get_task_loop(task):
return task.get_loop()

get_running_loop = asyncio.get_running_loop
return get_task_loop, get_running_loop


Expand Down
147 changes: 113 additions & 34 deletions loguru/_better_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
import traceback


def is_exception_group(exc):
return isinstance(exc, ExceptionGroup)


class SyntaxHighlighter:
_default_style = {
"comment": "\x1b[30m\x1b[1m{}\x1b[0m",
Expand All @@ -28,6 +32,12 @@ class SyntaxHighlighter:
_builtins = set(dir(builtins))
_constants = {"True", "False", "None"}
_punctation = {"(", ")", "[", "]", "{", "}", ":", ",", ";"}
_strings = {tokenize.STRING}
_fstring_middle = None

if sys.version_info >= (3, 12):
_strings.update({tokenize.FSTRING_START, tokenize.FSTRING_MIDDLE, tokenize.FSTRING_END})
_fstring_middle = tokenize.FSTRING_MIDDLE

def __init__(self, style=None):
self._style = style or self._default_style
Expand All @@ -38,7 +48,12 @@ def highlight(self, source):
output = ""

for token in self.tokenize(source):
type_, string, start, end, line = token
type_, string, (start_row, start_column), (_, end_column), line = token

if type_ == self._fstring_middle:
# When an f-string contains "{{" or "}}", they appear as "{" or "}" in the "string"
# attribute of the token. However, they do not count in the column position.
end_column += string.count("{") + string.count("}")

if type_ == tokenize.NAME:
if string in self._constants:
Expand All @@ -56,23 +71,20 @@ def highlight(self, source):
color = style["operator"]
elif type_ == tokenize.NUMBER:
color = style["number"]
elif type_ == tokenize.STRING:
elif type_ in self._strings:
color = style["string"]
elif type_ == tokenize.COMMENT:
color = style["comment"]
else:
color = style["other"]

start_row, start_column = start
_, end_column = end

if start_row != row:
source = source[:column]
source = source[column:]
row, column = start_row, 0

if type_ != tokenize.ENCODING:
output += line[column:start_column]
output += color.format(string)
output += color.format(line[start_column:end_column])

column = end_column

Expand Down Expand Up @@ -140,6 +152,15 @@ def _get_lib_dirs():
paths = {sysconfig.get_path(name, scheme) for scheme in schemes for name in names}
return [os.path.abspath(path).lower() + os.sep for path in paths if path in sys.path]

@staticmethod
def _indent(text, count, *, prefix="| "):
if count == 0:
yield text
return
for line in text.splitlines(True):
indented = " " * count + prefix + line
yield indented.rstrip() + "\n"

def _get_char(self, char, default):
try:
char.encode(self._encoding)
Expand Down Expand Up @@ -344,7 +365,9 @@ def _format_locations(self, frames_lines, *, has_introduction):

yield frame

def _format_exception(self, value, tb, *, seen=None, is_first=False, from_decorator=False):
def _format_exception(
self, value, tb, *, seen=None, is_first=False, from_decorator=False, group_nesting=0
):
# Implemented from built-in traceback module:
# https://github.com/python/cpython/blob/a5b76167/Lib/traceback.py#L468
exc_type, exc_value, exc_traceback = type(value), value, tb
Expand All @@ -356,46 +379,74 @@ def _format_exception(self, value, tb, *, seen=None, is_first=False, from_decora

if exc_value:
if exc_value.__cause__ is not None and id(exc_value.__cause__) not in seen:
for text in self._format_exception(
exc_value.__cause__, exc_value.__cause__.__traceback__, seen=seen
):
yield text
yield from self._format_exception(
exc_value.__cause__,
exc_value.__cause__.__traceback__,
seen=seen,
group_nesting=group_nesting,
)
cause = "The above exception was the direct cause of the following exception:"
if self._colorize:
cause = self._theme["cause"].format(cause)
if self._diagnose:
yield "\n\n" + cause + "\n\n\n"
yield from self._indent("\n\n" + cause + "\n\n\n", group_nesting)
else:
yield "\n" + cause + "\n\n"
yield from self._indent("\n" + cause + "\n\n", group_nesting)

elif (
exc_value.__context__ is not None
and id(exc_value.__context__) not in seen
and not exc_value.__suppress_context__
):
for text in self._format_exception(
exc_value.__context__, exc_value.__context__.__traceback__, seen=seen
):
yield text
yield from self._format_exception(
exc_value.__context__,
exc_value.__context__.__traceback__,
seen=seen,
group_nesting=group_nesting,
)
context = "During handling of the above exception, another exception occurred:"
if self._colorize:
context = self._theme["context"].format(context)
if self._diagnose:
yield "\n\n" + context + "\n\n\n"
yield from self._indent("\n\n" + context + "\n\n\n", group_nesting)
else:
yield "\n" + context + "\n\n"
yield from self._indent("\n" + context + "\n\n", group_nesting)

is_grouped = is_exception_group(value)

if is_grouped and group_nesting == 0:
yield from self._format_exception(
value,
tb,
seen=seen,
group_nesting=1,
is_first=is_first,
from_decorator=from_decorator,
)
return

try:
tracebacklimit = sys.tracebacklimit
traceback_limit = sys.tracebacklimit
except AttributeError:
tracebacklimit = None
traceback_limit = None

frames, final_source = self._extract_frames(
exc_traceback, is_first, limit=tracebacklimit, from_decorator=from_decorator
exc_traceback, is_first, limit=traceback_limit, from_decorator=from_decorator
)
exception_only = traceback.format_exception_only(exc_type, exc_value)

error_message = exception_only[-1][:-1] # Remove last new line temporarily
# Determining the correct index for the "Exception: message" part in the formatted exception
# is challenging. This is because it might be preceded by multiple lines specific to
# "SyntaxError" or followed by various notes. However, we can make an educated guess based
# on the indentation; the preliminary context for "SyntaxError" is always indented, while
# the Exception itself is not. This allows us to identify the correct index for the
# exception message.
error_message_index = 0
for error_message_index, part in enumerate(exception_only): # noqa: B007
if not part.startswith(" "):
break

error_message = exception_only[error_message_index][:-1] # Remove last new line temporarily

if self._colorize:
if ":" in error_message:
Expand All @@ -414,24 +465,52 @@ def _format_exception(self, value, tb, *, seen=None, is_first=False, from_decora

error_message = "\n" + error_message

exception_only[-1] = error_message + "\n"

frames_lines = traceback.format_list(frames) + exception_only
has_introduction = bool(frames)

if self._colorize or self._backtrace or self._diagnose:
frames_lines = self._format_locations(frames_lines, has_introduction=has_introduction)
exception_only[error_message_index] = error_message + "\n"

if is_first:
yield self._prefix

has_introduction = bool(frames)

if has_introduction:
introduction = "Traceback (most recent call last):"
if is_grouped:
introduction = "Exception Group Traceback (most recent call last):"
else:
introduction = "Traceback (most recent call last):"
if self._colorize:
introduction = self._theme["introduction"].format(introduction)
yield introduction + "\n"
if group_nesting == 1: # Implies we're processing the root ExceptionGroup.
yield from self._indent(introduction + "\n", group_nesting, prefix="+ ")
else:
yield from self._indent(introduction + "\n", group_nesting)

frames_lines = traceback.format_list(frames) + exception_only
if self._colorize or self._backtrace or self._diagnose:
frames_lines = self._format_locations(frames_lines, has_introduction=has_introduction)

yield "".join(frames_lines)
yield from self._indent("".join(frames_lines), group_nesting)

if is_grouped:
exc = None
for n, exc in enumerate(value.exceptions, start=1):
ruler = "+" + (" %s " % ("..." if n > 15 else n)).center(35, "-")
yield from self._indent(ruler, group_nesting, prefix="+-" if n == 1 else " ")
if n > 15:
message = "and %d more exceptions\n" % (len(value.exceptions) - 15)
yield from self._indent(message, group_nesting + 1)
break
elif group_nesting == 10 and is_exception_group(exc):
message = "... (max_group_depth is 10)\n"
yield from self._indent(message, group_nesting + 1)
else:
yield from self._format_exception(
exc,
exc.__traceback__,
seen=seen,
group_nesting=group_nesting + 1,
)
if not is_exception_group(exc) or group_nesting == 10:
yield from self._indent("-" * 35, group_nesting + 1, prefix="+-")

def format_exception(self, type_, value, tb, *, from_decorator=False):
yield from self._format_exception(value, tb, is_first=True, from_decorator=from_decorator)
Loading

0 comments on commit 72bf265

Please sign in to comment.