v0.166.0
 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 collections.abc import Callable
 9from functools import wraps
10from typing import Any
11
12from plain.utils.functional import keep_lazy
13
14
15class SafeData:
16    __slots__ = ()
17
18    def __html__(self) -> SafeData:
19        """
20        Return the html representation of a string for interoperability.
21
22        This allows other template engines to understand Plain's SafeData.
23        """
24        return self
25
26
27class SafeString(str, SafeData):
28    """
29    A str subclass that has been specifically marked as "safe" for HTML output
30    purposes.
31    """
32
33    __slots__ = ()
34
35    def __add__(self, rhs: str) -> SafeString | str:
36        """
37        Concatenating a safe string with another safe bytestring or
38        safe string is safe. Otherwise, the result is no longer safe.
39        """
40        t = super().__add__(rhs)
41        if isinstance(rhs, SafeData):
42            return SafeString(t)
43        return t
44
45    def __str__(self) -> str:
46        return self
47
48
49def _safety_decorator[T](
50    safety_marker: Callable[[Any], T], func: Callable[..., Any]
51) -> Callable[..., T]:
52    @wraps(func)
53    def wrapper(*args: Any, **kwargs: Any) -> T:
54        return safety_marker(func(*args, **kwargs))
55
56    return wrapper
57
58
59@keep_lazy(SafeString)
60def mark_safe(s: Any) -> SafeString | SafeData | Callable[..., Any]:
61    """
62    Explicitly mark a string as safe for (HTML) output purposes. The returned
63    object can be used everywhere a string is appropriate.
64
65    If used on a method as a decorator, mark the returned data as safe.
66
67    Can be called multiple times on a single string.
68    """
69    if hasattr(s, "__html__"):
70        return s
71    if callable(s):
72        return _safety_decorator(mark_safe, s)
73    return SafeString(s)