Repository: sunpy/sunpy-soar

Issue:
SOAR client does not correctly handle string interpolation in `Fido.fetch`
When using the `path=...` keyword argument in `Fido.fetch`, string interpolation can be used to direct certain files to certain directories. However, when used with the SOAR client, this throws an exception. This is demonstrated with the following code example:

```python
import sunpy_soar
from sunpy_soar.attrs import Product
from sunpy.net import Fido, attrs as a
result = Fido.search(a.Time("2021-02-12 16:0", "2021-02-13 02:00") & a.Level(2) & a.Instrument('EUI') & Product('EUI-FSI174-IMAGE'))
files = Fido.fetch(result, path='{instrument}')
```

throws the following exception

```python traceback
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
Input In [16], in <cell line: 1>()
----> 1 files = Fido.fetch(result[0][:1], path='data/{instrument}')

File ~/anaconda/envs/pyhc-summer-school/lib/python3.9/site-packages/sunpy/net/fido_factory.py:430, in UnifiedDownloaderFactory.fetch(self, path, max_conn, progress, overwrite, downloader, *query_results, **kwargs)
    427     raise ValueError(f"Query result has an unrecognized type: {type(query_result)} "
    428                      "Allowed types are QueryResponseRow, QueryResponseTable or UnifiedResponse.")
    429 for block in responses:
--> 430     result = block.client.fetch(block, path=path,
    431                                 downloader=downloader,
    432                                 wait=False, **kwargs)
    433     if result not in (NotImplemented, None):
    434         reslist.append(result)

File ~/anaconda/envs/pyhc-summer-school/lib/python3.9/site-packages/sunpy_soar/client.py:118, in SOARClient.fetch(self, query_results, path, downloader, **kwargs)
    116 for row in query_results:
    117     url = base_url + row['Data item ID']
--> 118     filepath = str(path).format(file=row['Filename'])
    119     log.debug(f'Queing URL: {url}')
    120     downloader.enqueue_file(url, filename=filepath)

KeyError: 'instrument'
```

Discussion:
wtbarnes: These test failures are not related to this PR and are tracked by #26 
wtbarnes: How do these tests handle downloaded files? Do they just download to the default sunpy directory?
dstansby: Yep
dstansby: Other tests should be fixed now if you want to rebase

Patch:
diff --git a/sunpy_soar/client.py b/sunpy_soar/client.py
index a2a50f4..968dcef 100644
--- a/sunpy_soar/client.py
+++ b/sunpy_soar/client.py
@@ -31,19 +31,14 @@ class SOARClient(BaseClient):
         return qrt
 
     @staticmethod
-    def _do_search(query):
+    def _construct_url(query):
         """
-        Query the SOAR server with a single query.
+        Construct search URL.
 
         Parameters
         ----------
         query : list[str]
             List of query items.
-
-        Returns
-        -------
-        astropy.table.QTable
-            Query results.
         """
         base_url = ('http://soar.esac.esa.int/soar-sl-tap/tap/'
                     'sync?REQUEST=doQuery&')
@@ -63,7 +58,24 @@ class SOARClient(BaseClient):
         request_str = [f'{item}={request_dict[item]}' for item in request_dict]
         request_str = '&'.join(request_str)
 
-        url = base_url + request_str
+        return base_url + request_str
+
+    @staticmethod
+    def _do_search(query):
+        """
+        Query the SOAR server with a single query.
+
+        Parameters
+        ----------
+        query : list[str]
+            List of query items.
+
+        Returns
+        -------
+        astropy.table.QTable
+            Query results.
+        """
+        url = SOARClient._construct_url(query)
         log.debug(f'Getting request from URL: {url}')
         # Get request info
         r = requests.get(url)
@@ -113,7 +125,7 @@ class SOARClient(BaseClient):
 
         for row in query_results:
             url = base_url + row['Data item ID']
-            filepath = str(path).format(file=row['Filename'])
+            filepath = str(path).format(file=row['Filename'], **row.response_block_map)
             log.debug(f'Queing URL: {url}')
             downloader.enqueue_file(url, filename=filepath)
 


Test patch:
diff --git a/sunpy_soar/tests/test_sunpy_soar.py b/sunpy_soar/tests/test_sunpy_soar.py
index 45e87f9..a93fef7 100644
--- a/sunpy_soar/tests/test_sunpy_soar.py
+++ b/sunpy_soar/tests/test_sunpy_soar.py
@@ -70,3 +70,16 @@ def test_no_instrument():
     time = a.Time('2020-04-16', '2020-04-17')
     res = SOARClient().search(time)
     assert len(res) == 50
+
+
+def test_download_path(tmp_path):
+    # Check that we can download things to a custom path using
+    # the search parameters
+    id = a.Instrument('EUI')
+    time = a.Time('2021-02-01', '2021-02-02')
+    level = a.Level(1)
+    res = Fido.search(id & time & level)
+    files = Fido.fetch(res[0, 0], path=tmp_path / '{instrument}')
+    assert len(files) == 1
+    for f in files:
+        assert 'EUI' in f