From 4acaa33a8c39d2408dee830dfa437cd22c30be02 Mon Sep 17 00:00:00 2001 From: Andreas Poehlmann Date: Thu, 16 Oct 2025 00:14:15 +0200 Subject: [PATCH 1/2] upath.core: deprecate _protocol_dispatch=False --- upath/core.py | 7 +++++++ upath/tests/test_extensions.py | 12 ++++++++++++ 2 files changed, 19 insertions(+) diff --git a/upath/core.py b/upath/core.py index ce309265..168667f8 100644 --- a/upath/core.py +++ b/upath/core.py @@ -337,6 +337,13 @@ def __new__( # # THIS IS DEPRECATED! # Use upath.extensions.ProxyUPath to extend the UPath API + warnings.warn( + f"{cls.__name__}._protocol_dispatch = False is deprecated and" + " will be removed in future universal_pathlib versions." + " To extend the UPath API, subclass upath.extensions.ProxyUPath", + DeprecationWarning, + stacklevel=2, + ) upath_cls = cls if issubclass(upath_cls, cls): diff --git a/upath/tests/test_extensions.py b/upath/tests/test_extensions.py index 586d61b1..11151baa 100644 --- a/upath/tests/test_extensions.py +++ b/upath/tests/test_extensions.py @@ -1,5 +1,6 @@ import pytest +from upath import UPath from upath.extensions import ProxyUPath from upath.implementations.local import FilePath from upath.implementations.memory import MemoryPath @@ -53,3 +54,14 @@ def write_bytes_reversed(self, value): r.parent.joinpath("file2").write_bytes_reversed(b"dlrow olleh") assert b.joinpath("file2").read_bytes() == b"hello world" + + +def test_protocol_dispatch_deprecation_warning(): + + class MyPath(UPath): + _protocol_dispatch = False + + with pytest.warns(DeprecationWarning, match="_protocol_dispatch = False"): + a = MyPath(".", protocol="memory") + + assert isinstance(a, MyPath) From 8a5f3301b6a4aa3a9f48ea93834df65dbe724f58 Mon Sep 17 00:00:00 2001 From: Andreas Poehlmann Date: Thu, 16 Oct 2025 09:31:33 +0200 Subject: [PATCH 2/2] readme: update the extensions section --- README.md | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 0d3cbfb5..747e4a55 100644 --- a/README.md +++ b/README.md @@ -602,25 +602,28 @@ class MyProtoPath(UPath): return args, protocol, storage_options ``` -#### Stopping UPath's subclass dispatch mechanism +#### Extending UPath's API interface -There are cases for which you want to disable the protocol dispatch mechanism of -the `UPath.__new__` constructor. For example if you want to extend the class API -of your `UPath` implementation, and use it as the base class for other, directly -instantiated subclasses. Together with other customization options this can be a -useful feature. Please be aware that in this case all protocols are handled with -the default implementation in UPath. Please always feel free to open an issue in -the issue tracker to discuss your use case. We're happy to help with finding the -most maintainable solution. +If you want to extend the class API +of your `UPath` implementation, it's recommended to subclass +`upath.extensions.ProxyUPath`. It's a thin proxy layer around +the public methods and attributes of a UPath instance. ```python -class ExtraUPath(UPath): - _protocol_dispatch = False # disable the registry return an ExtraUPath +from upath.extensions import ProxyUPath + +class ExtraUPath(ProxyUPath): def some_extra_method(self) -> str: - return "hello world" + return f"hello world {self.name}" + +e0 = ExtraUPath("s3://bucket/foo.txt") +e1 = ExtraUPath("memory://bar/baz.txt") -assert ExtraUPath("s3://bucket/file.txt").some_extra_method() == "hello world" +assert e0.some_extra_method() == "hello world foo.txt" +assert isinstance(e0, ExtraUPath) +assert e1.some_extra_method() == "hello world baz.txt" +assert isinstance(e1, ExtraUPath) ``` ## Migration Guide @@ -729,6 +732,12 @@ path = UPath("memory:///file.txt") os.remove(path) # TypeError: expected str, bytes or os.PathLike, not MemoryPath ``` +#### Extending UPath via `_protocol_dispatch=False` + +If you previously used `_protocol_dipatch=False` to enable extension of the +UPath API, we now recommend to subclass `upath.extensions.ProxyUPath`. See +the example in the main docs. + ### migrating to `v0.2.0` ### _FSSpecAccessor subclasses with custom filesystem access methods