From 85121de466f979a1cc5e1300c114328e76eed8ad Mon Sep 17 00:00:00 2001 From: Max Muoto Date: Mon, 16 Sep 2024 13:21:35 -0500 Subject: [PATCH] Update importlib resources for 3.13 (#12298) --- stdlib/@tests/stubtest_allowlists/py312.txt | 3 ++ stdlib/@tests/stubtest_allowlists/py313.txt | 13 ++--- .../test_cases/check_importlib_resources.py | 32 +++++++++++ stdlib/VERSIONS | 2 + stdlib/importlib/abc.pyi | 4 +- stdlib/importlib/resources/__init__.pyi | 54 +++++++++++++------ stdlib/importlib/resources/_common.pyi | 42 +++++++++++++++ stdlib/importlib/resources/_functional.pyi | 30 +++++++++++ 8 files changed, 153 insertions(+), 27 deletions(-) create mode 100644 stdlib/@tests/test_cases/check_importlib_resources.py create mode 100644 stdlib/importlib/resources/_common.pyi create mode 100644 stdlib/importlib/resources/_functional.pyi diff --git a/stdlib/@tests/stubtest_allowlists/py312.txt b/stdlib/@tests/stubtest_allowlists/py312.txt index 8b02abd9621c..14500a678fdf 100644 --- a/stdlib/@tests/stubtest_allowlists/py312.txt +++ b/stdlib/@tests/stubtest_allowlists/py312.txt @@ -164,6 +164,9 @@ platform.uname_result.processor unittest.TestCase.__init_subclass__ unittest.case.TestCase.__init_subclass__ +# Deprecated argument is supported at runtime by renaming it through a decorator. +importlib.resources._common.files +importlib.resources.files # Problematic protocol signature at runtime, see source code comments. importlib.abc.Traversable.open importlib.resources.abc.Traversable.open diff --git a/stdlib/@tests/stubtest_allowlists/py313.txt b/stdlib/@tests/stubtest_allowlists/py313.txt index b3bb5a823dfc..8ddbb8fe3089 100644 --- a/stdlib/@tests/stubtest_allowlists/py313.txt +++ b/stdlib/@tests/stubtest_allowlists/py313.txt @@ -4,16 +4,6 @@ # TODO: triage these new errors _tkinter.create -importlib.resources.Anchor -importlib.resources.Resource -importlib.resources.__all__ -importlib.resources.contents -importlib.resources.is_resource -importlib.resources.open_binary -importlib.resources.open_text -importlib.resources.path -importlib.resources.read_binary -importlib.resources.read_text os.path.splitroot tkinter.Misc.after_info tkinter.Misc.busy @@ -151,6 +141,9 @@ platform.uname_result.processor unittest.TestCase.__init_subclass__ unittest.case.TestCase.__init_subclass__ +# Deprecated argument is supported at runtime by renaming it through a decorator. +importlib.resources._common.files +importlib.resources.files # Problematic protocol signature at runtime, see source code comments. importlib.abc.Traversable.open importlib.resources.abc.Traversable.open diff --git a/stdlib/@tests/test_cases/check_importlib_resources.py b/stdlib/@tests/test_cases/check_importlib_resources.py new file mode 100644 index 000000000000..cf1507389bef --- /dev/null +++ b/stdlib/@tests/test_cases/check_importlib_resources.py @@ -0,0 +1,32 @@ +from __future__ import annotations + +import importlib.resources +import pathlib +import sys + + +class _CustomPathLike: + def __fspath__(self) -> str: + return "" + + +if sys.version_info >= (3, 13): + + def f(pth: pathlib.Path | str | _CustomPathLike) -> None: + importlib.resources.open_binary("pkg", pth) + # Encoding defaults to "utf-8" for one arg. + importlib.resources.open_text("pkg", pth) + # Otherwise, it must be specified. + importlib.resources.open_text("pkg", pth, pth) # type: ignore + importlib.resources.open_text("pkg", pth, pth, encoding="utf-8") + + # Encoding defaults to "utf-8" for one arg. + importlib.resources.read_text("pkg", pth) + # Otherwise, it must be specified. + importlib.resources.read_text("pkg", pth, pth) # type: ignore + importlib.resources.read_text("pkg", pth, pth, encoding="utf-8") + + importlib.resources.read_binary("pkg", pth) + importlib.resources.path("pkg", pth) + importlib.resources.is_resource("pkg", pth) + importlib.resources.contents("pkg", pth) diff --git a/stdlib/VERSIONS b/stdlib/VERSIONS index 66bf2bec7cb0..dfed62f694fc 100644 --- a/stdlib/VERSIONS +++ b/stdlib/VERSIONS @@ -161,6 +161,8 @@ importlib.metadata._meta: 3.10- importlib.metadata.diagnose: 3.13- importlib.readers: 3.10- importlib.resources: 3.7- +importlib.resources._common: 3.11- +importlib.resources._functional: 3.13- importlib.resources.abc: 3.11- importlib.resources.readers: 3.11- importlib.resources.simple: 3.11- diff --git a/stdlib/importlib/abc.pyi b/stdlib/importlib/abc.pyi index 3937481159dc..4a0a70d0930d 100644 --- a/stdlib/importlib/abc.pyi +++ b/stdlib/importlib/abc.pyi @@ -145,10 +145,10 @@ if sys.version_info >= (3, 9): # which is not the case. @overload @abstractmethod - def open(self, mode: Literal["r"] = "r", /, *, encoding: str | None = None, errors: str | None = None) -> IO[str]: ... + def open(self, mode: Literal["r"] = "r", *, encoding: str | None = None, errors: str | None = None) -> IO[str]: ... @overload @abstractmethod - def open(self, mode: Literal["rb"], /) -> IO[bytes]: ... + def open(self, mode: Literal["rb"]) -> IO[bytes]: ... @property @abstractmethod def name(self) -> str: ... diff --git a/stdlib/importlib/resources/__init__.pyi b/stdlib/importlib/resources/__init__.pyi index 8d656563772c..f82df8c591fa 100644 --- a/stdlib/importlib/resources/__init__.pyi +++ b/stdlib/importlib/resources/__init__.pyi @@ -7,10 +7,15 @@ from types import ModuleType from typing import Any, BinaryIO, TextIO from typing_extensions import TypeAlias +if sys.version_info >= (3, 11): + from importlib.resources._common import Package as Package +else: + Package: TypeAlias = str | ModuleType + if sys.version_info >= (3, 9): from importlib.abc import Traversable -__all__ = ["Package", "Resource", "contents", "is_resource", "open_binary", "open_text", "path", "read_binary", "read_text"] +__all__ = ["Package", "contents", "is_resource", "open_binary", "open_text", "path", "read_binary", "read_text"] if sys.version_info >= (3, 9): __all__ += ["as_file", "files"] @@ -18,26 +23,45 @@ if sys.version_info >= (3, 9): if sys.version_info >= (3, 10): __all__ += ["ResourceReader"] -Package: TypeAlias = str | ModuleType +if sys.version_info < (3, 13): + __all__ += ["Resource"] -if sys.version_info >= (3, 11): - Resource: TypeAlias = str -else: +if sys.version_info < (3, 11): Resource: TypeAlias = str | os.PathLike[Any] +elif sys.version_info < (3, 13): + Resource: TypeAlias = str -def open_binary(package: Package, resource: Resource) -> BinaryIO: ... -def open_text(package: Package, resource: Resource, encoding: str = "utf-8", errors: str = "strict") -> TextIO: ... -def read_binary(package: Package, resource: Resource) -> bytes: ... -def read_text(package: Package, resource: Resource, encoding: str = "utf-8", errors: str = "strict") -> str: ... -def path(package: Package, resource: Resource) -> AbstractContextManager[Path]: ... -def is_resource(package: Package, name: str) -> bool: ... -def contents(package: Package) -> Iterator[str]: ... +if sys.version_info >= (3, 13): + from importlib.resources._common import Anchor as Anchor -if sys.version_info >= (3, 9): + __all__ += ["Anchor"] + + from importlib.resources._functional import ( + contents as contents, + is_resource as is_resource, + open_binary as open_binary, + open_text as open_text, + path as path, + read_binary as read_binary, + read_text as read_text, + ) + +else: + def open_binary(package: Package, resource: Resource) -> BinaryIO: ... + def open_text(package: Package, resource: Resource, encoding: str = "utf-8", errors: str = "strict") -> TextIO: ... + def read_binary(package: Package, resource: Resource) -> bytes: ... + def read_text(package: Package, resource: Resource, encoding: str = "utf-8", errors: str = "strict") -> str: ... + def path(package: Package, resource: Resource) -> AbstractContextManager[Path]: ... + def is_resource(package: Package, name: str) -> bool: ... + def contents(package: Package) -> Iterator[str]: ... + +if sys.version_info >= (3, 11): + from importlib.resources._common import as_file as as_file +elif sys.version_info >= (3, 9): def as_file(path: Traversable) -> AbstractContextManager[Path]: ... -if sys.version_info >= (3, 12): - def files(anchor: Package | None = ...) -> Traversable: ... +if sys.version_info >= (3, 11): + from importlib.resources._common import files as files elif sys.version_info >= (3, 9): def files(package: Package) -> Traversable: ... diff --git a/stdlib/importlib/resources/_common.pyi b/stdlib/importlib/resources/_common.pyi new file mode 100644 index 000000000000..f04f70f25e23 --- /dev/null +++ b/stdlib/importlib/resources/_common.pyi @@ -0,0 +1,42 @@ +import sys + +# Even though this file is 3.11+ only, Pyright will complain in stubtest for older versions. +if sys.version_info >= (3, 11): + import types + from collections.abc import Callable + from contextlib import AbstractContextManager + from importlib.abc import ResourceReader, Traversable + from pathlib import Path + from typing import overload + from typing_extensions import TypeAlias, deprecated + + Package: TypeAlias = str | types.ModuleType + + if sys.version_info >= (3, 12): + Anchor: TypeAlias = Package + + def package_to_anchor( + func: Callable[[Anchor | None], Traversable] + ) -> Callable[[Anchor | None, Anchor | None], Traversable]: ... + @overload + def files(anchor: Anchor | None = None) -> Traversable: ... + @overload + @deprecated("First parameter to files is renamed to 'anchor'") + def files(package: Anchor | None = None) -> Traversable: ... + + else: + def files(package: Package) -> Traversable: ... + + def get_resource_reader(package: types.ModuleType) -> ResourceReader | None: ... + + if sys.version_info >= (3, 12): + def resolve(cand: Anchor | None) -> types.ModuleType: ... + + else: + def resolve(cand: Package) -> types.ModuleType: ... + + if sys.version_info < (3, 12): + def get_package(package: Package) -> types.ModuleType: ... + + def from_package(package: types.ModuleType) -> Traversable: ... + def as_file(path: Traversable) -> AbstractContextManager[Path]: ... diff --git a/stdlib/importlib/resources/_functional.pyi b/stdlib/importlib/resources/_functional.pyi new file mode 100644 index 000000000000..97e46bdf0a53 --- /dev/null +++ b/stdlib/importlib/resources/_functional.pyi @@ -0,0 +1,30 @@ +import sys + +# Even though this file is 3.13+ only, Pyright will complain in stubtest for older versions. +if sys.version_info >= (3, 13): + from _typeshed import StrPath + from collections.abc import Iterator + from contextlib import AbstractContextManager + from importlib.resources._common import Anchor + from io import TextIOWrapper + from pathlib import Path + from typing import BinaryIO, overload + from typing_extensions import Unpack + + def open_binary(anchor: Anchor, *path_names: StrPath) -> BinaryIO: ... + @overload + def open_text( + anchor: Anchor, *path_names: Unpack[tuple[StrPath]], encoding: str | None = "utf-8", errors: str | None = "strict" + ) -> TextIOWrapper: ... + @overload + def open_text(anchor: Anchor, *path_names: StrPath, encoding: str | None, errors: str | None = "strict") -> TextIOWrapper: ... + def read_binary(anchor: Anchor, *path_names: StrPath) -> bytes: ... + @overload + def read_text( + anchor: Anchor, *path_names: Unpack[tuple[StrPath]], encoding: str | None = "utf-8", errors: str | None = "strict" + ) -> str: ... + @overload + def read_text(anchor: Anchor, *path_names: StrPath, encoding: str | None, errors: str | None = "strict") -> str: ... + def path(anchor: Anchor, *path_names: StrPath) -> AbstractContextManager[Path]: ... + def is_resource(anchor: Anchor, *path_names: StrPath) -> bool: ... + def contents(anchor: Anchor, *path_names: StrPath) -> Iterator[str]: ...