From 312f2b708a6b4fdb63b8fc43f0a2b75f1bb6eb2e Mon Sep 17 00:00:00 2001 From: keithasaurus <592217+keithasaurus@users.noreply.github.com> Date: Sat, 30 Nov 2024 09:46:38 -0800 Subject: [PATCH] Minor speed improvements and python 3.13 (#17) * remove SafeString slots * remove duplicate autoplay * tuples * wip * wip * wip * wip * version --- .github/workflows/push.yml | 4 ++-- pyproject.toml | 2 +- simple_html/__init__.py | 33 ++++++++++++++++++++------------- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 50d5712..9f4cf53 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -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: diff --git a/pyproject.toml b/pyproject.toml index 3da5f7c..98cb2b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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 "] diff --git a/simple_html/__init__.py b/simple_html/__init__.py index 850ccb0..288d7bd 100644 --- a/simple_html/__init__.py +++ b/simple_html/__init__.py @@ -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 @@ -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( + "&", "&" # Must be done first! + ).replace("<", "<").replace(">", ">").replace('"', """).replace('\'', "'") + Node = Union[ str, SafeString, @@ -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", @@ -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("=", "=") .replace("\\", "\") .replace("`", "`") @@ -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: @@ -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): @@ -290,7 +297,7 @@ def _render(nodes: Iterable[Node], append_to_list: Callable[[str], None]) -> Non _common_safe_css_props = frozenset( - { + ( "color", "border", "margin", @@ -498,7 +505,7 @@ def _render(nodes: Iterable[Node], append_to_list: Callable[[str], None]) -> Non "word-wrap", "writing-mode", "z-index", - } + ) ) @@ -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};"