Skip to content

bpo-45514: Deprecate importlib resources legacy functions. #29036

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Nov 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion Doc/library/importlib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -865,7 +865,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 @@ -927,6 +929,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 @@ -942,6 +946,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 @@ -954,6 +960,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 @@ -967,6 +975,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 @@ -982,6 +992,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 @@ -990,6 +1002,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 @@ -1000,6 +1014,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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading