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

1from __future__ import annotations 

2 

3import collections.abc as cabc 

4import re 

5import string 

6import sys 

7import typing as t 

8 

9if t.TYPE_CHECKING: 

10 import typing_extensions as te 

11 

12 class HasHTML(te.Protocol): 

13 def __html__(self, /) -> str: 

14 ... 

15 

16 class TPEscape(te.Protocol): 

17 def __call__(self, s: t.Any, /) -> Markup: 

18 ... 

19 

20 

21_strip_comments_re = re.compile(r"<!--.*?-->", re.DOTALL) 

22_strip_tags_re = re.compile(r"<.*?>", re.DOTALL) 

23 

24 

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. 

29 

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. 

33 

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 &lt;em&gt;World&lt;/em&gt;!') 

40 

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. 

44 

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>') 

51 

52 This is a subclass of :class:`str`. It has the same methods, but 

53 escapes their arguments and returns a ``Markup`` instance. 

54 

55 >>> Markup("<em>%s</em>") % ("foo & bar",) 

56 Markup('<em>foo &amp; bar</em>') 

57 >>> Markup("<em>Hello</em> ") + "<foo>" 

58 Markup('<em>Hello</em> &lt;foo&gt;') 

59 """ 

60 

61 __slots__ = () 

62 

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__() 

68 

69 if encoding is None: 

70 return super().__new__(cls, object) 

71 

72 return super().__new__(cls, object, encoding, errors) 

73 

74 def __html__(self, /) -> te.Self: 

75 return self 

76 

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))) 

80 

81 return NotImplemented 

82 

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) 

86 

87 return NotImplemented 

88 

89 def __mul__(self, value: t.SupportsIndex, /) -> te.Self: 

90 return self.__class__(super().__mul__(value)) 

91 

92 def __rmul__(self, value: t.SupportsIndex, /) -> te.Self: 

93 return self.__class__(super().__mul__(value)) 

94 

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),) 

105 

106 return self.__class__(super().__mod__(value)) 

107 

108 def __repr__(self, /) -> str: 

109 return f"{self.__class__.__name__}({super().__repr__()})" 

110 

111 def join(self, iterable: cabc.Iterable[str | HasHTML], /) -> te.Self: 

112 return self.__class__(super().join(map(self.escape, iterable))) 

113 

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)] 

118 

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)] 

123 

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)] 

128 

129 def unescape(self, /) -> str: 

130 """Convert escaped markup back into a text string. This replaces 

131 HTML entities with the characters they represent. 

132 

133 >>> Markup("Main &raquo; <em>About</em>").unescape() 

134 'Main » <em>About</em>' 

135 """ 

136 from html import unescape 

137 

138 return unescape(str(self)) 

139 

140 def striptags(self, /) -> str: 

141 """:meth:`unescape` the markup, remove tags, and normalize 

142 whitespace to single spaces. 

143 

144 >>> Markup("Main &raquo;\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() 

152 

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) 

159 

160 if rv.__class__ is not cls: 

161 return cls(rv) 

162 

163 return rv # type: ignore[return-value] 

164 

165 def __getitem__(self, key: t.SupportsIndex | slice, /) -> te.Self: 

166 return self.__class__(super().__getitem__(key)) 

167 

168 def capitalize(self, /) -> te.Self: 

169 return self.__class__(super().capitalize()) 

170 

171 def title(self, /) -> te.Self: 

172 return self.__class__(super().title()) 

173 

174 def lower(self, /) -> te.Self: 

175 return self.__class__(super().lower()) 

176 

177 def upper(self, /) -> te.Self: 

178 return self.__class__(super().upper()) 

179 

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)) 

182 

183 def ljust(self, width: t.SupportsIndex, fillchar: str = " ", /) -> te.Self: 

184 return self.__class__(super().ljust(width, self.escape(fillchar))) 

185 

186 def rjust(self, width: t.SupportsIndex, fillchar: str = " ", /) -> te.Self: 

187 return self.__class__(super().rjust(width, self.escape(fillchar))) 

188 

189 def lstrip(self, chars: str | None = None, /) -> te.Self: 

190 return self.__class__(super().lstrip(chars)) 

191 

192 def rstrip(self, chars: str | None = None, /) -> te.Self: 

193 return self.__class__(super().rstrip(chars)) 

194 

195 def center(self, width: t.SupportsIndex, fillchar: str = " ", /) -> te.Self: 

196 return self.__class__(super().center(width, self.escape(fillchar))) 

197 

198 def strip(self, chars: str | None = None, /) -> te.Self: 

199 return self.__class__(super().strip(chars)) 

200 

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)) 

207 

208 def expandtabs(self, /, tabsize: t.SupportsIndex = 8) -> te.Self: 

209 return self.__class__(super().expandtabs(tabsize)) 

210 

211 def swapcase(self, /) -> te.Self: 

212 return self.__class__(super().swapcase()) 

213 

214 def zfill(self, width: t.SupportsIndex, /) -> te.Self: 

215 return self.__class__(super().zfill(width)) 

216 

217 def casefold(self, /) -> te.Self: 

218 return self.__class__(super().casefold()) 

219 

220 if sys.version_info >= (3, 9): 

221 

222 def removeprefix(self, prefix: str, /) -> te.Self: 

223 return self.__class__(super().removeprefix(prefix)) 

224 

225 def removesuffix(self, suffix: str) -> te.Self: 

226 return self.__class__(super().removesuffix(suffix)) 

227 

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) 

232 

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) 

237 

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)) 

241 

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)) 

249 

250 def __html_format__(self, format_spec: str, /) -> te.Self: 

251 if format_spec: 

252 raise ValueError("Unsupported format specification for Markup.") 

253 

254 return self 

255 

256 

257class EscapeFormatter(string.Formatter): 

258 __slots__ = ("escape",) 

259 

260 def __init__(self, escape: TPEscape) -> None: 

261 self.escape: TPEscape = escape 

262 super().__init__() 

263 

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)) 

280 

281 

282class _MarkupEscapeHelper: 

283 """Helper for :meth:`Markup.__mod__`.""" 

284 

285 __slots__ = ("obj", "escape") 

286 

287 def __init__(self, obj: t.Any, escape: TPEscape) -> None: 

288 self.obj: t.Any = obj 

289 self.escape: TPEscape = escape 

290 

291 def __getitem__(self, key: t.Any, /) -> te.Self: 

292 return self.__class__(self.obj[key], self.escape) 

293 

294 def __str__(self, /) -> str: 

295 return str(self.escape(self.obj)) 

296 

297 def __repr__(self, /) -> str: 

298 return str(self.escape(repr(self.obj))) 

299 

300 def __int__(self, /) -> int: 

301 return int(self.obj) 

302 

303 def __float__(self, /) -> float: 

304 return float(self.obj) 

305 

306 

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 

316 

317 

318def __getattr__(name: str) -> t.Any: 

319 if name == "__version__": 

320 import importlib.metadata 

321 import warnings 

322 

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") 

330 

331 raise AttributeError(name)