Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/markupsafe/__init__.py: 48%
160 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-01 06:54 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-01 06:54 +0000
1import functools
2import re
3import string
4import sys
5import typing as t
7if t.TYPE_CHECKING:
8 import typing_extensions as te
10 class HasHTML(te.Protocol):
11 def __html__(self) -> str:
12 pass
14 _P = te.ParamSpec("_P")
17__version__ = "2.1.3"
19_strip_comments_re = re.compile(r"<!--.*?-->", re.DOTALL)
20_strip_tags_re = re.compile(r"<.*?>", re.DOTALL)
23def _simple_escaping_wrapper(func: "t.Callable[_P, str]") -> "t.Callable[_P, Markup]":
24 @functools.wraps(func)
25 def wrapped(self: "Markup", *args: "_P.args", **kwargs: "_P.kwargs") -> "Markup":
26 arg_list = _escape_argspec(list(args), enumerate(args), self.escape)
27 _escape_argspec(kwargs, kwargs.items(), self.escape)
28 return self.__class__(func(self, *arg_list, **kwargs)) # type: ignore[arg-type]
30 return wrapped # type: ignore[return-value]
33class Markup(str):
34 """A string that is ready to be safely inserted into an HTML or XML
35 document, either because it was escaped or because it was marked
36 safe.
38 Passing an object to the constructor converts it to text and wraps
39 it to mark it safe without escaping. To escape the text, use the
40 :meth:`escape` class method instead.
42 >>> Markup("Hello, <em>World</em>!")
43 Markup('Hello, <em>World</em>!')
44 >>> Markup(42)
45 Markup('42')
46 >>> Markup.escape("Hello, <em>World</em>!")
47 Markup('Hello <em>World</em>!')
49 This implements the ``__html__()`` interface that some frameworks
50 use. Passing an object that implements ``__html__()`` will wrap the
51 output of that method, marking it safe.
53 >>> class Foo:
54 ... def __html__(self):
55 ... return '<a href="/foo">foo</a>'
56 ...
57 >>> Markup(Foo())
58 Markup('<a href="/foo">foo</a>')
60 This is a subclass of :class:`str`. It has the same methods, but
61 escapes their arguments and returns a ``Markup`` instance.
63 >>> Markup("<em>%s</em>") % ("foo & bar",)
64 Markup('<em>foo & bar</em>')
65 >>> Markup("<em>Hello</em> ") + "<foo>"
66 Markup('<em>Hello</em> <foo>')
67 """
69 __slots__ = ()
71 def __new__(
72 cls, base: t.Any = "", encoding: t.Optional[str] = None, errors: str = "strict"
73 ) -> "te.Self":
74 if hasattr(base, "__html__"):
75 base = base.__html__()
77 if encoding is None:
78 return super().__new__(cls, base)
80 return super().__new__(cls, base, encoding, errors)
82 def __html__(self) -> "te.Self":
83 return self
85 def __add__(self, other: t.Union[str, "HasHTML"]) -> "te.Self":
86 if isinstance(other, str) or hasattr(other, "__html__"):
87 return self.__class__(super().__add__(self.escape(other)))
89 return NotImplemented
91 def __radd__(self, other: t.Union[str, "HasHTML"]) -> "te.Self":
92 if isinstance(other, str) or hasattr(other, "__html__"):
93 return self.escape(other).__add__(self)
95 return NotImplemented
97 def __mul__(self, num: "te.SupportsIndex") -> "te.Self":
98 if isinstance(num, int):
99 return self.__class__(super().__mul__(num))
101 return NotImplemented
103 __rmul__ = __mul__
105 def __mod__(self, arg: t.Any) -> "te.Self":
106 if isinstance(arg, tuple):
107 # a tuple of arguments, each wrapped
108 arg = tuple(_MarkupEscapeHelper(x, self.escape) for x in arg)
109 elif hasattr(type(arg), "__getitem__") and not isinstance(arg, str):
110 # a mapping of arguments, wrapped
111 arg = _MarkupEscapeHelper(arg, self.escape)
112 else:
113 # a single argument, wrapped with the helper and a tuple
114 arg = (_MarkupEscapeHelper(arg, self.escape),)
116 return self.__class__(super().__mod__(arg))
118 def __repr__(self) -> str:
119 return f"{self.__class__.__name__}({super().__repr__()})"
121 def join(self, seq: t.Iterable[t.Union[str, "HasHTML"]]) -> "te.Self":
122 return self.__class__(super().join(map(self.escape, seq)))
124 join.__doc__ = str.join.__doc__
126 def split( # type: ignore[override]
127 self, sep: t.Optional[str] = None, maxsplit: int = -1
128 ) -> t.List["te.Self"]:
129 return [self.__class__(v) for v in super().split(sep, maxsplit)]
131 split.__doc__ = str.split.__doc__
133 def rsplit( # type: ignore[override]
134 self, sep: t.Optional[str] = None, maxsplit: int = -1
135 ) -> t.List["te.Self"]:
136 return [self.__class__(v) for v in super().rsplit(sep, maxsplit)]
138 rsplit.__doc__ = str.rsplit.__doc__
140 def splitlines( # type: ignore[override]
141 self, keepends: bool = False
142 ) -> t.List["te.Self"]:
143 return [self.__class__(v) for v in super().splitlines(keepends)]
145 splitlines.__doc__ = str.splitlines.__doc__
147 def unescape(self) -> str:
148 """Convert escaped markup back into a text string. This replaces
149 HTML entities with the characters they represent.
151 >>> Markup("Main » <em>About</em>").unescape()
152 'Main » <em>About</em>'
153 """
154 from html import unescape
156 return unescape(str(self))
158 def striptags(self) -> str:
159 """:meth:`unescape` the markup, remove tags, and normalize
160 whitespace to single spaces.
162 >>> Markup("Main »\t<em>About</em>").striptags()
163 'Main » About'
164 """
165 # Use two regexes to avoid ambiguous matches.
166 value = _strip_comments_re.sub("", self)
167 value = _strip_tags_re.sub("", value)
168 value = " ".join(value.split())
169 return self.__class__(value).unescape()
171 @classmethod
172 def escape(cls, s: t.Any) -> "te.Self":
173 """Escape a string. Calls :func:`escape` and ensures that for
174 subclasses the correct type is returned.
175 """
176 rv = escape(s)
178 if rv.__class__ is not cls:
179 return cls(rv)
181 return rv # type: ignore[return-value]
183 __getitem__ = _simple_escaping_wrapper(str.__getitem__)
184 capitalize = _simple_escaping_wrapper(str.capitalize)
185 title = _simple_escaping_wrapper(str.title)
186 lower = _simple_escaping_wrapper(str.lower)
187 upper = _simple_escaping_wrapper(str.upper)
188 replace = _simple_escaping_wrapper(str.replace)
189 ljust = _simple_escaping_wrapper(str.ljust)
190 rjust = _simple_escaping_wrapper(str.rjust)
191 lstrip = _simple_escaping_wrapper(str.lstrip)
192 rstrip = _simple_escaping_wrapper(str.rstrip)
193 center = _simple_escaping_wrapper(str.center)
194 strip = _simple_escaping_wrapper(str.strip)
195 translate = _simple_escaping_wrapper(str.translate)
196 expandtabs = _simple_escaping_wrapper(str.expandtabs)
197 swapcase = _simple_escaping_wrapper(str.swapcase)
198 zfill = _simple_escaping_wrapper(str.zfill)
199 casefold = _simple_escaping_wrapper(str.casefold)
201 if sys.version_info >= (3, 9):
202 removeprefix = _simple_escaping_wrapper(str.removeprefix)
203 removesuffix = _simple_escaping_wrapper(str.removesuffix)
205 def partition(self, sep: str) -> t.Tuple["te.Self", "te.Self", "te.Self"]:
206 l, s, r = super().partition(self.escape(sep))
207 cls = self.__class__
208 return cls(l), cls(s), cls(r)
210 def rpartition(self, sep: str) -> t.Tuple["te.Self", "te.Self", "te.Self"]:
211 l, s, r = super().rpartition(self.escape(sep))
212 cls = self.__class__
213 return cls(l), cls(s), cls(r)
215 def format(self, *args: t.Any, **kwargs: t.Any) -> "te.Self":
216 formatter = EscapeFormatter(self.escape)
217 return self.__class__(formatter.vformat(self, args, kwargs))
219 def format_map( # type: ignore[override]
220 self, map: t.Mapping[str, t.Any]
221 ) -> "te.Self":
222 formatter = EscapeFormatter(self.escape)
223 return self.__class__(formatter.vformat(self, (), map))
225 def __html_format__(self, format_spec: str) -> "te.Self":
226 if format_spec:
227 raise ValueError("Unsupported format specification for Markup.")
229 return self
232class EscapeFormatter(string.Formatter):
233 __slots__ = ("escape",)
235 def __init__(self, escape: t.Callable[[t.Any], Markup]) -> None:
236 self.escape = escape
237 super().__init__()
239 def format_field(self, value: t.Any, format_spec: str) -> str:
240 if hasattr(value, "__html_format__"):
241 rv = value.__html_format__(format_spec)
242 elif hasattr(value, "__html__"):
243 if format_spec:
244 raise ValueError(
245 f"Format specifier {format_spec} given, but {type(value)} does not"
246 " define __html_format__. A class that defines __html__ must define"
247 " __html_format__ to work with format specifiers."
248 )
249 rv = value.__html__()
250 else:
251 # We need to make sure the format spec is str here as
252 # otherwise the wrong callback methods are invoked.
253 rv = string.Formatter.format_field(self, value, str(format_spec))
254 return str(self.escape(rv))
257_ListOrDict = t.TypeVar("_ListOrDict", list, dict)
260def _escape_argspec(
261 obj: _ListOrDict, iterable: t.Iterable[t.Any], escape: t.Callable[[t.Any], Markup]
262) -> _ListOrDict:
263 """Helper for various string-wrapped functions."""
264 for key, value in iterable:
265 if isinstance(value, str) or hasattr(value, "__html__"):
266 obj[key] = escape(value)
268 return obj
271class _MarkupEscapeHelper:
272 """Helper for :meth:`Markup.__mod__`."""
274 __slots__ = ("obj", "escape")
276 def __init__(self, obj: t.Any, escape: t.Callable[[t.Any], Markup]) -> None:
277 self.obj = obj
278 self.escape = escape
280 def __getitem__(self, item: t.Any) -> "te.Self":
281 return self.__class__(self.obj[item], self.escape)
283 def __str__(self) -> str:
284 return str(self.escape(self.obj))
286 def __repr__(self) -> str:
287 return str(self.escape(repr(self.obj)))
289 def __int__(self) -> int:
290 return int(self.obj)
292 def __float__(self) -> float:
293 return float(self.obj)
296# circular import
297try:
298 from ._speedups import escape as escape
299 from ._speedups import escape_silent as escape_silent
300 from ._speedups import soft_str as soft_str
301except ImportError:
302 from ._native import escape as escape
303 from ._native import escape_silent as escape_silent # noqa: F401
304 from ._native import soft_str as soft_str # noqa: F401