Repository: fsspec/filesystem_spec

Issue:
cat_file (or equivalent) that returns the contents in text mode
Currently, `cat_file` only returns the contents of the file as bytes. The decoded content as a string would be really useful.

I see two ways to implement this:
1) extend `cat_file()` with `mode`/`encoding`/`errors` kwargs to pass it down to `open`.
2) introduce [`read_text()`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.read_text) or similar API.

Happy to go with either option and create a PR.  

Discussion:
efiop: Feels like if we decide to bother with `read_text/write_text` we might add `read_bytes/write_bytes` as well for symmetry.

Also this is interesting as it provides (arguably) a more pythonic interface as opposed to unix-like cat/pipe that we already have. I'm personally not a fan of creating too many different ways of doing the same thing and I'm not a fan of pathlib, but one could indeed argue that `read/write_bytes/text` should be more natural to the users. Overall I don't have very strong opinion here.
martindurant: Thank you @efiop . Hoping for more opinions too.
martindurant: read_bytes/write_bytes would be simple aliases of cat/pipe? I'll allow it :)
skshetry: >  read_bytes/write_bytes would be simple aliases of cat/pipe? I'll allow it :)

Thanks, I have added those aliases. I am not sure what you mean by simple aliases, simpler form than cat/pipe or just alias with same function signatures? Since we are calling them aliases, I went with latter (and I assumed that's what you meant). 🙂 



Patch:
diff --git a/fsspec/spec.py b/fsspec/spec.py
index f37ca14..4cb0d5f 100644
--- a/fsspec/spec.py
+++ b/fsspec/spec.py
@@ -649,6 +649,50 @@ class AbstractFileSystem(metaclass=_Cached):
         except:  # noqa: E722
             return False
 
+    def read_text(self, path, encoding=None, errors=None, newline=None, **kwargs):
+        """Get the contents of the file as a string.
+
+        Parameters
+        ----------
+        path: str
+            URL of file on this filesystems
+        encoding, errors, newline: same as `open`.
+        """
+        with self.open(
+            path,
+            mode="r",
+            encoding=encoding,
+            errors=errors,
+            newline=newline,
+            **kwargs,
+        ) as f:
+            return f.read()
+
+    def write_text(
+        self, path, value, encoding=None, errors=None, newline=None, **kwargs
+    ):
+        """Write the text to the given file.
+
+        An existing file will be overwritten.
+
+        Parameters
+        ----------
+        path: str
+            URL of file on this filesystems
+        value: str
+            Text to write.
+        encoding, errors, newline: same as `open`.
+        """
+        with self.open(
+            path,
+            mode="w",
+            encoding=encoding,
+            errors=errors,
+            newline=newline,
+            **kwargs,
+        ) as f:
+            return f.write(value)
+
     def cat_file(self, path, start=None, end=None, **kwargs):
         """Get the content of a file
 
@@ -1220,6 +1264,14 @@ class AbstractFileSystem(metaclass=_Cached):
     # ------------------------------------------------------------------------
     # Aliases
 
+    def read_bytes(self, path, start=None, end=None, **kwargs):
+        """Alias of `AbstractFileSystem.cat_file`."""
+        return self.cat_file(path, start=start, end=end, **kwargs)
+
+    def write_bytes(self, path, value, **kwargs):
+        """Alias of `AbstractFileSystem.pipe_file`."""
+        self.pipe_file(path, value, **kwargs)
+
     def makedir(self, path, create_parents=True, **kwargs):
         """Alias of `AbstractFileSystem.mkdir`."""
         return self.mkdir(path, create_parents=create_parents, **kwargs)


Test patch:
diff --git a/fsspec/tests/test_api.py b/fsspec/tests/test_api.py
index 65b513c..84bcb7a 100644
--- a/fsspec/tests/test_api.py
+++ b/fsspec/tests/test_api.py
@@ -171,6 +171,17 @@ def test_open_text(m):
     assert f.encoding == "latin1"
 
 
+def test_read_text(m):
+    with m.open("/myfile", "w", encoding="utf-8") as f:
+        f.write("some\nlines\nof\ntext")
+    assert m.read_text("/myfile", encoding="utf-8") == "some\nlines\nof\ntext"
+
+
+def test_write_text(m):
+    m.write_text("/myfile", "some\nlines\nof\ntext", encoding="utf-8")
+    assert m.read_text("/myfile", encoding="utf-8") == "some\nlines\nof\ntext"
+
+
 def test_chained_fs():
     d1 = tempfile.mkdtemp()
     d2 = tempfile.mkdtemp()