Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/markupsafe/__init__.py: 52%
170 statements
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-23 06:14 +0000
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-23 06:14 +0000
1from __future__ import annotations
3import collections.abc as cabc
4import re
5import string
6import sys
7import typing as t
9if t.TYPE_CHECKING:
10 import typing_extensions as te
12 class HasHTML(te.Protocol):
13 def __html__(self, /) -> str:
14 ...
16 class TPEscape(te.Protocol):
17 def __call__(self, s: t.Any, /) -> Markup:
18 ...
21_strip_comments_re = re.compile(r"<!--.*?-->", re.DOTALL)
22_strip_tags_re = re.compile(r"<.*?>", re.DOTALL)
25class Markup(str):
26 """A string that is ready to be safely inserted into an HTML or XML
27 document, either because it was escaped or because it was marked
28 safe.
30 Passing an object to the constructor converts it to text and wraps
31 it to mark it safe without escaping. To escape the text, use the
32 :meth:`escape` class method instead.
34 >>> Markup("Hello, <em>World</em>!")
35 Markup('Hello, <em>World</em>!')
36 >>> Markup(42)
37 Markup('42')
38 >>> Markup.escape("Hello, <em>World</em>!")
39 Markup('Hello <em>World</em>!')
41 This implements the ``__html__()`` interface that some frameworks
42 use. Passing an object that implements ``__html__()`` will wrap the
43 output of that method, marking it safe.
45 >>> class Foo:
46 ... def __html__(self):
47 ... return '<a href="/foo">foo</a>'
48 ...
49 >>> Markup(Foo())
50 Markup('<a href="/foo">foo</a>')
52 This is a subclass of :class:`str`. It has the same methods, but
53 escapes their arguments and returns a ``Markup`` instance.
55 >>> Markup("<em>%s</em>") % ("foo & bar",)
56 Markup('<em>foo & bar</em>')
57 >>> Markup("<em>Hello</em> ") + "<foo>"
58 Markup('<em>Hello</em> <foo>')
59 """
61 __slots__ = ()
63 def __new__(
64 cls, object: t.Any = "", encoding: str | None = None, errors: str = "strict"
65 ) -> te.Self:
66 if hasattr(object, "__html__"):
67 object = object.__html__()
69 if encoding is None:
70 return super().__new__(cls, object)
72 return super().__new__(cls, object, encoding, errors)
74 def __html__(self, /) -> te.Self:
75 return self
77 def __add__(self, value: str | HasHTML, /) -> te.Self:
78 if isinstance(value, str) or hasattr(value, "__html__"):
79 return self.__class__(super().__add__(self.escape(value)))
81 return NotImplemented
83 def __radd__(self, value: str | HasHTML, /) -> te.Self:
84 if isinstance(value, str) or hasattr(value, "__html__"):
85 return self.escape(value).__add__(self)
87 return NotImplemented
89 def __mul__(self, value: t.SupportsIndex, /) -> te.Self:
90 return self.__class__(super().__mul__(value))
92 def __rmul__(self, value: t.SupportsIndex, /) -> te.Self:
93 return self.__class__(super().__mul__(value))
95 def __mod__(self, value: t.Any, /) -> te.Self:
96 if isinstance(value, tuple):
97 # a tuple of arguments, each wrapped
98 value = tuple(_MarkupEscapeHelper(x, self.escape) for x in value)
99 elif hasattr(type(value), "__getitem__") and not isinstance(value, str):
100 # a mapping of arguments, wrapped
101 value = _MarkupEscapeHelper(value, self.escape)
102 else:
103 # a single argument, wrapped with the helper and a tuple
104 value = (_MarkupEscapeHelper(value, self.escape),)
106 return self.__class__(super().__mod__(value))
108 def __repr__(self, /) -> str:
109 return f"{self.__class__.__name__}({super().__repr__()})"
111 def join(self, iterable: cabc.Iterable[str | HasHTML], /) -> te.Self:
112 return self.__class__(super().join(map(self.escape, iterable)))
114 def split( # type: ignore[override]
115 self, /, sep: str | None = None, maxsplit: t.SupportsIndex = -1
116 ) -> list[te.Self]:
117 return [self.__class__(v) for v in super().split(sep, maxsplit)]
119 def rsplit( # type: ignore[override]
120 self, /, sep: str | None = None, maxsplit: t.SupportsIndex = -1
121 ) -> list[te.Self]:
122 return [self.__class__(v) for v in super().rsplit(sep, maxsplit)]
124 def splitlines( # type: ignore[override]
125 self, /, keepends: bool = False
126 ) -> list[te.Self]:
127 return [self.__class__(v) for v in super().splitlines(keepends)]
129 def unescape(self, /) -> str:
130 """Convert escaped markup back into a text string. This replaces
131 HTML entities with the characters they represent.
133 >>> Markup("Main » <em>About</em>").unescape()
134 'Main » <em>About</em>'
135 """
136 from html import unescape
138 return unescape(str(self))
140 def striptags(self, /) -> str:
141 """:meth:`unescape` the markup, remove tags, and normalize
142 whitespace to single spaces.
144 >>> Markup("Main »\t<em>About</em>").striptags()
145 'Main » About'
146 """
147 # Use two regexes to avoid ambiguous matches.
148 value = _strip_comments_re.sub("", self)
149 value = _strip_tags_re.sub("", value)
150 value = " ".join(value.split())
151 return self.__class__(value).unescape()
153 @classmethod
154 def escape(cls, s: t.Any, /) -> te.Self:
155 """Escape a string. Calls :func:`escape` and ensures that for
156 subclasses the correct type is returned.
157 """
158 rv = escape(s)
160 if rv.__class__ is not cls:
161 return cls(rv)
163 return rv # type: ignore[return-value]
165 def __getitem__(self, key: t.SupportsIndex | slice, /) -> te.Self:
166 return self.__class__(super().__getitem__(key))
168 def capitalize(self, /) -> te.Self:
169 return self.__class__(super().capitalize())
171 def title(self, /) -> te.Self:
172 return self.__class__(super().title())
174 def lower(self, /) -> te.Self:
175 return self.__class__(super().lower())
177 def upper(self, /) -> te.Self:
178 return self.__class__(super().upper())
180 def replace(self, old: str, new: str, count: t.SupportsIndex = -1, /) -> te.Self:
181 return self.__class__(super().replace(old, self.escape(new), count))
183 def ljust(self, width: t.SupportsIndex, fillchar: str = " ", /) -> te.Self:
184 return self.__class__(super().ljust(width, self.escape(fillchar)))
186 def rjust(self, width: t.SupportsIndex, fillchar: str = " ", /) -> te.Self:
187 return self.__class__(super().rjust(width, self.escape(fillchar)))
189 def lstrip(self, chars: str | None = None, /) -> te.Self:
190 return self.__class__(super().lstrip(chars))
192 def rstrip(self, chars: str | None = None, /) -> te.Self:
193 return self.__class__(super().rstrip(chars))
195 def center(self, width: t.SupportsIndex, fillchar: str = " ", /) -> te.Self:
196 return self.__class__(super().center(width, self.escape(fillchar)))
198 def strip(self, chars: str | None = None, /) -> te.Self:
199 return self.__class__(super().strip(chars))
201 def translate(
202 self,
203 table: cabc.Mapping[int, str | int | None], # type: ignore[override]
204 /,
205 ) -> str:
206 return self.__class__(super().translate(table))
208 def expandtabs(self, /, tabsize: t.SupportsIndex = 8) -> te.Self:
209 return self.__class__(super().expandtabs(tabsize))
211 def swapcase(self, /) -> te.Self:
212 return self.__class__(super().swapcase())
214 def zfill(self, width: t.SupportsIndex, /) -> te.Self:
215 return self.__class__(super().zfill(width))
217 def casefold(self, /) -> te.Self:
218 return self.__class__(super().casefold())
220 if sys.version_info >= (3, 9):
222 def removeprefix(self, prefix: str, /) -> te.Self:
223 return self.__class__(super().removeprefix(prefix))
225 def removesuffix(self, suffix: str) -> te.Self:
226 return self.__class__(super().removesuffix(suffix))
228 def partition(self, sep: str, /) -> tuple[te.Self, te.Self, te.Self]:
229 l, s, r = super().partition(sep)
230 cls = self.__class__
231 return cls(l), cls(s), cls(r)
233 def rpartition(self, sep: str, /) -> tuple[te.Self, te.Self, te.Self]:
234 l, s, r = super().rpartition(sep)
235 cls = self.__class__
236 return cls(l), cls(s), cls(r)
238 def format(self, *args: t.Any, **kwargs: t.Any) -> te.Self:
239 formatter = EscapeFormatter(self.escape)
240 return self.__class__(formatter.vformat(self, args, kwargs))
242 def format_map(
243 self,
244 mapping: cabc.Mapping[str, t.Any], # type: ignore[override]
245 /,
246 ) -> te.Self:
247 formatter = EscapeFormatter(self.escape)
248 return self.__class__(formatter.vformat(self, (), mapping))
250 def __html_format__(self, format_spec: str, /) -> te.Self:
251 if format_spec:
252 raise ValueError("Unsupported format specification for Markup.")
254 return self
257class EscapeFormatter(string.Formatter):
258 __slots__ = ("escape",)
260 def __init__(self, escape: TPEscape) -> None:
261 self.escape: TPEscape = escape
262 super().__init__()
264 def format_field(self, value: t.Any, format_spec: str) -> str:
265 if hasattr(value, "__html_format__"):
266 rv = value.__html_format__(format_spec)
267 elif hasattr(value, "__html__"):
268 if format_spec:
269 raise ValueError(
270 f"Format specifier {format_spec} given, but {type(value)} does not"
271 " define __html_format__. A class that defines __html__ must define"
272 " __html_format__ to work with format specifiers."
273 )
274 rv = value.__html__()
275 else:
276 # We need to make sure the format spec is str here as
277 # otherwise the wrong callback methods are invoked.
278 rv = super().format_field(value, str(format_spec))
279 return str(self.escape(rv))
282class _MarkupEscapeHelper:
283 """Helper for :meth:`Markup.__mod__`."""
285 __slots__ = ("obj", "escape")
287 def __init__(self, obj: t.Any, escape: TPEscape) -> None:
288 self.obj: t.Any = obj
289 self.escape: TPEscape = escape
291 def __getitem__(self, key: t.Any, /) -> te.Self:
292 return self.__class__(self.obj[key], self.escape)
294 def __str__(self, /) -> str:
295 return str(self.escape(self.obj))
297 def __repr__(self, /) -> str:
298 return str(self.escape(repr(self.obj)))
300 def __int__(self, /) -> int:
301 return int(self.obj)
303 def __float__(self, /) -> float:
304 return float(self.obj)
307# circular import
308try:
309 from ._speedups import escape as escape
310 from ._speedups import escape_silent as escape_silent
311 from ._speedups import soft_str as soft_str
312except ImportError:
313 from ._native import escape as escape
314 from ._native import escape_silent as escape_silent # noqa: F401
315 from ._native import soft_str as soft_str # noqa: F401
318def __getattr__(name: str) -> t.Any:
319 if name == "__version__":
320 import importlib.metadata
321 import warnings
323 warnings.warn(
324 "The '__version__' attribute is deprecated and will be removed in"
325 " MarkupSafe 3.1. Use feature detection, or"
326 ' `importlib.metadata.version("markupsafe")`, instead.',
327 stacklevel=2,
328 )
329 return importlib.metadata.version("flask-classful")
331 raise AttributeError(name)