Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: sudo apt-get update && sudo apt-get install -y docutils-common
- run: python -m pip install tox mypy flake8
- run: python -m pip install tox mypy flake8 jinja2 pandas
- run: make test
43 changes: 42 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,15 @@ Features and patterns
* Output is compact: Naturally produces no superfluous whitespace between
elements.

* Fragments provide ``_repr_html_()`` for Jupyter Notebook integration.
* Fragments provide ``_repr_html_()`` for Jupyter Notebook integration and
``__html__`` for integration with other ecosystems (see
`MarkupSafe`_).

.. _MarkupSafe: <https://markupsafe.palletsprojects.com/en/stable/html/>

* Fragments can include and render objects providing ``_repr_html_()`` and
``__html__()``. This means objects that already render as HTML in a
Jupyter notebook will be rendered by tinyhtml.

* Includes mypy typings.

Expand Down Expand Up @@ -163,6 +171,39 @@ Features and patterns
(like inline styles and scripts), or foreign elements (like inline SVG).
Instead, reference external files, or use ``raw()`` with appropriate caution.


Interoperability
----------------

Fragments implement `_repr_html_` and can be displayed in Jupyter notebooks as HTML,
but they can also render object that implement `_repr_html_`. Similarly fragments
can include and be included in other template systems that use the `__html__` convention,
such as Jinja2 via `MarkupSafe`_.

* Render fragments into a Jinja template.

.. code:: python

>>> import jinja2
>>> template = jinja2.Template('<div>{{ fragment }}</div>')
>>> frag = h('ul')(h('li')(i) for i in range(2))
>>> template.render(fragment=frag)
'<div><ul><li>0</li><li>1</li></ul></div>'


* Render an object the supports display in a Jupyter notebook, such as a pandas
dataframe.

.. code:: python

>>> import pandas as pd
>>> table = pd.DataFrame({'Fruit': ['apple', 'pear'], 'Count': [3, 4]})
>>> frag = h('div')(h('h1')('A table'), table)
>>> frag.render()
'<div><h1>A table</h1><div>\n<style scoped>\n .dataframe tbody tr th:only-of-type {\n vertical-align: middle;\n }\n\n .dataframe tbody tr th {\n vertical-align: top;\n }\n\n .dataframe thead th {\n text-align: right;\n }\n</style>\n<table border="1" class="dataframe">\n <thead>\n <tr style="text-align: right;">\n <th></th>\n <th>Fruit</th>\n <th>Count</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <th>0</th>\n <td>apple</td>\n <td>3</td>\n </tr>\n <tr>\n <th>1</th>\n <td>pear</td>\n <td>4</td>\n </tr>\n </tbody>\n</table>\n</div></div>'



License
-------

Expand Down
4 changes: 4 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
"tinyhtml": ["py.typed"],
},
python_requires=">=3.8",
extras_require={
'test': ['jinja2', 'pandas']
},
classifiers=[
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
Expand All @@ -33,6 +36,7 @@
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Text Processing :: Markup :: HTML",
Expand Down
25 changes: 22 additions & 3 deletions tinyhtml/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

import abc
from html import escape
from typing import Union, Dict, Iterable, List, Tuple
from typing import Union, Dict, Iterable, List, Tuple, Protocol, runtime_checkable


class Frag(abc.ABC):
Expand All @@ -30,13 +30,28 @@ def render_into(self, builder: List[str]) -> None:
def render(self) -> str:
return render(self)

_repr_html_ = __str__ = render
_repr_html_ = __str__ = __html__ = render

def __repr__(self) -> str:
return f"raw({self.render()!r})"


SupportsRender = Union[str, int, Frag, None, Iterable[Union[str, int, Frag, None]]]
@runtime_checkable
class SupportsDunderHTML(Protocol):

def __html__(self) -> str:
pass


@runtime_checkable
class SupportsJupyterReprHTML(Protocol):

def _repr_html_(self) -> str:
pass


RenderableItem = Union[SupportsDunderHTML, SupportsJupyterReprHTML, str, int, Frag, None]
SupportsRender = Union[RenderableItem, Iterable[RenderableItem]]


def render_into(frag: SupportsRender, builder: List[str]) -> None:
Expand All @@ -46,6 +61,10 @@ def render_into(frag: SupportsRender, builder: List[str]) -> None:
builder.append(escape(frag, quote=False))
elif isinstance(frag, Frag):
frag.render_into(builder)
elif isinstance(frag, SupportsDunderHTML):
builder.append(frag.__html__())
elif isinstance(frag, SupportsJupyterReprHTML):
builder.append(frag._repr_html_())
elif isinstance(frag, bytes):
raise TypeError(f"cannot render bytes as html: {frag!r}")
elif hasattr(frag, "__iter__"):
Expand Down
5 changes: 4 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
[tox]
envlist = py38,py39,py310,py311,py312
envlist = py38,py39,py310,py311,py312,py313

[testenv]
deps =
pandas
jinja2
commands =
python -m doctest README.rst

Expand Down