1import abc 2import importlib 3import io 4import sys 5import types 6import pathlib 7import contextlib 8 9from importlib.resources.abc import ResourceReader 10from test.support import import_helper, os_helper 11from . import zip as zip_ 12from . import _path 13 14 15from importlib.machinery import ModuleSpec 16 17 18class Reader(ResourceReader): 19 def __init__(self, **kwargs): 20 vars(self).update(kwargs) 21 22 def get_resource_reader(self, package): 23 return self 24 25 def open_resource(self, path): 26 self._path = path 27 if isinstance(self.file, Exception): 28 raise self.file 29 return self.file 30 31 def resource_path(self, path_): 32 self._path = path_ 33 if isinstance(self.path, Exception): 34 raise self.path 35 return self.path 36 37 def is_resource(self, path_): 38 self._path = path_ 39 if isinstance(self.path, Exception): 40 raise self.path 41 42 def part(entry): 43 return entry.split('/') 44 45 return any( 46 len(parts) == 1 and parts[0] == path_ for parts in map(part, self._contents) 47 ) 48 49 def contents(self): 50 if isinstance(self.path, Exception): 51 raise self.path 52 yield from self._contents 53 54 55def create_package_from_loader(loader, is_package=True): 56 name = 'testingpackage' 57 module = types.ModuleType(name) 58 spec = ModuleSpec(name, loader, origin='does-not-exist', is_package=is_package) 59 module.__spec__ = spec 60 module.__loader__ = loader 61 return module 62 63 64def create_package(file=None, path=None, is_package=True, contents=()): 65 return create_package_from_loader( 66 Reader(file=file, path=path, _contents=contents), 67 is_package, 68 ) 69 70 71class CommonTestsBase(metaclass=abc.ABCMeta): 72 """ 73 Tests shared by test_open, test_path, and test_read. 74 """ 75 76 @abc.abstractmethod 77 def execute(self, package, path): 78 """ 79 Call the pertinent legacy API function (e.g. open_text, path) 80 on package and path. 81 """ 82 83 def test_package_name(self): 84 """ 85 Passing in the package name should succeed. 86 """ 87 self.execute(self.data.__name__, 'utf-8.file') 88 89 def test_package_object(self): 90 """ 91 Passing in the package itself should succeed. 92 """ 93 self.execute(self.data, 'utf-8.file') 94 95 def test_string_path(self): 96 """ 97 Passing in a string for the path should succeed. 98 """ 99 path = 'utf-8.file' 100 self.execute(self.data, path) 101 102 def test_pathlib_path(self): 103 """ 104 Passing in a pathlib.PurePath object for the path should succeed. 105 """ 106 path = pathlib.PurePath('utf-8.file') 107 self.execute(self.data, path) 108 109 def test_importing_module_as_side_effect(self): 110 """ 111 The anchor package can already be imported. 112 """ 113 del sys.modules[self.data.__name__] 114 self.execute(self.data.__name__, 'utf-8.file') 115 116 def test_missing_path(self): 117 """ 118 Attempting to open or read or request the path for a 119 non-existent path should succeed if open_resource 120 can return a viable data stream. 121 """ 122 bytes_data = io.BytesIO(b'Hello, world!') 123 package = create_package(file=bytes_data, path=FileNotFoundError()) 124 self.execute(package, 'utf-8.file') 125 self.assertEqual(package.__loader__._path, 'utf-8.file') 126 127 def test_extant_path(self): 128 # Attempting to open or read or request the path when the 129 # path does exist should still succeed. Does not assert 130 # anything about the result. 131 bytes_data = io.BytesIO(b'Hello, world!') 132 # any path that exists 133 path = __file__ 134 package = create_package(file=bytes_data, path=path) 135 self.execute(package, 'utf-8.file') 136 self.assertEqual(package.__loader__._path, 'utf-8.file') 137 138 def test_useless_loader(self): 139 package = create_package(file=FileNotFoundError(), path=FileNotFoundError()) 140 with self.assertRaises(FileNotFoundError): 141 self.execute(package, 'utf-8.file') 142 143 144fixtures = dict( 145 data01={ 146 '__init__.py': '', 147 'binary.file': bytes(range(4)), 148 'utf-16.file': '\ufeffHello, UTF-16 world!\n'.encode('utf-16-le'), 149 'utf-8.file': 'Hello, UTF-8 world!\n'.encode('utf-8'), 150 'subdirectory': { 151 '__init__.py': '', 152 'binary.file': bytes(range(4, 8)), 153 }, 154 }, 155 data02={ 156 '__init__.py': '', 157 'one': {'__init__.py': '', 'resource1.txt': 'one resource'}, 158 'two': {'__init__.py': '', 'resource2.txt': 'two resource'}, 159 'subdirectory': {'subsubdir': {'resource.txt': 'a resource'}}, 160 }, 161 namespacedata01={ 162 'binary.file': bytes(range(4)), 163 'utf-16.file': '\ufeffHello, UTF-16 world!\n'.encode('utf-16-le'), 164 'utf-8.file': 'Hello, UTF-8 world!\n'.encode('utf-8'), 165 'subdirectory': { 166 'binary.file': bytes(range(12, 16)), 167 }, 168 }, 169) 170 171 172class ModuleSetup: 173 def setUp(self): 174 self.fixtures = contextlib.ExitStack() 175 self.addCleanup(self.fixtures.close) 176 177 self.fixtures.enter_context(import_helper.isolated_modules()) 178 self.data = self.load_fixture(self.MODULE) 179 180 def load_fixture(self, module): 181 self.tree_on_path({module: fixtures[module]}) 182 return importlib.import_module(module) 183 184 185class ZipSetup(ModuleSetup): 186 MODULE = 'data01' 187 188 def tree_on_path(self, spec): 189 temp_dir = self.fixtures.enter_context(os_helper.temp_dir()) 190 modules = pathlib.Path(temp_dir) / 'zipped modules.zip' 191 self.fixtures.enter_context( 192 import_helper.DirsOnSysPath(str(zip_.make_zip_file(spec, modules))) 193 ) 194 195 196class DiskSetup(ModuleSetup): 197 MODULE = 'data01' 198 199 def tree_on_path(self, spec): 200 temp_dir = self.fixtures.enter_context(os_helper.temp_dir()) 201 _path.build(spec, pathlib.Path(temp_dir)) 202 self.fixtures.enter_context(import_helper.DirsOnSysPath(temp_dir)) 203 204 205class CommonTests(DiskSetup, CommonTestsBase): 206 pass 207