Path: blob/main/py-polars/tests/unit/operations/test_display.py
8420 views
from __future__ import annotations12import string3from decimal import Decimal as D4from typing import TYPE_CHECKING, Any56import pytest78import polars as pl9from polars.exceptions import InvalidOperationError10from polars.testing import assert_frame_equal1112if TYPE_CHECKING:13from collections.abc import Iterator1415from polars._typing import PolarsDataType161718@pytest.fixture(autouse=True)19def _environ() -> Iterator[None]:20"""Fixture to ensure we run with default Config settings during tests."""21with pl.Config(restore_defaults=True):22yield232425@pytest.mark.parametrize(26("expected", "values"),27[28pytest.param(29"""shape: (1,)30Series: 'foo' [str]31[32"Somelongstringt…33]34""",35["Somelongstringto eeat wit me oundaf"],36id="Long string",37),38pytest.param(39"""shape: (1,)40Series: 'foo' [str]41[42"😀😁😂😃😄😅😆😇😈😉😊😋😌😎😏…43]44""",45["😀😁😂😃😄😅😆😇😈😉😊😋😌😎😏😐😑😒😓"],46id="Emojis",47),48pytest.param(49"""shape: (1,)50Series: 'foo' [str]51[52"yzäöüäöüäöüäö"53]54""",55["yzäöüäöüäöüäö"],56id="Characters with accents",57),58pytest.param(59"""shape: (100,)60Series: 'foo' [i64]61[62063164265366467…6895699670977198729973]74""",75[*range(100)],76id="Long series",77),78],79)80def test_fmt_series(81capfd: pytest.CaptureFixture[str], expected: str, values: list[Any]82) -> None:83s = pl.Series(name="foo", values=values)84with pl.Config(fmt_str_lengths=15):85print(s)86out, _err = capfd.readouterr()87assert out == expected888990def test_fmt_series_string_truncate_default(capfd: pytest.CaptureFixture[str]) -> None:91values = [92string.ascii_lowercase + "123",93string.ascii_lowercase + "1234",94string.ascii_lowercase + "12345",95]96s = pl.Series(name="foo", values=values)97print(s)98out, _ = capfd.readouterr()99expected = """shape: (3,)100Series: 'foo' [str]101[102"abcdefghijklmnopqrstuvwxyz123"103"abcdefghijklmnopqrstuvwxyz1234"104"abcdefghijklmnopqrstuvwxyz1234…105]106"""107assert out == expected108109110@pytest.mark.parametrize(111"dtype", [pl.String, pl.Categorical, pl.Enum(["abc", "abcd", "abcde"])]112)113def test_fmt_series_string_truncate_cat(114dtype: PolarsDataType, capfd: pytest.CaptureFixture[str]115) -> None:116s = pl.Series(name="foo", values=["abc", "abcd", "abcde"], dtype=dtype)117with pl.Config(fmt_str_lengths=4):118print(s)119out, _ = capfd.readouterr()120result = [s.strip() for s in out.split("\n")[3:6]]121expected = ['"abc"', '"abcd"', '"abcd…']122print(result)123assert result == expected124125126@pytest.mark.parametrize(127("values", "dtype", "expected"),128[129(130[-127, -1, 0, 1, 127],131pl.Int8,132"""shape: (5,)133Series: 'foo' [i8]134[135-127136-113701381139127140]""",141),142(143[-32768, -1, 0, 1, 32767],144pl.Int16,145"""shape: (5,)146Series: 'foo' [i16]147[148-32,768149-11500151115232,767153]""",154),155(156[-2147483648, -1, 0, 1, 2147483647],157pl.Int32,158"""shape: (5,)159Series: 'foo' [i32]160[161-2,147,483,648162-1163016411652,147,483,647166]""",167),168(169[-9223372036854775808, -1, 0, 1, 9223372036854775807],170pl.Int64,171"""shape: (5,)172Series: 'foo' [i64]173[174-9,223,372,036,854,775,808175-1176017711789,223,372,036,854,775,807179]""",180),181],182)183def test_fmt_signed_int_thousands_sep(184values: list[int], dtype: PolarsDataType, expected: str185) -> None:186s = pl.Series(name="foo", values=values, dtype=dtype)187with pl.Config(thousands_separator=True):188assert str(s) == expected189190191@pytest.mark.parametrize(192("values", "dtype", "expected"),193[194(195[0, 1, 127],196pl.UInt8,197"""shape: (3,)198Series: 'foo' [u8]199[20002011202127203]""",204),205(206[0, 1, 32767],207pl.UInt16,208"""shape: (3,)209Series: 'foo' [u16]210[2110212121332,767214]""",215),216(217[0, 1, 2147483647],218pl.UInt32,219"""shape: (3,)220Series: 'foo' [u32]221[222022312242,147,483,647225]""",226),227(228[0, 1, 9223372036854775807],229pl.UInt64,230"""shape: (3,)231Series: 'foo' [u64]232[233023412359,223,372,036,854,775,807236]""",237),238],239)240def test_fmt_unsigned_int_thousands_sep(241values: list[int], dtype: PolarsDataType, expected: str242) -> None:243s = pl.Series(name="foo", values=values, dtype=dtype)244with pl.Config(thousands_separator=True):245assert str(s) == expected246247248def test_fmt_float(capfd: pytest.CaptureFixture[str]) -> None:249s = pl.Series(name="foo", values=[7.966e-05, 7.9e-05, 8.4666e-05, 8.00007966])250print(s)251out, _err = capfd.readouterr()252expected = """shape: (4,)253Series: 'foo' [f64]254[2550.000082560.0000792570.0000852588.00008259]260"""261assert out == expected262263264def test_duration_smallest_units() -> None:265s = pl.Series(range(6), dtype=pl.Duration("us"))266assert (267str(s)268== "shape: (6,)\nSeries: '' [duration[μs]]\n[\n\t0µs\n\t1µs\n\t2µs\n\t3µs\n\t4µs\n\t5µs\n]"269)270s = pl.Series(range(6), dtype=pl.Duration("ms"))271assert (272str(s)273== "shape: (6,)\nSeries: '' [duration[ms]]\n[\n\t0ms\n\t1ms\n\t2ms\n\t3ms\n\t4ms\n\t5ms\n]"274)275s = pl.Series(range(6), dtype=pl.Duration("ns"))276assert (277str(s)278== "shape: (6,)\nSeries: '' [duration[ns]]\n[\n\t0ns\n\t1ns\n\t2ns\n\t3ns\n\t4ns\n\t5ns\n]"279)280281282def test_fmt_float_full() -> None:283fmt_float_full = "shape: (1,)\nSeries: '' [f64]\n[\n\t1.230498095872587\n]"284s = pl.Series([1.2304980958725870923])285286with pl.Config() as cfg:287cfg.set_fmt_float("full")288assert str(s) == fmt_float_full289290assert str(s) != fmt_float_full291292293def test_fmt_list_12188() -> None:294# set max_items to 1 < 4(size of failed list) to touch the testing branch.295with (296pl.Config(fmt_table_cell_list_len=1),297pytest.raises(InvalidOperationError, match="from `i64` to `u8` failed"),298):299pl.DataFrame(300{301"x": pl.int_range(250, 260, 1, eager=True),302}303).with_columns(u8=pl.col("x").cast(pl.UInt8))304305306def test_date_list_fmt() -> None:307df = pl.DataFrame(308{309"mydate": ["2020-01-01", "2020-01-02", "2020-01-05", "2020-01-05"],310"index": [1, 2, 5, 5],311}312)313314df = df.with_columns(pl.col("mydate").str.strptime(pl.Date, "%Y-%m-%d"))315assert (316str(df.group_by("index", maintain_order=True).agg(pl.col("mydate"))["mydate"])317== """shape: (3,)318Series: 'mydate' [list[date]]319[320[2020-01-01]321[2020-01-02]322[2020-01-05, 2020-01-05]323]"""324)325326327def test_fmt_series_cat_list() -> None:328s = pl.Series(329[330["a", "b"],331["b", "a"],332["b"],333],334).cast(pl.List(pl.Categorical))335336assert (337str(s)338== """shape: (3,)339Series: '' [list[cat]]340[341["a", "b"]342["b", "a"]343["b"]344]"""345)346347348def test_format_numeric_locale_options() -> None:349df = pl.DataFrame(350{351"a": ["xx", "yy"],352"b": [100000.987654321, -234567.89],353"c": [-11111111, 44444444444],354"d": [D("12345.6789"), D("-9999999.99")],355},356strict=False,357)358359# note: numeric digit grouping looks much better360# when right-aligned with fixed float precision361with pl.Config(362tbl_cell_numeric_alignment="RIGHT",363thousands_separator=",",364float_precision=3,365):366assert (367str(df)368== """shape: (2, 4)369┌─────┬──────────────┬────────────────┬─────────────────┐370│ a ┆ b ┆ c ┆ d │371│ --- ┆ --- ┆ --- ┆ --- │372│ str ┆ f64 ┆ i64 ┆ decimal[38,4] │373╞═════╪══════════════╪════════════════╪═════════════════╡374│ xx ┆ 100,000.988 ┆ -11,111,111 ┆ 12,345.6789 │375│ yy ┆ -234,567.890 ┆ 44,444,444,444 ┆ -9,999,999.9900 │376└─────┴──────────────┴────────────────┴─────────────────┘"""377)378379# switch digit/decimal separators380with pl.Config(381decimal_separator=",",382thousands_separator=".",383):384assert (385str(df)386== """shape: (2, 4)387┌─────┬────────────────┬────────────────┬─────────────────┐388│ a ┆ b ┆ c ┆ d │389│ --- ┆ --- ┆ --- ┆ --- │390│ str ┆ f64 ┆ i64 ┆ decimal[38,4] │391╞═════╪════════════════╪════════════════╪═════════════════╡392│ xx ┆ 100.000,987654 ┆ -11.111.111 ┆ 12.345,6789 │393│ yy ┆ -234.567,89 ┆ 44.444.444.444 ┆ -9.999.999,9900 │394└─────┴────────────────┴────────────────┴─────────────────┘"""395)396397# default (no digit grouping, standard digit/decimal separators)398assert (399str(df)400== """shape: (2, 4)401┌─────┬───────────────┬─────────────┬───────────────┐402│ a ┆ b ┆ c ┆ d │403│ --- ┆ --- ┆ --- ┆ --- │404│ str ┆ f64 ┆ i64 ┆ decimal[38,4] │405╞═════╪═══════════════╪═════════════╪═══════════════╡406│ xx ┆ 100000.987654 ┆ -11111111 ┆ 12345.6789 │407│ yy ┆ -234567.89 ┆ 44444444444 ┆ -9999999.9900 │408└─────┴───────────────┴─────────────┴───────────────┘"""409)410411412def test_fmt_decimal_max_scale() -> None:413values = [D("0.14282911023321884847623576259639164703")]414dtype = pl.Decimal(precision=38, scale=38)415s = pl.Series(values, dtype=dtype)416result = str(s)417expected = """shape: (1,)418Series: '' [decimal[38,38]]419[4200.14282911023321884847623576259639164703421]"""422assert result == expected423424425@pytest.mark.parametrize(426("lf", "expected"),427[428(429(430pl.LazyFrame({"a": [1]})431.with_columns(b=pl.col("a"))432.with_columns(c=pl.col("b"), d=pl.col("a"))433),434'simple π 4/4 ["a", "b", "c", "d"]',435),436(437(438pl.LazyFrame({"a_very_very_long_string": [1], "a": [1]})439.with_columns(b=pl.col("a"))440.with_columns(c=pl.col("b"), d=pl.col("a"))441),442'simple π 5/5 ["a_very_very_long_string", "a", ... 3 other columns]',443),444(445(446pl.LazyFrame({"an_even_longer_very_very_long_string": [1], "a": [1]})447.with_columns(b=pl.col("a"))448.with_columns(c=pl.col("b"), d=pl.col("a"))449),450'simple π 5/5 ["an_even_longer_very_very_long_string", ... 4 other columns]',451),452(453(454pl.LazyFrame({"a": [1]})455.with_columns(b=pl.col("a"))456.with_columns(c=pl.col("b"), a_very_long_string_at_the_end=pl.col("a"))457),458'simple π 4/4 ["a", "b", "c", ... 1 other column]',459),460(461(462pl.LazyFrame({"a": [1]})463.with_columns(b=pl.col("a"))464.with_columns(465a_very_long_string_in_the_middle=pl.col("b"), d=pl.col("a")466)467),468'simple π 4/4 ["a", "b", ... 2 other columns]',469),470],471)472def test_simple_project_format(lf: pl.LazyFrame, expected: str) -> None:473result = lf.explain()474assert expected in result475476477@pytest.mark.parametrize(478("df", "expected"),479[480pytest.param(481pl.DataFrame({"A": range(4)}),482"""shape: (4, 1)483+-----+484| A |485+=====+486| 0 |487| 1 |488| ... |489| 3 |490+-----+""",491id="Ellipsis correctly aligned",492),493pytest.param(494pl.DataFrame({"A": range(2)}),495"""shape: (2, 1)496+---+497| A |498+===+499| 0 |500| 1 |501+---+""",502id="No ellipsis needed",503),504],505)506def test_format_ascii_table_truncation(df: pl.DataFrame, expected: str) -> None:507with pl.Config(tbl_rows=3, tbl_hide_column_data_types=True, ascii_tables=True):508assert str(df) == expected509510511def test_format_21393() -> None:512assert pl.select(pl.format("{}", pl.lit(1, pl.Int128))).item() == "1"513514515def test_format_25625() -> None:516repr_str = repr(pl.format(""))517assert repr_str.startswith("<Expr ['str.format()'] at"), repr_str518519assert_frame_equal(520pl.DataFrame({}).select(pl.format("")), pl.DataFrame({"literal": ""})521)522assert_frame_equal(523pl.DataFrame({}).select(pl.format("x A + ")),524pl.DataFrame({"literal": "x A + "}),525)526assert_frame_equal(527pl.DataFrame({}).select(pl.format("x A + {{and here }}")),528pl.DataFrame({"literal": "x A + {{and here }}"}),529)530531532