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
+
+- 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