• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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