summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci-tests.yml9
-rw-r--r--setup.py4
-rw-r--r--src/webob/acceptparse.py38
-rw-r--r--src/webob/byterange.py10
-rw-r--r--src/webob/cachecontrol.py2
-rw-r--r--src/webob/client.py4
-rw-r--r--src/webob/compat.py8
-rw-r--r--src/webob/cookies.py20
-rw-r--r--src/webob/dec.py6
-rw-r--r--src/webob/descriptors.py8
-rw-r--r--src/webob/etag.py4
-rw-r--r--src/webob/exc.py2
-rw-r--r--src/webob/multidict.py8
-rw-r--r--src/webob/request.py18
-rw-r--r--src/webob/response.py10
-rw-r--r--src/webob/static.py2
-rw-r--r--src/webob/util.py4
-rw-r--r--tests/conftest.py2
-rw-r--r--tests/performance_test.py2
-rw-r--r--tests/test_compat.py6
-rw-r--r--tests/test_dec.py4
-rw-r--r--tests/test_descriptors.py4
-rw-r--r--tests/test_in_wsgiref.py2
-rw-r--r--tests/test_request.py2
-rw-r--r--tests/test_transcode.py4
-rw-r--r--tox.ini12
26 files changed, 97 insertions, 98 deletions
diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml
index 108a543..96c7e20 100644
--- a/.github/workflows/ci-tests.yml
+++ b/.github/workflows/ci-tests.yml
@@ -7,6 +7,7 @@ on:
- master
- "[0-9].[0-9]+-branch"
tags:
+ - "*"
# Build pull requests
pull_request:
@@ -15,11 +16,11 @@ jobs:
strategy:
matrix:
py:
- - "3.6"
- "3.7"
- "3.8"
- "3.9"
- - "pypy3"
+ - "3.10"
+ - "pypy-3.8"
os:
- "ubuntu-latest"
- "windows-latest"
@@ -34,9 +35,9 @@ jobs:
architecture: x86
- os: "macos-latest"
architecture: x86
- # Building on PyPy3 on Windows is broken
+ # PyPy on Windows is a no go
- os: "windows-latest"
- py: "pypy3"
+ py: "pypy-3.8"
name: "Python: ${{ matrix.py }}-${{ matrix.architecture }} on ${{ matrix.os }}"
runs-on: ${{ matrix.os }}
diff --git a/setup.py b/setup.py
index 8c81dc7..2b63ca2 100644
--- a/setup.py
+++ b/setup.py
@@ -32,11 +32,11 @@ setup(
"Topic :: Internet :: WWW/HTTP :: WSGI",
"Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
"Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware",
- "Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
],
@@ -48,7 +48,7 @@ setup(
license="MIT",
packages=find_packages("src", exclude=["tests"]),
package_dir={"": "src"},
- python_requires=">=3.6",
+ python_requires=">=3.7",
zip_safe=True,
extras_require={"testing": testing_extras, "docs": docs_extras},
)
diff --git a/src/webob/acceptparse.py b/src/webob/acceptparse.py
index ee5e78b..b186dad 100644
--- a/src/webob/acceptparse.py
+++ b/src/webob/acceptparse.py
@@ -43,9 +43,9 @@ def _item_qvalue_pair_to_header_element(pair):
if qvalue == 1.0:
element = item
elif qvalue == 0.0:
- element = "{};q=0".format(item)
+ element = f"{item};q=0"
else:
- element = "{};q={}".format(item, qvalue)
+ element = f"{item};q={qvalue}"
return element
@@ -315,13 +315,13 @@ class Accept:
if qvalue == 1.0:
if extension_params_segment:
- element = "{};q=1{}".format(media_range, extension_params_segment)
+ element = f"{media_range};q=1{extension_params_segment}"
else:
element = media_range
elif qvalue == 0.0:
- element = "{};q=0{}".format(media_range, extension_params_segment)
+ element = f"{media_range};q=0{extension_params_segment}"
else:
- element = "{};q={}{}".format(media_range, qvalue, extension_params_segment)
+ element = f"{media_range};q={qvalue}{extension_params_segment}"
return element
@@ -757,7 +757,7 @@ class AcceptValidHeader(Accept):
)
def __repr__(self):
- return "<{} ({!r})>".format(self.__class__.__name__, str(self))
+ return f"<{self.__class__.__name__} ({str(self)!r})>"
def __str__(self):
r"""
@@ -842,14 +842,14 @@ class AcceptValidHeader(Accept):
# Set mask type with wildcard subtype for malformed masks
try:
- mask_type, mask_subtype = [x.lower() for x in mask.split("/")]
+ mask_type, mask_subtype = (x.lower() for x in mask.split("/"))
except ValueError:
mask_type = mask
mask_subtype = "*"
# Set offer type with wildcard subtype for malformed offers
try:
- offer_type, offer_subtype = [x.lower() for x in offer.split("/")]
+ offer_type, offer_subtype = (x.lower() for x in offer.split("/"))
except ValueError:
offer_type = offer
offer_subtype = "*"
@@ -1560,7 +1560,7 @@ class AcceptNoHeader(_AcceptInvalidOrNoHeader):
return self.__add__(other=other)
def __repr__(self):
- return "<{}>".format(self.__class__.__name__)
+ return f"<{self.__class__.__name__}>"
def __str__(self):
"""Return the ``str`` ``'<no header in request>'``."""
@@ -1678,7 +1678,7 @@ class AcceptInvalidHeader(_AcceptInvalidOrNoHeader):
)
def __repr__(self):
- return "<{}>".format(self.__class__.__name__)
+ return f"<{self.__class__.__name__}>"
# We do not display the header_value, as it is untrusted input. The
# header_value could always be easily obtained from the .header_value
# property.
@@ -2039,7 +2039,7 @@ class AcceptCharsetValidHeader(AcceptCharset):
)
def __repr__(self):
- return "<{} ({!r})>".format(self.__class__.__name__, str(self))
+ return f"<{self.__class__.__name__} ({str(self)!r})>"
def __str__(self):
r"""
@@ -2559,7 +2559,7 @@ class AcceptCharsetNoHeader(_AcceptCharsetInvalidOrNoHeader):
return self.__add__(other=other)
def __repr__(self):
- return "<{}>".format(self.__class__.__name__)
+ return f"<{self.__class__.__name__}>"
def __str__(self):
"""Return the ``str`` ``'<no header in request>'``."""
@@ -2675,7 +2675,7 @@ class AcceptCharsetInvalidHeader(_AcceptCharsetInvalidOrNoHeader):
)
def __repr__(self):
- return "<{}>".format(self.__class__.__name__)
+ return f"<{self.__class__.__name__}>"
# We do not display the header_value, as it is untrusted input. The
# header_value could always be easily obtained from the .header_value
# property.
@@ -3051,7 +3051,7 @@ class AcceptEncodingValidHeader(AcceptEncoding):
)
def __repr__(self):
- return "<{} ({!r})>".format(self.__class__.__name__, str(self))
+ return f"<{self.__class__.__name__} ({str(self)!r})>"
def __str__(self):
r"""
@@ -3599,7 +3599,7 @@ class AcceptEncodingNoHeader(_AcceptEncodingInvalidOrNoHeader):
return self.__add__(other=other)
def __repr__(self):
- return "<{}>".format(self.__class__.__name__)
+ return f"<{self.__class__.__name__}>"
def __str__(self):
"""Return the ``str`` ``'<no header in request>'``."""
@@ -3716,7 +3716,7 @@ class AcceptEncodingInvalidHeader(_AcceptEncodingInvalidOrNoHeader):
)
def __repr__(self):
- return "<{}>".format(self.__class__.__name__)
+ return f"<{self.__class__.__name__}>"
# We do not display the header_value, as it is untrusted input. The
# header_value could always be easily obtained from the .header_value
# property.
@@ -4101,7 +4101,7 @@ class AcceptLanguageValidHeader(AcceptLanguage):
)
def __repr__(self):
- return "<{} ({!r})>".format(self.__class__.__name__, str(self))
+ return f"<{self.__class__.__name__} ({str(self)!r})>"
def __str__(self):
r"""
@@ -5172,7 +5172,7 @@ class AcceptLanguageNoHeader(_AcceptLanguageInvalidOrNoHeader):
return self.__add__(other=other)
def __repr__(self):
- return "<{}>".format(self.__class__.__name__)
+ return f"<{self.__class__.__name__}>"
def __str__(self):
"""Return the ``str`` ``'<no header in request>'``."""
@@ -5289,7 +5289,7 @@ class AcceptLanguageInvalidHeader(_AcceptLanguageInvalidOrNoHeader):
)
def __repr__(self):
- return "<{}>".format(self.__class__.__name__)
+ return f"<{self.__class__.__name__}>"
# We do not display the header_value, as it is untrusted input. The
# header_value could always be easily obtained from the .header_value
# property.
diff --git a/src/webob/byterange.py b/src/webob/byterange.py
index 44db453..06c2224 100644
--- a/src/webob/byterange.py
+++ b/src/webob/byterange.py
@@ -57,10 +57,10 @@ class Range:
if s >= 0:
r += "-"
return r
- return "bytes=%s-%s" % (s, e - 1)
+ return f"bytes={s}-{e - 1}"
def __repr__(self):
- return "<%s bytes %r-%r>" % (self.__class__.__name__, self.start, self.end)
+ return f"<{self.__class__.__name__} bytes {self.start!r}-{self.end!r}>"
def __iter__(self):
return iter((self.start, self.end))
@@ -96,13 +96,13 @@ class ContentRange:
def __init__(self, start, stop, length):
if not _is_content_range_valid(start, stop, length):
- raise ValueError("Bad start:stop/length: %r-%r/%r" % (start, stop, length))
+ raise ValueError(f"Bad start:stop/length: {start!r}-{stop!r}/{length!r}")
self.start = start
self.stop = stop # this is python-style range end (non-inclusive)
self.length = length
def __repr__(self):
- return "<%s %s>" % (self.__class__.__name__, self)
+ return f"<{self.__class__.__name__} {self}>"
def __str__(self):
if self.length is None:
@@ -113,7 +113,7 @@ class ContentRange:
assert self.stop is None
return "bytes */%s" % length
stop = self.stop - 1 # from non-inclusive to HTTP-style
- return "bytes %s-%s/%s" % (self.start, stop, length)
+ return f"bytes {self.start}-{stop}/{length}"
def __iter__(self):
"""
diff --git a/src/webob/cachecontrol.py b/src/webob/cachecontrol.py
index 1029344..59932d3 100644
--- a/src/webob/cachecontrol.py
+++ b/src/webob/cachecontrol.py
@@ -229,5 +229,5 @@ def serialize_cache_control(properties):
value = str(value)
if need_quote_re.search(value):
value = '"%s"' % value
- parts.append("%s=%s" % (name, value))
+ parts.append(f"{name}={value}")
return ", ".join(parts)
diff --git a/src/webob/client.py b/src/webob/client.py
index 980137a..1b32d9e 100644
--- a/src/webob/client.py
+++ b/src/webob/client.py
@@ -127,7 +127,7 @@ class SendRequest:
return resp(environ, start_response)
raise
headers_out = self.parse_headers(res.msg)
- status = "%s %s" % (res.status, res.reason)
+ status = f"{res.status} {res.reason}"
start_response(status, headers_out)
length = res.getheader("content-length")
# FIXME: This shouldn't really read in all the content at once
@@ -178,7 +178,7 @@ class SendRequest:
try:
header, value = full_header.split(":", 1)
except Exception:
- raise ValueError("Invalid header: %r" % (full_header,))
+ raise ValueError(f"Invalid header: {full_header!r}")
value = value.strip()
if "\n" in value or "\r\n" in value: # pragma: no cover
diff --git a/src/webob/compat.py b/src/webob/compat.py
index 735d510..55fbef9 100644
--- a/src/webob/compat.py
+++ b/src/webob/compat.py
@@ -20,9 +20,9 @@ class cgi_FieldStorage(_cgi_FieldStorage): # pragma: no cover
"""
if self.file:
- return "FieldStorage(%r, %r)" % (self.name, self.filename)
+ return f"FieldStorage({self.name!r}, {self.filename!r})"
- return "FieldStorage(%r, %r, %r)" % (self.name, self.filename, self.value)
+ return f"FieldStorage({self.name!r}, {self.filename!r}, {self.value!r})"
# Work around https://bugs.python.org/issue27777
def make_file(self):
@@ -38,7 +38,7 @@ class cgi_FieldStorage(_cgi_FieldStorage): # pragma: no cover
ib = self.innerboundary
if not cgi.valid_boundary(ib):
- raise ValueError("Invalid boundary in multipart form: %r" % (ib,))
+ raise ValueError(f"Invalid boundary in multipart form: {ib!r}")
self.list = []
if self.qs_on_post:
@@ -58,7 +58,7 @@ class cgi_FieldStorage(_cgi_FieldStorage): # pragma: no cover
if not isinstance(first_line, bytes):
raise ValueError(
- "%s should return bytes, got %s" % (self.fp, type(first_line).__name__)
+ f"{self.fp} should return bytes, got {type(first_line).__name__}"
)
self.bytes_read += len(first_line)
diff --git a/src/webob/cookies.py b/src/webob/cookies.py
index 8244846..ed20327 100644
--- a/src/webob/cookies.py
+++ b/src/webob/cookies.py
@@ -153,7 +153,7 @@ class RequestCookies(MutableMapping):
self._environ["HTTP_COOKIE"] = ""
def __repr__(self):
- return "<RequestCookies (dict-like) with values %r>" % (self._cache,)
+ return f"<RequestCookies (dict-like) with values {self._cache!r}>"
class Cookie(dict):
@@ -192,7 +192,7 @@ class Cookie(dict):
__str__ = serialize
def __repr__(self):
- return "<%s: [%s]>" % (
+ return "<{}: [{}]>".format(
self.__class__.__name__,
", ".join(map(repr, self.values())),
)
@@ -321,7 +321,7 @@ class Morsel(dict):
__str__ = serialize
def __repr__(self):
- return "<%s: %s=%r>" % (
+ return "<{}: {}={!r}>".format(
self.__class__.__name__,
text_(self.name),
text_(self.value),
@@ -340,7 +340,7 @@ _re_expires_val = r"\w{3},\s[\w\d-]{9,11}\s[\d:]{8}\sGMT"
_re_cookie_str_key = r"(%s+?)" % _re_legal_char
_re_cookie_str_equal = r"\s*=\s*"
_re_unquoted_val = r"(?:%s|\\(?:[0-3][0-7][0-7]|.))*" % _re_legal_char
-_re_cookie_str_val = r"(%s|%s|%s)" % (_re_quoted, _re_expires_val, _re_unquoted_val)
+_re_cookie_str_val = rf"({_re_quoted}|{_re_expires_val}|{_re_unquoted_val})"
_re_cookie_str = _re_cookie_str_key + _re_cookie_str_equal + _re_cookie_str_val
_rx_cookie = re.compile(bytes_(_re_cookie_str, "ascii"))
@@ -614,7 +614,7 @@ def make_cookie(
class JSONSerializer:
- """ A serializer which uses `json.dumps`` and ``json.loads``"""
+ """A serializer which uses `json.dumps`` and ``json.loads``"""
def dumps(self, appstruct):
return bytes_(json.dumps(appstruct), encoding="utf-8")
@@ -627,7 +627,7 @@ class JSONSerializer:
class Base64Serializer:
- """ A serializer which uses base64 to encode/decode data"""
+ """A serializer which uses base64 to encode/decode data"""
def __init__(self, serializer=None):
if serializer is None:
@@ -818,12 +818,12 @@ class CookieProfile:
self.request = None
def __call__(self, request):
- """ Bind a request to a copy of this instance and return it"""
+ """Bind a request to a copy of this instance and return it"""
return self.bind(request)
def bind(self, request):
- """ Bind a request to a copy of this instance and return it"""
+ """Bind a request to a copy of this instance and return it"""
selfish = CookieProfile(
self.cookie_name,
@@ -871,7 +871,7 @@ class CookieProfile:
httponly=_default,
samesite=_default,
):
- """ Set the cookies on a response."""
+ """Set the cookies on a response."""
cookies = self.get_headers(
value,
domains=domains,
@@ -1083,7 +1083,7 @@ class SignedCookieProfile(CookieProfile):
)
def bind(self, request):
- """ Bind a request to a copy of this instance and return it"""
+ """Bind a request to a copy of this instance and return it"""
selfish = SignedCookieProfile(
self.secret,
diff --git a/src/webob/dec.py b/src/webob/dec.py
index 02ec255..caa1bfb 100644
--- a/src/webob/dec.py
+++ b/src/webob/dec.py
@@ -94,7 +94,7 @@ class wsgify:
self.middleware_wraps = middleware_wraps
def __repr__(self):
- return "<%s at %s wrapping %r>" % (self.__class__.__name__, id(self), self.func)
+ return f"<{self.__class__.__name__} at {id(self)} wrapping {self.func!r}>"
def __get__(self, obj, type=None):
# This handles wrapping methods
@@ -307,7 +307,7 @@ class _UnboundMiddleware:
self.kw = kw
def __repr__(self):
- return "<%s at %s wrapping %r>" % (self.__class__.__name__, id(self), self.app)
+ return f"<{self.__class__.__name__} at {id(self)} wrapping {self.app!r}>"
def __call__(self, func, app=None):
if app is None:
@@ -327,7 +327,7 @@ class _MiddlewareFactory:
self.kw = kw
def __repr__(self):
- return "<%s at %s wrapping %r>" % (
+ return "<{} at {} wrapping {!r}>".format(
self.__class__.__name__,
id(self),
self.middleware,
diff --git a/src/webob/descriptors.py b/src/webob/descriptors.py
index e24b4f4..7230fc9 100644
--- a/src/webob/descriptors.py
+++ b/src/webob/descriptors.py
@@ -95,9 +95,7 @@ def deprecated_property(attr, name, text, version): # pragma: no cover
"""
def warn():
- warn_deprecation(
- "The attribute %s is deprecated: %s" % (name, text), version, 3
- )
+ warn_deprecation(f"The attribute {name} is deprecated: {text}", version, 3)
def fget(self):
warn()
@@ -141,7 +139,7 @@ def header_getter(header, rfc_section):
def converter(prop, parse, serialize, convert_name=None):
assert isinstance(prop, property)
- convert_name = convert_name or "``%s`` and ``%s``" % (
+ convert_name = convert_name or "``{}`` and ``{}``".format(
parse.__name__,
serialize.__name__,
)
@@ -340,5 +338,5 @@ def serialize_auth(val):
if isinstance(params, dict):
params = ", ".join(map('%s="%s"'.__mod__, params.items()))
assert isinstance(params, str)
- return "%s %s" % (authtype, params)
+ return f"{authtype} {params}"
return val
diff --git a/src/webob/etag.py b/src/webob/etag.py
index 6e91d45..e715765 100644
--- a/src/webob/etag.py
+++ b/src/webob/etag.py
@@ -141,7 +141,7 @@ class IfRange:
return bool(self.etag)
def __repr__(self):
- return "%s(%r)" % (self.__class__.__name__, self.etag)
+ return f"{self.__class__.__name__}({self.etag!r})"
def __str__(self):
return str(self.etag) if self.etag else ""
@@ -158,7 +158,7 @@ class IfRangeDate:
return last_modified and (last_modified <= self.date)
def __repr__(self):
- return "%s(%r)" % (self.__class__.__name__, self.date)
+ return f"{self.__class__.__name__}({self.date!r})"
def __str__(self):
return serialize_date(self.date)
diff --git a/src/webob/exc.py b/src/webob/exc.py
index 0893223..23652e1 100644
--- a/src/webob/exc.py
+++ b/src/webob/exc.py
@@ -279,7 +279,7 @@ ${body}"""
json_formatter=None,
**kw,
):
- Response.__init__(self, status="%s %s" % (self.code, self.title), **kw)
+ Response.__init__(self, status=f"{self.code} {self.title}", **kw)
Exception.__init__(self, detail)
if headers:
diff --git a/src/webob/multidict.py b/src/webob/multidict.py
index e11bcb7..c2680c6 100644
--- a/src/webob/multidict.py
+++ b/src/webob/multidict.py
@@ -133,7 +133,7 @@ class MultiDict(MutableMapping):
raise KeyError("Key not found: %r" % key)
if len(v) > 1:
- raise KeyError("Multiple values match %r: %r" % (key, v))
+ raise KeyError(f"Multiple values match {key!r}: {v!r}")
return v[0]
@@ -260,7 +260,7 @@ class MultiDict(MutableMapping):
def __repr__(self):
items = map("(%r, %r)".__mod__, _hide_passwd(self.items()))
- return "%s([%s])" % (self.__class__.__name__, ", ".join(items))
+ return "{}([{}])".format(self.__class__.__name__, ", ".join(items))
def __len__(self):
return len(self._items)
@@ -452,7 +452,7 @@ class NoVars:
self.reason = reason or "N/A"
def __getitem__(self, key):
- raise KeyError("No key %r: %s" % (key, self.reason))
+ raise KeyError(f"No key {key!r}: {self.reason}")
def __setitem__(self, *args, **kw):
raise KeyError("Cannot add variables: %s" % self.reason)
@@ -491,7 +491,7 @@ class NoVars:
return self
def __repr__(self):
- return "<%s: %s>" % (self.__class__.__name__, self.reason)
+ return f"<{self.__class__.__name__}: {self.reason}>"
def __len__(self):
return 0
diff --git a/src/webob/request.py b/src/webob/request.py
index bd6a293..20d7a25 100644
--- a/src/webob/request.py
+++ b/src/webob/request.py
@@ -85,7 +85,7 @@ class BaseRequest:
def __init__(self, environ, **kw):
if type(environ) is not dict:
- raise TypeError("WSGI environ must be a dict; you passed %r" % (environ,))
+ raise TypeError(f"WSGI environ must be a dict; you passed {environ!r}")
self.__dict__["environ"] = environ
@@ -99,7 +99,7 @@ class BaseRequest:
for name, value in kw.items():
if not hasattr(cls, name):
- raise TypeError("Unexpected keyword: %s=%r" % (name, value))
+ raise TypeError(f"Unexpected keyword: {name}={value!r}")
setattr(self, name, value)
def encget(self, key, default=NoDefault, encattr=None):
@@ -1187,10 +1187,10 @@ class BaseRequest:
def __repr__(self):
try:
- name = "%s %s" % (self.method, self.url)
+ name = f"{self.method} {self.url}"
except KeyError:
name = "(invalid WSGI environ)"
- msg = "<%s at 0x%x %s>" % (self.__class__.__name__, abs(id(self)), name)
+ msg = f"<{self.__class__.__name__} at 0x{abs(id(self)):x} {name}>"
return msg
@@ -1205,7 +1205,7 @@ class BaseRequest:
host = self.host_url
assert url.startswith(host)
url = url[len(host) :]
- parts = [bytes_("%s %s %s" % (self.method, url, self.http_version))]
+ parts = [bytes_(f"{self.method} {url} {self.http_version}")]
# acquire body before we handle headers so that
# content-length will be set
@@ -1222,7 +1222,7 @@ class BaseRequest:
body = self.body
for k, v in sorted(self.headers.items()):
- header = bytes_("%s: %s" % (k, v))
+ header = bytes_(f"{k}: {v}")
parts.append(header)
if body:
@@ -1499,7 +1499,7 @@ class AdhocAttrMixin:
class Request(AdhocAttrMixin, BaseRequest):
- """ The default request implementation """
+ """The default request implementation"""
def environ_from_url(path):
@@ -1616,7 +1616,7 @@ class LimitedLengthFile(io.RawIOBase):
self.remaining = maxlen
def __repr__(self):
- return "<%s(%r, maxlen=%s)>" % (self.__class__.__name__, self.file, self.maxlen)
+ return f"<{self.__class__.__name__}({self.file!r}, maxlen={self.maxlen})>"
def fileno(self):
return self.file.fileno()
@@ -1698,7 +1698,7 @@ def _encode_multipart(vars, content_type, fout=None):
if value.type_options:
for ct_name, ct_value in sorted(value.type_options.items()):
- wt('; %s="%s"' % (ct_name, ct_value))
+ wt(f'; {ct_name}="{ct_value}"')
w(CRLF)
elif mime_type:
wt("Content-type: %s" % mime_type)
diff --git a/src/webob/response.py b/src/webob/response.py
index 51d6c89..a20d68c 100644
--- a/src/webob/response.py
+++ b/src/webob/response.py
@@ -325,7 +325,7 @@ class Response:
for name, value in kw.items():
if not hasattr(self.__class__, name):
# Not a basic attribute
- raise TypeError("Unexpected keyword: %s=%r" % (name, value))
+ raise TypeError(f"Unexpected keyword: {name}={value!r}")
setattr(self, name, value)
@classmethod
@@ -352,7 +352,7 @@ class Response:
if status.startswith(_http):
(http_ver, status_num, status_text) = status.split(None, 2)
- status = "%s %s" % (text_(status_num), text_(status_text))
+ status = f"{text_(status_num)} {text_(status_text)}"
while 1:
line = fp.readline().strip()
@@ -397,7 +397,7 @@ class Response:
#
def __repr__(self):
- return "<%s at 0x%x %s>" % (self.__class__.__name__, abs(id(self)), self.status)
+ return f"<{self.__class__.__name__} at 0x{abs(id(self)):x} {self.status}>"
def __str__(self, skip_body=False):
parts = [self.status]
@@ -977,7 +977,7 @@ class Response:
for k, v in sorted(value_dict.items()):
if not _OK_PARAM_RE.search(v):
v = '"%s"' % v.replace('"', '\\"')
- params.append("; %s=%s" % (k, v))
+ params.append(f"; {k}={v}")
ct = self.headers.pop("Content-Type", "").split(";", 1)[0]
ct += "".join(params)
self.headers["Content-Type"] = ct
@@ -1550,7 +1550,7 @@ class ResponseBodyFile:
if not self.response.has_body:
return 0
- return sum([len(chunk) for chunk in self.response.app_iter])
+ return sum(len(chunk) for chunk in self.response.app_iter)
class AppIterRange:
diff --git a/src/webob/static.py b/src/webob/static.py
index 114bfd0..36631e2 100644
--- a/src/webob/static.py
+++ b/src/webob/static.py
@@ -39,7 +39,7 @@ class FileApp:
try:
stat = os.stat(self.filename)
except OSError as e:
- msg = "Can't open %r: %s" % (self.filename, e)
+ msg = f"Can't open {self.filename!r}: {e}"
return exc.HTTPNotFound(comment=msg)
try:
diff --git a/src/webob/util.py b/src/webob/util.py
index ccabe5b..d26358e 100644
--- a/src/webob/util.py
+++ b/src/webob/util.py
@@ -83,12 +83,12 @@ def header_docstring(header, rfc_section):
if header.isupper():
header = _trans_key(header)
major_section = rfc_section.split(".")[0]
- link = "http://www.w3.org/Protocols/rfc2616/rfc2616-sec%s.html#sec%s" % (
+ link = "http://www.w3.org/Protocols/rfc2616/rfc2616-sec{}.html#sec{}".format(
major_section,
rfc_section,
)
- return "Gets and sets the ``%s`` header (`HTTP spec section %s <%s>`_)." % (
+ return "Gets and sets the ``{}`` header (`HTTP spec section {} <{}>`_).".format(
header,
rfc_section,
link,
diff --git a/tests/conftest.py b/tests/conftest.py
index f8a1073..62dcdfb 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -54,7 +54,7 @@ def serve():
server = _make_test_server(app)
try:
worker = threading.Thread(target=server.serve_forever)
- worker.setDaemon(True)
+ worker.daemon = True
worker.start()
server.url = "http://localhost:%d" % server.server_port
log.debug("server started on %s", server.url)
diff --git a/tests/performance_test.py b/tests/performance_test.py
index 0d5f932..a7790ca 100644
--- a/tests/performance_test.py
+++ b/tests/performance_test.py
@@ -50,7 +50,7 @@ if __name__ == "__main__":
if os.environ.get("SHOW_OUTPUT") != "0":
print("Note you can also use:)")
- print(" %s %s open" % (sys.executable, __file__))
+ print(f" {sys.executable} {__file__} open")
print('to run ab and open a browser (or "run" to just run ab)')
print("Now do:")
print("ab -n 1000 http://localhost:8080/")
diff --git a/tests/test_compat.py b/tests/test_compat.py
index 44554e5..245117a 100644
--- a/tests/test_compat.py
+++ b/tests/test_compat.py
@@ -87,7 +87,7 @@ Larry
--JfISa01"""
env = {
"REQUEST_METHOD": "POST",
- "CONTENT_TYPE": "multipart/form-data; boundary={}".format(BOUNDARY),
+ "CONTENT_TYPE": f"multipart/form-data; boundary={BOUNDARY}",
"CONTENT_LENGTH": str(len(POSTDATA)),
}
fp = BytesIO(POSTDATA.encode("latin-1"))
@@ -124,7 +124,7 @@ ADMIN
--4ddfd368-cb07-4b9e-b003-876010298a6c--"""
env = {
"REQUEST_METHOD": "POST",
- "CONTENT_TYPE": "multipart/form-data; boundary={}".format(BOUNDARY),
+ "CONTENT_TYPE": f"multipart/form-data; boundary={BOUNDARY}",
"CONTENT_LENGTH": str(len(POSTDATA)),
}
fp = BytesIO(POSTDATA.encode("latin-1"))
@@ -167,7 +167,7 @@ Content-Disposition: form-data; name="submit"
env = {
"REQUEST_METHOD": "POST",
- "CONTENT_TYPE": "multipart/form-data; boundary={}".format(BOUNDARY),
+ "CONTENT_TYPE": f"multipart/form-data; boundary={BOUNDARY}",
"CONTENT_LENGTH": "560",
}
# Add some leading whitespace to our post data that will cause the
diff --git a/tests/test_dec.py b/tests/test_dec.py
index 1c0200f..de9c685 100644
--- a/tests/test_dec.py
+++ b/tests/test_dec.py
@@ -150,11 +150,11 @@ class DecoratorTests(unittest.TestCase):
@wsgify
def test_app(req):
- return Response("%s: %s" % (req.POST["speaker"], req.POST["words"]))
+ return Response("{}: {}".format(req.POST["speaker"], req.POST["words"]))
resp = test_app.post("/url/path", post_dict)
self.assertEqual(
- resp.body, bytes_("%s: %s" % (post_dict["speaker"], post_dict["words"]))
+ resp.body, bytes_("{}: {}".format(post_dict["speaker"], post_dict["words"]))
)
def test_wsgify_request_method(self):
diff --git a/tests/test_descriptors.py b/tests/test_descriptors.py
index a214094..24acca0 100644
--- a/tests/test_descriptors.py
+++ b/tests/test_descriptors.py
@@ -927,7 +927,7 @@ def test_serialize_auth_digest_multiple():
val = serialize_auth(("Digest", 'realm="WebOb", nonce=abcde12345, qop=foo'))
flags = val[len("Digest") :]
- result = sorted([x.strip() for x in flags.split(",")])
+ result = sorted(x.strip() for x in flags.split(","))
assert result == ["nonce=abcde12345", "qop=foo", 'realm="WebOb"']
@@ -938,7 +938,7 @@ def test_serialize_auth_digest_tuple():
("Digest", {"realm": '"WebOb"', "nonce": "abcde12345", "qop": "foo"})
)
flags = val[len("Digest") :]
- result = sorted([x.strip() for x in flags.split(",")])
+ result = sorted(x.strip() for x in flags.split(","))
assert result == ['nonce="abcde12345"', 'qop="foo"', 'realm=""WebOb""']
diff --git a/tests/test_in_wsgiref.py b/tests/test_in_wsgiref.py
index 5d3e3a9..f872776 100644
--- a/tests/test_in_wsgiref.py
+++ b/tests/test_in_wsgiref.py
@@ -71,7 +71,7 @@ def _test_app_req_interrupt(env, sr):
if cl != target_cl:
raise AssertionError(
- "request.content_length is %s instead of %s" % (cl, target_cl)
+ f"request.content_length is {cl} instead of {target_cl}"
)
op = _test_ops_req_interrupt[req.path_info]
log.info("Running test: %s", req.path_info)
diff --git a/tests/test_request.py b/tests/test_request.py
index 294a511..86fbdfb 100644
--- a/tests/test_request.py
+++ b/tests/test_request.py
@@ -3123,7 +3123,7 @@ def simpleapp(environ, start_response):
request.url,
),
"urlvars: %r\n" % request.urlvars,
- "urlargs: %r\n" % (request.urlargs,),
+ f"urlargs: {request.urlargs!r}\n",
"is_xhr: %r\n" % request.is_xhr,
"if_modified_since: %r\n" % request.if_modified_since,
"user_agent: %r\n" % request.user_agent,
diff --git a/tests/test_transcode.py b/tests/test_transcode.py
index 87a48ef..57a88fc 100644
--- a/tests/test_transcode.py
+++ b/tests/test_transcode.py
@@ -24,7 +24,7 @@ def test_transcode():
v = req.POST[req.query_string]
if hasattr(v, "filename"):
- r = Response(text_("%s\n%r" % (v.filename, v.value)))
+ r = Response(text_(f"{v.filename}\n{v.value!r}"))
else:
r = Response(v)
@@ -45,7 +45,7 @@ def test_transcode():
r = test(t2)
assert r.text == "file\n%r" % text.encode("cp1251")
r = test(t3)
- assert r.text == "%s\n%r" % (text, b"foo")
+ assert r.text == "{}\n{!r}".format(text, b"foo")
def test_transcode_query():
diff --git a/tox.ini b/tox.ini
index 3709486..8c30a6a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,7 +1,7 @@
[tox]
envlist =
lint,
- py36,py37,py38,py39,pypy3,
+ py37,py38,py39,py310,pypy3,
docs,
coverage
isolated_build = True
@@ -35,15 +35,15 @@ commands =
check-manifest
# flake8 src/webob/ tests
# build sdist/wheel
- python -m pep517.build .
+ python -m build .
twine check dist/*
deps =
black
+ build
check-manifest
flake8
flake8-bugbear
isort
- pep517
readme_renderer
twine
@@ -80,12 +80,12 @@ commands =
# Make sure we aren't forgetting anything
check-manifest
# build sdist/wheel
- python -m pep517.build .
+ python -m build .
# Verify all is well
twine check dist/*
deps =
- readme_renderer
+ build
check-manifest
- pep517
+ readme_renderer
twine