diff --git a/HISTORY.md b/HISTORY.md index 1dd1caea..b39bee8c 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,9 @@ # cloudpathlib Changelog +## Unreleased + +- Fixed `CloudPath(...) / other` to correctly attempt to fall back on `other`'s `__rtruediv__` implementation, in order to support classes that explicitly support the `/` with a `CloudPath` instance. Previously, this would always raise a `TypeError` if `other` were not a `str` or `PurePosixPath`. (PR [#479](https://github.com/drivendataorg/cloudpathlib/pull/479)) + ## v0.20.0 (2024-10-18) - Added support for custom schemes in CloudPath and Client subclases. (Issue [#466](https://github.com/drivendataorg/cloudpathlib/issues/466), PR [#467](https://github.com/drivendataorg/cloudpathlib/pull/467)) diff --git a/cloudpathlib/cloudpath.py b/cloudpathlib/cloudpath.py index ef28d5e6..5845e929 100644 --- a/cloudpathlib/cloudpath.py +++ b/cloudpathlib/cloudpath.py @@ -888,7 +888,7 @@ def _dispatch_to_path(self, func: str, *args, **kwargs) -> Any: def __truediv__(self, other: Union[str, PurePosixPath]) -> Self: if not isinstance(other, (str, PurePosixPath)): - raise TypeError(f"Can only join path {repr(self)} with strings or posix paths.") + return NotImplemented return self._dispatch_to_path("__truediv__", other) diff --git a/tests/test_cloudpath_manipulation.py b/tests/test_cloudpath_manipulation.py index d42b154a..b9e70669 100644 --- a/tests/test_cloudpath_manipulation.py +++ b/tests/test_cloudpath_manipulation.py @@ -204,3 +204,28 @@ def test_parser(rig): else: # always posixpath since our dispath goes to PurePosixPath assert rig.create_cloud_path("a/b/c").parser == posixpath + + +def test_truediv_fallback(rig): + """Another class with __rtruediv__ method should be able to use / operator with CloudPath.""" + + class CustomClassSupportsCloudPath: + def __init__(self, value: str): + self.value = value + + def __rtruediv__(self, other): + if isinstance(other, CloudPath): + return other / self.value + return NotImplemented + + assert rig.create_cloud_path("a/b") / CustomClassSupportsCloudPath( + "c" + ) == rig.create_cloud_path("a/b/c") + + # Expect TypeError if / operation with CloudPath is not supported + class CustomClassDoesNotSupportCloudPath: + def __init__(self, value: str): + self.value = value + + with pytest.raises(TypeError): + rig.create_cloud_path("a/b") / CustomClassDoesNotSupportCloudPath("c")