Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: show native object (if possible) in repr #1702

Merged
merged 5 commits into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 9 additions & 29 deletions narwhals/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from narwhals.translate import to_native
from narwhals.utils import find_stacklevel
from narwhals.utils import flatten
from narwhals.utils import generate_repr
from narwhals.utils import is_sequence_but_not_str
from narwhals.utils import parse_version

Expand Down Expand Up @@ -414,18 +415,7 @@ def __array__(self, dtype: Any = None, copy: bool | None = None) -> np.ndarray:
return self._compliant_frame.__array__(dtype, copy=copy)

def __repr__(self) -> str: # pragma: no cover
header = " Narwhals DataFrame "
length = len(header)
return (
"β”Œ"
+ "─" * length
+ "┐\n"
+ f"|{header}|\n"
+ "| Use `.to_native` to see native output |\n"
+ "β””"
+ "─" * length
+ "β”˜"
)
return generate_repr("Narwhals DataFrame", self.to_native().__repr__())

def __arrow_c_stream__(self, requested_schema: object | None = None) -> object:
"""Export a DataFrame via the Arrow PyCapsule Interface.
Expand Down Expand Up @@ -3581,18 +3571,7 @@ def __init__(
raise AssertionError(msg)

def __repr__(self) -> str: # pragma: no cover
header = " Narwhals LazyFrame "
length = len(header)
return (
"β”Œ"
+ "─" * length
+ "┐\n"
+ f"|{header}|\n"
+ "| Use `.to_native` to see native output |\n"
+ "β””"
+ "─" * length
+ "β”˜"
)
return generate_repr("Narwhals LazyFrame", self.to_native().__repr__())

@property
def implementation(self) -> Implementation:
Expand Down Expand Up @@ -3640,11 +3619,12 @@ def collect(self) -> DataFrame[Any]:
... }
... )
>>> lf = nw.from_native(lf_pl)
>>> lf
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
| Narwhals LazyFrame |
| Use `.to_native` to see native output |
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
>>> lf # doctest:+ELLIPSIS
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
| Narwhals LazyFrame |
|-----------------------------|
|<LazyFrame at ...
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
>>> df = lf.group_by("a").agg(nw.all().sum()).collect()
>>> df.to_native().sort("a")
shape: (3, 3)
Expand Down
14 changes: 2 additions & 12 deletions narwhals/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from narwhals.dtypes import _validate_dtype
from narwhals.typing import IntoSeriesT
from narwhals.utils import _validate_rolling_arguments
from narwhals.utils import generate_repr
from narwhals.utils import parse_version

if TYPE_CHECKING:
Expand Down Expand Up @@ -404,18 +405,7 @@ def pipe(self, function: Callable[[Any], Self], *args: Any, **kwargs: Any) -> Se
return function(self, *args, **kwargs)

def __repr__(self) -> str: # pragma: no cover
header = " Narwhals Series "
length = len(header)
return (
"β”Œ"
+ "─" * length
+ "┐\n"
+ f"|{header}|\n"
+ "| Use `.to_native()` to see native output |\n"
+ "β””"
+ "─" * length
+ "β”˜"
)
return generate_repr("Narwhals Series", self.to_native().__repr__())

def __len__(self) -> int:
return len(self._compliant_series)
Expand Down
11 changes: 6 additions & 5 deletions narwhals/stable/v1/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,11 +430,12 @@ def collect(self) -> DataFrame[Any]:
... }
... )
>>> lf = nw.from_native(lf_pl)
>>> lf
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
| Narwhals LazyFrame |
| Use `.to_native` to see native output |
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
>>> lf # doctest:+ELLIPSIS
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
| Narwhals LazyFrame |
|-----------------------------|
|<LazyFrame at ...
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
>>> df = lf.group_by("a").agg(nw.all().sum()).collect()
>>> df.to_native().sort("a")
shape: (3, 3)
Expand Down
33 changes: 33 additions & 0 deletions narwhals/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import os
import re
from enum import Enum
from enum import auto
Expand Down Expand Up @@ -960,3 +961,35 @@ def _validate_rolling_arguments(
min_periods = window_size

return window_size, min_periods


def generate_repr(header: str, native_repr: str) -> str:
try:
terminal_width = os.get_terminal_size().columns
except OSError:
terminal_width = 80
native_lines = native_repr.splitlines()
max_native_width = max(len(line) for line in native_lines)

if max_native_width + 2 < terminal_width:
length = max(max_native_width, len(header))
output = f"β”Œ{'─'*length}┐\n"
header_extra = length - len(header)
output += (
f"|{' '*(header_extra//2)}{header}{' '*(header_extra//2 + header_extra%2)}|\n"
)
output += f"|{'-'*(length)}|\n"
start_extra = (length - max_native_width) // 2
end_extra = (length - max_native_width) // 2 + (length - max_native_width) % 2
for line in native_lines:
output += f"|{' '*(start_extra)}{line}{' '*(end_extra + max_native_width - len(line))}|\n"
output += f"β””{'─' * length}β”˜"
return output

diff = 39 - len(header)
return (
f"β”Œ{'─' * (39)}┐\n"
f"|{' '*(diff//2)}{header}{' '*(diff//2+diff%2)}|\n"
"| Use `.to_native` to see native output |\nβ””"
f"{'─' * 39}β”˜"
)
73 changes: 73 additions & 0 deletions tests/repr_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from __future__ import annotations

import pandas as pd
import pytest

import narwhals.stable.v1 as nw


def test_repr() -> None:
duckdb = pytest.importorskip("duckdb")
df = pd.DataFrame({"a": [1, 2, 3], "b": ["fdaf", "fda", "cf"]})
result = nw.from_native(df).__repr__()
expected = (
"β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”\n"
"|Narwhals DataFrame|\n"
"|------------------|\n"
"| a b |\n"
"| 0 1 fdaf |\n"
"| 1 2 fda |\n"
"| 2 3 cf |\n"
"β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜"
)
assert result == expected
result = nw.from_native(df).lazy().__repr__()
expected = (
"β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”\n"
"|Narwhals LazyFrame|\n"
"|------------------|\n"
"| a b |\n"
"| 0 1 fdaf |\n"
"| 1 2 fda |\n"
"| 2 3 cf |\n"
"β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜"
)
Comment on lines +24 to +34
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I admit I panicked for a moment to see values in a lazyframe, but it's pandas so all good 😁

assert result == expected
result = nw.from_native(df)["a"].__repr__()
expected = (
"β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”\n"
"| Narwhals Series |\n"
"|---------------------|\n"
"|0 1 |\n"
"|1 2 |\n"
"|2 3 |\n"
"|Name: a, dtype: int64|\n"
"β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜"
)
assert result == expected
result = nw.from_native(duckdb.table("df")).__repr__()
expected = (
"β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”\n"
"|Narwhals DataFrame |\n"
"|-------------------|\n"
"|β”Œβ”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”|\n"
"|β”‚ a β”‚ b β”‚|\n"
"|β”‚ int64 β”‚ varchar β”‚|\n"
"|β”œβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€|\n"
"|β”‚ 1 β”‚ fdaf β”‚|\n"
"|β”‚ 2 β”‚ fda β”‚|\n"
"|β”‚ 3 β”‚ cf β”‚|\n"
"|β””β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜|\n"
"β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜"
)
assert result == expected
# Make something wider than the terminal size
df = pd.DataFrame({"a": [1, 2, 3], "b": ["fdaf" * 100, "fda", "cf"]})
result = nw.from_native(duckdb.table("df")).__repr__()
expected = (
"β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”\n"
"| Narwhals DataFrame |\n"
"| Use `.to_native` to see native output |\n"
"β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜"
)
assert result == expected
Loading