Skip to content

Commit

Permalink
bpo-45514: Deprecate importlib resources legacy functions. (GH-29036)
Browse files Browse the repository at this point in the history
* bpo-45514: Apply changes from importlib_resources@a3ef4128c6

* Mark legacy functions as deprecated in the docs and link to the migration docs in importlib_resources docs.

* Apply changes from importlib_resources@329ae9d5f2c.

* Indicate importlib.resources as a module.

Co-authored-by: Filipe Laíns <lains@riseup.net>
  • Loading branch information
jaraco and FFY00 committed Nov 24, 2021
1 parent 3245270 commit d5cd2ef
Show file tree
Hide file tree
Showing 10 changed files with 204 additions and 84 deletions.
18 changes: 17 additions & 1 deletion Doc/library/importlib.rst
Expand Up @@ -917,7 +917,9 @@ not** have to exist as physical files and directories on the file system.
on `using importlib.resources
<http://importlib-resources.readthedocs.io/en/latest/using.html>`_ and
`migrating from pkg_resources to importlib.resources
<http://importlib-resources.readthedocs.io/en/latest/migration.html>`_.
<http://importlib-resources.readthedocs.io/en/latest/migration.html>`_
and
`migrating legacy usage <https://importlib-resources.readthedocs.io/en/latest/using.html#migrating-from-legacy>`_.

Loaders that wish to support resource reading should implement a
``get_resource_reader(fullname)`` method as specified by
Expand Down Expand Up @@ -979,6 +981,8 @@ The following functions are available.
sub-resources (i.e. it cannot be a directory). This function returns a
``typing.BinaryIO`` instance, a binary I/O stream open for reading.

.. deprecated:: 3.11


.. function:: open_text(package, resource, encoding='utf-8', errors='strict')

Expand All @@ -994,6 +998,8 @@ The following functions are available.
This function returns a ``typing.TextIO`` instance, a text I/O stream open
for reading.

.. deprecated:: 3.11


.. function:: read_binary(package, resource)

Expand All @@ -1006,6 +1012,8 @@ The following functions are available.
sub-resources (i.e. it cannot be a directory). This function returns the
contents of the resource as :class:`bytes`.

.. deprecated:: 3.11


.. function:: read_text(package, resource, encoding='utf-8', errors='strict')

Expand All @@ -1019,6 +1027,8 @@ The following functions are available.
have the same meaning as with built-in :func:`open`. This function
returns the contents of the resource as :class:`str`.

.. deprecated:: 3.11


.. function:: path(package, resource)

Expand All @@ -1034,6 +1044,8 @@ The following functions are available.
within *package*; it may not contain path separators and it may not have
sub-resources (i.e. it cannot be a directory).

.. deprecated:: 3.11


.. function:: is_resource(package, name)

Expand All @@ -1042,6 +1054,8 @@ The following functions are available.
*package* is either a name or a module object which conforms to the
``Package`` requirements.

.. deprecated:: 3.11


.. function:: contents(package)

Expand All @@ -1052,6 +1066,8 @@ The following functions are available.
*package* is either a name or a module object which conforms to the
``Package`` requirements.

.. deprecated:: 3.11


:mod:`importlib.machinery` -- Importers and path hooks
------------------------------------------------------
Expand Down
20 changes: 18 additions & 2 deletions Lib/importlib/_itertools.py
@@ -1,11 +1,27 @@
from itertools import filterfalse

from typing import (
Callable,
Iterable,
Iterator,
Optional,
Set,
TypeVar,
Union,
)

def unique_everseen(iterable, key=None):
# Type and type variable definitions
_T = TypeVar('_T')
_U = TypeVar('_U')


def unique_everseen(
iterable: Iterable[_T], key: Optional[Callable[[_T], _U]] = None
) -> Iterator[_T]:
"List unique elements, preserving order. Remember all elements ever seen."
# unique_everseen('AAAABBBCCDAABBB') --> A B C D
# unique_everseen('ABBCcAD', str.lower) --> A B C D
seen = set()
seen: Set[Union[_T, _U]] = set()
seen_add = seen.add
if key is None:
for element in filterfalse(seen.__contains__, iterable):
Expand Down
24 changes: 24 additions & 0 deletions Lib/importlib/_legacy.py
@@ -1,6 +1,8 @@
import functools
import os
import pathlib
import types
import warnings

from typing import Union, Iterable, ContextManager, BinaryIO, TextIO

Expand All @@ -10,16 +12,34 @@
Resource = Union[str, os.PathLike]


def deprecated(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
warnings.warn(
f"{func.__name__} is deprecated. Use files() instead. "
"Refer to https://importlib-resources.readthedocs.io"
"/en/latest/using.html#migrating-from-legacy for migration advice.",
DeprecationWarning,
stacklevel=2,
)
return func(*args, **kwargs)

return wrapper


@deprecated
def open_binary(package: Package, resource: Resource) -> BinaryIO:
"""Return a file-like object opened for binary reading of the resource."""
return (_common.files(package) / _common.normalize_path(resource)).open('rb')


@deprecated
def read_binary(package: Package, resource: Resource) -> bytes:
"""Return the binary contents of the resource."""
return (_common.files(package) / _common.normalize_path(resource)).read_bytes()


@deprecated
def open_text(
package: Package,
resource: Resource,
Expand All @@ -32,6 +52,7 @@ def open_text(
)


@deprecated
def read_text(
package: Package,
resource: Resource,
Expand All @@ -47,6 +68,7 @@ def read_text(
return fp.read()


@deprecated
def contents(package: Package) -> Iterable[str]:
"""Return an iterable of entries in `package`.
Expand All @@ -57,6 +79,7 @@ def contents(package: Package) -> Iterable[str]:
return [path.name for path in _common.files(package).iterdir()]


@deprecated
def is_resource(package: Package, name: str) -> bool:
"""True if `name` is a resource inside `package`.
Expand All @@ -69,6 +92,7 @@ def is_resource(package: Package, name: str) -> bool:
)


@deprecated
def path(
package: Package,
resource: Resource,
Expand Down
9 changes: 9 additions & 0 deletions Lib/test/test_importlib/resources/util.py
@@ -1,8 +1,10 @@
import abc
import contextlib
import importlib
import io
import sys
import types
import warnings
from pathlib import Path, PurePath

from .. import data01
Expand Down Expand Up @@ -67,6 +69,13 @@ def create_package(file=None, path=None, is_package=True, contents=()):
)


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


class CommonTests(metaclass=abc.ABCMeta):
"""
Tests shared by test_open, test_path, and test_read.
Expand Down
3 changes: 2 additions & 1 deletion Lib/test/test_importlib/test_contents.py
Expand Up @@ -15,7 +15,8 @@ class ContentsTests:
}

def test_contents(self):
assert self.expected <= set(resources.contents(self.data))
with util.suppress_known_deprecation():
assert self.expected <= set(resources.contents(self.data))


class ContentsDiskTests(ContentsTests, unittest.TestCase):
Expand Down
53 changes: 32 additions & 21 deletions Lib/test/test_importlib/test_open.py
Expand Up @@ -7,38 +7,47 @@

class CommonBinaryTests(util.CommonTests, unittest.TestCase):
def execute(self, package, path):
with resources.open_binary(package, path):
pass
with util.suppress_known_deprecation():
with resources.open_binary(package, path):
pass


class CommonTextTests(util.CommonTests, unittest.TestCase):
def execute(self, package, path):
with resources.open_text(package, path):
pass
with util.suppress_known_deprecation():
with resources.open_text(package, path):
pass


class OpenTests:
def test_open_binary(self):
with resources.open_binary(self.data, 'binary.file') as fp:
result = fp.read()
self.assertEqual(result, b'\x00\x01\x02\x03')
with util.suppress_known_deprecation():
with resources.open_binary(self.data, 'binary.file') as fp:
result = fp.read()
self.assertEqual(result, b'\x00\x01\x02\x03')

def test_open_text_default_encoding(self):
with resources.open_text(self.data, 'utf-8.file') as fp:
result = fp.read()
with util.suppress_known_deprecation():
with resources.open_text(self.data, 'utf-8.file') as fp:
result = fp.read()
self.assertEqual(result, 'Hello, UTF-8 world!\n')

def test_open_text_given_encoding(self):
with resources.open_text(self.data, 'utf-16.file', 'utf-16', 'strict') as fp:
result = fp.read()
with util.suppress_known_deprecation():
with resources.open_text(
self.data, 'utf-16.file', 'utf-16', 'strict'
) as fp:
result = fp.read()
self.assertEqual(result, 'Hello, UTF-16 world!\n')

def test_open_text_with_errors(self):
# Raises UnicodeError without the 'errors' argument.
with resources.open_text(self.data, 'utf-16.file', 'utf-8', 'strict') as fp:
self.assertRaises(UnicodeError, fp.read)
with resources.open_text(self.data, 'utf-16.file', 'utf-8', 'ignore') as fp:
result = fp.read()
with util.suppress_known_deprecation():
with resources.open_text(self.data, 'utf-16.file', 'utf-8', 'strict') as fp:
self.assertRaises(UnicodeError, fp.read)
with util.suppress_known_deprecation():
with resources.open_text(self.data, 'utf-16.file', 'utf-8', 'ignore') as fp:
result = fp.read()
self.assertEqual(
result,
'H\x00e\x00l\x00l\x00o\x00,\x00 '
Expand All @@ -47,14 +56,16 @@ def test_open_text_with_errors(self):
)

def test_open_binary_FileNotFoundError(self):
self.assertRaises(
FileNotFoundError, resources.open_binary, self.data, 'does-not-exist'
)
with util.suppress_known_deprecation():
self.assertRaises(
FileNotFoundError, resources.open_binary, self.data, 'does-not-exist'
)

def test_open_text_FileNotFoundError(self):
self.assertRaises(
FileNotFoundError, resources.open_text, self.data, 'does-not-exist'
)
with util.suppress_known_deprecation():
self.assertRaises(
FileNotFoundError, resources.open_text, self.data, 'does-not-exist'
)


class OpenDiskTests(OpenTests, unittest.TestCase):
Expand Down
28 changes: 16 additions & 12 deletions Lib/test/test_importlib/test_path.py
Expand Up @@ -8,21 +8,23 @@

class CommonTests(util.CommonTests, unittest.TestCase):
def execute(self, package, path):
with resources.path(package, path):
pass
with util.suppress_known_deprecation():
with resources.path(package, path):
pass


class PathTests:
def test_reading(self):
# Path should be readable.
# Test also implicitly verifies the returned object is a pathlib.Path
# instance.
with resources.path(self.data, 'utf-8.file') as path:
self.assertTrue(path.name.endswith("utf-8.file"), repr(path))
# pathlib.Path.read_text() was introduced in Python 3.5.
with path.open('r', encoding='utf-8') as file:
text = file.read()
self.assertEqual('Hello, UTF-8 world!\n', text)
with util.suppress_known_deprecation():
with resources.path(self.data, 'utf-8.file') as path:
self.assertTrue(path.name.endswith("utf-8.file"), repr(path))
# pathlib.Path.read_text() was introduced in Python 3.5.
with path.open('r', encoding='utf-8') as file:
text = file.read()
self.assertEqual('Hello, UTF-8 world!\n', text)


class PathDiskTests(PathTests, unittest.TestCase):
Expand All @@ -32,8 +34,9 @@ def test_natural_path(self):
# Guarantee the internal implementation detail that
# file-system-backed resources do not get the tempdir
# treatment.
with resources.path(self.data, 'utf-8.file') as path:
assert 'data' in str(path)
with util.suppress_known_deprecation():
with resources.path(self.data, 'utf-8.file') as path:
assert 'data' in str(path)


class PathMemoryTests(PathTests, unittest.TestCase):
Expand All @@ -51,8 +54,9 @@ class PathZipTests(PathTests, util.ZipSetup, unittest.TestCase):
def test_remove_in_context_manager(self):
# It is not an error if the file that was temporarily stashed on the
# file system is removed inside the `with` stanza.
with resources.path(self.data, 'utf-8.file') as path:
path.unlink()
with util.suppress_known_deprecation():
with resources.path(self.data, 'utf-8.file') as path:
path.unlink()


if __name__ == '__main__':
Expand Down

0 comments on commit d5cd2ef

Please sign in to comment.