Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/markupsafe/__init__.py: 38%

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

187 statements  

1from __future__ import annotations 

2 

3import collections.abc as cabc 

4import string 

5import typing as t 

6 

7try: 

8 from ._speedups import _escape_inner 

9except ImportError: 

10 from ._native import _escape_inner 

11 

12if t.TYPE_CHECKING: 

13 import typing_extensions as te 

14 

15 

16class _HasHTML(t.Protocol): 

17 def __html__(self, /) -> str: ... 

18 

19 

20class _TPEscape(t.Protocol): 

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

22 

23 

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. 

28 

29 If the object has an ``__html__`` method, it is called and the 

30 return value is assumed to already be safe for HTML. 

31 

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

41 

42 if hasattr(s, "__html__"): 

43 return Markup(s.__html__()) 

44 

45 return Markup(_escape_inner(str(s))) 

46 

47 

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``. 

52 

53 >>> escape(None) 

54 Markup('None') 

55 >>> escape_silent(None) 

56 Markup('') 

57 """ 

58 if s is None: 

59 return Markup() 

60 

61 return escape(s) 

62 

63 

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. 

69 

70 >>> value = escape("<User 1>") 

71 >>> value 

72 Markup('&lt;User 1&gt;') 

73 >>> escape(str(value)) 

74 Markup('&amp;lt;User 1&amp;gt;') 

75 >>> escape(soft_str(value)) 

76 Markup('&lt;User 1&gt;') 

77 """ 

78 if not isinstance(s, str): 

79 return str(s) 

80 

81 return s 

82 

83 

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. 

88 

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. 

92 

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

99 

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. 

103 

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

110 

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

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

113 

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

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

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

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

118 """ 

119 

120 __slots__ = () 

121 

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

127 

128 if encoding is None: 

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

130 

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

132 

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

134 return self 

135 

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

139 

140 return NotImplemented 

141 

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) 

145 

146 return NotImplemented 

147 

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

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

150 

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

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

153 

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

164 

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

166 

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

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

169 

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

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

172 

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

177 

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

182 

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

187 

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

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

190 HTML entities with the characters they represent. 

191 

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

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

194 """ 

195 from html import unescape 

196 

197 return unescape(str(self)) 

198 

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

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

201 whitespace to single spaces. 

202 

203 >>> Markup("Main &raquo;\t<em>About</em>").striptags() 

204 'Main » About' 

205 """ 

206 value = str(self) 

207 parts = [] 

208 pos = 0 

209 

210 while (start := value.find("<", pos)) != -1: 

211 if value.startswith("<!--", start): 

212 # comment 

213 if (end := value.find("-->", start + 4)) == -1: 

214 # unclosed 

215 break 

216 

217 end += 3 

218 else: 

219 # tag 

220 if (end := value.find(">", start)) == -1: 

221 # unclosed 

222 break 

223 

224 end += 1 

225 

226 parts.append(value[pos:start]) 

227 pos = end 

228 

229 # found at least one tag, combine parts 

230 if pos > 0: 

231 # any trailing data after the last closed tag 

232 parts.append(value[pos:]) 

233 value = "".join(parts) 

234 

235 # collapse spaces 

236 value = " ".join(value.split()) 

237 # unescape the processed value using the current class 

238 return self.__class__(value).unescape() 

239 

240 @classmethod 

241 def escape(cls, s: t.Any, /) -> te.Self: 

242 """Escape a string. Calls :func:`escape` and ensures that for 

243 subclasses the correct type is returned. 

244 """ 

245 rv = escape(s) 

246 

247 if rv.__class__ is not cls: 

248 return cls(rv) 

249 

250 return rv 

251 

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

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

254 

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

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

257 

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

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

260 

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

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

263 

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

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

266 

267 def replace(self, old: str, new: str, count: t.SupportsIndex = -1, /) -> te.Self: 

268 return self.__class__(super().replace(old, self.escape(new), count)) 

269 

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

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

272 

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

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

275 

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

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

278 

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

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

281 

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

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

284 

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

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

287 

288 def translate( 

289 self, 

290 table: cabc.Mapping[int, str | int | None], # type: ignore[override] 

291 /, 

292 ) -> str: 

293 return self.__class__(super().translate(table)) 

294 

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

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

297 

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

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

300 

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

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

303 

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

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

306 

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

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

309 

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

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

312 

313 def partition(self, sep: str, /) -> tuple[te.Self, te.Self, te.Self]: 

314 left, sep, right = super().partition(sep) 

315 cls = self.__class__ 

316 return cls(left), cls(sep), cls(right) 

317 

318 def rpartition(self, sep: str, /) -> tuple[te.Self, te.Self, te.Self]: 

319 left, sep, right = super().rpartition(sep) 

320 cls = self.__class__ 

321 return cls(left), cls(sep), cls(right) 

322 

323 def format(self, *args: t.Any, **kwargs: t.Any) -> te.Self: 

324 formatter = EscapeFormatter(self.escape) 

325 return self.__class__(formatter.vformat(self, args, kwargs)) 

326 

327 def format_map( 

328 self, 

329 mapping: cabc.Mapping[str, t.Any], # type: ignore[override] 

330 /, 

331 ) -> te.Self: 

332 formatter = EscapeFormatter(self.escape) 

333 return self.__class__(formatter.vformat(self, (), mapping)) 

334 

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

336 if format_spec: 

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

338 

339 return self 

340 

341 

342class EscapeFormatter(string.Formatter): 

343 __slots__ = ("escape",) 

344 

345 def __init__(self, escape: _TPEscape) -> None: 

346 self.escape: _TPEscape = escape 

347 super().__init__() 

348 

349 def format_field(self, value: t.Any, format_spec: str) -> str: 

350 if hasattr(value, "__html_format__"): 

351 rv = value.__html_format__(format_spec) 

352 elif hasattr(value, "__html__"): 

353 if format_spec: 

354 raise ValueError( 

355 f"Format specifier {format_spec} given, but {type(value)} does not" 

356 " define __html_format__. A class that defines __html__ must define" 

357 " __html_format__ to work with format specifiers." 

358 ) 

359 rv = value.__html__() 

360 else: 

361 # We need to make sure the format spec is str here as 

362 # otherwise the wrong callback methods are invoked. 

363 rv = super().format_field(value, str(format_spec)) 

364 return str(self.escape(rv)) 

365 

366 

367class _MarkupEscapeHelper: 

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

369 

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

371 

372 def __init__(self, obj: t.Any, escape: _TPEscape) -> None: 

373 self.obj: t.Any = obj 

374 self.escape: _TPEscape = escape 

375 

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

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

378 

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

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

381 

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

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

384 

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

386 return int(self.obj) 

387 

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

389 return float(self.obj) 

390 

391 

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

393 if name == "__version__": 

394 import importlib.metadata 

395 import warnings 

396 

397 warnings.warn( 

398 "The '__version__' attribute is deprecated and will be removed in" 

399 " MarkupSafe 3.1. Use feature detection, or" 

400 ' `importlib.metadata.version("markupsafe")`, instead.', 

401 DeprecationWarning, 

402 stacklevel=2, 

403 ) 

404 return importlib.metadata.version("markupsafe") 

405 

406 raise AttributeError(name)