diff --git a/pyproject.toml b/pyproject.toml index 671af64c..abe1fe8c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ maintainers = [ requires-python = ">=3.9" dependencies = [ "fsspec >=2024.5.0", - "pathlib-abc ==0.4.3", + "pathlib-abc ==0.5.0", ] classifiers = [ "Programming Language :: Python :: 3", diff --git a/upath/core.py b/upath/core.py index 5c889ca9..3ab82c38 100644 --- a/upath/core.py +++ b/upath/core.py @@ -415,6 +415,9 @@ def with_segments(self, *pathsegments: JoinablePathLike) -> Self: ) def __str__(self) -> str: + return self.__vfspath__() + + def __vfspath__(self) -> str: return self._chain_parser.chain(self._chain.to_list())[0] def __repr__(self) -> str: @@ -484,12 +487,8 @@ def iterdir(self) -> Iterator[Self]: _, _, name = name.removesuffix(sep).rpartition(self.parser.sep) yield base.with_segments(str(base), name) - def __open_rb__(self, buffering: int = -1) -> BinaryIO: - block_size = _buffering2blocksize("wb", buffering) - kw = {} - if block_size is not None: - kw["block_size"] = block_size - return self.fs.open(self.path, mode="rb", **kw) + def __open_reader__(self) -> BinaryIO: + return self.fs.open(self.path, mode="rb") def readlink(self) -> Self: raise NotImplementedError @@ -523,12 +522,8 @@ def mkdir( if not self.is_dir(): raise FileExistsError(str(self)) - def __open_wb__(self, buffering: int = -1) -> BinaryIO: - block_size = _buffering2blocksize("wb", buffering) - kw = {} - if block_size is not None: - kw["block_size"] = block_size - return self.fs.open(self.path, mode="wb", **kw) + def __open_writer__(self, mode: Literal["a", "w", "x"]) -> BinaryIO: + return self.fs.open(self.path, mode=f"{mode}b") # --- upath overrides --------------------------------------------- diff --git a/upath/extensions.py b/upath/extensions.py index 3c9cec06..d1f55dee 100644 --- a/upath/extensions.py +++ b/upath/extensions.py @@ -123,8 +123,8 @@ def iterdir(self) -> Iterator[Self]: for pth in self.__wrapped__.iterdir(): yield self._from_upath(pth) - def __open_rb__(self, buffering: int = -1) -> BinaryIO: - return self.__wrapped__.__open_rb__(buffering) + def __open_reader__(self) -> BinaryIO: + return self.__wrapped__.__open_reader__() def readlink(self) -> Self: return self._from_upath(self.__wrapped__.readlink()) @@ -144,8 +144,8 @@ def mkdir( ) -> None: self.__wrapped__.mkdir(mode=mode, parents=parents, exist_ok=exist_ok) - def __open_wb__(self, buffering: int = -1) -> BinaryIO: - return self.__wrapped__.__open_wb__(buffering) + def __open_writer__(self, mode: Literal["a", "w", "x"]) -> BinaryIO: + return self.__wrapped__.__open_writer__(mode) @overload def open( diff --git a/upath/types/__init__.py b/upath/types/__init__.py index 7a18913a..b7e0bed8 100644 --- a/upath/types/__init__.py +++ b/upath/types/__init__.py @@ -17,13 +17,12 @@ from typing import overload from typing import runtime_checkable -from pathlib_abc import magic_open - from upath.types._abc import JoinablePath from upath.types._abc import PathInfo from upath.types._abc import PathParser from upath.types._abc import ReadablePath from upath.types._abc import WritablePath +from upath.types._abc import vfsopen if TYPE_CHECKING: if sys.version_info > (3, 11): @@ -110,7 +109,7 @@ def open( errors: str | None = None, newline: str | None = None, ) -> IO[Any]: - return magic_open(self, mode, buffering, encoding, errors, newline) + return vfsopen(self, mode, buffering, encoding, errors, newline) if sys.version_info >= (3, 14): @@ -125,11 +124,10 @@ class CompatJoinablePath(Protocol): # not available in Python 3.9.* pathlib: # - `parser` # - `with_segments` + # - `__vfspath__` # - `full_match` __slots__ = () - def __str__(self) -> str: ... - @property def anchor(self) -> str: ... @property @@ -161,8 +159,8 @@ def parents(self) -> Sequence[Self]: ... @runtime_checkable class CompatReadablePath(CompatJoinablePath, Protocol): # not available in Python 3.9.* pathlib: - # - `__open_rb__` # - `info` + # - `__open_reader__` # - `copy` # - `copy_into` # - `walk` @@ -187,7 +185,7 @@ def readlink(self) -> Self: ... @runtime_checkable class CompatWritablePath(CompatJoinablePath, Protocol): # not available in Python 3.9.* pathlib: - # - `__open_wb__` + # - `__open_writer__` # - `_copy_from` __slots__ = () diff --git a/upath/types/_abc.py b/upath/types/_abc.py index 237cfcad..12cd7d5b 100644 --- a/upath/types/_abc.py +++ b/upath/types/_abc.py @@ -5,6 +5,8 @@ from pathlib_abc import PathParser from pathlib_abc import ReadablePath from pathlib_abc import WritablePath +from pathlib_abc import vfsopen +from pathlib_abc import vfspath __all__ = [ "JoinablePath", @@ -12,4 +14,6 @@ "WritablePath", "PathInfo", "PathParser", + "vfsopen", + "vfspath", ] diff --git a/upath/types/_abc.pyi b/upath/types/_abc.pyi index 2788fb4c..6964b2cc 100644 --- a/upath/types/_abc.pyi +++ b/upath/types/_abc.pyi @@ -7,8 +7,10 @@ from typing import Any from typing import BinaryIO from typing import Callable from typing import Iterator +from typing import Literal from typing import Protocol from typing import Sequence +from typing import TextIO from typing import TypeVar from typing import runtime_checkable @@ -26,7 +28,7 @@ class JoinablePath(ABC): @abstractmethod def with_segments(self, *pathsegments: str | Self) -> Self: ... @abstractmethod - def __str__(self) -> str: ... + def __vfspath__(self) -> str: ... @property def anchor(self) -> str: ... @property @@ -61,7 +63,7 @@ class ReadablePath(JoinablePath): @abstractmethod def info(self) -> PathInfo: ... @abstractmethod - def __open_rb__(self, buffering: int = ...) -> BinaryIO: ... + def __open_reader__(self) -> BinaryIO: ... def read_bytes(self) -> bytes: ... def read_text( self, @@ -93,7 +95,7 @@ class WritablePath(JoinablePath): @abstractmethod def mkdir(self) -> None: ... @abstractmethod - def __open_wb__(self, buffering: int = ...) -> BinaryIO: ... + def __open_writer__(self, mode: Literal["a", "w", "x"]) -> BinaryIO: ... def write_bytes(self, data: bytes) -> int: ... def write_text( self, @@ -119,3 +121,26 @@ class PathInfo(Protocol): def is_dir(self, *, follow_symlinks: bool = True) -> bool: ... def is_file(self, *, follow_symlinks: bool = True) -> bool: ... def is_symlink(self) -> bool: ... + +class SupportsOpenReader(Protocol): + def __open_reader__(self) -> BinaryIO: ... + +class SupportsOpenWriter(Protocol): + def __open_writer__(self, mode: Literal["a", "w", "x"]) -> BinaryIO: ... + +class SupportsOpenUpdater(Protocol): + def __open_updater__(self, mode: Literal["r+", "w+", "+r", "+w"]) -> BinaryIO: ... + +def vfsopen( + obj: SupportsOpenReader | SupportsOpenWriter | SupportsOpenUpdater, + mode="r", + buffering: int = -1, + encoding: str | None = None, + errors: str | None = None, + newline: str | None = None, +) -> BinaryIO | TextIO: ... + +class SupportsVFSPath(Protocol): + def __vfspath__(self) -> str: ... + +def vfspath(obj: SupportsVFSPath) -> str: ...