summaryrefslogtreecommitdiffstats
path: root/contrib/python
diff options
context:
space:
mode:
authorrobot-contrib <[email protected]>2024-10-22 10:34:11 +0300
committerrobot-contrib <[email protected]>2024-10-22 10:46:51 +0300
commit46fcf833e07a09937c4706e97ef898da21e2d48a (patch)
tree142bf42051026bd1e23bbfa7ffb38306eb058265 /contrib/python
parentf31afaca1d29160f76a2e6480e1ebf634cb95b98 (diff)
Update contrib/python/MarkupSafe/py3 to 3.0.0
commit_hash:541556d8b37adaf0555d01e3ec72cc263f013e0a
Diffstat (limited to 'contrib/python')
-rw-r--r--contrib/python/MarkupSafe/py3/.dist-info/METADATA115
-rw-r--r--contrib/python/MarkupSafe/py3/LICENSE.txt (renamed from contrib/python/MarkupSafe/py3/LICENSE.rst)0
-rw-r--r--contrib/python/MarkupSafe/py3/README.md40
-rw-r--r--contrib/python/MarkupSafe/py3/README.rst67
-rw-r--r--contrib/python/MarkupSafe/py3/markupsafe/__init__.py353
-rw-r--r--contrib/python/MarkupSafe/py3/markupsafe/_native.py61
-rw-r--r--contrib/python/MarkupSafe/py3/markupsafe/_speedups.c156
-rw-r--r--contrib/python/MarkupSafe/py3/markupsafe/_speedups.pyi10
-rw-r--r--contrib/python/MarkupSafe/py3/tests/__init__.py0
-rw-r--r--contrib/python/MarkupSafe/py3/tests/conftest.py36
-rw-r--r--contrib/python/MarkupSafe/py3/tests/test_escape.py13
-rw-r--r--contrib/python/MarkupSafe/py3/tests/test_exception_custom_html.py9
-rw-r--r--contrib/python/MarkupSafe/py3/tests/test_leak.py25
-rw-r--r--contrib/python/MarkupSafe/py3/tests/test_markupsafe.py75
-rw-r--r--contrib/python/MarkupSafe/py3/ya.make2
15 files changed, 415 insertions, 547 deletions
diff --git a/contrib/python/MarkupSafe/py3/.dist-info/METADATA b/contrib/python/MarkupSafe/py3/.dist-info/METADATA
index dfe37d52dfb..d92149c3c5d 100644
--- a/contrib/python/MarkupSafe/py3/.dist-info/METADATA
+++ b/contrib/python/MarkupSafe/py3/.dist-info/METADATA
@@ -1,16 +1,41 @@
Metadata-Version: 2.1
Name: MarkupSafe
-Version: 2.1.5
+Version: 3.0.0
Summary: Safely add untrusted strings to HTML/XML markup.
-Home-page: https://palletsprojects.com/p/markupsafe/
-Maintainer: Pallets
-Maintainer-email: [email protected]
-License: BSD-3-Clause
+Maintainer-email: Pallets <[email protected]>
+License: Copyright 2010 Pallets
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ 3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
Project-URL: Donate, https://palletsprojects.com/donate
Project-URL: Documentation, https://markupsafe.palletsprojects.com/
Project-URL: Changes, https://markupsafe.palletsprojects.com/changes/
-Project-URL: Source Code, https://github.com/pallets/markupsafe/
-Project-URL: Issue Tracker, https://github.com/pallets/markupsafe/issues/
+Project-URL: Source, https://github.com/pallets/markupsafe/
Project-URL: Chat, https://discord.gg/pallets
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
@@ -20,12 +45,12 @@ Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Text Processing :: Markup :: HTML
-Requires-Python: >=3.7
-Description-Content-Type: text/x-rst
-License-File: LICENSE.rst
+Classifier: Typing :: Typed
+Requires-Python: >=3.9
+Description-Content-Type: text/markdown
+License-File: LICENSE.txt
-MarkupSafe
-==========
+# MarkupSafe
MarkupSafe implements a text object that escapes characters so it is
safe to use in HTML and XML. Characters that have special meanings are
@@ -34,60 +59,34 @@ injection attacks, meaning untrusted user input can safely be displayed
on a page.
-Installing
-----------
+## Examples
-Install and update using `pip`_:
+```pycon
+>>> from markupsafe import Markup, escape
-.. code-block:: text
+>>> # escape replaces special characters and wraps in Markup
+>>> escape("<script>alert(document.cookie);</script>")
+Markup('&lt;script&gt;alert(document.cookie);&lt;/script&gt;')
- pip install -U MarkupSafe
+>>> # wrap in Markup to mark text "safe" and prevent escaping
+>>> Markup("<strong>Hello</strong>")
+Markup('<strong>hello</strong>')
-.. _pip: https://pip.pypa.io/en/stable/getting-started/
+>>> escape(Markup("<strong>Hello</strong>"))
+Markup('<strong>hello</strong>')
+>>> # Markup is a str subclass
+>>> # methods and operators escape their arguments
+>>> template = Markup("Hello <em>{name}</em>")
+>>> template.format(name='"World"')
+Markup('Hello <em>&#34;World&#34;</em>')
+```
-Examples
---------
-
-.. code-block:: pycon
-
- >>> from markupsafe import Markup, escape
-
- >>> # escape replaces special characters and wraps in Markup
- >>> escape("<script>alert(document.cookie);</script>")
- Markup('&lt;script&gt;alert(document.cookie);&lt;/script&gt;')
-
- >>> # wrap in Markup to mark text "safe" and prevent escaping
- >>> Markup("<strong>Hello</strong>")
- Markup('<strong>hello</strong>')
-
- >>> escape(Markup("<strong>Hello</strong>"))
- Markup('<strong>hello</strong>')
-
- >>> # Markup is a str subclass
- >>> # methods and operators escape their arguments
- >>> template = Markup("Hello <em>{name}</em>")
- >>> template.format(name='"World"')
- Markup('Hello <em>&#34;World&#34;</em>')
-
-
-Donate
-------
+## Donate
The Pallets organization develops and supports MarkupSafe and other
popular packages. In order to grow the community of contributors and
users, and allow the maintainers to devote more time to the projects,
-`please donate today`_.
-
-.. _please donate today: https://palletsprojects.com/donate
-
-
-Links
------
+[please donate today][].
-- Documentation: https://markupsafe.palletsprojects.com/
-- Changes: https://markupsafe.palletsprojects.com/changes/
-- PyPI Releases: https://pypi.org/project/MarkupSafe/
-- Source Code: https://github.com/pallets/markupsafe/
-- Issue Tracker: https://github.com/pallets/markupsafe/issues/
-- Chat: https://discord.gg/pallets
+[please donate today]: https://palletsprojects.com/donate
diff --git a/contrib/python/MarkupSafe/py3/LICENSE.rst b/contrib/python/MarkupSafe/py3/LICENSE.txt
index 9d227a0cc43..9d227a0cc43 100644
--- a/contrib/python/MarkupSafe/py3/LICENSE.rst
+++ b/contrib/python/MarkupSafe/py3/LICENSE.txt
diff --git a/contrib/python/MarkupSafe/py3/README.md b/contrib/python/MarkupSafe/py3/README.md
new file mode 100644
index 00000000000..df8fbaf908a
--- /dev/null
+++ b/contrib/python/MarkupSafe/py3/README.md
@@ -0,0 +1,40 @@
+# MarkupSafe
+
+MarkupSafe implements a text object that escapes characters so it is
+safe to use in HTML and XML. Characters that have special meanings are
+replaced so that they display as the actual characters. This mitigates
+injection attacks, meaning untrusted user input can safely be displayed
+on a page.
+
+
+## Examples
+
+```pycon
+>>> from markupsafe import Markup, escape
+
+>>> # escape replaces special characters and wraps in Markup
+>>> escape("<script>alert(document.cookie);</script>")
+Markup('&lt;script&gt;alert(document.cookie);&lt;/script&gt;')
+
+>>> # wrap in Markup to mark text "safe" and prevent escaping
+>>> Markup("<strong>Hello</strong>")
+Markup('<strong>hello</strong>')
+
+>>> escape(Markup("<strong>Hello</strong>"))
+Markup('<strong>hello</strong>')
+
+>>> # Markup is a str subclass
+>>> # methods and operators escape their arguments
+>>> template = Markup("Hello <em>{name}</em>")
+>>> template.format(name='"World"')
+Markup('Hello <em>&#34;World&#34;</em>')
+```
+
+## Donate
+
+The Pallets organization develops and supports MarkupSafe and other
+popular packages. In order to grow the community of contributors and
+users, and allow the maintainers to devote more time to the projects,
+[please donate today][].
+
+[please donate today]: https://palletsprojects.com/donate
diff --git a/contrib/python/MarkupSafe/py3/README.rst b/contrib/python/MarkupSafe/py3/README.rst
deleted file mode 100644
index 16431d3a6ab..00000000000
--- a/contrib/python/MarkupSafe/py3/README.rst
+++ /dev/null
@@ -1,67 +0,0 @@
-MarkupSafe
-==========
-
-MarkupSafe implements a text object that escapes characters so it is
-safe to use in HTML and XML. Characters that have special meanings are
-replaced so that they display as the actual characters. This mitigates
-injection attacks, meaning untrusted user input can safely be displayed
-on a page.
-
-
-Installing
-----------
-
-Install and update using `pip`_:
-
-.. code-block:: text
-
- pip install -U MarkupSafe
-
-.. _pip: https://pip.pypa.io/en/stable/getting-started/
-
-
-Examples
---------
-
-.. code-block:: pycon
-
- >>> from markupsafe import Markup, escape
-
- >>> # escape replaces special characters and wraps in Markup
- >>> escape("<script>alert(document.cookie);</script>")
- Markup('&lt;script&gt;alert(document.cookie);&lt;/script&gt;')
-
- >>> # wrap in Markup to mark text "safe" and prevent escaping
- >>> Markup("<strong>Hello</strong>")
- Markup('<strong>hello</strong>')
-
- >>> escape(Markup("<strong>Hello</strong>"))
- Markup('<strong>hello</strong>')
-
- >>> # Markup is a str subclass
- >>> # methods and operators escape their arguments
- >>> template = Markup("Hello <em>{name}</em>")
- >>> template.format(name='"World"')
- Markup('Hello <em>&#34;World&#34;</em>')
-
-
-Donate
-------
-
-The Pallets organization develops and supports MarkupSafe and other
-popular packages. In order to grow the community of contributors and
-users, and allow the maintainers to devote more time to the projects,
-`please donate today`_.
-
-.. _please donate today: https://palletsprojects.com/donate
-
-
-Links
------
-
-- Documentation: https://markupsafe.palletsprojects.com/
-- Changes: https://markupsafe.palletsprojects.com/changes/
-- PyPI Releases: https://pypi.org/project/MarkupSafe/
-- Source Code: https://github.com/pallets/markupsafe/
-- Issue Tracker: https://github.com/pallets/markupsafe/issues/
-- Chat: https://discord.gg/pallets
diff --git a/contrib/python/MarkupSafe/py3/markupsafe/__init__.py b/contrib/python/MarkupSafe/py3/markupsafe/__init__.py
index b40f24c66de..0afddc0c39f 100644
--- a/contrib/python/MarkupSafe/py3/markupsafe/__init__.py
+++ b/contrib/python/MarkupSafe/py3/markupsafe/__init__.py
@@ -1,29 +1,82 @@
-import functools
+from __future__ import annotations
+
+import collections.abc as cabc
import string
-import sys
import typing as t
+try:
+ from ._speedups import _escape_inner
+except ImportError:
+ from ._native import _escape_inner
+
if t.TYPE_CHECKING:
import typing_extensions as te
- class HasHTML(te.Protocol):
- def __html__(self) -> str:
- pass
- _P = te.ParamSpec("_P")
+class _HasHTML(t.Protocol):
+ def __html__(self, /) -> str: ...
+
+
+class _TPEscape(t.Protocol):
+ def __call__(self, s: t.Any, /) -> Markup: ...
+
+
+def escape(s: t.Any, /) -> Markup:
+ """Replace the characters ``&``, ``<``, ``>``, ``'``, and ``"`` in
+ the string with HTML-safe sequences. Use this if you need to display
+ text that might contain such characters in HTML.
+ If the object has an ``__html__`` method, it is called and the
+ return value is assumed to already be safe for HTML.
+
+ :param s: An object to be converted to a string and escaped.
+ :return: A :class:`Markup` string with the escaped text.
+ """
+ # If the object is already a plain string, skip __html__ check and string
+ # conversion. This is the most common use case.
+ if s.__class__ is str:
+ return Markup(_escape_inner(s))
+
+ if hasattr(s, "__html__"):
+ return Markup(s.__html__())
+
+ return Markup(_escape_inner(str(s)))
-__version__ = "2.1.5"
+def escape_silent(s: t.Any | None, /) -> Markup:
+ """Like :func:`escape` but treats ``None`` as the empty string.
+ Useful with optional values, as otherwise you get the string
+ ``'None'`` when the value is ``None``.
-def _simple_escaping_wrapper(func: "t.Callable[_P, str]") -> "t.Callable[_P, Markup]":
- @functools.wraps(func)
- def wrapped(self: "Markup", *args: "_P.args", **kwargs: "_P.kwargs") -> "Markup":
- arg_list = _escape_argspec(list(args), enumerate(args), self.escape)
- _escape_argspec(kwargs, kwargs.items(), self.escape)
- return self.__class__(func(self, *arg_list, **kwargs)) # type: ignore[arg-type]
+ >>> escape(None)
+ Markup('None')
+ >>> escape_silent(None)
+ Markup('')
+ """
+ if s is None:
+ return Markup()
+
+ return escape(s)
- return wrapped # type: ignore[return-value]
+
+def soft_str(s: t.Any, /) -> str:
+ """Convert an object to a string if it isn't already. This preserves
+ a :class:`Markup` string rather than converting it back to a basic
+ string, so it will still be marked as safe and won't be escaped
+ again.
+
+ >>> value = escape("<User 1>")
+ >>> value
+ Markup('&lt;User 1&gt;')
+ >>> escape(str(value))
+ Markup('&amp;lt;User 1&amp;gt;')
+ >>> escape(soft_str(value))
+ Markup('&lt;User 1&gt;')
+ """
+ if not isinstance(s, str):
+ return str(s)
+
+ return s
class Markup(str):
@@ -65,82 +118,72 @@ class Markup(str):
__slots__ = ()
def __new__(
- cls, base: t.Any = "", encoding: t.Optional[str] = None, errors: str = "strict"
- ) -> "te.Self":
- if hasattr(base, "__html__"):
- base = base.__html__()
+ cls, object: t.Any = "", encoding: str | None = None, errors: str = "strict"
+ ) -> te.Self:
+ if hasattr(object, "__html__"):
+ object = object.__html__()
if encoding is None:
- return super().__new__(cls, base)
+ return super().__new__(cls, object)
- return super().__new__(cls, base, encoding, errors)
+ return super().__new__(cls, object, encoding, errors)
- def __html__(self) -> "te.Self":
+ def __html__(self, /) -> te.Self:
return self
- def __add__(self, other: t.Union[str, "HasHTML"]) -> "te.Self":
- if isinstance(other, str) or hasattr(other, "__html__"):
- return self.__class__(super().__add__(self.escape(other)))
+ def __add__(self, value: str | _HasHTML, /) -> te.Self:
+ if isinstance(value, str) or hasattr(value, "__html__"):
+ return self.__class__(super().__add__(self.escape(value)))
return NotImplemented
- def __radd__(self, other: t.Union[str, "HasHTML"]) -> "te.Self":
- if isinstance(other, str) or hasattr(other, "__html__"):
- return self.escape(other).__add__(self)
+ def __radd__(self, value: str | _HasHTML, /) -> te.Self:
+ if isinstance(value, str) or hasattr(value, "__html__"):
+ return self.escape(value).__add__(self)
return NotImplemented
- def __mul__(self, num: "te.SupportsIndex") -> "te.Self":
- if isinstance(num, int):
- return self.__class__(super().__mul__(num))
+ def __mul__(self, value: t.SupportsIndex, /) -> te.Self:
+ return self.__class__(super().__mul__(value))
- return NotImplemented
-
- __rmul__ = __mul__
+ def __rmul__(self, value: t.SupportsIndex, /) -> te.Self:
+ return self.__class__(super().__mul__(value))
- def __mod__(self, arg: t.Any) -> "te.Self":
- if isinstance(arg, tuple):
+ def __mod__(self, value: t.Any, /) -> te.Self:
+ if isinstance(value, tuple):
# a tuple of arguments, each wrapped
- arg = tuple(_MarkupEscapeHelper(x, self.escape) for x in arg)
- elif hasattr(type(arg), "__getitem__") and not isinstance(arg, str):
+ value = tuple(_MarkupEscapeHelper(x, self.escape) for x in value)
+ elif hasattr(type(value), "__getitem__") and not isinstance(value, str):
# a mapping of arguments, wrapped
- arg = _MarkupEscapeHelper(arg, self.escape)
+ value = _MarkupEscapeHelper(value, self.escape)
else:
# a single argument, wrapped with the helper and a tuple
- arg = (_MarkupEscapeHelper(arg, self.escape),)
+ value = (_MarkupEscapeHelper(value, self.escape),)
- return self.__class__(super().__mod__(arg))
+ return self.__class__(super().__mod__(value))
- def __repr__(self) -> str:
+ def __repr__(self, /) -> str:
return f"{self.__class__.__name__}({super().__repr__()})"
- def join(self, seq: t.Iterable[t.Union[str, "HasHTML"]]) -> "te.Self":
- return self.__class__(super().join(map(self.escape, seq)))
-
- join.__doc__ = str.join.__doc__
+ def join(self, iterable: cabc.Iterable[str | _HasHTML], /) -> te.Self:
+ return self.__class__(super().join(map(self.escape, iterable)))
def split( # type: ignore[override]
- self, sep: t.Optional[str] = None, maxsplit: int = -1
- ) -> t.List["te.Self"]:
+ self, /, sep: str | None = None, maxsplit: t.SupportsIndex = -1
+ ) -> list[te.Self]:
return [self.__class__(v) for v in super().split(sep, maxsplit)]
- split.__doc__ = str.split.__doc__
-
def rsplit( # type: ignore[override]
- self, sep: t.Optional[str] = None, maxsplit: int = -1
- ) -> t.List["te.Self"]:
+ self, /, sep: str | None = None, maxsplit: t.SupportsIndex = -1
+ ) -> list[te.Self]:
return [self.__class__(v) for v in super().rsplit(sep, maxsplit)]
- rsplit.__doc__ = str.rsplit.__doc__
-
def splitlines( # type: ignore[override]
- self, keepends: bool = False
- ) -> t.List["te.Self"]:
+ self, /, keepends: bool = False
+ ) -> list[te.Self]:
return [self.__class__(v) for v in super().splitlines(keepends)]
- splitlines.__doc__ = str.splitlines.__doc__
-
- def unescape(self) -> str:
+ def unescape(self, /) -> str:
"""Convert escaped markup back into a text string. This replaces
HTML entities with the characters they represent.
@@ -151,7 +194,7 @@ class Markup(str):
return unescape(str(self))
- def striptags(self) -> str:
+ def striptags(self, /) -> str:
""":meth:`unescape` the markup, remove tags, and normalize
whitespace to single spaces.
@@ -163,31 +206,17 @@ class Markup(str):
# Look for comments then tags separately. Otherwise, a comment that
# contains a tag would end early, leaving some of the comment behind.
- while True:
- # keep finding comment start marks
- start = value.find("<!--")
-
- if start == -1:
- break
-
+ # keep finding comment start marks
+ while (start := value.find("<!--")) != -1:
# find a comment end mark beyond the start, otherwise stop
- end = value.find("-->", start)
-
- if end == -1:
+ if (end := value.find("-->", start)) == -1:
break
value = f"{value[:start]}{value[end + 3:]}"
# remove tags using the same method
- while True:
- start = value.find("<")
-
- if start == -1:
- break
-
- end = value.find(">", start)
-
- if end == -1:
+ while (start := value.find("<")) != -1:
+ if (end := value.find(">", start)) == -1:
break
value = f"{value[:start]}{value[end + 1:]}"
@@ -197,7 +226,7 @@ class Markup(str):
return self.__class__(value).unescape()
@classmethod
- def escape(cls, s: t.Any) -> "te.Self":
+ def escape(cls, s: t.Any, /) -> te.Self:
"""Escape a string. Calls :func:`escape` and ensures that for
subclasses the correct type is returned.
"""
@@ -208,49 +237,90 @@ class Markup(str):
return rv # type: ignore[return-value]
- __getitem__ = _simple_escaping_wrapper(str.__getitem__)
- capitalize = _simple_escaping_wrapper(str.capitalize)
- title = _simple_escaping_wrapper(str.title)
- lower = _simple_escaping_wrapper(str.lower)
- upper = _simple_escaping_wrapper(str.upper)
- replace = _simple_escaping_wrapper(str.replace)
- ljust = _simple_escaping_wrapper(str.ljust)
- rjust = _simple_escaping_wrapper(str.rjust)
- lstrip = _simple_escaping_wrapper(str.lstrip)
- rstrip = _simple_escaping_wrapper(str.rstrip)
- center = _simple_escaping_wrapper(str.center)
- strip = _simple_escaping_wrapper(str.strip)
- translate = _simple_escaping_wrapper(str.translate)
- expandtabs = _simple_escaping_wrapper(str.expandtabs)
- swapcase = _simple_escaping_wrapper(str.swapcase)
- zfill = _simple_escaping_wrapper(str.zfill)
- casefold = _simple_escaping_wrapper(str.casefold)
+ def __getitem__(self, key: t.SupportsIndex | slice, /) -> te.Self:
+ return self.__class__(super().__getitem__(key))
+
+ def capitalize(self, /) -> te.Self:
+ return self.__class__(super().capitalize())
+
+ def title(self, /) -> te.Self:
+ return self.__class__(super().title())
+
+ def lower(self, /) -> te.Self:
+ return self.__class__(super().lower())
+
+ def upper(self, /) -> te.Self:
+ return self.__class__(super().upper())
+
+ def replace(self, old: str, new: str, count: t.SupportsIndex = -1, /) -> te.Self:
+ return self.__class__(super().replace(old, self.escape(new), count))
+
+ def ljust(self, width: t.SupportsIndex, fillchar: str = " ", /) -> te.Self:
+ return self.__class__(super().ljust(width, self.escape(fillchar)))
+
+ def rjust(self, width: t.SupportsIndex, fillchar: str = " ", /) -> te.Self:
+ return self.__class__(super().rjust(width, self.escape(fillchar)))
+
+ def lstrip(self, chars: str | None = None, /) -> te.Self:
+ return self.__class__(super().lstrip(chars))
+
+ def rstrip(self, chars: str | None = None, /) -> te.Self:
+ return self.__class__(super().rstrip(chars))
+
+ def center(self, width: t.SupportsIndex, fillchar: str = " ", /) -> te.Self:
+ return self.__class__(super().center(width, self.escape(fillchar)))
+
+ def strip(self, chars: str | None = None, /) -> te.Self:
+ return self.__class__(super().strip(chars))
+
+ def translate(
+ self,
+ table: cabc.Mapping[int, str | int | None], # type: ignore[override]
+ /,
+ ) -> str:
+ return self.__class__(super().translate(table))
- if sys.version_info >= (3, 9):
- removeprefix = _simple_escaping_wrapper(str.removeprefix)
- removesuffix = _simple_escaping_wrapper(str.removesuffix)
+ def expandtabs(self, /, tabsize: t.SupportsIndex = 8) -> te.Self:
+ return self.__class__(super().expandtabs(tabsize))
- def partition(self, sep: str) -> t.Tuple["te.Self", "te.Self", "te.Self"]:
- l, s, r = super().partition(self.escape(sep))
+ def swapcase(self, /) -> te.Self:
+ return self.__class__(super().swapcase())
+
+ def zfill(self, width: t.SupportsIndex, /) -> te.Self:
+ return self.__class__(super().zfill(width))
+
+ def casefold(self, /) -> te.Self:
+ return self.__class__(super().casefold())
+
+ def removeprefix(self, prefix: str, /) -> te.Self:
+ return self.__class__(super().removeprefix(prefix))
+
+ def removesuffix(self, suffix: str) -> te.Self:
+ return self.__class__(super().removesuffix(suffix))
+
+ def partition(self, sep: str, /) -> tuple[te.Self, te.Self, te.Self]:
+ left, sep, right = super().partition(sep)
cls = self.__class__
- return cls(l), cls(s), cls(r)
+ return cls(left), cls(sep), cls(right)
- def rpartition(self, sep: str) -> t.Tuple["te.Self", "te.Self", "te.Self"]:
- l, s, r = super().rpartition(self.escape(sep))
+ def rpartition(self, sep: str, /) -> tuple[te.Self, te.Self, te.Self]:
+ left, sep, right = super().rpartition(sep)
cls = self.__class__
- return cls(l), cls(s), cls(r)
+ return cls(left), cls(sep), cls(right)
- def format(self, *args: t.Any, **kwargs: t.Any) -> "te.Self":
+ def format(self, *args: t.Any, **kwargs: t.Any) -> te.Self:
formatter = EscapeFormatter(self.escape)
return self.__class__(formatter.vformat(self, args, kwargs))
- def format_map( # type: ignore[override]
- self, map: t.Mapping[str, t.Any]
- ) -> "te.Self":
+ def format_map(
+ self,
+ mapping: cabc.Mapping[str, t.Any], # type: ignore[override]
+ /,
+ ) -> te.Self:
formatter = EscapeFormatter(self.escape)
- return self.__class__(formatter.vformat(self, (), map))
+ return self.__class__(formatter.vformat(self, (), mapping))
- def __html_format__(self, format_spec: str) -> "te.Self":
+ def __html_format__(self, format_spec: str, /) -> te.Self:
if format_spec:
raise ValueError("Unsupported format specification for Markup.")
@@ -260,8 +330,8 @@ class Markup(str):
class EscapeFormatter(string.Formatter):
__slots__ = ("escape",)
- def __init__(self, escape: t.Callable[[t.Any], Markup]) -> None:
- self.escape = escape
+ def __init__(self, escape: _TPEscape) -> None:
+ self.escape: _TPEscape = escape
super().__init__()
def format_field(self, value: t.Any, format_spec: str) -> str:
@@ -278,55 +348,46 @@ class EscapeFormatter(string.Formatter):
else:
# We need to make sure the format spec is str here as
# otherwise the wrong callback methods are invoked.
- rv = string.Formatter.format_field(self, value, str(format_spec))
+ rv = super().format_field(value, str(format_spec))
return str(self.escape(rv))
-_ListOrDict = t.TypeVar("_ListOrDict", list, dict)
-
-
-def _escape_argspec(
- obj: _ListOrDict, iterable: t.Iterable[t.Any], escape: t.Callable[[t.Any], Markup]
-) -> _ListOrDict:
- """Helper for various string-wrapped functions."""
- for key, value in iterable:
- if isinstance(value, str) or hasattr(value, "__html__"):
- obj[key] = escape(value)
-
- return obj
-
-
class _MarkupEscapeHelper:
"""Helper for :meth:`Markup.__mod__`."""
__slots__ = ("obj", "escape")
- def __init__(self, obj: t.Any, escape: t.Callable[[t.Any], Markup]) -> None:
- self.obj = obj
- self.escape = escape
+ def __init__(self, obj: t.Any, escape: _TPEscape) -> None:
+ self.obj: t.Any = obj
+ self.escape: _TPEscape = escape
- def __getitem__(self, item: t.Any) -> "te.Self":
- return self.__class__(self.obj[item], self.escape)
+ def __getitem__(self, key: t.Any, /) -> te.Self:
+ return self.__class__(self.obj[key], self.escape)
- def __str__(self) -> str:
+ def __str__(self, /) -> str:
return str(self.escape(self.obj))
- def __repr__(self) -> str:
+ def __repr__(self, /) -> str:
return str(self.escape(repr(self.obj)))
- def __int__(self) -> int:
+ def __int__(self, /) -> int:
return int(self.obj)
- def __float__(self) -> float:
+ def __float__(self, /) -> float:
return float(self.obj)
-# circular import
-try:
- from ._speedups import escape as escape
- from ._speedups import escape_silent as escape_silent
- from ._speedups import soft_str as soft_str
-except ImportError:
- from ._native import escape as escape
- from ._native import escape_silent as escape_silent # noqa: F401
- from ._native import soft_str as soft_str # noqa: F401
+def __getattr__(name: str) -> t.Any:
+ if name == "__version__":
+ import importlib.metadata
+ import warnings
+
+ warnings.warn(
+ "The '__version__' attribute is deprecated and will be removed in"
+ " MarkupSafe 3.1. Use feature detection, or"
+ ' `importlib.metadata.version("markupsafe")`, instead.',
+ stacklevel=2,
+ )
+ return importlib.metadata.version("markupsafe")
+
+ raise AttributeError(name)
diff --git a/contrib/python/MarkupSafe/py3/markupsafe/_native.py b/contrib/python/MarkupSafe/py3/markupsafe/_native.py
index 8117b2716d1..088b3bca983 100644
--- a/contrib/python/MarkupSafe/py3/markupsafe/_native.py
+++ b/contrib/python/MarkupSafe/py3/markupsafe/_native.py
@@ -1,63 +1,8 @@
-import typing as t
-
-from . import Markup
-
-
-def escape(s: t.Any) -> Markup:
- """Replace the characters ``&``, ``<``, ``>``, ``'``, and ``"`` in
- the string with HTML-safe sequences. Use this if you need to display
- text that might contain such characters in HTML.
-
- If the object has an ``__html__`` method, it is called and the
- return value is assumed to already be safe for HTML.
-
- :param s: An object to be converted to a string and escaped.
- :return: A :class:`Markup` string with the escaped text.
- """
- if hasattr(s, "__html__"):
- return Markup(s.__html__())
-
- return Markup(
- str(s)
- .replace("&", "&amp;")
+def _escape_inner(s: str, /) -> str:
+ return (
+ s.replace("&", "&amp;")
.replace(">", "&gt;")
.replace("<", "&lt;")
.replace("'", "&#39;")
.replace('"', "&#34;")
)
-
-
-def escape_silent(s: t.Optional[t.Any]) -> Markup:
- """Like :func:`escape` but treats ``None`` as the empty string.
- Useful with optional values, as otherwise you get the string
- ``'None'`` when the value is ``None``.
-
- >>> escape(None)
- Markup('None')
- >>> escape_silent(None)
- Markup('')
- """
- if s is None:
- return Markup()
-
- return escape(s)
-
-
-def soft_str(s: t.Any) -> str:
- """Convert an object to a string if it isn't already. This preserves
- a :class:`Markup` string rather than converting it back to a basic
- string, so it will still be marked as safe and won't be escaped
- again.
-
- >>> value = escape("<User 1>")
- >>> value
- Markup('&lt;User 1&gt;')
- >>> escape(str(value))
- Markup('&amp;lt;User 1&amp;gt;')
- >>> escape(soft_str(value))
- Markup('&lt;User 1&gt;')
- """
- if not isinstance(s, str):
- return str(s)
-
- return s
diff --git a/contrib/python/MarkupSafe/py3/markupsafe/_speedups.c b/contrib/python/MarkupSafe/py3/markupsafe/_speedups.c
index 3c463fb82d5..50f1ac1dd62 100644
--- a/contrib/python/MarkupSafe/py3/markupsafe/_speedups.c
+++ b/contrib/python/MarkupSafe/py3/markupsafe/_speedups.c
@@ -1,22 +1,5 @@
#include <Python.h>
-static PyObject* markup;
-
-static int
-init_constants(void)
-{
- PyObject *module;
-
- /* import markup type so that we can mark the return value */
- module = PyImport_ImportModule("markupsafe");
- if (!module)
- return 0;
- markup = PyObject_GetAttrString(module, "Markup");
- Py_DECREF(module);
-
- return 1;
-}
-
#define GET_DELTA(inp, inp_end, delta) \
while (inp < inp_end) { \
switch (*inp++) { \
@@ -166,135 +149,29 @@ escape_unicode_kind4(PyUnicodeObject *in)
}
static PyObject*
-escape_unicode(PyUnicodeObject *in)
+escape_unicode(PyObject *self, PyObject *s)
{
- if (PyUnicode_READY(in))
+ if (!PyUnicode_CheckExact(s))
+ return NULL;
+
+ // This check is no longer needed in Python 3.12.
+ if (PyUnicode_READY(s))
return NULL;
- switch (PyUnicode_KIND(in)) {
+ switch (PyUnicode_KIND(s)) {
case PyUnicode_1BYTE_KIND:
- return escape_unicode_kind1(in);
+ return escape_unicode_kind1(s);
case PyUnicode_2BYTE_KIND:
- return escape_unicode_kind2(in);
+ return escape_unicode_kind2(s);
case PyUnicode_4BYTE_KIND:
- return escape_unicode_kind4(in);
+ return escape_unicode_kind4(s);
}
assert(0); /* shouldn't happen */
return NULL;
}
-static PyObject*
-escape(PyObject *self, PyObject *text)
-{
- static PyObject *id_html;
- PyObject *s = NULL, *rv = NULL, *html;
-
- if (id_html == NULL) {
- id_html = PyUnicode_InternFromString("__html__");
- if (id_html == NULL) {
- return NULL;
- }
- }
-
- /* we don't have to escape integers, bools or floats */
- if (PyLong_CheckExact(text) ||
- PyFloat_CheckExact(text) || PyBool_Check(text) ||
- text == Py_None)
- return PyObject_CallFunctionObjArgs(markup, text, NULL);
-
- /* if the object has an __html__ method that performs the escaping */
- html = PyObject_GetAttr(text ,id_html);
- if (html) {
- s = PyObject_CallObject(html, NULL);
- Py_DECREF(html);
- if (s == NULL) {
- return NULL;
- }
- /* Convert to Markup object */
- rv = PyObject_CallFunctionObjArgs(markup, (PyObject*)s, NULL);
- Py_DECREF(s);
- return rv;
- }
-
- /* otherwise make the object unicode if it isn't, then escape */
- PyErr_Clear();
- if (!PyUnicode_Check(text)) {
- PyObject *unicode = PyObject_Str(text);
- if (!unicode)
- return NULL;
- s = escape_unicode((PyUnicodeObject*)unicode);
- Py_DECREF(unicode);
- }
- else
- s = escape_unicode((PyUnicodeObject*)text);
-
- /* convert the unicode string into a markup object. */
- rv = PyObject_CallFunctionObjArgs(markup, (PyObject*)s, NULL);
- Py_DECREF(s);
- return rv;
-}
-
-
-static PyObject*
-escape_silent(PyObject *self, PyObject *text)
-{
- if (text != Py_None)
- return escape(self, text);
- return PyObject_CallFunctionObjArgs(markup, NULL);
-}
-
-
-static PyObject*
-soft_str(PyObject *self, PyObject *s)
-{
- if (!PyUnicode_Check(s))
- return PyObject_Str(s);
- Py_INCREF(s);
- return s;
-}
-
-
static PyMethodDef module_methods[] = {
- {
- "escape",
- (PyCFunction)escape,
- METH_O,
- "Replace the characters ``&``, ``<``, ``>``, ``'``, and ``\"`` in"
- " the string with HTML-safe sequences. Use this if you need to display"
- " text that might contain such characters in HTML.\n\n"
- "If the object has an ``__html__`` method, it is called and the"
- " return value is assumed to already be safe for HTML.\n\n"
- ":param s: An object to be converted to a string and escaped.\n"
- ":return: A :class:`Markup` string with the escaped text.\n"
- },
- {
- "escape_silent",
- (PyCFunction)escape_silent,
- METH_O,
- "Like :func:`escape` but treats ``None`` as the empty string."
- " Useful with optional values, as otherwise you get the string"
- " ``'None'`` when the value is ``None``.\n\n"
- ">>> escape(None)\n"
- "Markup('None')\n"
- ">>> escape_silent(None)\n"
- "Markup('')\n"
- },
- {
- "soft_str",
- (PyCFunction)soft_str,
- METH_O,
- "Convert an object to a string if it isn't already. This preserves"
- " a :class:`Markup` string rather than converting it back to a basic"
- " string, so it will still be marked as safe and won't be escaped"
- " again.\n\n"
- ">>> value = escape(\"<User 1>\")\n"
- ">>> value\n"
- "Markup('&lt;User 1&gt;')\n"
- ">>> escape(str(value))\n"
- "Markup('&amp;lt;User 1&amp;gt;')\n"
- ">>> escape(soft_str(value))\n"
- "Markup('&lt;User 1&gt;')\n"
- },
+ {"_escape_inner", (PyCFunction)escape_unicode, METH_O, NULL},
{NULL, NULL, 0, NULL} /* Sentinel */
};
@@ -313,8 +190,15 @@ static struct PyModuleDef module_definition = {
PyMODINIT_FUNC
PyInit__speedups(void)
{
- if (!init_constants())
+ PyObject *m = PyModule_Create(&module_definition);
+
+ if (m == NULL) {
return NULL;
+ }
+
+ #ifdef Py_GIL_DISABLED
+ PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
+ #endif
- return PyModule_Create(&module_definition);
+ return m;
}
diff --git a/contrib/python/MarkupSafe/py3/markupsafe/_speedups.pyi b/contrib/python/MarkupSafe/py3/markupsafe/_speedups.pyi
index f673240f6d2..8c8885852a2 100644
--- a/contrib/python/MarkupSafe/py3/markupsafe/_speedups.pyi
+++ b/contrib/python/MarkupSafe/py3/markupsafe/_speedups.pyi
@@ -1,9 +1 @@
-from typing import Any
-from typing import Optional
-
-from . import Markup
-
-def escape(s: Any) -> Markup: ...
-def escape_silent(s: Optional[Any]) -> Markup: ...
-def soft_str(s: Any) -> str: ...
-def soft_unicode(s: Any) -> str: ...
+def _escape_inner(s: str, /) -> str: ...
diff --git a/contrib/python/MarkupSafe/py3/tests/__init__.py b/contrib/python/MarkupSafe/py3/tests/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/contrib/python/MarkupSafe/py3/tests/__init__.py
diff --git a/contrib/python/MarkupSafe/py3/tests/conftest.py b/contrib/python/MarkupSafe/py3/tests/conftest.py
index d040ea8b1c6..d941970b8e9 100644
--- a/contrib/python/MarkupSafe/py3/tests/conftest.py
+++ b/contrib/python/MarkupSafe/py3/tests/conftest.py
@@ -1,5 +1,12 @@
+from __future__ import annotations
+
+import sys
+import typing as t
+from types import ModuleType
+
import pytest
+import markupsafe
from markupsafe import _native
try:
@@ -8,8 +15,17 @@ except ImportError:
_speedups = None # type: ignore
+def pytest_report_header() -> list[str]:
+ """Return a list of strings to be displayed in the header of the report."""
+ if sys.version_info >= (3, 13):
+ return [f"Free-threaded: {not sys._is_gil_enabled()}"]
+
+ return []
+
+
@pytest.fixture(
scope="session",
+ autouse=True,
params=(
_native,
pytest.param(
@@ -18,20 +34,6 @@ except ImportError:
),
),
)
-def _mod(request):
- return request.param
-
-
[email protected](scope="session")
-def escape(_mod):
- return _mod.escape
-
-
[email protected](scope="session")
-def escape_silent(_mod):
- return _mod.escape_silent
-
-
[email protected](scope="session")
-def soft_str(_mod):
- return _mod.soft_str
+def _mod(request: pytest.FixtureRequest) -> None:
+ mod = t.cast(ModuleType, request.param)
+ markupsafe._escape_inner = mod._escape_inner # type: ignore[attr-defined]
diff --git a/contrib/python/MarkupSafe/py3/tests/test_escape.py b/contrib/python/MarkupSafe/py3/tests/test_escape.py
index bf53facedda..03e0b64892f 100644
--- a/contrib/python/MarkupSafe/py3/tests/test_escape.py
+++ b/contrib/python/MarkupSafe/py3/tests/test_escape.py
@@ -1,5 +1,8 @@
+from __future__ import annotations
+
import pytest
+from markupsafe import escape
from markupsafe import Markup
@@ -18,12 +21,12 @@ from markupsafe import Markup
("こんにちは&><'\"", "こんにちは&amp;&gt;&lt;&#39;&#34;"),
# 4 byte
(
- "\U0001F363\U0001F362&><'\"\U0001F37A xyz",
- "\U0001F363\U0001F362&amp;&gt;&lt;&#39;&#34;\U0001F37A xyz",
+ "\U0001f363\U0001f362&><'\"\U0001f37a xyz",
+ "\U0001f363\U0001f362&amp;&gt;&lt;&#39;&#34;\U0001f37a xyz",
),
- ("&><'\"\U0001F37A xyz", "&amp;&gt;&lt;&#39;&#34;\U0001F37A xyz"),
- ("\U0001F363\U0001F362&><'\"", "\U0001F363\U0001F362&amp;&gt;&lt;&#39;&#34;"),
+ ("&><'\"\U0001f37a xyz", "&amp;&gt;&lt;&#39;&#34;\U0001f37a xyz"),
+ ("\U0001f363\U0001f362&><'\"", "\U0001f363\U0001f362&amp;&gt;&lt;&#39;&#34;"),
),
)
-def test_escape(escape, value, expect):
+def test_escape(value: str, expect: str) -> None:
assert escape(value) == Markup(expect)
diff --git a/contrib/python/MarkupSafe/py3/tests/test_exception_custom_html.py b/contrib/python/MarkupSafe/py3/tests/test_exception_custom_html.py
index ec2f10b1d19..582c7491367 100644
--- a/contrib/python/MarkupSafe/py3/tests/test_exception_custom_html.py
+++ b/contrib/python/MarkupSafe/py3/tests/test_exception_custom_html.py
@@ -1,12 +1,16 @@
+from __future__ import annotations
+
import pytest
+from markupsafe import escape
+
class CustomHtmlThatRaises:
- def __html__(self):
+ def __html__(self) -> str:
raise ValueError(123)
-def test_exception_custom_html(escape):
+def test_exception_custom_html() -> None:
"""Checks whether exceptions in custom __html__ implementations are
propagated correctly.
@@ -14,5 +18,6 @@ def test_exception_custom_html(escape):
https://github.com/pallets/markupsafe/issues/108
"""
obj = CustomHtmlThatRaises()
+
with pytest.raises(ValueError):
escape(obj)
diff --git a/contrib/python/MarkupSafe/py3/tests/test_leak.py b/contrib/python/MarkupSafe/py3/tests/test_leak.py
index 55b10b98a42..b786e0725de 100644
--- a/contrib/python/MarkupSafe/py3/tests/test_leak.py
+++ b/contrib/python/MarkupSafe/py3/tests/test_leak.py
@@ -1,28 +1,25 @@
-import gc
-import platform
+from __future__ import annotations
-import pytest
+import gc
from markupsafe import escape
- escape.__module__ == "markupsafe._native",
- reason="only test memory leak with speedups",
-)
-def test_markup_leaks():
+def test_markup_leaks() -> None:
counts = set()
+ # Try to start with a "clean" count. Works for PyPy but not 3.13 JIT.
+ gc.collect()
- for _i in range(20):
- for _j in range(1000):
+ for _ in range(20):
+ for _ in range(1000):
escape("foo")
escape("<foo>")
escape("foo")
escape("<foo>")
- if platform.python_implementation() == "PyPy":
- gc.collect()
-
counts.add(len(gc.get_objects()))
- assert len(counts) == 1
+ # Some implementations, such as PyPy and Python 3.13 JIT, end up with 2
+ # counts rather than one. Presumably this is internals stabilizing. A leak
+ # would presumably have a different count every loop.
+ assert len(counts) < 3
diff --git a/contrib/python/MarkupSafe/py3/tests/test_markupsafe.py b/contrib/python/MarkupSafe/py3/tests/test_markupsafe.py
index 94bea387954..85d9ea16be7 100644
--- a/contrib/python/MarkupSafe/py3/tests/test_markupsafe.py
+++ b/contrib/python/MarkupSafe/py3/tests/test_markupsafe.py
@@ -1,9 +1,16 @@
+from __future__ import annotations
+
+import typing as t
+
import pytest
+from markupsafe import escape
+from markupsafe import escape_silent
from markupsafe import Markup
+from markupsafe import soft_str
-def test_adding(escape):
+def test_adding() -> None:
unsafe = '<script type="application/x-some-script">alert("foo");</script>'
safe = Markup("<em>username</em>")
assert unsafe + safe == str(escape(unsafe)) + str(safe)
@@ -22,22 +29,22 @@ def test_adding(escape):
("%.2f", 3.14, "3.14"),
),
)
-def test_string_interpolation(template, data, expect):
+def test_string_interpolation(template: str, data: t.Any, expect: str) -> None:
assert Markup(template) % data == expect
-def test_type_behavior():
+def test_type_behavior() -> None:
assert type(Markup("foo") + "bar") is Markup
x = Markup("foo")
assert x.__html__() is x
-def test_html_interop():
+def test_html_interop() -> None:
class Foo:
- def __html__(self):
+ def __html__(self) -> str:
return "<em>awesome</em>"
- def __str__(self):
+ def __str__(self) -> str:
return "awesome"
assert Markup(Foo()) == "<em>awesome</em>"
@@ -46,18 +53,18 @@ def test_html_interop():
@pytest.mark.parametrize("args", ["foo", 42, ("foo", 42)])
-def test_missing_interpol(args):
+def test_missing_interpol(args: t.Any) -> None:
with pytest.raises(TypeError):
- Markup("<em></em>") % args
+ assert Markup("<em></em>") % args
-def test_tuple_interpol():
+def test_tuple_interpol() -> None:
result = Markup("<em>%s:%s</em>") % ("<foo>", "<bar>")
expect = Markup("<em>&lt;foo&gt;:&lt;bar&gt;</em>")
assert result == expect
-def test_dict_interpol():
+def test_dict_interpol() -> None:
result = Markup("<em>%(foo)s</em>") % {"foo": "<foo>"}
expect = Markup("<em>&lt;foo&gt;</em>")
assert result == expect
@@ -67,7 +74,7 @@ def test_dict_interpol():
assert result == expect
-def test_escaping(escape):
+def test_escaping() -> None:
assert escape("\"<>&'") == "&#34;&lt;&gt;&amp;&#39;"
assert (
Markup(
@@ -82,7 +89,7 @@ def test_escaping(escape):
)
-def test_unescape():
+def test_unescape() -> None:
assert Markup("&lt;test&gt;").unescape() == "<test>"
result = Markup("jack & tavi are cooler than mike &amp; russ").unescape()
@@ -97,7 +104,7 @@ def test_unescape():
assert twice == expect
-def test_format():
+def test_format() -> None:
result = Markup("<em>{awesome}</em>").format(awesome="<awesome>")
assert result == "<em>&lt;awesome&gt;</em>"
@@ -108,39 +115,39 @@ def test_format():
assert result == "<bar/>"
-def test_format_map():
+def test_format_map() -> None:
result = Markup("<em>{value}</em>").format_map({"value": "<value>"})
assert result == "<em>&lt;value&gt;</em>"
-def test_formatting_empty():
+def test_formatting_empty() -> None:
formatted = Markup("{}").format(0)
assert formatted == Markup("0")
-def test_custom_formatting():
+def test_custom_formatting() -> None:
class HasHTMLOnly:
- def __html__(self):
+ def __html__(self) -> Markup:
return Markup("<foo>")
class HasHTMLAndFormat:
- def __html__(self):
+ def __html__(self) -> Markup:
return Markup("<foo>")
- def __html_format__(self, spec):
+ def __html_format__(self, spec: str) -> Markup:
return Markup("<FORMAT>")
assert Markup("{0}").format(HasHTMLOnly()) == Markup("<foo>")
assert Markup("{0}").format(HasHTMLAndFormat()) == Markup("<FORMAT>")
-def test_complex_custom_formatting():
+def test_complex_custom_formatting() -> None:
class User:
- def __init__(self, id, username):
+ def __init__(self, id: int, username: str) -> None:
self.id = id
self.username = username
- def __html_format__(self, format_spec):
+ def __html_format__(self, format_spec: str) -> Markup:
if format_spec == "link":
return Markup('<a href="/user/{0}">{1}</a>').format(
self.id, self.__html__()
@@ -150,7 +157,7 @@ def test_complex_custom_formatting():
return self.__html__()
- def __html__(self):
+ def __html__(self) -> Markup:
return Markup("<span class=user>{0}</span>").format(self.username)
user = User(1, "foo")
@@ -159,43 +166,43 @@ def test_complex_custom_formatting():
assert result == expect
-def test_formatting_with_objects():
+def test_formatting_with_objects() -> None:
class Stringable:
- def __str__(self):
+ def __str__(self) -> str:
return "строка"
assert Markup("{s}").format(s=Stringable()) == Markup("строка")
-def test_escape_silent(escape, escape_silent):
+def test_escape_silent() -> None:
assert escape_silent(None) == Markup()
assert escape(None) == Markup(None)
assert escape_silent("<foo>") == Markup("&lt;foo&gt;")
-def test_splitting():
+def test_splitting() -> None:
expect = [Markup("a"), Markup("b")]
assert Markup("a b").split() == expect
assert Markup("a b").rsplit() == expect
assert Markup("a\nb").splitlines() == expect
-def test_mul():
+def test_mul() -> None:
assert Markup("a") * 3 == Markup("aaa")
-def test_escape_return_type(escape):
+def test_escape_return_type() -> None:
assert isinstance(escape("a"), Markup)
assert isinstance(escape(Markup("a")), Markup)
class Foo:
- def __html__(self):
+ def __html__(self) -> str:
return "<strong>Foo</strong>"
assert isinstance(escape(Foo()), Markup)
-def test_soft_str(soft_str):
- assert type(soft_str("")) is str
- assert type(soft_str(Markup())) is Markup
- assert type(soft_str(15)) is str
+def test_soft_str() -> None:
+ assert type(soft_str("")) is str # noqa: E721
+ assert type(soft_str(Markup())) is Markup # noqa: E721
+ assert type(soft_str(15)) is str # noqa: E721
diff --git a/contrib/python/MarkupSafe/py3/ya.make b/contrib/python/MarkupSafe/py3/ya.make
index f1664c93e8f..e8dcc16c61d 100644
--- a/contrib/python/MarkupSafe/py3/ya.make
+++ b/contrib/python/MarkupSafe/py3/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(2.1.5)
+VERSION(3.0.0)
LICENSE(BSD-3-Clause)