Repository: astropy/astroquery

Issue:
SDSS: add get_query_payload kwarg back to all methods
With the recent refactoring in #2477  it got refactored out from a few methods, this is a reminder issue that it should be, and systematically, added back to all methods to help with quick debugging.

Comments:

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 373f9faf..bcf8521b 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -141,6 +141,7 @@ sdss
 
 - The default data release has been changed to DR17. [#2478]
 
+- Optional keyword arguments are now keyword only. [#2477, #2532]
 
 
 Infrastructure, Utility and Other Changes and Additions
diff --git a/astroquery/sdss/core.py b/astroquery/sdss/core.py
index bad511b9..b9c80ebb 100644
--- a/astroquery/sdss/core.py
+++ b/astroquery/sdss/core.py
@@ -2,7 +2,6 @@
 """
 Access Sloan Digital Sky Survey database online.
 """
-import io
 import warnings
 import numpy as np
 
@@ -518,9 +517,9 @@ class SDSSClass(BaseQuery):
                                  timeout=timeout, cache=cache)
         return response
 
-    def get_spectra_async(self, coordinates=None, radius=2. * u.arcsec,
+    def get_spectra_async(self, *, coordinates=None, radius=2. * u.arcsec,
                           matches=None, plate=None, fiberID=None, mjd=None,
-                          timeout=TIMEOUT,
+                          timeout=TIMEOUT, get_query_payload=False,
                           data_release=conf.default_release, cache=True,
                           show_progress=True):
         """
@@ -559,6 +558,9 @@ class SDSSClass(BaseQuery):
         timeout : float, optional
             Time limit (in seconds) for establishing successful connection with
             remote server.  Defaults to `SDSSClass.TIMEOUT`.
+        get_query_payload : bool, optional
+            If True, this will return the data the query would have sent out,
+            but does not actually do the query.
         data_release : int, optional
             The data release of the SDSS to use. With the default server, this
             only supports DR8 or later.
@@ -599,12 +601,19 @@ class SDSSClass(BaseQuery):
             if coordinates is None:
                 matches = self.query_specobj(plate=plate, mjd=mjd, fiberID=fiberID,
                                              fields=['run2d', 'plate', 'mjd', 'fiberID'],
-                                             timeout=timeout, data_release=data_release, cache=cache)
+                                             timeout=timeout, get_query_payload=get_query_payload,
+                                             data_release=data_release, cache=cache)
             else:
-                matches = self.query_crossid(coordinates, radius=radius,
+                matches = self.query_crossid(coordinates, radius=radius, timeout=timeout,
                                              specobj_fields=['run2d', 'plate', 'mjd', 'fiberID'],
-                                             spectro=True,
-                                             timeout=timeout, data_release=data_release, cache=cache)
+                                             spectro=True, get_query_payload=get_query_payload,
+                                             data_release=data_release, cache=cache)
+            if get_query_payload:
+                if coordinates is None:
+                    return matches
+                else:
+                    return matches[0]
+
             if matches is None:
                 warnings.warn("Query returned no results.", NoResultsWarning)
                 return
@@ -638,10 +647,10 @@ class SDSSClass(BaseQuery):
         return results
 
     @prepend_docstr_nosections(get_spectra_async.__doc__)
-    def get_spectra(self, coordinates=None, radius=2. * u.arcsec,
+    def get_spectra(self, *, coordinates=None, radius=2. * u.arcsec,
                     matches=None, plate=None, fiberID=None, mjd=None,
-                    timeout=TIMEOUT, cache=True,
-                    data_release=conf.default_release,
+                    timeout=TIMEOUT, get_query_payload=False,
+                    data_release=conf.default_release, cache=True,
                     show_progress=True):
         """
         Returns
@@ -654,9 +663,14 @@ class SDSSClass(BaseQuery):
                                                radius=radius, matches=matches,
                                                plate=plate, fiberID=fiberID,
                                                mjd=mjd, timeout=timeout,
+                                               get_query_payload=get_query_payload,
                                                data_release=data_release,
+                                               cache=cache,
                                                show_progress=show_progress)
 
+        if get_query_payload:
+            return readable_objs
+
         if readable_objs is not None:
             if isinstance(readable_objs, dict):
                 return readable_objs
@@ -666,7 +680,7 @@ class SDSSClass(BaseQuery):
     def get_images_async(self, coordinates=None, radius=2. * u.arcsec,
                          matches=None, run=None, rerun=301, camcol=None,
                          field=None, band='g', timeout=TIMEOUT,
-                         cache=True,
+                         cache=True, get_query_payload=False,
                          data_release=conf.default_release,
                          show_progress=True):
         """
@@ -714,6 +728,9 @@ class SDSSClass(BaseQuery):
         timeout : float, optional
             Time limit (in seconds) for establishing successful connection with
             remote server.  Defaults to `SDSSClass.TIMEOUT`.
+        get_query_payload : bool, optional
+            If True, this will return the data the query would have sent out,
+            but does not actually do the query.
         cache : bool, optional
             Cache the images using astropy's caching system
         data_release : int, optional
@@ -753,12 +770,19 @@ class SDSSClass(BaseQuery):
                 matches = self.query_photoobj(run=run, rerun=rerun,
                                               camcol=camcol, field=field,
                                               fields=['run', 'rerun', 'camcol', 'field'],
-                                              timeout=timeout,
+                                              timeout=timeout, get_query_payload=get_query_payload,
                                               data_release=data_release, cache=cache)
             else:
-                matches = self.query_crossid(coordinates, radius=radius,
+                matches = self.query_crossid(coordinates, radius=radius, timeout=timeout,
                                              fields=['run', 'rerun', 'camcol', 'field'],
-                                             timeout=timeout, data_release=data_release, cache=cache)
+                                             get_query_payload=get_query_payload,
+                                             data_release=data_release, cache=cache)
+            if get_query_payload:
+                if coordinates is None:
+                    return matches
+                else:
+                    return matches[0]
+
             if matches is None:
                 warnings.warn("Query returned no results.", NoResultsWarning)
                 return
@@ -786,7 +810,7 @@ class SDSSClass(BaseQuery):
         return results
 
     @prepend_docstr_nosections(get_images_async.__doc__)
-    def get_images(self, coordinates=None, radius=2. * u.arcsec,
+    def get_images(self, *, coordinates=None, radius=2. * u.arcsec,
                    matches=None, run=None, rerun=301, camcol=None, field=None,
                    band='g', timeout=TIMEOUT, cache=True,
                    get_query_payload=False, data_release=conf.default_release,
@@ -798,10 +822,22 @@ class SDSSClass(BaseQuery):
 
         """
 
-        readable_objs = self.get_images_async(
-            coordinates=coordinates, radius=radius, matches=matches, run=run,
-            rerun=rerun, data_release=data_release, camcol=camcol, field=field,
-            band=band, timeout=timeout, show_progress=show_progress)
+        readable_objs = self.get_images_async(coordinates=coordinates,
+                                              radius=radius,
+                                              matches=matches,
+                                              run=run,
+                                              rerun=rerun,
+                                              camcol=camcol,
+                                              field=field,
+                                              band=band,
+                                              timeout=timeout,
+                                              cache=cache,
+                                              get_query_payload=get_query_payload,
+                                              data_release=data_release,
+                                              show_progress=show_progress)
+
+        if get_query_payload:
+            return readable_objs
 
         if readable_objs is not None:
             if isinstance(readable_objs, dict):
@@ -906,7 +942,7 @@ class SDSSClass(BaseQuery):
         else:
             return arr
 
-    def _args_to_payload(self, coordinates=None,
+    def _args_to_payload(self, *, coordinates=None,
                          fields=None, spectro=False, region=False,
                          plate=None, mjd=None, fiberID=None, run=None,
                          rerun=301, camcol=None, field=None,


Test patch:
diff --git a/astroquery/sdss/tests/test_sdss.py b/astroquery/sdss/tests/test_sdss.py
index 495dcf71..6f5516a5 100644
--- a/astroquery/sdss/tests/test_sdss.py
+++ b/astroquery/sdss/tests/test_sdss.py
@@ -177,7 +177,7 @@ def test_sdss_spectrum_mjd(patch_request, patch_get_readable_fileobj, dr):
 @pytest.mark.parametrize("dr", dr_list)
 def test_sdss_spectrum_coords(patch_request, patch_get_readable_fileobj, dr,
                               coords=coords):
-    sp = sdss.SDSS.get_spectra(coords, data_release=dr)
+    sp = sdss.SDSS.get_spectra(coordinates=coords, data_release=dr)
     image_tester(sp, 'spectra')
 
 
@@ -220,7 +220,7 @@ def test_sdss_image_run(patch_request, patch_get_readable_fileobj, dr):
 @pytest.mark.parametrize("dr", dr_list)
 def test_sdss_image_coord(patch_request, patch_get_readable_fileobj, dr,
                           coord=coords):
-    img = sdss.SDSS.get_images(coords, data_release=dr)
+    img = sdss.SDSS.get_images(coordinates=coords, data_release=dr)
     image_tester(img, 'images')
 
 
@@ -454,6 +454,63 @@ def test_photoobj_run_camcol_field_payload(patch_request, dr):
     assert query_payload['format'] == 'csv'
 
 
+@pytest.mark.parametrize("dr", dr_list)
+def test_get_spectra_specobj_payload(patch_request, dr):
+    expect = ("SELECT DISTINCT "
+              "s.run2d, s.plate, s.mjd, s.fiberID "
+              "FROM PhotoObjAll AS p "
+              "JOIN SpecObjAll AS s ON p.objID = s.bestObjID "
+              "WHERE "
+              "(s.plate=751 AND s.mjd=52251)")
+    query_payload = sdss.SDSS.get_spectra_async(plate=751, mjd=52251,
+                                                get_query_payload=True,
+                                                data_release=dr)
+    assert query_payload['cmd'] == expect
+    assert query_payload['format'] == 'csv'
+
+
+@pytest.mark.parametrize("dr", dr_list)
+def test_get_spectra_coordinates_payload(patch_request, dr):
+    expect = ("SELECT\r\n"
+              "s.run2d, s.plate, s.mjd, s.fiberID, s.SpecObjID AS obj_id, dbo.fPhotoTypeN(p.type) AS type "
+              "FROM #upload u JOIN #x x ON x.up_id = u.up_id JOIN PhotoObjAll AS p ON p.objID = x.objID "
+              "JOIN SpecObjAll AS s ON p.objID = s.bestObjID "
+              "ORDER BY x.up_id")
+    query_payload = sdss.SDSS.get_spectra_async(coordinates=coords_column,
+                                                get_query_payload=True,
+                                                data_release=dr)
+    assert query_payload['uquery'] == expect
+    assert query_payload['format'] == 'csv'
+    assert query_payload['photoScope'] == 'nearPrim'
+
+
+@pytest.mark.parametrize("dr", dr_list)
+def test_get_images_photoobj_payload(patch_request, dr):
+    expect = ("SELECT DISTINCT "
+              "p.run, p.rerun, p.camcol, p.field "
+              "FROM PhotoObjAll AS p WHERE "
+              "(p.run=5714 AND p.camcol=6 AND p.rerun=301)")
+    query_payload = sdss.SDSS.get_images_async(run=5714, camcol=6,
+                                               get_query_payload=True,
+                                               data_release=dr)
+    assert query_payload['cmd'] == expect
+    assert query_payload['format'] == 'csv'
+
+
+@pytest.mark.parametrize("dr", dr_list)
+def test_get_images_coordinates_payload(patch_request, dr):
+    expect = ("SELECT\r\n"
+              "p.run, p.rerun, p.camcol, p.field, dbo.fPhotoTypeN(p.type) AS type "
+              "FROM #upload u JOIN #x x ON x.up_id = u.up_id JOIN PhotoObjAll AS p ON p.objID = x.objID "
+              "ORDER BY x.up_id")
+    query_payload = sdss.SDSS.get_images_async(coordinates=coords_column,
+                                               get_query_payload=True,
+                                               data_release=dr)
+    assert query_payload['uquery'] == expect
+    assert query_payload['format'] == 'csv'
+    assert query_payload['photoScope'] == 'nearPrim'
+
+
 @pytest.mark.parametrize("dr", dr_list)
 def test_spectra_plate_mjd_payload(patch_request, dr):
     expect = ("SELECT DISTINCT "
diff --git a/astroquery/sdss/tests/test_sdss_remote.py b/astroquery/sdss/tests/test_sdss_remote.py
index eb190cbd..329e94b8 100644
--- a/astroquery/sdss/tests/test_sdss_remote.py
+++ b/astroquery/sdss/tests/test_sdss_remote.py
@@ -63,7 +63,7 @@ class TestSDSSRemote:
         sp = sdss.SDSS.get_spectra(plate=2345, fiberID=572)
 
     def test_sdss_spectrum_coords(self):
-        sp = sdss.SDSS.get_spectra(self.coords)
+        sp = sdss.SDSS.get_spectra(coordinates=self.coords)
 
     def test_sdss_sql(self):
         query = """
@@ -91,7 +91,7 @@ class TestSDSSRemote:
         img = sdss.SDSS.get_images(run=1904, camcol=3, field=164)
 
     def test_sdss_image_coord(self):
-        img = sdss.SDSS.get_images(self.coords)
+        img = sdss.SDSS.get_images(coordinates=self.coords)
 
     def test_sdss_specobj(self):
         colnames = ['ra', 'dec', 'objid', 'run', 'rerun', 'camcol', 'field',
@@ -161,7 +161,7 @@ class TestSDSSRemote:
                                "self._request, fix it before merging #586"))
     def test_spectra_timeout(self):
         with pytest.raises(TimeoutError):
-            sdss.SDSS.get_spectra(self.coords, timeout=self.mintimeout)
+            sdss.SDSS.get_spectra(coordinates=self.coords, timeout=self.mintimeout)
 
     def test_query_non_default_field(self):
         # A regression test for #469