Воркед
This commit is contained in:
1 parent
ac32c55ab9
commit
ee94d8e529
1138 files changed
+55771
-77901
No files matched your search
@@ -3,6 +3,7 @@ import collections
|
||||
import dataclasses
|
||||
import inspect
|
||||
import os
|
||||
import reprlib
|
||||
import sys
|
||||
from array import array
|
||||
from collections import Counter, UserDict, UserList, defaultdict, deque
|
||||
@@ -15,6 +16,7 @@ from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
DefaultDict,
|
||||
Deque,
|
||||
Dict,
|
||||
Iterable,
|
||||
List,
|
||||
@@ -30,7 +32,7 @@ from pip._vendor.rich.repr import RichReprResult
|
||||
try:
|
||||
import attr as _attr_module
|
||||
|
||||
_has_attrs = True
|
||||
_has_attrs = hasattr(_attr_module, "ib")
|
||||
except ImportError: # pragma: no cover
|
||||
_has_attrs = False
|
||||
|
||||
@@ -55,13 +57,6 @@ if TYPE_CHECKING:
|
||||
)
|
||||
|
||||
|
||||
JUPYTER_CLASSES_TO_NOT_RENDER = {
|
||||
# Matplotlib "Artists" manage their own rendering in a Jupyter notebook, and we should not try to render them too.
|
||||
# "Typically, all [Matplotlib] visible elements in a figure are subclasses of Artist."
|
||||
"matplotlib.artist.Artist",
|
||||
}
|
||||
|
||||
|
||||
def _is_attr_object(obj: Any) -> bool:
|
||||
"""Check if an object was created with attrs module."""
|
||||
return _has_attrs and _attr_module.has(type(obj))
|
||||
@@ -84,7 +79,10 @@ def _is_dataclass_repr(obj: object) -> bool:
|
||||
# Digging in to a lot of internals here
|
||||
# Catching all exceptions in case something is missing on a non CPython implementation
|
||||
try:
|
||||
return obj.__repr__.__code__.co_filename == dataclasses.__file__
|
||||
return obj.__repr__.__code__.co_filename in (
|
||||
dataclasses.__file__,
|
||||
reprlib.__file__,
|
||||
)
|
||||
except Exception: # pragma: no coverage
|
||||
return False
|
||||
|
||||
@@ -120,69 +118,44 @@ def _ipy_display_hook(
|
||||
indent_guides: bool = False,
|
||||
max_length: Optional[int] = None,
|
||||
max_string: Optional[int] = None,
|
||||
max_depth: Optional[int] = None,
|
||||
expand_all: bool = False,
|
||||
) -> None:
|
||||
) -> Union[str, None]:
|
||||
# needed here to prevent circular import:
|
||||
from ._inspect import is_object_one_of_types
|
||||
from .console import ConsoleRenderable
|
||||
|
||||
# always skip rich generated jupyter renderables or None values
|
||||
if _safe_isinstance(value, JupyterRenderable) or value is None:
|
||||
return
|
||||
return None
|
||||
|
||||
console = console or get_console()
|
||||
if console.is_jupyter:
|
||||
# Delegate rendering to IPython if the object (and IPython) supports it
|
||||
# https://ipython.readthedocs.io/en/stable/config/integrating.html#rich-display
|
||||
ipython_repr_methods = [
|
||||
"_repr_html_",
|
||||
"_repr_markdown_",
|
||||
"_repr_json_",
|
||||
"_repr_latex_",
|
||||
"_repr_jpeg_",
|
||||
"_repr_png_",
|
||||
"_repr_svg_",
|
||||
"_repr_mimebundle_",
|
||||
]
|
||||
for repr_method in ipython_repr_methods:
|
||||
method = getattr(value, repr_method, None)
|
||||
if inspect.ismethod(method):
|
||||
# Calling the method ourselves isn't ideal. The interface for the `_repr_*_` methods
|
||||
# specifies that if they return None, then they should not be rendered
|
||||
# by the notebook.
|
||||
try:
|
||||
repr_result = method()
|
||||
except Exception:
|
||||
continue # If the method raises, treat it as if it doesn't exist, try any others
|
||||
if repr_result is not None:
|
||||
return # Delegate rendering to IPython
|
||||
|
||||
# When in a Jupyter notebook let's avoid the display of some specific classes,
|
||||
# as they result in the rendering of useless and noisy lines such as `<Figure size 432x288 with 1 Axes>`.
|
||||
# What does this do?
|
||||
# --> if the class has "matplotlib.artist.Artist" in its hierarchy for example, we don't render it.
|
||||
if is_object_one_of_types(value, JUPYTER_CLASSES_TO_NOT_RENDER):
|
||||
return
|
||||
|
||||
# certain renderables should start on a new line
|
||||
if _safe_isinstance(value, ConsoleRenderable):
|
||||
console.line()
|
||||
|
||||
console.print(
|
||||
value
|
||||
if _safe_isinstance(value, RichRenderable)
|
||||
else Pretty(
|
||||
value,
|
||||
overflow=overflow,
|
||||
indent_guides=indent_guides,
|
||||
max_length=max_length,
|
||||
max_string=max_string,
|
||||
expand_all=expand_all,
|
||||
margin=12,
|
||||
),
|
||||
crop=crop,
|
||||
new_line_start=True,
|
||||
)
|
||||
with console.capture() as capture:
|
||||
# certain renderables should start on a new line
|
||||
if _safe_isinstance(value, ConsoleRenderable):
|
||||
console.line()
|
||||
console.print(
|
||||
(
|
||||
value
|
||||
if _safe_isinstance(value, RichRenderable)
|
||||
else Pretty(
|
||||
value,
|
||||
overflow=overflow,
|
||||
indent_guides=indent_guides,
|
||||
max_length=max_length,
|
||||
max_string=max_string,
|
||||
max_depth=max_depth,
|
||||
expand_all=expand_all,
|
||||
margin=12,
|
||||
)
|
||||
),
|
||||
crop=crop,
|
||||
new_line_start=True,
|
||||
end="",
|
||||
)
|
||||
# strip trailing newline, not usually part of a text repr
|
||||
# I'm not sure if this should be prevented at a lower level
|
||||
return capture.get().rstrip("\n")
|
||||
|
||||
|
||||
def _safe_isinstance(
|
||||
@@ -202,6 +175,7 @@ def install(
|
||||
indent_guides: bool = False,
|
||||
max_length: Optional[int] = None,
|
||||
max_string: Optional[int] = None,
|
||||
max_depth: Optional[int] = None,
|
||||
expand_all: bool = False,
|
||||
) -> None:
|
||||
"""Install automatic pretty printing in the Python REPL.
|
||||
@@ -214,6 +188,7 @@ def install(
|
||||
max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
|
||||
Defaults to None.
|
||||
max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to None.
|
||||
max_depth (int, optional): Maximum depth of nested data structures, or None for no maximum. Defaults to None.
|
||||
expand_all (bool, optional): Expand all containers. Defaults to False.
|
||||
max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100.
|
||||
"""
|
||||
@@ -228,22 +203,28 @@ def install(
|
||||
assert console is not None
|
||||
builtins._ = None # type: ignore[attr-defined]
|
||||
console.print(
|
||||
value
|
||||
if _safe_isinstance(value, RichRenderable)
|
||||
else Pretty(
|
||||
value,
|
||||
overflow=overflow,
|
||||
indent_guides=indent_guides,
|
||||
max_length=max_length,
|
||||
max_string=max_string,
|
||||
expand_all=expand_all,
|
||||
(
|
||||
value
|
||||
if _safe_isinstance(value, RichRenderable)
|
||||
else Pretty(
|
||||
value,
|
||||
overflow=overflow,
|
||||
indent_guides=indent_guides,
|
||||
max_length=max_length,
|
||||
max_string=max_string,
|
||||
max_depth=max_depth,
|
||||
expand_all=expand_all,
|
||||
)
|
||||
),
|
||||
crop=crop,
|
||||
)
|
||||
builtins._ = value # type: ignore[attr-defined]
|
||||
|
||||
try: # pragma: no cover
|
||||
try:
|
||||
ip = get_ipython() # type: ignore[name-defined]
|
||||
except NameError:
|
||||
sys.displayhook = display_hook
|
||||
else:
|
||||
from IPython.core.formatters import BaseFormatter
|
||||
|
||||
class RichFormatter(BaseFormatter): # type: ignore[misc]
|
||||
@@ -258,6 +239,7 @@ def install(
|
||||
indent_guides=indent_guides,
|
||||
max_length=max_length,
|
||||
max_string=max_string,
|
||||
max_depth=max_depth,
|
||||
expand_all=expand_all,
|
||||
)
|
||||
else:
|
||||
@@ -266,8 +248,6 @@ def install(
|
||||
# replace plain text formatter with rich formatter
|
||||
rich_formatter = RichFormatter()
|
||||
ip.display_formatter.formatters["text/plain"] = rich_formatter
|
||||
except Exception:
|
||||
sys.displayhook = display_hook
|
||||
|
||||
|
||||
class Pretty(JupyterMixin):
|
||||
@@ -333,7 +313,7 @@ class Pretty(JupyterMixin):
|
||||
max_depth=self.max_depth,
|
||||
expand_all=self.expand_all,
|
||||
)
|
||||
pretty_text = Text(
|
||||
pretty_text = Text.from_ansi(
|
||||
pretty_str,
|
||||
justify=self.justify or options.justify,
|
||||
overflow=self.overflow or options.overflow,
|
||||
@@ -365,6 +345,7 @@ class Pretty(JupyterMixin):
|
||||
indent_size=self.indent_size,
|
||||
max_length=self.max_length,
|
||||
max_string=self.max_string,
|
||||
max_depth=self.max_depth,
|
||||
expand_all=self.expand_all,
|
||||
)
|
||||
text_width = (
|
||||
@@ -381,6 +362,16 @@ def _get_braces_for_defaultdict(_object: DefaultDict[Any, Any]) -> Tuple[str, st
|
||||
)
|
||||
|
||||
|
||||
def _get_braces_for_deque(_object: Deque[Any]) -> Tuple[str, str, str]:
|
||||
if _object.maxlen is None:
|
||||
return ("deque([", "])", "deque()")
|
||||
return (
|
||||
"deque([",
|
||||
f"], maxlen={_object.maxlen})",
|
||||
f"deque(maxlen={_object.maxlen})",
|
||||
)
|
||||
|
||||
|
||||
def _get_braces_for_array(_object: "array[Any]") -> Tuple[str, str, str]:
|
||||
return (f"array({_object.typecode!r}, [", "])", f"array({_object.typecode!r})")
|
||||
|
||||
@@ -390,7 +381,7 @@ _BRACES: Dict[type, Callable[[Any], Tuple[str, str, str]]] = {
|
||||
array: _get_braces_for_array,
|
||||
defaultdict: _get_braces_for_defaultdict,
|
||||
Counter: lambda _object: ("Counter({", "})", "Counter()"),
|
||||
deque: lambda _object: ("deque([", "])", "deque()"),
|
||||
deque: _get_braces_for_deque,
|
||||
dict: lambda _object: ("{", "}", "{}"),
|
||||
UserDict: lambda _object: ("{", "}", "{}"),
|
||||
frozenset: lambda _object: ("frozenset({", "})", "frozenset()"),
|
||||
@@ -427,7 +418,7 @@ class Node:
|
||||
is_tuple: bool = False
|
||||
is_namedtuple: bool = False
|
||||
children: Optional[List["Node"]] = None
|
||||
key_separator = ": "
|
||||
key_separator: str = ": "
|
||||
separator: str = ", "
|
||||
|
||||
def iter_tokens(self) -> Iterable[str]:
|
||||
@@ -630,8 +621,12 @@ def traverse(
|
||||
def _traverse(obj: Any, root: bool = False, depth: int = 0) -> Node:
|
||||
"""Walk the object depth first."""
|
||||
|
||||
obj_id = id(obj)
|
||||
if obj_id in visited_ids:
|
||||
# Recursion detected
|
||||
return Node(value_repr="...")
|
||||
|
||||
obj_type = type(obj)
|
||||
py_version = (sys.version_info.major, sys.version_info.minor)
|
||||
children: List[Node]
|
||||
reached_max_depth = max_depth is not None and depth >= max_depth
|
||||
|
||||
@@ -667,6 +662,7 @@ def traverse(
|
||||
pass
|
||||
|
||||
if rich_repr_result is not None:
|
||||
push_visited(obj_id)
|
||||
angular = getattr(obj.__rich_repr__, "angular", False)
|
||||
args = list(iter_rich_args(rich_repr_result))
|
||||
class_name = obj.__class__.__name__
|
||||
@@ -676,7 +672,10 @@ def traverse(
|
||||
append = children.append
|
||||
|
||||
if reached_max_depth:
|
||||
node = Node(value_repr=f"...")
|
||||
if angular:
|
||||
node = Node(value_repr=f"<{class_name}...>")
|
||||
else:
|
||||
node = Node(value_repr=f"{class_name}(...)")
|
||||
else:
|
||||
if angular:
|
||||
node = Node(
|
||||
@@ -711,14 +710,16 @@ def traverse(
|
||||
children=[],
|
||||
last=root,
|
||||
)
|
||||
pop_visited(obj_id)
|
||||
elif _is_attr_object(obj) and not fake_attributes:
|
||||
push_visited(obj_id)
|
||||
children = []
|
||||
append = children.append
|
||||
|
||||
attr_fields = _get_attr_fields(obj)
|
||||
if attr_fields:
|
||||
if reached_max_depth:
|
||||
node = Node(value_repr=f"...")
|
||||
node = Node(value_repr=f"{obj.__class__.__name__}(...)")
|
||||
else:
|
||||
node = Node(
|
||||
open_brace=f"{obj.__class__.__name__}(",
|
||||
@@ -727,9 +728,9 @@ def traverse(
|
||||
last=root,
|
||||
)
|
||||
|
||||
def iter_attrs() -> Iterable[
|
||||
Tuple[str, Any, Optional[Callable[[Any], str]]]
|
||||
]:
|
||||
def iter_attrs() -> (
|
||||
Iterable[Tuple[str, Any, Optional[Callable[[Any], str]]]]
|
||||
):
|
||||
"""Iterate over attr fields and values."""
|
||||
for attr in attr_fields:
|
||||
if attr.repr:
|
||||
@@ -758,33 +759,31 @@ def traverse(
|
||||
node = Node(
|
||||
value_repr=f"{obj.__class__.__name__}()", children=[], last=root
|
||||
)
|
||||
|
||||
pop_visited(obj_id)
|
||||
elif (
|
||||
is_dataclass(obj)
|
||||
and not _safe_isinstance(obj, type)
|
||||
and not fake_attributes
|
||||
and (_is_dataclass_repr(obj) or py_version == (3, 6))
|
||||
and _is_dataclass_repr(obj)
|
||||
):
|
||||
obj_id = id(obj)
|
||||
if obj_id in visited_ids:
|
||||
# Recursion detected
|
||||
return Node(value_repr="...")
|
||||
push_visited(obj_id)
|
||||
|
||||
children = []
|
||||
append = children.append
|
||||
if reached_max_depth:
|
||||
node = Node(value_repr=f"...")
|
||||
node = Node(value_repr=f"{obj.__class__.__name__}(...)")
|
||||
else:
|
||||
node = Node(
|
||||
open_brace=f"{obj.__class__.__name__}(",
|
||||
close_brace=")",
|
||||
children=children,
|
||||
last=root,
|
||||
empty=f"{obj.__class__.__name__}()",
|
||||
)
|
||||
|
||||
for last, field in loop_last(
|
||||
field for field in fields(obj) if field.repr
|
||||
field
|
||||
for field in fields(obj)
|
||||
if field.repr and hasattr(obj, field.name)
|
||||
):
|
||||
child_node = _traverse(getattr(obj, field.name), depth=depth + 1)
|
||||
child_node.key_repr = field.name
|
||||
@@ -792,42 +791,43 @@ def traverse(
|
||||
child_node.key_separator = "="
|
||||
append(child_node)
|
||||
|
||||
pop_visited(obj_id)
|
||||
pop_visited(obj_id)
|
||||
elif _is_namedtuple(obj) and _has_default_namedtuple_repr(obj):
|
||||
push_visited(obj_id)
|
||||
class_name = obj.__class__.__name__
|
||||
if reached_max_depth:
|
||||
node = Node(value_repr="...")
|
||||
# If we've reached the max depth, we still show the class name, but not its contents
|
||||
node = Node(
|
||||
value_repr=f"{class_name}(...)",
|
||||
)
|
||||
else:
|
||||
children = []
|
||||
class_name = obj.__class__.__name__
|
||||
append = children.append
|
||||
node = Node(
|
||||
open_brace=f"{class_name}(",
|
||||
close_brace=")",
|
||||
children=children,
|
||||
empty=f"{class_name}()",
|
||||
)
|
||||
append = children.append
|
||||
for last, (key, value) in loop_last(obj._asdict().items()):
|
||||
child_node = _traverse(value, depth=depth + 1)
|
||||
child_node.key_repr = key
|
||||
child_node.last = last
|
||||
child_node.key_separator = "="
|
||||
append(child_node)
|
||||
pop_visited(obj_id)
|
||||
elif _safe_isinstance(obj, _CONTAINERS):
|
||||
for container_type in _CONTAINERS:
|
||||
if _safe_isinstance(obj, container_type):
|
||||
obj_type = container_type
|
||||
break
|
||||
|
||||
obj_id = id(obj)
|
||||
if obj_id in visited_ids:
|
||||
# Recursion detected
|
||||
return Node(value_repr="...")
|
||||
push_visited(obj_id)
|
||||
|
||||
open_brace, close_brace, empty = _BRACES[obj_type](obj)
|
||||
|
||||
if reached_max_depth:
|
||||
node = Node(value_repr=f"...", last=root)
|
||||
node = Node(value_repr=f"{open_brace}...{close_brace}")
|
||||
elif obj_type.__repr__ != type(obj).__repr__:
|
||||
node = Node(value_repr=to_repr(obj), last=root)
|
||||
elif obj:
|
||||
@@ -867,7 +867,7 @@ def traverse(
|
||||
pop_visited(obj_id)
|
||||
else:
|
||||
node = Node(value_repr=to_repr(obj), last=root)
|
||||
node.is_tuple = _safe_isinstance(obj, tuple)
|
||||
node.is_tuple = type(obj) == tuple
|
||||
node.is_namedtuple = _is_namedtuple(obj)
|
||||
return node
|
||||
|
||||
@@ -1008,3 +1008,9 @@ if __name__ == "__main__": # pragma: no cover
|
||||
from pip._vendor.rich import print
|
||||
|
||||
print(Pretty(data, indent_guides=True, max_string=20))
|
||||
|
||||
class Thing:
|
||||
def __repr__(self) -> str:
|
||||
return "Hello\x1b[38;5;239m World!"
|
||||
|
||||
print(Pretty(Thing()))
|
||||
Reference in new issue
Block a user