Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 34k
Description
Bug report
Bug description:
I'm writing a custom importer and discovered that the function signature for importlib.abc.Traversable.read_text() is incompatible with the usage in importlib.resources._functional.read_text(), specifically on Python 3.13.
importlib.abc.Traversable.read_text()is a concrete method; its implementation calls the.open()method, which is an abstract method that must be implemented. The expectation is therefore that implementing.open()in a Traversable subclass is sufficient for.read_text()to work.Note below that the
.read_text()method is not marked as abstract, and includes only one parameter:encoding:cpython/Lib/importlib/resources/abc.py
Lines 84 to 90 in 30aeb00
defread_text(self, encoding: Optional[str] =None) ->str: """ Read contents of self as text """ withself.open(encoding=encoding) asstrm: returnstrm.read() Application code that attempts to read a package resource, like
importlib.resources.read_text(module, "resource.txt")ultimately leads to a call toimportlib.resources._functional.read_text(), which attempts to call the.read_text()method of a Traversable subclass, but includes anerrorsparameter that doesn't exist in Traversable's default concrete method:cpython/Lib/importlib/resources/_functional.py
Lines 28 to 32 in 30aeb00
defread_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) returnresource.read_text(encoding=encoding, errors=errors) Consequently, it appears to be necessary for all Traversable subclasses to not only re-implement its concrete
.read_text()method, but also to override its signature.
I think that the Traversable .read_text() method signature, and the call site in importlib.resources._functional.read_text(), need to align with each other.
I'd like to submit a PR for this! However, I would like confirmation that an errors parameter should be added to the Traversable.read_text() method.
Note that adding an errors parameter was previously discussed in #88368.
Demonstration of TypeError bug
importioimportsysimporttypingimportpathlibimporttypesimportimportlib.abcimportimportlib.machineryimportimportlib.metadataimportimportlib.resources.abcclassExampleFinder(importlib.abc.MetaPathFinder): deffind_spec( self, fullname: str, path: typing.Sequence[str] |None, target: types.ModuleType|None=None, ) ->importlib.machinery.ModuleSpec|None: iffullname!="demonstrate_error": returnNoneprint(f"ExampleFinder.find_spec('{fullname}')") spec=importlib.machinery.ModuleSpec( name=fullname, loader=ExampleLoader(), is_package=True, ) returnspecsys.meta_path.append(ExampleFinder()) classExampleLoader(importlib.abc.Loader): defexec_module(self, module: types.ModuleType) ->None: print(f"ExampleLoader.exec_module({module})") exec("", module.__dict__) defget_resource_reader(self, fullname: str) ->"ExampleTraversableResources": print(f"ExampleLoader.get_resource_reader('{fullname}')") returnExampleTraversableResources(fullname) classExampleTraversableResources(importlib.resources.abc.TraversableResources): def__init__(self, fullname: str) ->None: self.fullname=fullnamedeffiles(self) ->"ExampleTraversable": print("ExampleTraversableResources.files()") returnExampleTraversable(self.fullname) # ----------------------------------------------------------------------------# ExampleTraversable implements all five of the Traversable abstract methods.# Specifically, it is expected that implementing `.open()` will be sufficient,# but this will not be the case.#classExampleTraversable(importlib.resources.abc.Traversable): def__init__(self, path: str): self._path=pathdefiterdir(self) ->typing.Iterator["ExampleTraversable"]: yieldExampleTraversable("resource.txt") defis_dir(self) ->bool: returnFalsedefis_file(self) ->bool: returnTruedefopen(self, mode='r', *args, **kwargs) ->typing.IO[typing.AnyStr]: returnio.StringIO("Nice! The call to .read_text() succeeded!") # Uncomment this `.read_text()` method to make `.read_text()` calls work.# It overrides the `Traversable.read_text()` signature.## def read_text(self, encoding: str | None, errors: str | None) -> str:# print(f"ExampleTraversable.read_text('{encoding}', '{errors}')")# return str(super().read_text(encoding))@propertydefname(self) ->str: returnpathlib.PurePosixPath(self._path).name# -------------------------------------------------------------------------------# Everything above allows us to import this hard-coded module# and demonstrate a TypeError lurking in the Traversable.read_text() signature.#importdemonstrate_error# The next line will raise a TypeError.# `importlib/resources/_functional.py:read_text()` calls `Traversable.read_text()`# with an `errors` argument that is not supported by the default concrete method.print(importlib.resources.read_text(demonstrate_error, "resource.txt"))CPython versions tested on:
3.13
Operating systems tested on:
Linux