Coverage for /pythoncovmergedfiles/medio/medio/usr/lib/python3.9/importlib/resources.py: 37%
101 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-10-20 07:00 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-10-20 07:00 +0000
1import os
3from . import abc as resources_abc
4from . import _common
5from ._common import as_file
6from contextlib import contextmanager, suppress
7from importlib import import_module
8from importlib.abc import ResourceLoader
9from io import BytesIO, TextIOWrapper
10from pathlib import Path
11from types import ModuleType
12from typing import ContextManager, Iterable, Optional, Union
13from typing import cast
14from typing.io import BinaryIO, TextIO
17__all__ = [
18 'Package',
19 'Resource',
20 'as_file',
21 'contents',
22 'files',
23 'is_resource',
24 'open_binary',
25 'open_text',
26 'path',
27 'read_binary',
28 'read_text',
29 ]
32Package = Union[str, ModuleType]
33Resource = Union[str, os.PathLike]
36def _resolve(name) -> ModuleType:
37 """If name is a string, resolve to a module."""
38 if hasattr(name, '__spec__'):
39 return name
40 return import_module(name)
43def _get_package(package) -> ModuleType:
44 """Take a package name or module object and return the module.
46 If a name, the module is imported. If the resolved module
47 object is not a package, raise an exception.
48 """
49 module = _resolve(package)
50 if module.__spec__.submodule_search_locations is None:
51 raise TypeError('{!r} is not a package'.format(package))
52 return module
55def _normalize_path(path) -> str:
56 """Normalize a path by ensuring it is a string.
58 If the resulting string contains path separators, an exception is raised.
59 """
60 parent, file_name = os.path.split(path)
61 if parent:
62 raise ValueError('{!r} must be only a file name'.format(path))
63 return file_name
66def _get_resource_reader(
67 package: ModuleType) -> Optional[resources_abc.ResourceReader]:
68 # Return the package's loader if it's a ResourceReader. We can't use
69 # a issubclass() check here because apparently abc.'s __subclasscheck__()
70 # hook wants to create a weak reference to the object, but
71 # zipimport.zipimporter does not support weak references, resulting in a
72 # TypeError. That seems terrible.
73 spec = package.__spec__
74 if hasattr(spec.loader, 'get_resource_reader'):
75 return cast(resources_abc.ResourceReader,
76 spec.loader.get_resource_reader(spec.name))
77 return None
80def _check_location(package):
81 if package.__spec__.origin is None or not package.__spec__.has_location:
82 raise FileNotFoundError(f'Package has no location {package!r}')
85def open_binary(package: Package, resource: Resource) -> BinaryIO:
86 """Return a file-like object opened for binary reading of the resource."""
87 resource = _normalize_path(resource)
88 package = _get_package(package)
89 reader = _get_resource_reader(package)
90 if reader is not None:
91 return reader.open_resource(resource)
92 absolute_package_path = os.path.abspath(
93 package.__spec__.origin or 'non-existent file')
94 package_path = os.path.dirname(absolute_package_path)
95 full_path = os.path.join(package_path, resource)
96 try:
97 return open(full_path, mode='rb')
98 except OSError:
99 # Just assume the loader is a resource loader; all the relevant
100 # importlib.machinery loaders are and an AttributeError for
101 # get_data() will make it clear what is needed from the loader.
102 loader = cast(ResourceLoader, package.__spec__.loader)
103 data = None
104 if hasattr(package.__spec__.loader, 'get_data'):
105 with suppress(OSError):
106 data = loader.get_data(full_path)
107 if data is None:
108 package_name = package.__spec__.name
109 message = '{!r} resource not found in {!r}'.format(
110 resource, package_name)
111 raise FileNotFoundError(message)
112 return BytesIO(data)
115def open_text(package: Package,
116 resource: Resource,
117 encoding: str = 'utf-8',
118 errors: str = 'strict') -> TextIO:
119 """Return a file-like object opened for text reading of the resource."""
120 return TextIOWrapper(
121 open_binary(package, resource), encoding=encoding, errors=errors)
124def read_binary(package: Package, resource: Resource) -> bytes:
125 """Return the binary contents of the resource."""
126 with open_binary(package, resource) as fp:
127 return fp.read()
130def read_text(package: Package,
131 resource: Resource,
132 encoding: str = 'utf-8',
133 errors: str = 'strict') -> str:
134 """Return the decoded string of the resource.
136 The decoding-related arguments have the same semantics as those of
137 bytes.decode().
138 """
139 with open_text(package, resource, encoding, errors) as fp:
140 return fp.read()
143def files(package: Package) -> resources_abc.Traversable:
144 """
145 Get a Traversable resource from a package
146 """
147 return _common.from_package(_get_package(package))
150def path(
151 package: Package, resource: Resource,
152 ) -> 'ContextManager[Path]':
153 """A context manager providing a file path object to the resource.
155 If the resource does not already exist on its own on the file system,
156 a temporary file will be created. If the file was created, the file
157 will be deleted upon exiting the context manager (no exception is
158 raised if the file was deleted prior to the context manager
159 exiting).
160 """
161 reader = _get_resource_reader(_get_package(package))
162 return (
163 _path_from_reader(reader, resource)
164 if reader else
165 _common.as_file(files(package).joinpath(_normalize_path(resource)))
166 )
169@contextmanager
170def _path_from_reader(reader, resource):
171 norm_resource = _normalize_path(resource)
172 with suppress(FileNotFoundError):
173 yield Path(reader.resource_path(norm_resource))
174 return
175 opener_reader = reader.open_resource(norm_resource)
176 with _common._tempfile(opener_reader.read, suffix=norm_resource) as res:
177 yield res
180def is_resource(package: Package, name: str) -> bool:
181 """True if 'name' is a resource inside 'package'.
183 Directories are *not* resources.
184 """
185 package = _get_package(package)
186 _normalize_path(name)
187 reader = _get_resource_reader(package)
188 if reader is not None:
189 return reader.is_resource(name)
190 package_contents = set(contents(package))
191 if name not in package_contents:
192 return False
193 return (_common.from_package(package) / name).is_file()
196def contents(package: Package) -> Iterable[str]:
197 """Return an iterable of entries in 'package'.
199 Note that not all entries are resources. Specifically, directories are
200 not considered resources. Use `is_resource()` on each entry returned here
201 to check if it is a resource or not.
202 """
203 package = _get_package(package)
204 reader = _get_resource_reader(package)
205 if reader is not None:
206 return reader.contents()
207 # Is the package a namespace package? By definition, namespace packages
208 # cannot have resources.
209 namespace = (
210 package.__spec__.origin is None or
211 package.__spec__.origin == 'namespace'
212 )
213 if namespace or not package.__spec__.has_location:
214 return ()
215 return list(item.name for item in _common.from_package(package).iterdir())