Repository: fsspec/filesystem_spec
Pull request #975 (2022-06-06 10:07:24)

Add truncate arg into LocalFileSystem touch()
We try to implement a fsspec-backend and are digging into the `AbstractFileSystem` spec.

I was reading the implementation of `LocalFileSystem` for reference, but there were some differences from the specifications.

If the implementation is as [specified](https://filesystem-spec.readthedocs.io/en/latest/api.html#fsspec.spec.AbstractFileSystem.touch), the following test should pass, but it didn't.

This PR fix the implementation so that it does not ignore the truncate argument.

```
def test_local_touch_truncate():
    f = fsspec.filesystem("file")
    f.touch('/tmp/a', truncate=True)

    f.pipe('/tmp/a', b'a')
    f.touch('/tmp/a', truncate=True)
    assert f.cat('/tmp/a') == b''

    f.pipe('/tmp/a', b'a')
    f.touch('/tmp/a', truncate=False)
    assert f.cat('/tmp/a') == b'a'
```

```
_____________________________________________________________________ test_local_touch_truncate _____________________________________________________________________

    def test_local_touch_truncate():
        f = fsspec.filesystem("file")
        f.touch('/tmp/a', truncate=True)
    
        f.pipe('/tmp/a', b'a')
        f.touch('/tmp/a', truncate=True)
>       assert f.cat('/tmp/a') == b''
E       AssertionError: assert b'a' == b''
E         Full diff:
E         - b''
E         + b'a'
E         ?  
```

Regards,


martindurant: Please create a test that shows this flag in action, like you have already shown in your comment. Otherwise good to go.
KoyamaSohei: Thanks @martindurant  for the review :)
I just added the test.
martindurant: Confirmed that the gcsfs failure is a new problem with gcsfs ( https://github.com/fsspec/gcsfs/pull/477 ) so no need for it to hold up this PR.

Patch:
diff --git a/fsspec/implementations/local.py b/fsspec/implementations/local.py
index d106285..82da7af 100644
--- a/fsspec/implementations/local.py
+++ b/fsspec/implementations/local.py
@@ -158,7 +158,7 @@ class LocalFileSystem(AbstractFileSystem):
             self.makedirs(self._parent(path), exist_ok=True)
         return LocalFileOpener(path, mode, fs=self, **kwargs)
 
-    def touch(self, path, **kwargs):
+    def touch(self, path, truncate=True, **kwargs):
         path = self._strip_protocol(path)
         if self.auto_mkdir:
             self.makedirs(self._parent(path), exist_ok=True)
@@ -166,6 +166,8 @@ class LocalFileSystem(AbstractFileSystem):
             os.utime(path, None)
         else:
             open(path, "a").close()
+        if truncate:
+            os.truncate(path, 0)
 
     def created(self, path):
         info = self.info(path=path)
diff --git a/tox.ini b/tox.ini
index bb1de41..9ea10c0 100644
--- a/tox.ini
+++ b/tox.ini
@@ -84,24 +84,29 @@ commands=
 description=Run gcsfs (@master) test suite against fsspec.
 extras=gcs
 conda_channels=
-    defaults
     conda-forge
+    defaults
 conda_deps=
-    {[core]conda_deps}
-deps=
-    {[core]deps}
-    vcrpy
+    pytest
     ujson
+    requests
+    decorator
+    google-auth
+    aiohttp
     google-auth-oauthlib
-    crcmod
+    flake8
+    black
+    google-cloud-core
+    google-api-core
+    google-api-python-client
 changedir=.tox/gcsfs/tmp
 whitelist_externals=
     rm
     git
 setenv=
-    GCSFS_RECORD_MODE=none
     GOOGLE_APPLICATION_CREDENTIALS=gcsfs/gcsfs/tests/fake-secret.json
 commands=
     rm -rf gcsfs
     git clone https://github.com/fsspec/gcsfs
+    pip install ./gcsfs  --no-deps
     pytest -vv gcsfs/gcsfs -k 'not fuse'


Test patch:
diff --git a/fsspec/implementations/tests/test_local.py b/fsspec/implementations/tests/test_local.py
index b32ad34..9e31723 100644
--- a/fsspec/implementations/tests/test_local.py
+++ b/fsspec/implementations/tests/test_local.py
@@ -342,6 +342,18 @@ def test_touch(tmpdir):
         assert info2["mtime"] > info["mtime"]
 
 
+def test_touch_truncate(tmpdir):
+    fn = str(tmpdir + "/tfile")
+    fs = fsspec.filesystem("file")
+    fs.touch(fn, truncate=True)
+    fs.pipe(fn, b"a")
+    fs.touch(fn, truncate=True)
+    assert fs.cat(fn) == b""
+    fs.pipe(fn, b"a")
+    fs.touch(fn, truncate=False)
+    assert fs.cat(fn) == b"a"
+
+
 def test_directories(tmpdir):
     tmpdir = make_path_posix(str(tmpdir))
     fs = LocalFileSystem()