diff --git a/docs/changelog.md b/docs/changelog.md
index 1e48a374..fba71996 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -1,6 +1,13 @@
ITables ChangeLog
=================
+1.6.1 (2023-10-01)
+------------------
+
+**Fixed**
+- We have fixed an issue when rendering Pandas style objects in Google Colab ([#199](https://github.com/mwouts/itables/issues/199))
+
+
1.6.0 (2023-09-30)
------------------
diff --git a/itables/javascript.py b/itables/javascript.py
index 999b15b4..e9b2fdc5 100644
--- a/itables/javascript.py
+++ b/itables/javascript.py
@@ -30,6 +30,7 @@
logger = logging.getLogger(__name__)
_OPTIONS_NOT_AVAILABLE_WITH_TO_HTML = {
+ "tags",
"footer",
"column_filters",
"maxBytes",
@@ -226,7 +227,15 @@ def replace_value(template, pattern, value):
after making sure that the pattern is found exactly once."""
if sys.version_info >= (3,):
assert isinstance(template, str)
- assert template.count(pattern) == 1
+ count = template.count(pattern)
+ if not count:
+ raise ValueError("pattern={} was not found in template".format(pattern))
+ elif count > 1:
+ raise ValueError(
+ "pattern={} was found multiple times ({}) in template".format(
+ pattern, count
+ )
+ )
return template.replace(pattern, value)
@@ -410,12 +419,6 @@ def to_html_datatable_using_to_html(
# These options are used here, not in DataTable
classes = kwargs.pop("classes")
style = kwargs.pop("style")
- tags = kwargs.pop("tags")
-
- if caption is not None:
- tags = '{}
{}'.format(
- tags, caption
- )
showIndex = kwargs.pop("showIndex")
@@ -434,36 +437,51 @@ def to_html_datatable_using_to_html(
# Polars DataFrame
showIndex = False
- if not showIndex:
- try:
- df.hide()
- except AttributeError:
- # Not a Pandas Styler object
- pass
-
if "dom" not in kwargs and _df_fits_in_one_page(df, kwargs):
kwargs["dom"] = "t"
- tableId = tableId or str(uuid.uuid4())
+ tableId = (
+ tableId
+ # default UUID in Pandas styler objects has uuid_len=5
+ or str(uuid.uuid4())[:5]
+ )
if isinstance(df, pd_style.Styler):
- df.set_uuid(tableId)
- tableId = "T_" + tableId
- table_html = df.to_html()
- else:
- table_html = df.to_html(table_id=tableId)
+ if not showIndex:
+ try:
+ df = df.hide()
+ except AttributeError:
+ pass
- if style:
- style = 'style="{}"'.format(style)
- else:
- style = ""
+ if style:
+ style = 'style="{}"'.format(style)
+ else:
+ style = ""
- html_table = replace_value(
- table_html,
- ''.format(tableId),
- """{tags}""".format(
- tableId=tableId, classes=classes, style=style, tags=tags
- ),
- )
+ try:
+ to_html_args = dict(
+ table_uuid=tableId,
+ table_attributes="""class="{classes}"{style}""".format(
+ classes=classes, style=style
+ ),
+ caption=caption,
+ )
+ html_table = df.to_html(**to_html_args)
+ except TypeError:
+ if caption is not None:
+ warnings.warn(
+ "caption is not supported by Styler.to_html in your version of Pandas"
+ )
+ del to_html_args["caption"]
+ html_table = df.to_html(**to_html_args)
+ tableId = "T_" + tableId
+ else:
+ if caption is not None:
+ raise NotImplementedError(
+ "caption is not supported when using df.to_html. "
+ "Use either Pandas Style, or set use_to_html=False."
+ )
+ # NB: style is not available neither
+ html_table = df.to_html(table_id=tableId, classes=classes)
return html_table_from_template(
html_table,
@@ -606,5 +624,6 @@ def safe_reset_index(df):
def show(df=None, caption=None, **kwargs):
"""Show a dataframe"""
- html = to_html_datatable(df, caption=caption, connected=_CONNECTED, **kwargs)
+ connected = kwargs.pop("connected", _CONNECTED)
+ html = to_html_datatable(df, caption=caption, connected=connected, **kwargs)
display(HTML(html))
diff --git a/itables/version.py b/itables/version.py
index c99b4ffe..acd2cad8 100644
--- a/itables/version.py
+++ b/itables/version.py
@@ -1,3 +1,3 @@
"""ITables' version number"""
-__version__ = "1.6.0"
+__version__ = "1.6.1"
diff --git a/tests/conftest.py b/tests/conftest.py
index 77811b2d..d5abdc24 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,7 +1,7 @@
import pandas as pd
import pytest
-from itables.sample_dfs import get_dict_of_test_dfs
+from itables.sample_dfs import PANDAS_VERSION_MAJOR, get_dict_of_test_dfs
@pytest.fixture(params=list(get_dict_of_test_dfs()))
@@ -19,3 +19,13 @@ def lengthMenu(request):
if request.param == "2D-array":
return [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]]
return None
+
+
+@pytest.fixture(params=[False, True])
+def connected(request):
+ return request.param
+
+
+@pytest.fixture(params=[False, True] if PANDAS_VERSION_MAJOR >= 1 else [False])
+def use_to_html(request):
+ return request.param
diff --git a/tests/test_javascript.py b/tests/test_javascript.py
index d73205c0..9e45108f 100644
--- a/tests/test_javascript.py
+++ b/tests/test_javascript.py
@@ -1,4 +1,26 @@
-from itables.javascript import _df_fits_in_one_page, to_html_datatable
+import pytest
+
+from itables.javascript import _df_fits_in_one_page, replace_value, to_html_datatable
+
+
+def test_replace_value(
+ template="line1\nline2\nline3\n", pattern="line2", value="new line2"
+):
+ assert replace_value(template, pattern, value) == "line1\nnew line2\nline3\n"
+
+
+def test_replace_value_not_found(
+ template="line1\nline2\nline3\n", pattern="line4", value="new line4"
+):
+ with pytest.raises(ValueError, match="not found"):
+ assert replace_value(template, pattern, value)
+
+
+def test_replace_value_multiple(
+ template="line1\nline2\nline2\n", pattern="line2", value="new line2"
+):
+ with pytest.raises(ValueError, match="found multiple times"):
+ assert replace_value(template, pattern, value)
def test_warn_on_unexpected_types_not_in_html(df):
diff --git a/tests/test_json_dumps.py b/tests/test_json_dumps.py
index 937f6a13..a1cc286b 100644
--- a/tests/test_json_dumps.py
+++ b/tests/test_json_dumps.py
@@ -27,19 +27,19 @@ def coloredColumnDefs():
]
-def test_warning_when_eval_functions_is_missing(df, coloredColumnDefs):
+def test_warning_when_eval_functions_is_missing(df, coloredColumnDefs, connected):
with pytest.warns(UserWarning, match="starts with 'function'"):
- show(df, columnDefs=coloredColumnDefs)
+ show(df, connected=connected, columnDefs=coloredColumnDefs)
-def test_no_warning_when_eval_functions_is_false(df, coloredColumnDefs):
+def test_no_warning_when_eval_functions_is_false(df, coloredColumnDefs, connected):
warnings.simplefilter("error")
- show(df, columnDefs=coloredColumnDefs, eval_functions=False)
+ show(df, connected=connected, columnDefs=coloredColumnDefs, eval_functions=False)
-def test_no_warning_when_eval_functions_is_true(df, coloredColumnDefs):
+def test_no_warning_when_eval_functions_is_true(df, coloredColumnDefs, connected):
warnings.simplefilter("error")
- show(df, columnDefs=coloredColumnDefs, eval_functions=True)
+ show(df, connected=connected, columnDefs=coloredColumnDefs, eval_functions=True)
@pytest.mark.parametrize("obj", ["a", 1, 1.0, [1.0, "a", {"a": [0, 1]}]])
diff --git a/tests/test_polars.py b/tests/test_polars.py
index c8fa5fa6..19d40e68 100644
--- a/tests/test_polars.py
+++ b/tests/test_polars.py
@@ -12,12 +12,12 @@
@pytest.mark.parametrize(
"name,x", [(name, x) for name, x in get_dict_of_test_series(polars=True).items()]
)
-def test_show_polars_series(name, x):
- to_html_datatable(x)
+def test_show_polars_series(name, x, use_to_html):
+ to_html_datatable(x, use_to_html)
@pytest.mark.parametrize(
"name,df", [(name, df) for name, df in get_dict_of_test_dfs(polars=True).items()]
)
-def test_show_polars_df(name, df):
- to_html_datatable(df)
+def test_show_polars_df(name, df, use_to_html):
+ to_html_datatable(df, use_to_html)
diff --git a/tests/test_sample_dfs.py b/tests/test_sample_dfs.py
index b31a3f12..7001f752 100644
--- a/tests/test_sample_dfs.py
+++ b/tests/test_sample_dfs.py
@@ -32,51 +32,61 @@
pytestmark.append(pytest.mark.filterwarnings("ignore::DeprecationWarning"))
-def test_get_countries():
+def test_get_countries(connected, use_to_html):
df = get_countries()
assert len(df.columns) > 5
assert len(df.index) > 100
- show(df)
+ show(df, connected=connected, use_to_html=use_to_html)
@pytest.mark.skipif(sys.version_info < (3,), reason="fails in Py2")
-def test_get_population():
+def test_get_population(connected, use_to_html):
x = get_population()
assert len(x) > 30
assert x.max() > 7e9
- show(x)
+ show(x, connected=connected, use_to_html=use_to_html)
-def test_get_indicators():
+def test_get_indicators(connected, use_to_html):
df = get_indicators()
assert len(df.index) == 500
assert len(df.columns)
- show(df)
+ show(df, connected=connected, use_to_html=use_to_html)
@pytest.mark.skipif(
sys.version_info < (3, 7),
reason="AttributeError: 'Styler' object has no attribute 'to_html'",
)
-def test_get_pandas_styler():
+def test_get_pandas_styler(connected, use_to_html):
styler = get_pandas_styler()
- show(styler)
+ show(styler, connected=connected, use_to_html=use_to_html)
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, monkeypatch):
+def test_show_test_dfs(df, connected, use_to_html, lengthMenu, monkeypatch):
if "bigint" in df.columns:
monkeypatch.setattr("itables.options.warn_on_int_to_str_conversion", False)
- show(df, **kwargs_remove_none(lengthMenu=lengthMenu))
+ show(
+ df,
+ connected=connected,
+ use_to_html=use_to_html,
+ **kwargs_remove_none(lengthMenu=lengthMenu)
+ )
-def test_to_html_datatable(df, lengthMenu, monkeypatch):
+def test_to_html_datatable(df, connected, use_to_html, lengthMenu, monkeypatch):
if "bigint" in df.columns:
monkeypatch.setattr("itables.options.warn_on_int_to_str_conversion", False)
- to_html_datatable(df, **kwargs_remove_none(lengthMenu=lengthMenu))
+ to_html_datatable(
+ df,
+ connected=connected,
+ use_to_html=use_to_html,
+ **kwargs_remove_none(lengthMenu=lengthMenu)
+ )
def test_ordered_categories():
@@ -92,16 +102,16 @@ def test_format_column(series_name, series):
@pytest.mark.parametrize("series_name,series", get_dict_of_test_series().items())
-def test_show_test_series(series_name, series, monkeypatch):
+def test_show_test_series(series_name, series, connected, use_to_html, monkeypatch):
if "bigint" in series_name:
monkeypatch.setattr("itables.options.warn_on_int_to_str_conversion", False)
- show(series)
+ show(series, connected=connected, use_to_html=use_to_html)
-def test_show_df_with_duplicate_column_names():
+def test_show_df_with_duplicate_column_names(connected, use_to_html):
df = pd.DataFrame({"a": [0], "b": [0.0], "c": ["str"]})
df.columns = ["duplicated_name"] * 3
- show(df)
+ show(df, connected=connected, use_to_html=use_to_html)
@pytest.mark.parametrize("type", COLUMN_TYPES)