Skip to content

Commit

Permalink
Drop support for Python 2.7, 3.5, 3.6. (#100)
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Howitz committed Dec 20, 2022
1 parent 9cb3064 commit f334cde
Show file tree
Hide file tree
Showing 14 changed files with 44 additions and 133 deletions.
4 changes: 0 additions & 4 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,11 @@ jobs:
config:
# [Python version, tox env]
- ["3.9", "lint"]
- ["2.7", "py27"]
- ["3.5", "py35"]
- ["3.6", "py36"]
- ["3.7", "py37"]
- ["3.8", "py38"]
- ["3.9", "py39"]
- ["3.10", "py310"]
- ["3.11", "py311"]
- ["pypy-2.7", "pypy"]
- ["pypy-3.7", "pypy3"]
- ["3.9", "docs"]
- ["3.9", "coverage"]
Expand Down
9 changes: 1 addition & 8 deletions .meta.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# https://github.com/zopefoundation/meta/tree/master/config/pure-python
[meta]
template = "pure-python"
commit-id = "200573eb414d2228d463da3de7d71a6d6335a704"
commit-id = "1ef8b9b8"

[python]
with-windows = false
Expand Down Expand Up @@ -30,10 +30,3 @@ additional-rules = [
"recursive-include src *.txt",
"prune docs/_build",
]

[flake8]
additional-config = [
"# F401 imported but unused",
"per-file-ignores =",
" src/zope/testbrowser/_compat.py: F401",
]
4 changes: 3 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
CHANGES
=======

5.7 (unreleased)
6.0 (unreleased)
----------------

- Drop support for Python 2.7, 3.5, 3.6.

- Add support for Python 3.11.

- Do not break in ``mechRepr`` when using ``<input type="date">``.
Expand Down
3 changes: 0 additions & 3 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ universal = 1

[flake8]
doctests = 1
# F401 imported but unused
per-file-ignores =
src/zope/testbrowser/_compat.py: F401

[check-manifest]
ignore =
Expand Down
7 changes: 1 addition & 6 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

setup(
name='zope.testbrowser',
version='5.7.dev0',
version='6.0.dev0',
url='https://github.com/zopefoundation/zope.testbrowser',
license='ZPL 2.1',
project_urls={
Expand All @@ -43,11 +43,7 @@
'Environment :: Web Environment',
'Intended Audience :: Developers',
'License :: OSI Approved :: Zope Public License',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
Expand All @@ -74,7 +70,6 @@
'BeautifulSoup4',
'SoupSieve >= 1.9.0',
'WSGIProxy2',
'six',
],
extras_require={
'docs': [
Expand Down
48 changes: 0 additions & 48 deletions src/zope/testbrowser/_compat.py

This file was deleted.

51 changes: 19 additions & 32 deletions src/zope/testbrowser/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@
"""Webtest-based Functional Doctest interfaces
"""

import http.client
import io
import re
import time
import urllib.parse
import urllib.request
import urllib.robotparser
from contextlib import contextmanager

from six import string_types
from six.moves import urllib_robotparser

import webtest
from bs4 import BeautifulSoup
from soupsieve import escape as css_escape
Expand All @@ -31,15 +32,11 @@

import zope.testbrowser.cookies
from zope.testbrowser import interfaces
from zope.testbrowser._compat import PYTHON2
from zope.testbrowser._compat import httpclient
from zope.testbrowser._compat import urllib_request
from zope.testbrowser._compat import urlparse


__docformat__ = "reStructuredText"

HTTPError = urllib_request.HTTPError
HTTPError = urllib.request.HTTPError
RegexType = type(re.compile(''))
_compress_re = re.compile(r"\s+")

Expand Down Expand Up @@ -67,7 +64,7 @@ class TestbrowserApp(webtest.TestApp):
restricted = False

def _assertAllowed(self, url):
parsed = urlparse.urlparse(url)
parsed = urllib.parse.urlparse(url)
if self.restricted:
# We are in restricted mode, check host part only
host = parsed.netloc.partition(':')[0]
Expand All @@ -80,9 +77,9 @@ def _assertAllowed(self, url):
raise HostNotAllowed(url)
else:
# Unrestricted mode: retrieve robots.txt and check against it
robotsurl = urlparse.urlunsplit((parsed.scheme, parsed.netloc,
'/robots.txt', '', ''))
rp = urllib_robotparser.RobotFileParser()
robotsurl = urllib.parse.urlunsplit(
(parsed.scheme, parsed.netloc, '/robots.txt', '', ''))
rp = urllib.robotparser.RobotFileParser()
rp.set_url(robotsurl)
rp.read()
if not rp.can_fetch("*", url):
Expand All @@ -105,7 +102,7 @@ def _remove_fragment(self, url):
# assuming it is called on every request and therefore _last_fragment
# will not get outdated. ``getRequestUrlWithFragment()`` will
# reconstruct url with fragment for the last request.
scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
scheme, netloc, path, query, fragment = urllib.parse.urlsplit(url)
self._last_fragment = fragment
return super(TestbrowserApp, self)._remove_fragment(url)

Expand Down Expand Up @@ -225,10 +222,7 @@ def headers(self):

inp = '\n'.join(resptxt)
stream = io.BytesIO(inp.encode('latin1'))
if PYTHON2:
return httpclient.HTTPMessage(stream)
else:
return httpclient.parse_headers(stream)
return http.client.parse_headers(stream)

@property
def cookies(self):
Expand Down Expand Up @@ -287,7 +281,7 @@ def _processRequest(self, url, make_request):
while resp.status_int in REDIRECTS and remaining_redirects:
remaining_redirects -= 1
self._req_referrer = url
url = urlparse.urljoin(url, resp.headers['location'])
url = urllib.parse.urljoin(url, resp.headers['location'])
with self._preparedRequest(url) as reqargs:
resp = self.testapp.get(url, **reqargs)
assert remaining_redirects > 0, (
Expand Down Expand Up @@ -315,8 +309,8 @@ def _submit(self, form, name=None, index=None, coord=None, **args):
if form.method.upper() != "GET":
args.setdefault("content_type", form.enctype)
else:
parsed = urlparse.urlparse(url)._replace(query='', fragment='')
url = urlparse.urlunparse(parsed)
parsed = urllib.parse.urlparse(url)._replace(query='', fragment='')
url = urllib.parse.urlunparse(parsed)
return form.response.goto(url, method=form.method,
params=fields, **args)

Expand Down Expand Up @@ -501,7 +495,7 @@ def _preparedRequest(self, url):
headers['Content-Type'] = self._req_content_type

headers['Connection'] = 'close'
headers['Host'] = urlparse.urlparse(url).netloc
headers['Host'] = urllib.parse.urlparse(url).netloc
headers['User-Agent'] = 'Python-urllib/2.4'

headers.update(self._req_headers)
Expand Down Expand Up @@ -534,7 +528,7 @@ def _absoluteUrl(self, url):
raise BrowserStateError(
"can't fetch relative reference: not viewing any document")

return str(urlparse.urljoin(self._getBaseUrl(), url))
return str(urllib.parse.urljoin(self._getBaseUrl(), url))

def toStr(self, s):
"""Convert possibly unicode object to native string using response
Expand All @@ -549,9 +543,7 @@ def toStr(self, s):
if isinstance(s, tuple):
return tuple(subs)
return subs
if PYTHON2 and not isinstance(s, bytes):
return s.encode(self._response.charset)
if not PYTHON2 and isinstance(s, bytes):
if isinstance(s, bytes):
return s.decode(self._response.charset)
return s

Expand Down Expand Up @@ -877,7 +869,7 @@ def displayValue(self, value):
if self._browser_counter != self.browser._counter:
raise interfaces.ExpiredError

if isinstance(value, string_types):
if isinstance(value, str):
value = [value]
if not self.multiple and len(value) > 1:
raise ItemCountError(
Expand Down Expand Up @@ -1432,12 +1424,7 @@ class Timer(object):
end_time = 0

def _getTime(self):
if hasattr(time, 'perf_counter'):
# Python 3
return time.perf_counter()
else:
# Python 2
return time.time()
return time.perf_counter()

def start(self):
"""Begin a timing period"""
Expand Down
24 changes: 11 additions & 13 deletions src/zope/testbrowser/cookies.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,18 @@
##############################################################################

import datetime
import http.cookies
import time

import six
import urllib.parse
import urllib.request
from collections.abc import MutableMapping
from urllib.parse import quote as url_quote

import pytz
import zope.interface

from zope.testbrowser import interfaces
from zope.testbrowser import utils
from zope.testbrowser._compat import MutableMapping
from zope.testbrowser._compat import httpcookies
from zope.testbrowser._compat import url_quote
from zope.testbrowser._compat import urllib_request
from zope.testbrowser._compat import urlparse


# Cookies class helpers
Expand Down Expand Up @@ -117,7 +115,7 @@ def url(self):

@property
def _request(self):
return urllib_request.Request(self._url)
return urllib.request.Request(self._url)

@property
def header(self):
Expand Down Expand Up @@ -297,7 +295,7 @@ def _verifyDomain(self, domain, ck):
'cookie for this url (%s)' % (self.url,))

def _verifyPath(self, path, ck):
self_path = urlparse.urlparse(self.url)[2]
self_path = urllib.parse.urlparse(self.url)[2]
if not self_path.startswith(path):
raise ValueError('current url must start with path, if given')
if ck is not None and ck.path != path and ck.path.startswith(path):
Expand All @@ -322,17 +320,17 @@ def _setCookie(self, name, value, domain, expires, path, secure, comment,
else:
protocol = 'http'
url = '%s://%s%s' % (protocol, tmp_domain, path or '/')
request = urllib_request.Request(url)
request = urllib.request.Request(url)
else:
request = self._request
if request is None:
# TODO: fix exception
raise BrowserStateError(
'cannot create cookie without request or domain')
c = httpcookies.SimpleCookie()
c = http.cookies.SimpleCookie()
name = str(name)
# Cookie value must be native string
c[name] = value.encode('utf8') if not six.PY3 else value
c[name] = value
if secure:
c[name]['secure'] = True
if domain:
Expand Down Expand Up @@ -381,7 +379,7 @@ def _is_expired(self, value, now): # now = int(time.time())
return True
elif value <= dnow:
return True
elif isinstance(value, six.string_types):
elif isinstance(value, str):
if datetime.datetime.fromtimestamp(
utils.http2time(value), pytz.UTC) <= dnow:
return True
Expand Down
5 changes: 2 additions & 3 deletions src/zope/testbrowser/ftests/wsgitestapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,14 @@
##############################################################################
"""A minimal WSGI application used as a test fixture."""

import html
import mimetypes
import os
from datetime import datetime

from webob import Request
from webob import Response

from zope.testbrowser._compat import html_escape


class NotFound(Exception):
pass
Expand Down Expand Up @@ -79,7 +78,7 @@ def __init__(self, params):

def __getitem__(self, key):
if key in self.params:
return html_escape(self.params[key])
return html.escape(self.params[key])
return ''


Expand Down
3 changes: 0 additions & 3 deletions src/zope/testbrowser/tests/test_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -1259,9 +1259,6 @@ def test_non_ascii_in_input_field(self):
>>> browser.open('http://localhost/')
>>> non_ascii = browser.getControl(name='text').value
>>> from .. import _compat
>>> if _compat.PYTHON2:
... non_ascii = non_ascii.decode('utf-8')
>>> non_ascii == UNICODE_TEST
True
Expand Down

0 comments on commit f334cde

Please sign in to comment.