summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Lord <davidism@gmail.com>2020-01-30 08:22:06 -0800
committerGitHub <noreply@github.com>2020-01-30 08:22:06 -0800
commit32b2a46f04e7752b5aedb05f0e1b3864e16fcee3 (patch)
treeca951757a32e038c5904a770215a4385a9b438de
parentf43bf5f567cafbafb379382439e4be14ebf2506e (diff)
parentc3d647d951981135e1e4a501a40fb6481b4b534a (diff)
downloadmarkupsafe-32b2a46f04e7752b5aedb05f0e1b3864e16fcee3.tar.gz
Merge pull request #115 from pallets/drop-python2
Drop Python 2
-rw-r--r--.azure-pipelines.yml16
-rw-r--r--.editorconfig3
-rw-r--r--.pre-commit-config.yaml5
-rw-r--r--CHANGES.rst8
-rw-r--r--README.rst10
-rw-r--r--bench/runbench.py11
-rw-r--r--docs/conf.py4
-rw-r--r--docs/escaping.rst2
-rw-r--r--docs/formatting.rst14
-rw-r--r--docs/html.rst10
-rw-r--r--docs/index.rst10
-rw-r--r--setup.cfg9
-rw-r--r--setup.py23
-rw-r--r--src/markupsafe/__init__.py171
-rw-r--r--src/markupsafe/_compat.py33
-rw-r--r--src/markupsafe/_constants.py9
-rw-r--r--src/markupsafe/_native.py32
-rw-r--r--src/markupsafe/_speedups.c312
-rw-r--r--tests/conftest.py5
-rw-r--r--tests/test_escape.py23
-rw-r--r--tests/test_exception_custom_html.py7
-rw-r--r--tests/test_leak.py9
-rw-r--r--tests/test_markupsafe.py62
-rw-r--r--tox.ini2
24 files changed, 307 insertions, 483 deletions
diff --git a/.azure-pipelines.yml b/.azure-pipelines.yml
index 1f258e1..bb5e977 100644
--- a/.azure-pipelines.yml
+++ b/.azure-pipelines.yml
@@ -9,6 +9,7 @@ trigger:
jobs:
- job: Test
+
variables:
vmImage: 'ubuntu-latest'
python.version: '3.8'
@@ -28,10 +29,6 @@ jobs:
python.version: '3.7'
Python 3.6 Linux:
python.version: '3.6'
- Python 3.5 Linux:
- python.version: '3.5'
- Python 2.7 Linux:
- python.version: '2.7'
Docs:
TOXENV: 'docs'
Style:
@@ -56,16 +53,15 @@ jobs:
dependsOn: Test
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/')
+ variables:
+ CIBW_SKIP: 'cp27-* cp35-*'
+
strategy:
matrix:
Linux:
vmImage: 'ubuntu-latest'
Windows:
vmImage: 'windows-latest'
- CIBW_SKIP: 'cp35-*'
- Windows Python 3.5:
- vmImage: 'vs2017-win2016'
- CIBW_BUILD: 'cp35-*'
Mac:
vmImage: 'macos-latest'
@@ -76,10 +72,6 @@ jobs:
- task: UsePythonVersion@0
displayName: Use Python
- - script: choco install vcpython27 -f -y
- displayName: Install Visual C++ for Python 2.7
- condition: eq(variables['vmImage'], 'windows-latest')
-
- script: pip install cibuildwheel
displayName: Install cibuildwheel
diff --git a/.editorconfig b/.editorconfig
index e32c802..220a591 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -11,3 +11,6 @@ max_line_length = 88
[*.{yml,yaml,json,js,css,html}]
indent_size = 2
+
+[*.c]
+indent_style = tab
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index cdbcfed..7f8d643 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,4 +1,9 @@
repos:
+ - repo: https://github.com/asottile/pyupgrade
+ rev: v1.26.2
+ hooks:
+ - id: pyupgrade
+ args: ["--py36-plus"]
- repo: https://github.com/asottile/reorder_python_imports
rev: v1.9.0
hooks:
diff --git a/CHANGES.rst b/CHANGES.rst
index 63ecd67..a846912 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,3 +1,11 @@
+Version 2.0.0
+-------------
+
+Unreleased
+
+- Drop Python 2.7, 3.4, and 3.5 support.
+
+
Version 1.1.1
-------------
diff --git a/README.rst b/README.rst
index 1a58058..63ac13c 100644
--- a/README.rst
+++ b/README.rst
@@ -27,17 +27,17 @@ Examples
>>> from markupsafe import Markup, escape
>>> # escape replaces special characters and wraps in Markup
- >>> escape('<script>alert(document.cookie);</script>')
+ >>> escape("<script>alert(document.cookie);</script>")
Markup(u'&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>")
Markup('<strong>hello</strong>')
- >>> escape(Markup('<strong>Hello</strong>'))
+ >>> escape(Markup("<strong>Hello</strong>"))
Markup('<strong>hello</strong>')
- >>> # Markup is a text subclass (str on Python 3, unicode on Python 2)
+ >>> # Markup is a str subclass
>>> # methods and operators escape their arguments
>>> template = Markup("Hello <em>%s</em>")
- >>> template % '"World"'
+ >>> template % ('"World"',)
Markup('Hello <em>&#34;World&#34;</em>')
diff --git a/bench/runbench.py b/bench/runbench.py
index 38cf128..f20cd49 100644
--- a/bench/runbench.py
+++ b/bench/runbench.py
@@ -1,9 +1,3 @@
-#!/usr/bin/env python
-"""
- Runs the benchmarks
-"""
-from __future__ import print_function
-
import os
import re
import sys
@@ -24,10 +18,9 @@ def list_benchmarks():
def run_bench(name):
- sys.stdout.write("%-32s" % name)
- sys.stdout.flush()
+ print(name)
Popen(
- [sys.executable, "-mtimeit", "-s", "from bench_%s import run" % name, "run()"]
+ [sys.executable, "-m", "timeit", "-s", f"from bench_{name} import run", "run()"]
).wait()
diff --git a/docs/conf.py b/docs/conf.py
index f349847..46123a3 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -32,11 +32,11 @@ html_sidebars = {
"**": ["localtoc.html", "relations.html", "searchbox.html"],
}
singlehtml_sidebars = {"index": ["project.html", "localtoc.html"]}
-html_title = "MarkupSafe Documentation ({})".format(version)
+html_title = f"MarkupSafe Documentation ({version})"
html_show_sourcelink = False
# LaTeX ----------------------------------------------------------------
latex_documents = [
- (master_doc, "MarkupSafe-{}.tex".format(version), html_title, author, "manual")
+ (master_doc, f"MarkupSafe-{version}.tex", html_title, author, "manual")
]
diff --git a/docs/escaping.rst b/docs/escaping.rst
index d99674d..9e7000a 100644
--- a/docs/escaping.rst
+++ b/docs/escaping.rst
@@ -18,4 +18,4 @@ Optional Values
Convert an Object to a String
-----------------------------
-.. autofunction:: soft_unicode
+.. autofunction:: soft_str
diff --git a/docs/formatting.rst b/docs/formatting.rst
index c425134..c14f917 100644
--- a/docs/formatting.rst
+++ b/docs/formatting.rst
@@ -26,7 +26,7 @@ to use an ``__html_format__`` method.
is escaped.
For example, to implement a ``User`` that wraps its ``name`` in a
-``span`` tag, and adds a link when using the ``'link'`` format
+``span`` tag, and adds a link when using the ``"link"`` format
specifier:
.. code-block:: python
@@ -37,12 +37,12 @@ specifier:
self.name = name
def __html_format__(self, format_spec):
- if format_spec == 'link':
+ if format_spec == "link":
return Markup(
'<a href="/user/{}">{}</a>'
).format(self.id, self.__html__())
elif format_spec:
- raise ValueError('Invalid format spec')
+ raise ValueError("Invalid format spec")
return self.__html__()
def __html__(self):
@@ -53,10 +53,10 @@ specifier:
.. code-block:: pycon
- >>> user = User(3, '<script>')
+ >>> user = User(3, "<script>")
>>> escape(user)
Markup('<span class="user">&lt;script&gt;</span>')
- >>> Markup('<p>User: {user:link}').format(user=user)
+ >>> Markup("<p>User: {user:link}").format(user=user)
Markup('<p>User: <a href="/user/3"><span class="user">&lt;script&gt;</span></a>
See Python's docs on :ref:`format string syntax <python:formatstrings>`.
@@ -70,8 +70,8 @@ formatting.
.. code-block:: pycon
- >>> user = User(3, '<script>')
- >>> Markup('<a href="/user/%d">"%s</a>') % (user.id, user.name)
+ >>> user = User(3, "<script>")
+ >>> Markup('<a href="/user/%d">%s</a>') % (user.id, user.name)
Markup('<a href="/user/3">&lt;script&gt;</a>')
See Python's docs on :ref:`printf-style formatting <python:old-string-formatting>`.
diff --git a/docs/html.rst b/docs/html.rst
index 3a0c11b..dec87af 100644
--- a/docs/html.rst
+++ b/docs/html.rst
@@ -20,11 +20,11 @@ For example, an ``Image`` class might automatically generate an
self.url = url
def __html__(self):
- return '<img src="%s">' % self.url
+ return f'<img src="{self.url}">'
.. code-block:: pycon
- >>> img = Image('/static/logo.png')
+ >>> img = Image("/static/logo.png")
>>> Markup(img)
Markup('<img src="/static/logo.png">')
@@ -40,12 +40,10 @@ should still be escaped:
self.name = name
def __html__(self):
- return '<a href="/user/{}">{}</a>'.format(
- self.id, escape(self.name)
- )
+ return f'<a href="/user/{self.id}">{escape(self.name)}</a>'
.. code-block:: pycon
- >>> user = User(3, '<script>')
+ >>> user = User(3, "<script>")
>>> escape(user)
Markup('<a href="/users/3">&lt;script&gt;</a>')
diff --git a/docs/index.rst b/docs/index.rst
index 3db77ef..5c45e64 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -13,20 +13,14 @@ object. The object won't be escaped anymore, but any text that is used
with it will be, ensuring that the result remains safe to use in HTML.
>>> from markupsafe import escape
->>> hello = escape('<em>Hello</em>')
+>>> hello = escape("<em>Hello</em>")
>>> hello
Markup('&lt;em&gt;Hello&lt;/em&gt;')
>>> escape(hello)
Markup('&lt;em&gt;Hello&lt;/em&gt;')
->>> hello + ' <strong>World</strong>'
+>>> hello + " <strong>World</strong>"
Markup('&lt;em&gt;Hello&lt;/em&gt; &lt;strong&gt;World&lt;/strong&gt;')
-.. note::
-
- The docs assume you're using Python 3. The terms "text" and "string"
- refer to the :class:`str` class. In Python 2, this would be the
- ``unicode`` class instead.
-
Installing
----------
diff --git a/setup.cfg b/setup.cfg
index f5cafd8..44ee656 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,6 @@
[metadata]
license_file = LICENSE.rst
+long_description = file:README.rst
long_description_content_type = text/x-rst
[tool:pytest]
@@ -16,9 +17,8 @@ source =
[coverage:paths]
source =
- src/markupsafe
- .tox/*/lib/python*/site-packages/markupsafe
- .tox/*/site-packages/markupsafe
+ src
+ */site-packages
[flake8]
# B = bugbear
@@ -38,6 +38,3 @@ ignore =
W503
# up to 88 allowed by bugbear B950
max-line-length = 80
-# _compat names and imports will always look bad, ignore warnings
-exclude =
- src/markupsafe/_compat.py
diff --git a/setup.py b/setup.py
index 8f9476a..02e0c3f 100644
--- a/setup.py
+++ b/setup.py
@@ -1,6 +1,4 @@
-from __future__ import print_function
-
-import io
+import platform
import re
import sys
from distutils.errors import CCompilerError
@@ -12,15 +10,9 @@ from setuptools import find_packages
from setuptools import setup
from setuptools.command.build_ext import build_ext
-with io.open("README.rst", "rt", encoding="utf8") as f:
- readme = f.read()
-
-with io.open("src/markupsafe/__init__.py", "rt", encoding="utf8") as f:
+with open("src/markupsafe/__init__.py", encoding="utf8") as f:
version = re.search(r'__version__ = "(.*?)"', f.read()).group(1)
-is_jython = "java" in sys.platform
-is_pypy = hasattr(sys, "pypy_version_info")
-
ext_modules = [Extension("markupsafe._speedups", ["src/markupsafe/_speedups.c"])]
@@ -60,12 +52,9 @@ def run_setup(with_binary):
"Issue tracker": "https://github.com/pallets/markupsafe/issues",
},
license="BSD-3-Clause",
- author="Armin Ronacher",
- author_email="armin.ronacher@active-4.com",
- maintainer="The Pallets Team",
+ maintainer="Pallets",
maintainer_email="contact@palletsprojects.com",
description="Safely add untrusted strings to HTML/XML markup.",
- long_description=readme,
classifiers=[
"Development Status :: 5 - Production/Stable",
"Environment :: Web Environment",
@@ -73,8 +62,6 @@ def run_setup(with_binary):
"License :: OSI Approved :: BSD License",
"Operating System :: OS Independent",
"Programming Language :: Python",
- "Programming Language :: Python :: 2",
- "Programming Language :: Python :: 3",
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Text Processing :: Markup :: HTML",
@@ -82,7 +69,7 @@ def run_setup(with_binary):
packages=find_packages("src"),
package_dir={"": "src"},
include_package_data=True,
- python_requires=">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*",
+ python_requires=">=3.6",
cmdclass={"build_ext": ve_build_ext},
ext_modules=ext_modules if with_binary else [],
)
@@ -95,7 +82,7 @@ def show_message(*lines):
print("=" * 74)
-if not (is_pypy or is_jython):
+if platform.python_implementation() not in {"PyPy", "Jython"}:
try:
run_setup(True)
except BuildFailed:
diff --git a/src/markupsafe/__init__.py b/src/markupsafe/__init__.py
index da05ed3..39f468b 100644
--- a/src/markupsafe/__init__.py
+++ b/src/markupsafe/__init__.py
@@ -1,34 +1,20 @@
-# -*- coding: utf-8 -*-
"""
-markupsafe
-~~~~~~~~~~
-
Implements an escape function and a Markup string to replace HTML
special characters with safe representations.
-
-:copyright: 2010 Pallets
-:license: BSD-3-Clause
"""
import re
import string
+from collections import abc
-from ._compat import int_types
-from ._compat import iteritems
-from ._compat import Mapping
-from ._compat import PY2
-from ._compat import string_types
-from ._compat import text_type
-from ._compat import unichr
+__version__ = "2.0.0a1"
-__version__ = "1.1.1"
-
-__all__ = ["Markup", "soft_unicode", "escape", "escape_silent"]
+__all__ = ["Markup", "escape", "escape_silent", "soft_str", "soft_unicode"]
_striptags_re = re.compile(r"(<!--.*?-->|<[^>]*>)")
_entity_re = re.compile(r"&([^& ;]+);")
-class Markup(text_type):
+class Markup(str):
"""A string that is ready to be safely inserted into an HTML or XML
document, either because it was escaped or because it was marked
safe.
@@ -37,11 +23,11 @@ class Markup(text_type):
it to mark it safe without escaping. To escape the text, use the
:meth:`escape` class method instead.
- >>> Markup('Hello, <em>World</em>!')
+ >>> Markup("Hello, <em>World</em>!")
Markup('Hello, <em>World</em>!')
>>> Markup(42)
Markup('42')
- >>> Markup.escape('Hello, <em>World</em>!')
+ >>> Markup.escape("Hello, <em>World</em>!")
Markup('Hello &lt;em&gt;World&lt;/em&gt;!')
This implements the ``__html__()`` interface that some frameworks
@@ -55,41 +41,40 @@ class Markup(text_type):
>>> Markup(Foo())
Markup('<a href="/foo">foo</a>')
- This is a subclass of the text type (``str`` in Python 3,
- ``unicode`` in Python 2). It has the same methods as that type, but
- all methods escape their arguments and return a ``Markup`` instance.
+ This is a subclass of :class:`str`. It has the same methods, but
+ escapes their arguments and returns a ``Markup`` instance.
- >>> Markup('<em>%s</em>') % 'foo & bar'
+ >>> Markup("<em>%s</em>") % ("foo & bar",)
Markup('<em>foo &amp; bar</em>')
- >>> Markup('<em>Hello</em> ') + '<foo>'
+ >>> Markup("<em>Hello</em> ") + "<foo>"
Markup('<em>Hello</em> &lt;foo&gt;')
"""
__slots__ = ()
- def __new__(cls, base=u"", encoding=None, errors="strict"):
+ def __new__(cls, base="", encoding=None, errors="strict"):
if hasattr(base, "__html__"):
base = base.__html__()
if encoding is None:
- return text_type.__new__(cls, base)
- return text_type.__new__(cls, base, encoding, errors)
+ return super().__new__(cls, base)
+ return super().__new__(cls, base, encoding, errors)
def __html__(self):
return self
def __add__(self, other):
- if isinstance(other, string_types) or hasattr(other, "__html__"):
- return self.__class__(super(Markup, self).__add__(self.escape(other)))
+ if isinstance(other, str) or hasattr(other, "__html__"):
+ return self.__class__(super().__add__(self.escape(other)))
return NotImplemented
def __radd__(self, other):
- if hasattr(other, "__html__") or isinstance(other, string_types):
+ if isinstance(other, str) or hasattr(other, "__html__"):
return self.escape(other).__add__(self)
return NotImplemented
def __mul__(self, num):
- if isinstance(num, int_types):
- return self.__class__(text_type.__mul__(self, num))
+ if isinstance(num, int):
+ return self.__class__(super().__mul__(num))
return NotImplemented
__rmul__ = __mul__
@@ -99,36 +84,36 @@ class Markup(text_type):
arg = tuple(_MarkupEscapeHelper(x, self.escape) for x in arg)
else:
arg = _MarkupEscapeHelper(arg, self.escape)
- return self.__class__(text_type.__mod__(self, arg))
+ return self.__class__(super().__mod__(arg))
def __repr__(self):
- return "%s(%s)" % (self.__class__.__name__, text_type.__repr__(self))
+ return f"{self.__class__.__name__}({super().__repr__()})"
def join(self, seq):
- return self.__class__(text_type.join(self, map(self.escape, seq)))
+ return self.__class__(super().join(map(self.escape, seq)))
- join.__doc__ = text_type.join.__doc__
+ join.__doc__ = str.join.__doc__
def split(self, *args, **kwargs):
- return list(map(self.__class__, text_type.split(self, *args, **kwargs)))
+ return list(map(self.__class__, super().split(*args, **kwargs)))
- split.__doc__ = text_type.split.__doc__
+ split.__doc__ = str.split.__doc__
def rsplit(self, *args, **kwargs):
- return list(map(self.__class__, text_type.rsplit(self, *args, **kwargs)))
+ return list(map(self.__class__, super().rsplit(*args, **kwargs)))
- rsplit.__doc__ = text_type.rsplit.__doc__
+ rsplit.__doc__ = str.rsplit.__doc__
def splitlines(self, *args, **kwargs):
- return list(map(self.__class__, text_type.splitlines(self, *args, **kwargs)))
+ return list(map(self.__class__, super().splitlines(*args, **kwargs)))
- splitlines.__doc__ = text_type.splitlines.__doc__
+ splitlines.__doc__ = str.splitlines.__doc__
def unescape(self):
"""Convert escaped markup back into a text string. This replaces
HTML entities with the characters they represent.
- >>> Markup('Main &raquo; <em>About</em>').unescape()
+ >>> Markup("Main &raquo; <em>About</em>").unescape()
'Main » <em>About</em>'
"""
from ._constants import HTML_ENTITIES
@@ -136,27 +121,27 @@ class Markup(text_type):
def handle_match(m):
name = m.group(1)
if name in HTML_ENTITIES:
- return unichr(HTML_ENTITIES[name])
+ return chr(HTML_ENTITIES[name])
try:
if name[:2] in ("#x", "#X"):
- return unichr(int(name[2:], 16))
+ return chr(int(name[2:], 16))
elif name.startswith("#"):
- return unichr(int(name[1:]))
+ return chr(int(name[1:]))
except ValueError:
pass
# Don't modify unexpected input.
return m.group()
- return _entity_re.sub(handle_match, text_type(self))
+ return _entity_re.sub(handle_match, str(self))
def striptags(self):
""":meth:`unescape` the markup, remove tags, and normalize
whitespace to single spaces.
- >>> Markup('Main &raquo;\t<em>About</em>').striptags()
+ >>> Markup("Main &raquo;\t<em>About</em>").striptags()
'Main » About'
"""
- stripped = u" ".join(_striptags_re.sub("", self).split())
+ stripped = " ".join(_striptags_re.sub("", self).split())
return Markup(stripped).unescape()
@classmethod
@@ -170,11 +155,11 @@ class Markup(text_type):
return rv
def make_simple_escaping_wrapper(name): # noqa: B902
- orig = getattr(text_type, name)
+ orig = getattr(str, name)
def func(self, *args, **kwargs):
args = _escape_argspec(list(args), enumerate(args), self.escape)
- _escape_argspec(kwargs, iteritems(kwargs), self.escape)
+ _escape_argspec(kwargs, kwargs.items(), self.escape)
return self.__class__(orig(self, *args, **kwargs))
func.__name__ = orig.__name__
@@ -201,11 +186,13 @@ class Markup(text_type):
):
locals()[method] = make_simple_escaping_wrapper(method)
+ del method, make_simple_escaping_wrapper
+
def partition(self, sep):
- return tuple(map(self.__class__, text_type.partition(self, self.escape(sep))))
+ return tuple(map(self.__class__, super().partition(self.escape(sep))))
def rpartition(self, sep):
- return tuple(map(self.__class__, text_type.rpartition(self, self.escape(sep))))
+ return tuple(map(self.__class__, super().rpartition(self.escape(sep))))
def format(self, *args, **kwargs):
formatter = EscapeFormatter(self.escape)
@@ -214,17 +201,11 @@ class Markup(text_type):
def __html_format__(self, format_spec):
if format_spec:
- raise ValueError("Unsupported format specification " "for Markup.")
+ raise ValueError("Unsupported format specification for Markup.")
return self
- # not in python 3
- if hasattr(text_type, "__getslice__"):
- __getslice__ = make_simple_escaping_wrapper("__getslice__")
-
- del method, make_simple_escaping_wrapper
-
-class _MagicFormatMapping(Mapping):
+class _MagicFormatMapping(abc.Mapping):
"""This class implements a dummy wrapper to fix a bug in the Python
standard library for string formatting.
@@ -255,43 +236,38 @@ class _MagicFormatMapping(Mapping):
return len(self._kwargs)
-if hasattr(text_type, "format"):
-
- class EscapeFormatter(string.Formatter):
- def __init__(self, escape):
- self.escape = escape
-
- def format_field(self, value, format_spec):
- if hasattr(value, "__html_format__"):
- rv = value.__html_format__(format_spec)
- elif hasattr(value, "__html__"):
- if format_spec:
- raise ValueError(
- "Format specifier {0} given, but {1} does not"
- " define __html_format__. A class that defines"
- " __html__ must define __html_format__ to work"
- " with format specifiers.".format(format_spec, type(value))
- )
- rv = value.__html__()
- else:
- # We need to make sure the format spec is unicode here as
- # otherwise the wrong callback methods are invoked. For
- # instance a byte string there would invoke __str__ and
- # not __unicode__.
- rv = string.Formatter.format_field(self, value, text_type(format_spec))
- return text_type(self.escape(rv))
+class EscapeFormatter(string.Formatter):
+ def __init__(self, escape):
+ self.escape = escape
+
+ def format_field(self, value, format_spec):
+ if hasattr(value, "__html_format__"):
+ rv = value.__html_format__(format_spec)
+ elif hasattr(value, "__html__"):
+ if format_spec:
+ raise ValueError(
+ f"Format specifier {format_spec} given, but {type(value)} does not"
+ " define __html_format__. A class that defines __html__ must define"
+ " __html_format__ to work with format specifiers."
+ )
+ rv = value.__html__()
+ 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))
+ return str(self.escape(rv))
def _escape_argspec(obj, iterable, escape):
"""Helper for various string-wrapped functions."""
for key, value in iterable:
- if hasattr(value, "__html__") or isinstance(value, string_types):
+ if isinstance(value, str) or hasattr(value, "__html__"):
obj[key] = escape(value)
return obj
-class _MarkupEscapeHelper(object):
- """Helper for Markup.__mod__"""
+class _MarkupEscapeHelper:
+ """Helper for :meth:`Markup.__mod__`."""
def __init__(self, obj, escape):
self.obj = obj
@@ -301,9 +277,7 @@ class _MarkupEscapeHelper(object):
return _MarkupEscapeHelper(self.obj[item], self.escape)
def __str__(self):
- return text_type(self.escape(self.obj))
-
- __unicode__ = __str__
+ return str(self.escape(self.obj))
def __repr__(self):
return str(self.escape(repr(self.obj)))
@@ -315,13 +289,8 @@ class _MarkupEscapeHelper(object):
return float(self.obj)
-# we have to import it down here as the speedups and native
-# modules imports the markup type which is define above.
+# circular import
try:
- from ._speedups import escape, escape_silent, soft_unicode
+ from ._speedups import escape, escape_silent, soft_str, soft_unicode
except ImportError:
- from ._native import escape, escape_silent, soft_unicode
-
-if not PY2:
- soft_str = soft_unicode
- __all__.append("soft_str")
+ from ._native import escape, escape_silent, soft_str, soft_unicode
diff --git a/src/markupsafe/_compat.py b/src/markupsafe/_compat.py
deleted file mode 100644
index bc05090..0000000
--- a/src/markupsafe/_compat.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-markupsafe._compat
-~~~~~~~~~~~~~~~~~~
-
-:copyright: 2010 Pallets
-:license: BSD-3-Clause
-"""
-import sys
-
-PY2 = sys.version_info[0] == 2
-
-if not PY2:
- text_type = str
- string_types = (str,)
- unichr = chr
- int_types = (int,)
-
- def iteritems(x):
- return iter(x.items())
-
- from collections.abc import Mapping
-
-else:
- text_type = unicode
- string_types = (str, unicode)
- unichr = unichr
- int_types = (int, long)
-
- def iteritems(x):
- return x.iteritems()
-
- from collections import Mapping
diff --git a/src/markupsafe/_constants.py b/src/markupsafe/_constants.py
index 7c57c2d..7638937 100644
--- a/src/markupsafe/_constants.py
+++ b/src/markupsafe/_constants.py
@@ -1,12 +1,3 @@
-# -*- coding: utf-8 -*-
-"""
-markupsafe._constants
-~~~~~~~~~~~~~~~~~~~~~
-
-:copyright: 2010 Pallets
-:license: BSD-3-Clause
-"""
-
HTML_ENTITIES = {
"AElig": 198,
"Aacute": 193,
diff --git a/src/markupsafe/_native.py b/src/markupsafe/_native.py
index cd08752..87cefcf 100644
--- a/src/markupsafe/_native.py
+++ b/src/markupsafe/_native.py
@@ -1,15 +1,7 @@
-# -*- coding: utf-8 -*-
"""
-markupsafe._native
-~~~~~~~~~~~~~~~~~~
-
Native Python implementation used when the C module is not compiled.
-
-:copyright: 2010 Pallets
-:license: BSD-3-Clause
"""
from . import Markup
-from ._compat import text_type
def escape(s):
@@ -26,7 +18,7 @@ def escape(s):
if hasattr(s, "__html__"):
return Markup(s.__html__())
return Markup(
- text_type(s)
+ str(s)
.replace("&", "&amp;")
.replace(">", "&gt;")
.replace("<", "&lt;")
@@ -50,20 +42,32 @@ def escape_silent(s):
return escape(s)
-def soft_unicode(s):
+def soft_str(s):
"""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 = escape("<User 1>")
>>> value
Markup('&lt;User 1&gt;')
>>> escape(str(value))
Markup('&amp;lt;User 1&amp;gt;')
- >>> escape(soft_unicode(value))
+ >>> escape(soft_str(value))
Markup('&lt;User 1&gt;')
"""
- if not isinstance(s, text_type):
- s = text_type(s)
+ if not isinstance(s, str):
+ return str(s)
return s
+
+
+def soft_unicode(s):
+ import warnings
+
+ warnings.warn(
+ "'soft_unicode' has been renamed to 'soft_str'. The old name"
+ " will be removed in version 2.1.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return soft_str(s)
diff --git a/src/markupsafe/_speedups.c b/src/markupsafe/_speedups.c
index 12d2c4a..0f2e7b2 100644
--- a/src/markupsafe/_speedups.c
+++ b/src/markupsafe/_speedups.c
@@ -1,23 +1,9 @@
/**
- * markupsafe._speedups
- * ~~~~~~~~~~~~~~~~~~~~
- *
* C implementation of escaping for better performance. Used instead of
* the native Python implementation when compiled.
- *
- * :copyright: 2010 Pallets
- * :license: BSD-3-Clause
*/
#include <Python.h>
-#if PY_MAJOR_VERSION < 3
-#define ESCAPED_CHARS_TABLE_SIZE 63
-#define UNICHR(x) (PyUnicode_AS_UNICODE((PyUnicodeObject*)PyUnicode_DecodeASCII(x, strlen(x), NULL)));
-
-static Py_ssize_t escaped_chars_delta_len[ESCAPED_CHARS_TABLE_SIZE];
-static Py_UNICODE *escaped_chars_repl[ESCAPED_CHARS_TABLE_SIZE];
-#endif
-
static PyObject* markup;
static int
@@ -25,21 +11,6 @@ init_constants(void)
{
PyObject *module;
-#if PY_MAJOR_VERSION < 3
- /* mapping of characters to replace */
- escaped_chars_repl['"'] = UNICHR("&#34;");
- escaped_chars_repl['\''] = UNICHR("&#39;");
- escaped_chars_repl['&'] = UNICHR("&amp;");
- escaped_chars_repl['<'] = UNICHR("&lt;");
- escaped_chars_repl['>'] = UNICHR("&gt;");
-
- /* lengths of those characters when replaced - 1 */
- memset(escaped_chars_delta_len, 0, sizeof (escaped_chars_delta_len));
- escaped_chars_delta_len['"'] = escaped_chars_delta_len['\''] = \
- escaped_chars_delta_len['&'] = 4;
- escaped_chars_delta_len['<'] = escaped_chars_delta_len['>'] = 3;
-#endif
-
/* import markup type so that we can mark the return value */
module = PyImport_ImportModule("markupsafe");
if (!module)
@@ -50,137 +21,74 @@ init_constants(void)
return 1;
}
-#if PY_MAJOR_VERSION < 3
-static PyObject*
-escape_unicode(PyUnicodeObject *in)
-{
- PyUnicodeObject *out;
- Py_UNICODE *inp = PyUnicode_AS_UNICODE(in);
- const Py_UNICODE *inp_end = PyUnicode_AS_UNICODE(in) + PyUnicode_GET_SIZE(in);
- Py_UNICODE *next_escp;
- Py_UNICODE *outp;
- Py_ssize_t delta=0, erepl=0, delta_len=0;
-
- /* First we need to figure out how long the escaped string will be */
- while (*(inp) || inp < inp_end) {
- if (*inp < ESCAPED_CHARS_TABLE_SIZE) {
- delta += escaped_chars_delta_len[*inp];
- erepl += !!escaped_chars_delta_len[*inp];
- }
- ++inp;
- }
-
- /* Do we need to escape anything at all? */
- if (!erepl) {
- Py_INCREF(in);
- return (PyObject*)in;
- }
-
- out = (PyUnicodeObject*)PyUnicode_FromUnicode(NULL, PyUnicode_GET_SIZE(in) + delta);
- if (!out)
- return NULL;
-
- outp = PyUnicode_AS_UNICODE(out);
- inp = PyUnicode_AS_UNICODE(in);
- while (erepl-- > 0) {
- /* look for the next substitution */
- next_escp = inp;
- while (next_escp < inp_end) {
- if (*next_escp < ESCAPED_CHARS_TABLE_SIZE &&
- (delta_len = escaped_chars_delta_len[*next_escp])) {
- ++delta_len;
- break;
- }
- ++next_escp;
- }
-
- if (next_escp > inp) {
- /* copy unescaped chars between inp and next_escp */
- Py_UNICODE_COPY(outp, inp, next_escp-inp);
- outp += next_escp - inp;
- }
-
- /* escape 'next_escp' */
- Py_UNICODE_COPY(outp, escaped_chars_repl[*next_escp], delta_len);
- outp += delta_len;
-
- inp = next_escp + 1;
- }
- if (inp < inp_end)
- Py_UNICODE_COPY(outp, inp, PyUnicode_GET_SIZE(in) - (inp - PyUnicode_AS_UNICODE(in)));
-
- return (PyObject*)out;
-}
-#else /* PY_MAJOR_VERSION < 3 */
-
#define GET_DELTA(inp, inp_end, delta) \
- while (inp < inp_end) { \
- switch (*inp++) { \
- case '"': \
- case '\'': \
- case '&': \
- delta += 4; \
- break; \
- case '<': \
- case '>': \
- delta += 3; \
- break; \
- } \
+ while (inp < inp_end) { \
+ switch (*inp++) { \
+ case '"': \
+ case '\'': \
+ case '&': \
+ delta += 4; \
+ break; \
+ case '<': \
+ case '>': \
+ delta += 3; \
+ break; \
+ } \
}
#define DO_ESCAPE(inp, inp_end, outp) \
- { \
- Py_ssize_t ncopy = 0; \
- while (inp < inp_end) { \
- switch (*inp) { \
- case '"': \
+ { \
+ Py_ssize_t ncopy = 0; \
+ while (inp < inp_end) { \
+ switch (*inp) { \
+ case '"': \
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
outp += ncopy; ncopy = 0; \
- *outp++ = '&'; \
- *outp++ = '#'; \
- *outp++ = '3'; \
- *outp++ = '4'; \
- *outp++ = ';'; \
- break; \
- case '\'': \
+ *outp++ = '&'; \
+ *outp++ = '#'; \
+ *outp++ = '3'; \
+ *outp++ = '4'; \
+ *outp++ = ';'; \
+ break; \
+ case '\'': \
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
outp += ncopy; ncopy = 0; \
- *outp++ = '&'; \
- *outp++ = '#'; \
- *outp++ = '3'; \
- *outp++ = '9'; \
- *outp++ = ';'; \
- break; \
- case '&': \
+ *outp++ = '&'; \
+ *outp++ = '#'; \
+ *outp++ = '3'; \
+ *outp++ = '9'; \
+ *outp++ = ';'; \
+ break; \
+ case '&': \
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
outp += ncopy; ncopy = 0; \
- *outp++ = '&'; \
- *outp++ = 'a'; \
- *outp++ = 'm'; \
- *outp++ = 'p'; \
- *outp++ = ';'; \
- break; \
- case '<': \
+ *outp++ = '&'; \
+ *outp++ = 'a'; \
+ *outp++ = 'm'; \
+ *outp++ = 'p'; \
+ *outp++ = ';'; \
+ break; \
+ case '<': \
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
outp += ncopy; ncopy = 0; \
- *outp++ = '&'; \
- *outp++ = 'l'; \
- *outp++ = 't'; \
- *outp++ = ';'; \
- break; \
- case '>': \
+ *outp++ = '&'; \
+ *outp++ = 'l'; \
+ *outp++ = 't'; \
+ *outp++ = ';'; \
+ break; \
+ case '>': \
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
outp += ncopy; ncopy = 0; \
- *outp++ = '&'; \
- *outp++ = 'g'; \
- *outp++ = 't'; \
- *outp++ = ';'; \
- break; \
- default: \
+ *outp++ = '&'; \
+ *outp++ = 'g'; \
+ *outp++ = 't'; \
+ *outp++ = ';'; \
+ break; \
+ default: \
ncopy++; \
- } \
- inp++; \
- } \
+ } \
+ inp++; \
+ } \
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
}
@@ -278,7 +186,6 @@ escape_unicode(PyUnicodeObject *in)
assert(0); /* shouldn't happen */
return NULL;
}
-#endif /* PY_MAJOR_VERSION < 3 */
static PyObject*
escape(PyObject *self, PyObject *text)
@@ -287,11 +194,7 @@ escape(PyObject *self, PyObject *text)
PyObject *s = NULL, *rv = NULL, *html;
if (id_html == NULL) {
-#if PY_MAJOR_VERSION < 3
- id_html = PyString_InternFromString("__html__");
-#else
id_html = PyUnicode_InternFromString("__html__");
-#endif
if (id_html == NULL) {
return NULL;
}
@@ -299,11 +202,8 @@ escape(PyObject *self, PyObject *text)
/* we don't have to escape integers, bools or floats */
if (PyLong_CheckExact(text) ||
-#if PY_MAJOR_VERSION < 3
- PyInt_CheckExact(text) ||
-#endif
- PyFloat_CheckExact(text) || PyBool_Check(text) ||
- text == Py_None)
+ 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 */
@@ -323,11 +223,7 @@ escape(PyObject *self, PyObject *text)
/* otherwise make the object unicode if it isn't, then escape */
PyErr_Clear();
if (!PyUnicode_Check(text)) {
-#if PY_MAJOR_VERSION < 3
- PyObject *unicode = PyObject_Unicode(text);
-#else
PyObject *unicode = PyObject_Str(text);
-#endif
if (!unicode)
return NULL;
s = escape_unicode((PyUnicodeObject*)unicode);
@@ -353,54 +249,80 @@ escape_silent(PyObject *self, PyObject *text)
static PyObject*
-soft_unicode(PyObject *self, PyObject *s)
+soft_str(PyObject *self, PyObject *s)
{
if (!PyUnicode_Check(s))
-#if PY_MAJOR_VERSION < 3
- return PyObject_Unicode(s);
-#else
return PyObject_Str(s);
-#endif
Py_INCREF(s);
return s;
}
-static PyMethodDef module_methods[] = {
- {"escape", (PyCFunction)escape, METH_O,
- "escape(s) -> markup\n\n"
- "Convert the characters &, <, >, ', and \" in string s to HTML-safe\n"
- "sequences. Use this if you need to display text that might contain\n"
- "such characters in HTML. Marks return value as markup string."},
- {"escape_silent", (PyCFunction)escape_silent, METH_O,
- "escape_silent(s) -> markup\n\n"
- "Like escape but converts None to an empty string."},
- {"soft_unicode", (PyCFunction)soft_unicode, METH_O,
- "soft_unicode(object) -> string\n\n"
- "Make a string unicode if it isn't already. That way a markup\n"
- "string is not converted back to unicode."},
- {NULL, NULL, 0, NULL} /* Sentinel */
-};
-
-
-#if PY_MAJOR_VERSION < 3
-
-#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */
-#define PyMODINIT_FUNC void
-#endif
-PyMODINIT_FUNC
-init_speedups(void)
+static PyObject*
+soft_unicode(PyObject *self, PyObject *s)
{
- if (!init_constants())
- return;
-
- Py_InitModule3("markupsafe._speedups", module_methods, "");
+ PyErr_WarnEx(
+ PyExc_DeprecationWarning,
+ "'soft_unicode' has been renamed to 'soft_str'. The old name"
+ " will be removed in version 2.1.",
+ 2
+ );
+ return soft_str(self, s);
}
-#else /* Python 3.x module initialization */
+
+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"
+ },
+ {
+ "soft_unicode",
+ (PyCFunction)soft_unicode,
+ METH_O,
+ ""
+ },
+ {NULL, NULL, 0, NULL} /* Sentinel */
+};
static struct PyModuleDef module_definition = {
- PyModuleDef_HEAD_INIT,
+ PyModuleDef_HEAD_INIT,
"markupsafe._speedups",
NULL,
-1,
@@ -419,5 +341,3 @@ PyInit__speedups(void)
return PyModule_Create(&module_definition);
}
-
-#endif
diff --git a/tests/conftest.py b/tests/conftest.py
index 296cd58..7141547 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -34,4 +34,9 @@ def escape_silent(_mod):
@pytest.fixture(scope="session")
def soft_str(_mod):
+ return _mod.soft_str
+
+
+@pytest.fixture(scope="session")
+def soft_unicode(_mod):
return _mod.soft_unicode
diff --git a/tests/test_escape.py b/tests/test_escape.py
index 788134a..bf53fac 100644
--- a/tests/test_escape.py
+++ b/tests/test_escape.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import pytest
from markupsafe import Markup
@@ -8,22 +7,22 @@ from markupsafe import Markup
("value", "expect"),
(
# empty
- (u"", u""),
+ ("", ""),
# ascii
- (u"abcd&><'\"efgh", u"abcd&amp;&gt;&lt;&#39;&#34;efgh"),
- (u"&><'\"efgh", u"&amp;&gt;&lt;&#39;&#34;efgh"),
- (u"abcd&><'\"", u"abcd&amp;&gt;&lt;&#39;&#34;"),
+ ("abcd&><'\"efgh", "abcd&amp;&gt;&lt;&#39;&#34;efgh"),
+ ("&><'\"efgh", "&amp;&gt;&lt;&#39;&#34;efgh"),
+ ("abcd&><'\"", "abcd&amp;&gt;&lt;&#39;&#34;"),
# 2 byte
- (u"こんにちは&><'\"こんばんは", u"こんにちは&amp;&gt;&lt;&#39;&#34;こんばんは"),
- (u"&><'\"こんばんは", u"&amp;&gt;&lt;&#39;&#34;こんばんは"),
- (u"こんにちは&><'\"", u"こんにちは&amp;&gt;&lt;&#39;&#34;"),
+ ("こんにちは&><'\"こんばんは", "こんにちは&amp;&gt;&lt;&#39;&#34;こんばんは"),
+ ("&><'\"こんばんは", "&amp;&gt;&lt;&#39;&#34;こんばんは"),
+ ("こんにちは&><'\"", "こんにちは&amp;&gt;&lt;&#39;&#34;"),
# 4 byte
(
- u"\U0001F363\U0001F362&><'\"\U0001F37A xyz",
- u"\U0001F363\U0001F362&amp;&gt;&lt;&#39;&#34;\U0001F37A xyz",
+ "\U0001F363\U0001F362&><'\"\U0001F37A xyz",
+ "\U0001F363\U0001F362&amp;&gt;&lt;&#39;&#34;\U0001F37A xyz",
),
- (u"&><'\"\U0001F37A xyz", u"&amp;&gt;&lt;&#39;&#34;\U0001F37A xyz"),
- (u"\U0001F363\U0001F362&><'\"", u"\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):
diff --git a/tests/test_exception_custom_html.py b/tests/test_exception_custom_html.py
index 5f9ffde..ec2f10b 100644
--- a/tests/test_exception_custom_html.py
+++ b/tests/test_exception_custom_html.py
@@ -1,15 +1,12 @@
-# -*- coding: utf-8 -*-
import pytest
-from markupsafe import escape
-
-class CustomHtmlThatRaises(object):
+class CustomHtmlThatRaises:
def __html__(self):
raise ValueError(123)
-def test_exception_custom_html():
+def test_exception_custom_html(escape):
"""Checks whether exceptions in custom __html__ implementations are
propagated correctly.
diff --git a/tests/test_leak.py b/tests/test_leak.py
index b36a4ce..55b10b9 100644
--- a/tests/test_leak.py
+++ b/tests/test_leak.py
@@ -1,6 +1,5 @@
-# -*- coding: utf-8 -*-
import gc
-import sys
+import platform
import pytest
@@ -18,10 +17,10 @@ def test_markup_leaks():
for _j in range(1000):
escape("foo")
escape("<foo>")
- escape(u"foo")
- escape(u"<foo>")
+ escape("foo")
+ escape("<foo>")
- if hasattr(sys, "pypy_version_info"):
+ if platform.python_implementation() == "PyPy":
gc.collect()
counts.add(len(gc.get_objects()))
diff --git a/tests/test_markupsafe.py b/tests/test_markupsafe.py
index 5b08006..bd42d98 100644
--- a/tests/test_markupsafe.py
+++ b/tests/test_markupsafe.py
@@ -1,17 +1,12 @@
-# -*- coding: utf-8 -*-
import pytest
-from markupsafe import escape
-from markupsafe import escape_silent
from markupsafe import Markup
-from markupsafe._compat import PY2
-from markupsafe._compat import text_type
-def test_adding():
+def test_adding(escape):
unsafe = '<script type="application/x-some-script">alert("foo");</script>'
safe = Markup("<em>username</em>")
- assert unsafe + safe == text_type(escape(unsafe)) + text_type(safe)
+ assert unsafe + safe == str(escape(unsafe)) + str(safe)
@pytest.mark.parametrize(
@@ -38,15 +33,13 @@ def test_type_behavior():
def test_html_interop():
- class Foo(object):
+ class Foo:
def __html__(self):
return "<em>awesome</em>"
- def __unicode__(self):
+ def __str__(self):
return "awesome"
- __str__ = __unicode__
-
assert Markup(Foo()) == "<em>awesome</em>"
result = Markup("<strong>%s</strong>") % Foo()
assert result == "<strong><em>awesome</em></strong>"
@@ -54,21 +47,21 @@ def test_html_interop():
def test_tuple_interpol():
result = Markup("<em>%s:%s</em>") % ("<foo>", "<bar>")
- expect = Markup(u"<em>&lt;foo&gt;:&lt;bar&gt;</em>")
+ expect = Markup("<em>&lt;foo&gt;:&lt;bar&gt;</em>")
assert result == expect
def test_dict_interpol():
result = Markup("<em>%(foo)s</em>") % {"foo": "<foo>"}
- expect = Markup(u"<em>&lt;foo&gt;</em>")
+ expect = Markup("<em>&lt;foo&gt;</em>")
assert result == expect
result = Markup("<em>%(foo)s:%(bar)s</em>") % {"foo": "<foo>", "bar": "<bar>"}
- expect = Markup(u"<em>&lt;foo&gt;:&lt;bar&gt;</em>")
+ expect = Markup("<em>&lt;foo&gt;:&lt;bar&gt;</em>")
assert result == expect
-def test_escaping():
+def test_escaping(escape):
assert escape("\"<>&'") == "&#34;&lt;&gt;&amp;&#39;"
assert Markup("<em>Foo &amp; Bar</em>").striptags() == "Foo & Bar"
@@ -105,11 +98,11 @@ def test_formatting_empty():
def test_custom_formatting():
- class HasHTMLOnly(object):
+ class HasHTMLOnly:
def __html__(self):
return Markup("<foo>")
- class HasHTMLAndFormat(object):
+ class HasHTMLAndFormat:
def __html__(self):
return Markup("<foo>")
@@ -121,7 +114,7 @@ def test_custom_formatting():
def test_complex_custom_formatting():
- class User(object):
+ class User:
def __init__(self, id, username):
self.id = id
self.username = username
@@ -146,19 +139,11 @@ def test_complex_custom_formatting():
def test_formatting_with_objects():
- class Stringable(object):
- def __unicode__(self):
- return u"строка"
-
- if PY2:
-
- def __str__(self):
- return "some other value"
-
- else:
- __str__ = __unicode__
+ class Stringable:
+ def __str__(self):
+ return "строка"
- assert Markup("{s}").format(s=Stringable()) == Markup(u"строка")
+ assert Markup("{s}").format(s=Stringable()) == Markup("строка")
def test_all_set():
@@ -168,10 +153,10 @@ def test_all_set():
getattr(markup, item)
-def test_escape_silent():
+def test_escape_silent(escape, escape_silent):
assert escape_silent(None) == Markup()
assert escape(None) == Markup(None)
- assert escape_silent("<foo>") == Markup(u"&lt;foo&gt;")
+ assert escape_silent("<foo>") == Markup("&lt;foo&gt;")
def test_splitting():
@@ -185,7 +170,7 @@ def test_mul():
assert Markup("a") * 3 == Markup("aaa")
-def test_escape_return_type():
+def test_escape_return_type(escape):
assert isinstance(escape("a"), Markup)
assert isinstance(escape(Markup("a")), Markup)
@@ -194,3 +179,14 @@ def test_escape_return_type():
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_unicode_deprecated(soft_unicode):
+ with pytest.warns(DeprecationWarning):
+ assert type(soft_unicode(Markup())) is Markup
diff --git a/tox.ini b/tox.ini
index 679ebeb..736b6e7 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,6 @@
[tox]
envlist =
- py{38,37,36,35,27,py3,py}
+ py{38,37,36,py3}
style
docs
skip_missing_interpreters = true