Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/markupsafe/__init__.py: 52%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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.5"
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 value = str(self)
163 # Look for comments then tags separately. Otherwise, a comment that
164 # contains a tag would end early, leaving some of the comment behind.
166 while True:
167 # keep finding comment start marks
168 start = value.find("<!--")
170 if start == -1:
171 break
173 # find a comment end mark beyond the start, otherwise stop
174 end = value.find("-->", start)
176 if end == -1:
177 break
179 value = f"{value[:start]}{value[end + 3:]}"
181 # remove tags using the same method
182 while True:
183 start = value.find("<")
185 if start == -1:
186 break
188 end = value.find(">", start)
190 if end == -1:
191 break
193 value = f"{value[:start]}{value[end + 1:]}"
195 # collapse spaces
196 value = " ".join(value.split())
197 return self.__class__(value).unescape()
199 @classmethod
200 def escape(cls, s: t.Any) -> "te.Self":
201 """Escape a string. Calls :func:`escape` and ensures that for
202 subclasses the correct type is returned.
203 """
204 rv = escape(s)
206 if rv.__class__ is not cls:
207 return cls(rv)
209 return rv # type: ignore[return-value]
211 __getitem__ = _simple_escaping_wrapper(str.__getitem__)
212 capitalize = _simple_escaping_wrapper(str.capitalize)
213 title = _simple_escaping_wrapper(str.title)
214 lower = _simple_escaping_wrapper(str.lower)
215 upper = _simple_escaping_wrapper(str.upper)
216 replace = _simple_escaping_wrapper(str.replace)
217 ljust = _simple_escaping_wrapper(str.ljust)
218 rjust = _simple_escaping_wrapper(str.rjust)
219 lstrip = _simple_escaping_wrapper(str.lstrip)
220 rstrip = _simple_escaping_wrapper(str.rstrip)
221 center = _simple_escaping_wrapper(str.center)
222 strip = _simple_escaping_wrapper(str.strip)
223 translate = _simple_escaping_wrapper(str.translate)
224 expandtabs = _simple_escaping_wrapper(str.expandtabs)
225 swapcase = _simple_escaping_wrapper(str.swapcase)
226 zfill = _simple_escaping_wrapper(str.zfill)
227 casefold = _simple_escaping_wrapper(str.casefold)
229 if sys.version_info >= (3, 9):
230 removeprefix = _simple_escaping_wrapper(str.removeprefix)
231 removesuffix = _simple_escaping_wrapper(str.removesuffix)
233 def partition(self, sep: str) -> t.Tuple["te.Self", "te.Self", "te.Self"]:
234 l, s, r = super().partition(self.escape(sep))
235 cls = self.__class__
236 return cls(l), cls(s), cls(r)
238 def rpartition(self, sep: str) -> t.Tuple["te.Self", "te.Self", "te.Self"]:
239 l, s, r = super().rpartition(self.escape(sep))
240 cls = self.__class__
241 return cls(l), cls(s), cls(r)
243 def format(self, *args: t.Any, **kwargs: t.Any) -> "te.Self":
244 formatter = EscapeFormatter(self.escape)
245 return self.__class__(formatter.vformat(self, args, kwargs))
247 def format_map( # type: ignore[override]
248 self, map: t.Mapping[str, t.Any]
249 ) -> "te.Self":
250 formatter = EscapeFormatter(self.escape)
251 return self.__class__(formatter.vformat(self, (), map))
253 def __html_format__(self, format_spec: str) -> "te.Self":
254 if format_spec:
255 raise ValueError("Unsupported format specification for Markup.")
257 return self
260class EscapeFormatter(string.Formatter):
261 __slots__ = ("escape",)
263 def __init__(self, escape: t.Callable[[t.Any], Markup]) -> None:
264 self.escape = escape
265 super().__init__()
267 def format_field(self, value: t.Any, format_spec: str) -> str:
268 if hasattr(value, "__html_format__"):
269 rv = value.__html_format__(format_spec)
270 elif hasattr(value, "__html__"):
271 if format_spec:
272 raise ValueError(
273 f"Format specifier {format_spec} given, but {type(value)} does not"
274 " define __html_format__. A class that defines __html__ must define"
275 " __html_format__ to work with format specifiers."
276 )
277 rv = value.__html__()
278 else:
279 # We need to make sure the format spec is str here as
280 # otherwise the wrong callback methods are invoked.
281 rv = string.Formatter.format_field(self, value, str(format_spec))
282 return str(self.escape(rv))
285_ListOrDict = t.TypeVar("_ListOrDict", list, dict)
288def _escape_argspec(
289 obj: _ListOrDict, iterable: t.Iterable[t.Any], escape: t.Callable[[t.Any], Markup]
290) -> _ListOrDict:
291 """Helper for various string-wrapped functions."""
292 for key, value in iterable:
293 if isinstance(value, str) or hasattr(value, "__html__"):
294 obj[key] = escape(value)
296 return obj
299class _MarkupEscapeHelper:
300 """Helper for :meth:`Markup.__mod__`."""
302 __slots__ = ("obj", "escape")
304 def __init__(self, obj: t.Any, escape: t.Callable[[t.Any], Markup]) -> None:
305 self.obj = obj
306 self.escape = escape
308 def __getitem__(self, item: t.Any) -> "te.Self":
309 return self.__class__(self.obj[item], self.escape)
311 def __str__(self) -> str:
312 return str(self.escape(self.obj))
314 def __repr__(self) -> str:
315 return str(self.escape(repr(self.obj)))
317 def __int__(self) -> int:
318 return int(self.obj)
320 def __float__(self) -> float:
321 return float(self.obj)
324# circular import
325try:
326 from ._speedups import escape as escape
327 from ._speedups import escape_silent as escape_silent
328 from ._speedups import soft_str as soft_str
329except ImportError:
330 from ._native import escape as escape
331 from ._native import escape_silent as escape_silent # noqa: F401
332 from ._native import soft_str as soft_str # noqa: F401