diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index c599bcbfd4170..ba917aa4939eb 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -43,9 +43,6 @@ from pandas.core.shared_docs import _shared_docs from pandas.io.formats.format import save_to_buffer - -jinja2 = import_optional_dependency("jinja2", extra="DataFrame.style requires jinja2.") - from pandas.io.formats.style_render import ( CSSProperties, CSSStyles, @@ -78,22 +75,18 @@ from pandas import ExcelWriter -try: - import matplotlib as mpl - import matplotlib.pyplot as plt - - has_mpl = True -except ImportError: - has_mpl = False - @contextmanager def _mpl(func: Callable) -> Generator[tuple[Any, Any], None, None]: - if has_mpl: - yield plt, mpl - else: + try: + import matplotlib as mpl + import matplotlib.pyplot as plt + + except ImportError: raise ImportError(f"{func.__name__} requires matplotlib.") + yield plt, mpl + #### # Shared Doc Strings @@ -3490,6 +3483,10 @@ def from_custom_template( Has the correct ``env``,``template_html``, ``template_html_table`` and ``template_html_style`` class attributes set. """ + jinja2 = import_optional_dependency( + "jinja2", extra="DataFrame.style requires jinja2." + ) + loader = jinja2.ChoiceLoader([jinja2.FileSystemLoader(searchpath), cls.loader]) # mypy doesn't like dynamically-defined classes diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 7f2c237c8b296..b15a2c7a65a94 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -48,8 +48,6 @@ Axis, Level, ) -jinja2 = import_optional_dependency("jinja2", extra="DataFrame.style requires jinja2.") -from markupsafe import escape as escape_html # markupsafe is jinja2 dependency BaseFormatter = Union[str, Callable] ExtFormatter = Union[BaseFormatter, Dict[Any, Optional[BaseFormatter]]] @@ -72,13 +70,15 @@ class StylerRenderer: Base class to process rendering a Styler with a specified jinja2 template. """ - loader = jinja2.PackageLoader("pandas", "io/formats/templates") - env = jinja2.Environment(loader=loader, trim_blocks=True) - template_html = env.get_template("html.tpl") - template_html_table = env.get_template("html_table.tpl") - template_html_style = env.get_template("html_style.tpl") - template_latex = env.get_template("latex.tpl") - template_string = env.get_template("string.tpl") + # For cached class properties defined below + _loader = None + _env = None + custom_template_directory = None + template_html = "html.tpl" + template_html_table = "html_table.tpl" + template_html_style = "html_style.tpl" + template_latex = "latex.tpl" + template_string = "string.tpl" def __init__( self, @@ -147,6 +147,37 @@ def __init__( tuple[int, int], Callable[[Any], str] ] = defaultdict(lambda: partial(_default_formatter, precision=precision)) + @classmethod + @property + def loader(cls): + if cls._loader is None: + jinja2 = import_optional_dependency( + "jinja2", extra="DataFrame.style requires jinja2." + ) + cls._loader = jinja2.PackageLoader("pandas", "io/formats/templates") + return cls._loader + + @classmethod + @property + def env(cls): + if cls._env is None: + jinja2 = import_optional_dependency( + "jinja2", extra="DataFrame.style requires jinja2." + ) + if cls.custom_template_directory is None: + cls._env = jinja2.Environment(loader=cls.loader, trim_blocks=True) + else: + cls._env = jinja2.Environment( + loader=jinja2.ChoiceLoader( + [ + jinja2.FileSystemLoader(cls.custom_template_directory), + cls.loader, + ] + ), + trim_blocks=True, + ) + return cls._env + def _render( self, sparse_index: bool, @@ -206,10 +237,11 @@ def _render_html( """ d = self._render(sparse_index, sparse_columns, max_rows, max_cols, " ") d.update(kwargs) - return self.template_html.render( + template_html = self.env.get_template(self.template_html) + return template_html.render( **d, - html_table_tpl=self.template_html_table, - html_style_tpl=self.template_html_style, + html_table_tpl=self.env.get_template(self.template_html_table), + html_style_tpl=self.env.get_template(self.template_html_style), ) def _render_latex( @@ -219,13 +251,14 @@ def _render_latex( Render a Styler in latex format """ d = self._render(sparse_index, sparse_columns, None, None) + template_latex = self.env.get_template(self.template_latex) self._translate_latex(d, clines=clines) - self.template_latex.globals["parse_wrap"] = _parse_latex_table_wrapping - self.template_latex.globals["parse_table"] = _parse_latex_table_styles - self.template_latex.globals["parse_cell"] = _parse_latex_cell_styles - self.template_latex.globals["parse_header"] = _parse_latex_header_span + template_latex.globals["parse_wrap"] = _parse_latex_table_wrapping + template_latex.globals["parse_table"] = _parse_latex_table_styles + template_latex.globals["parse_cell"] = _parse_latex_cell_styles + template_latex.globals["parse_header"] = _parse_latex_header_span d.update(kwargs) - return self.template_latex.render(**d) + return template_latex.render(**d) def _render_string( self, @@ -240,7 +273,8 @@ def _render_string( """ d = self._render(sparse_index, sparse_columns, max_rows, max_cols) d.update(kwargs) - return self.template_string.render(**d) + template_string = self.env.get_template(self.template_string) + return template_string.render(**d) def _compute(self): """ @@ -1778,11 +1812,11 @@ def wrapper(x): return wrapper -def _str_escape(x, escape): +def _str_escape(x, escape, markupsafe): """if escaping: only use on str, else return input""" if isinstance(x, str): if escape == "html": - return escape_html(x) + return markupsafe.escape(x) elif escape == "latex": return _escape_latex(x) elif escape == "latex-math": @@ -1840,7 +1874,10 @@ def _maybe_wrap_formatter( # Replace chars if escaping if escape is not None: - func_1 = lambda x: func_0(_str_escape(x, escape=escape)) + markupsafe = import_optional_dependency( + "markupsafe", extra="DataFrame.style requires markupsafe." + ) + func_1 = lambda x: func_0(_str_escape(x, escape=escape, markupsafe=markupsafe)) else: func_1 = func_0 diff --git a/pandas/tests/io/formats/style/custom_templates/myhtml.tpl b/pandas/tests/io/formats/style/custom_templates/myhtml.tpl new file mode 100644 index 0000000000000..1e204d0bd4568 --- /dev/null +++ b/pandas/tests/io/formats/style/custom_templates/myhtml.tpl @@ -0,0 +1,5 @@ +{% extends "html_table.tpl" %} +{% block table %} +