Skip to content

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

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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