Repository: sunpy/sunpy

Issue:
Permission denied errors on VSO results are ugly
Doing `Fido.fetch(path=/)` for instance leads to some ugly parfive error output


Discussion:
I can work on this! How would I run tests for debugging?
Just ask Fido to download to a directory you don't have write access to.
@Cadair I want to work on this. Will raising an ExceptionError work for this?

Resolving patch:
diff --git a/sunpy/net/fido_factory.py b/sunpy/net/fido_factory.py
--- a/sunpy/net/fido_factory.py
+++ b/sunpy/net/fido_factory.py
@@ -8,6 +8,7 @@
 `Fido.fetch <sunpy.net.fido_factory.UnifiedDownloaderFactory.fetch>`.
 
 """
+import os
 from collections.abc import Sequence
 
 from parfive import Downloader, Results
@@ -346,6 +347,9 @@ def fetch(self, *query_results, path=None, max_conn=5, progress=True,
         >>> filepaths = Fido.fetch(filepaths)  # doctest: +SKIP
 
         """
+        if path is not None and not os.access(path, os.W_OK):
+            raise PermissionError('You do not have permission to write'
+                                  ' file in this directory')
 
         if "wait" in kwargs:
             raise ValueError("wait is not a valid keyword argument to Fido.fetch.")


Test patch:
diff --git a/sunpy/net/dataretriever/tests/test_noaa.py b/sunpy/net/dataretriever/tests/test_noaa.py
--- a/sunpy/net/dataretriever/tests/test_noaa.py
+++ b/sunpy/net/dataretriever/tests/test_noaa.py
@@ -113,17 +113,19 @@ def test_query(mock_search):
 @mock.patch('parfive.Downloader.download',
             return_value=None)
 @mock.patch('parfive.Downloader.enqueue_file')
-def test_fetch(mock_wait, mock_search, mock_enqueue):
+def test_fetch(mock_wait, mock_search, mock_enqueue, tmp_path):
+    path = tmp_path / "sub"
+    path.mkdir()
     qr1 = LCClient.search(Time('2012/10/4', '2012/10/6'),
                           Instrument('noaa-indices'))
-    LCClient.fetch(qr1, path="/some/path/{file}")
+    LCClient.fetch(qr1, path=path / "{file}")
 
     # Here we assert that the `fetch` function has called the parfive
     # Downloader.enqueue_file method with the correct arguments. Everything
     # that happens after this point should either be tested in the
     # GenericClient tests or in parfive itself.
     assert mock_enqueue.called_once_with(("ftp://ftp.swpc.noaa.gov/pub/weekly/RecentIndices.txt",
-                                          "/some/path/RecentIndices.txt"))
+                                          path / "RecentIndices.txt"))
 
 
 @mock.patch('sunpy.net.dataretriever.sources.noaa.NOAAIndicesClient.search',
@@ -132,17 +134,19 @@ def test_fetch(mock_wait, mock_search, mock_enqueue):
 @mock.patch('parfive.Downloader.download',
             return_value=None)
 @mock.patch('parfive.Downloader.enqueue_file')
-def test_fido(mock_wait, mock_search, mock_enqueue):
+def test_fido(mock_wait, mock_search, mock_enqueue, tmp_path):
+    path = tmp_path / "sub"
+    path.mkdir()
     qr1 = Fido.search(Time('2012/10/4', '2012/10/6'),
                       Instrument('noaa-indices'))
-    Fido.fetch(qr1, path="/some/path/{file}")
+    Fido.fetch(qr1, path=path)
 
     # Here we assert that the `fetch` function has called the parfive
     # Downloader.enqueue_file method with the correct arguments. Everything
     # that happens after this point should either be tested in the
     # GenericClient tests or in parfive itself.
     assert mock_enqueue.called_once_with(("ftp://ftp.swpc.noaa.gov/pub/weekly/RecentIndices.txt",
-                                          "/some/path/RecentIndices.txt"))
+                                          path / "RecentIndices.txt"))
 
 
 @pytest.mark.remote_data
diff --git a/sunpy/net/tests/test_fido.py b/sunpy/net/tests/test_fido.py
--- a/sunpy/net/tests/test_fido.py
+++ b/sunpy/net/tests/test_fido.py
@@ -249,6 +249,12 @@ def test_repr():
 def filter_queries(queries):
     return attr.and_(queries) not in queries
 
+def test_path():
+    results = Fido.search(
+        a.Time("2012/1/1", "2012/1/5"), a.Instrument("lyra"))
+
+    with pytest.raises(PermissionError):
+        Fido.fetch(results, path="/some/path/{file}")
 
 @settings(deadline=50000)
 @given(st.tuples(offline_query(), offline_query()).filter(filter_queries))