Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 22 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions upath/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
12 changes: 12 additions & 0 deletions upath/tests/test_extensions.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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)