summaryrefslogtreecommitdiffstats
path: root/Lib/importlib/resources.py
diff options
context:
space:
mode:
authorJason R. Coombs <jaraco@jaraco.com>2020-10-25 18:21:46 (GMT)
committerGitHub <noreply@github.com>2020-10-25 18:21:46 (GMT)
commitdf8d4c83a6e1727e766191896aeabde886979587 (patch)
treea286f28927d1cd736787880784d2c1af908b45b8 /Lib/importlib/resources.py
parentc32f2976b8f4034724c3270397aa16f38daf470f (diff)
downloadcpython-df8d4c83a6e1727e766191896aeabde886979587.zip
cpython-df8d4c83a6e1727e766191896aeabde886979587.tar.gz
cpython-df8d4c83a6e1727e766191896aeabde886979587.tar.bz2
bpo-41490: ``path`` and ``contents`` to aggressively close handles (#22915)
* bpo-41490: ``path`` method to aggressively close handles * Add blurb * In ZipReader.contents, eagerly evaluate the contents to release references to the zipfile. * Instead use _ensure_sequence to ensure any iterable from a reader is eagerly converted to a list if it's not already a sequence.
Diffstat (limited to 'Lib/importlib/resources.py')
-rw-r--r--Lib/importlib/resources.py37
1 files changed, 27 insertions, 10 deletions
diff --git a/Lib/importlib/resources.py b/Lib/importlib/resources.py
index 4535619..4169171 100644
--- a/Lib/importlib/resources.py
+++ b/Lib/importlib/resources.py
@@ -1,8 +1,9 @@
import os
+import io
from . import _common
from ._common import as_file, files
-from contextlib import contextmanager, suppress
+from contextlib import suppress
from importlib.abc import ResourceLoader
from io import BytesIO, TextIOWrapper
from pathlib import Path
@@ -10,6 +11,8 @@ from types import ModuleType
from typing import ContextManager, Iterable, Union
from typing import cast
from typing.io import BinaryIO, TextIO
+from collections.abc import Sequence
+from functools import singledispatch
__all__ = [
@@ -102,22 +105,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:
- 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:
@@ -146,7 +153,7 @@ def contents(package: Package) -> Iterable[str]:
package = _common.get_package(package)
reader = _common.get_resource_reader(package)
if reader is not None:
- return reader.contents()
+ return _ensure_sequence(reader.contents())
# Is the package a namespace package? By definition, namespace packages
# cannot have resources.
namespace = (
@@ -156,3 +163,13 @@ def contents(package: Package) -> Iterable[str]:
if namespace or not package.__spec__.has_location:
return ()
return list(item.name for item in _common.from_package(package).iterdir())
+
+
+@singledispatch
+def _ensure_sequence(iterable):
+ return list(iterable)
+
+
+@_ensure_sequence.register(Sequence)
+def _(iterable):
+ return iterable