Skip to content

Commit

Permalink
Minor speed improvements and python 3.13 (#17)
Browse files Browse the repository at this point in the history
* remove SafeString slots

* remove duplicate autoplay

* tuples

* wip

* wip

* wip

* wip

* version
  • Loading branch information
keithasaurus authored Nov 30, 2024
1 parent ea9bfc8 commit 312f2b7
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 16 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: [3.8, 3.9, '3.10', '3.11', '3.12']
poetry-version: [1.6.1]
python-version: [3.8, 3.9, '3.10', '3.11', '3.12', '3.13']
poetry-version: [1.8.4]
os: [ubuntu-22.04, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "simple-html"
version = "1.2.2"
version = "1.2.3"
readme = "README.md"
description = "Template-less html rendering in Python"
authors = ["Keith Philpott <[email protected]>"]
Expand Down
33 changes: 20 additions & 13 deletions simple_html/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
from html import escape
from types import GeneratorType
from typing import Tuple, Union, Dict, List, FrozenSet, Generator, Iterable, Any, Callable


class SafeString:
__slots__ = ("safe_str",)

def __init__(self, safe_str: str) -> None:
self.safe_str = safe_str

Expand All @@ -19,6 +16,17 @@ def __repr__(self) -> str:
return f"SafeString(safe_str='{self.safe_str}')"


def faster_escape(s: str) -> str:
"""
This is nearly duplicate of html.escape in the standard lib.
it's a little faster because:
- we don't check if some of the replacements are desired
- we don't re-assign a variable many times.
"""
return s.replace(
"&", "&amp;" # Must be done first!
).replace("<", "&lt;").replace(">", "&gt;").replace('"', "&quot;").replace('\'', "&#x27;")

Node = Union[
str,
SafeString,
Expand All @@ -31,10 +39,9 @@ def __repr__(self) -> str:
TagTuple = Tuple[str, Tuple[Node, ...], str]

_common_safe_attribute_names: FrozenSet[str] = frozenset(
{
(
"alt",
"autoplay",
"autoplay",
"charset",
"checked",
"class",
Expand Down Expand Up @@ -80,13 +87,13 @@ def __repr__(self) -> str:
"type",
"value",
"width",
}
)
)


def escape_attribute_key(k: str) -> str:
return (
escape(k, True)
faster_escape(k)
.replace("=", "&#x3D;")
.replace("\\", "&#x5C;")
.replace("`", "&#x60;")
Expand Down Expand Up @@ -134,7 +141,7 @@ def __call__(
)

if isinstance(val, str):
attrs += f' {key}="{escape(val, True)}"'
attrs += f' {key}="{faster_escape(val)}"'
elif isinstance(val, SafeString):
attrs += f' {key}="{val.safe_str}"'
elif val is None:
Expand Down Expand Up @@ -278,7 +285,7 @@ def _render(nodes: Iterable[Node], append_to_list: Callable[[str], None]) -> Non
elif isinstance(node, SafeString):
append_to_list(node.safe_str)
elif isinstance(node, str):
append_to_list(escape(node))
append_to_list(faster_escape(node))
elif isinstance(node, Tag):
append_to_list(node.rendered)
elif isinstance(node, list):
Expand All @@ -290,7 +297,7 @@ def _render(nodes: Iterable[Node], append_to_list: Callable[[str], None]) -> Non


_common_safe_css_props = frozenset(
{
(
"color",
"border",
"margin",
Expand Down Expand Up @@ -498,7 +505,7 @@ def _render(nodes: Iterable[Node], append_to_list: Callable[[str], None]) -> Non
"word-wrap",
"writing-mode",
"z-index",
}
)
)


Expand All @@ -511,12 +518,12 @@ def render_styles(
if isinstance(k, SafeString):
k = k.safe_str
else:
k = escape(k, True)
k = faster_escape(k)

if isinstance(v, SafeString):
v = v.safe_str
elif isinstance(v, str):
v = escape(v, True)
v = faster_escape(v)
# note that ints and floats pass through these condition checks

ret += f"{k}:{v};"
Expand Down

0 comments on commit 312f2b7

Please sign in to comment.