Repository: fsspec/filesystem_spec

Issue:
AttributeError: 'ZipFileSystem' object has no attribute 'zip'
When creating a `ZipFileSystem` object it is possible that `__init__` fails to create the `ZipFile` for example because of no permission to read. When python garbage collects this object there is an `AttributeError: 'ZipFileSystem' object has no attribute 'zip'` error because it tries to access a `zip` attribute that does not exist. This can be reproduced with the following:

```python
import fsspec
import os
import tempfile
import zipfile
from pathlib import Path


with tempfile.TemporaryDirectory() as tmpdir:
    os.chdir(tmpdir)

    txt_path = Path('data.txt')
    txt_path.write_text('some text\n')
    zip_path = Path('data.zip')
    zip_handle = zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED)
    zip_handle.write(txt_path)
    zip_handle.close()

    os.chmod(zip_path, 0)

    try:
        handle = fsspec.open(f'zip://{txt_path}::file://{zip_path}', 'r')
    except Exception:
        pass
```

Running this script should not give any output, but it gives:

```shell
$ python3 fsspec_zip_issue.py 
Exception ignored in: <function ZipFileSystem.__del__ at 0x7f1a8e322040>
Traceback (most recent call last):
  File "/.../lib/python3.8/site-packages/fsspec/implementations/zip.py", line 71, in __del__
    self.zip.close()
AttributeError: 'ZipFileSystem' object has no attribute 'zip'
```

Discussion:
martindurant: I am happy to include your change and will merge when green.

I think the cause was not quite what you suggest, because apparently `__init__` for the ZipFileSystem instance did complete successfully. during interpreter shutdown, the `zip` attribute might be cleaned up before the instance itself is; so shutdown is the only time the exception might be seen.

Patch:
diff --git a/fsspec/asyn.py b/fsspec/asyn.py
index 602901b..4563254 100644
--- a/fsspec/asyn.py
+++ b/fsspec/asyn.py
@@ -369,6 +369,9 @@ class AsyncFileSystem(AbstractFileSystem):
                 continue
             raise ex
 
+    async def _pipe_file(self, path, value, **kwargs):
+        raise NotImplementedError
+
     async def _pipe(self, path, value=None, batch_size=None, **kwargs):
         if isinstance(path, str):
             path = {path: value}
diff --git a/fsspec/generic.py b/fsspec/generic.py
index a8de34e..360f59a 100644
--- a/fsspec/generic.py
+++ b/fsspec/generic.py
@@ -97,6 +97,37 @@ class GenericFileSystem(AsyncFileSystem):
         else:
             return [o["name"] for o in out]
 
+    async def _cat_file(
+        self,
+        url,
+        method=None,
+        protocol=None,
+        storage_options=None,
+        fs=None,
+        **kwargs,
+    ):
+        fs = fs or _resolve_fs(url, method or self.method, protocol, storage_options)
+        if fs.async_impl:
+            return await fs._cat_file(url, **kwargs)
+        else:
+            return fs.cat_file(url, **kwargs)
+
+    async def _pipe_file(
+        self,
+        path,
+        value,
+        method=None,
+        protocol=None,
+        storage_options=None,
+        fs=None,
+        **kwargs,
+    ):
+        fs = fs or _resolve_fs(path, method or self.method, protocol, storage_options)
+        if fs.async_impl:
+            return await fs._pipe_file(path, value, **kwargs)
+        else:
+            return fs.pipe_file(path, value, **kwargs)
+
     async def _rm(
         self, url, method=None, protocol=None, storage_options=None, fs=None, **kwargs
     ):
diff --git a/fsspec/implementations/zip.py b/fsspec/implementations/zip.py
index 9f742dc..dc90d5d 100644
--- a/fsspec/implementations/zip.py
+++ b/fsspec/implementations/zip.py
@@ -67,7 +67,8 @@ class ZipFileSystem(AbstractArchiveFileSystem):
         return super()._strip_protocol(path).lstrip("/")
 
     def __del__(self):
-        self.zip.close()
+        if hasattr(self, "zip"):
+            self.zip.close()
 
     def _get_dirs(self):
         if self.dir_cache is None:


Test patch:
diff --git a/fsspec/tests/test_generic.py b/fsspec/tests/test_generic.py
index 797f7ee..238e985 100644
--- a/fsspec/tests/test_generic.py
+++ b/fsspec/tests/test_generic.py
@@ -32,3 +32,15 @@ def test_cp_async_to_sync(server, m):
 
     fs.rm("memory://realfile")
     assert not m.exists("realfile")
+
+
+def test_pipe_cat_sync(m):
+    fs = fsspec.filesystem("generic", default_method="current")
+    fs.pipe("memory://afile", b"data")
+    assert fs.cat("memory://afile") == b"data"
+
+
+def test_cat_async(server):
+    fsspec.filesystem("http", headers={"give_length": "true", "head_ok": "true"})
+    fs = fsspec.filesystem("generic", default_method="current")
+    assert fs.cat(server + "/index/realfile") == data