Repository: sunpy/sunpy

Issue:
Not passing in notify for a JSOC search is ambiguous 
We require a search for JSOC to need a notify attribute in order to fetch data. However if you do not provide one and then pass it into fetch, the error is ambiguous. We need to catch this in our JSOCClient and raise a better error message.

Example:
```python
In [1]: from sunpy.net import attrs as a, Fido

In [2]: Fido.fetch(Fido.search(a.Time("2011/01/01", "2011/01/01 00:01"), a.jsoc.Series.aia_lev1_euv_12s))
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-2-20e16f459549> in <module>
----> 1 Fido.fetch(Fido.search(a.Time("2011/01/01", "2011/01/01 00:01"), a.jsoc.Series.aia_lev1_euv_12s))

~/Git/sunpy/sunpy/net/fido_factory.py in fetch(self, path, max_conn, progress, overwrite, downloader, *query_results, **kwargs)
    444                                  "Allowed types are QueryResponseRow, QueryResponseTable or UnifiedResponse.")
    445             for block in responses:
--> 446                 result = block.client.fetch(block, path=path,
    447                                             downloader=downloader,
    448                                             wait=False, **kwargs)

~/Git/sunpy/sunpy/net/base_client.py in wrapper(self, query_results, **kwargs)
    292         if isinstance(query_results, QueryResponseRow):
    293             query_results = query_results.as_table()
--> 294         return func(self, query_results, **kwargs)
    295 
    296     return wrapper

~/Git/sunpy/sunpy/net/jsoc/jsoc.py in fetch(self, jsoc_response, path, progress, overwrite, downloader, wait, sleep, max_conn, **kwargs)
    469 
    470         # Make staging request to JSOC
--> 471         responses = self.request_data(jsoc_response)
    472 
    473         defaults = {'max_splits': 2}

~/Git/sunpy/sunpy/net/jsoc/jsoc.py in request_data(self, jsoc_response, method, **kwargs)
    385 
    386             ds = self._make_recordset(**block)
--> 387             cd = drms.Client(email=block.get('notify', ''))
    388             protocol = block.get('protocol', 'fits')
    389             cutout = block.get('cutout')

~/.virtualenvs/sunpy-dev/lib/python3.9/site-packages/drms/client.py in __init__(self, server, email, verbose, debug)
    627         self._info_cache = {}
    628         self.verbose = verbose  # use property for convertion to bool
--> 629         self.email = email  # use property for email validation
    630 
    631     def __repr__(self):

~/.virtualenvs/sunpy-dev/lib/python3.9/site-packages/drms/client.py in email(self, value)
    849     def email(self, value):
    850         if value is not None and not self.check_email(value):
--> 851             raise ValueError('Email address is invalid or not registered')
    852         self._email = value
    853 

ValueError: Email address is invalid or not registered
```

While this error is true, the email is invalid or registered since it was never passed in the first place.


Discussion:
What's the correct code for getting the ideal output in this situation?
There is no correct code. It is about improving the error message when the code is like this. 
> There is no correct code. It is about improving the error message when the code is like this.

Misread this, my apologies.
So would the replacing of the errors suffice? Over here not passing ```a.jsoc.Notify("sunpy@sunpy.org")``` should raise a separate error?
We should capture that error and then raise a more informative error if "a.jsoc.Notify" is missing.
In theory this could be changed upstream in DRMS. 
> We should capture that error and then raise a more informative error if "a.jsoc.Notify" is missing.

Can I make a PR addressing this?
> > We should capture that error and then raise a more informative error if "a.jsoc.Notify" is missing.
> 
> Can I make a PR addressing this?

Sure
> In theory this could be changed upstream in DRMS.

Would it be better to make the change over at DRMS and raising an error saying "Email address is missing"?
Both will need to happen. 
@nabobalis So while looking at the [docs](https://docs.sunpy.org/en/stable/guide/acquiring_data/jsoc.html?highlight=.jsoc#), the examples listed out for using the Fido client doesn't require an email, it works even without the email. I was wondering why enclosing the search in ```Fido.fetch``` causes the verification of emails. Is this the correct/intended behavior?  
Yes to get data from JSOC you have to search with "a.jsoc.Notify" and that gets used inside fetch.
> Yes to get data from JSOC you have to search with "a.jsoc.Notify" and that gets used inside fetch.

```python
res = Fido.search(a.Time("2011/01/01", "2011/01/01 00:01"), a.jsoc.Series.aia_lev1_euv_12s)
``` 
Gives a result without having to use ```a.jsoc.Notify``` so I was a little confused, so only when ```Fido.fetch``` is used we check for the right attributes and raise an error?
> > Yes to get data from JSOC you have to search with "a.jsoc.Notify" and that gets used inside fetch.
> 
> ```python
> res = Fido.search(a.Time("2011/01/01", "2011/01/01 00:01"), a.jsoc.Series.aia_lev1_euv_12s)
> ```
> 
> Gives a result without having to use `a.jsoc.Notify` so I was a little confused, so only when `Fido.fetch` is used we check for the right attributes and raise an error?

Yes, its confusing. 
> > Yes to get data from JSOC you have to search with "a.jsoc.Notify" and that gets used inside fetch.
> 
> 
> 
> ```python
> 
> res = Fido.search(a.Time("2011/01/01", "2011/01/01 00:01"), a.jsoc.Series.aia_lev1_euv_12s)
> 
> ``` 
> 
> Gives a result without having to use ```a.jsoc.Notify``` so I was a little confused, so only when ```Fido.fetch``` is used we check for the right attributes and raise an error?

Just to clarify, you can make a query against the JSOC without registering your email. You just can't download data without an email. This is because the export process, wherein the data and header are combined into a single FITS file and made available for download (which is effectively what `Fido.fetch` is requesting) is the expensive step for the data center. The email registration process is there to make it more difficult to spam this expensive step. 
> > > Yes to get data from JSOC you have to search with "a.jsoc.Notify" and that gets used inside fetch.
> > 
> > 
> > ```python
> > res = Fido.search(a.Time("2011/01/01", "2011/01/01 00:01"), a.jsoc.Series.aia_lev1_euv_12s)
> > ```
> > 
> > 
> > Gives a result without having to use `a.jsoc.Notify` so I was a little confused, so only when `Fido.fetch` is used we check for the right attributes and raise an error?
> 
> Just to clarify, you can make a query against the JSOC without registering your email. You just can't download data without an email. This is because the export process, wherein the data and header are combined into a single FITS file and made available for download (which is effectively what `Fido.fetch` is requesting) is the expensive step for the data center. The email registration process is there to make it more difficult to spam this expensive step.

Ah makes more sense now, thanks @wtbarnes, this clears up alot of confusion.

Resolving patch:
diff --git a/sunpy/net/jsoc/jsoc.py b/sunpy/net/jsoc/jsoc.py
--- a/sunpy/net/jsoc/jsoc.py
+++ b/sunpy/net/jsoc/jsoc.py
@@ -461,6 +461,12 @@ def fetch(self, jsoc_response, path=None, progress=True, overwrite=False,
             A Results object
 
        """
+        for resp in jsoc_response.query_args:
+            if 'notify' not in resp:
+                raise ValueError('A registered email is required to get data from JSOC. '
+                                 'Please supply an email with attrs.jsoc.Notify to Fido.search. '
+                                 'Then pass those new results back into Fido.fetch')
+
         if len(jsoc_response) != jsoc_response._original_num_rows:
             warnings.warn("Downloading of sliced JSOC results is not supported. "
                           "All the files present in the original response will "


Test patch:
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
@@ -492,6 +492,14 @@ def test_fido_no_time(mocker):
     jsoc_mock.assert_called_once()
 
 
+@pytest.mark.remote_data
+def test_jsoc_missing_email():
+    res = Fido.search(a.Time("2011/01/01", "2011/01/01 00:01"), a.jsoc.Series.aia_lev1_euv_12s)
+
+    with pytest.raises(ValueError, match=r"A registered email is required to get data from JSOC.*"):
+        Fido.fetch(res)
+
+
 @pytest.mark.remote_data
 def test_slice_jsoc():
     tstart = '2011/06/07 06:32:45'