Skip to content

Commit

Permalink
Added a truncated dictionary printer for __repr__'ing metadata.
Browse files Browse the repository at this point in the history
Also ensure that internal lists/dictionaries are also truncated.
  • Loading branch information
LTLA committed Oct 31, 2023
1 parent cf62973 commit ccf9404
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 62 deletions.
2 changes: 1 addition & 1 deletion src/biocutils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@
from .subset import subset
from .is_list_of_type import is_list_of_type
from .normalize_subscript import normalize_subscript
from .print_truncated_list import print_truncated_list
from .print_truncated import print_truncated, print_truncated_list, print_truncated_dict
from .print_wrapped_table import print_wrapped_table, create_floating_names, truncate_strings, print_type
133 changes: 133 additions & 0 deletions src/biocutils/print_truncated.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
from typing import Optional, List, Callable, Dict


def print_truncated(x, truncated_to: int = 3, full_threshold: int = 10) -> str:
"""
Pretty-print an object, replacing the middle elements of lists/dictionaries
with an ellipsis if there are too many. This provides a useful preview of
an object without spewing out all of its contents on the screen.
Args:
x: Object to be printed.
truncated_to:
Number of elements to truncate to, at the start and end of the list
or dictionary. This should be less than half of ``full_threshold``.
full_threshold:
Threshold on the number of elements, below which the list or
dictionary is shown in its entirety.
Returns:
String containing the pretty-printed contents.
"""
if isinstance(x, dict):
return print_truncated_dict(x, truncated_to=truncated_to, full_threshold=full_threshold)
elif isinstance(x, list):
return print_truncated_list(x, truncated_to=truncated_to, full_threshold=full_threshold)
else:
return repr(x)


def print_truncated_list(x: List, truncated_to: int = 3, full_threshold: int = 10, transform: Optional[Callable] = None, sep: str = ", ", include_brackets: bool = True) -> str:
"""
Pretty-print a list, replacing the middle elements with an ellipsis if
there are too many. This provides a useful preview of an object without
spewing out all of its contents on the screen.
Args:
x: List to be printed.
truncated_to:
Number of elements to truncate to, at the start and end of the
list. This should be less than half of ``full_threshold``.
full_threshold:
Threshold on the number of elements, below which the list is
shown in its entirety.
transform:
Optional transformation to apply to the elements of ``x``
after truncation but before printing. Defaults to
:py:meth:`~print_truncated` if not supplied.
sep:
Separator between elements in the printed list.
include_brackets:
Whether to include the start/end brackets.
Returns:
String containing the pretty-printed truncated list.
"""
collected = []
if transform is None:
transform = lambda y : print_truncated(y, truncated_to=truncated_to, full_threshold=full_threshold)

if len(x) > full_threshold and len(x) > truncated_to * 2:
for i in range(truncated_to):
collected.append(transform(x[i]))
collected.append("...")
for i in range(truncated_to, 0, -1):
collected.append(transform(x[len(x) - i]))
else:
for c in x:
collected.append(transform(c))

output = sep.join(collected)
if include_brackets:
output = "[" + output + "]"
return output


def print_truncated_dict(x: Dict, truncated_to: int = 3, full_threshold: int = 10, transform: Optional[Callable] = None, sep: str = ", ", include_brackets: bool = True) -> str:
"""
Pretty-print a dictionary, replacing the middle elements with an ellipsis
if there are too many. This provides a useful preview of an object without
spewing out all of its contents on the screen.
Args:
x: Dictionary to be printed.
truncated_to:
Number of elements to truncate to, at the start and end of the
sequence. This should be less than half of ``full_threshold``.
full_threshold:
Threshold on the number of elements, below which the list is
shown in its entirety.
transform:
Optional transformation to apply to the values of ``x`` after
truncation but before printing. Defaults to
:py:meth:`~print_truncated` if not supplied.
sep:
Separator between elements in the printed list.
include_brackets:
Whether to include the start/end brackets.
Returns:
String containing the pretty-printed truncated dict.
"""
collected = []
if transform is None:
transform = lambda y : print_truncated(y, truncated_to=truncated_to, full_threshold=full_threshold)

all_keys = x.keys()
if len(x) > full_threshold and len(x) > truncated_to * 2:
all_keys = list(all_keys)
for i in range(truncated_to):
collected.append(repr(all_keys[i]) + ": " + transform(x[all_keys[i]]))
collected.append("...")
for i in range(len(x) - truncated_to, len(x)):
collected.append(repr(all_keys[i]) + ": " + transform(x[all_keys[i]]))
else:
for c in all_keys:
collected.append(repr(c) + ": " + transform(x[c]))

output = sep.join(collected)
if include_brackets:
output = "{" + output + "}"
return output
51 changes: 0 additions & 51 deletions src/biocutils/print_truncated_list.py

This file was deleted.

23 changes: 23 additions & 0 deletions tests/test_print_truncated.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from biocutils import print_truncated_list, print_truncated_dict, print_truncated


def test_print_truncated_list():
assert print_truncated_list(range(6)) == repr(list(range(6)))
assert print_truncated_list(range(10)) == repr(list(range(10)))
assert print_truncated_list(range(200)) == "[0, 1, 2, ..., 197, 198, 199]"
assert print_truncated_list(["A", "B", "C", "D", "E", "F"], transform=lambda x : repr("foo_" + x)) == "['foo_A', 'foo_B', 'foo_C', 'foo_D', 'foo_E', 'foo_F']"
assert print_truncated_list(["A", "B", "C", "D", "E", "F"], truncated_to=2, full_threshold=5, transform=lambda x : repr("foo_" + x)) == "['foo_A', 'foo_B', ..., 'foo_E', 'foo_F']"
assert print_truncated_list(range(200), sep=" ", include_brackets=False) == "0 1 2 ... 197 198 199"


def test_print_truncated_dict():
assert print_truncated_dict({"A":"B"}) == "{'A': 'B'}"
assert print_truncated_dict({"A":"B", "C": [1,2,3,4,5,6,7,8,9,10,11]}) == "{'A': 'B', 'C': [1, 2, 3, ..., 9, 10, 11]}"
assert print_truncated_dict({"A":-1, "B": 0, "C": 1, "D": 2, "E": True, "F": False }, truncated_to = 2, full_threshold = 5) == "{'A': -1, 'B': 0, ..., 'E': True, 'F': False}"
assert print_truncated_dict({"A":-1, "B": 0, "C": 1, "D": 2, "E": True, "F": False }, sep=" ", include_brackets=False) == "'A': -1 'B': 0 'C': 1 'D': 2 'E': True 'F': False"


def test_print_truncated():
internal = {"A":-1, "B": 0, "C": 1, "D": 2, "E": True, "F": False }
expected = "{'A': -1, 'B': 0, ..., 'E': True, 'F': False}"
assert print_truncated([internal] * 10, truncated_to = 2, full_threshold = 5) == "[" + (expected + ", " ) * 2 + "..." + (", " + expected) * 2 + "]"
10 changes: 0 additions & 10 deletions tests/test_print_truncated_list.py

This file was deleted.

0 comments on commit ccf9404

Please sign in to comment.