From 9992a549c82ea38cfec9dfe24da7594accff60e6 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 19 Oct 2020 14:03:39 -0400 Subject: [PATCH 01/17] Add test capturing deleting zips. From python/cpython#21748. --- importlib_resources/tests/_compat.py | 13 ++++ importlib_resources/tests/test_resource.py | 71 ++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 importlib_resources/tests/_compat.py diff --git a/importlib_resources/tests/_compat.py b/importlib_resources/tests/_compat.py new file mode 100644 index 00000000..a38e542a --- /dev/null +++ b/importlib_resources/tests/_compat.py @@ -0,0 +1,13 @@ +try: + # Python 3.10 + from test.support import import_helper +except ImportError: + class import_helper: + from test.support import modules_setup, modules_cleanup + + +try: + # Python 3.10 + from test.support.os_helper import unlink +except ImportError: + from test.support import unlink # noqa diff --git a/importlib_resources/tests/test_resource.py b/importlib_resources/tests/test_resource.py index 8c5a72cb..aa93d4f1 100644 --- a/importlib_resources/tests/test_resource.py +++ b/importlib_resources/tests/test_resource.py @@ -1,11 +1,15 @@ import sys import unittest import importlib_resources as resources +import uuid + +from importlib_resources._compat import Path from . import data01 from . import zipdata01, zipdata02 from . import util from importlib import import_module +from ._compat import import_helper, unlink class ResourceTests: @@ -166,5 +170,72 @@ def test_namespaces_cannot_have_resources(self): 'importlib_resources.tests.data03.namespace', 'resource1.txt') +class DeletingZipsTest(unittest.TestCase): + """Having accessed resources in a zip file should not keep an open + reference to the zip. + """ + ZIP_MODULE = zipdata01 + + def setUp(self): + modules = import_helper.modules_setup() + self.addCleanup(import_helper.modules_cleanup, *modules) + + data_path = Path(self.ZIP_MODULE.__file__) + data_dir = data_path.parent + self.source_zip_path = data_dir / 'ziptestdata.zip' + self.zip_path = Path.cwd() / '{}.zip'.format(uuid.uuid4()) + self.zip_path.write_bytes(self.source_zip_path.read_bytes()) + sys.path.append(str(self.zip_path)) + self.data = import_module('ziptestdata') + + def tearDown(self): + try: + sys.path.remove(str(self.zip_path)) + except ValueError: + pass + + try: + del sys.path_importer_cache[str(self.zip_path)] + del sys.modules[self.data.__name__] + except KeyError: + pass + + try: + unlink(self.zip_path) + except OSError: + # If the test fails, this will probably fail too + pass + + def test_contents_does_not_keep_open(self): + c = resources.contents('ziptestdata') + self.zip_path.unlink() + + def test_is_resource_does_not_keep_open(self): + c = resources.is_resource('ziptestdata', 'binary.file') + self.zip_path.unlink() + + def test_is_resource_failure_does_not_keep_open(self): + c = resources.is_resource('ziptestdata', 'not-present') + self.zip_path.unlink() + + def test_path_does_not_keep_open(self): + c = resources.path('ziptestdata', 'binary.file') + self.zip_path.unlink() + + def test_entered_path_does_not_keep_open(self): + # This is what certifi does on import to make its bundle + # available for the process duration. + c = resources.path('ziptestdata', 'binary.file').__enter__() + self.zip_path.unlink() + + def test_read_binary_does_not_keep_open(self): + c = resources.read_binary('ziptestdata', 'binary.file') + self.zip_path.unlink() + + def test_read_text_does_not_keep_open(self): + c = resources.read_text('ziptestdata', 'utf-8.file', encoding='utf-8') + self.zip_path.unlink() + + if __name__ == '__main__': unittest.main() From 87361b1473b936370795adf262fe95f2ff1daa9a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 19 Oct 2020 14:06:28 -0400 Subject: [PATCH 02/17] =?UTF-8?q?=F0=9F=91=B9=20Feed=20the=20hobgoblins=20?= =?UTF-8?q?(delint).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- importlib_resources/tests/test_resource.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/importlib_resources/tests/test_resource.py b/importlib_resources/tests/test_resource.py index aa93d4f1..d9c390e8 100644 --- a/importlib_resources/tests/test_resource.py +++ b/importlib_resources/tests/test_resource.py @@ -209,32 +209,39 @@ def tearDown(self): def test_contents_does_not_keep_open(self): c = resources.contents('ziptestdata') self.zip_path.unlink() + del c def test_is_resource_does_not_keep_open(self): c = resources.is_resource('ziptestdata', 'binary.file') self.zip_path.unlink() + del c def test_is_resource_failure_does_not_keep_open(self): c = resources.is_resource('ziptestdata', 'not-present') self.zip_path.unlink() + del c def test_path_does_not_keep_open(self): c = resources.path('ziptestdata', 'binary.file') self.zip_path.unlink() + del c def test_entered_path_does_not_keep_open(self): # This is what certifi does on import to make its bundle # available for the process duration. c = resources.path('ziptestdata', 'binary.file').__enter__() self.zip_path.unlink() + del c def test_read_binary_does_not_keep_open(self): c = resources.read_binary('ziptestdata', 'binary.file') self.zip_path.unlink() + del c def test_read_text_does_not_keep_open(self): c = resources.read_text('ziptestdata', 'utf-8.file', encoding='utf-8') self.zip_path.unlink() + del c if __name__ == '__main__': From 5d047c90d444eff9ed3e011ba67b60fb83805eaa Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 19 Oct 2020 14:43:47 -0400 Subject: [PATCH 03/17] Ensure unlink gets fspath treatment --- importlib_resources/tests/_compat.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/importlib_resources/tests/_compat.py b/importlib_resources/tests/_compat.py index a38e542a..c70e8690 100644 --- a/importlib_resources/tests/_compat.py +++ b/importlib_resources/tests/_compat.py @@ -6,8 +6,18 @@ class import_helper: from test.support import modules_setup, modules_cleanup +try: + from os import fspath +except ImportError: + # Python 3.5 + fspath = str + + try: # Python 3.10 from test.support.os_helper import unlink except ImportError: - from test.support import unlink # noqa + from test.support import unlink as _unlink + + def unlink(target): + return _unlink(fspath(target)) From 922871bb5d3c4a0e7e947ae8c62a9b79ddc50368 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 19 Oct 2020 14:56:28 -0400 Subject: [PATCH 04/17] Add Python 2.7 compatibility for modules_setup/cleanup --- importlib_resources/tests/_compat.py | 13 ++++++++++--- importlib_resources/tests/py27compat.py | 23 +++++++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 importlib_resources/tests/py27compat.py diff --git a/importlib_resources/tests/_compat.py b/importlib_resources/tests/_compat.py index c70e8690..42f36d2f 100644 --- a/importlib_resources/tests/_compat.py +++ b/importlib_resources/tests/_compat.py @@ -1,9 +1,16 @@ try: - # Python 3.10 from test.support import import_helper except ImportError: - class import_helper: - from test.support import modules_setup, modules_cleanup + try: + # Python 3.9 and earlier + class import_helper: + from test.support import modules_setup, modules_cleanup + except ImportError: + from . import py27compat + + class import_helper: + modules_setup = staticmethod(py27compat.modules_setup) + modules_cleanup = staticmethod(py27compat.modules_cleanup) try: diff --git a/importlib_resources/tests/py27compat.py b/importlib_resources/tests/py27compat.py new file mode 100644 index 00000000..90c959e5 --- /dev/null +++ b/importlib_resources/tests/py27compat.py @@ -0,0 +1,23 @@ +import sys + + +def modules_setup(): + return sys.modules.copy(), + + +def modules_cleanup(oldmodules): + # Encoders/decoders are registered permanently within the internal + # codec cache. If we destroy the corresponding modules their + # globals will be set to None which will trip up the cached functions. + encodings = [(k, v) for k, v in sys.modules.items() + if k.startswith('encodings.')] + sys.modules.clear() + sys.modules.update(encodings) + # XXX: This kind of problem can affect more than just encodings. + # In particular extension modules (such as _ssl) don't cope + # with reloading properly. Really, test modules should be cleaning + # out the test specific modules they know they added (ala test_runpy) + # rather than relying on this function (as test_importhooks and test_pkg + # do currently). Implicitly imported *real* modules should be left alone + # (see issue 10556). + sys.modules.update(oldmodules) From 6ab2b584448e2a67e87092791f92046b744df470 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 19 Oct 2020 17:25:16 -0400 Subject: [PATCH 05/17] Defer construction of the zipfile.Path and release the handle after grabbing a temporary file. Closes #110. --- importlib_resources/_common.py | 6 +++++- importlib_resources/readers.py | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/importlib_resources/_common.py b/importlib_resources/_common.py index f54c78d7..4028a43c 100644 --- a/importlib_resources/_common.py +++ b/importlib_resources/_common.py @@ -108,7 +108,11 @@ def as_file(path): Given a Traversable object, return that object as a path on the local file system in a context manager. """ - with _tempfile(path.read_bytes, suffix=path.name) as local: + reader = path.read_bytes + with _tempfile(reader, suffix=path.name) as local: + # release the handle to the path and reader + del reader + del path yield local diff --git a/importlib_resources/readers.py b/importlib_resources/readers.py index 0e0b17ab..3b07c76c 100644 --- a/importlib_resources/readers.py +++ b/importlib_resources/readers.py @@ -22,8 +22,8 @@ def files(self): class ZipReader(abc.TraversableResources): def __init__(self, loader, module): _, _, name = module.rpartition('.') - prefix = loader.prefix.replace('\\', '/') + name + '/' - self.path = ZipPath(loader.archive, prefix) + self.prefix = loader.prefix.replace('\\', '/') + name + '/' + self.archive = loader.archive def open_resource(self, resource): try: @@ -38,4 +38,4 @@ def is_resource(self, path): return target.is_file() and target.exists() def files(self): - return self.path + return ZipPath(self.archive, self.prefix) From 01a2ed4fa9cc8e9da28cbfd3a7c0cb504d787fca Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 20 Oct 2020 09:21:23 -0400 Subject: [PATCH 06/17] Try sleeping to see if that gives gc time to clean up and close the zipfile. --- importlib_resources/_common.py | 1 + 1 file changed, 1 insertion(+) diff --git a/importlib_resources/_common.py b/importlib_resources/_common.py index 4028a43c..0625c2b9 100644 --- a/importlib_resources/_common.py +++ b/importlib_resources/_common.py @@ -113,6 +113,7 @@ def as_file(path): # release the handle to the path and reader del reader del path + __import__('time').sleep(.5) yield local From 0410114959879919b9971ad3268ff9564e55e34e Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 20 Oct 2020 09:53:45 -0400 Subject: [PATCH 07/17] Also delete references to reader in _path_from_reader --- importlib_resources/_common.py | 1 - importlib_resources/_py3.py | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/importlib_resources/_common.py b/importlib_resources/_common.py index 0625c2b9..4028a43c 100644 --- a/importlib_resources/_common.py +++ b/importlib_resources/_common.py @@ -113,7 +113,6 @@ def as_file(path): # release the handle to the path and reader del reader del path - __import__('time').sleep(.5) yield local diff --git a/importlib_resources/_py3.py b/importlib_resources/_py3.py index 5998f215..af112037 100644 --- a/importlib_resources/_py3.py +++ b/importlib_resources/_py3.py @@ -109,6 +109,8 @@ def _path_from_reader(reader, resource): return opener_reader = reader.open_resource(norm_resource) with _common._tempfile(opener_reader.read, suffix=norm_resource) as res: + del opener_reader + del reader yield res From e838d1b7084c0e62587053f51345fadcd0480b06 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 20 Oct 2020 10:12:33 -0400 Subject: [PATCH 08/17] Also delete the reader in _tempfile --- importlib_resources/_common.py | 1 + 1 file changed, 1 insertion(+) diff --git a/importlib_resources/_common.py b/importlib_resources/_common.py index 4028a43c..b4d538ce 100644 --- a/importlib_resources/_common.py +++ b/importlib_resources/_common.py @@ -93,6 +93,7 @@ def _tempfile(reader, suffix=''): try: os.write(fd, reader()) os.close(fd) + del reader yield Path(raw_path) finally: try: From f661cb4e7f130e8722c0a66db92367b27b97d280 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 21 Oct 2020 11:12:11 -0400 Subject: [PATCH 09/17] This approach begins to work, but it requires as_file to be aware of the internals of zipfile.Path. --- importlib_resources/_common.py | 15 +++++++++------ importlib_resources/_py3.py | 11 +++++++++-- tox.ini | 4 ++-- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/importlib_resources/_common.py b/importlib_resources/_common.py index b4d538ce..1d96b61c 100644 --- a/importlib_resources/_common.py +++ b/importlib_resources/_common.py @@ -93,7 +93,6 @@ def _tempfile(reader, suffix=''): try: os.write(fd, reader()) os.close(fd) - del reader yield Path(raw_path) finally: try: @@ -109,11 +108,15 @@ def as_file(path): Given a Traversable object, return that object as a path on the local file system in a context manager. """ - reader = path.read_bytes - with _tempfile(reader, suffix=path.name) as local: - # release the handle to the path and reader - del reader - del path + content = path.read_bytes() + name = path.name + + def reader(): + return content + + del path.root + del path + with _tempfile(reader, suffix=name) as local: yield local diff --git a/importlib_resources/_py3.py b/importlib_resources/_py3.py index af112037..209ce24d 100644 --- a/importlib_resources/_py3.py +++ b/importlib_resources/_py3.py @@ -96,11 +96,18 @@ def path( return ( _path_from_reader(reader, resource) if reader else - _common.as_file( - _common.files(package).joinpath(_common.normalize_path(resource))) + _fallback_path(package, resource) ) +@contextmanager +def _fallback_path(package, resource): + files = _common.files(package).joinpath(_common.normalize_path(resource)) + with _common.as_file(files) as res: + del files + yield res + + @contextmanager def _path_from_reader(reader, resource): norm_resource = _common.normalize_path(resource) diff --git a/tox.ini b/tox.ini index 2aeb8476..9366e2ca 100644 --- a/tox.ini +++ b/tox.ini @@ -7,8 +7,8 @@ toxworkdir={env:TOX_WORK_DIR:.tox} [testenv] commands = - !cov,!diffcov: python -m unittest discover - cov,diffcov: python -m coverage run {[coverage]rc} -m unittest discover {posargs} + !cov,!diffcov: python -m unittest {posargs:discover} + cov,diffcov: python -m coverage run {[coverage]rc} -m unittest {posargs:posargs} cov,diffcov: python -m coverage combine {[coverage]rc} cov: python -m coverage html {[coverage]rc} cov: python -m coverage xml {[coverage]rc} From 4657df76608f3587e56d69abadffedccb988241f Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 21 Oct 2020 12:58:12 -0400 Subject: [PATCH 10/17] Remove unnecessary context managers. --- importlib_resources/_common.py | 13 ++----------- importlib_resources/_py3.py | 11 ++--------- 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/importlib_resources/_common.py b/importlib_resources/_common.py index 1d96b61c..a7c2bf81 100644 --- a/importlib_resources/_common.py +++ b/importlib_resources/_common.py @@ -93,6 +93,7 @@ def _tempfile(reader, suffix=''): try: os.write(fd, reader()) os.close(fd) + del reader yield Path(raw_path) finally: try: @@ -102,22 +103,12 @@ def _tempfile(reader, suffix=''): @singledispatch -@contextlib.contextmanager def as_file(path): """ Given a Traversable object, return that object as a path on the local file system in a context manager. """ - content = path.read_bytes() - name = path.name - - def reader(): - return content - - del path.root - del path - with _tempfile(reader, suffix=name) as local: - yield local + return _tempfile(path.read_bytes, suffix=path.name) @as_file.register(Path) diff --git a/importlib_resources/_py3.py b/importlib_resources/_py3.py index 209ce24d..af112037 100644 --- a/importlib_resources/_py3.py +++ b/importlib_resources/_py3.py @@ -96,18 +96,11 @@ def path( return ( _path_from_reader(reader, resource) if reader else - _fallback_path(package, resource) + _common.as_file( + _common.files(package).joinpath(_common.normalize_path(resource))) ) -@contextmanager -def _fallback_path(package, resource): - files = _common.files(package).joinpath(_common.normalize_path(resource)) - with _common.as_file(files) as res: - del files - yield res - - @contextmanager def _path_from_reader(reader, resource): norm_resource = _common.normalize_path(resource) From eb8570c25fc80be6431c3a43f218daabd08f4911 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 21 Oct 2020 15:01:10 -0400 Subject: [PATCH 11/17] Refactor _path_from_reader to avoid introduction of extraneous context managers. --- importlib_resources/_py3.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/importlib_resources/_py3.py b/importlib_resources/_py3.py index af112037..7b535d0b 100644 --- a/importlib_resources/_py3.py +++ b/importlib_resources/_py3.py @@ -1,8 +1,9 @@ import os import sys +import io from . import _common -from contextlib import contextmanager, suppress +from contextlib import suppress from importlib.abc import ResourceLoader from io import BytesIO, TextIOWrapper from pathlib import Path @@ -94,24 +95,26 @@ def path( """ reader = _common.get_resource_reader(_common.get_package(package)) return ( - _path_from_reader(reader, resource) + _path_from_reader(reader, _common.normalize_path(resource)) if reader else _common.as_file( _common.files(package).joinpath(_common.normalize_path(resource))) ) -@contextmanager def _path_from_reader(reader, resource): - norm_resource = _common.normalize_path(resource) + return _path_from_resource_path(reader, resource) or \ + _path_from_open_resource(reader, resource) + + +def _path_from_resource_path(reader, resource): with suppress(FileNotFoundError): - yield Path(reader.resource_path(norm_resource)) - return - opener_reader = reader.open_resource(norm_resource) - with _common._tempfile(opener_reader.read, suffix=norm_resource) as res: - del opener_reader - del reader - yield res + return Path(reader.resource_path(resource)) + + +def _path_from_open_resource(reader, resource): + saved = io.BytesIO(reader.open_resource(resource).read()) + return _common._tempfile(saved.read, suffix=resource) def is_resource(package: Package, name: str) -> bool: From b0809487a3256aeb9af1e24349e26801ea69363d Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 21 Oct 2020 15:27:52 -0400 Subject: [PATCH 12/17] Skip this test, because it's difficult to achieve. --- importlib_resources/tests/test_resource.py | 1 + 1 file changed, 1 insertion(+) diff --git a/importlib_resources/tests/test_resource.py b/importlib_resources/tests/test_resource.py index d9c390e8..0733ccf7 100644 --- a/importlib_resources/tests/test_resource.py +++ b/importlib_resources/tests/test_resource.py @@ -221,6 +221,7 @@ def test_is_resource_failure_does_not_keep_open(self): self.zip_path.unlink() del c + @unittest.skip("Desired but not supported.") def test_path_does_not_keep_open(self): c = resources.path('ziptestdata', 'binary.file') self.zip_path.unlink() From 0fae833adbdb56a93ff5e49fa53d35f466322a0d Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 21 Oct 2020 16:23:15 -0400 Subject: [PATCH 13/17] Ignore types on compatibility objects --- importlib_resources/tests/_compat.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/importlib_resources/tests/_compat.py b/importlib_resources/tests/_compat.py index 42f36d2f..edadf450 100644 --- a/importlib_resources/tests/_compat.py +++ b/importlib_resources/tests/_compat.py @@ -1,23 +1,23 @@ try: - from test.support import import_helper + from test.support import import_helper # type: ignore except ImportError: try: # Python 3.9 and earlier - class import_helper: + class import_helper: # type: ignore from test.support import modules_setup, modules_cleanup except ImportError: from . import py27compat - class import_helper: + class import_helper: # type: ignore modules_setup = staticmethod(py27compat.modules_setup) modules_cleanup = staticmethod(py27compat.modules_cleanup) try: - from os import fspath + from os import fspath # type: ignore except ImportError: # Python 3.5 - fspath = str + fspath = str # type: ignore try: From f85c3e18f12744c2c1021aebfa0fae283b78a323 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 21 Oct 2020 16:27:18 -0400 Subject: [PATCH 14/17] Fix posargs for cov, diffcov --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 9366e2ca..d466a725 100644 --- a/tox.ini +++ b/tox.ini @@ -8,7 +8,7 @@ toxworkdir={env:TOX_WORK_DIR:.tox} [testenv] commands = !cov,!diffcov: python -m unittest {posargs:discover} - cov,diffcov: python -m coverage run {[coverage]rc} -m unittest {posargs:posargs} + cov,diffcov: python -m coverage run {[coverage]rc} -m unittest {posargs:discover} cov,diffcov: python -m coverage combine {[coverage]rc} cov: python -m coverage html {[coverage]rc} cov: python -m coverage xml {[coverage]rc} From c929cbca3358ecb12d87dc0415823666f2d9c038 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 21 Oct 2020 16:39:41 -0400 Subject: [PATCH 15/17] Exclude zip files from coverage so Delete tests don't trip up coverage. --- coverage.ini | 3 ++- importlib_resources/tests/test_resource.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/coverage.ini b/coverage.ini index adcb579d..da82b3df 100644 --- a/coverage.ini +++ b/coverage.ini @@ -2,7 +2,7 @@ branch = true parallel = true omit = - setup* + setup* .tox/*/lib/python*/site-packages/* */tests/*.py */testing/*.py @@ -10,6 +10,7 @@ omit = importlib_resources/__init__.py importlib_resources/_compat.py importlib_resources/abc.py + *.zip* plugins = coverplug diff --git a/importlib_resources/tests/test_resource.py b/importlib_resources/tests/test_resource.py index 0733ccf7..6920ac16 100644 --- a/importlib_resources/tests/test_resource.py +++ b/importlib_resources/tests/test_resource.py @@ -183,7 +183,7 @@ def setUp(self): data_path = Path(self.ZIP_MODULE.__file__) data_dir = data_path.parent self.source_zip_path = data_dir / 'ziptestdata.zip' - self.zip_path = Path.cwd() / '{}.zip'.format(uuid.uuid4()) + self.zip_path = Path('{}.zip'.format(uuid.uuid4())).absolute() self.zip_path.write_bytes(self.source_zip_path.read_bytes()) sys.path.append(str(self.zip_path)) self.data = import_module('ziptestdata') From 9b018ff8c29511b22741fdcc2d3ac3ad66795af9 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 21 Oct 2020 16:46:48 -0400 Subject: [PATCH 16/17] Consolidate import_helper functions. --- importlib_resources/tests/util.py | 29 +++-------------------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/importlib_resources/tests/util.py b/importlib_resources/tests/util.py index 8c26496d..1d6fa729 100644 --- a/importlib_resources/tests/util.py +++ b/importlib_resources/tests/util.py @@ -9,30 +9,7 @@ from . import zipdata01 from .._compat import ABC, Path, PurePath, FileNotFoundError from ..abc import ResourceReader - -try: - from test.support import modules_setup, modules_cleanup -except ImportError: - # Python 2.7. - def modules_setup(): - return sys.modules.copy(), - - def modules_cleanup(oldmodules): - # Encoders/decoders are registered permanently within the internal - # codec cache. If we destroy the corresponding modules their - # globals will be set to None which will trip up the cached functions. - encodings = [(k, v) for k, v in sys.modules.items() - if k.startswith('encodings.')] - sys.modules.clear() - sys.modules.update(encodings) - # XXX: This kind of problem can affect more than just encodings. In - # particular extension modules (such as _ssl) don't cope with reloading - # properly. Really, test modules should be cleaning out the test - # specific modules they know they added (ala test_runpy) rather than - # relying on this function (as test_importhooks and test_pkg do - # currently). Implicitly imported *real* modules should be left alone - # (see issue 10556). - sys.modules.update(oldmodules) +from ._compat import import_helper try: @@ -205,8 +182,8 @@ def tearDownClass(cls): pass def setUp(self): - modules = modules_setup() - self.addCleanup(modules_cleanup, *modules) + modules = import_helper.modules_setup() + self.addCleanup(import_helper.modules_cleanup, *modules) class ZipSetup(ZipSetupBase): From 522f271d4cd7f790235a19efad40f963a7e3387a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 22 Oct 2020 15:02:05 -0400 Subject: [PATCH 17/17] Update changelog. --- docs/changelog.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index bff615dd..113d7f2d 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -2,6 +2,14 @@ importlib_resources NEWS ========================== +v3.1.0 +====== + +* #110 and bpo-41490: ``path`` method is more aggressive about + releasing handles to zipfile objects early, enabling use-cases + like ``certifi`` to leave the context open but delete the underlying + zip file. + v3.0.0 ======