Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/markupsafe/__init__.py: 63%
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
1from __future__ import annotations
3import collections.abc as cabc
4import string
5import typing as t
7try:
8 from ._speedups import _escape_inner
9except ImportError:
10 from ._native import _escape_inner
12if t.TYPE_CHECKING:
13 import typing_extensions as te
16class _HasHTML(t.Protocol):
17 def __html__(self, /) -> str: ...
20class _TPEscape(t.Protocol):
21 def __call__(self, s: t.Any, /) -> Markup: ...
24def escape(s: t.Any, /) -> Markup:
25 """Replace the characters ``&``, ``<``, ``>``, ``'``, and ``"`` in
26 the string with HTML-safe sequences. Use this if you need to display
27 text that might contain such characters in HTML.
29 If the object has an ``__html__`` method, it is called and the
30 return value is assumed to already be safe for HTML.
32 :param s: An object to be converted to a string and escaped.
33 :return: A :class:`Markup` string with the escaped text.
34 """
35 # If the object is already a plain string, skip __html__ check and string
36 # conversion. This is the most common use case.
37 # Use type(s) instead of s.__class__ because a proxy object may be reporting
38 # the __class__ of the proxied value.
39 if type(s) is str:
40 return Markup(_escape_inner(s))
42 if hasattr(s, "__html__"):
43 return Markup(s.__html__())
45 return Markup(_escape_inner(str(s)))
48def escape_silent(s: t.Any | None, /) -> Markup:
49 """Like :func:`escape` but treats ``None`` as the empty string.
50 Useful with optional values, as otherwise you get the string
51 ``'None'`` when the value is ``None``.
53 >>> escape(None)
54 Markup('None')
55 >>> escape_silent(None)
56 Markup('')
57 """
58 if s is None:
59 return Markup()
61 return escape(s)
64def soft_str(s: t.Any, /) -> str:
65 """Convert an object to a string if it isn't already. This preserves
66 a :class:`Markup` string rather than converting it back to a basic
67 string, so it will still be marked as safe and won't be escaped
68 again.
70 >>> value = escape("<User 1>")
71 >>> value
72 Markup('<User 1>')
73 >>> escape(str(value))
74 Markup('&lt;User 1&gt;')
75 >>> escape(soft_str(value))
76 Markup('<User 1>')
77 """
78 if not isinstance(s, str):
79 return str(s)
81 return s
84class Markup(str):
85 """A string that is ready to be safely inserted into an HTML or XML
86 document, either because it was escaped or because it was marked
87 safe.
89 Passing an object to the constructor converts it to text and wraps
90 it to mark it safe without escaping. To escape the text, use the
91 :meth:`escape` class method instead.
93 >>> Markup("Hello, <em>World</em>!")
94 Markup('Hello, <em>World</em>!')
95 >>> Markup(42)
96 Markup('42')
97 >>> Markup.escape("Hello, <em>World</em>!")
98 Markup('Hello <em>World</em>!')
100 This implements the ``__html__()`` interface that some frameworks
101 use. Passing an object that implements ``__html__()`` will wrap the
102 output of that method, marking it safe.
104 >>> class Foo:
105 ... def __html__(self):
106 ... return '<a href="/foo">foo</a>'
107 ...
108 >>> Markup(Foo())
109 Markup('<a href="/foo">foo</a>')
111 This is a subclass of :class:`str`. It has the same methods, but
112 escapes their arguments and returns a ``Markup`` instance.
114 >>> Markup("<em>%s</em>") % ("foo & bar",)
115 Markup('<em>foo & bar</em>')
116 >>> Markup("<em>Hello</em> ") + "<foo>"
117 Markup('<em>Hello</em> <foo>')
118 """
120 __slots__ = ()
122 def __new__(
123 cls, object: t.Any = "", encoding: str | None = None, errors: str = "strict"
124 ) -> te.Self:
125 if hasattr(object, "__html__"):
126 object = object.__html__()
128 if encoding is None:
129 return super().__new__(cls, object)
131 return super().__new__(cls, object, encoding, errors)
133 def __html__(self, /) -> te.Self:
134 return self
136 def __add__(self, value: str | _HasHTML, /) -> te.Self:
137 if isinstance(value, str) or hasattr(value, "__html__"):
138 return self.__class__(super().__add__(self.escape(value)))
140 return NotImplemented
142 def __radd__(self, value: str | _HasHTML, /) -> te.Self:
143 if isinstance(value, str) or hasattr(value, "__html__"):
144 return self.escape(value).__add__(self)
146 return NotImplemented
148 def __mul__(self, value: t.SupportsIndex, /) -> te.Self:
149 return self.__class__(super().__mul__(value))
151 def __rmul__(self, value: t.SupportsIndex, /) -> te.Self:
152 return self.__class__(super().__mul__(value))
154 def __mod__(self, value: t.Any, /) -> te.Self:
155 if isinstance(value, tuple):
156 # a tuple of arguments, each wrapped
157 value = tuple(_MarkupEscapeHelper(x, self.escape) for x in value)
158 elif hasattr(type(value), "__getitem__") and not isinstance(value, str):
159 # a mapping of arguments, wrapped
160 value = _MarkupEscapeHelper(value, self.escape)
161 else:
162 # a single argument, wrapped with the helper and a tuple
163 value = (_MarkupEscapeHelper(value, self.escape),)
165 return self.__class__(super().__mod__(value))
167 def __repr__(self, /) -> str:
168 return f"{self.__class__.__name__}({super().__repr__()})"
170 def join(self, iterable: cabc.Iterable[str | _HasHTML], /) -> te.Self:
171 return self.__class__(super().join(map(self.escape, iterable)))
173 def split( # type: ignore[override]
174 self, /, sep: str | None = None, maxsplit: t.SupportsIndex = -1
175 ) -> list[te.Self]:
176 return [self.__class__(v) for v in super().split(sep, maxsplit)]
178 def rsplit( # type: ignore[override]
179 self, /, sep: str | None = None, maxsplit: t.SupportsIndex = -1
180 ) -> list[te.Self]:
181 return [self.__class__(v) for v in super().rsplit(sep, maxsplit)]
183 def splitlines( # type: ignore[override]
184 self, /, keepends: bool = False
185 ) -> list[te.Self]:
186 return [self.__class__(v) for v in super().splitlines(keepends)]
188 def unescape(self, /) -> str:
189 """Convert escaped markup back into a text string. This replaces
190 HTML entities with the characters they represent.
192 >>> Markup("Main » <em>About</em>").unescape()
193 'Main » <em>About</em>'
194 """
195 from html import unescape
197 return unescape(str(self))
199 def striptags(self, /) -> str:
200 """:meth:`unescape` the markup, remove tags, and normalize
201 whitespace to single spaces.
203 >>> Markup("Main »\t<em>About</em>").striptags()
204 'Main » About'
205 """
206 value = str(self)
208 # Look for comments then tags separately. Otherwise, a comment that
209 # contains a tag would end early, leaving some of the comment behind.
211 # keep finding comment start marks
212 while (start := value.find("<!--")) != -1:
213 # find a comment end mark beyond the start, otherwise stop
214 if (end := value.find("-->", start)) == -1:
215 break
217 value = f"{value[:start]}{value[end + 3 :]}"
219 # remove tags using the same method
220 while (start := value.find("<")) != -1:
221 if (end := value.find(">", start)) == -1:
222 break
224 value = f"{value[:start]}{value[end + 1 :]}"
226 # collapse spaces
227 value = " ".join(value.split())
228 return self.__class__(value).unescape()
230 @classmethod
231 def escape(cls, s: t.Any, /) -> te.Self:
232 """Escape a string. Calls :func:`escape` and ensures that for
233 subclasses the correct type is returned.
234 """
235 rv = escape(s)
237 if rv.__class__ is not cls:
238 return cls(rv)
240 return rv # type: ignore[return-value]
242 def __getitem__(self, key: t.SupportsIndex | slice, /) -> te.Self:
243 return self.__class__(super().__getitem__(key))
245 def capitalize(self, /) -> te.Self:
246 return self.__class__(super().capitalize())
248 def title(self, /) -> te.Self:
249 return self.__class__(super().title())
251 def lower(self, /) -> te.Self:
252 return self.__class__(super().lower())
254 def upper(self, /) -> te.Self:
255 return self.__class__(super().upper())
257 def replace(self, old: str, new: str, count: t.SupportsIndex = -1, /) -> te.Self:
258 return self.__class__(super().replace(old, self.escape(new), count))
260 def ljust(self, width: t.SupportsIndex, fillchar: str = " ", /) -> te.Self:
261 return self.__class__(super().ljust(width, self.escape(fillchar)))
263 def rjust(self, width: t.SupportsIndex, fillchar: str = " ", /) -> te.Self:
264 return self.__class__(super().rjust(width, self.escape(fillchar)))
266 def lstrip(self, chars: str | None = None, /) -> te.Self:
267 return self.__class__(super().lstrip(chars))
269 def rstrip(self, chars: str | None = None, /) -> te.Self:
270 return self.__class__(super().rstrip(chars))
272 def center(self, width: t.SupportsIndex, fillchar: str = " ", /) -> te.Self:
273 return self.__class__(super().center(width, self.escape(fillchar)))
275 def strip(self, chars: str | None = None, /) -> te.Self:
276 return self.__class__(super().strip(chars))
278 def translate(
279 self,
280 table: cabc.Mapping[int, str | int | None], # type: ignore[override]
281 /,
282 ) -> str:
283 return self.__class__(super().translate(table))
285 def expandtabs(self, /, tabsize: t.SupportsIndex = 8) -> te.Self:
286 return self.__class__(super().expandtabs(tabsize))
288 def swapcase(self, /) -> te.Self:
289 return self.__class__(super().swapcase())
291 def zfill(self, width: t.SupportsIndex, /) -> te.Self:
292 return self.__class__(super().zfill(width))
294 def casefold(self, /) -> te.Self:
295 return self.__class__(super().casefold())
297 def removeprefix(self, prefix: str, /) -> te.Self:
298 return self.__class__(super().removeprefix(prefix))
300 def removesuffix(self, suffix: str) -> te.Self:
301 return self.__class__(super().removesuffix(suffix))
303 def partition(self, sep: str, /) -> tuple[te.Self, te.Self, te.Self]:
304 left, sep, right = super().partition(sep)
305 cls = self.__class__
306 return cls(left), cls(sep), cls(right)
308 def rpartition(self, sep: str, /) -> tuple[te.Self, te.Self, te.Self]:
309 left, sep, right = super().rpartition(sep)
310 cls = self.__class__
311 return cls(left), cls(sep), cls(right)
313 def format(self, *args: t.Any, **kwargs: t.Any) -> te.Self:
314 formatter = EscapeFormatter(self.escape)
315 return self.__class__(formatter.vformat(self, args, kwargs))
317 def format_map(
318 self,
319 mapping: cabc.Mapping[str, t.Any], # type: ignore[override]
320 /,
321 ) -> te.Self:
322 formatter = EscapeFormatter(self.escape)
323 return self.__class__(formatter.vformat(self, (), mapping))
325 def __html_format__(self, format_spec: str, /) -> te.Self:
326 if format_spec:
327 raise ValueError("Unsupported format specification for Markup.")
329 return self
332class EscapeFormatter(string.Formatter):
333 __slots__ = ("escape",)
335 def __init__(self, escape: _TPEscape) -> None:
336 self.escape: _TPEscape = escape
337 super().__init__()
339 def format_field(self, value: t.Any, format_spec: str) -> str:
340 if hasattr(value, "__html_format__"):
341 rv = value.__html_format__(format_spec)
342 elif hasattr(value, "__html__"):
343 if format_spec:
344 raise ValueError(
345 f"Format specifier {format_spec} given, but {type(value)} does not"
346 " define __html_format__. A class that defines __html__ must define"
347 " __html_format__ to work with format specifiers."
348 )
349 rv = value.__html__()
350 else:
351 # We need to make sure the format spec is str here as
352 # otherwise the wrong callback methods are invoked.
353 rv = super().format_field(value, str(format_spec))
354 return str(self.escape(rv))
357class _MarkupEscapeHelper:
358 """Helper for :meth:`Markup.__mod__`."""
360 __slots__ = ("obj", "escape")
362 def __init__(self, obj: t.Any, escape: _TPEscape) -> None:
363 self.obj: t.Any = obj
364 self.escape: _TPEscape = escape
366 def __getitem__(self, key: t.Any, /) -> te.Self:
367 return self.__class__(self.obj[key], self.escape)
369 def __str__(self, /) -> str:
370 return str(self.escape(self.obj))
372 def __repr__(self, /) -> str:
373 return str(self.escape(repr(self.obj)))
375 def __int__(self, /) -> int:
376 return int(self.obj)
378 def __float__(self, /) -> float:
379 return float(self.obj)
382def __getattr__(name: str) -> t.Any:
383 if name == "__version__":
384 import importlib.metadata
385 import warnings
387 warnings.warn(
388 "The '__version__' attribute is deprecated and will be removed in"
389 " MarkupSafe 3.1. Use feature detection, or"
390 ' `importlib.metadata.version("markupsafe")`, instead.',
391 DeprecationWarning,
392 stacklevel=2,
393 )
394 return importlib.metadata.version("markupsafe")
396 raise AttributeError(name)