From ed7724ce1cb9302ffb1b7fdcea2aaf4eb9d5f765 Mon Sep 17 00:00:00 2001 From: J van Zundert Date: Sat, 15 Jul 2023 19:02:06 +0100 Subject: [PATCH] chore(python): Add various unit tests (#9903) --- py-polars/polars/config.py | 2 +- py-polars/polars/utils/show_versions.py | 2 +- py-polars/polars/utils/various.py | 6 --- py-polars/tests/unit/namespaces/test_array.py | 11 +++++ .../tests/unit/namespaces/test_struct.py | 2 + py-polars/tests/unit/test_api.py | 25 +++++++++++ py-polars/tests/unit/test_cfg.py | 43 ++++++++++++++++++- py-polars/tests/unit/test_show_graph.py | 15 +++++++ .../tests/unit/utils/test_parse_expr_input.py | 8 ++++ py-polars/tests/unit/utils/test_utils.py | 13 +++++- 10 files changed, 117 insertions(+), 10 deletions(-) create mode 100644 py-polars/tests/unit/test_show_graph.py diff --git a/py-polars/polars/config.py b/py-polars/polars/config.py index c16f95733a59..fc4e49f86929 100644 --- a/py-polars/polars/config.py +++ b/py-polars/polars/config.py @@ -10,7 +10,7 @@ # dummy func required (so docs build) -def _get_float_fmt() -> str: +def _get_float_fmt() -> str: # pragma: no cover return "n/a" diff --git a/py-polars/polars/utils/show_versions.py b/py-polars/polars/utils/show_versions.py index ce4b39ac3719..f34db533ec26 100644 --- a/py-polars/polars/utils/show_versions.py +++ b/py-polars/polars/utils/show_versions.py @@ -87,6 +87,6 @@ def _get_dependency_version(dep_name: str) -> str: if hasattr(module, "__version__"): module_version = module.__version__ else: - module_version = importlib.metadata.version(dep_name) + module_version = importlib.metadata.version(dep_name) # pragma: no cover return module_version diff --git a/py-polars/polars/utils/various.py b/py-polars/polars/utils/various.py index b5a50cfd9b49..69d3cb502fe6 100644 --- a/py-polars/polars/utils/various.py +++ b/py-polars/polars/utils/various.py @@ -19,7 +19,6 @@ Int64, Time, Utf8, - is_polars_dtype, unpack_dtypes, ) from polars.dependencies import _PYARROW_AVAILABLE @@ -72,11 +71,6 @@ def is_bool_sequence(val: object) -> TypeGuard[Sequence[bool]]: return isinstance(val, Sequence) and _is_iterable_of(val, bool) -def is_dtype_sequence(val: object) -> TypeGuard[Sequence[PolarsDataType]]: - """Check whether the given object is a sequence of polars DataTypes.""" - return isinstance(val, Sequence) and all(is_polars_dtype(x) for x in val) - - def is_int_sequence(val: object) -> TypeGuard[Sequence[int]]: """Check whether the given sequence is a sequence of integers.""" return isinstance(val, Sequence) and _is_iterable_of(val, int) diff --git a/py-polars/tests/unit/namespaces/test_array.py b/py-polars/tests/unit/namespaces/test_array.py index f12b76172a56..ac69510cd8ed 100644 --- a/py-polars/tests/unit/namespaces/test_array.py +++ b/py-polars/tests/unit/namespaces/test_array.py @@ -1,6 +1,7 @@ import numpy as np import polars as pl +from polars.testing import assert_frame_equal def test_arr_min_max() -> None: @@ -14,6 +15,16 @@ def test_arr_sum() -> None: assert s.arr.sum().to_list() == [3, 7] +def test_arr_unique() -> None: + df = pl.DataFrame( + {"a": pl.Series("a", [[1, 1], [4, 3]], dtype=pl.Array(width=2, inner=pl.Int64))} + ) + + out = df.select(pl.col("a").arr.unique(maintain_order=True)) + expected = pl.DataFrame({"a": [[1], [4, 3]]}) + assert_frame_equal(out, expected) + + def test_array_to_numpy() -> None: s = pl.Series([[1, 2], [3, 4], [5, 6]], dtype=pl.Array(width=2, inner=pl.Int64)) assert (s.to_numpy() == np.array([[1, 2], [3, 4], [5, 6]])).all() diff --git a/py-polars/tests/unit/namespaces/test_struct.py b/py-polars/tests/unit/namespaces/test_struct.py index b0ab63ea094d..db9dec236160 100644 --- a/py-polars/tests/unit/namespaces/test_struct.py +++ b/py-polars/tests/unit/namespaces/test_struct.py @@ -15,6 +15,8 @@ def test_struct_various() -> None: assert s[1] == {"int": 2, "str": "b", "bool": None, "list": [3]} assert s.struct.field("list").to_list() == [[1, 2], [3]] assert s.struct.field("int").to_list() == [1, 2] + assert s.struct["list"].to_list() == [[1, 2], [3]] + assert s.struct["int"].to_list() == [1, 2] assert_frame_equal(df.to_struct("my_struct").struct.unnest(), df) assert s.struct._ipython_key_completions_() == s.struct.fields diff --git a/py-polars/tests/unit/test_api.py b/py-polars/tests/unit/test_api.py index 94e130895652..206b7173b9b2 100644 --- a/py-polars/tests/unit/test_api.py +++ b/py-polars/tests/unit/test_api.py @@ -1,5 +1,7 @@ from __future__ import annotations +import pytest + import polars as pl from polars.testing import assert_frame_equal @@ -137,3 +139,26 @@ def test_class_namespaces_are_registered() -> None: assert ( ns in namespaces ), f"{ns!r} should be registered in {pcls.__name__}._accessors" + + +def test_namespace_cannot_override_builtin() -> None: + with pytest.raises(AttributeError): + + @pl.api.register_dataframe_namespace("dt") + class CustomDt: + def __init__(self, df: pl.DataFrame): + self._df = df + + +def test_namespace_warning_on_override() -> None: + @pl.api.register_dataframe_namespace("math") + class CustomMath: + def __init__(self, df: pl.DataFrame): + self._df = df + + with pytest.raises(UserWarning): + + @pl.api.register_dataframe_namespace("math") + class CustomMath2: + def __init__(self, df: pl.DataFrame): + self._df = df diff --git a/py-polars/tests/unit/test_cfg.py b/py-polars/tests/unit/test_cfg.py index e4b9512cc537..1027a739fd51 100644 --- a/py-polars/tests/unit/test_cfg.py +++ b/py-polars/tests/unit/test_cfg.py @@ -513,7 +513,7 @@ def test_string_cache() -> None: @pytest.mark.write_disk() def test_config_load_save(tmp_path: Path) -> None: - for file in (None, tmp_path / "polars.config"): + for file in (None, tmp_path / "polars.config", str(tmp_path / "polars.config")): # set some config options... pl.Config.set_tbl_cols(12) pl.Config.set_verbose(True) @@ -577,3 +577,44 @@ def test_config_scope() -> None: # expect scope-exit to restore original state assert pl.Config.state() == initial_state + + +def test_config_raise_error_if_not_exist() -> None: + with pytest.raises(AttributeError), pl.Config(i_do_not_exist=True): + pass + + +def test_config_state_env_only() -> None: + pl.Config.set_verbose(False) + pl.Config.set_fmt_float("full") + + state_all = pl.Config.state(env_only=False) + state_env_only = pl.Config.state(env_only=True) + assert len(state_env_only) < len(state_all) + assert "set_fmt_float" in state_all + assert "set_fmt_float" not in state_env_only + + +def test_activate_decimals() -> None: + with pl.Config() as cfg: + cfg.activate_decimals(True) + assert os.environ.get("POLARS_ACTIVATE_DECIMAL") == "1" + cfg.activate_decimals(False) + assert "POLARS_ACTIVATE_DECIMAL" not in os.environ + + +def test_set_streaming_chunk_size() -> None: + with pl.Config() as cfg: + cfg.set_streaming_chunk_size(8) + assert os.environ.get("POLARS_STREAMING_CHUNK_SIZE") == "8" + + with pytest.raises(ValueError), pl.Config() as cfg: + cfg.set_streaming_chunk_size(0) + + +def test_set_fmt_str_lengths_invalid_length() -> None: + with pl.Config() as cfg: + with pytest.raises(ValueError): + cfg.set_fmt_str_lengths(0) + with pytest.raises(ValueError): + cfg.set_fmt_str_lengths(-2) diff --git a/py-polars/tests/unit/test_show_graph.py b/py-polars/tests/unit/test_show_graph.py new file mode 100644 index 000000000000..09a9b9484933 --- /dev/null +++ b/py-polars/tests/unit/test_show_graph.py @@ -0,0 +1,15 @@ +import polars as pl + + +def test_show_graph() -> None: + # only test raw output, otherwise we need graphviz and matplotlib + ldf = pl.LazyFrame( + { + "a": ["a", "b", "a", "b", "b", "c"], + "b": [1, 2, 3, 4, 5, 6], + "c": [6, 5, 4, 3, 2, 1], + } + ) + query = ldf.groupby("a", maintain_order=True).agg(pl.all().sum()).sort("a") + out = query.show_graph(raw_output=True) + assert isinstance(out, str) diff --git a/py-polars/tests/unit/utils/test_parse_expr_input.py b/py-polars/tests/unit/utils/test_parse_expr_input.py index 4e039382c1e7..b1eae283b31a 100644 --- a/py-polars/tests/unit/utils/test_parse_expr_input.py +++ b/py-polars/tests/unit/utils/test_parse_expr_input.py @@ -92,3 +92,11 @@ def test_parse_as_expression_structify() -> None: result = wrap_expr(parse_as_expression(pl.col("a", "b"), structify=True)) expected = pl.struct("a", "b") assert_expr_equal(result, expected) + + +def test_parse_as_expression_structify_multiple_outputs() -> None: + # note: this only works because assert_expr_equal evaluates on a dataframe with + # columns "a" and "b" + result = wrap_expr(parse_as_expression(pl.col("*"), structify=True)) + expected = pl.struct("a", "b") + assert_expr_equal(result, expected) diff --git a/py-polars/tests/unit/utils/test_utils.py b/py-polars/tests/unit/utils/test_utils.py index 388b6c2f5855..7119dab5cfed 100644 --- a/py-polars/tests/unit/utils/test_utils.py +++ b/py-polars/tests/unit/utils/test_utils.py @@ -16,7 +16,8 @@ _timedelta_to_pl_timedelta, ) from polars.utils.decorators import deprecate_nonkeyword_arguments, redirect -from polars.utils.various import parse_version +from polars.utils.meta import get_idx_type +from polars.utils.various import _in_notebook, parse_version if TYPE_CHECKING: from polars.type_aliases import TimeUnit @@ -158,3 +159,13 @@ def bar(self, upper: bool = False) -> str: return "BAZ" if upper else "baz" assert DemoClass2().foo() == "BAZ" # type: ignore[attr-defined] + + +def test_get_idx_type_deprecation() -> None: + with pytest.deprecated_call(): + get_idx_type() + + +def test_in_notebook() -> None: + # private function, but easier to test this separately and mock it in the callers + assert not _in_notebook()