Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drop support for Python 2.7 #328

Merged
merged 16 commits into from
Jun 8, 2020
21 changes: 2 additions & 19 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,6 @@ matrix:
- python: 3.8
env: TOXENV=mypy

- python: 2.7
env: TOXENV=coverage-py27-tw171,codecov
- python: 2.7
env: TOXENV=coverage-py27-tw184,codecov
- python: 2.7
env: TOXENV=coverage-py27-twcurrent,codecov

- python: 3.5
env: TOXENV=coverage-py35-tw171,codecov
- python: 3.5
Expand Down Expand Up @@ -63,13 +56,6 @@ matrix:
- python: 3.8
env: TOXENV=coverage-py38-twcurrent,codecov

- python: pypy
env: TOXENV=coverage-pypy2-tw171,codecov
- python: pypy
env: TOXENV=coverage-pypy2-tw184,codecov
- python: pypy
env: TOXENV=coverage-pypy2-twcurrent,codecov

- python: pypy3
env: TOXENV=coverage-pypy3-tw171,codecov
- python: pypy3
Expand All @@ -79,19 +65,16 @@ matrix:

# Test against Twisted trunk in case something in development breaks us.
# This is allowed to fail below, since the bug may be in Twisted.
- python: 2.7
env: TOXENV=coverage-py27-twtrunk,codecov
- python: 3.8
env: TOXENV=coverage-py38-twtrunk,codecov

- python: 2.7
- python: 3.8
env: TOXENV=docs
- python: 2.7
- python: 3.8
env: TOXENV=docs-linkcheck

allow_failures:
# Tests against Twisted trunk are allow to fail, as they are not supported.
- env: TOXENV=coverage-py27-twtrunk,codecov
- env: TOXENV=coverage-py38-twtrunk,codecov

# This depends on external web sites, so it's allowed to fail.
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ Klein is an open source project that welcomes contributions of all kinds coming
Getting started
===============

Here is a list of shell commands that will install the dependencies of Klein, run the test suite with Python 2.7 and the current version of Twisted, compile the documentation, and check for coding style issues with flake8.
Here is a list of shell commands that will install the dependencies of Klein, run the test suite with Python 3.8 and the current version of Twisted, compile the documentation, and check for coding style issues with flake8.

.. code-block:: shell

pip install --user tox
tox -e py27-twcurrent
tox -e py38-twcurrent
tox -e docs
tox -e flake8

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ build-backend = "setuptools.build_meta"
[tool.black]

line-length = 80
target-version = ["py27"]
target-version = ["py35"]
4 changes: 1 addition & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.5',
wsanchez marked this conversation as resolved.
Show resolved Hide resolved
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
Expand All @@ -27,13 +27,11 @@
use_incremental=True,
install_requires=[
"attrs",
'enum34 ; python_version<"3.4"',
"hyperlink",
"incremental",
"six",
"Tubes",
"Twisted>=15.5", # 15.5 is the first version to support Py3.3+
'typing ; python_version<"3.5"',
"Werkzeug",
"zope.interface",
],
Expand Down
2 changes: 1 addition & 1 deletion src/klein/_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ def branch_f(instance, request, *a, **kw):
Rule(
url.rstrip("/") + "/" + "<path:__rest__>",
*args,
**branchKwargs
**branchKwargs,
)
)

Expand Down
6 changes: 2 additions & 4 deletions src/klein/_dihttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ def urlFromRequest(request):

url = DecodedURL.fromText(request.uri.decode("charmap"))
url = url.replace(
scheme=u"https" if request.isSecure() else u"http",
host=host,
port=port,
scheme="https" if request.isSecure() else "http", host=host, port=port,
)
return url

Expand Down Expand Up @@ -124,7 +122,7 @@ class Response(object):
],
default=attr.Factory(dict),
)
body = attr.ib(type=Any, default=u"")
body = attr.ib(type=Any, default="")

def _applyToRequest(self, request):
# type: (IRequest) -> Any
Expand Down
2 changes: 1 addition & 1 deletion src/klein/_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ def hidden(cls, name, value, **kw):
formInputType="hidden",
noLabel=True,
value=value,
**kw
**kw,
).maybeNamed(name)

@classmethod
Expand Down
6 changes: 3 additions & 3 deletions src/klein/_plating.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,17 +245,17 @@ def mymethod(instance, request, *args, **kw):
json_data.update(data)
for ignored in self._presentationSlots:
json_data.pop(ignored, None)
text_type = u"json"
text_type = "json"
ready = yield resolveDeferredObjects(json_data)
result = dumps(ready)
else:
data[self.CONTENT] = loader.load()
text_type = u"html"
text_type = "html"
result = self._elementify(instance, data)
request.setHeader(
b"content-type",
(
u"text/{format}; charset=utf-8".format(
"text/{format}; charset=utf-8".format(
format=text_type
).encode("charmap")
),
Expand Down
10 changes: 5 additions & 5 deletions src/klein/_request_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ def uri(self):
# This code borrows from t.w.server.Request._prePathURL.

if request.isSecure():
scheme = u"https"
scheme = "https"
else:
scheme = u"http"
scheme = "http"

netloc = nativeString(request.getRequestHostname())

Expand All @@ -74,13 +74,13 @@ def uri(self):
else:
default = 80
if port != default:
netloc += u":{}".format(port)
netloc += ":{}".format(port)

path = nativeString(request.uri)
if path and path[0] == u"/":
if path and path[0] == "/":
path = path[1:]

return DecodedURL.fromText(u"{}://{}/{}".format(scheme, netloc, path))
return DecodedURL.fromText("{}://{}/{}".format(scheme, netloc, path))

@property
def headers(self):
Expand Down
2 changes: 1 addition & 1 deletion src/klein/_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def _extractURLparts(request):
if not path_info.startswith(b"/"):
path_info = b"/" + path_info

url_scheme = u"https" if request.isSecure() else u"http"
url_scheme = "https" if request.isSecure() else "http"

utf8Failures = []
try:
Expand Down
6 changes: 3 additions & 3 deletions src/klein/_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,16 +166,16 @@ def procureSession(self, request, forceInsecure=False):
# manipulation could succeed (no CSRF token check could
# ever succeed, for example).
raise NoSuchSession(
u"Can't initialize a session on a "
u"{method} request.".format(
"Can't initialize a session on a "
"{method} request.".format(
method=request.method.decode("ascii")
)
)
if not self._setCookieOnGET:
# We don't have a session ID at all, and we're not allowed
# by policy to set a cookie on the client.
raise NoSuchSession(
u"Cannot auto-initialize a session for this request."
"Cannot auto-initialize a session for this request."
)
session = yield self._store.newSession(sentSecurely, mechanism)
identifierInCookie = session.identifier
Expand Down
2 changes: 1 addition & 1 deletion src/klein/storage/_memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ def loadSession(self, identifier, isConfidential, authenticatedBy):
else:
return fail(
NoSuchSession(
u"Session not found in memory store {id!r}".format(
"Session not found in memory store {id!r}".format(
id=identifier
)
)
Expand Down
10 changes: 5 additions & 5 deletions src/klein/test/_strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def idna_characters(): # pragma: no cover
break
result.append(unichr(i))

_idnaCharacters = u"".join(result)
_idnaCharacters = "".join(result)

return _idnaCharacters

Expand Down Expand Up @@ -115,7 +115,7 @@ def latin1_text(draw, min_size=0, max_size=None):
@param max_size: The maximum number of characters in the text.
Use C{None} for an unbounded size.
"""
return u"".join(
return "".join(
draw(
lists(
characters(max_codepoint=255),
Expand Down Expand Up @@ -195,7 +195,7 @@ def hostname_labels(draw, allow_idn=True): # pragma: no cover
text(
min_size=1,
max_size=63,
alphabet=unicode(ascii_letters + digits + u"-"),
alphabet=unicode(ascii_letters + digits + "-"),
)
),
)
Expand Down Expand Up @@ -234,7 +234,7 @@ def hostnames(
),
)

name = u".".join(labels)
name = ".".join(labels)

# Filter names that are not IDNA-encodable.
# We try pretty hard not to generate bogus names in the first place... but
Expand Down Expand Up @@ -306,7 +306,7 @@ def encoded_urls(draw): # pragma: no cover
port = None

return EncodedURL(
scheme=cast(Text, draw(sampled_from((u"http", u"https")))),
scheme=cast(Text, draw(sampled_from(("http", "https")))),
host=host,
port=port,
path=path,
Expand Down
23 changes: 0 additions & 23 deletions src/klein/test/_trial.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@
Extensions to L{twisted.trial}.
"""

import sys
from typing import Any

from twisted import version as twistedVersion
from twisted.trial.unittest import SynchronousTestCase

from zope.interface import Interface
Expand All @@ -23,27 +21,6 @@ class TestCase(SynchronousTestCase):
Extensions to L{SynchronousTestCase}.
"""

if (twistedVersion.major, twistedVersion.minor) < (16, 4):

def assertRegex(self, text, regex, msg=None):
# type: (str, Any, str) -> None
"""
Fail the test if a C{regexp} search of C{text} fails.

@param text: Text which is under test.

@param regex: A regular expression object or a string containing a
regular expression suitable for use by re.search().

@param msg: Text used as the error message on failure.
"""
if sys.version_info[:2] > (2, 7):
super(TestCase, self).assertRegex(text, regex, msg)
else:
# Python 2.7 has unittest.assertRegexpMatches() which was
# renamed to unittest.assertRegex() in Python 3.2
super(TestCase, self).assertRegexpMatches(text, regex, msg)

def assertProvides(self, interface, obj):
# type: (Interface, Any) -> None
"""
Expand Down