From 189d15fcbe99a1ec624fb19992544096e2a538f3 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 21 Mar 2024 10:11:18 +0100 Subject: [PATCH 1/6] Apply CPython PR, sans docs and changelogs --- importlib_resources/__init__.py | 17 ++ importlib_resources/functional.py | 88 ++++++++ importlib_resources/tests/test_functional.py | 217 +++++++++++++++++++ 3 files changed, 322 insertions(+) create mode 100644 importlib_resources/functional.py create mode 100644 importlib_resources/tests/test_functional.py diff --git a/importlib_resources/__init__.py b/importlib_resources/__init__.py index ae83cd0..0d029ab 100644 --- a/importlib_resources/__init__.py +++ b/importlib_resources/__init__.py @@ -7,6 +7,16 @@ Anchor, ) +from .functional import ( + contents, + is_resource, + open_binary, + open_text, + path, + read_binary, + read_text, +) + from .abc import ResourceReader @@ -16,4 +26,11 @@ 'ResourceReader', 'as_file', 'files', + 'contents', + 'is_resource', + 'open_binary', + 'open_text', + 'path', + 'read_binary', + 'read_text', ] diff --git a/importlib_resources/functional.py b/importlib_resources/functional.py new file mode 100644 index 0000000..80c192f --- /dev/null +++ b/importlib_resources/functional.py @@ -0,0 +1,88 @@ +"""Simplified function-based API for importlib.resources + + +""" + +import os +import warnings + +from ._common import files, as_file + + +_MISSING = object() + +def open_binary(anchor, *path_names): + """Open for binary reading the *resource* within *package*.""" + return _get_resource(anchor, path_names).open('rb') + + +def open_text(anchor, *path_names, encoding=_MISSING, errors='strict'): + """Open for text reading the *resource* within *package*.""" + encoding = _get_encoding_arg(path_names, encoding) + resource = _get_resource(anchor, path_names) + return resource.open('r', encoding=encoding, errors=errors) + + +def read_binary(anchor, *path_names): + """Read and return contents of *resource* within *package* as bytes.""" + return _get_resource(anchor, path_names).read_bytes() + + +def read_text(anchor, *path_names, encoding=_MISSING, errors='strict'): + """Read and return contents of *resource* within *package* as str.""" + encoding = _get_encoding_arg(path_names, encoding) + resource = _get_resource(anchor, path_names) + return resource.read_text(encoding=encoding, errors=errors) + + +def path(anchor, *path_names): + """Return the path to the *resource* as an actual file system path.""" + return as_file(_get_resource(anchor, path_names)) + + +def is_resource(anchor, *path_names): + """Return ``True`` if there is a resource named *name* in the package, + + Otherwise returns ``False``. + """ + return _get_resource(anchor, path_names).is_file() + + +def contents(anchor, *path_names): + """Return an iterable over the named resources within the package. + + The iterable returns :class:`str` resources (e.g. files). + The iterable does not recurse into subdirectories. + """ + warnings.warn( + "importlib.resources.contents is deprecated. " + "Use files(anchor).iterdir() instead.", + DeprecationWarning, + stacklevel=1, + ) + return ( + resource.name + for resource + in _get_resource(anchor, path_names).iterdir() + ) + + +def _get_encoding_arg(path_names, encoding): + # For compatibility with versions where *encoding* was a positional + # argument, it needs to be given explicitly when there are multiple + # *path_names*. + # This limitation can be removed in Python 3.15. + if encoding is _MISSING: + if len(path_names) > 1: + raise TypeError( + "'encoding' argument required with multiple path names", + ) + else: + return 'utf-8' + return encoding + + +def _get_resource(anchor, path_names): + if anchor is None: + raise TypeError("anchor must be module or string, got None") + return files(anchor).joinpath(*path_names) diff --git a/importlib_resources/tests/test_functional.py b/importlib_resources/tests/test_functional.py new file mode 100644 index 0000000..cc7199d --- /dev/null +++ b/importlib_resources/tests/test_functional.py @@ -0,0 +1,217 @@ +import unittest +import os + +from test.support.warnings_helper import ignore_warnings, check_warnings + +import importlib.resources + +# Since the functional API forwards to Traversable, we only test +# filesystem resources here -- not zip files, namespace packages etc. +# We do test for two kinds of Anchor, though. + + +class StringAnchorMixin: + anchor01 = 'test.test_importlib.resources.data01' + anchor02 = 'test.test_importlib.resources.data02' + + +class ModuleAnchorMixin: + from test.test_importlib.resources import data01 as anchor01 + from test.test_importlib.resources import data02 as anchor02 + + +class FunctionalAPIBase(): + 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 + + def test_read_text(self): + self.assertEqual( + importlib.resources.read_text(self.anchor01, 'utf-8.file'), + 'Hello, UTF-8 world!\n', + ) + self.assertEqual( + importlib.resources.read_text( + self.anchor02, 'subdirectory', 'subsubdir', 'resource.txt', + encoding='utf-8', + ), + 'a resource', + ) + for path_parts in self._gen_resourcetxt_path_parts(): + self.assertEqual( + importlib.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): + importlib.resources.read_text(self.anchor01) + with self.assertRaises(OSError): + importlib.resources.read_text(self.anchor01, 'no-such-file') + with self.assertRaises(UnicodeDecodeError): + importlib.resources.read_text(self.anchor01, 'utf-16.file') + self.assertEqual( + importlib.resources.read_text( + self.anchor01, 'binary.file', encoding='latin1', + ), + '\x00\x01\x02\x03', + ) + self.assertEqual( + importlib.resources.read_text( + self.anchor01, 'utf-16.file', + errors='backslashreplace', + ), + 'Hello, UTF-16 world!\n'.encode('utf-16').decode( + errors='backslashreplace', + ) + ) + + def test_read_binary(self): + self.assertEqual( + importlib.resources.read_binary(self.anchor01, 'utf-8.file'), + b'Hello, UTF-8 world!\n', + ) + for path_parts in self._gen_resourcetxt_path_parts(): + self.assertEqual( + importlib.resources.read_binary(self.anchor02, *path_parts), + b'a resource', + ) + + def test_open_text(self): + with importlib.resources.open_text(self.anchor01, 'utf-8.file') as f: + self.assertEqual(f.read(), 'Hello, UTF-8 world!\n') + for path_parts in self._gen_resourcetxt_path_parts(): + with importlib.resources.open_text( + self.anchor02, *path_parts, + encoding='utf-8', + ) as f: + self.assertEqual(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): + importlib.resources.open_text(self.anchor01) + with self.assertRaises(OSError): + importlib.resources.open_text(self.anchor01, 'no-such-file') + with importlib.resources.open_text(self.anchor01, 'utf-16.file') as f: + with self.assertRaises(UnicodeDecodeError): + f.read() + with importlib.resources.open_text( + self.anchor01, 'binary.file', encoding='latin1', + ) as f: + self.assertEqual(f.read(), '\x00\x01\x02\x03') + with importlib.resources.open_text( + self.anchor01, 'utf-16.file', + errors='backslashreplace', + ) as f: + self.assertEqual( + f.read(), + 'Hello, UTF-16 world!\n'.encode('utf-16').decode( + errors='backslashreplace', + ) + ) + + def test_open_binary(self): + with importlib.resources.open_binary(self.anchor01, 'utf-8.file') as f: + self.assertEqual(f.read(), b'Hello, UTF-8 world!\n') + for path_parts in self._gen_resourcetxt_path_parts(): + with importlib.resources.open_binary( + self.anchor02, *path_parts, + ) as f: + self.assertEqual(f.read(), b'a resource') + + def test_path(self): + with importlib.resources.path(self.anchor01, 'utf-8.file') as path: + with open(str(path)) as f: + self.assertEqual(f.read(), 'Hello, UTF-8 world!\n') + with importlib.resources.path(self.anchor01) as path: + with open(os.path.join(path, 'utf-8.file')) as f: + self.assertEqual(f.read(), 'Hello, UTF-8 world!\n') + + def test_is_resource(self): + is_resource = importlib.resources.is_resource + self.assertTrue(is_resource(self.anchor01, 'utf-8.file')) + self.assertFalse(is_resource(self.anchor01, 'no_such_file')) + self.assertFalse(is_resource(self.anchor01)) + self.assertFalse(is_resource(self.anchor01, 'subdirectory')) + for path_parts in self._gen_resourcetxt_path_parts(): + self.assertTrue(is_resource(self.anchor02, *path_parts)) + + def test_contents(self): + is_resource = importlib.resources.is_resource + with check_warnings((".*contents.*", DeprecationWarning)): + c = importlib.resources.contents(self.anchor01) + self.assertGreaterEqual( + set(c), + {'utf-8.file', 'utf-16.file', 'binary.file', 'subdirectory'}, + ) + with (self.assertRaises(OSError), + check_warnings((".*contents.*", DeprecationWarning)), + ): + importlib.resources.contents(self.anchor01, 'utf-8.file') + for path_parts in self._gen_resourcetxt_path_parts(): + with (self.assertRaises(OSError), + check_warnings((".*contents.*", DeprecationWarning)), + ): + importlib.resources.contents(self.anchor01, *path_parts) + with check_warnings((".*contents.*", DeprecationWarning)): + c = importlib.resources.contents(self.anchor01, 'subdirectory') + self.assertGreaterEqual( + set(c), + {'binary.file'}, + ) + + @ignore_warnings(category=DeprecationWarning) + def test_common_errors(self): + for func in ( + importlib.resources.read_text, + importlib.resources.read_binary, + importlib.resources.open_text, + importlib.resources.open_binary, + importlib.resources.path, + importlib.resources.is_resource, + importlib.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 ( + importlib.resources.read_text, + importlib.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( + unittest.TestCase, FunctionalAPIBase, StringAnchorMixin, +): + pass + + +class FunctionalAPITest_ModuleAnchor( + unittest.TestCase, FunctionalAPIBase, ModuleAnchorMixin, +): + pass From 1e98e351779d53092d7988d362503b54b3dc6b35 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 21 Mar 2024 10:46:17 +0100 Subject: [PATCH 2/6] Adapt to importlib_resources --- importlib_resources/functional.py | 3 - importlib_resources/tests/test_functional.py | 91 ++++++++++---------- 2 files changed, 46 insertions(+), 48 deletions(-) diff --git a/importlib_resources/functional.py b/importlib_resources/functional.py index 80c192f..9f01680 100644 --- a/importlib_resources/functional.py +++ b/importlib_resources/functional.py @@ -1,9 +1,6 @@ """Simplified function-based API for importlib.resources - - """ -import os import warnings from ._common import files, as_file diff --git a/importlib_resources/tests/test_functional.py b/importlib_resources/tests/test_functional.py index cc7199d..9a9b4e1 100644 --- a/importlib_resources/tests/test_functional.py +++ b/importlib_resources/tests/test_functional.py @@ -3,7 +3,7 @@ from test.support.warnings_helper import ignore_warnings, check_warnings -import importlib.resources +import importlib_resources as resources # Since the functional API forwards to Traversable, we only test # filesystem resources here -- not zip files, namespace packages etc. @@ -11,13 +11,13 @@ class StringAnchorMixin: - anchor01 = 'test.test_importlib.resources.data01' - anchor02 = 'test.test_importlib.resources.data02' + anchor01 = 'importlib_resources.tests.data01' + anchor02 = 'importlib_resources.tests.data02' class ModuleAnchorMixin: - from test.test_importlib.resources import data01 as anchor01 - from test.test_importlib.resources import data02 as anchor02 + from . import data01 as anchor01 + from . import data02 as anchor02 class FunctionalAPIBase(): @@ -34,11 +34,11 @@ def _gen_resourcetxt_path_parts(self): def test_read_text(self): self.assertEqual( - importlib.resources.read_text(self.anchor01, 'utf-8.file'), + resources.read_text(self.anchor01, 'utf-8.file'), 'Hello, UTF-8 world!\n', ) self.assertEqual( - importlib.resources.read_text( + resources.read_text( self.anchor02, 'subdirectory', 'subsubdir', 'resource.txt', encoding='utf-8', ), @@ -46,7 +46,7 @@ def test_read_text(self): ) for path_parts in self._gen_resourcetxt_path_parts(): self.assertEqual( - importlib.resources.read_text( + resources.read_text( self.anchor02, *path_parts, encoding='utf-8', ), 'a resource', @@ -54,19 +54,19 @@ def test_read_text(self): # Use generic OSError, since e.g. attempting to read a directory can # fail with PermissionError rather than IsADirectoryError with self.assertRaises(OSError): - importlib.resources.read_text(self.anchor01) + resources.read_text(self.anchor01) with self.assertRaises(OSError): - importlib.resources.read_text(self.anchor01, 'no-such-file') + resources.read_text(self.anchor01, 'no-such-file') with self.assertRaises(UnicodeDecodeError): - importlib.resources.read_text(self.anchor01, 'utf-16.file') + resources.read_text(self.anchor01, 'utf-16.file') self.assertEqual( - importlib.resources.read_text( + resources.read_text( self.anchor01, 'binary.file', encoding='latin1', ), '\x00\x01\x02\x03', ) self.assertEqual( - importlib.resources.read_text( + resources.read_text( self.anchor01, 'utf-16.file', errors='backslashreplace', ), @@ -77,20 +77,20 @@ def test_read_text(self): def test_read_binary(self): self.assertEqual( - importlib.resources.read_binary(self.anchor01, 'utf-8.file'), + resources.read_binary(self.anchor01, 'utf-8.file'), b'Hello, UTF-8 world!\n', ) for path_parts in self._gen_resourcetxt_path_parts(): self.assertEqual( - importlib.resources.read_binary(self.anchor02, *path_parts), + resources.read_binary(self.anchor02, *path_parts), b'a resource', ) def test_open_text(self): - with importlib.resources.open_text(self.anchor01, 'utf-8.file') as f: + with resources.open_text(self.anchor01, 'utf-8.file') as f: self.assertEqual(f.read(), 'Hello, UTF-8 world!\n') for path_parts in self._gen_resourcetxt_path_parts(): - with importlib.resources.open_text( + with resources.open_text( self.anchor02, *path_parts, encoding='utf-8', ) as f: @@ -98,17 +98,17 @@ def test_open_text(self): # Use generic OSError, since e.g. attempting to read a directory can # fail with PermissionError rather than IsADirectoryError with self.assertRaises(OSError): - importlib.resources.open_text(self.anchor01) + resources.open_text(self.anchor01) with self.assertRaises(OSError): - importlib.resources.open_text(self.anchor01, 'no-such-file') - with importlib.resources.open_text(self.anchor01, 'utf-16.file') as f: + 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 importlib.resources.open_text( + with resources.open_text( self.anchor01, 'binary.file', encoding='latin1', ) as f: self.assertEqual(f.read(), '\x00\x01\x02\x03') - with importlib.resources.open_text( + with resources.open_text( self.anchor01, 'utf-16.file', errors='backslashreplace', ) as f: @@ -120,24 +120,24 @@ def test_open_text(self): ) def test_open_binary(self): - with importlib.resources.open_binary(self.anchor01, 'utf-8.file') as f: + with resources.open_binary(self.anchor01, 'utf-8.file') as f: self.assertEqual(f.read(), b'Hello, UTF-8 world!\n') for path_parts in self._gen_resourcetxt_path_parts(): - with importlib.resources.open_binary( + with resources.open_binary( self.anchor02, *path_parts, ) as f: self.assertEqual(f.read(), b'a resource') def test_path(self): - with importlib.resources.path(self.anchor01, 'utf-8.file') as path: - with open(str(path)) as f: + with resources.path(self.anchor01, 'utf-8.file') as path: + with open(str(path), encoding='utf-8') as f: self.assertEqual(f.read(), 'Hello, UTF-8 world!\n') - with importlib.resources.path(self.anchor01) as path: - with open(os.path.join(path, 'utf-8.file')) as f: + with resources.path(self.anchor01) as path: + with open(os.path.join(path, 'utf-8.file'), encoding='utf-8') as f: self.assertEqual(f.read(), 'Hello, UTF-8 world!\n') def test_is_resource(self): - is_resource = importlib.resources.is_resource + is_resource = resources.is_resource self.assertTrue(is_resource(self.anchor01, 'utf-8.file')) self.assertFalse(is_resource(self.anchor01, 'no_such_file')) self.assertFalse(is_resource(self.anchor01)) @@ -146,24 +146,25 @@ def test_is_resource(self): self.assertTrue(is_resource(self.anchor02, *path_parts)) def test_contents(self): - is_resource = importlib.resources.is_resource with check_warnings((".*contents.*", DeprecationWarning)): - c = importlib.resources.contents(self.anchor01) + c = resources.contents(self.anchor01) self.assertGreaterEqual( set(c), {'utf-8.file', 'utf-16.file', 'binary.file', 'subdirectory'}, ) - with (self.assertRaises(OSError), + with ( + self.assertRaises(OSError), check_warnings((".*contents.*", DeprecationWarning)), ): - importlib.resources.contents(self.anchor01, 'utf-8.file') + list(resources.contents(self.anchor01, 'utf-8.file')) for path_parts in self._gen_resourcetxt_path_parts(): - with (self.assertRaises(OSError), + with ( + self.assertRaises(OSError), check_warnings((".*contents.*", DeprecationWarning)), ): - importlib.resources.contents(self.anchor01, *path_parts) + list(resources.contents(self.anchor01, *path_parts)) with check_warnings((".*contents.*", DeprecationWarning)): - c = importlib.resources.contents(self.anchor01, 'subdirectory') + c = resources.contents(self.anchor01, 'subdirectory') self.assertGreaterEqual( set(c), {'binary.file'}, @@ -172,13 +173,13 @@ def test_contents(self): @ignore_warnings(category=DeprecationWarning) def test_common_errors(self): for func in ( - importlib.resources.read_text, - importlib.resources.read_binary, - importlib.resources.open_text, - importlib.resources.open_binary, - importlib.resources.path, - importlib.resources.is_resource, - importlib.resources.contents, + 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 @@ -193,8 +194,8 @@ def test_common_errors(self): def test_text_errors(self): for func in ( - importlib.resources.read_text, - importlib.resources.open_text, + resources.read_text, + resources.open_text, ): with self.subTest(func=func): # Multiple path arguments need explicit encoding argument. From 558f5bf9f266998e616deaf8a9d373da37b33054 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 21 Mar 2024 10:52:42 +0100 Subject: [PATCH 3/6] Formatting nitpicks --- importlib_resources/functional.py | 4 ++-- importlib_resources/tests/test_functional.py | 20 +++++++++++++------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/importlib_resources/functional.py b/importlib_resources/functional.py index 9f01680..9e3ea15 100644 --- a/importlib_resources/functional.py +++ b/importlib_resources/functional.py @@ -1,5 +1,4 @@ -"""Simplified function-based API for importlib.resources -""" +"""Simplified function-based API for importlib.resources""" import warnings @@ -8,6 +7,7 @@ _MISSING = object() + def open_binary(anchor, *path_names): """Open for binary reading the *resource* within *package*.""" return _get_resource(anchor, path_names).open('rb') diff --git a/importlib_resources/tests/test_functional.py b/importlib_resources/tests/test_functional.py index 9a9b4e1..f359900 100644 --- a/importlib_resources/tests/test_functional.py +++ b/importlib_resources/tests/test_functional.py @@ -20,7 +20,7 @@ class ModuleAnchorMixin: from . import data02 as anchor02 -class FunctionalAPIBase(): +class FunctionalAPIBase: def _gen_resourcetxt_path_parts(self): """Yield various names of a text file in anchor02, each in a subTest """ @@ -61,18 +61,21 @@ def test_read_text(self): resources.read_text(self.anchor01, 'utf-16.file') self.assertEqual( resources.read_text( - self.anchor01, 'binary.file', encoding='latin1', + self.anchor01, + 'binary.file', + encoding='latin1', ), '\x00\x01\x02\x03', ) self.assertEqual( resources.read_text( - self.anchor01, 'utf-16.file', + self.anchor01, + 'utf-16.file', errors='backslashreplace', ), 'Hello, UTF-16 world!\n'.encode('utf-16').decode( errors='backslashreplace', - ) + ), ) def test_read_binary(self): @@ -105,18 +108,21 @@ def test_open_text(self): with self.assertRaises(UnicodeDecodeError): f.read() with resources.open_text( - self.anchor01, 'binary.file', encoding='latin1', + self.anchor01, + 'binary.file', + encoding='latin1', ) as f: self.assertEqual(f.read(), '\x00\x01\x02\x03') with resources.open_text( - self.anchor01, 'utf-16.file', + self.anchor01, + 'utf-16.file', errors='backslashreplace', ) as f: self.assertEqual( f.read(), 'Hello, UTF-16 world!\n'.encode('utf-16').decode( errors='backslashreplace', - ) + ), ) def test_open_binary(self): From 8fdadde235ce5d5d8f80934bb2f9c9b29cb0da78 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 21 Mar 2024 11:06:01 +0100 Subject: [PATCH 4/6] Port tests to Python 3.8 --- importlib_resources/tests/test_functional.py | 24 ++++++++++++-------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/importlib_resources/tests/test_functional.py b/importlib_resources/tests/test_functional.py index f359900..39a8b5d 100644 --- a/importlib_resources/tests/test_functional.py +++ b/importlib_resources/tests/test_functional.py @@ -1,7 +1,12 @@ import unittest import os +import contextlib -from test.support.warnings_helper import ignore_warnings, check_warnings +try: + from test.support.warnings_helper import ignore_warnings, check_warnings +except ImportError: + # older Python versions + from test.support import ignore_warnings, check_warnings import importlib_resources as resources @@ -158,16 +163,17 @@ def test_contents(self): set(c), {'utf-8.file', 'utf-16.file', 'binary.file', 'subdirectory'}, ) - with ( - self.assertRaises(OSError), - check_warnings((".*contents.*", DeprecationWarning)), - ): + with contextlib.ExitStack() as cm: + cm.enter_context(self.assertRaises(OSError)) + cm.enter_context(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), - check_warnings((".*contents.*", DeprecationWarning)), - ): + with contextlib.ExitStack() as cm: + cm.enter_context(self.assertRaises(OSError)) + cm.enter_context(check_warnings((".*contents.*", DeprecationWarning))) + list(resources.contents(self.anchor01, *path_parts)) with check_warnings((".*contents.*", DeprecationWarning)): c = resources.contents(self.anchor01, 'subdirectory') From 2df6ced9be7d45cdc443ceb8c7e66ab846d19ebc Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 21 Mar 2024 11:09:55 +0100 Subject: [PATCH 5/6] Use Ruff style, rather than PEP 8 --- importlib_resources/functional.py | 6 +--- importlib_resources/tests/test_functional.py | 32 ++++++++++++++------ 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/importlib_resources/functional.py b/importlib_resources/functional.py index 9e3ea15..f59416f 100644 --- a/importlib_resources/functional.py +++ b/importlib_resources/functional.py @@ -57,11 +57,7 @@ def contents(anchor, *path_names): DeprecationWarning, stacklevel=1, ) - return ( - resource.name - for resource - in _get_resource(anchor, path_names).iterdir() - ) + return (resource.name for resource in _get_resource(anchor, path_names).iterdir()) def _get_encoding_arg(path_names, encoding): diff --git a/importlib_resources/tests/test_functional.py b/importlib_resources/tests/test_functional.py index 39a8b5d..69706cf 100644 --- a/importlib_resources/tests/test_functional.py +++ b/importlib_resources/tests/test_functional.py @@ -27,8 +27,7 @@ class ModuleAnchorMixin: class FunctionalAPIBase: def _gen_resourcetxt_path_parts(self): - """Yield various names of a text file in anchor02, each in a subTest - """ + """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',), @@ -44,7 +43,10 @@ def test_read_text(self): ) self.assertEqual( resources.read_text( - self.anchor02, 'subdirectory', 'subsubdir', 'resource.txt', + self.anchor02, + 'subdirectory', + 'subsubdir', + 'resource.txt', encoding='utf-8', ), 'a resource', @@ -52,7 +54,9 @@ def test_read_text(self): for path_parts in self._gen_resourcetxt_path_parts(): self.assertEqual( resources.read_text( - self.anchor02, *path_parts, encoding='utf-8', + self.anchor02, + *path_parts, + encoding='utf-8', ), 'a resource', ) @@ -99,7 +103,8 @@ def test_open_text(self): self.assertEqual(f.read(), 'Hello, UTF-8 world!\n') for path_parts in self._gen_resourcetxt_path_parts(): with resources.open_text( - self.anchor02, *path_parts, + self.anchor02, + *path_parts, encoding='utf-8', ) as f: self.assertEqual(f.read(), 'a resource') @@ -135,7 +140,8 @@ def test_open_binary(self): self.assertEqual(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, + self.anchor02, + *path_parts, ) as f: self.assertEqual(f.read(), b'a resource') @@ -213,18 +219,24 @@ def test_text_errors(self): # Multiple path arguments need explicit encoding argument. with self.assertRaises(TypeError): func( - self.anchor02, 'subdirectory', - 'subsubdir', 'resource.txt', + self.anchor02, + 'subdirectory', + 'subsubdir', + 'resource.txt', ) class FunctionalAPITest_StringAnchor( - unittest.TestCase, FunctionalAPIBase, StringAnchorMixin, + unittest.TestCase, + FunctionalAPIBase, + StringAnchorMixin, ): pass class FunctionalAPITest_ModuleAnchor( - unittest.TestCase, FunctionalAPIBase, ModuleAnchorMixin, + unittest.TestCase, + FunctionalAPIBase, + ModuleAnchorMixin, ): pass From fa60969a37ed01302d2ed01956e8ef18eba87923 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 21 Mar 2024 09:34:23 -0400 Subject: [PATCH 6/6] Add news fragment. --- newsfragments/303.feature.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 newsfragments/303.feature.rst diff --git a/newsfragments/303.feature.rst b/newsfragments/303.feature.rst new file mode 100644 index 0000000..537a0c2 --- /dev/null +++ b/newsfragments/303.feature.rst @@ -0,0 +1,10 @@ +The functions +``is_resource()``, +``open_binary()``, +``open_text()``, +``path()``, +``read_binary()``, and +``read_text()`` are un-deprecated, and support +subdirectories via multiple positional arguments. +The ``contents()`` function also allows subdirectories, +but remains deprecated.