from textwrap import (
dedent,
indent,
)
import numpy as np
import pytest
from pandas import (
DataFrame,
MultiIndex,
option_context,
)
jinja2 = pytest.importorskip("jinja2")
from pandas.io.formats.style import Styler
@pytest.fixture
def env():
loader = jinja2.PackageLoader("pandas", "io/formats/templates")
env = jinja2.Environment(loader=loader, trim_blocks=True)
return env
@pytest.fixture
def styler():
return Styler(DataFrame([[2.61], [2.69]], index=["a", "b"], columns=["A"]))
@pytest.fixture
def styler_mi():
midx = MultiIndex.from_product([["a", "b"], ["c", "d"]])
return Styler(DataFrame(np.arange(16).reshape(4, 4), index=midx, columns=midx))
@pytest.fixture
def tpl_style(env):
return env.get_template("html_style.tpl")
@pytest.fixture
def tpl_table(env):
return env.get_template("html_table.tpl")
def test_html_template_extends_options():
# make sure if templates are edited tests are updated as are setup fixtures
# to understand the dependency
with open("pandas/io/formats/templates/html.tpl", encoding="utf-8") as file:
result = file.read()
assert "{% include html_style_tpl %}" in result
assert "{% include html_table_tpl %}" in result
def test_exclude_styles(styler):
result = styler.to_html(exclude_styles=True, doctype_html=True)
expected = dedent(
"""\
"""
)
assert result == expected
def test_w3_html_format(styler):
styler.set_uuid("").set_table_styles([{"selector": "th", "props": "att2:v2;"}]).map(
lambda x: "att1:v1;"
).set_table_attributes('class="my-cls1" style="attr3:v3;"').set_td_classes(
DataFrame(["my-cls2"], index=["a"], columns=["A"])
).format(
"{:.1f}"
).set_caption(
"A comprehensive test"
)
expected = dedent(
"""\
A comprehensive test
A
a
2.6
b
2.7
"""
)
assert expected == styler.to_html()
def test_colspan_w3():
# GH 36223
df = DataFrame(data=[[1, 2]], columns=[["l0", "l0"], ["l1a", "l1b"]])
styler = Styler(df, uuid="_", cell_ids=False)
assert 'l0 ' in styler.to_html()
def test_rowspan_w3():
# GH 38533
df = DataFrame(data=[[1, 2]], index=[["l0", "l0"], ["l1a", "l1b"]])
styler = Styler(df, uuid="_", cell_ids=False)
assert 'l0 ' in styler.to_html()
def test_styles(styler):
styler.set_uuid("abc")
styler.set_table_styles([{"selector": "td", "props": "color: red;"}])
result = styler.to_html(doctype_html=True)
expected = dedent(
"""\
"""
)
assert result == expected
def test_doctype(styler):
result = styler.to_html(doctype_html=False)
assert "" not in result
assert "" not in result
assert "" not in result
assert "" not in result
def test_doctype_encoding(styler):
with option_context("styler.render.encoding", "ASCII"):
result = styler.to_html(doctype_html=True)
assert ' ' in result
result = styler.to_html(doctype_html=True, encoding="ANSI")
assert ' ' in result
def test_bold_headers_arg(styler):
result = styler.to_html(bold_headers=True)
assert "th {\n font-weight: bold;\n}" in result
result = styler.to_html()
assert "th {\n font-weight: bold;\n}" not in result
def test_caption_arg(styler):
result = styler.to_html(caption="foo bar")
assert "foo bar " in result
result = styler.to_html()
assert "foo bar " not in result
def test_block_names(tpl_style, tpl_table):
# catch accidental removal of a block
expected_style = {
"before_style",
"style",
"table_styles",
"before_cellstyle",
"cellstyle",
}
expected_table = {
"before_table",
"table",
"caption",
"thead",
"tbody",
"after_table",
"before_head_rows",
"head_tr",
"after_head_rows",
"before_rows",
"tr",
"after_rows",
}
result1 = set(tpl_style.blocks)
assert result1 == expected_style
result2 = set(tpl_table.blocks)
assert result2 == expected_table
def test_from_custom_template_table(tmpdir):
p = tmpdir.mkdir("tpl").join("myhtml_table.tpl")
p.write(
dedent(
"""\
{% extends "html_table.tpl" %}
{% block table %}
{{custom_title}}
{{ super() }}
{% endblock table %}"""
)
)
result = Styler.from_custom_template(str(tmpdir.join("tpl")), "myhtml_table.tpl")
assert issubclass(result, Styler)
assert result.env is not Styler.env
assert result.template_html_table is not Styler.template_html_table
styler = result(DataFrame({"A": [1, 2]}))
assert "My Title \n\n\n
{{ super() }}
{% endblock style %}"""
)
)
result = Styler.from_custom_template(
str(tmpdir.join("tpl")), html_style="myhtml_style.tpl"
)
assert issubclass(result, Styler)
assert result.env is not Styler.env
assert result.template_html_style is not Styler.template_html_style
styler = result(DataFrame({"A": [1, 2]}))
assert ' \n\n
"""
)
result = styler_mi.to_html()
assert result == expected
def test_include_css_style_rules_only_for_visible_cells(styler_mi):
# GH 43619
result = (
styler_mi.set_uuid("")
.map(lambda v: "color: blue;")
.hide(styler_mi.data.columns[1:], axis="columns")
.hide(styler_mi.data.index[1:], axis="index")
.to_html()
)
expected_styles = dedent(
"""\
"""
)
assert expected_styles in result
def test_include_css_style_rules_only_for_visible_index_labels(styler_mi):
# GH 43619
result = (
styler_mi.set_uuid("")
.map_index(lambda v: "color: blue;", axis="index")
.hide(styler_mi.data.columns, axis="columns")
.hide(styler_mi.data.index[1:], axis="index")
.to_html()
)
expected_styles = dedent(
"""\
"""
)
assert expected_styles in result
def test_include_css_style_rules_only_for_visible_column_labels(styler_mi):
# GH 43619
result = (
styler_mi.set_uuid("")
.map_index(lambda v: "color: blue;", axis="columns")
.hide(styler_mi.data.columns[1:], axis="columns")
.hide(styler_mi.data.index, axis="index")
.to_html()
)
expected_styles = dedent(
"""\
"""
)
assert expected_styles in result
def test_hiding_index_columns_multiindex_alignment():
# gh 43644
midx = MultiIndex.from_product(
[["i0", "j0"], ["i1"], ["i2", "j2"]], names=["i-0", "i-1", "i-2"]
)
cidx = MultiIndex.from_product(
[["c0"], ["c1", "d1"], ["c2", "d2"]], names=["c-0", "c-1", "c-2"]
)
df = DataFrame(np.arange(16).reshape(4, 4), index=midx, columns=cidx)
styler = Styler(df, uuid_len=0)
styler.hide(level=1, axis=0).hide(level=0, axis=1)
styler.hide([("j0", "i1", "j2")], axis=0)
styler.hide([("c0", "d1", "d2")], axis=1)
result = styler.to_html()
expected = dedent(
"""\
c-1
c1
d1
c-2
c2
d2
c2
i-0
i-2
i0
i2
0
1
2
j2
4
5
6
j0
i2
8
9
10
"""
)
assert result == expected
def test_hiding_index_columns_multiindex_trimming():
# gh 44272
df = DataFrame(np.arange(64).reshape(8, 8))
df.columns = MultiIndex.from_product([[0, 1, 2, 3], [0, 1]])
df.index = MultiIndex.from_product([[0, 1, 2, 3], [0, 1]])
df.index.names, df.columns.names = ["a", "b"], ["c", "d"]
styler = Styler(df, cell_ids=False, uuid_len=0)
styler.hide([(0, 0), (0, 1), (1, 0)], axis=1).hide([(0, 0), (0, 1), (1, 0)], axis=0)
with option_context("styler.render.max_rows", 4, "styler.render.max_columns", 4):
result = styler.to_html()
expected = dedent(
"""\
c
1
2
3
d
1
0
1
0
...
a
b
1
1
27
28
29
30
...
2
0
35
36
37
38
...
1
43
44
45
46
...
3
0
51
52
53
54
...
...
...
...
...
...
...
...
"""
)
assert result == expected
@pytest.mark.parametrize("type", ["data", "index"])
@pytest.mark.parametrize(
"text, exp, found",
[
("no link, just text", False, ""),
("subdomain not www: sub.web.com", False, ""),
("www subdomain: www.web.com other", True, "www.web.com"),
("scheme full structure: http://www.web.com", True, "http://www.web.com"),
("scheme no top-level: http://www.web", True, "http://www.web"),
("no scheme, no top-level: www.web", False, "www.web"),
("https scheme: https://www.web.com", True, "https://www.web.com"),
("ftp scheme: ftp://www.web", True, "ftp://www.web"),
("ftps scheme: ftps://www.web", True, "ftps://www.web"),
("subdirectories: www.web.com/directory", True, "www.web.com/directory"),
("Multiple domains: www.1.2.3.4", True, "www.1.2.3.4"),
("with port: http://web.com:80", True, "http://web.com:80"),
(
"full net_loc scheme: http://user:pass@web.com",
True,
"http://user:pass@web.com",
),
(
"with valid special chars: http://web.com/,.':;~!@#$*()[]",
True,
"http://web.com/,.':;~!@#$*()[]",
),
],
)
def test_rendered_links(type, text, exp, found):
if type == "data":
df = DataFrame([text])
styler = df.style.format(hyperlinks="html")
else:
df = DataFrame([0], index=[text])
styler = df.style.format_index(hyperlinks="html")
rendered = f'{found} '
result = styler.to_html()
assert (rendered in result) is exp
assert (text in result) is not exp # test conversion done when expected and not
def test_multiple_rendered_links():
links = ("www.a.b", "http://a.c", "https://a.d", "ftp://a.e")
# pylint: disable-next=consider-using-f-string
df = DataFrame(["text {} {} text {} {}".format(*links)])
result = df.style.format(hyperlinks="html").to_html()
href = '{0} '
for link in links:
assert href.format(link) in result
assert href.format("text") not in result
def test_concat(styler):
other = styler.data.agg(["mean"]).style
styler.concat(other).set_uuid("X")
result = styler.to_html()
fp = "foot0_"
expected = dedent(
f"""\
b
2.690000
mean
2.650000
"""
)
assert expected in result
def test_concat_recursion(styler):
df = styler.data
styler1 = styler
styler2 = Styler(df.agg(["mean"]), precision=3)
styler3 = Styler(df.agg(["mean"]), precision=4)
styler1.concat(styler2.concat(styler3)).set_uuid("X")
result = styler.to_html()
# notice that the second concat (last of the output html),
# there are two `foot_` in the id and class
fp1 = "foot0_"
fp2 = "foot0_foot0_"
expected = dedent(
f"""\
b
2.690000
mean
2.650
mean
2.6500
"""
)
assert expected in result
def test_concat_chain(styler):
df = styler.data
styler1 = styler
styler2 = Styler(df.agg(["mean"]), precision=3)
styler3 = Styler(df.agg(["mean"]), precision=4)
styler1.concat(styler2).concat(styler3).set_uuid("X")
result = styler.to_html()
fp1 = "foot0_"
fp2 = "foot1_"
expected = dedent(
f"""\
b
2.690000
mean
2.650
mean
2.6500
"""
)
assert expected in result
def test_concat_combined():
def html_lines(foot_prefix: str):
assert foot_prefix.endswith("_") or foot_prefix == ""
fp = foot_prefix
return indent(
dedent(
f"""\
a
2.610000
b
2.690000
"""
),
prefix=" " * 4,
)
df = DataFrame([[2.61], [2.69]], index=["a", "b"], columns=["A"])
s1 = df.style.highlight_max(color="red")
s2 = df.style.highlight_max(color="green")
s3 = df.style.highlight_max(color="blue")
s4 = df.style.highlight_max(color="yellow")
result = s1.concat(s2).concat(s3.concat(s4)).set_uuid("X").to_html()
expected_css = dedent(
"""\
"""
)
expected_table = (
dedent(
"""\
A
"""
)
+ html_lines("")
+ html_lines("foot0_")
+ html_lines("foot1_")
+ html_lines("foot1_foot0_")
+ dedent(
"""\
"""
)
)
assert expected_css + expected_table == result
def test_to_html_na_rep_non_scalar_data(datapath):
# GH47103
df = DataFrame([{"a": 1, "b": [1, 2, 3], "c": np.nan}])
result = df.style.format(na_rep="-").to_html(table_uuid="test")
expected = """\
"""
assert result == expected