Skip to content

Commit c6d593f

Browse files
committed
wip
1 parent 554e4f6 commit c6d593f

File tree

21 files changed

+349
-39
lines changed

21 files changed

+349
-39
lines changed

LICENSES/COLORAMA_LICENSE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

pandas/core/_formats.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"""
2+
Constants for formatting. Usable by both pandas.core and elsewhere.
3+
4+
The names are chosen to match colorama/ansi.py, whose license is included
5+
in LICENSES/COLORAMA_LICENSE
6+
"""
7+
import re
8+
from typing import Any, Callable, List, Optional
9+
10+
from pandas._libs import missing as libmissing
11+
12+
CSI = "\033["
13+
ANSI_PAT = re.compile(r"\x1B[@-_][0-?]*[ -/]*[@-~]")
14+
15+
16+
def strip_ansi(x):
17+
return ANSI_PAT.sub("", x)
18+
19+
20+
def format_with(value: str, formatters: List[str]):
21+
return "".join(formatters + [value, AnsiStyle.RESET_ALL])
22+
23+
24+
class AnsiFore:
25+
BLACK = f"{CSI}30m"
26+
RED = f"{CSI}31m"
27+
GREEN = f"{CSI}32m"
28+
YELLOW = f"{CSI}33m"
29+
BLUE = f"{CSI}34m"
30+
MAGENTA = f"{CSI}35m"
31+
CYAN = f"{CSI}36m"
32+
WHITE = f"{CSI}37m"
33+
RESET = f"{CSI}39m"
34+
35+
36+
class AnsiBack:
37+
BLACK = f"{CSI}40m"
38+
RED = f"{CSI}41m"
39+
GREEN = f"{CSI}42m"
40+
YELLOW = f"{CSI}43m"
41+
BLUE = f"{CSI}44m"
42+
MAGENTA = f"{CSI}45m"
43+
CYAN = f"{CSI}46m"
44+
WHITE = f"{CSI}47m"
45+
RESET = f"{CSI}49m"
46+
47+
48+
class AnsiStyle:
49+
BRIGHT = f"{CSI}1m"
50+
DIM = f"{CSI}2m"
51+
NORMAL = f"{CSI}22m"
52+
RESET_ALL = f"{CSI}0m"
53+
54+
55+
class NAFormatterMixin:
56+
def _formatter(
57+
self, boxed: bool = False, terminal=False
58+
) -> Callable[[Any], Optional[str]]:
59+
def formatter(x):
60+
if x is libmissing.NA and terminal:
61+
return format_with("NA", [AnsiFore.RED])
62+
elif boxed:
63+
return str(x)
64+
else:
65+
return repr(x)
66+
67+
return formatter

pandas/core/arrays/base.py

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
This is an experimental API and subject to breaking changes
66
without warning.
77
"""
8+
import inspect
89
import operator
910
from typing import Any, Callable, Dict, Optional, Sequence, Tuple, Union
11+
import warnings
1012

1113
import numpy as np
1214

@@ -910,20 +912,35 @@ def view(self, dtype=None) -> Union[ABCExtensionArray, np.ndarray]:
910912
# ------------------------------------------------------------------------
911913
# Printing
912914
# ------------------------------------------------------------------------
913-
914-
def __repr__(self) -> str:
915+
def _repr_base(self, is_repr=True):
915916
from pandas.io.formats.printing import format_object_summary
916917

917918
# the short repr has no trailing newline, while the truncated
918919
# repr does. So we include a newline in our template, and strip
919920
# any trailing newlines from format_object_summary
921+
922+
if is_repr:
923+
terminal = True
924+
else:
925+
terminal = False
926+
# compatibility for older EAs
927+
kwargs = _check_formatter_signature(self._formatter, terminal=terminal)
928+
920929
data = format_object_summary(
921-
self, self._formatter(), indent_for_name=False
930+
self, self._formatter(**kwargs), indent_for_name=False
922931
).rstrip(", \n")
923932
class_name = f"<{type(self).__name__}>\n"
924933
return f"{class_name}{data}\nLength: {len(self)}, dtype: {self.dtype}"
925934

926-
def _formatter(self, boxed: bool = False) -> Callable[[Any], Optional[str]]:
935+
def __repr__(self) -> str:
936+
return self._repr_base(is_repr=True)
937+
938+
def __str__(self):
939+
return self._repr_base(is_repr=False)
940+
941+
def _formatter(
942+
self, boxed: bool = False, terminal: bool = False
943+
) -> Callable[[Any], Optional[str]]:
927944
"""Formatting function for scalar values.
928945
929946
This is used in the default '__repr__'. The returned formatting
@@ -937,6 +954,12 @@ def _formatter(self, boxed: bool = False) -> Callable[[Any], Optional[str]]:
937954
itself (False). This may be useful if you want scalar values
938955
to appear differently within a Series versus on its own (e.g.
939956
quoted or not).
957+
terminal : bool, default False
958+
Indicator whether the result is being printed to a terminal
959+
screen. This may be used to detect whether ANSI codes should
960+
be used to style terminal output.
961+
962+
.. versionadded:: 1.0.0
940963
941964
Returns
942965
-------
@@ -1203,3 +1226,13 @@ def _create_arithmetic_method(cls, op):
12031226
@classmethod
12041227
def _create_comparison_method(cls, op):
12051228
return cls._create_method(op, coerce_to_dtype=False)
1229+
1230+
1231+
def _check_formatter_signature(formatter, **kwargs):
1232+
if len(inspect.signature(formatter).parameters) == 1:
1233+
warnings.warn(
1234+
"'_formatter' signature is incorrect. Ensure it matches "
1235+
"the base class' signature."
1236+
)
1237+
kwargs.pop("terminal", None)
1238+
return kwargs

pandas/core/arrays/boolean.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from pandas.core.dtypes.missing import isna, notna
2828

2929
from pandas.core import nanops, ops
30+
from pandas.core._formats import NAFormatterMixin
3031
from pandas.core.algorithms import take
3132
from pandas.core.arrays import ExtensionArray, ExtensionOpsMixin
3233
import pandas.core.common as com
@@ -197,7 +198,7 @@ def coerce_to_array(values, mask=None, copy: bool = False):
197198
return values, mask
198199

199200

200-
class BooleanArray(ExtensionArray, ExtensionOpsMixin):
201+
class BooleanArray(NAFormatterMixin, ExtensionArray, ExtensionOpsMixin):
201202
"""
202203
Array of boolean (True/False) data with missing values.
203204
@@ -295,9 +296,6 @@ def _values_for_factorize(self) -> Tuple[np.ndarray, Any]:
295296
def _from_factorized(cls, values, original: "BooleanArray"):
296297
return cls._from_sequence(values, dtype=original.dtype)
297298

298-
def _formatter(self, boxed=False):
299-
return str
300-
301299
@property
302300
def _hasna(self) -> bool:
303301
# Note: this is expensive right now! The hope is that we can

pandas/core/arrays/categorical.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ def _constructor(self) -> Type["Categorical"]:
459459
def _from_sequence(cls, scalars, dtype=None, copy=False):
460460
return Categorical(scalars, dtype=dtype)
461461

462-
def _formatter(self, boxed=False):
462+
def _formatter(self, boxed=False, terminal=False):
463463
# Defer to CategoricalFormatter's formatter.
464464
return None
465465

pandas/core/arrays/datetimelike.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ def _format_native_types(self, na_rep="NaT", date_format=None):
389389
"""
390390
raise AbstractMethodError(self)
391391

392-
def _formatter(self, boxed=False):
392+
def _formatter(self, boxed=False, terminal=False):
393393
# TODO: Remove Datetime & DatetimeTZ formatters.
394394
return "'{}'".format
395395

pandas/core/arrays/integer.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from pandas.core.dtypes.missing import isna, notna
2525

2626
from pandas.core import nanops, ops
27+
from pandas.core._formats import NAFormatterMixin
2728
from pandas.core.algorithms import take
2829
from pandas.core.arrays import ExtensionArray, ExtensionOpsMixin
2930
import pandas.core.common as com
@@ -259,7 +260,7 @@ def coerce_to_array(values, dtype, mask=None, copy=False):
259260
return values, mask
260261

261262

262-
class IntegerArray(ExtensionArray, ExtensionOpsMixin):
263+
class IntegerArray(NAFormatterMixin, ExtensionArray, ExtensionOpsMixin):
263264
"""
264265
Array of integer (optional missing) values.
265266

pandas/core/arrays/period.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -615,7 +615,7 @@ def asfreq(self, freq=None, how="E"):
615615
# ------------------------------------------------------------------
616616
# Rendering Methods
617617

618-
def _formatter(self, boxed=False):
618+
def _formatter(self, boxed=False, terminal=False):
619619
if boxed:
620620
return str
621621
return "'{}'".format

pandas/core/arrays/sparse/array.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1471,7 +1471,7 @@ def __repr__(self) -> str:
14711471
pp_index = printing.pprint_thing(self.sp_index)
14721472
return f"{pp_str}\nFill: {pp_fill}\n{pp_index}"
14731473

1474-
def _formatter(self, boxed=False):
1474+
def _formatter(self, boxed=False, terminal=False):
14751475
# Defer to the formatter from the GenericArrayFormatter calling us.
14761476
# This will infer the correct formatter from the dtype of the values.
14771477
return None

pandas/core/arrays/string_.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
from pandas import compat
1515
from pandas.core import ops
16+
from pandas.core._formats import NAFormatterMixin
1617
from pandas.core.arrays import PandasArray
1718
from pandas.core.construction import extract_array
1819
from pandas.core.missing import isna
@@ -82,7 +83,7 @@ def __from_arrow__(self, array):
8283
return StringArray._concat_same_type(results)
8384

8485

85-
class StringArray(PandasArray):
86+
class StringArray(NAFormatterMixin, PandasArray):
8687
"""
8788
Extension array for string data.
8889

pandas/core/arrays/timedeltas.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@ def median(
457457
# ----------------------------------------------------------------
458458
# Rendering Methods
459459

460-
def _formatter(self, boxed=False):
460+
def _formatter(self, boxed=False, terminal=False):
461461
from pandas.io.formats.format import _get_format_timedelta64
462462

463463
return _get_format_timedelta64(self, box=True)

pandas/core/frame.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -655,7 +655,7 @@ def _info_repr(self) -> bool:
655655
self._repr_fits_horizontal_() and self._repr_fits_vertical_()
656656
)
657657

658-
def __repr__(self) -> str:
658+
def _repr_base(self, is_repr: bool = True) -> str:
659659
"""
660660
Return a string representation for a particular DataFrame.
661661
"""
@@ -681,10 +681,17 @@ def __repr__(self) -> str:
681681
line_width=width,
682682
max_colwidth=max_colwidth,
683683
show_dimensions=show_dimensions,
684+
terminal=is_repr,
684685
)
685686

686687
return buf.getvalue()
687688

689+
def __repr__(self) -> str:
690+
return self._repr_base(is_repr=True)
691+
692+
def __str__(self) -> str:
693+
return self._repr_base(is_repr=False)
694+
688695
def _repr_html_(self) -> Optional[str]:
689696
"""
690697
Return a html representation for a particular DataFrame.
@@ -761,6 +768,7 @@ def to_string(
761768
line_width: Optional[int] = None,
762769
max_colwidth: Optional[int] = None,
763770
encoding: Optional[str] = None,
771+
terminal: bool = False,
764772
) -> Optional[str]:
765773
"""
766774
Render a DataFrame to a console-friendly tabular output.
@@ -812,6 +820,7 @@ def to_string(
812820
show_dimensions=show_dimensions,
813821
decimal=decimal,
814822
line_width=line_width,
823+
terminal=terminal,
815824
)
816825
return formatter.to_string(buf=buf, encoding=encoding)
817826

pandas/core/series.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1292,8 +1292,7 @@ def reset_index(self, level=None, drop=False, name=None, inplace=False):
12921292

12931293
# ----------------------------------------------------------------------
12941294
# Rendering Methods
1295-
1296-
def __repr__(self) -> str:
1295+
def _repr_base(self, is_repr: bool = True):
12971296
"""
12981297
Return a string representation for a particular Series.
12991298
"""
@@ -1318,11 +1317,18 @@ def __repr__(self) -> str:
13181317
min_rows=min_rows,
13191318
max_rows=max_rows,
13201319
length=show_dimensions,
1320+
terminal=is_repr,
13211321
)
13221322
result = buf.getvalue()
13231323

13241324
return result
13251325

1326+
def __repr__(self) -> str:
1327+
return self._repr_base(is_repr=True)
1328+
1329+
def __str__(self) -> str:
1330+
return self._repr_base(is_repr=False)
1331+
13261332
def to_string(
13271333
self,
13281334
buf=None,
@@ -1335,6 +1341,7 @@ def to_string(
13351341
name=False,
13361342
max_rows=None,
13371343
min_rows=None,
1344+
terminal: bool = False,
13381345
):
13391346
"""
13401347
Render a string representation of the Series.
@@ -1382,6 +1389,7 @@ def to_string(
13821389
float_format=float_format,
13831390
min_rows=min_rows,
13841391
max_rows=max_rows,
1392+
terminal=terminal,
13851393
)
13861394
result = formatter.to_string()
13871395

0 commit comments

Comments
 (0)