summaryrefslogtreecommitdiffstats
path: root/Lib/test/test_importlib/resources/test_files.py
blob: fe597d72422f838b132b27f64eda1192a003228c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
import typing
import os
import pathlib
import py_compile
import shutil
import textwrap
import unittest
import warnings
import importlib
import contextlib

from importlib import resources
from importlib.resources.abc import Traversable
from . import data01
from . import util
from . import _path
from test.support import os_helper, import_helper


@contextlib.contextmanager
def suppress_known_deprecation():
    with warnings.catch_warnings(record=True) as ctx:
        warnings.simplefilter('default', category=DeprecationWarning)
        yield ctx


class FilesTests:
    def test_read_bytes(self):
        files = resources.files(self.data)
        actual = files.joinpath('utf-8.file').read_bytes()
        assert actual == b'Hello, UTF-8 world!\n'

    def test_read_text(self):
        files = resources.files(self.data)
        actual = files.joinpath('utf-8.file').read_text(encoding='utf-8')
        assert actual == 'Hello, UTF-8 world!\n'

    @unittest.skipUnless(
        hasattr(typing, 'runtime_checkable'),
        "Only suitable when typing supports runtime_checkable",
    )
    def test_traversable(self):
        assert isinstance(resources.files(self.data), Traversable)

    def test_old_parameter(self):
        """
        Files used to take a 'package' parameter. Make sure anyone
        passing by name is still supported.
        """
        with suppress_known_deprecation():
            resources.files(package=self.data)


class OpenDiskTests(FilesTests, unittest.TestCase):
    def setUp(self):
        self.data = data01


class OpenZipTests(FilesTests, util.ZipSetup, unittest.TestCase):
    pass


class OpenNamespaceTests(FilesTests, unittest.TestCase):
    def setUp(self):
        from . import namespacedata01

        self.data = namespacedata01


class SiteDir:
    def setUp(self):
        self.fixtures = contextlib.ExitStack()
        self.addCleanup(self.fixtures.close)
        self.site_dir = self.fixtures.enter_context(os_helper.temp_dir())
        self.fixtures.enter_context(import_helper.DirsOnSysPath(self.site_dir))
        self.fixtures.enter_context(import_helper.isolated_modules())


class ModulesFilesTests(SiteDir, unittest.TestCase):
    def test_module_resources(self):
        """
        A module can have resources found adjacent to the module.
        """
        spec = {
            'mod.py': '',
            'res.txt': 'resources are the best',
        }
        _path.build(spec, self.site_dir)
        import mod

        actual = resources.files(mod).joinpath('res.txt').read_text(encoding='utf-8')
        assert actual == spec['res.txt']


class ImplicitContextFilesTests(SiteDir, unittest.TestCase):
    def test_implicit_files(self):
        """
        Without any parameter, files() will infer the location as the caller.
        """
        spec = {
            'somepkg': {
                '__init__.py': textwrap.dedent(
                    """
                    import importlib.resources as res
                    val = res.files().joinpath('res.txt').read_text(encoding='utf-8')
                    """
                ),
                'res.txt': 'resources are the best',
            },
        }
        _path.build(spec, self.site_dir)
        assert importlib.import_module('somepkg').val == 'resources are the best'

    def test_implicit_files_zip_submodule(self):
        """
        Special test for gh-121735 for Python 3.12.
        """
        import zipfile

        def create_zip_from_directory(source_dir, zip_filename):
            with zipfile.ZipFile(zip_filename, 'w') as zipf:
                for root, _, files in os.walk(source_dir):
                    for file in files:
                        file_path = os.path.join(root, file)
                        # Ensure files are at the root
                        arcname = os.path.relpath(file_path, source_dir)
                        zipf.write(file_path, arcname)

        set_val = textwrap.dedent(
            """
            import importlib.resources as res
            val = res.files().joinpath('res.txt').read_text(encoding='utf-8')
            """
        )
        spec = {
            'somepkg': {
                '__init__.py': set_val,
                'submod.py': set_val,
                'res.txt': 'resources are the best',
            },
        }
        build_dir = self.fixtures.enter_context(os_helper.temp_dir())
        _path.build(spec, build_dir)
        zip_file = os.path.join(self.site_dir, 'thepkg.zip')
        create_zip_from_directory(build_dir, zip_file)
        self.fixtures.enter_context(import_helper.DirsOnSysPath(zip_file))
        assert importlib.import_module('somepkg.submod').val == 'resources are the best'

    def _compile_importlib(self):
        """
        Make a compiled-only copy of the importlib resources package.

        Currently only code is copied, as importlib resources doesn't itself
        have any resources.
        """
        bin_site = self.fixtures.enter_context(os_helper.temp_dir())
        c_resources = pathlib.Path(bin_site, 'c_resources')
        sources = pathlib.Path(resources.__file__).parent

        for source_path in sources.glob('**/*.py'):
            c_path = c_resources.joinpath(source_path.relative_to(sources)).with_suffix('.pyc')
            py_compile.compile(source_path, c_path)
        self.fixtures.enter_context(import_helper.DirsOnSysPath(bin_site))

    def test_implicit_files_with_compiled_importlib(self):
        """
        Caller detection works for compiled-only resources module.

        python/cpython#123085
        """
        set_val = textwrap.dedent(
            f"""
            import {resources.__name__} as res
            val = res.files().joinpath('res.txt').read_text(encoding='utf-8')
            """
        )
        spec = {
            'frozenpkg': {
                '__init__.py': set_val.replace(resources.__name__, 'c_resources'),
                'res.txt': 'resources are the best',
            },
        }
        _path.build(spec, self.site_dir)
        self._compile_importlib()
        assert importlib.import_module('frozenpkg').val == 'resources are the best'


if __name__ == '__main__':
    unittest.main()