Repository: fsspec/universal_pathlib (python)

Issue:
`S3Path.touch` with `exist_ok=True` on existing file raises an error
`S3Path.touch` with `exist_ok=True` on existing file raises an error. 
[Here](https://github.com/fsspec/universal_pathlib/blob/569ceabb73503c20521949b6a5c7e3de8c6d411f/upath/core.py#L756) `exist_ok=True` sets `truncate` to `False`
and this raises the error [here ](https://github.com/fsspec/s3fs/blob/3966b39c5d57ba14fc8fb543c1d80f66648a4096/s3fs/core.py#L1095)

On 0.1.4 it worked fine.

Pull request:
Fix touch exists_ok if file exists
Close #216 

This fixes behavior of touch. `truncate` should never be set on an existing file when using touch from the pathlib api.
This also silently ignores an error when the filesystem does not support touching an existing file. (We could consider throwing a warning in this case)

Patch:
diff --git a/upath/core.py b/upath/core.py
index 5650819..4c86e28 100644
--- a/upath/core.py
+++ b/upath/core.py
@@ -976,7 +976,16 @@ class UPath(PathlibPathShim, Path):
         raise NotImplementedError
 
     def touch(self, mode=0o666, exist_ok=True) -> None:
-        self.fs.touch(self.path, truncate=not exist_ok)
+        exists = self.fs.exists(self.path)
+        if exists and not exist_ok:
+            raise FileExistsError(str(self))
+        if not exists:
+            self.fs.touch(self.path, truncate=True)
+        else:
+            try:
+                self.fs.touch(self.path, truncate=False)
+            except (NotImplementedError, ValueError):
+                pass  # unsupported by filesystem
 
     def mkdir(self, mode=0o777, parents=False, exist_ok=False) -> None:
         if parents and not exist_ok and self.exists():
diff --git a/upath/implementations/cloud.py b/upath/implementations/cloud.py
index a7fe60b..e2f4cb9 100644
--- a/upath/implementations/cloud.py
+++ b/upath/implementations/cloud.py
@@ -80,10 +80,3 @@ class S3Path(CloudPath):
 
 class AzurePath(CloudPath):
     __slots__ = ()
-
-    def touch(self, mode=0o666, exist_ok=True):
-        if exist_ok and self.exists():
-            with self.fs.open(self.path, mode="a"):
-                pass
-        else:
-            self.fs.touch(self.path, truncate=True)


Test patch:
diff --git a/upath/tests/cases.py b/upath/tests/cases.py
index 1d50af5..4f02539 100644
--- a/upath/tests/cases.py
+++ b/upath/tests/cases.py
@@ -320,6 +320,19 @@ class BaseTests:
     def test_symlink_to(self):
         pass
 
+    def test_touch_exists_ok_false(self):
+        f = self.path.joinpath("file1.txt")
+        assert f.exists()
+        with pytest.raises(FileExistsError):
+            f.touch(exist_ok=False)
+
+    def test_touch_exists_ok_true(self):
+        f = self.path.joinpath("file1.txt")
+        assert f.exists()
+        data = f.read_text()
+        f.touch(exist_ok=True)
+        assert f.read_text() == data
+
     def test_touch_unlink(self):
         path = self.path.joinpath("test_touch.txt")
         path.touch()
diff --git a/upath/tests/implementations/test_data.py b/upath/tests/implementations/test_data.py
index 6fb1132..9ada068 100644
--- a/upath/tests/implementations/test_data.py
+++ b/upath/tests/implementations/test_data.py
@@ -133,9 +133,15 @@ class TestUPathDataPath(BaseTests):
         with pytest.raises(NotImplementedError):
             list(self.path.rglob("*"))
 
+    def test_touch_exists_ok_false(self):
+        with pytest.raises(FileExistsError):
+            self.path.touch(exist_ok=False)
+
+    def test_touch_exists_ok_true(self):
+        self.path.touch()
+
     def test_touch_unlink(self):
-        with pytest.raises(NotImplementedError):
-            self.path.touch()
+        self.path.touch()
         with pytest.raises(NotImplementedError):
             self.path.unlink()
 
diff --git a/upath/tests/implementations/test_s3.py b/upath/tests/implementations/test_s3.py
index 391bd4f..89e7116 100644
--- a/upath/tests/implementations/test_s3.py
+++ b/upath/tests/implementations/test_s3.py
@@ -53,20 +53,6 @@ class TestUPathS3(BaseTests):
             assert x.name != ""
             assert x.exists()
 
-    def test_touch_unlink(self):
-        path = self.path.joinpath("test_touch.txt")
-        path.touch()
-        assert path.exists()
-        path.unlink()
-        assert not path.exists()
-
-        # should raise FileNotFoundError since file is missing
-        with pytest.raises(FileNotFoundError):
-            path.unlink()
-
-        # file doesn't exists, but missing_ok is True
-        path.unlink(missing_ok=True)
-
     @pytest.mark.parametrize(
         "joiner", [["bucket", "path", "file"], ["bucket/path/file"]]
     )