Plain is headed towards 1.0! Subscribe for development updates →

 1"""
 2Functions for working with "safe strings": strings that can be displayed safely
 3without further escaping in HTML. Marking something as a "safe string" means
 4that the producer of the string has already turned characters that should not
 5be interpreted by the HTML engine (e.g. '<') into the appropriate entities.
 6"""
 7
 8from __future__ import annotations
 9
10from collections.abc import Callable
11from functools import wraps
12from typing import Any, TypeVar
13
14from plain.utils.functional import keep_lazy
15
16_T = TypeVar("_T")
17
18
19class SafeData:
20    __slots__ = ()
21
22    def __html__(self) -> SafeData:
23        """
24        Return the html representation of a string for interoperability.
25
26        This allows other template engines to understand Plain's SafeData.
27        """
28        return self
29
30
31class SafeString(str, SafeData):
32    """
33    A str subclass that has been specifically marked as "safe" for HTML output
34    purposes.
35    """
36
37    __slots__ = ()
38
39    def __add__(self, rhs: str) -> SafeString | str:
40        """
41        Concatenating a safe string with another safe bytestring or
42        safe string is safe. Otherwise, the result is no longer safe.
43        """
44        t = super().__add__(rhs)
45        if isinstance(rhs, SafeData):
46            return SafeString(t)
47        return t
48
49    def __str__(self) -> str:
50        return self
51
52
53def _safety_decorator(
54    safety_marker: Callable[[Any], _T], func: Callable[..., Any]
55) -> Callable[..., _T]:
56    @wraps(func)
57    def wrapper(*args: Any, **kwargs: Any) -> _T:
58        return safety_marker(func(*args, **kwargs))
59
60    return wrapper
61
62
63@keep_lazy(SafeString)
64def mark_safe(s: Any) -> SafeString | SafeData | Callable[..., Any]:
65    """
66    Explicitly mark a string as safe for (HTML) output purposes. The returned
67    object can be used everywhere a string is appropriate.
68
69    If used on a method as a decorator, mark the returned data as safe.
70
71    Can be called multiple times on a single string.
72    """
73    if hasattr(s, "__html__"):
74        return s
75    if callable(s):
76        return _safety_decorator(mark_safe, s)
77    return SafeString(s)