Skip to content

Commit 9fc01f1

Browse files
committed
Merge branch 'master' into markdown-code
2 parents 5e35957 + 73a285c commit 9fc01f1

File tree

11 files changed

+144
-35
lines changed

11 files changed

+144
-35
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1919

2020
- Handle stdout/stderr being null https://github.com/Textualize/rich/pull/2513
2121
- Fix NO_COLOR support on legacy Windows https://github.com/Textualize/rich/pull/2458
22+
- Fix pretty printer handling of cyclic references https://github.com/Textualize/rich/pull/2524
2223
- Fix missing `mode` property on file wrapper breaking uploads via `requests` https://github.com/Textualize/rich/pull/2495
2324

2425
### Changed
@@ -36,6 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3637
### Fixed
3738

3839
- Fixed missing typing extensions dependency on 3.9 https://github.com/Textualize/rich/issues/2386
40+
- Fixed Databricks Notebook is not detected as Jupyter environment. https://github.com/Textualize/rich/issues/2422
3941

4042
## [12.5.0] - 2022-07-11
4143

CONTRIBUTORS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ The following people have contributed to the development of Rich:
88
- [Gregory Beauregard](https://github.com/GBeauregard/pyffstream)
99
- [Dennis Brakhane](https://github.com/brakhane)
1010
- [Darren Burns](https://github.com/darrenburns)
11+
- [Jim Crist-Harif](https://github.com/jcrist)
1112
- [Ed Davis](https://github.com/davised)
1213
- [Pete Davison](https://github.com/pd93)
1314
- [James Estevez](https://github.com/jstvz)
1415
- [Oleksis Fraga](https://github.com/oleksis)
1516
- [Andy Gimblett](https://github.com/gimbo)
1617
- [Michał Górny](https://github.com/mgorny)
18+
- [Nok Lam Chan](https://github.com/noklam)
1719
- [Leron Gray](https://github.com/daddycocoaman)
1820
- [Kenneth Hoste](https://github.com/boegel)
1921
- [Lanqing Huang](https://github.com/lqhuang)
@@ -46,6 +48,7 @@ The following people have contributed to the development of Rich:
4648
- [Nicolas Simonds](https://github.com/0xDEC0DE)
4749
- [Aaron Stephens](https://github.com/aaronst)
4850
- [Gabriele N. Tornetta](https://github.com/p403n1x87)
51+
- [Nils Vu](https://github.com/nilsvu)
4952
- [Arian Mollik Wasi](https://github.com/wasi-master)
5053
- [Handhika Yanuar Pratama](https://github.com/theDreamer911)
5154
- [za](https://github.com/za)

rich/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from ._extension import load_ipython_extension # noqa: F401
77

8-
__all__ = ["get_console", "reconfigure", "print", "inspect"]
8+
__all__ = ["get_console", "reconfigure", "print", "inspect", "print_json"]
99

1010
if TYPE_CHECKING:
1111
from .console import Console
@@ -40,7 +40,8 @@ def reconfigure(*args: Any, **kwargs: Any) -> None:
4040
"""Reconfigures the global console by replacing it with another.
4141
4242
Args:
43-
console (Console): Replacement console instance.
43+
*args (Any): Positional arguments for the replacement :class:`~rich.console.Console`.
44+
**kwargs (Any): Keyword arguments for the replacement :class:`~rich.console.Console`.
4445
"""
4546
from rich.console import Console
4647

rich/ansi.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def __init__(self) -> None:
120120
self.style = Style.null()
121121

122122
def decode(self, terminal_text: str) -> Iterable[Text]:
123-
"""Decode ANSI codes in an interable of lines.
123+
"""Decode ANSI codes in an iterable of lines.
124124
125125
Args:
126126
lines (Iterable[str]): An iterable of lines of terminal output.

rich/console.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -522,7 +522,11 @@ def _is_jupyter() -> bool: # pragma: no cover
522522
return False
523523
ipython = get_ipython() # type: ignore[name-defined]
524524
shell = ipython.__class__.__name__
525-
if "google.colab" in str(ipython.__class__) or shell == "ZMQInteractiveShell":
525+
if (
526+
"google.colab" in str(ipython.__class__)
527+
or os.getenv("DATABRICKS_RUNTIME_VERSION")
528+
or shell == "ZMQInteractiveShell"
529+
):
526530
return True # Jupyter notebook or qtconsole
527531
elif shell == "TerminalInteractiveShell":
528532
return False # Terminal running IPython

rich/filesize.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"""Functions for reporting filesizes. Borrowed from https://github.com/PyFilesystem/pyfilesystem2
33
44
The functions declared in this module should cover the different
5-
usecases needed to generate a string representation of a file size
5+
use cases needed to generate a string representation of a file size
66
using several different units. Since there are many standards regarding
77
file size units, three different functions have been implemented.
88

rich/pretty.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,11 @@ def to_repr(obj: Any) -> str:
636636
def _traverse(obj: Any, root: bool = False, depth: int = 0) -> Node:
637637
"""Walk the object depth first."""
638638

639+
obj_id = id(obj)
640+
if obj_id in visited_ids:
641+
# Recursion detected
642+
return Node(value_repr="...")
643+
639644
obj_type = type(obj)
640645
py_version = (sys.version_info.major, sys.version_info.minor)
641646
children: List[Node]
@@ -673,6 +678,7 @@ def iter_rich_args(rich_args: Any) -> Iterable[Union[Any, Tuple[str, Any]]]:
673678
pass
674679

675680
if rich_repr_result is not None:
681+
push_visited(obj_id)
676682
angular = getattr(obj.__rich_repr__, "angular", False)
677683
args = list(iter_rich_args(rich_repr_result))
678684
class_name = obj.__class__.__name__
@@ -720,7 +726,9 @@ def iter_rich_args(rich_args: Any) -> Iterable[Union[Any, Tuple[str, Any]]]:
720726
children=[],
721727
last=root,
722728
)
729+
pop_visited(obj_id)
723730
elif _is_attr_object(obj) and not fake_attributes:
731+
push_visited(obj_id)
724732
children = []
725733
append = children.append
726734

@@ -767,19 +775,14 @@ def iter_attrs() -> Iterable[
767775
node = Node(
768776
value_repr=f"{obj.__class__.__name__}()", children=[], last=root
769777
)
770-
778+
pop_visited(obj_id)
771779
elif (
772780
is_dataclass(obj)
773781
and not _safe_isinstance(obj, type)
774782
and not fake_attributes
775783
and (_is_dataclass_repr(obj) or py_version == (3, 6))
776784
):
777-
obj_id = id(obj)
778-
if obj_id in visited_ids:
779-
# Recursion detected
780-
return Node(value_repr="...")
781785
push_visited(obj_id)
782-
783786
children = []
784787
append = children.append
785788
if reached_max_depth:
@@ -801,8 +804,9 @@ def iter_attrs() -> Iterable[
801804
child_node.key_separator = "="
802805
append(child_node)
803806

804-
pop_visited(obj_id)
807+
pop_visited(obj_id)
805808
elif _is_namedtuple(obj) and _has_default_namedtuple_repr(obj):
809+
push_visited(obj_id)
806810
class_name = obj.__class__.__name__
807811
if reached_max_depth:
808812
# If we've reached the max depth, we still show the class name, but not its contents
@@ -824,16 +828,13 @@ def iter_attrs() -> Iterable[
824828
child_node.last = last
825829
child_node.key_separator = "="
826830
append(child_node)
831+
pop_visited(obj_id)
827832
elif _safe_isinstance(obj, _CONTAINERS):
828833
for container_type in _CONTAINERS:
829834
if _safe_isinstance(obj, container_type):
830835
obj_type = container_type
831836
break
832837

833-
obj_id = id(obj)
834-
if obj_id in visited_ids:
835-
# Recursion detected
836-
return Node(value_repr="...")
837838
push_visited(obj_id)
838839

839840
open_brace, close_brace, empty = _BRACES[obj_type](obj)

rich/traceback.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ def extract(
337337
from rich import _IMPORT_CWD
338338

339339
def safe_str(_object: Any) -> str:
340-
"""Don't allow exceptions from __str__ to propegate."""
340+
"""Don't allow exceptions from __str__ to propagate."""
341341
try:
342342
return str(_object)
343343
except Exception:
@@ -389,19 +389,17 @@ def safe_str(_object: Any) -> str:
389389
del stack.frames[:]
390390

391391
cause = getattr(exc_value, "__cause__", None)
392-
if cause and cause.__traceback__:
392+
if cause:
393393
exc_type = cause.__class__
394394
exc_value = cause
395+
# __traceback__ can be None, e.g. for exceptions raised by the
396+
# 'multiprocessing' module
395397
traceback = cause.__traceback__
396398
is_cause = True
397399
continue
398400

399401
cause = exc_value.__context__
400-
if (
401-
cause
402-
and cause.__traceback__
403-
and not getattr(exc_value, "__suppress_context__", False)
404-
):
402+
if cause and not getattr(exc_value, "__suppress_context__", False):
405403
exc_type = cause.__class__
406404
exc_value = cause
407405
traceback = cause.__traceback__

tests/test_pretty.py

Lines changed: 105 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from array import array
55
from collections import UserDict, defaultdict
66
from dataclasses import dataclass, field
7-
from typing import List, NamedTuple
7+
from typing import List, NamedTuple, Any
88
from unittest.mock import patch
99

1010
import attr
@@ -315,12 +315,112 @@ def __repr__(self):
315315
assert result == "BrokenAttr()"
316316

317317

318-
def test_recursive():
318+
def test_reference_cycle_container():
319319
test = []
320320
test.append(test)
321-
result = pretty_repr(test)
322-
expected = "[...]"
323-
assert result == expected
321+
res = pretty_repr(test)
322+
assert res == "[...]"
323+
324+
test = [1, []]
325+
test[1].append(test)
326+
res = pretty_repr(test)
327+
assert res == "[1, [...]]"
328+
329+
# Not a cyclic reference, just a repeated reference
330+
a = [2]
331+
test = [1, [a, a]]
332+
res = pretty_repr(test)
333+
assert res == "[1, [[2], [2]]]"
334+
335+
336+
def test_reference_cycle_namedtuple():
337+
class Example(NamedTuple):
338+
x: int
339+
y: Any
340+
341+
test = Example(1, [Example(2, [])])
342+
test.y[0].y.append(test)
343+
res = pretty_repr(test)
344+
assert res == "Example(x=1, y=[Example(x=2, y=[...])])"
345+
346+
# Not a cyclic reference, just a repeated reference
347+
a = Example(2, None)
348+
test = Example(1, [a, a])
349+
res = pretty_repr(test)
350+
assert res == "Example(x=1, y=[Example(x=2, y=None), Example(x=2, y=None)])"
351+
352+
353+
def test_reference_cycle_dataclass():
354+
@dataclass
355+
class Example:
356+
x: int
357+
y: Any
358+
359+
test = Example(1, None)
360+
test.y = test
361+
res = pretty_repr(test)
362+
assert res == "Example(x=1, y=...)"
363+
364+
test = Example(1, Example(2, None))
365+
test.y.y = test
366+
res = pretty_repr(test)
367+
assert res == "Example(x=1, y=Example(x=2, y=...))"
368+
369+
# Not a cyclic reference, just a repeated reference
370+
a = Example(2, None)
371+
test = Example(1, [a, a])
372+
res = pretty_repr(test)
373+
assert res == "Example(x=1, y=[Example(x=2, y=None), Example(x=2, y=None)])"
374+
375+
376+
def test_reference_cycle_attrs():
377+
@attr.define
378+
class Example:
379+
x: int
380+
y: Any
381+
382+
test = Example(1, None)
383+
test.y = test
384+
res = pretty_repr(test)
385+
assert res == "Example(x=1, y=...)"
386+
387+
test = Example(1, Example(2, None))
388+
test.y.y = test
389+
res = pretty_repr(test)
390+
assert res == "Example(x=1, y=Example(x=2, y=...))"
391+
392+
# Not a cyclic reference, just a repeated reference
393+
a = Example(2, None)
394+
test = Example(1, [a, a])
395+
res = pretty_repr(test)
396+
assert res == "Example(x=1, y=[Example(x=2, y=None), Example(x=2, y=None)])"
397+
398+
399+
def test_reference_cycle_custom_repr():
400+
class Example:
401+
def __init__(self, x, y):
402+
self.x = x
403+
self.y = y
404+
405+
def __rich_repr__(self):
406+
yield ("x", self.x)
407+
yield ("y", self.y)
408+
409+
test = Example(1, None)
410+
test.y = test
411+
res = pretty_repr(test)
412+
assert res == "Example(x=1, y=...)"
413+
414+
test = Example(1, Example(2, None))
415+
test.y.y = test
416+
res = pretty_repr(test)
417+
assert res == "Example(x=1, y=Example(x=2, y=...))"
418+
419+
# Not a cyclic reference, just a repeated reference
420+
a = Example(2, None)
421+
test = Example(1, [a, a])
422+
res = pretty_repr(test)
423+
assert res == "Example(x=1, y=[Example(x=2, y=None), Example(x=2, y=None)])"
324424

325425

326426
def test_max_depth():

tests/test_tree.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ def test_render_tree_hide_root_win32():
139139
def test_tree_measure():
140140
tree = Tree("foo")
141141
tree.add("bar")
142-
tree.add("musroom risotto")
142+
tree.add("mushroom risotto")
143143
console = Console()
144144
measurement = Measurement.get(console, console.options, tree)
145-
assert measurement == Measurement(11, 19)
145+
assert measurement == Measurement(12, 20)

0 commit comments

Comments
 (0)