Gentoo Archives: gentoo-commits

From: "Michał Górny" <mgorny@g.o>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] repo/gentoo:master commit in: dev-python/cherrypy/files/, dev-python/cherrypy/
Date: Mon, 31 Jan 2022 22:28:06
Message-Id: 1643668072.b09cd1265ba60e15dbb549d18cc10b9b11e424f7.mgorny@gentoo
1 commit: b09cd1265ba60e15dbb549d18cc10b9b11e424f7
2 Author: Michał Górny <mgorny <AT> gentoo <DOT> org>
3 AuthorDate: Mon Jan 31 22:08:19 2022 +0000
4 Commit: Michał Górny <mgorny <AT> gentoo <DOT> org>
5 CommitDate: Mon Jan 31 22:27:52 2022 +0000
6 URL: https://gitweb.gentoo.org/repo/gentoo.git/commit/?id=b09cd126
7
8 dev-python/cherrypy: Add pypy3 love
9
10 Signed-off-by: Michał Górny <mgorny <AT> gentoo.org>
11
12 dev-python/cherrypy/cherrypy-18.6.1.ebuild | 9 +-
13 .../files/cherrypy-18.6.1-close-files.patch | 416 +++++++++++++++++++++
14 2 files changed, 423 insertions(+), 2 deletions(-)
15
16 diff --git a/dev-python/cherrypy/cherrypy-18.6.1.ebuild b/dev-python/cherrypy/cherrypy-18.6.1.ebuild
17 index a039a64ffe1e..d1177e6159ad 100644
18 --- a/dev-python/cherrypy/cherrypy-18.6.1.ebuild
19 +++ b/dev-python/cherrypy/cherrypy-18.6.1.ebuild
20 @@ -1,9 +1,9 @@
21 -# Copyright 1999-2021 Gentoo Authors
22 +# Copyright 1999-2022 Gentoo Authors
23 # Distributed under the terms of the GNU General Public License v2
24
25 EAPI=8
26
27 -PYTHON_COMPAT=( python3_{8..10} )
28 +PYTHON_COMPAT=( python3_{8..10} pypy3 )
29 inherit distutils-r1
30
31 MY_P="CherryPy-${PV}"
32 @@ -40,6 +40,11 @@ BDEPEND="
33 distutils_enable_tests pytest
34
35 python_prepare_all() {
36 + local PATCHES=(
37 + # https://github.com/cherrypy/cherrypy/pull/1946
38 + "${FILESDIR}"/${P}-close-files.patch
39 + )
40 +
41 sed -r -e '/(pytest-sugar|pytest-cov)/ d' \
42 -i setup.py || die
43
44
45 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
46 new file mode 100644
47 index 000000000000..478d717dfb8c
48 --- /dev/null
49 +++ b/dev-python/cherrypy/files/cherrypy-18.6.1-close-files.patch
50 @@ -0,0 +1,416 @@
51 +From 94a2cc036203c6da55174ef3b105c0c875bbc79f Mon Sep 17 00:00:00 2001
52 +From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= <mgorny@g.o>
53 +Date: Mon, 31 Jan 2022 22:25:34 +0100
54 +Subject: [PATCH] Use context managers to close files properly and fix tests on
55 + PyPy
56 +
57 +Use context managers (`with`) to ensure that all open files are closed
58 +correctly. This resolves resource leaks and test failures with PyPy3.7.
59 +
60 +The code prior to this change used four approaches for closing files:
61 +
62 +1. Using a context manager (`with` clause).
63 +
64 +2. Using a try/finally clause.
65 +
66 +3. Closing the file in the same scope (unreliable: file object can leak
67 + on exception).
68 +
69 +4. Not closing open files at all.
70 +
71 +The last point is a real problem for PyPy since it does not GC
72 +unreachable objects as aggressively as CPython does. While leaving
73 +a function scope on CPython causes the file objects private to it to
74 +be destroyed (and therefore closed), in PyPy they can stay dangling
75 +for some time. When combines with buffered writes, this means that
76 +writes can still remain pending after returning from function.
77 +
78 +Using a context manager is a simple, consistent way to ensure that
79 +the file object is closed once it is no longer needed. In turn, this
80 +guarantees that all pending writes will be performed upon function
81 +return and the code won't be hiting race conditions between writing
82 +a file and reading it afterwards.
83 +---
84 + cherrypy/_cperror.py | 3 ++-
85 + cherrypy/_cpmodpy.py | 5 +----
86 + cherrypy/lib/auth_digest.py | 13 ++++++------
87 + cherrypy/lib/covercp.py | 40 ++++++++++++++++++------------------
88 + cherrypy/lib/reprconf.py | 5 +----
89 + cherrypy/lib/sessions.py | 10 ++-------
90 + cherrypy/process/plugins.py | 3 ++-
91 + cherrypy/test/helper.py | 3 ++-
92 + cherrypy/test/logtest.py | 33 ++++++++++++++++-------------
93 + cherrypy/test/modfastcgi.py | 5 +----
94 + cherrypy/test/modfcgid.py | 5 +----
95 + cherrypy/test/modpy.py | 5 +----
96 + cherrypy/test/modwsgi.py | 5 +----
97 + cherrypy/test/test_core.py | 5 ++---
98 + cherrypy/test/test_states.py | 11 +++++-----
99 + 15 files changed, 67 insertions(+), 84 deletions(-)
100 +
101 +diff --git a/cherrypy/_cperror.py b/cherrypy/_cperror.py
102 +index 4e727682..ebf1dcf6 100644
103 +--- a/cherrypy/_cperror.py
104 ++++ b/cherrypy/_cperror.py
105 +@@ -532,7 +532,8 @@ def get_error_page(status, **kwargs):
106 + return result
107 + else:
108 + # Load the template from this path.
109 +- template = io.open(error_page, newline='').read()
110 ++ with io.open(error_page, newline='') as f:
111 ++ template = f.read()
112 + except Exception:
113 + e = _format_exception(*_exc_info())[-1]
114 + m = kwargs['message']
115 +diff --git a/cherrypy/_cpmodpy.py b/cherrypy/_cpmodpy.py
116 +index 0e608c48..a08f0ed9 100644
117 +--- a/cherrypy/_cpmodpy.py
118 ++++ b/cherrypy/_cpmodpy.py
119 +@@ -339,11 +339,8 @@ LoadModule python_module modules/mod_python.so
120 + }
121 +
122 + mpconf = os.path.join(os.path.dirname(__file__), 'cpmodpy.conf')
123 +- f = open(mpconf, 'wb')
124 +- try:
125 ++ with open(mpconf, 'wb') as f:
126 + f.write(conf_data)
127 +- finally:
128 +- f.close()
129 +
130 + response = read_process(self.apache_path, '-k start -f %s' % mpconf)
131 + self.ready = True
132 +diff --git a/cherrypy/lib/auth_digest.py b/cherrypy/lib/auth_digest.py
133 +index fbb5df64..981e9a5d 100644
134 +--- a/cherrypy/lib/auth_digest.py
135 ++++ b/cherrypy/lib/auth_digest.py
136 +@@ -101,13 +101,12 @@ def get_ha1_file_htdigest(filename):
137 + """
138 + def get_ha1(realm, username):
139 + result = None
140 +- f = open(filename, 'r')
141 +- for line in f:
142 +- u, r, ha1 = line.rstrip().split(':')
143 +- if u == username and r == realm:
144 +- result = ha1
145 +- break
146 +- f.close()
147 ++ with open(filename, 'r') as f:
148 ++ for line in f:
149 ++ u, r, ha1 = line.rstrip().split(':')
150 ++ if u == username and r == realm:
151 ++ result = ha1
152 ++ break
153 + return result
154 +
155 + return get_ha1
156 +diff --git a/cherrypy/lib/covercp.py b/cherrypy/lib/covercp.py
157 +index 3e219713..005fafa5 100644
158 +--- a/cherrypy/lib/covercp.py
159 ++++ b/cherrypy/lib/covercp.py
160 +@@ -334,26 +334,26 @@ class CoverStats(object):
161 + yield '</body></html>'
162 +
163 + def annotated_file(self, filename, statements, excluded, missing):
164 +- source = open(filename, 'r')
165 +- buffer = []
166 +- for lineno, line in enumerate(source.readlines()):
167 +- lineno += 1
168 +- line = line.strip('\n\r')
169 +- empty_the_buffer = True
170 +- if lineno in excluded:
171 +- template = TEMPLATE_LOC_EXCLUDED
172 +- elif lineno in missing:
173 +- template = TEMPLATE_LOC_NOT_COVERED
174 +- elif lineno in statements:
175 +- template = TEMPLATE_LOC_COVERED
176 +- else:
177 +- empty_the_buffer = False
178 +- buffer.append((lineno, line))
179 +- if empty_the_buffer:
180 +- for lno, pastline in buffer:
181 +- yield template % (lno, cgi.escape(pastline))
182 +- buffer = []
183 +- yield template % (lineno, cgi.escape(line))
184 ++ with open(filename, 'r') as source:
185 ++ buffer = []
186 ++ for lineno, line in enumerate(source.readlines()):
187 ++ lineno += 1
188 ++ line = line.strip('\n\r')
189 ++ empty_the_buffer = True
190 ++ if lineno in excluded:
191 ++ template = TEMPLATE_LOC_EXCLUDED
192 ++ elif lineno in missing:
193 ++ template = TEMPLATE_LOC_NOT_COVERED
194 ++ elif lineno in statements:
195 ++ template = TEMPLATE_LOC_COVERED
196 ++ else:
197 ++ empty_the_buffer = False
198 ++ buffer.append((lineno, line))
199 ++ if empty_the_buffer:
200 ++ for lno, pastline in buffer:
201 ++ yield template % (lno, cgi.escape(pastline))
202 ++ buffer = []
203 ++ yield template % (lineno, cgi.escape(line))
204 +
205 + @cherrypy.expose
206 + def report(self, name):
207 +diff --git a/cherrypy/lib/reprconf.py b/cherrypy/lib/reprconf.py
208 +index 3976652e..76381d7b 100644
209 +--- a/cherrypy/lib/reprconf.py
210 ++++ b/cherrypy/lib/reprconf.py
211 +@@ -163,11 +163,8 @@ class Parser(configparser.ConfigParser):
212 + # fp = open(filename)
213 + # except IOError:
214 + # continue
215 +- fp = open(filename)
216 +- try:
217 ++ with open(filename) as fp:
218 + self._read(fp, filename)
219 +- finally:
220 +- fp.close()
221 +
222 + def as_dict(self, raw=False, vars=None):
223 + """Convert an INI file to a dictionary"""
224 +diff --git a/cherrypy/lib/sessions.py b/cherrypy/lib/sessions.py
225 +index 5b3328f2..0f56a4fa 100644
226 +--- a/cherrypy/lib/sessions.py
227 ++++ b/cherrypy/lib/sessions.py
228 +@@ -516,11 +516,8 @@ class FileSession(Session):
229 + if path is None:
230 + path = self._get_file_path()
231 + try:
232 +- f = open(path, 'rb')
233 +- try:
234 ++ with open(path, 'rb') as f:
235 + return pickle.load(f)
236 +- finally:
237 +- f.close()
238 + except (IOError, EOFError):
239 + e = sys.exc_info()[1]
240 + if self.debug:
241 +@@ -531,11 +528,8 @@ class FileSession(Session):
242 + def _save(self, expiration_time):
243 + assert self.locked, ('The session was saved without being locked. '
244 + "Check your tools' priority levels.")
245 +- f = open(self._get_file_path(), 'wb')
246 +- try:
247 ++ with open(self._get_file_path(), 'wb') as f:
248 + pickle.dump((self._data, expiration_time), f, self.pickle_protocol)
249 +- finally:
250 +- f.close()
251 +
252 + def _delete(self):
253 + assert self.locked, ('The session deletion without being locked. '
254 +diff --git a/cherrypy/process/plugins.py b/cherrypy/process/plugins.py
255 +index 2a9952de..e96fb1ce 100644
256 +--- a/cherrypy/process/plugins.py
257 ++++ b/cherrypy/process/plugins.py
258 +@@ -436,7 +436,8 @@ class PIDFile(SimplePlugin):
259 + if self.finalized:
260 + self.bus.log('PID %r already written to %r.' % (pid, self.pidfile))
261 + else:
262 +- open(self.pidfile, 'wb').write(ntob('%s\n' % pid, 'utf8'))
263 ++ with open(self.pidfile, 'wb') as f:
264 ++ f.write(ntob('%s\n' % pid, 'utf8'))
265 + self.bus.log('PID %r written to %r.' % (pid, self.pidfile))
266 + self.finalized = True
267 + start.priority = 70
268 +diff --git a/cherrypy/test/helper.py b/cherrypy/test/helper.py
269 +index c1ca4535..cae49533 100644
270 +--- a/cherrypy/test/helper.py
271 ++++ b/cherrypy/test/helper.py
272 +@@ -505,7 +505,8 @@ server.ssl_private_key: r'%s'
273 +
274 + def get_pid(self):
275 + if self.daemonize:
276 +- return int(open(self.pid_file, 'rb').read())
277 ++ with open(self.pid_file, 'rb') as f:
278 ++ return int(f.read())
279 + return self._proc.pid
280 +
281 + def join(self):
282 +diff --git a/cherrypy/test/logtest.py b/cherrypy/test/logtest.py
283 +index 344be987..112bdc25 100644
284 +--- a/cherrypy/test/logtest.py
285 ++++ b/cherrypy/test/logtest.py
286 +@@ -97,7 +97,8 @@ class LogCase(object):
287 +
288 + def emptyLog(self):
289 + """Overwrite self.logfile with 0 bytes."""
290 +- open(self.logfile, 'wb').write('')
291 ++ with open(self.logfile, 'wb') as f:
292 ++ f.write('')
293 +
294 + def markLog(self, key=None):
295 + """Insert a marker line into the log and set self.lastmarker."""
296 +@@ -105,10 +106,11 @@ class LogCase(object):
297 + key = str(time.time())
298 + self.lastmarker = key
299 +
300 +- open(self.logfile, 'ab+').write(
301 +- b'%s%s\n'
302 +- % (self.markerPrefix, key.encode('utf-8'))
303 +- )
304 ++ with open(self.logfile, 'ab+') as f:
305 ++ f.write(
306 ++ b'%s%s\n'
307 ++ % (self.markerPrefix, key.encode('utf-8'))
308 ++ )
309 +
310 + def _read_marked_region(self, marker=None):
311 + """Return lines from self.logfile in the marked region.
312 +@@ -122,20 +124,23 @@ class LogCase(object):
313 + logfile = self.logfile
314 + marker = marker or self.lastmarker
315 + if marker is None:
316 +- return open(logfile, 'rb').readlines()
317 ++ with open(logfile, 'rb') as f:
318 ++ return f.readlines()
319 +
320 + if isinstance(marker, str):
321 + marker = marker.encode('utf-8')
322 + data = []
323 + in_region = False
324 +- for line in open(logfile, 'rb'):
325 +- if in_region:
326 +- if line.startswith(self.markerPrefix) and marker not in line:
327 +- break
328 +- else:
329 +- data.append(line)
330 +- elif marker in line:
331 +- in_region = True
332 ++ with open(logfile, 'rb') as f:
333 ++ for line in f:
334 ++ if in_region:
335 ++ if (line.startswith(self.markerPrefix)
336 ++ and marker not in line):
337 ++ break
338 ++ else:
339 ++ data.append(line)
340 ++ elif marker in line:
341 ++ in_region = True
342 + return data
343 +
344 + def assertInLog(self, line, marker=None):
345 +diff --git a/cherrypy/test/modfastcgi.py b/cherrypy/test/modfastcgi.py
346 +index 79ec3d18..0c6d01e2 100644
347 +--- a/cherrypy/test/modfastcgi.py
348 ++++ b/cherrypy/test/modfastcgi.py
349 +@@ -112,15 +112,12 @@ class ModFCGISupervisor(helper.LocalWSGISupervisor):
350 + fcgiconf = os.path.join(curdir, fcgiconf)
351 +
352 + # Write the Apache conf file.
353 +- f = open(fcgiconf, 'wb')
354 +- try:
355 ++ with open(fcgiconf, 'wb') as f:
356 + server = repr(os.path.join(curdir, 'fastcgi.pyc'))[1:-1]
357 + output = self.template % {'port': self.port, 'root': curdir,
358 + 'server': server}
359 + output = output.replace('\r\n', '\n')
360 + f.write(output)
361 +- finally:
362 +- f.close()
363 +
364 + result = read_process(APACHE_PATH, '-k start -f %s' % fcgiconf)
365 + if result:
366 +diff --git a/cherrypy/test/modfcgid.py b/cherrypy/test/modfcgid.py
367 +index d101bd67..ea373004 100644
368 +--- a/cherrypy/test/modfcgid.py
369 ++++ b/cherrypy/test/modfcgid.py
370 +@@ -101,15 +101,12 @@ class ModFCGISupervisor(helper.LocalSupervisor):
371 + fcgiconf = os.path.join(curdir, fcgiconf)
372 +
373 + # Write the Apache conf file.
374 +- f = open(fcgiconf, 'wb')
375 +- try:
376 ++ with open(fcgiconf, 'wb') as f:
377 + server = repr(os.path.join(curdir, 'fastcgi.pyc'))[1:-1]
378 + output = self.template % {'port': self.port, 'root': curdir,
379 + 'server': server}
380 + output = ntob(output.replace('\r\n', '\n'))
381 + f.write(output)
382 +- finally:
383 +- f.close()
384 +
385 + result = read_process(APACHE_PATH, '-k start -f %s' % fcgiconf)
386 + if result:
387 +diff --git a/cherrypy/test/modpy.py b/cherrypy/test/modpy.py
388 +index 7c288d2c..024453e9 100644
389 +--- a/cherrypy/test/modpy.py
390 ++++ b/cherrypy/test/modpy.py
391 +@@ -107,13 +107,10 @@ class ModPythonSupervisor(helper.Supervisor):
392 + if not os.path.isabs(mpconf):
393 + mpconf = os.path.join(curdir, mpconf)
394 +
395 +- f = open(mpconf, 'wb')
396 +- try:
397 ++ with open(mpconf, 'wb') as f:
398 + f.write(self.template %
399 + {'port': self.port, 'modulename': modulename,
400 + 'host': self.host})
401 +- finally:
402 +- f.close()
403 +
404 + result = read_process(APACHE_PATH, '-k start -f %s' % mpconf)
405 + if result:
406 +diff --git a/cherrypy/test/modwsgi.py b/cherrypy/test/modwsgi.py
407 +index da7d240b..24c72684 100644
408 +--- a/cherrypy/test/modwsgi.py
409 ++++ b/cherrypy/test/modwsgi.py
410 +@@ -109,14 +109,11 @@ class ModWSGISupervisor(helper.Supervisor):
411 + if not os.path.isabs(mpconf):
412 + mpconf = os.path.join(curdir, mpconf)
413 +
414 +- f = open(mpconf, 'wb')
415 +- try:
416 ++ with open(mpconf, 'wb') as f:
417 + output = (self.template %
418 + {'port': self.port, 'testmod': modulename,
419 + 'curdir': curdir})
420 + f.write(output)
421 +- finally:
422 +- f.close()
423 +
424 + result = read_process(APACHE_PATH, '-k start -f %s' % mpconf)
425 + if result:
426 +diff --git a/cherrypy/test/test_core.py b/cherrypy/test/test_core.py
427 +index 6fde3a97..42460b3f 100644
428 +--- a/cherrypy/test/test_core.py
429 ++++ b/cherrypy/test/test_core.py
430 +@@ -586,9 +586,8 @@ class CoreRequestHandlingTest(helper.CPWebCase):
431 + def testFavicon(self):
432 + # favicon.ico is served by staticfile.
433 + icofilename = os.path.join(localDir, '../favicon.ico')
434 +- icofile = open(icofilename, 'rb')
435 +- data = icofile.read()
436 +- icofile.close()
437 ++ with open(icofilename, 'rb') as icofile:
438 ++ data = icofile.read()
439 +
440 + self.getPage('/favicon.ico')
441 + self.assertBody(data)
442 +diff --git a/cherrypy/test/test_states.py b/cherrypy/test/test_states.py
443 +index 28dd6510..d59a4d87 100644
444 +--- a/cherrypy/test/test_states.py
445 ++++ b/cherrypy/test/test_states.py
446 +@@ -424,11 +424,12 @@ test_case_name: "test_signal_handler_unsubscribe"
447 + p.join()
448 +
449 + # Assert the old handler ran.
450 +- log_lines = list(open(p.error_log, 'rb'))
451 +- assert any(
452 +- line.endswith(b'I am an old SIGTERM handler.\n')
453 +- for line in log_lines
454 +- )
455 ++ with open(p.error_log, 'rb') as f:
456 ++ log_lines = list(f)
457 ++ assert any(
458 ++ line.endswith(b'I am an old SIGTERM handler.\n')
459 ++ for line in log_lines
460 ++ )
461 +
462 +
463 + def test_safe_wait_INADDR_ANY(): # pylint: disable=invalid-name
464 +--
465 +2.35.1
466 +