11import os
22
3+ from . import abc as resources_abc
34from . import _common
4- from ._common import as_file , files
5+ from ._common import as_file
56from contextlib import contextmanager , suppress
7+ from importlib import import_module
68from importlib .abc import ResourceLoader
79from io import BytesIO , TextIOWrapper
810from pathlib import Path
911from types import ModuleType
10- from typing import ContextManager , Iterable , Union
12+ from typing import ContextManager , Iterable , Optional , Union
1113from typing import cast
1214from typing .io import BinaryIO , TextIO
1315
3133Resource = Union [str , os .PathLike ]
3234
3335
36+ def _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 )
41+
42+
43+ def _get_package (package ) -> ModuleType :
44+ """Take a package name or module object and return the module.
45+
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
53+
54+
55+ def _normalize_path (path ) -> str :
56+ """Normalize a path by ensuring it is a string.
57+
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
64+
65+
66+ def _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
78+
79+
80+ def _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} ' )
83+
84+
3485def open_binary (package : Package , resource : Resource ) -> BinaryIO :
3586 """Return a file-like object opened for binary reading of the resource."""
36- resource = _common . normalize_path (resource )
37- package = _common . get_package (package )
38- reader = _common . get_resource_reader (package )
87+ resource = _normalize_path (resource )
88+ package = _get_package (package )
89+ reader = _get_resource_reader (package )
3990 if reader is not None :
4091 return reader .open_resource (resource )
4192 absolute_package_path = os .path .abspath (
@@ -89,6 +140,13 @@ def read_text(package: Package,
89140 return fp .read ()
90141
91142
143+ def files (package : Package ) -> resources_abc .Traversable :
144+ """
145+ Get a Traversable resource from a package
146+ """
147+ return _common .from_package (_get_package (package ))
148+
149+
92150def path (
93151 package : Package , resource : Resource ,
94152 ) -> 'ContextManager[Path]' :
@@ -100,18 +158,17 @@ def path(
100158 raised if the file was deleted prior to the context manager
101159 exiting).
102160 """
103- reader = _common . get_resource_reader ( _common . get_package (package ))
161+ reader = _get_resource_reader ( _get_package (package ))
104162 return (
105163 _path_from_reader (reader , resource )
106164 if reader else
107- _common .as_file (
108- _common .files (package ).joinpath (_common .normalize_path (resource )))
165+ _common .as_file (files (package ).joinpath (_normalize_path (resource )))
109166 )
110167
111168
112169@contextmanager
113170def _path_from_reader (reader , resource ):
114- norm_resource = _common . normalize_path (resource )
171+ norm_resource = _normalize_path (resource )
115172 with suppress (FileNotFoundError ):
116173 yield Path (reader .resource_path (norm_resource ))
117174 return
@@ -125,9 +182,9 @@ def is_resource(package: Package, name: str) -> bool:
125182
126183 Directories are *not* resources.
127184 """
128- package = _common . get_package (package )
129- _common . normalize_path (name )
130- reader = _common . get_resource_reader (package )
185+ package = _get_package (package )
186+ _normalize_path (name )
187+ reader = _get_resource_reader (package )
131188 if reader is not None :
132189 return reader .is_resource (name )
133190 package_contents = set (contents (package ))
@@ -143,8 +200,8 @@ def contents(package: Package) -> Iterable[str]:
143200 not considered resources. Use `is_resource()` on each entry returned here
144201 to check if it is a resource or not.
145202 """
146- package = _common . get_package (package )
147- reader = _common . get_resource_reader (package )
203+ package = _get_package (package )
204+ reader = _get_resource_reader (package )
148205 if reader is not None :
149206 return reader .contents ()
150207 # Is the package a namespace package? By definition, namespace packages
0 commit comments