Hello community,

here is the log from the commit of package python-cheroot for openSUSE:Factory 
checked in at 2019-11-04 17:08:30
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-cheroot (Old)
 and      /work/SRC/openSUSE:Factory/.python-cheroot.new.2990 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-cheroot"

Mon Nov  4 17:08:30 2019 rev:9 rq:742156 version:8.2.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-cheroot/python-cheroot.changes    
2019-10-09 15:17:45.416761145 +0200
+++ /work/SRC/openSUSE:Factory/.python-cheroot.new.2990/python-cheroot.changes  
2019-11-04 17:08:31.476396904 +0100
@@ -1,0 +2,11 @@
+Wed Oct 23 13:38:06 UTC 2019 - Marketa Calabkova <mcalabk...@suse.com>
+
+- Update to 8.2.1
+  * Deprecated use of negative timeouts as alias for infinite timeouts in 
ThreadPool.stop.
+  * For OPTION requests, bypass URI as path if it does not appear absolute.
+  * Workers are now request-based, addressing the long-standing issue with 
keep-alive connections
+  * Remove custom setup.cfg parser handling, allowing the project (including 
sdist) 
+    to build/run on setuptools 41.4. Now building cheroot requires setuptools 
30.3 or later 
+    (for declarative config support) and preferably 34.4 or later (as 
indicated in pyproject.toml).
+
+-------------------------------------------------------------------

Old:
----
  cheroot-7.0.0.tar.gz

New:
----
  cheroot-8.2.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-cheroot.spec ++++++
--- /var/tmp/diff_new_pack.9frw3c/_old  2019-11-04 17:08:32.068397536 +0100
+++ /var/tmp/diff_new_pack.9frw3c/_new  2019-11-04 17:08:32.068397536 +0100
@@ -19,7 +19,7 @@
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 %define pypi_name cheroot
 Name:           python-%{pypi_name}
-Version:        7.0.0
+Version:        8.2.1
 Release:        0
 Summary:        Pure-python HTTP server
 License:        BSD-3-Clause
@@ -32,9 +32,9 @@
 BuildRequires:  %{python_module pytest-mock >= 1.10.4}
 BuildRequires:  %{python_module requests-unixsocket}
 BuildRequires:  %{python_module requests}
+BuildRequires:  %{python_module setuptools >= 34.4}
 BuildRequires:  %{python_module setuptools_scm >= 1.15.0}
 BuildRequires:  %{python_module setuptools_scm_git_archive >= 1.0}
-BuildRequires:  %{python_module setuptools}
 BuildRequires:  %{python_module six >= 1.11.0}
 BuildRequires:  %{python_module trustme}
 BuildRequires:  fdupes
@@ -63,10 +63,6 @@
 %autosetup -n cheroot-%{version} -p1
 # do not require cov/xdist/etc
 sed -i -e '/addopts/d' pytest.ini
-for file in "%{pypi_name}.egg-info/requires.txt" "setup.cfg"; do
-    sed -i "s/backports.functools_lru_cache$/backports.functools_lru_cache ; 
python_version < '3.3'/" \
-        "${file}"
-done
 
 %build
 %python_build

++++++ cheroot-7.0.0.tar.gz -> cheroot-8.2.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cheroot-7.0.0/.circleci/config.yml 
new/cheroot-8.2.1/.circleci/config.yml
--- old/cheroot-7.0.0/.circleci/config.yml      2019-09-26 22:59:15.000000000 
+0200
+++ new/cheroot-8.2.1/.circleci/config.yml      2019-10-18 02:59:30.000000000 
+0200
@@ -35,7 +35,7 @@
     - checkout
     - run:
         name: Run tests
-        command: tox -e py27,py34,py35,py36,py37,pypy3 -- -p no:sugar
+        command: tox -e py27,py34,py35,py36,py37,pypy3 -- -p no:sugar 
$(circleci tests glob **/test/**.py | circleci tests split --split-by=timings | 
grep -v '__init__.py')
         # Environment variables for py-cryptography library
         environment:
           LDFLAGS: "-L/usr/local/opt/openssl/lib"
@@ -51,7 +51,7 @@
     steps:
     - checkout
     - run: pip install tox
-    - run: tox -e py27,py34,py35,py36,py37
+    - run: tox -e py27,py34,py35,py36,py37 -- $(circleci tests glob 
**/test/**.py | circleci tests split --split-by=timings | grep -v '__init__.py')
 
 workflows:
   version: 2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cheroot-7.0.0/.github/FUNDING.yml 
new/cheroot-8.2.1/.github/FUNDING.yml
--- old/cheroot-7.0.0/.github/FUNDING.yml       1970-01-01 01:00:00.000000000 
+0100
+++ new/cheroot-8.2.1/.github/FUNDING.yml       2019-10-18 02:59:30.000000000 
+0200
@@ -0,0 +1,14 @@
+# These are supported funding model platforms
+
+github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, 
user2]
+- jaraco
+- webknjaz
+# patreon: # Replace with a single Patreon username
+# open_collective: # Replace with a single Open Collective username
+# ko_fi: # Replace with a single Ko-fi username
+tidelift: pypi/Cheroot  # A single Tidelift platform-name/package-name e.g., 
npm/babel
+# community_bridge: # Replace with a single Community Bridge project-name 
e.g., cloud-foundry
+# liberapay: # Replace with a single Liberapay username
+# issuehunt: # Replace with a single IssueHunt username
+# otechie: # Replace with a single Otechie username
+# custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 
'link2']
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cheroot-7.0.0/.github/workflows/python-tests.yml 
new/cheroot-8.2.1/.github/workflows/python-tests.yml
--- old/cheroot-7.0.0/.github/workflows/python-tests.yml        2019-09-26 
22:59:15.000000000 +0200
+++ new/cheroot-8.2.1/.github/workflows/python-tests.yml        2019-10-18 
02:59:30.000000000 +0200
@@ -49,10 +49,8 @@
         + repr(ssl.OPENSSL_VERSION_NUMBER))"
       env: ${{ matrix.env }}
     - name: Log PyOpenSSL version
-      run: >-
-        python -m tox --run-command
-        "{envpython} -m OpenSSL.debug"
-        || :
+      run: |
+        python -m tox -e openssl-version
       env: ${{ matrix.env }}
     - name: Test with tox
       run: |
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cheroot-7.0.0/.pyup.yml new/cheroot-8.2.1/.pyup.yml
--- old/cheroot-7.0.0/.pyup.yml 1970-01-01 01:00:00.000000000 +0100
+++ new/cheroot-8.2.1/.pyup.yml 2019-10-18 02:59:30.000000000 +0200
@@ -0,0 +1 @@
+pin: False
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cheroot-7.0.0/CHANGES.rst 
new/cheroot-8.2.1/CHANGES.rst
--- old/cheroot-7.0.0/CHANGES.rst       2019-09-26 22:59:15.000000000 +0200
+++ new/cheroot-8.2.1/CHANGES.rst       2019-10-18 02:59:30.000000000 +0200
@@ -1,3 +1,34 @@
+v8.2.1
+======
+
+- :cp-issue:`1818`: Restore support for ``None``
+  default argument to ``WebCase.getPage()``.
+
+v8.2.0
+======
+
+- Deprecated use of negative timeouts as alias for
+  infinite timeouts in ``ThreadPool.stop``.
+- :cp-issue:`1662` via :pr:`74`: For OPTION requests,
+  bypass URI as path if it does not appear absolute.
+
+v8.1.0
+======
+
+- Workers are now request-based, addressing the
+  long-standing issue with keep-alive connections
+  (:issue:`91` via :pr:`199`).
+
+v8.0.0
+======
+
+- :issue:`231` via :pr:`232`: Remove custom setup.cfg
+  parser handling, allowing the project (including sdist)
+  to build/run on setuptools 41.4. Now building cheroot
+  requires setuptools 30.3 or later (for declarative
+  config support) and preferably 34.4 or later (as
+  indicated in pyproject.toml).
+
 v7.0.0
 ======
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cheroot-7.0.0/PKG-INFO new/cheroot-8.2.1/PKG-INFO
--- old/cheroot-7.0.0/PKG-INFO  2019-09-26 22:59:42.000000000 +0200
+++ new/cheroot-8.2.1/PKG-INFO  2019-10-18 02:59:51.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: cheroot
-Version: 7.0.0
+Version: 8.2.1
 Summary: Highly-optimized, pure-python HTTP server
 Home-page: https://cheroot.cherrypy.org
 Author: CherryPy Team
@@ -102,6 +102,6 @@
 Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
 Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
 Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Server
-Requires-Python: >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*
-Provides-Extra: testing
+Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7
 Provides-Extra: docs
+Provides-Extra: testing
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cheroot-7.0.0/cheroot/connections.py 
new/cheroot-8.2.1/cheroot/connections.py
--- old/cheroot-7.0.0/cheroot/connections.py    1970-01-01 01:00:00.000000000 
+0100
+++ new/cheroot-8.2.1/cheroot/connections.py    2019-10-18 02:59:30.000000000 
+0200
@@ -0,0 +1,279 @@
+"""Utilities to manage open connections."""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import io
+import os
+import select
+import socket
+import time
+
+from . import errors
+from .makefile import MakeFile
+
+import six
+
+try:
+    import fcntl
+except ImportError:
+    try:
+        from ctypes import windll, WinError
+        import ctypes.wintypes
+        _SetHandleInformation = windll.kernel32.SetHandleInformation
+        _SetHandleInformation.argtypes = [
+            ctypes.wintypes.HANDLE,
+            ctypes.wintypes.DWORD,
+            ctypes.wintypes.DWORD,
+        ]
+        _SetHandleInformation.restype = ctypes.wintypes.BOOL
+    except ImportError:
+        def prevent_socket_inheritance(sock):
+            """Stub inheritance prevention.
+
+            Dummy function, since neither fcntl nor ctypes are available.
+            """
+            pass
+    else:
+        def prevent_socket_inheritance(sock):
+            """Mark the given socket fd as non-inheritable (Windows)."""
+            if not _SetHandleInformation(sock.fileno(), 1, 0):
+                raise WinError()
+else:
+    def prevent_socket_inheritance(sock):
+        """Mark the given socket fd as non-inheritable (POSIX)."""
+        fd = sock.fileno()
+        old_flags = fcntl.fcntl(fd, fcntl.F_GETFD)
+        fcntl.fcntl(fd, fcntl.F_SETFD, old_flags | fcntl.FD_CLOEXEC)
+
+
+class ConnectionManager:
+    """Class which manages HTTPConnection objects.
+
+    This is for connections which are being kept-alive for follow-up requests.
+    """
+
+    def __init__(self, server):
+        """Initialize ConnectionManager object.
+
+        Args:
+            server (cheroot.server.HTTPServer): web server object
+                that uses this ConnectionManager instance.
+        """
+        self.server = server
+        self.connections = []
+
+    def put(self, conn):
+        """Put idle connection into the ConnectionManager to be managed.
+
+        Args:
+            conn (cheroot.server.HTTPConnection): HTTP connection
+                to be managed.
+        """
+        conn.last_used = time.time()
+        conn.ready_with_data = conn.rfile.has_data()
+        self.connections.append(conn)
+
+    def expire(self):
+        """Expire least recently used connections.
+
+        This happens if there are either too many open connections, or if the
+        connections have been timed out.
+
+        This should be called periodically.
+        """
+        if not self.connections:
+            return
+
+        # Look at the first connection - if it can be closed, then do
+        # that, and wait for get_conn to return it.
+        conn = self.connections[0]
+        if conn.closeable:
+            return
+
+        # Too many connections?
+        ka_limit = self.server.keep_alive_conn_limit
+        if ka_limit is not None and len(self.connections) > ka_limit:
+            conn.closeable = True
+            return
+
+        # Connection too old?
+        if (conn.last_used + self.server.timeout) < time.time():
+            conn.closeable = True
+            return
+
+    def get_conn(self, server_socket):
+        """Return a HTTPConnection object which is ready to be handled.
+
+        A connection returned by this method should be ready for a worker
+        to handle it. If there are no connections ready, None will be
+        returned.
+
+        Any connection returned by this method will need to be `put`
+        back if it should be examined again for another request.
+
+        Args:
+            server_socket (socket.socket): Socket to listen to for new
+            connections.
+        Returns:
+            cheroot.server.HTTPConnection instance, or None.
+
+        """
+        # Grab file descriptors from sockets, but stop if we find a
+        # connection which is already marked as ready.
+        socket_dict = {}
+        for conn in self.connections:
+            if conn.closeable or conn.ready_with_data:
+                break
+            socket_dict[conn.socket.fileno()] = conn
+        else:
+            # No ready connection.
+            conn = None
+
+        # We have a connection ready for use.
+        if conn:
+            self.connections.remove(conn)
+            return conn
+
+        # Will require a select call.
+        ss_fileno = server_socket.fileno()
+        socket_dict[ss_fileno] = server_socket
+        try:
+            rlist, _, _ = select.select(list(socket_dict), [], [], 0.1)
+            # No available socket.
+            if not rlist:
+                return None
+        except OSError:
+            # Mark any connection which no longer appears valid.
+            for fno, conn in list(socket_dict.items()):
+                # If the server socket is invalid, we'll just ignore it and
+                # wait to be shutdown.
+                if fno == ss_fileno:
+                    continue
+                try:
+                    os.fstat(fno)
+                except OSError:
+                    # Socket is invalid, close the connection, insert at
+                    # the front.
+                    self.connections.remove(conn)
+                    self.connections.insert(0, conn)
+                    conn.closeable = True
+
+            # Wait for the next tick to occur.
+            return None
+
+        try:
+            # See if we have a new connection coming in.
+            rlist.remove(ss_fileno)
+        except ValueError:
+            # No new connection, but reuse existing socket.
+            conn = socket_dict[rlist.pop()]
+        else:
+            conn = server_socket
+
+        # All remaining connections in rlist should be marked as ready.
+        for fno in rlist:
+            socket_dict[fno].ready_with_data = True
+
+        # New connection.
+        if conn is server_socket:
+            return self._from_server_socket(server_socket)
+
+        self.connections.remove(conn)
+        return conn
+
+    def _from_server_socket(self, server_socket):
+        try:
+            s, addr = server_socket.accept()
+            if self.server.stats['Enabled']:
+                self.server.stats['Accepts'] += 1
+            prevent_socket_inheritance(s)
+            if hasattr(s, 'settimeout'):
+                s.settimeout(self.server.timeout)
+
+            mf = MakeFile
+            ssl_env = {}
+            # if ssl cert and key are set, we try to be a secure HTTP server
+            if self.server.ssl_adapter is not None:
+                try:
+                    s, ssl_env = self.server.ssl_adapter.wrap(s)
+                except errors.NoSSLError:
+                    msg = (
+                        'The client sent a plain HTTP request, but '
+                        'this server only speaks HTTPS on this port.'
+                    )
+                    buf = [
+                        '%s 400 Bad Request\r\n' % self.server.protocol,
+                        'Content-Length: %s\r\n' % len(msg),
+                        'Content-Type: text/plain\r\n\r\n',
+                        msg,
+                    ]
+
+                    sock_to_make = s if not six.PY2 else s._sock
+                    wfile = mf(sock_to_make, 'wb', io.DEFAULT_BUFFER_SIZE)
+                    try:
+                        wfile.write(''.join(buf).encode('ISO-8859-1'))
+                    except socket.error as ex:
+                        if ex.args[0] not in errors.socket_errors_to_ignore:
+                            raise
+                    return
+                if not s:
+                    return
+                mf = self.server.ssl_adapter.makefile
+                # Re-apply our timeout since we may have a new socket object
+                if hasattr(s, 'settimeout'):
+                    s.settimeout(self.server.timeout)
+
+            conn = self.server.ConnectionClass(self.server, s, mf)
+
+            if not isinstance(
+                    self.server.bind_addr,
+                    (six.text_type, six.binary_type),
+            ):
+                # optional values
+                # Until we do DNS lookups, omit REMOTE_HOST
+                if addr is None:  # sometimes this can happen
+                    # figure out if AF_INET or AF_INET6.
+                    if len(s.getsockname()) == 2:
+                        # AF_INET
+                        addr = ('0.0.0.0', 0)
+                    else:
+                        # AF_INET6
+                        addr = ('::', 0)
+                conn.remote_addr = addr[0]
+                conn.remote_port = addr[1]
+
+            conn.ssl_env = ssl_env
+            return conn
+
+        except socket.timeout:
+            # The only reason for the timeout in start() is so we can
+            # notice keyboard interrupts on Win32, which don't interrupt
+            # accept() by default
+            return
+        except socket.error as ex:
+            if self.server.stats['Enabled']:
+                self.server.stats['Socket Errors'] += 1
+            if ex.args[0] in errors.socket_error_eintr:
+                # I *think* this is right. EINTR should occur when a signal
+                # is received during the accept() call; all docs say retry
+                # the call, and I *think* I'm reading it right that Python
+                # will then go ahead and poll for and handle the signal
+                # elsewhere. See
+                # https://github.com/cherrypy/cherrypy/issues/707.
+                return
+            if ex.args[0] in errors.socket_errors_nonblocking:
+                # Just try again. See
+                # https://github.com/cherrypy/cherrypy/issues/479.
+                return
+            if ex.args[0] in errors.socket_errors_to_ignore:
+                # Our socket was closed.
+                # See https://github.com/cherrypy/cherrypy/issues/686.
+                return
+            raise
+
+    def close(self):
+        """Close all monitored connections."""
+        for conn in self.connections[:]:
+            conn.close()
+        self.connections = []
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cheroot-7.0.0/cheroot/makefile.py 
new/cheroot-8.2.1/cheroot/makefile.py
--- old/cheroot-7.0.0/cheroot/makefile.py       2019-09-26 22:59:15.000000000 
+0200
+++ new/cheroot-8.2.1/cheroot/makefile.py       2019-10-18 02:59:30.000000000 
+0200
@@ -235,6 +235,7 @@
                         break
                     buf.write(data)
                 return buf.getvalue()
+
             else:
                 # Read until size bytes or \n or EOF seen, whichever comes
                 # first
@@ -279,6 +280,11 @@
                     buf_len += n
                     # assert buf_len == buf.tell()
                 return buf.getvalue()
+
+        def has_data(self):
+            """Return true if there is buffered data to read."""
+            return bool(self._rbuf.getvalue())
+
     else:
         def read(self, size=-1):
             """Read data from the socket to buffer."""
@@ -395,6 +401,10 @@
                     buf_len += n
                 return ''.join(buffers)
 
+        def has_data(self):
+            """Return true if there is buffered data to read."""
+            return bool(self._rbuf)
+
 
 if not six.PY2:
     class StreamReader(io.BufferedReader):
@@ -411,6 +421,10 @@
             self.bytes_read += len(val)
             return val
 
+        def has_data(self):
+            """Return true if there is buffered data to read."""
+            return len(self._read_buf) > self._read_pos
+
     class StreamWriter(BufferedWriter):
         """Socket stream writer."""
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cheroot-7.0.0/cheroot/server.py 
new/cheroot-8.2.1/cheroot/server.py
--- old/cheroot-7.0.0/cheroot/server.py 2019-09-26 22:59:15.000000000 +0200
+++ new/cheroot-8.2.1/cheroot/server.py 2019-10-18 02:59:30.000000000 +0200
@@ -84,7 +84,7 @@
 from six.moves import queue
 from six.moves import urllib
 
-from . import errors, __version__
+from . import connections, errors, __version__
 from ._compat import bton, ntou
 from ._compat import IS_PPC
 from .workers import threadpool
@@ -827,12 +827,14 @@
             self.simple_response('400 Bad Request', 'Malformed Request-URI')
             return False
 
+        uri_is_absolute_form = (scheme or authority)
+
         if self.method == b'OPTIONS':
             # TODO: cover this branch with tests
             path = (
                 uri
                 # https://tools.ietf.org/html/rfc7230#section-5.3.4
-                if self.proxy_mode or uri == ASTERISK
+                if (self.proxy_mode and uri_is_absolute_form)
                 else path
             )
         elif self.method == b'CONNECT':
@@ -871,8 +873,6 @@
             authority = path = _authority
             scheme = qs = fragment = EMPTY
         else:
-            uri_is_absolute_form = (scheme or authority)
-
             disallowed_absolute = (
                 self.strict_mode
                 and not self.proxy_mode
@@ -1227,6 +1227,11 @@
     peercreds_enabled = False
     peercreds_resolve_enabled = False
 
+    # Fields set by ConnectionManager.
+    closeable = False
+    last_used = None
+    ready_with_data = False
+
     def __init__(self, server, sock, makefile=MakeFile):
         """Initialize HTTPConnection instance.
 
@@ -1255,30 +1260,26 @@
         )
 
     def communicate(self):
-        """Read each request and respond appropriately."""
+        """Read each request and respond appropriately.
+
+        Returns true if the connection should be kept open.
+        """
         request_seen = False
         try:
-            while True:
-                # (re)set req to None so that if something goes wrong in
-                # the RequestHandlerClass constructor, the error doesn't
-                # get written to the previous request.
-                req = None
-                req = self.RequestHandlerClass(self.server, self)
-
-                # This order of operations should guarantee correct pipelining.
-                req.parse_request()
-                if self.server.stats['Enabled']:
-                    self.requests_seen += 1
-                if not req.ready:
-                    # Something went wrong in the parsing (and the server has
-                    # probably already made a simple_response). Return and
-                    # let the conn close.
-                    return
-
-                request_seen = True
-                req.respond()
-                if req.close_connection:
-                    return
+            req = self.RequestHandlerClass(self.server, self)
+            req.parse_request()
+            if self.server.stats['Enabled']:
+                self.requests_seen += 1
+            if not req.ready:
+                # Something went wrong in the parsing (and the server has
+                # probably already made a simple_response). Return and
+                # let the conn close.
+                return False
+
+            request_seen = True
+            req.respond()
+            if not req.close_connection:
+                return True
         except socket.error as ex:
             errnum = ex.args[0]
             # sadly SSL sockets return a different (longer) time out string
@@ -1307,6 +1308,7 @@
                 repr(ex), level=logging.ERROR, traceback=True,
             )
             self._conditional_error(req, '500 Internal Server Error')
+        return False
 
     linger = False
 
@@ -1474,39 +1476,6 @@
             self.socket._sock.close()
 
 
-try:
-    import fcntl
-except ImportError:
-    try:
-        from ctypes import windll, WinError
-        import ctypes.wintypes
-        _SetHandleInformation = windll.kernel32.SetHandleInformation
-        _SetHandleInformation.argtypes = [
-            ctypes.wintypes.HANDLE,
-            ctypes.wintypes.DWORD,
-            ctypes.wintypes.DWORD,
-        ]
-        _SetHandleInformation.restype = ctypes.wintypes.BOOL
-    except ImportError:
-        def prevent_socket_inheritance(sock):
-            """Stub inheritance prevention.
-
-            Dummy function, since neither fcntl nor ctypes are available.
-            """
-            pass
-    else:
-        def prevent_socket_inheritance(sock):
-            """Mark the given socket fd as non-inheritable (Windows)."""
-            if not _SetHandleInformation(sock.fileno(), 1, 0):
-                raise WinError()
-else:
-    def prevent_socket_inheritance(sock):
-        """Mark the given socket fd as non-inheritable (POSIX)."""
-        fd = sock.fileno()
-        old_flags = fcntl.fcntl(fd, fcntl.F_GETFD)
-        fcntl.fcntl(fd, fcntl.F_SETFD, old_flags | fcntl.FD_CLOEXEC)
-
-
 class HTTPServer:
     """An HTTP server."""
 
@@ -1582,6 +1551,11 @@
     peercreds_resolve_enabled = False
     """If True, username/group will be looked up in the OS from peercreds."""
 
+    keep_alive_conn_limit = 10
+    """The maximum number of waiting keep-alive connections that will be kept 
open.
+
+    Default is 10. Set to None to have unlimited connections."""
+
     def __init__(
         self, bind_addr, gateway,
         minthreads=10, maxthreads=-1, server_name=None,
@@ -1603,6 +1577,7 @@
         self.requests = threadpool.ThreadPool(
             self, min=minthreads or 1, max=maxthreads,
         )
+        self.connections = connections.ConnectionManager(self)
 
         if not server_name:
             server_name = self.version
@@ -1936,7 +1911,7 @@
     def prepare_socket(bind_addr, family, type, proto, nodelay, ssl_adapter):
         """Create and prepare the socket object."""
         sock = socket.socket(family, type, proto)
-        prevent_socket_inheritance(sock)
+        connections.prevent_socket_inheritance(sock)
 
         host, port = bind_addr[:2]
         IS_EPHEMERAL_PORT = port == 0
@@ -2012,102 +1987,18 @@
 
     def tick(self):
         """Accept a new connection and put it on the Queue."""
-        try:
-            s, addr = self.socket.accept()
-            if self.stats['Enabled']:
-                self.stats['Accepts'] += 1
-            if not self.ready:
-                return
-
-            prevent_socket_inheritance(s)
-            if hasattr(s, 'settimeout'):
-                s.settimeout(self.timeout)
-
-            mf = MakeFile
-            ssl_env = {}
-            # if ssl cert and key are set, we try to be a secure HTTP server
-            if self.ssl_adapter is not None:
-                try:
-                    s, ssl_env = self.ssl_adapter.wrap(s)
-                except errors.NoSSLError:
-                    msg = (
-                        'The client sent a plain HTTP request, but '
-                        'this server only speaks HTTPS on this port.'
-                    )
-                    buf = [
-                        '%s 400 Bad Request\r\n' % self.protocol,
-                        'Content-Length: %s\r\n' % len(msg),
-                        'Content-Type: text/plain\r\n\r\n',
-                        msg,
-                    ]
-
-                    sock_to_make = s if not six.PY2 else s._sock
-                    wfile = mf(sock_to_make, 'wb', io.DEFAULT_BUFFER_SIZE)
-                    try:
-                        wfile.write(''.join(buf).encode('ISO-8859-1'))
-                    except socket.error as ex:
-                        if ex.args[0] not in errors.socket_errors_to_ignore:
-                            raise
-                    return
-                if not s:
-                    return
-                mf = self.ssl_adapter.makefile
-                # Re-apply our timeout since we may have a new socket object
-                if hasattr(s, 'settimeout'):
-                    s.settimeout(self.timeout)
-
-            conn = self.ConnectionClass(self, s, mf)
-
-            if not isinstance(
-                    self.bind_addr,
-                    (six.text_type, six.binary_type),
-            ):
-                # optional values
-                # Until we do DNS lookups, omit REMOTE_HOST
-                if addr is None:  # sometimes this can happen
-                    # figure out if AF_INET or AF_INET6.
-                    if len(s.getsockname()) == 2:
-                        # AF_INET
-                        addr = ('0.0.0.0', 0)
-                    else:
-                        # AF_INET6
-                        addr = ('::', 0)
-                conn.remote_addr = addr[0]
-                conn.remote_port = addr[1]
-
-            conn.ssl_env = ssl_env
+        if not self.ready:
+            return
 
+        conn = self.connections.get_conn(self.socket)
+        if conn:
             try:
                 self.requests.put(conn)
             except queue.Full:
                 # Just drop the conn. TODO: write 503 back?
                 conn.close()
-                return
-        except socket.timeout:
-            # The only reason for the timeout in start() is so we can
-            # notice keyboard interrupts on Win32, which don't interrupt
-            # accept() by default
-            return
-        except socket.error as ex:
-            if self.stats['Enabled']:
-                self.stats['Socket Errors'] += 1
-            if ex.args[0] in errors.socket_error_eintr:
-                # I *think* this is right. EINTR should occur when a signal
-                # is received during the accept() call; all docs say retry
-                # the call, and I *think* I'm reading it right that Python
-                # will then go ahead and poll for and handle the signal
-                # elsewhere. See
-                # https://github.com/cherrypy/cherrypy/issues/707.
-                return
-            if ex.args[0] in errors.socket_errors_nonblocking:
-                # Just try again. See
-                # https://github.com/cherrypy/cherrypy/issues/479.
-                return
-            if ex.args[0] in errors.socket_errors_to_ignore:
-                # Our socket was closed.
-                # See https://github.com/cherrypy/cherrypy/issues/686.
-                return
-            raise
+
+        self.connections.expire()
 
     @property
     def interrupt(self):
@@ -2169,6 +2060,7 @@
                 sock.close()
             self.socket = None
 
+        self.connections.close()
         self.requests.stop(self.shutdown_timeout)
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cheroot-7.0.0/cheroot/test/test_conn.py 
new/cheroot-8.2.1/cheroot/test/test_conn.py
--- old/cheroot-7.0.0/cheroot/test/test_conn.py 2019-09-26 22:59:15.000000000 
+0200
+++ new/cheroot-8.2.1/cheroot/test/test_conn.py 2019-10-18 02:59:30.000000000 
+0200
@@ -116,6 +116,7 @@
     wsgi_server.max_request_body_size = 1001
     wsgi_server.timeout = timeout
     wsgi_server.server_client = wsgi_server_client
+    wsgi_server.keep_alive_conn_limit = 2
     return wsgi_server
 
 
@@ -389,6 +390,81 @@
     test_client.server_instance.protocol = original_server_protocol
 
 
+def test_keepalive_conn_management(test_client):
+    """Test management of Keep-Alive connections."""
+    test_client.server_instance.timeout = 2
+
+    def connection():
+        # Initialize a persistent HTTP connection
+        http_connection = test_client.get_connection()
+        http_connection.auto_open = False
+        http_connection.connect()
+        return http_connection
+
+    def request(conn):
+        status_line, actual_headers, actual_resp_body = test_client.get(
+            '/page3', headers=[('Connection', 'Keep-Alive')],
+            http_conn=conn, protocol='HTTP/1.0',
+        )
+        actual_status = int(status_line[:3])
+        assert actual_status == 200
+        assert status_line[4:] == 'OK'
+        assert actual_resp_body == pov.encode()
+        assert header_has_value('Connection', 'Keep-Alive', actual_headers)
+
+    disconnect_errors = (
+        http_client.BadStatusLine,
+        http_client.CannotSendRequest,
+        http_client.NotConnected,
+    )
+
+    # Make a new connection.
+    c1 = connection()
+    request(c1)
+
+    # Make a second one.
+    c2 = connection()
+    request(c2)
+
+    # Reusing the first connection should still work.
+    request(c1)
+
+    # Creating a new connection should still work.
+    c3 = connection()
+    request(c3)
+
+    # Allow a tick.
+    time.sleep(0.2)
+
+    # That's three connections, we should expect the one used less recently
+    # to be expired.
+    with pytest.raises(disconnect_errors):
+        request(c2)
+
+    # But the oldest created one should still be valid.
+    # (As well as the newest one).
+    request(c1)
+    request(c3)
+
+    # Wait for some of our timeout.
+    time.sleep(1.0)
+
+    # Refresh the third connection.
+    request(c3)
+
+    # Wait for the remainder of our timeout, plus one tick.
+    time.sleep(1.2)
+
+    # First connection should now be expired.
+    with pytest.raises(disconnect_errors):
+        request(c1)
+
+    # But the third one should still be valid.
+    request(c3)
+
+    test_client.server_instance.timeout = timeout
+
+
 @pytest.mark.parametrize(
     'timeout_before_headers',
     (
@@ -539,7 +615,7 @@
         response = conn.response_class(conn.sock, method='GET')
         # there is a bug in python3 regarding the buffering of
         # ``conn.sock``. Until that bug get's fixed we will
-        # monkey patch the ``reponse`` instance.
+        # monkey patch the ``response`` instance.
         # https://bugs.python.org/issue23377
         if not six.PY2:
             response.fp = conn.sock.makefile('rb', 0)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cheroot-7.0.0/cheroot/test/test_ssl.py 
new/cheroot-8.2.1/cheroot/test/test_ssl.py
--- old/cheroot-7.0.0/cheroot/test/test_ssl.py  2019-09-26 22:59:15.000000000 
+0200
+++ new/cheroot-8.2.1/cheroot/test/test_ssl.py  2019-10-18 02:59:30.000000000 
+0200
@@ -86,7 +86,7 @@
         return super(HelloWorldGateway, self).respond()
 
 
-def make_tls_http_server(bind_addr, ssl_adapter):
+def make_tls_http_server(bind_addr, ssl_adapter, request):
     """Create and start an HTTP server bound to bind_addr."""
     httpserver = HTTPServer(
         bind_addr=bind_addr,
@@ -100,28 +100,15 @@
     while not httpserver.ready:
         time.sleep(0.1)
 
+    request.addfinalizer(httpserver.stop)
+
     return httpserver
 
 
 @pytest.fixture
-def tls_http_server():
+def tls_http_server(request):
     """Provision a server creator as a fixture."""
-    def start_srv():
-        bind_addr, ssl_adapter = yield
-        httpserver = make_tls_http_server(bind_addr, ssl_adapter)
-        yield httpserver
-        yield httpserver
-
-    srv_creator = iter(start_srv())
-    next(srv_creator)
-    yield srv_creator
-    try:
-        while True:
-            httpserver = next(srv_creator)
-            if httpserver is not None:
-                httpserver.stop()
-    except StopIteration:
-        pass
+    return functools.partial(make_tls_http_server, request=request)
 
 
 @pytest.fixture
@@ -183,12 +170,7 @@
 
     tls_certificate.configure_cert(tls_adapter.context)
 
-    tlshttpserver = tls_http_server.send(
-        (
-            (interface, port),
-            tls_adapter,
-        ),
-    )
+    tlshttpserver = tls_http_server((interface, port), tls_adapter)
 
     # testclient = get_server_client(tlshttpserver)
     # testclient.get('/')
@@ -277,12 +259,7 @@
         ca.configure_trust(tls_adapter.context)
         tls_certificate.configure_cert(tls_adapter.context)
 
-        tlshttpserver = tls_http_server.send(
-            (
-                (interface, port),
-                tls_adapter,
-            ),
-        )
+        tlshttpserver = tls_http_server((interface, port), tls_adapter)
 
         interface, _host, port = _get_conn_data(tlshttpserver.bind_addr)
 
@@ -315,6 +292,16 @@
             assert resp.text == 'Hello world!'
             return
 
+        # xfail some flaky tests
+        # https://github.com/cherrypy/cheroot/issues/237
+        issue_237 = (
+            IS_MACOS
+            and adapter_type == 'builtin'
+            and tls_verify_mode != ssl.CERT_NONE
+        )
+        if issue_237:
+            pytest.xfail('Test sometimes fails')
+
         expected_ssl_errors = (
             requests.exceptions.SSLError,
             OpenSSL.SSL.Error,
@@ -414,6 +401,15 @@
     tls_certificate_private_key_pem_path,
 ):
     """Ensure that connecting over HTTP to HTTPS port is handled."""
+    # disable some flaky tests
+    # https://github.com/cherrypy/cheroot/issues/225
+    issue_225 = (
+        IS_MACOS
+        and adapter_type == 'builtin'
+    )
+    if issue_225:
+        pytest.xfail('Test fails in Travis-CI')
+
     tls_adapter_cls = get_ssl_adapter_class(name=adapter_type)
     tls_adapter = tls_adapter_cls(
         tls_certificate_chain_pem_path, tls_certificate_private_key_pem_path,
@@ -424,12 +420,7 @@
     tls_certificate.configure_cert(tls_adapter.context)
 
     interface, _host, port = _get_conn_data(ip_addr)
-    tlshttpserver = tls_http_server.send(
-        (
-            (interface, port),
-            tls_adapter,
-        ),
-    )
+    tlshttpserver = tls_http_server((interface, port), tls_adapter)
 
     interface, host, port = _get_conn_data(
         tlshttpserver.bind_addr,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cheroot-7.0.0/cheroot/test/webtest.py 
new/cheroot-8.2.1/cheroot/test/webtest.py
--- old/cheroot-7.0.0/cheroot/test/webtest.py   2019-09-26 22:59:15.000000000 
+0200
+++ new/cheroot-8.2.1/cheroot/test/webtest.py   2019-10-18 02:59:30.000000000 
+0200
@@ -174,7 +174,7 @@
 
     def getPage(
         self, url, headers=None, method='GET', body=None,
-        protocol=None, raise_subcls=None,
+        protocol=None, raise_subcls=(),
     ):
         """Open the url with debugging support. Return status, headers, body.
 
@@ -202,13 +202,17 @@
         if isinstance(body, six.text_type):
             body = body.encode('utf-8')
 
+        # for compatibility, support raise_subcls is None
+        raise_subcls = raise_subcls or ()
+
         self.url = url
         self.time = None
         start = time.time()
         result = openURL(
             url, headers, method, body, self.HOST, self.PORT,
             self.HTTP_CONN, protocol or self.PROTOCOL,
-            raise_subcls=raise_subcls, ssl_context=self.ssl_context,
+            raise_subcls=raise_subcls,
+            ssl_context=self.ssl_context,
         )
         self.time = time.time() - start
         self.status, self.headers, self.body = result
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cheroot-7.0.0/cheroot/workers/threadpool.py 
new/cheroot-8.2.1/cheroot/workers/threadpool.py
--- old/cheroot-7.0.0/cheroot/workers/threadpool.py     2019-09-26 
22:59:15.000000000 +0200
+++ new/cheroot-8.2.1/cheroot/workers/threadpool.py     2019-10-18 
02:59:30.000000000 +0200
@@ -8,9 +8,12 @@
 import threading
 import time
 import socket
+import warnings
 
 from six.moves import queue
 
+from jaraco.functools import pass_none
+
 
 __all__ = ('WorkerThread', 'ThreadPool')
 
@@ -108,14 +111,23 @@
                 if conn is _SHUTDOWNREQUEST:
                     return
 
+                # Just close the connection and move on.
+                if conn.closeable:
+                    conn.close()
+                    continue
+
                 self.conn = conn
                 is_stats_enabled = self.server.stats['Enabled']
                 if is_stats_enabled:
                     self.start_time = time.time()
+                keep_conn_open = False
                 try:
-                    conn.communicate()
+                    keep_conn_open = conn.communicate()
                 finally:
-                    conn.close()
+                    if keep_conn_open:
+                        self.server.connections.put(conn)
+                    else:
+                        conn.close()
                     if is_stats_enabled:
                         self.requests_seen += self.conn.requests_seen
                         self.bytes_read += self.conn.rfile.bytes_read
@@ -243,44 +255,67 @@
         Args:
             timeout (int): time to wait for threads to stop gracefully
         """
+        # for compatability, negative timeouts are treated like None
+        # TODO: treat negative timeouts like already expired timeouts
+        if timeout is not None and timeout < 0:
+            timeout = None
+            warnings.warning(
+                'In the future, negative timeouts to Server.stop() '
+                'will be equivalent to a timeout of zero.',
+                stacklevel=2,
+            )
+
+        if timeout is not None:
+            endtime = time.time() + timeout
+
         # Must shut down threads here so the code that calls
         # this method can know when all threads are stopped.
         for worker in self._threads:
             self._queue.put(_SHUTDOWNREQUEST)
 
-        # Don't join currentThread (when stop is called inside a request).
-        current = threading.currentThread()
-        if timeout is not None and timeout >= 0:
-            endtime = time.time() + timeout
-        while self._threads:
-            worker = self._threads.pop()
-            if worker is not current and worker.is_alive():
-                try:
-                    if timeout is None or timeout < 0:
-                        worker.join()
-                    else:
-                        remaining_time = endtime - time.time()
-                        if remaining_time > 0:
-                            worker.join(remaining_time)
-                        if worker.is_alive():
-                            # We exhausted the timeout.
-                            # Forcibly shut down the socket.
-                            c = worker.conn
-                            if c and not c.rfile.closed:
-                                try:
-                                    c.socket.shutdown(socket.SHUT_RD)
-                                except TypeError:
-                                    # pyOpenSSL sockets don't take an arg
-                                    c.socket.shutdown()
-                            worker.join()
-                except (
-                    AssertionError,
-                    # Ignore repeated Ctrl-C.
-                    # See
-                    # https://github.com/cherrypy/cherrypy/issues/691.
-                    KeyboardInterrupt,
-                ):
-                    pass
+        ignored_errors = (
+            # TODO: explain this exception.
+            AssertionError,
+            # Ignore repeated Ctrl-C. See cherrypy#691.
+            KeyboardInterrupt,
+        )
+
+        for worker in self._clear_threads():
+            remaining_time = timeout and endtime - time.time()
+            try:
+                worker.join(remaining_time)
+                if worker.is_alive():
+                    # Timeout exhausted; forcibly shut down the socket.
+                    self._force_close(worker.conn)
+                    worker.join()
+            except ignored_errors:
+                pass
+
+    @staticmethod
+    @pass_none
+    def _force_close(conn):
+        if conn.rfile.closed:
+            return
+        try:
+            try:
+                conn.socket.shutdown(socket.SHUT_RD)
+            except TypeError:
+                # pyOpenSSL sockets don't take an arg
+                conn.socket.shutdown()
+        except OSError:
+            # shutdown sometimes fails (race with 'closed' check?)
+            # ref #238
+            pass
+
+    def _clear_threads(self):
+        """Clear self._threads and yield all joinable threads."""
+        # threads = pop_all(self._threads)
+        threads, self._threads[:] = self._threads[:], []
+        return (
+            thread
+            for thread in threads
+            if thread is not threading.currentThread()
+        )
 
     @property
     def qsize(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cheroot-7.0.0/cheroot.egg-info/PKG-INFO 
new/cheroot-8.2.1/cheroot.egg-info/PKG-INFO
--- old/cheroot-7.0.0/cheroot.egg-info/PKG-INFO 2019-09-26 22:59:41.000000000 
+0200
+++ new/cheroot-8.2.1/cheroot.egg-info/PKG-INFO 2019-10-18 02:59:50.000000000 
+0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: cheroot
-Version: 7.0.0
+Version: 8.2.1
 Summary: Highly-optimized, pure-python HTTP server
 Home-page: https://cheroot.cherrypy.org
 Author: CherryPy Team
@@ -102,6 +102,6 @@
 Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
 Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
 Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Server
-Requires-Python: >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*
-Provides-Extra: testing
+Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7
 Provides-Extra: docs
+Provides-Extra: testing
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cheroot-7.0.0/cheroot.egg-info/SOURCES.txt 
new/cheroot-8.2.1/cheroot.egg-info/SOURCES.txt
--- old/cheroot-7.0.0/cheroot.egg-info/SOURCES.txt      2019-09-26 
22:59:42.000000000 +0200
+++ new/cheroot-8.2.1/cheroot.egg-info/SOURCES.txt      2019-10-18 
02:59:51.000000000 +0200
@@ -5,6 +5,7 @@
 .gitignore
 .pre-commit-config.yaml
 .pre-commit-config.yaml.failing
+.pyup.yml
 .readthedocs.yml
 .travis.yml
 CHANGES.rst
@@ -18,6 +19,7 @@
 .circleci/config.yml
 .github/CODE_OF_CONDUCT.md
 .github/CONTRIBUTING.rst
+.github/FUNDING.yml
 .github/ISSUE_TEMPLATE.md
 .github/PULL_REQUEST_TEMPLATE.md
 .github/config.yml
@@ -30,6 +32,7 @@
 cheroot/__main__.py
 cheroot/_compat.py
 cheroot/cli.py
+cheroot/connections.py
 cheroot/errors.py
 cheroot/makefile.py
 cheroot/server.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cheroot-7.0.0/cheroot.egg-info/requires.txt 
new/cheroot-8.2.1/cheroot.egg-info/requires.txt
--- old/cheroot-7.0.0/cheroot.egg-info/requires.txt     2019-09-26 
22:59:41.000000000 +0200
+++ new/cheroot-8.2.1/cheroot.egg-info/requires.txt     2019-10-18 
02:59:50.000000000 +0200
@@ -16,7 +16,7 @@
 
 [testing]
 pytest>=2.8
-pytest-mock==1.10.4
+pytest-mock>=1.11.0
 pytest-sugar>=0.9.1
 pytest-testmon>=0.9.7
 pytest-watch==4.2.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cheroot-7.0.0/docs/contribute.rst 
new/cheroot-8.2.1/docs/contribute.rst
--- old/cheroot-7.0.0/docs/contribute.rst       2019-09-26 22:59:15.000000000 
+0200
+++ new/cheroot-8.2.1/docs/contribute.rst       2019-10-18 02:59:30.000000000 
+0200
@@ -5,7 +5,7 @@
 ~~~~~~~~~~~~~~~~
 
 - You need to install `Python`_ 3 which is required for building docs.
-For example, Python 3.7.
+  For example, Python 3.7.
 
 Then, `create and activate a virtual environment`_.
 And install `tox`_.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cheroot-7.0.0/setup.cfg new/cheroot-8.2.1/setup.cfg
--- old/cheroot-7.0.0/setup.cfg 2019-09-26 22:59:42.000000000 +0200
+++ new/cheroot-8.2.1/setup.cfg 2019-10-18 02:59:51.000000000 +0200
@@ -76,7 +76,7 @@
        collective.checkdocs
 testing = 
        pytest>=2.8
-       pytest-mock==1.10.4
+       pytest-mock>=1.11.0
        pytest-sugar>=0.9.1
        pytest-testmon>=0.9.7
        pytest-watch==4.2.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cheroot-7.0.0/setup.py new/cheroot-8.2.1/setup.py
--- old/cheroot-7.0.0/setup.py  2019-09-26 22:59:15.000000000 +0200
+++ new/cheroot-8.2.1/setup.py  2019-10-18 02:59:30.000000000 +0200
@@ -1,138 +1,8 @@
-#! /usr/bin/env python
+#!/usr/bin/env python
+
 """Cheroot package setuptools installer."""
 
 import setuptools
 
-
-try:
-    from setuptools.config import read_configuration, ConfigOptionsHandler
-    import setuptools.config
-    import setuptools.dist
-
-    # Set default value for 'use_scm_version'
-    setattr(setuptools.dist.Distribution, 'use_scm_version', False)
-
-    # Attach bool parser to 'use_scm_version' option
-    class ShimConfigOptionsHandler(ConfigOptionsHandler):
-        """Extension class for ConfigOptionsHandler."""
-
-        @property
-        def parsers(self):
-            """Return an option mapping with default data type parsers."""
-            _orig_parsers = super(ShimConfigOptionsHandler, self).parsers
-            return dict(use_scm_version=self._parse_bool, **_orig_parsers)
-
-    setuptools.config.ConfigOptionsHandler = ShimConfigOptionsHandler
-except ImportError:
-    """This is a shim for setuptools<30.3."""
-    import io
-    import json
-
-    try:
-        from configparser import ConfigParser, NoSectionError
-    except ImportError:
-        from ConfigParser import ConfigParser, NoSectionError
-        ConfigParser.read_file = ConfigParser.readfp
-
-    def maybe_read_files(d):
-        """Read files if the string starts with `file:` marker."""
-        d = d.strip()
-        if not d.startswith('file:'):
-            return d
-        descs = []
-        for fname in map(str.strip, d[5:].split(',')):
-            with io.open(fname, encoding='utf-8') as f:
-                descs.append(f.read())
-        return ''.join(descs)
-
-    def cfg_val_to_list(v):
-        """Turn config val to list and filter out empty lines."""
-        return list(filter(bool, map(str.strip, v.strip().splitlines())))
-
-    def cfg_val_to_dict(v):
-        """Turn config val to dict and filter out empty lines."""
-        return dict(
-            map(
-                lambda l: list(map(str.strip, l.split('=', 1))),
-                filter(bool, map(str.strip, v.strip().splitlines())),
-            ),
-        )
-
-    def cfg_val_to_primitive(v):
-        """Parse primitive config val to appropriate data type."""
-        return json.loads(v.strip().lower())
-
-    def read_configuration(filepath):
-        """Read metadata and options from setup.cfg located at filepath."""
-        cfg = ConfigParser()
-        with io.open(filepath, encoding='utf-8') as f:
-            cfg.read_file(f)
-
-        md = dict(cfg.items('metadata'))
-        for list_key in 'classifiers', 'keywords':
-            try:
-                md[list_key] = cfg_val_to_list(md[list_key])
-            except KeyError:
-                pass
-        try:
-            md['long_description'] = maybe_read_files(md['long_description'])
-        except KeyError:
-            pass
-        opt = dict(cfg.items('options'))
-        for list_key in 'use_scm_version', 'zip_safe':
-            try:
-                opt[list_key] = cfg_val_to_primitive(opt[list_key])
-            except KeyError:
-                pass
-        for list_key in 'scripts', 'install_requires', 'setup_requires':
-            try:
-                opt[list_key] = cfg_val_to_list(opt[list_key])
-            except KeyError:
-                pass
-        try:
-            opt['package_dir'] = cfg_val_to_dict(opt['package_dir'])
-        except KeyError:
-            pass
-        opt_package_data = dict(cfg.items('options.package_data'))
-        try:
-            if not opt_package_data.get('', '').strip():
-                opt_package_data[''] = opt_package_data['*']
-                del opt_package_data['*']
-        except KeyError:
-            pass
-        try:
-            opt_extras_require = dict(cfg.items('options.extras_require'))
-            opt['extras_require'] = {}
-            for k, v in opt_extras_require.items():
-                opt['extras_require'][k] = cfg_val_to_list(v)
-        except NoSectionError:
-            pass
-        opt['package_data'] = {}
-        for k, v in opt_package_data.items():
-            opt['package_data'][k] = cfg_val_to_list(v)
-        cur_pkgs = opt.get('packages', '').strip()
-        if '\n' in cur_pkgs:
-            opt['packages'] = cfg_val_to_list(opt['packages'])
-        elif cur_pkgs.startswith('find:'):
-            opt_packages_find = dict(cfg.items('options.packages.find'))
-            opt['packages'] = setuptools.find_packages(**opt_packages_find)
-        return {'metadata': md, 'options': opt}
-
-
-setup_params = {}
-declarative_setup_params = read_configuration('setup.cfg')
-
-# Patch incorrectly decoded package_dir option
-# ``egg_info`` demands native strings failing with unicode under Python 2
-# Ref https://github.com/pypa/setuptools/issues/1136
-if 'package_dir' in declarative_setup_params['options']:
-    declarative_setup_params['options']['package_dir'] = {
-        str(k): str(v)
-        for k, v in declarative_setup_params['options']['package_dir'].items()
-    }
-
-setup_params = dict(setup_params, **declarative_setup_params['metadata'])
-setup_params = dict(setup_params, **declarative_setup_params['options'])
-
-
-__name__ == '__main__' and setuptools.setup(**setup_params)
+if __name__ == '__main__':
+    setuptools.setup(use_scm_version=True)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cheroot-7.0.0/tox.ini new/cheroot-8.2.1/tox.ini
--- old/cheroot-7.0.0/tox.ini   2019-09-26 22:59:15.000000000 +0200
+++ new/cheroot-8.2.1/tox.ini   2019-10-18 02:59:30.000000000 +0200
@@ -1,22 +1,12 @@
 [tox]
 envlist = python
 minversion = 3.13.2
-requires =
-    pip >= 9
-    tox-run-command >= 0.4
 
 [testenv]
 deps =
-    pip >= 9
     setuptools>=31.0.1
-whitelist_externals =
-    rm
-    bash
-    test
 commands =
-    rm -rf .eggs/
-    bash -c "if [[ '{env:CIRCLECI:disabled}' == 'disabled' ]]; then pytest 
--testmon-off {posargs}; fi"
-    bash -c "if [[ '{env:CIRCLECI:disabled}' != 'disabled' ]]; then circleci 
tests glob **/test/**.py | circleci tests split --split-by=timings | grep -v 
'__init__.py' | xargs pytest --testmon-off {posargs}; fi"
+    pytest --testmon-off {posargs}
     codecov -f coverage.xml -X gcov
 usedevelop = True
 extras = testing
@@ -36,6 +26,10 @@
     PYTHONDONTWRITEBYTECODE=x
     WEBTEST_INTERACTIVE=false
 
+[testenv:openssl-version]
+commands =
+    python -m OpenSSL.debug
+
 [testenv:build-docs]
 basepython = python3.7
 description = Build The Docs


Reply via email to