diff --git a/.travis.yml b/.travis.yml index 507dd018f..4cdef8603 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,13 +28,6 @@ matrix: - python: 3.8 env: TOXENV=mypy - - python: 2.7 - env: TOXENV=coverage-py27-tw184,codecov - - python: 2.7 - env: TOXENV=coverage-py27-tw192,codecov - - python: 2.7 - env: TOXENV=coverage-py27-twcurrent,codecov - - python: 3.5 env: TOXENV=coverage-py35-tw184,codecov - python: 3.5 @@ -63,13 +56,6 @@ matrix: - python: 3.8 env: TOXENV=coverage-py38-twcurrent,codecov - - python: pypy - env: TOXENV=coverage-pypy2-tw184,codecov - - python: pypy - env: TOXENV=coverage-pypy2-tw192,codecov - - python: pypy - env: TOXENV=coverage-pypy2-twcurrent,codecov - - python: pypy3 env: TOXENV=coverage-pypy3-tw184,codecov - python: pypy3 @@ -82,11 +68,14 @@ matrix: - 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 + - python: 3.8 + env: TOXENV=packaging + allow_failures: # Tests against Twisted trunk are allow to fail, as they are not supported. - env: TOXENV=coverage-py38-twtrunk,codecov diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 8b0947124..4f25daf9f 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -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 diff --git a/MANIFEST.in b/MANIFEST.in index 01a7f583e..37949acbc 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -7,7 +7,9 @@ include LICENSE include NEWS.rst include pyproject.toml include README.rst +include release.py include tox.ini recursive-include docs *.py recursive-include docs *.rst recursive-include docs *.txt +recursive-exclude docs/_build * diff --git a/pyproject.toml b/pyproject.toml index e7efe6aeb..85b5fb44e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,4 +7,4 @@ build-backend = "setuptools.build_meta" [tool.black] line-length = 80 -target-version = ["py27"] +target-version = ["py35"] diff --git a/release.py b/release.py index b87c01c17..5001b72ad 100644 --- a/release.py +++ b/release.py @@ -58,7 +58,7 @@ def currentVersion() -> Version: # code from its source tree. Boo. versionInfo: Dict[str, Any] = {} versonFile = Path(__file__).parent / "src" / "klein" / "_version.py" - exec (versonFile.read_text(), versionInfo) # noqa: E211 # black py2.7 + exec(versonFile.read_text(), versionInfo) # noqa: E211 # black py2.7 return versionInfo["__version__"] diff --git a/setup.py b/setup.py index 50370c5b1..26d88b97b 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,6 @@ 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', @@ -24,17 +23,16 @@ ], description="werkzeug + twisted.web", long_description=long_description, + python_requires=">=3.5", setup_requires=["incremental"], 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", ], diff --git a/src/klein/_app.py b/src/klein/_app.py index dd6010491..809de9b88 100644 --- a/src/klein/_app.py +++ b/src/klein/_app.py @@ -232,7 +232,7 @@ def branch_f(instance, request, *a, **kw): Rule( url.rstrip("/") + "/" + "", *args, - **branchKwargs + **branchKwargs, ) ) diff --git a/src/klein/_dihttp.py b/src/klein/_dihttp.py index 14fd6c418..74dfd89f3 100644 --- a/src/klein/_dihttp.py +++ b/src/klein/_dihttp.py @@ -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 @@ -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 diff --git a/src/klein/_form.py b/src/klein/_form.py index 56b434384..896165a25 100644 --- a/src/klein/_form.py +++ b/src/klein/_form.py @@ -253,7 +253,7 @@ def hidden(cls, name, value, **kw): formInputType="hidden", noLabel=True, value=value, - **kw + **kw, ).maybeNamed(name) @classmethod diff --git a/src/klein/_plating.py b/src/klein/_plating.py index 1b8a8d792..82aaeac9d 100644 --- a/src/klein/_plating.py +++ b/src/klein/_plating.py @@ -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") ), diff --git a/src/klein/_request_compat.py b/src/klein/_request_compat.py index d3ce091e5..c35f1d9af 100644 --- a/src/klein/_request_compat.py +++ b/src/klein/_request_compat.py @@ -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()) @@ -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): diff --git a/src/klein/_resource.py b/src/klein/_resource.py index 1870fb5c2..585b40b0a 100644 --- a/src/klein/_resource.py +++ b/src/klein/_resource.py @@ -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: diff --git a/src/klein/_session.py b/src/klein/_session.py index 658e132dd..f907a5c14 100644 --- a/src/klein/_session.py +++ b/src/klein/_session.py @@ -166,8 +166,8 @@ 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") ) ) @@ -175,7 +175,7 @@ def procureSession(self, request, forceInsecure=False): # 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 diff --git a/src/klein/storage/_memory.py b/src/klein/storage/_memory.py index 6f67ab945..b50502468 100644 --- a/src/klein/storage/_memory.py +++ b/src/klein/storage/_memory.py @@ -151,7 +151,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 ) ) diff --git a/src/klein/test/_strategies.py b/src/klein/test/_strategies.py index ec1aba6c2..d25306e6a 100644 --- a/src/klein/test/_strategies.py +++ b/src/klein/test/_strategies.py @@ -75,7 +75,7 @@ def idna_characters(): # pragma: no cover break result.append(unichr(i)) - _idnaCharacters = u"".join(result) + _idnaCharacters = "".join(result) return _idnaCharacters @@ -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), @@ -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 + "-"), ) ), ) @@ -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 @@ -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, diff --git a/src/klein/test/_trial.py b/src/klein/test/_trial.py index 612e25ec6..b79aee0df 100644 --- a/src/klein/test/_trial.py +++ b/src/klein/test/_trial.py @@ -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 @@ -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 """ diff --git a/src/klein/test/test_form.py b/src/klein/test/test_form.py index 7a0c40b5b..3e11cf557 100644 --- a/src/klein/test/test_form.py +++ b/src/klein/test/test_form.py @@ -85,7 +85,7 @@ def handler(self, name, value): @requirer.require( router.route("/handle-submit", methods=["POST"]), name=Field.text(), - button=Field.submit(u"OK"), + button=Field.submit("OK"), ) def handlerWithSubmit(self, name, button): # type: (str, str) -> None @@ -122,7 +122,7 @@ def constrained(self, goldilocks): @requirer.require( router.route("/render", methods=["GET"]), - form=Form.rendererFor(handler, action=u"/handle"), + form=Form.rendererFor(handler, action="/handle"), ) def renderer(self, form): # type: (IRequest, Form) -> Form @@ -130,7 +130,7 @@ def renderer(self, form): @requirer.require( router.route("/render-submit", methods=["GET"]), - form=Form.rendererFor(handlerWithSubmit, action=u"/handle-submit"), + form=Form.rendererFor(handlerWithSubmit, action="/handle-submit"), ) def submitRenderer(self, form): # type: (IRequest, RenderableForm) -> RenderableForm @@ -138,7 +138,7 @@ def submitRenderer(self, form): @requirer.require( router.route("/render-custom", methods=["GET"]), - form=Form.rendererFor(handler, action=u"/handle"), + form=Form.rendererFor(handler, action="/handle"), ) def customFormRender(self, form): # type: (RenderableForm) -> Any @@ -150,7 +150,7 @@ def customFormRender(self, form): @requirer.require( router.route("/render-cascade", methods=["GET"]), - form=Form.rendererFor(handler, action=u"/handle"), + form=Form.rendererFor(handler, action="/handle"), ) def cascadeRenderer(self, form): # type: (RenderableForm) -> RenderableForm @@ -195,7 +195,7 @@ def emptyHandler(self): @requirer.require( router.route("/render-empty", methods=["GET"]), - form=Form.rendererFor(emptyHandler, action=u"/handle-empty"), + form=Form.rendererFor(emptyHandler, action="/handle-empty"), ) def emptyRenderer(self, form): # type: (RenderableForm) -> RenderableForm @@ -235,7 +235,7 @@ def test_textConverter(self): """ Convert a string of either type to text. """ - text = u"f\xf6o" + text = "f\xf6o" for string in (text, text.encode("utf-8")): result = textConverter(string) # type: ignore[type-var] self.assertIsInstance(result, Text) @@ -264,7 +264,7 @@ def test_handling(self): ) self.assertEqual(response.code, 200) self.assertEqual(self.successResultOf(content(response)), b"yay") - self.assertEqual(to.calls, [(u"hello", 1234)]) + self.assertEqual(to.calls, [("hello", 1234)]) def test_handlingPassword(self): # type: () -> None @@ -291,7 +291,7 @@ def test_handlingPassword(self): self.assertEqual( self.successResultOf(content(response)), b"password received" ) - self.assertEqual(to.calls, [(u"password", u"asdfjkl;")]) + self.assertEqual(to.calls, [("password", "asdfjkl;")]) def test_numberConstraints(self): # type: () -> None @@ -333,7 +333,7 @@ def test_numberConstraints(self): self.assertEqual(tooLow.code, 400) self.assertEqual(justRight.code, 200) self.assertEqual(self.successResultOf(content(justRight)), b"got it") - self.assertEqual(to.calls, [(u"constrained", 7)]) + self.assertEqual(to.calls, [("constrained", 7)]) def test_missingRequiredParameter(self): # type: () -> None @@ -406,7 +406,7 @@ def test_handlingGET(self): ) self.assertEqual(response.code, 200) self.assertEqual(self.successResultOf(content(response)), b"got") - self.assertEqual(calls, [(u"hello, big world", 4321)]) + self.assertEqual(calls, [("hello, big world", 4321)]) def test_validatingParameters(self): # type: () -> None @@ -465,12 +465,9 @@ def test_customParameterValidation(self): self.assertEqual(len(errors), 1) errorText = cast(str, errors[0].text) self.assertIsNot(errorText, None) - self.assertTrue( - errorText.startswith("invalid literal for int() with base 10: ") + self.assertEqual( + errorText, "invalid literal for int() with base 10: 'not a number'", ) - # Peculiar 2-step assert because pypy2 (invalidly) sticks a 'u' in - # there. - self.assertTrue(errorText.endswith("'not a number'")) def test_handlingJSON(self): # type: () -> None @@ -490,12 +487,12 @@ def test_handlingJSON(self): stub.post( "https://localhost/handle", json=dict(name="hello", value="1234", ignoreme="extraneous"), - headers={u"X-Test-Session": session.identifier}, + headers={"X-Test-Session": session.identifier}, ) ) self.assertEqual(response.code, 200) self.assertEqual(self.successResultOf(content(response)), b"yay") - self.assertEqual(to.calls, [(u"hello", 1234)]) + self.assertEqual(to.calls, [("hello", 1234)]) def test_missingOptionalParameterJSON(self): # type: () -> None @@ -529,7 +526,7 @@ def test_missingOptionalParameterJSON(self): self.assertEqual(response2.code, 200) self.assertEqual(self.successResultOf(content(response)), b"okay") self.assertEqual(self.successResultOf(content(response2)), b"okay") - self.assertEqual(to.calls, [(u"one", 7.0), (u"two", 2.0)]) + self.assertEqual(to.calls, [("one", 7.0), ("two", 2.0)]) def test_rendering(self): # type: () -> None diff --git a/src/klein/test/test_headers.py b/src/klein/test/test_headers.py index df3fb11ae..bd5943930 100644 --- a/src/klein/test/test_headers.py +++ b/src/klein/test/test_headers.py @@ -428,7 +428,7 @@ def test_removeTextName(self): """ rawHeaders = ((b"a", b"1"), (b"b", b"2a"), (b"c", b"3"), (b"b", b"2b")) headers = self.headers(rawHeaders=rawHeaders) - headers.remove(name=u"b") + headers.remove(name="b") self.assertRawHeadersEqual( headers.rawHeaders, ((b"a", b"1"), (b"c", b"3")) @@ -471,7 +471,7 @@ def test_addValueTextName(self): """ rawHeaders = ((b"a", b"1"), (b"b", b"2a")) headers = self.headers(rawHeaders=rawHeaders) - headers.addValue(name=u"b", value=u"2b") + headers.addValue(name="b", value="2b") self.assertRawHeadersEqual( headers.rawHeaders, ((b"a", b"1"), (b"b", b"2a"), (b"b", b"2b")) @@ -486,7 +486,7 @@ def test_addValueBytesNameTextValue(self): headers = self.headers(rawHeaders=()) e = cast(TestCase, self).assertRaises( - TypeError, headers.addValue, b"a", u"1" + TypeError, headers.addValue, b"a", "1" ) cast(TestCase, self).assertRegex( str(e), "value u?'1' must be bytes to match name b?'a'" @@ -501,7 +501,7 @@ def test_addValueTextNameBytesValue(self): headers = self.headers(rawHeaders=()) e = cast(TestCase, self).assertRaises( - TypeError, headers.addValue, u"a", b"1" + TypeError, headers.addValue, "a", b"1" ) cast(TestCase, self).assertRegex( str(e), "value b?'1' must be text to match name u?'a'" diff --git a/src/klein/test/test_request.py b/src/klein/test/test_request.py index b26116ac0..922f4af8d 100644 --- a/src/klein/test/test_request.py +++ b/src/klein/test/test_request.py @@ -26,8 +26,8 @@ class FrozenHTTPRequestTests(FrozenHTTPMessageTestsMixIn, TestCase): def requestFromBytes(data=b""): # type: (bytes) -> FrozenHTTPRequest return FrozenHTTPRequest( - method=u"GET", - uri=DecodedURL.fromText(u"https://twistedmatrix.com/"), + method="GET", + uri=DecodedURL.fromText("https://twistedmatrix.com/"), headers=FrozenHTTPHeaders(rawHeaders=()), body=data, ) @@ -54,8 +54,8 @@ def test_initInvalidBodyType(self): e = self.assertRaises( TypeError, FrozenHTTPRequest, - method=u"GET", - uri=DecodedURL.fromText(u"https://twistedmatrix.com/"), + method="GET", + uri=DecodedURL.fromText("https://twistedmatrix.com/"), headers=FrozenHTTPHeaders(rawHeaders=()), body=object(), ) diff --git a/src/klein/test/test_request_compat.py b/src/klein/test/test_request_compat.py index ae35d48b0..4cae71efb 100644 --- a/src/klein/test/test_request_compat.py +++ b/src/klein/test/test_request_compat.py @@ -85,12 +85,10 @@ def test_uri(self, url): assert uri.port is not None # Tells mypy it's not an Optional path = ( - uri.replace(scheme=u"", host=u"", port=None) - .asText() - .encode("ascii") + uri.replace(scheme="", host="", port=None).asText().encode("ascii") ) legacyRequest = self.legacyRequest( - isSecure=(uri.scheme == u"https"), + isSecure=(uri.scheme == "https"), host=uri.host.encode("ascii"), port=uri.port, path=path, @@ -104,14 +102,14 @@ def test_uri(self, url): def strURL(url): # type: (EncodedURL) -> Text return ( - u"URL(scheme={url.scheme!r}, " - u"userinfo={url.userinfo!r}, " - u"host={url.host!r}, " - u"port={url.port!r}, " - u"path={url.path!r}, " - u"query={url.query!r}, " - u"fragment={url.fragment!r}, " - u"rooted={url.rooted})" + "URL(scheme={url.scheme!r}, " + "userinfo={url.userinfo!r}, " + "host={url.host!r}, " + "port={url.port!r}, " + "path={url.path!r}, " + "query={url.query!r}, " + "fragment={url.fragment!r}, " + "rooted={url.rooted})" ).format(url=url) self.assertEqual( diff --git a/src/klein/test/test_requirer.py b/src/klein/test/test_requirer.py index e693dc532..e0d2d0d5b 100644 --- a/src/klein/test/test_requirer.py +++ b/src/klein/test/test_requirer.py @@ -41,7 +41,7 @@ def requiresURL(url): """ This is a route that requires a URL. """ - return url.child(u"hello/ world").asText() + return url.child("hello/ world").asText() class ISample(Interface): diff --git a/src/klein/test/test_resource.py b/src/klein/test/test_resource.py index d90dba50b..7e90823db 100644 --- a/src/klein/test/test_resource.py +++ b/src/klein/test/test_resource.py @@ -519,7 +519,7 @@ def test_renderUnicode(self): @app.route("/snowman") def snowman(request): - return u"\u2603" + return "\u2603" d = _render(self.kr, request) @@ -1070,8 +1070,8 @@ def root(request): self.assertFired(d) def test_ensure_utf8_bytes(self): - self.assertEqual(ensure_utf8_bytes(u"abc"), b"abc") - self.assertEqual(ensure_utf8_bytes(u"\u2202"), b"\xe2\x88\x82") + self.assertEqual(ensure_utf8_bytes("abc"), b"abc") + self.assertEqual(ensure_utf8_bytes("\u2202"), b"\xe2\x88\x82") self.assertEqual(ensure_utf8_bytes(b"\xe2\x88\x82"), b"\xe2\x88\x82") def test_decodesPath(self): @@ -1098,16 +1098,7 @@ def test_failedDecodePathInfo(self): self.assertEqual(b"Non-UTF-8 encoding in URL.", rv) self.assertEqual(1, len(self.flushLoggedErrors(UnicodeDecodeError))) - def test_urlDecodeErrorReprPy2(self): - """ - URLDecodeError.__repr__ formats properly. - """ - self.assertEqual( - ")>", - repr(_URLDecodeError(ValueError)), - ) - - def test_urlDecodeErrorReprPy3(self): + def test_urlDecodeErrorRepr(self): """ URLDecodeError.__repr__ formats properly. """ @@ -1116,11 +1107,6 @@ def test_urlDecodeErrorReprPy3(self): repr(_URLDecodeError(ValueError)), ) - if _PY3: - test_urlDecodeErrorReprPy2.skip = "Only works on Py2" # type: ignore - else: - test_urlDecodeErrorReprPy3.skip = "Only works on Py3" # type: ignore - def test_subroutedBranch(self): subapp = Klein() @@ -1247,7 +1233,7 @@ def test_afUnixSocket(self): """ request = requestMock(b"/f\xc3\xb6\xc3\xb6") server_mock = Mock(Server) - server_mock.getRequestHostname = u"/var/run/twisted.socket" + server_mock.getRequestHostname = "/var/run/twisted.socket" request.host = server_mock ( url_scheme, diff --git a/src/klein/test/test_session.py b/src/klein/test/test_session.py index 0b574b68f..76b12b5fc 100644 --- a/src/klein/test/test_session.py +++ b/src/klein/test/test_session.py @@ -210,7 +210,7 @@ def test_unknownSessionHeader(self): sessions, exceptions, token, cookie, treq = simpleSessionRouter() response = self.successResultOf( - treq.get("https://unittest.example.com/", headers={token: u"bad"}) + treq.get("https://unittest.example.com/", headers={token: "bad"}) ) self.assertEqual(response.code, 200) self.assertEqual(len(sessions), 0) diff --git a/tox.ini b/tox.ini index f8474efc3..323df0feb 100644 --- a/tox.ini +++ b/tox.ini @@ -2,7 +2,7 @@ envlist = flake8, black, mypy - coverage-py{27,35,36,37,38,py2,py3}-tw{184,192,current,trunk} + coverage-py{35,36,37,38,39,py3}-tw{184,192,current,trunk} coverage_report docs, docs-linkcheck packaging @@ -44,8 +44,8 @@ deps = zope.interface==5.1.0 {test,coverage}: treq==20.3.0 - {test,coverage}-py{27,py2}: hypothesis==4.57.1 - {test,coverage}-py{35,36,37,38,py3}: hypothesis==5.8.6 + {test,coverage}-py{27,py2}: hypothesis==4.57.1 # rq.filter: <5 + {test,coverage}-py{35,36,37,38,39,py3}: hypothesis==5.8.6 {test,coverage}: idna==2.8 {test,coverage}-py{27,py2}: mock==3.0.5 # rq.filter: <4 {test,coverage}-py{27,py2}: typing==3.7.4.1 @@ -67,14 +67,12 @@ setenv = description = run tests basepython = - py27: python2.7 py35: python3.5 py36: python3.6 py37: python3.7 py38: python3.8 py39: python3.9 - pypy2: pypy pypy3: pypy3 deps = {[default]deps} @@ -385,7 +383,9 @@ ignore_missing_imports = True description = generate coverage report -depends = {test,coverage}-py{27,35,36,37,38,py2,py3}-tw{171,184,current,trunk} +depends = + coverage-py{35,36,37,38,39,py3}-tw{1,2}{0,1,2,3,4,5,6,7,8,9}{0,1,2,3,4,5,6,7,8,9} + coverage-py{35,36,37,38,39,py3}-tw{current,trunk} basepython = {[default]basepython}