Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/markupsafe/__init__.py: 43%
169 statements
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-22 06:29 +0000
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-22 06:29 +0000
1import functools
2import string
3import sys
4import typing as t
6if t.TYPE_CHECKING:
7 import typing_extensions as te
9 class HasHTML(te.Protocol):
10 def __html__(self) -> str:
11 pass
13 _P = te.ParamSpec("_P")
16__version__ = "2.1.4"
19def _simple_escaping_wrapper(func: "t.Callable[_P, str]") -> "t.Callable[_P, Markup]":
20 @functools.wraps(func)
21 def wrapped(self: "Markup", *args: "_P.args", **kwargs: "_P.kwargs") -> "Markup":
22 arg_list = _escape_argspec(list(args), enumerate(args), self.escape)
23 _escape_argspec(kwargs, kwargs.items(), self.escape)
24 return self.__class__(func(self, *arg_list, **kwargs)) # type: ignore[arg-type]
26 return wrapped # type: ignore[return-value]
29class Markup(str):
30 """A string that is ready to be safely inserted into an HTML or XML
31 document, either because it was escaped or because it was marked
32 safe.
34 Passing an object to the constructor converts it to text and wraps
35 it to mark it safe without escaping. To escape the text, use the
36 :meth:`escape` class method instead.
38 >>> Markup("Hello, <em>World</em>!")
39 Markup('Hello, <em>World</em>!')
40 >>> Markup(42)
41 Markup('42')
42 >>> Markup.escape("Hello, <em>World</em>!")
43 Markup('Hello <em>World</em>!')
45 This implements the ``__html__()`` interface that some frameworks
46 use. Passing an object that implements ``__html__()`` will wrap the
47 output of that method, marking it safe.
49 >>> class Foo:
50 ... def __html__(self):
51 ... return '<a href="/foo">foo</a>'
52 ...
53 >>> Markup(Foo())
54 Markup('<a href="/foo">foo</a>')
56 This is a subclass of :class:`str`. It has the same methods, but
57 escapes their arguments and returns a ``Markup`` instance.
59 >>> Markup("<em>%s</em>") % ("foo & bar",)
60 Markup('<em>foo & bar</em>')
61 >>> Markup("<em>Hello</em> ") + "<foo>"
62 Markup('<em>Hello</em> <foo>')
63 """
65 __slots__ = ()
67 def __new__(
68 cls, base: t.Any = "", encoding: t.Optional[str] = None, errors: str = "strict"
69 ) -> "te.Self":
70 if hasattr(base, "__html__"):
71 base = base.__html__()
73 if encoding is None:
74 return super().__new__(cls, base)
76 return super().__new__(cls, base, encoding, errors)
78 def __html__(self) -> "te.Self":
79 return self
81 def __add__(self, other: t.Union[str, "HasHTML"]) -> "te.Self":
82 if isinstance(other, str) or hasattr(other, "__html__"):
83 return self.__class__(super().__add__(self.escape(other)))
85 return NotImplemented
87 def __radd__(self, other: t.Union[str, "HasHTML"]) -> "te.Self":
88 if isinstance(other, str) or hasattr(other, "__html__"):
89 return self.escape(other).__add__(self)
91 return NotImplemented
93 def __mul__(self, num: "te.SupportsIndex") -> "te.Self":
94 if isinstance(num, int):
95 return self.__class__(super().__mul__(num))
97 return NotImplemented
99 __rmul__ = __mul__
101 def __mod__(self, arg: t.Any) -> "te.Self":
102 if isinstance(arg, tuple):
103 # a tuple of arguments, each wrapped
104 arg = tuple(_MarkupEscapeHelper(x, self.escape) for x in arg)
105 elif hasattr(type(arg), "__getitem__") and not isinstance(arg, str):
106 # a mapping of arguments, wrapped
107 arg = _MarkupEscapeHelper(arg, self.escape)
108 else:
109 # a single argument, wrapped with the helper and a tuple
110 arg = (_MarkupEscapeHelper(arg, self.escape),)
112 return self.__class__(super().__mod__(arg))
114 def __repr__(self) -> str:
115 return f"{self.__class__.__name__}({super().__repr__()})"
117 def join(self, seq: t.Iterable[t.Union[str, "HasHTML"]]) -> "te.Self":
118 return self.__class__(super().join(map(self.escape, seq)))
120 join.__doc__ = str.join.__doc__
122 def split( # type: ignore[override]
123 self, sep: t.Optional[str] = None, maxsplit: int = -1
124 ) -> t.List["te.Self"]:
125 return [self.__class__(v) for v in super().split(sep, maxsplit)]
127 split.__doc__ = str.split.__doc__
129 def rsplit( # type: ignore[override]
130 self, sep: t.Optional[str] = None, maxsplit: int = -1
131 ) -> t.List["te.Self"]:
132 return [self.__class__(v) for v in super().rsplit(sep, maxsplit)]
134 rsplit.__doc__ = str.rsplit.__doc__
136 def splitlines( # type: ignore[override]
137 self, keepends: bool = False
138 ) -> t.List["te.Self"]:
139 return [self.__class__(v) for v in super().splitlines(keepends)]
141 splitlines.__doc__ = str.splitlines.__doc__
143 def unescape(self) -> str:
144 """Convert escaped markup back into a text string. This replaces
145 HTML entities with the characters they represent.
147 >>> Markup("Main » <em>About</em>").unescape()
148 'Main » <em>About</em>'
149 """
150 from html import unescape
152 return unescape(str(self))
154 def striptags(self) -> str:
155 """:meth:`unescape` the markup, remove tags, and normalize
156 whitespace to single spaces.
158 >>> Markup("Main »\t<em>About</em>").striptags()
159 'Main » About'
160 """
161 # collapse spaces
162 value = " ".join(self.split())
164 # Look for comments then tags separately. Otherwise, a comment that
165 # contains a tag would end early, leaving some of the comment behind.
167 while True:
168 # keep finding comment start marks
169 start = value.find("<!--")
171 if start == -1:
172 break
174 # find a comment end mark beyond the start, otherwise stop
175 end = value.find("-->", start)
177 if end == -1:
178 break
180 value = f"{value[:start]}{value[end + 3:]}"
182 # remove tags using the same method
183 while True:
184 start = value.find("<")
186 if start == -1:
187 break
189 end = value.find(">", start)
191 if end == -1:
192 break
194 value = f"{value[:start]}{value[end + 1:]}"
196 return self.__class__(value).unescape()
198 @classmethod
199 def escape(cls, s: t.Any) -> "te.Self":
200 """Escape a string. Calls :func:`escape` and ensures that for
201 subclasses the correct type is returned.
202 """
203 rv = escape(s)
205 if rv.__class__ is not cls:
206 return cls(rv)
208 return rv # type: ignore[return-value]
210 __getitem__ = _simple_escaping_wrapper(str.__getitem__)
211 capitalize = _simple_escaping_wrapper(str.capitalize)
212 title = _simple_escaping_wrapper(str.title)
213 lower = _simple_escaping_wrapper(str.lower)
214 upper = _simple_escaping_wrapper(str.upper)
215 replace = _simple_escaping_wrapper(str.replace)
216 ljust = _simple_escaping_wrapper(str.ljust)
217 rjust = _simple_escaping_wrapper(str.rjust)
218 lstrip = _simple_escaping_wrapper(str.lstrip)
219 rstrip = _simple_escaping_wrapper(str.rstrip)
220 center = _simple_escaping_wrapper(str.center)
221 strip = _simple_escaping_wrapper(str.strip)
222 translate = _simple_escaping_wrapper(str.translate)
223 expandtabs = _simple_escaping_wrapper(str.expandtabs)
224 swapcase = _simple_escaping_wrapper(str.swapcase)
225 zfill = _simple_escaping_wrapper(str.zfill)
226 casefold = _simple_escaping_wrapper(str.casefold)
228 if sys.version_info >= (3, 9):
229 removeprefix = _simple_escaping_wrapper(str.removeprefix)
230 removesuffix = _simple_escaping_wrapper(str.removesuffix)
232 def partition(self, sep: str) -> t.Tuple["te.Self", "te.Self", "te.Self"]:
233 l, s, r = super().partition(self.escape(sep))
234 cls = self.__class__
235 return cls(l), cls(s), cls(r)
237 def rpartition(self, sep: str) -> t.Tuple["te.Self", "te.Self", "te.Self"]:
238 l, s, r = super().rpartition(self.escape(sep))
239 cls = self.__class__
240 return cls(l), cls(s), cls(r)
242 def format(self, *args: t.Any, **kwargs: t.Any) -> "te.Self":
243 formatter = EscapeFormatter(self.escape)
244 return self.__class__(formatter.vformat(self, args, kwargs))
246 def format_map( # type: ignore[override]
247 self, map: t.Mapping[str, t.Any]
248 ) -> "te.Self":
249 formatter = EscapeFormatter(self.escape)
250 return self.__class__(formatter.vformat(self, (), map))
252 def __html_format__(self, format_spec: str) -> "te.Self":
253 if format_spec:
254 raise ValueError("Unsupported format specification for Markup.")
256 return self
259class EscapeFormatter(string.Formatter):
260 __slots__ = ("escape",)
262 def __init__(self, escape: t.Callable[[t.Any], Markup]) -> None:
263 self.escape = escape
264 super().__init__()
266 def format_field(self, value: t.Any, format_spec: str) -> str:
267 if hasattr(value, "__html_format__"):
268 rv = value.__html_format__(format_spec)
269 elif hasattr(value, "__html__"):
270 if format_spec:
271 raise ValueError(
272 f"Format specifier {format_spec} given, but {type(value)} does not"
273 " define __html_format__. A class that defines __html__ must define"
274 " __html_format__ to work with format specifiers."
275 )
276 rv = value.__html__()
277 else:
278 # We need to make sure the format spec is str here as
279 # otherwise the wrong callback methods are invoked.
280 rv = string.Formatter.format_field(self, value, str(format_spec))
281 return str(self.escape(rv))
284_ListOrDict = t.TypeVar("_ListOrDict", list, dict)
287def _escape_argspec(
288 obj: _ListOrDict, iterable: t.Iterable[t.Any], escape: t.Callable[[t.Any], Markup]
289) -> _ListOrDict:
290 """Helper for various string-wrapped functions."""
291 for key, value in iterable:
292 if isinstance(value, str) or hasattr(value, "__html__"):
293 obj[key] = escape(value)
295 return obj
298class _MarkupEscapeHelper:
299 """Helper for :meth:`Markup.__mod__`."""
301 __slots__ = ("obj", "escape")
303 def __init__(self, obj: t.Any, escape: t.Callable[[t.Any], Markup]) -> None:
304 self.obj = obj
305 self.escape = escape
307 def __getitem__(self, item: t.Any) -> "te.Self":
308 return self.__class__(self.obj[item], self.escape)
310 def __str__(self) -> str:
311 return str(self.escape(self.obj))
313 def __repr__(self) -> str:
314 return str(self.escape(repr(self.obj)))
316 def __int__(self) -> int:
317 return int(self.obj)
319 def __float__(self) -> float:
320 return float(self.obj)
323# circular import
324try:
325 from ._speedups import escape as escape
326 from ._speedups import escape_silent as escape_silent
327 from ._speedups import soft_str as soft_str
328except ImportError:
329 from ._native import escape as escape
330 from ._native import escape_silent as escape_silent # noqa: F401
331 from ._native import soft_str as soft_str # noqa: F401