cpython/Lib/test/test_importlib/resources/test_functional.py
Jason R. Coombs 480edc1aae
gh-121190: Emit a better error message from importlib.resources.files() when module spec is None" (#148460)
Also merges incidental changes from importlib_resources 7.1.

Co-authored by: Yuichiro Tachibana (Tsuchiya) <t.yic.yt@gmail.com>
2026-04-12 18:15:01 -04:00

261 lines
8.8 KiB
Python

import importlib
import importlib.resources as resources
import os
import unittest
from test.support import warnings_helper
from . import util
class StringAnchorMixin:
anchor01 = 'data01'
anchor02 = 'data02'
class ModuleAnchorMixin:
@property
def anchor01(self):
return importlib.import_module('data01')
@property
def anchor02(self):
return importlib.import_module('data02')
class FunctionalAPIBase:
def setUp(self):
super().setUp()
self.load_fixture('data02')
def _gen_resourcetxt_path_parts(self):
"""Yield various names of a text file in anchor02, each in a subTest"""
for path_parts in (
('subdirectory', 'subsubdir', 'resource.txt'),
('subdirectory/subsubdir/resource.txt',),
('subdirectory/subsubdir', 'resource.txt'),
):
with self.subTest(path_parts=path_parts):
yield path_parts
@staticmethod
def remove_utf16_bom(string):
"""Remove an architecture-specific UTF-16 BOM prefix when present.
Some platforms surface UTF-16 BOM bytes as escaped text when the
fixture is intentionally decoded as UTF-8 with ``errors='backslashreplace'``.
Strip that prefix so assertions validate content consistently."""
for bom in ('\\xff\\xfe', '\\xfe\\xff', '\ufeff'):
if string.startswith(bom):
return string[len(bom) :]
return string
def test_read_text(self):
assert (
resources.read_text(self.anchor01, 'utf-8.file') == 'Hello, UTF-8 world!\n'
)
assert (
resources.read_text(
self.anchor02,
'subdirectory',
'subsubdir',
'resource.txt',
encoding='utf-8',
)
== 'a resource'
)
for path_parts in self._gen_resourcetxt_path_parts():
assert (
resources.read_text(
self.anchor02,
*path_parts,
encoding='utf-8',
)
== 'a resource'
)
# Use generic OSError, since e.g. attempting to read a directory can
# fail with PermissionError rather than IsADirectoryError
with self.assertRaises(OSError):
resources.read_text(self.anchor01)
with self.assertRaises((OSError, resources.abc.TraversalError)):
resources.read_text(self.anchor01, 'no-such-file')
with self.assertRaises(UnicodeDecodeError):
resources.read_text(self.anchor01, 'utf-16.file')
assert (
resources.read_text(
self.anchor01,
'binary.file',
encoding='latin1',
)
== '\x00\x01\x02\x03'
)
assert self.remove_utf16_bom(
resources.read_text(
self.anchor01,
'utf-16.file',
errors='backslashreplace',
),
) == 'Hello, UTF-16 world!\n'.encode('utf-16-le').decode(
errors='backslashreplace',
)
def test_read_binary(self):
assert (
resources.read_binary(self.anchor01, 'utf-8.file')
== b'Hello, UTF-8 world!\n'
)
for path_parts in self._gen_resourcetxt_path_parts():
assert resources.read_binary(self.anchor02, *path_parts) == b'a resource'
def test_open_text(self):
with resources.open_text(self.anchor01, 'utf-8.file') as f:
assert f.read() == 'Hello, UTF-8 world!\n'
for path_parts in self._gen_resourcetxt_path_parts():
with resources.open_text(
self.anchor02,
*path_parts,
encoding='utf-8',
) as f:
assert f.read() == 'a resource'
# Use generic OSError, since e.g. attempting to read a directory can
# fail with PermissionError rather than IsADirectoryError
with self.assertRaises(OSError):
resources.open_text(self.anchor01)
with self.assertRaises((OSError, resources.abc.TraversalError)):
resources.open_text(self.anchor01, 'no-such-file')
with resources.open_text(self.anchor01, 'utf-16.file') as f:
with self.assertRaises(UnicodeDecodeError):
f.read()
with resources.open_text(
self.anchor01,
'binary.file',
encoding='latin1',
) as f:
assert f.read() == '\x00\x01\x02\x03'
with resources.open_text(
self.anchor01,
'utf-16.file',
errors='backslashreplace',
) as f:
assert self.remove_utf16_bom(f.read()) == 'Hello, UTF-16 world!\n'.encode(
'utf-16-le'
).decode(
errors='backslashreplace',
)
def test_open_binary(self):
with resources.open_binary(self.anchor01, 'utf-8.file') as f:
assert f.read() == b'Hello, UTF-8 world!\n'
for path_parts in self._gen_resourcetxt_path_parts():
with resources.open_binary(
self.anchor02,
*path_parts,
) as f:
assert f.read() == b'a resource'
def test_path(self):
with resources.path(self.anchor01, 'utf-8.file') as path:
with open(str(path), encoding='utf-8') as f:
assert f.read() == 'Hello, UTF-8 world!\n'
with resources.path(self.anchor01) as path:
with open(os.path.join(path, 'utf-8.file'), encoding='utf-8') as f:
assert f.read() == 'Hello, UTF-8 world!\n'
def test_is_resource(self):
is_resource = resources.is_resource
assert is_resource(self.anchor01, 'utf-8.file')
assert not is_resource(self.anchor01, 'no_such_file')
assert not is_resource(self.anchor01)
assert not is_resource(self.anchor01, 'subdirectory')
for path_parts in self._gen_resourcetxt_path_parts():
assert is_resource(self.anchor02, *path_parts)
def test_contents(self):
with warnings_helper.check_warnings((".*contents.*", DeprecationWarning)):
c = resources.contents(self.anchor01)
assert set(c) >= {'utf-8.file', 'utf-16.file', 'binary.file', 'subdirectory'}
with (
self.assertRaises(OSError),
warnings_helper.check_warnings((
".*contents.*",
DeprecationWarning,
)),
):
list(resources.contents(self.anchor01, 'utf-8.file'))
for path_parts in self._gen_resourcetxt_path_parts():
with (
self.assertRaises((OSError, resources.abc.TraversalError)),
warnings_helper.check_warnings((
".*contents.*",
DeprecationWarning,
)),
):
list(resources.contents(self.anchor01, *path_parts))
with warnings_helper.check_warnings((".*contents.*", DeprecationWarning)):
c = resources.contents(self.anchor01, 'subdirectory')
assert set(c) >= {'binary.file'}
@warnings_helper.ignore_warnings(category=DeprecationWarning)
def test_common_errors(self):
for func in (
resources.read_text,
resources.read_binary,
resources.open_text,
resources.open_binary,
resources.path,
resources.is_resource,
resources.contents,
):
with self.subTest(func=func):
# Rejecting None anchor
with self.assertRaises(TypeError):
func(None)
# Rejecting invalid anchor type
with self.assertRaises((TypeError, AttributeError)):
func(1234)
# Unknown module
with self.assertRaises(ModuleNotFoundError):
func('$missing module$')
def test_text_errors(self):
for func in (
resources.read_text,
resources.open_text,
):
with self.subTest(func=func):
# Multiple path arguments need explicit encoding argument.
with self.assertRaises(TypeError):
func(
self.anchor02,
'subdirectory',
'subsubdir',
'resource.txt',
)
class FunctionalAPITest_StringAnchor_Disk(
StringAnchorMixin,
FunctionalAPIBase,
util.DiskSetup,
unittest.TestCase,
):
pass
class FunctionalAPITest_ModuleAnchor_Disk(
ModuleAnchorMixin,
FunctionalAPIBase,
util.DiskSetup,
unittest.TestCase,
):
pass
class FunctionalAPITest_StringAnchor_Memory(
StringAnchorMixin,
FunctionalAPIBase,
util.MemorySetup,
unittest.TestCase,
):
pass