Skip to content

Commit 2d11544

Browse files
attack68JulianWgs
authored andcommitted
REGR: Subclassing Styler and addressing from_custom_template (pandas-dev#42091)
1 parent 4aeb113 commit 2d11544

File tree

10 files changed

+159
-41
lines changed

10 files changed

+159
-41
lines changed

doc/source/reference/style.rst

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ Styler properties
2424

2525
Styler.env
2626
Styler.template_html
27+
Styler.template_html_style
28+
Styler.template_html_table
2729
Styler.template_latex
2830
Styler.loader
2931

doc/source/user_guide/style.ipynb

+53-7
Original file line numberDiff line numberDiff line change
@@ -1780,7 +1780,7 @@
17801780
" Styler.loader, # the default\n",
17811781
" ])\n",
17821782
" )\n",
1783-
" template_html = env.get_template(\"myhtml.tpl\")"
1783+
" template_html_table = env.get_template(\"myhtml.tpl\")"
17841784
]
17851785
},
17861786
{
@@ -1833,14 +1833,35 @@
18331833
"outputs": [],
18341834
"source": [
18351835
"EasyStyler = Styler.from_custom_template(\"templates\", \"myhtml.tpl\")\n",
1836-
"EasyStyler(df3)"
1836+
"HTML(EasyStyler(df3).render(table_title=\"Another Title\"))"
18371837
]
18381838
},
18391839
{
18401840
"cell_type": "markdown",
18411841
"metadata": {},
18421842
"source": [
1843-
"Here's the template structure:"
1843+
"#### Template Structure\n",
1844+
"\n",
1845+
"Here's the template structure for the both the style generation template and the table generation template:"
1846+
]
1847+
},
1848+
{
1849+
"cell_type": "markdown",
1850+
"metadata": {},
1851+
"source": [
1852+
"Style template:"
1853+
]
1854+
},
1855+
{
1856+
"cell_type": "code",
1857+
"execution_count": null,
1858+
"metadata": {
1859+
"nbsphinx": "hidden"
1860+
},
1861+
"outputs": [],
1862+
"source": [
1863+
"with open(\"templates/html_style_structure.html\") as f:\n",
1864+
" style_structure = f.read()"
18441865
]
18451866
},
18461867
{
@@ -1849,10 +1870,35 @@
18491870
"metadata": {},
18501871
"outputs": [],
18511872
"source": [
1852-
"with open(\"templates/template_structure.html\") as f:\n",
1853-
" structure = f.read()\n",
1854-
" \n",
1855-
"HTML(structure)"
1873+
"HTML(style_structure)"
1874+
]
1875+
},
1876+
{
1877+
"cell_type": "markdown",
1878+
"metadata": {},
1879+
"source": [
1880+
"Table template:"
1881+
]
1882+
},
1883+
{
1884+
"cell_type": "code",
1885+
"execution_count": null,
1886+
"metadata": {
1887+
"nbsphinx": "hidden"
1888+
},
1889+
"outputs": [],
1890+
"source": [
1891+
"with open(\"templates/html_table_structure.html\") as f:\n",
1892+
" table_structure = f.read()"
1893+
]
1894+
},
1895+
{
1896+
"cell_type": "code",
1897+
"execution_count": null,
1898+
"metadata": {},
1899+
"outputs": [],
1900+
"source": [
1901+
"HTML(table_structure)"
18561902
]
18571903
},
18581904
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<!--
2+
This is an HTML fragment that gets included into a notebook & rst document
3+
4+
Inspired by nbconvert
5+
6+
https://github.com/jupyter/nbconvert/blob/8ac591a0b8694147d0f34bf6392594c2811c1395/docs/source/template_structure.html
7+
8+
9+
-->
10+
<style type="text/css">
11+
/* Overrides of notebook CSS for static HTML export */
12+
.template_block {
13+
background-color: hsla(120, 60%, 70%, 0.2);
14+
margin: 10px;
15+
padding: 5px;
16+
border: 1px solid hsla(120, 60%, 70%, 0.5);
17+
border-left: 2px solid black;
18+
}
19+
.template_block pre {
20+
background: transparent;
21+
padding: 0;
22+
}
23+
.big_vertical_ellipsis {
24+
font-size: 24pt;
25+
}
26+
</style>
27+
28+
<div class="template_block">before_style</div>
29+
<div class="template_block">style
30+
<pre>&lt;style type=&quot;text/css&quot;&gt;</pre>
31+
<div class="template_block">table_styles</div>
32+
<div class="template_block">before_cellstyle</div>
33+
<div class="template_block">cellstyle</div>
34+
<pre>&lt;/style&gt;</pre>
35+
</div><!-- /style -->

doc/source/user_guide/templates/template_structure.html renamed to doc/source/user_guide/templates/html_table_structure.html

-9
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,6 @@
2525
}
2626
</style>
2727

28-
<div class="template_block">before_style</div>
29-
<div class="template_block">style
30-
<pre>&lt;style type=&quot;text/css&quot;&gt;</pre>
31-
<div class="template_block">table_styles</div>
32-
<div class="template_block">before_cellstyle</div>
33-
<div class="template_block">cellstyle</div>
34-
<pre>&lt;/style&gt;</pre>
35-
</div><!-- /style -->
36-
3728
<div class="template_block" >before_table</div>
3829

3930
<div class="template_block" >table

doc/source/user_guide/templates/myhtml.tpl

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{% extends "html.tpl" %}
1+
{% extends "html_table.tpl" %}
22
{% block table %}
33
<h1>{{ table_title|default("My Table") }}</h1>
44
{{ super() }}

doc/source/whatsnew/v1.3.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,7 @@ Other API changes
707707
- Added new ``engine`` and ``**engine_kwargs`` parameters to :meth:`DataFrame.to_sql` to support other future "SQL engines". Currently we still only use ``SQLAlchemy`` under the hood, but more engines are planned to be supported such as `turbodbc <https://turbodbc.readthedocs.io/en/latest/>`_ (:issue:`36893`)
708708
- Removed redundant ``freq`` from :class:`PeriodIndex` string representation (:issue:`41653`)
709709
- :meth:`ExtensionDtype.construct_array_type` is now a required method instead of an optional one for :class:`ExtensionDtype` subclasses (:issue:`24860`)
710+
- :meth:`.Styler.from_custom_template` now has two new arguments for template names, and removed the old ``name``, due to template inheritance having been introducing for better parsing (:issue:`42053`). Subclassing modifications to Styler attributes are also needed.
710711

711712
.. _whatsnew_130.api_breaking.build:
712713

pandas/io/formats/style.py

+21-6
Original file line numberDiff line numberDiff line change
@@ -2550,23 +2550,35 @@ def highlight_quantile(
25502550
)
25512551

25522552
@classmethod
2553-
def from_custom_template(cls, searchpath, name):
2553+
def from_custom_template(
2554+
cls, searchpath, html_table: str | None = None, html_style: str | None = None
2555+
):
25542556
"""
25552557
Factory function for creating a subclass of ``Styler``.
25562558
2557-
Uses a custom template and Jinja environment.
2559+
Uses custom templates and Jinja environment.
2560+
2561+
.. versionchanged:: 1.3.0
25582562
25592563
Parameters
25602564
----------
25612565
searchpath : str or list
25622566
Path or paths of directories containing the templates.
2563-
name : str
2564-
Name of your custom template to use for rendering.
2567+
html_table : str
2568+
Name of your custom template to replace the html_table template.
2569+
2570+
.. versionadded:: 1.3.0
2571+
2572+
html_style : str
2573+
Name of your custom template to replace the html_style template.
2574+
2575+
.. versionadded:: 1.3.0
25652576
25662577
Returns
25672578
-------
25682579
MyStyler : subclass of Styler
2569-
Has the correct ``env`` and ``template`` class attributes set.
2580+
Has the correct ``env``,``template_html``, ``template_html_table`` and
2581+
``template_html_style`` class attributes set.
25702582
"""
25712583
loader = jinja2.ChoiceLoader([jinja2.FileSystemLoader(searchpath), cls.loader])
25722584

@@ -2575,7 +2587,10 @@ def from_custom_template(cls, searchpath, name):
25752587
# error: Invalid base class "cls"
25762588
class MyStyler(cls): # type:ignore[valid-type,misc]
25772589
env = jinja2.Environment(loader=loader)
2578-
template_html = env.get_template(name)
2590+
if html_table:
2591+
template_html_table = env.get_template(html_table)
2592+
if html_style:
2593+
template_html_style = env.get_template(html_style)
25792594

25802595
return MyStyler
25812596

pandas/io/formats/style_render.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ class StylerRenderer:
6767
loader = jinja2.PackageLoader("pandas", "io/formats/templates")
6868
env = jinja2.Environment(loader=loader, trim_blocks=True)
6969
template_html = env.get_template("html.tpl")
70+
template_html_table = env.get_template("html_table.tpl")
71+
template_html_style = env.get_template("html_style.tpl")
7072
template_latex = env.get_template("latex.tpl")
7173

7274
def __init__(
@@ -120,7 +122,11 @@ def _render_html(self, sparse_index: bool, sparse_columns: bool, **kwargs) -> st
120122
# TODO: namespace all the pandas keys
121123
d = self._translate(sparse_index, sparse_columns)
122124
d.update(kwargs)
123-
return self.template_html.render(**d)
125+
return self.template_html.render(
126+
**d,
127+
html_table_tpl=self.template_html_table,
128+
html_style_tpl=self.template_html_style,
129+
)
124130

125131
def _render_latex(self, sparse_index: bool, sparse_columns: bool, **kwargs) -> str:
126132
"""

pandas/io/formats/templates/html.tpl

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
{# Update the template_structure.html documentation too #}
1+
{# Update the html_style/table_structure.html documentation too #}
22
{% if doctype_html %}
33
<!DOCTYPE html>
44
<html>
55
<head>
66
<meta charset="{{encoding}}">
7-
{% if not exclude_styles %}{% include "html_style.tpl" %}{% endif %}
7+
{% if not exclude_styles %}{% include html_style_tpl %}{% endif %}
88
</head>
99
<body>
10-
{% include "html_table.tpl" %}
10+
{% include html_table_tpl %}
1111
</body>
1212
</html>
1313
{% elif not doctype_html %}
14-
{% if not exclude_styles %}{% include "html_style.tpl" %}{% endif %}
15-
{% include "html_table.tpl" %}
14+
{% if not exclude_styles %}{% include html_style_tpl %}{% endif %}
15+
{% include html_table_tpl %}
1616
{% endif %}

pandas/tests/io/formats/style/test_html.py

+34-12
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ def test_html_template_extends_options():
4141
# to understand the dependency
4242
with open("pandas/io/formats/templates/html.tpl") as file:
4343
result = file.read()
44-
assert '{% include "html_style.tpl" %}' in result
45-
assert '{% include "html_table.tpl" %}' in result
44+
assert "{% include html_style_tpl %}" in result
45+
assert "{% include html_table_tpl %}" in result
4646

4747

4848
def test_exclude_styles(styler):
@@ -223,24 +223,46 @@ def test_block_names(tpl_style, tpl_table):
223223
assert result2 == expected_table
224224

225225

226-
def test_from_custom_template(tmpdir):
227-
p = tmpdir.mkdir("templates").join("myhtml.tpl")
226+
def test_from_custom_template_table(tmpdir):
227+
p = tmpdir.mkdir("tpl").join("myhtml_table.tpl")
228228
p.write(
229229
dedent(
230230
"""\
231-
{% extends "html.tpl" %}
232-
{% block table %}
233-
<h1>{{ table_title|default("My Table") }}</h1>
234-
{{ super() }}
235-
{% endblock table %}"""
231+
{% extends "html_table.tpl" %}
232+
{% block table %}
233+
<h1>{{custom_title}}</h1>
234+
{{ super() }}
235+
{% endblock table %}"""
236236
)
237237
)
238-
result = Styler.from_custom_template(str(tmpdir.join("templates")), "myhtml.tpl")
238+
result = Styler.from_custom_template(str(tmpdir.join("tpl")), "myhtml_table.tpl")
239239
assert issubclass(result, Styler)
240240
assert result.env is not Styler.env
241-
assert result.template_html is not Styler.template_html
241+
assert result.template_html_table is not Styler.template_html_table
242242
styler = result(DataFrame({"A": [1, 2]}))
243-
assert styler.render()
243+
assert "<h1>My Title</h1>\n\n\n<table" in styler.render(custom_title="My Title")
244+
245+
246+
def test_from_custom_template_style(tmpdir):
247+
p = tmpdir.mkdir("tpl").join("myhtml_style.tpl")
248+
p.write(
249+
dedent(
250+
"""\
251+
{% extends "html_style.tpl" %}
252+
{% block style %}
253+
<link rel="stylesheet" href="mystyle.css">
254+
{{ super() }}
255+
{% endblock style %}"""
256+
)
257+
)
258+
result = Styler.from_custom_template(
259+
str(tmpdir.join("tpl")), html_style="myhtml_style.tpl"
260+
)
261+
assert issubclass(result, Styler)
262+
assert result.env is not Styler.env
263+
assert result.template_html_style is not Styler.template_html_style
264+
styler = result(DataFrame({"A": [1, 2]}))
265+
assert '<link rel="stylesheet" href="mystyle.css">\n\n<style' in styler.render()
244266

245267

246268
def test_caption_as_sequence(styler):

0 commit comments

Comments
 (0)