Skip to content

Commit 2173f5e

Browse files
committed
Added 'head' function to utils.
1 parent 232182b commit 2173f5e

File tree

6 files changed

+165
-3
lines changed

6 files changed

+165
-3
lines changed

.isort.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ known_third_party =
2121
faker
2222
github
2323
importlib_resources
24+
pandas
2425
pydash
2526
pytest
2627
pytest_cov

doc-source/api/utils.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,3 @@
33
=================================
44

55
.. automodule:: domdf_python_tools.utils
6-
:undoc-members:

doc-source/requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ autodocsumm>=0.2.0
22
default_values>=0.0.9
33
domdf_sphinx_theme>=0.1.0
44
extras_require>=0.2.0
5+
pandas>=1.0.0
56
seed_intersphinx_mapping>=0.1.1
67
sphinx>=3.0.3
78
sphinx-copybutton>=0.2.12
89
sphinx-notfound-page>=0.5
910
sphinx-prompt>=1.1.0
1011
sphinx-tabs>=1.1.13
11-
sphinx-toolbox>=0.3.3
12+
sphinx-toolbox>=0.5.0
1213
sphinxcontrib-httpdomain>=1.7.0
1314
sphinxemoji>=0.1.6
1415
toctree_plus>=0.0.4

domdf_python_tools/utils.py

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,22 @@
5252
import inspect
5353
import itertools
5454
import sys
55-
from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, Sequence, Tuple, Union
55+
from pprint import pformat
56+
from typing import TYPE_CHECKING, Any, Callable, Dict, Generator, Iterable, List, Optional, Sequence, Tuple, Union
5657

5758
# 3rd party
5859
import deprecation # type: ignore
60+
from typing_extensions import Protocol, runtime_checkable
5961

6062
# this package
6163
import domdf_python_tools.words
6264
from domdf_python_tools import __version__
6365

66+
if TYPE_CHECKING:
67+
# 3rd party
68+
from pandas import DataFrame, Series # type: ignore
69+
from pandas._typing import FrameOrSeries # type: ignore
70+
6471
__all__ = [
6572
"pyversion",
6673
"SPACE_PLACEHOLDER",
@@ -82,6 +89,9 @@
8289
"double_chain",
8390
"posargs2kwargs",
8491
"convert_indents",
92+
"etc",
93+
"head",
94+
"HasHead",
8595
]
8696

8797
#: The current major python version.
@@ -91,6 +101,16 @@
91101
SPACE_PLACEHOLDER = '␣'
92102

93103

104+
@runtime_checkable
105+
class String(Protocol):
106+
"""
107+
Protocol for classes that implement ``__str__``.
108+
"""
109+
110+
def __str__(self) -> str:
111+
... # pragma: no cover
112+
113+
94114
def check_dependencies(dependencies: Iterable[str], prt: bool = True) -> List[str]:
95115
"""
96116
Check whether one or more dependencies are available to be imported.
@@ -415,3 +435,70 @@ def convert_indents(text: str, tab_width: int = 4, from_: str = "\t", to: str =
415435
)(
416436
domdf_python_tools.words.word_join
417437
)
438+
439+
440+
@runtime_checkable
441+
class HasHead(Protocol):
442+
"""
443+
:class:`typing.Protocol` for classes that have a ``head``.
444+
445+
This includes :class:`pandas.DataFrame` and :class:`pandas.Series`.
446+
"""
447+
448+
def head(self: "FrameOrSeries", n: int = 5) -> "FrameOrSeries":
449+
... # pragma: no cover
450+
451+
def to_string(self, *args, **kwargs) -> Optional[str]:
452+
... # pragma: no cover
453+
454+
455+
class _Etcetera(str):
456+
457+
def __new__(cls):
458+
return str.__new__(cls, "...") # type: ignore
459+
460+
def __repr__(self) -> str:
461+
return str(self)
462+
463+
464+
etc = _Etcetera()
465+
"""
466+
Object that provides an ellipsis string
467+
468+
.. versionadded:: 0.8.0
469+
"""
470+
471+
472+
def head(obj: Union[Tuple, List, "DataFrame", "Series", "String"], n: int = 10) -> str:
473+
"""
474+
Returns the head of the given object.
475+
476+
:param obj:
477+
:param n: Show the first ``n`` items of ``obj``.
478+
479+
.. versionadded:: 0.8.0
480+
"""
481+
482+
if isinstance(obj, tuple) and hasattr(obj, "_fields"):
483+
if len(obj) <= n:
484+
return repr(obj)
485+
else:
486+
# Likely a namedtuple
487+
head_of_namedtuple = {k: v for k, v in zip(obj._fields[:n], obj[:n])} # type: ignore
488+
repr_fmt = '(' + ', '.join(f'{k}={v!r}' for k, v in head_of_namedtuple.items()) + f', {etc})'
489+
return obj.__class__.__name__ + repr_fmt
490+
491+
elif isinstance(obj, (list, tuple)):
492+
if len(obj) > n:
493+
return pformat(obj.__class__((*obj[:n], etc)))
494+
else:
495+
return pformat(obj)
496+
497+
elif isinstance(obj, HasHead):
498+
return obj.head(n).to_string()
499+
500+
elif len(obj) <= n: # type: ignore
501+
return str(obj)
502+
503+
else:
504+
return str(obj[:n]) + etc # type: ignore

tests/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
coverage>=5.1
22
coverage_pyver_pragma>=0.0.5
33
faker>=4.1.2
4+
pandas>=1.0.0; implementation_name == "cpython" and python_version < "3.9"
45
pytest>=6.0.0
56
pytest-cov>=2.8.1
67
pytest-randomly>=3.3.1

tests/test_utils.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@
1313
import re
1414
import sys
1515
import types
16+
from collections import namedtuple
1617

1718
# 3rd party
1819
import pytest
1920

2021
# this package
2122
from domdf_python_tools import utils
2223
from domdf_python_tools.testing import testing_boolean_values
24+
from domdf_python_tools.utils import HasHead, head
2325

2426

2527
def test_pyversion():
@@ -433,3 +435,74 @@ def test_convert_indents():
433435
assert utils.convert_indents("hello world", tab_width=2, from_=" ") == "hello world"
434436
assert utils.convert_indents(" hello world", tab_width=2, from_=" ") == " hello world"
435437
assert utils.convert_indents(" hello world", tab_width=2, from_=" ") == " hello world"
438+
439+
440+
class TestHead:
441+
442+
def test_protocol(self):
443+
assert not isinstance(str, HasHead)
444+
assert not isinstance(int, HasHead)
445+
assert not isinstance(float, HasHead)
446+
assert not isinstance(tuple, HasHead)
447+
assert not isinstance(list, HasHead)
448+
449+
def test_protocol_pandas(self):
450+
pandas = pytest.importorskip("pandas")
451+
452+
assert isinstance(pandas.DataFrame, HasHead)
453+
assert isinstance(pandas.Series, HasHead)
454+
455+
def test_namedtuple(self):
456+
foo = namedtuple("foo", "a, b, c, d, e, f, g, h, i, j, k, l, m")
457+
assert head(
458+
foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)
459+
) == "foo(a=1, b=2, c=3, d=4, e=5, f=6, g=7, h=8, i=9, j=10, ...)"
460+
assert head(
461+
foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13), 13
462+
) == "foo(a=1, b=2, c=3, d=4, e=5, f=6, g=7, h=8, i=9, j=10, k=11, l=12, m=13)"
463+
464+
def test_tuple(self):
465+
assert head((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)) == "(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...)"
466+
assert head((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13),
467+
13) == "(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)"
468+
469+
def test_list(self):
470+
assert head([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]) == "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...]"
471+
assert head([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13],
472+
13) == "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]"
473+
474+
def test_data_frame(self):
475+
pandas = pytest.importorskip("pandas")
476+
477+
df = pandas.DataFrame(
478+
data=[["Bob", 20, "Apprentice"], ["Alice", 23, "Secretary"], ["Mario", 39, "Plumber"]],
479+
columns=["Name", "Age", "Occupation"],
480+
)
481+
assert head(
482+
df
483+
) == """ Name Age Occupation
484+
0 Bob 20 Apprentice
485+
1 Alice 23 Secretary
486+
2 Mario 39 Plumber\
487+
"""
488+
assert head(df, 1) == " Name Age Occupation\n0 Bob 20 Apprentice"
489+
490+
def test_series(self):
491+
pandas = pytest.importorskip("pandas")
492+
493+
df = pandas.DataFrame(
494+
data=[["Bob", 20, "Apprentice"], ["Alice", 23, "Secretary"], ["Mario", 39, "Plumber"]],
495+
columns=["Name", "Age", "Occupation"],
496+
)
497+
ser = df.iloc[0]
498+
assert head(ser) == """\
499+
Name Bob
500+
Age 20
501+
Occupation Apprentice\
502+
"""
503+
assert head(ser, 1) == "Name Bob"
504+
505+
def test_str(self):
506+
assert head("Hello World") == "Hello Worl..."
507+
assert head("Hello World", 11) == "Hello World"
508+
assert head("Hello World", 5) == "Hello..."

0 commit comments

Comments
 (0)