diff options
author | Michał Górny <mgorny@gentoo.org> | 2022-01-31 23:08:19 +0100 |
---|---|---|
committer | Michał Górny <mgorny@gentoo.org> | 2022-01-31 23:27:52 +0100 |
commit | b09cd1265ba60e15dbb549d18cc10b9b11e424f7 (patch) | |
tree | a0770786865690273bb003d82e313eff33e35942 /dev-python/cherrypy | |
parent | media-sound/murmur: Revbump to fix init script (diff) | |
download | gentoo-b09cd1265ba60e15dbb549d18cc10b9b11e424f7.tar.gz gentoo-b09cd1265ba60e15dbb549d18cc10b9b11e424f7.tar.bz2 gentoo-b09cd1265ba60e15dbb549d18cc10b9b11e424f7.zip |
dev-python/cherrypy: Add pypy3 love
Signed-off-by: Michał Górny <mgorny@gentoo.org>
Diffstat (limited to 'dev-python/cherrypy')
-rw-r--r-- | dev-python/cherrypy/cherrypy-18.6.1.ebuild | 9 | ||||
-rw-r--r-- | dev-python/cherrypy/files/cherrypy-18.6.1-close-files.patch | 416 |
2 files changed, 423 insertions, 2 deletions
diff --git a/dev-python/cherrypy/cherrypy-18.6.1.ebuild b/dev-python/cherrypy/cherrypy-18.6.1.ebuild index a039a64ffe1e..d1177e6159ad 100644 --- a/dev-python/cherrypy/cherrypy-18.6.1.ebuild +++ b/dev-python/cherrypy/cherrypy-18.6.1.ebuild @@ -1,9 +1,9 @@ -# Copyright 1999-2021 Gentoo Authors +# Copyright 1999-2022 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 EAPI=8 -PYTHON_COMPAT=( python3_{8..10} ) +PYTHON_COMPAT=( python3_{8..10} pypy3 ) inherit distutils-r1 MY_P="CherryPy-${PV}" @@ -40,6 +40,11 @@ BDEPEND=" distutils_enable_tests pytest python_prepare_all() { + local PATCHES=( + # https://github.com/cherrypy/cherrypy/pull/1946 + "${FILESDIR}"/${P}-close-files.patch + ) + sed -r -e '/(pytest-sugar|pytest-cov)/ d' \ -i setup.py || die diff --git a/dev-python/cherrypy/files/cherrypy-18.6.1-close-files.patch b/dev-python/cherrypy/files/cherrypy-18.6.1-close-files.patch new file mode 100644 index 000000000000..478d717dfb8c --- /dev/null +++ b/dev-python/cherrypy/files/cherrypy-18.6.1-close-files.patch @@ -0,0 +1,416 @@ +From 94a2cc036203c6da55174ef3b105c0c875bbc79f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= <mgorny@gentoo.org> +Date: Mon, 31 Jan 2022 22:25:34 +0100 +Subject: [PATCH] Use context managers to close files properly and fix tests on + PyPy + +Use context managers (`with`) to ensure that all open files are closed +correctly. This resolves resource leaks and test failures with PyPy3.7. + +The code prior to this change used four approaches for closing files: + +1. Using a context manager (`with` clause). + +2. Using a try/finally clause. + +3. Closing the file in the same scope (unreliable: file object can leak + on exception). + +4. Not closing open files at all. + +The last point is a real problem for PyPy since it does not GC +unreachable objects as aggressively as CPython does. While leaving +a function scope on CPython causes the file objects private to it to +be destroyed (and therefore closed), in PyPy they can stay dangling +for some time. When combines with buffered writes, this means that +writes can still remain pending after returning from function. + +Using a context manager is a simple, consistent way to ensure that +the file object is closed once it is no longer needed. In turn, this +guarantees that all pending writes will be performed upon function +return and the code won't be hiting race conditions between writing +a file and reading it afterwards. +--- + cherrypy/_cperror.py | 3 ++- + cherrypy/_cpmodpy.py | 5 +---- + cherrypy/lib/auth_digest.py | 13 ++++++------ + cherrypy/lib/covercp.py | 40 ++++++++++++++++++------------------ + cherrypy/lib/reprconf.py | 5 +---- + cherrypy/lib/sessions.py | 10 ++------- + cherrypy/process/plugins.py | 3 ++- + cherrypy/test/helper.py | 3 ++- + cherrypy/test/logtest.py | 33 ++++++++++++++++------------- + cherrypy/test/modfastcgi.py | 5 +---- + cherrypy/test/modfcgid.py | 5 +---- + cherrypy/test/modpy.py | 5 +---- + cherrypy/test/modwsgi.py | 5 +---- + cherrypy/test/test_core.py | 5 ++--- + cherrypy/test/test_states.py | 11 +++++----- + 15 files changed, 67 insertions(+), 84 deletions(-) + +diff --git a/cherrypy/_cperror.py b/cherrypy/_cperror.py +index 4e727682..ebf1dcf6 100644 +--- a/cherrypy/_cperror.py ++++ b/cherrypy/_cperror.py +@@ -532,7 +532,8 @@ def get_error_page(status, **kwargs): + return result + else: + # Load the template from this path. +- template = io.open(error_page, newline='').read() ++ with io.open(error_page, newline='') as f: ++ template = f.read() + except Exception: + e = _format_exception(*_exc_info())[-1] + m = kwargs['message'] +diff --git a/cherrypy/_cpmodpy.py b/cherrypy/_cpmodpy.py +index 0e608c48..a08f0ed9 100644 +--- a/cherrypy/_cpmodpy.py ++++ b/cherrypy/_cpmodpy.py +@@ -339,11 +339,8 @@ LoadModule python_module modules/mod_python.so + } + + mpconf = os.path.join(os.path.dirname(__file__), 'cpmodpy.conf') +- f = open(mpconf, 'wb') +- try: ++ with open(mpconf, 'wb') as f: + f.write(conf_data) +- finally: +- f.close() + + response = read_process(self.apache_path, '-k start -f %s' % mpconf) + self.ready = True +diff --git a/cherrypy/lib/auth_digest.py b/cherrypy/lib/auth_digest.py +index fbb5df64..981e9a5d 100644 +--- a/cherrypy/lib/auth_digest.py ++++ b/cherrypy/lib/auth_digest.py +@@ -101,13 +101,12 @@ def get_ha1_file_htdigest(filename): + """ + def get_ha1(realm, username): + result = None +- f = open(filename, 'r') +- for line in f: +- u, r, ha1 = line.rstrip().split(':') +- if u == username and r == realm: +- result = ha1 +- break +- f.close() ++ with open(filename, 'r') as f: ++ for line in f: ++ u, r, ha1 = line.rstrip().split(':') ++ if u == username and r == realm: ++ result = ha1 ++ break + return result + + return get_ha1 +diff --git a/cherrypy/lib/covercp.py b/cherrypy/lib/covercp.py +index 3e219713..005fafa5 100644 +--- a/cherrypy/lib/covercp.py ++++ b/cherrypy/lib/covercp.py +@@ -334,26 +334,26 @@ class CoverStats(object): + yield '</body></html>' + + def annotated_file(self, filename, statements, excluded, missing): +- source = open(filename, 'r') +- buffer = [] +- for lineno, line in enumerate(source.readlines()): +- lineno += 1 +- line = line.strip('\n\r') +- empty_the_buffer = True +- if lineno in excluded: +- template = TEMPLATE_LOC_EXCLUDED +- elif lineno in missing: +- template = TEMPLATE_LOC_NOT_COVERED +- elif lineno in statements: +- template = TEMPLATE_LOC_COVERED +- else: +- empty_the_buffer = False +- buffer.append((lineno, line)) +- if empty_the_buffer: +- for lno, pastline in buffer: +- yield template % (lno, cgi.escape(pastline)) +- buffer = [] +- yield template % (lineno, cgi.escape(line)) ++ with open(filename, 'r') as source: ++ buffer = [] ++ for lineno, line in enumerate(source.readlines()): ++ lineno += 1 ++ line = line.strip('\n\r') ++ empty_the_buffer = True ++ if lineno in excluded: ++ template = TEMPLATE_LOC_EXCLUDED ++ elif lineno in missing: ++ template = TEMPLATE_LOC_NOT_COVERED ++ elif lineno in statements: ++ template = TEMPLATE_LOC_COVERED ++ else: ++ empty_the_buffer = False ++ buffer.append((lineno, line)) ++ if empty_the_buffer: ++ for lno, pastline in buffer: ++ yield template % (lno, cgi.escape(pastline)) ++ buffer = [] ++ yield template % (lineno, cgi.escape(line)) + + @cherrypy.expose + def report(self, name): +diff --git a/cherrypy/lib/reprconf.py b/cherrypy/lib/reprconf.py +index 3976652e..76381d7b 100644 +--- a/cherrypy/lib/reprconf.py ++++ b/cherrypy/lib/reprconf.py +@@ -163,11 +163,8 @@ class Parser(configparser.ConfigParser): + # fp = open(filename) + # except IOError: + # continue +- fp = open(filename) +- try: ++ with open(filename) as fp: + self._read(fp, filename) +- finally: +- fp.close() + + def as_dict(self, raw=False, vars=None): + """Convert an INI file to a dictionary""" +diff --git a/cherrypy/lib/sessions.py b/cherrypy/lib/sessions.py +index 5b3328f2..0f56a4fa 100644 +--- a/cherrypy/lib/sessions.py ++++ b/cherrypy/lib/sessions.py +@@ -516,11 +516,8 @@ class FileSession(Session): + if path is None: + path = self._get_file_path() + try: +- f = open(path, 'rb') +- try: ++ with open(path, 'rb') as f: + return pickle.load(f) +- finally: +- f.close() + except (IOError, EOFError): + e = sys.exc_info()[1] + if self.debug: +@@ -531,11 +528,8 @@ class FileSession(Session): + def _save(self, expiration_time): + assert self.locked, ('The session was saved without being locked. ' + "Check your tools' priority levels.") +- f = open(self._get_file_path(), 'wb') +- try: ++ with open(self._get_file_path(), 'wb') as f: + pickle.dump((self._data, expiration_time), f, self.pickle_protocol) +- finally: +- f.close() + + def _delete(self): + assert self.locked, ('The session deletion without being locked. ' +diff --git a/cherrypy/process/plugins.py b/cherrypy/process/plugins.py +index 2a9952de..e96fb1ce 100644 +--- a/cherrypy/process/plugins.py ++++ b/cherrypy/process/plugins.py +@@ -436,7 +436,8 @@ class PIDFile(SimplePlugin): + if self.finalized: + self.bus.log('PID %r already written to %r.' % (pid, self.pidfile)) + else: +- open(self.pidfile, 'wb').write(ntob('%s\n' % pid, 'utf8')) ++ with open(self.pidfile, 'wb') as f: ++ f.write(ntob('%s\n' % pid, 'utf8')) + self.bus.log('PID %r written to %r.' % (pid, self.pidfile)) + self.finalized = True + start.priority = 70 +diff --git a/cherrypy/test/helper.py b/cherrypy/test/helper.py +index c1ca4535..cae49533 100644 +--- a/cherrypy/test/helper.py ++++ b/cherrypy/test/helper.py +@@ -505,7 +505,8 @@ server.ssl_private_key: r'%s' + + def get_pid(self): + if self.daemonize: +- return int(open(self.pid_file, 'rb').read()) ++ with open(self.pid_file, 'rb') as f: ++ return int(f.read()) + return self._proc.pid + + def join(self): +diff --git a/cherrypy/test/logtest.py b/cherrypy/test/logtest.py +index 344be987..112bdc25 100644 +--- a/cherrypy/test/logtest.py ++++ b/cherrypy/test/logtest.py +@@ -97,7 +97,8 @@ class LogCase(object): + + def emptyLog(self): + """Overwrite self.logfile with 0 bytes.""" +- open(self.logfile, 'wb').write('') ++ with open(self.logfile, 'wb') as f: ++ f.write('') + + def markLog(self, key=None): + """Insert a marker line into the log and set self.lastmarker.""" +@@ -105,10 +106,11 @@ class LogCase(object): + key = str(time.time()) + self.lastmarker = key + +- open(self.logfile, 'ab+').write( +- b'%s%s\n' +- % (self.markerPrefix, key.encode('utf-8')) +- ) ++ with open(self.logfile, 'ab+') as f: ++ f.write( ++ b'%s%s\n' ++ % (self.markerPrefix, key.encode('utf-8')) ++ ) + + def _read_marked_region(self, marker=None): + """Return lines from self.logfile in the marked region. +@@ -122,20 +124,23 @@ class LogCase(object): + logfile = self.logfile + marker = marker or self.lastmarker + if marker is None: +- return open(logfile, 'rb').readlines() ++ with open(logfile, 'rb') as f: ++ return f.readlines() + + if isinstance(marker, str): + marker = marker.encode('utf-8') + data = [] + in_region = False +- for line in open(logfile, 'rb'): +- if in_region: +- if line.startswith(self.markerPrefix) and marker not in line: +- break +- else: +- data.append(line) +- elif marker in line: +- in_region = True ++ with open(logfile, 'rb') as f: ++ for line in f: ++ if in_region: ++ if (line.startswith(self.markerPrefix) ++ and marker not in line): ++ break ++ else: ++ data.append(line) ++ elif marker in line: ++ in_region = True + return data + + def assertInLog(self, line, marker=None): +diff --git a/cherrypy/test/modfastcgi.py b/cherrypy/test/modfastcgi.py +index 79ec3d18..0c6d01e2 100644 +--- a/cherrypy/test/modfastcgi.py ++++ b/cherrypy/test/modfastcgi.py +@@ -112,15 +112,12 @@ class ModFCGISupervisor(helper.LocalWSGISupervisor): + fcgiconf = os.path.join(curdir, fcgiconf) + + # Write the Apache conf file. +- f = open(fcgiconf, 'wb') +- try: ++ with open(fcgiconf, 'wb') as f: + server = repr(os.path.join(curdir, 'fastcgi.pyc'))[1:-1] + output = self.template % {'port': self.port, 'root': curdir, + 'server': server} + output = output.replace('\r\n', '\n') + f.write(output) +- finally: +- f.close() + + result = read_process(APACHE_PATH, '-k start -f %s' % fcgiconf) + if result: +diff --git a/cherrypy/test/modfcgid.py b/cherrypy/test/modfcgid.py +index d101bd67..ea373004 100644 +--- a/cherrypy/test/modfcgid.py ++++ b/cherrypy/test/modfcgid.py +@@ -101,15 +101,12 @@ class ModFCGISupervisor(helper.LocalSupervisor): + fcgiconf = os.path.join(curdir, fcgiconf) + + # Write the Apache conf file. +- f = open(fcgiconf, 'wb') +- try: ++ with open(fcgiconf, 'wb') as f: + server = repr(os.path.join(curdir, 'fastcgi.pyc'))[1:-1] + output = self.template % {'port': self.port, 'root': curdir, + 'server': server} + output = ntob(output.replace('\r\n', '\n')) + f.write(output) +- finally: +- f.close() + + result = read_process(APACHE_PATH, '-k start -f %s' % fcgiconf) + if result: +diff --git a/cherrypy/test/modpy.py b/cherrypy/test/modpy.py +index 7c288d2c..024453e9 100644 +--- a/cherrypy/test/modpy.py ++++ b/cherrypy/test/modpy.py +@@ -107,13 +107,10 @@ class ModPythonSupervisor(helper.Supervisor): + if not os.path.isabs(mpconf): + mpconf = os.path.join(curdir, mpconf) + +- f = open(mpconf, 'wb') +- try: ++ with open(mpconf, 'wb') as f: + f.write(self.template % + {'port': self.port, 'modulename': modulename, + 'host': self.host}) +- finally: +- f.close() + + result = read_process(APACHE_PATH, '-k start -f %s' % mpconf) + if result: +diff --git a/cherrypy/test/modwsgi.py b/cherrypy/test/modwsgi.py +index da7d240b..24c72684 100644 +--- a/cherrypy/test/modwsgi.py ++++ b/cherrypy/test/modwsgi.py +@@ -109,14 +109,11 @@ class ModWSGISupervisor(helper.Supervisor): + if not os.path.isabs(mpconf): + mpconf = os.path.join(curdir, mpconf) + +- f = open(mpconf, 'wb') +- try: ++ with open(mpconf, 'wb') as f: + output = (self.template % + {'port': self.port, 'testmod': modulename, + 'curdir': curdir}) + f.write(output) +- finally: +- f.close() + + result = read_process(APACHE_PATH, '-k start -f %s' % mpconf) + if result: +diff --git a/cherrypy/test/test_core.py b/cherrypy/test/test_core.py +index 6fde3a97..42460b3f 100644 +--- a/cherrypy/test/test_core.py ++++ b/cherrypy/test/test_core.py +@@ -586,9 +586,8 @@ class CoreRequestHandlingTest(helper.CPWebCase): + def testFavicon(self): + # favicon.ico is served by staticfile. + icofilename = os.path.join(localDir, '../favicon.ico') +- icofile = open(icofilename, 'rb') +- data = icofile.read() +- icofile.close() ++ with open(icofilename, 'rb') as icofile: ++ data = icofile.read() + + self.getPage('/favicon.ico') + self.assertBody(data) +diff --git a/cherrypy/test/test_states.py b/cherrypy/test/test_states.py +index 28dd6510..d59a4d87 100644 +--- a/cherrypy/test/test_states.py ++++ b/cherrypy/test/test_states.py +@@ -424,11 +424,12 @@ test_case_name: "test_signal_handler_unsubscribe" + p.join() + + # Assert the old handler ran. +- log_lines = list(open(p.error_log, 'rb')) +- assert any( +- line.endswith(b'I am an old SIGTERM handler.\n') +- for line in log_lines +- ) ++ with open(p.error_log, 'rb') as f: ++ log_lines = list(f) ++ assert any( ++ line.endswith(b'I am an old SIGTERM handler.\n') ++ for line in log_lines ++ ) + + + def test_safe_wait_INADDR_ANY(): # pylint: disable=invalid-name +-- +2.35.1 + |