From fe279823453481ca8f4bb3648b170af4856c3218 Mon Sep 17 00:00:00 2001 From: Marc Wouts Date: Mon, 23 Jan 2023 09:00:47 +0000 Subject: [PATCH] lengthMenu can be either a 1D or 2D array (#153) * update the csv file only if it is missing * passing an option with value=None is not allowed * fix set dom=t when the table fits on one page * Version 1.4.5 --- docs/changelog.md | 12 +++++++++++ itables/javascript.py | 40 +++++++++++++++++++++++++++------- itables/version.py | 2 +- tests/conftest.py | 30 ++++++++++++++++++++++++++ tests/test_javascript.py | 42 ++++++++++++++++++++++++++++-------- tests/test_sample_dfs.py | 15 +++++++++---- tests/test_update_samples.py | 25 ++++++++++----------- 7 files changed, 132 insertions(+), 34 deletions(-) create mode 100644 tests/conftest.py diff --git a/docs/changelog.md b/docs/changelog.md index ff313556..9b04efde 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,6 +1,18 @@ ITables ChangeLog ================= +1.4.5 (2023-01-23) +------------------ + +**Fixed** +- Fixed an issue when `lengthMenu` is a 2D array ([#151](https://github.com/mwouts/itables/issues/151)) + + +**Changed** +- We make sure that no argument passed to `show` is equal to `None` (for all tested options, passing `None` results in a datatable that never loads) +- Running the test collection will not update the CSV files used for testing anymore + + 1.4.4 (2023-01-15) ------------------ diff --git a/itables/javascript.py b/itables/javascript.py index e0fac6cf..1340f7ff 100644 --- a/itables/javascript.py +++ b/itables/javascript.py @@ -220,16 +220,21 @@ def to_html_datatable(df=None, caption=None, tableId=None, connected=True, **kwa ): kwargs[option] = getattr(opt, option) + for name, value in kwargs.items(): + if value is None: + raise ValueError( + "Please don't pass an option with a value equal to None ('{}=None')".format( + name + ) + ) + # These options are used here, not in DataTable classes = kwargs.pop("classes") style = kwargs.pop("style") css = kwargs.pop("css") tags = kwargs.pop("tags") - # Only display the table if the rows fit on one 'page' - if "dom" not in kwargs and len(df) <= 10: # the default page has 10 rows - if "lengthMenu" not in kwargs or len(df) <= min(kwargs["lengthMenu"]): - kwargs["dom"] = "t" + _set_dom_equals_t_if_df_fits_in_one_page(df, kwargs) if caption is not None: tags = '{}{}'.format( @@ -275,10 +280,6 @@ def to_html_datatable(df=None, caption=None, tableId=None, connected=True, **kwa "'header', 'footer' or False, not {}".format(column_filters) ) - # Do not show the page menu when the table has fewer rows than min length menu - if "paging" not in kwargs and len(df.index) <= kwargs.get("lengthMenu", [10])[0]: - kwargs["paging"] = False - # Load the HTML template if connected: output = read_package_file("html/datatables_template_connected.html") @@ -362,6 +363,29 @@ def _column_count_in_header(table_header): return max(line.count("") for line in table_header.split("")) +def _min_rows(kwargs): + if "lengthMenu" not in kwargs: + return 10 + + lengthMenu = kwargs["lengthMenu"] + min_rows = lengthMenu[0] + + if isinstance(min_rows, (int, float)): + return min_rows + + return min_rows[0] + + +def _set_dom_equals_t_if_df_fits_in_one_page(df, kwargs): + """Display just the table (not the search box, etc...) if the rows fit on one 'page'""" + if "dom" in kwargs: + return + + if len(df) <= _min_rows(kwargs): + kwargs["dom"] = "t" + return + + def safe_reset_index(df): try: return df.reset_index() diff --git a/itables/version.py b/itables/version.py index 3ec6a9a7..a9909607 100644 --- a/itables/version.py +++ b/itables/version.py @@ -1,3 +1,3 @@ """ITables' version number""" -__version__ = "1.4.4" +__version__ = "1.4.5" diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..b49fde67 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,30 @@ +import pandas as pd +import pytest + +from itables.sample_dfs import get_dict_of_test_dfs + + +@pytest.fixture(params=list(get_dict_of_test_dfs())) +def df(request): + name = request.param + df = get_dict_of_test_dfs()[name] + assert isinstance(df, pd.DataFrame) + return df + + +@pytest.fixture(params=["None", "1D-array", "2D-array"]) +def lengthMenu(request): + if request.param == "None": + return None + if request.param == "1D-array": + return [2, 5, 10, 20, 50] + if request.param == "2D-array": + return [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]] + raise KeyError(request.param) + + +@pytest.fixture(params=["None", "lfrtip"]) +def dom(request): + if request.param == "None": + return None + return request.param diff --git a/tests/test_javascript.py b/tests/test_javascript.py index 0ba15323..79cf2868 100644 --- a/tests/test_javascript.py +++ b/tests/test_javascript.py @@ -1,14 +1,38 @@ -import pandas as pd -import pytest - -from itables.javascript import to_html_datatable - - -@pytest.fixture() -def df(): - return pd.DataFrame([1, 2]) +from itables.javascript import ( + _set_dom_equals_t_if_df_fits_in_one_page, + to_html_datatable, +) def test_warn_on_unexpected_types_not_in_html(df): html = to_html_datatable(df) assert "warn_on_unexpected_types" not in html + + +def test_set_dom_equals_t_if_df_fits_in_one_page(df, dom, lengthMenu): + kwargs = dict(lengthMenu=lengthMenu, dom=dom) + kwargs = {key: value for key, value in kwargs.items() if value is not None} + _set_dom_equals_t_if_df_fits_in_one_page(df, kwargs) + + if dom is not None: + assert kwargs["dom"] == dom + return + + if lengthMenu is None: + if len(df) <= 10: + assert kwargs["dom"] == "t" + return + + assert "dom" not in kwargs + return + + min_rows = lengthMenu[0] + if isinstance(min_rows, list): + min_rows = min_rows[0] + + if len(df) <= min_rows: + assert kwargs["dom"] == "t" + return + + assert "dom" not in kwargs + return diff --git a/tests/test_sample_dfs.py b/tests/test_sample_dfs.py index c888acfe..d97bc8b9 100644 --- a/tests/test_sample_dfs.py +++ b/tests/test_sample_dfs.py @@ -4,7 +4,7 @@ import pandas as pd import pytest -from itables import show +from itables import show, to_html_datatable from itables.datatables_format import TableValuesEncoder, _format_column from itables.sample_dfs import ( COLUMN_TYPES, @@ -48,9 +48,16 @@ def test_get_indicators(): show(df) -@pytest.mark.parametrize("df_name,df", get_dict_of_test_dfs().items()) -def test_show_test_dfs(df_name, df): - show(df) +def kwargs_remove_none(**kwargs): + return {key: value for key, value in kwargs.items() if value is not None} + + +def test_show_test_dfs(df, lengthMenu): + show(df, **kwargs_remove_none(lengthMenu=lengthMenu)) + + +def test_to_html_datatable(df, lengthMenu): + to_html_datatable(df, **kwargs_remove_none(lengthMenu=lengthMenu)) def test_ordered_categories(): diff --git a/tests/test_update_samples.py b/tests/test_update_samples.py index cf683c11..34e7ac19 100644 --- a/tests/test_update_samples.py +++ b/tests/test_update_samples.py @@ -1,25 +1,26 @@ from pathlib import Path -import pytest import world_bank_data as wb -sample_dir = Path(__file__).parent / ".." / "itables" / "samples" +SAMPLE_DIR = Path(__file__).parent / ".." / "itables" / "samples" -def test_update_countries(): +def create_csv_file_if_missing(df, csv_file): + if not csv_file.exists(): + with open(str(csv_file), "w") as fp: + fp.write(df.to_csv()) + + +def test_update_countries(csv_file=SAMPLE_DIR / "countries.csv"): df = wb.get_countries() - with open(str(sample_dir / "countries.csv"), "w") as fp: - fp.write(df.to_csv()) + create_csv_file_if_missing(df, csv_file) -def test_update_population(): +def test_update_population(csv_file=SAMPLE_DIR / "population.csv"): x = wb.get_series("SP.POP.TOTL", mrv=1, simplify_index=True) - with open(str(sample_dir / "population.csv"), "w") as fp: - fp.write(x.to_csv()) + create_csv_file_if_missing(x, csv_file) -@pytest.mark.skip("The indicators appear to change often") -def test_update_indicators(): +def test_update_indicators(csv_file=SAMPLE_DIR / "indicators.csv"): df = wb.get_indicators().sort_index().head(500) - with open(str(sample_dir / "indicators.csv"), "w") as fp: - fp.write(df.to_csv()) + create_csv_file_if_missing(df, csv_file)