Skip to content

Improve error message when providing UPath instances to open() #124

@ap--

Description

@ap--

Currently opening a UPath via open fails with a FileNotFoundError because the URI returned via __fspath__() is just interpreted as a string path.

>>> import upath
>>> pth = upath.UPath("memory:///abc.txt")
>>> pth.write_text("hello world")
11
>>> with open(pth, "rt") as f:
...     print(f.read())
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or directory: 'memory:/abc.txt'

There's two possible ways of improving this error:

  1. We raise an exception in non-local UPath subclasses in __fspath__.
  2. Or we could keep the basic functionality that fspath returns a string and raise an exception if we detect that the result was used in open
    import os
    
    import sys
    from textwrap import dedent
    from typing import Self
    
    
    class _upath_str(str):
        """helper str subclass for better error messages
    
        This helper class allows us to catch when a user tries
        to provide a UPath instance of a non-local filesystem
        to Python's `open()` function.
        """
        def __str__(self) -> Self:
            return self
    
    
    def _upath_audit_hook(name: str, args: tuple) -> None:
        """audit hook matching the `open` event
    
        Raises a better error message if we detect that a
        user tries to use UPath instances with `open()`.
        """
        if name == "open" and isinstance(args[0], _upath_str):
            msg = dedent(
                f"""\
                builtins.open does not support non-local UPath instances.
                  Use the pathlib interface instead:
                    >>> pth = UPath({args[0]!r})
                    >>> with pth.open() as f:
                    >>>    ...
                """
            )
            raise OSError(msg)
    
    
    sys.addaudithook(_upath_audit_hook)
    
    
    class A:
        def __fspath__(self):
            return _upath_str("abc")
    
        def __str__(self):
            return _upath_str("abc")
    
    a = A()
    b = _upath_str("b")
    print(type(b))
    c = str(a)
    print(type(c))
    print(type(str(c)))
    
    os.fspath(a)
    
    with open(a) as f:
        pass

(1) would be preferable, but (2) might be useful, since there's some code out there that uses __fspath__ to convert os.PathLike to strings.

Need to do a survey of code on github, and if we choose (1) there needs to be a deprecation period.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions