Repository: astropy/astroquery

Issue:
Throw an error when unsupported arguments are passed in `astroquery.alma`
A user recently tried to issue this:

```python
from astroquery.alma import Alma
from astropy import units as u
rs = Alma.query_object("V* CW Cha", radius=0.05*u.arcmin, public=True, science=True)
print(len(set(rs['obs_id'])))
```

The `query_object` function does *not* support the `radius` argument.  The function they actually want is `query_region`, but calling `query_object` still succeeds quietly but is misleading to the caller.  Functions should error out when unsupported arguments are passed to the query functions.

Comments:

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index d951b9a5..b716fc3d 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -24,6 +24,18 @@ alma
 ^^^^
 
 - Fixed a regression to handle arrays of string input for the ``query`` methods. [#2094]
+- Throws an error when an unsupported ``kwargs`` (or argument) is passed in to a function. [#2475]
+
+
+astrometry.net
+^^^^^^^^^^^^^^
+
+- Added a ``verbose=`` keyword argument to ``AstrometryNet`` to control whether or not
+  to show any information during solving. [#2484]
+
+- Fixed a bug which caused ``solve_timeout`` to not be respected when an image was
+  solved by constructing a source list internally before sending data to
+  astrometry.net. [#2484]
 
 cadc
 ^^^^
diff --git a/astroquery/alma/core.py b/astroquery/alma/core.py
index 54224d86..097bd233 100644
--- a/astroquery/alma/core.py
+++ b/astroquery/alma/core.py
@@ -162,6 +162,7 @@ ALMA_FORM_KEYS = {
 def _gen_sql(payload):
     sql = 'select * from ivoa.obscore'
     where = ''
+    unused_payload = payload.copy()
     if payload:
         for constraint in payload:
             for attrib_category in ALMA_FORM_KEYS.values():
@@ -181,6 +182,18 @@ def _gen_sql(payload):
                             else:
                                 where = ' WHERE '
                             where += attrib_where
+
+                        # Delete this key to see what's left over afterward
+                        #
+                        # Use pop to avoid the slight possibility of trying to remove
+                        # an already removed key
+                        unused_payload.pop(constraint)
+
+    if unused_payload:
+        # Left over (unused) constraints passed.  Let the user know.
+        remaining = [f'{p} -> {unused_payload[p]}' for p in unused_payload]
+        raise TypeError(f'Unsupported arguments were passed:\n{remaining}')
+
     return sql + where
 
 
@@ -296,7 +309,8 @@ class AlmaClass(QueryWithLogin):
                                 payload=payload, **kwargs)
 
     def query_async(self, payload, *, public=True, science=True,
-                    legacy_columns=False, get_query_payload=None, **kwargs):
+                    legacy_columns=False, get_query_payload=None,
+                    maxrec=None, **kwargs):
         """
         Perform a generic query with user-specified payload
 
@@ -313,6 +327,10 @@ class AlmaClass(QueryWithLogin):
         legacy_columns : bool
             True to return the columns from the obsolete ALMA advanced query,
             otherwise return the current columns based on ObsCore model.
+        get_query_payload : bool
+            Flag to indicate whether to simply return the payload.
+        maxrec : integer
+            Cap on the amount of records returned.  Default is no limit.
 
         Returns
         -------
@@ -340,7 +358,7 @@ class AlmaClass(QueryWithLogin):
             return payload
 
         query = _gen_sql(payload)
-        result = self.query_tap(query, maxrec=payload.get('maxrec', None))
+        result = self.query_tap(query, maxrec=maxrec)
         if result is not None:
             result = result.to_table()
         else:
@@ -588,7 +606,7 @@ class AlmaClass(QueryWithLogin):
         proprietary or not.
         """
         query = "select distinct data_rights from ivoa.obscore where " \
-                "obs_id='{}'".format(uid)
+                "member_ous_uid='{}'".format(uid)
         result = self.query_tap(query)
         if result:
             tableresult = result.to_table()
diff --git a/astroquery/astrometry_net/core.py b/astroquery/astrometry_net/core.py
index dd7ed52e..a6dbc6fa 100644
--- a/astroquery/astrometry_net/core.py
+++ b/astroquery/astrometry_net/core.py
@@ -190,7 +190,7 @@ class AstrometryNetClass(BaseQuery):
                                  'values for {}'.format(scale_type, required_keys))
 
     def monitor_submission(self, submission_id,
-                           solve_timeout=TIMEOUT):
+                           solve_timeout=TIMEOUT, verbose=True):
         """
         Monitor the submission for completion.
 
@@ -202,6 +202,8 @@ class AstrometryNetClass(BaseQuery):
         solve_timeout : ``int``
             Time, in seconds, to wait for the astrometry.net solver to find
             a solution.
+        verbose : bool, optional
+            Whether to print out information about the solving
 
         Returns
         -------
@@ -223,7 +225,8 @@ class AstrometryNetClass(BaseQuery):
         """
         has_completed = False
         job_id = None
-        print('Solving', end='', flush=True)
+        if verbose:
+            print('Solving', end='', flush=True)
         start_time = time.time()
         status = ''
         while not has_completed:
@@ -242,7 +245,8 @@ class AstrometryNetClass(BaseQuery):
             elapsed = now - start_time
             timed_out = elapsed > solve_timeout
             has_completed = (status in ['success', 'failure'] or timed_out)
-            print('.', end='', flush=True)
+            if verbose:
+                print('.', end='', flush=True)
         if status == 'success':
             wcs_url = url_helpers.join(self.URL, 'wcs_file', str(job_id))
             wcs_response = self._request('GET', wcs_url)
@@ -259,6 +263,7 @@ class AstrometryNetClass(BaseQuery):
 
     def solve_from_source_list(self, x, y, image_width, image_height,
                                solve_timeout=TIMEOUT,
+                               verbose=True,
                                **settings
                                ):
         """
@@ -278,6 +283,8 @@ class AstrometryNetClass(BaseQuery):
         solve_timeout : int
             Time, in seconds, to wait for the astrometry.net solver to find
             a solution.
+        verbose : bool, optional
+            Whether to print out information about the solving
 
         For a list of the remaining settings, use the method
         `~AstrometryNetClass.show_allowed_settings`.
@@ -301,13 +308,15 @@ class AstrometryNetClass(BaseQuery):
         response_d = response.json()
         submission_id = response_d['subid']
         return self.monitor_submission(submission_id,
-                                       solve_timeout=solve_timeout)
+                                       solve_timeout=solve_timeout,
+                                       verbose=verbose)
 
     def solve_from_image(self, image_file_path, force_image_upload=False,
                          ra_key=None, dec_key=None,
                          ra_dec_units=None,
                          fwhm=3, detect_threshold=5,
                          solve_timeout=TIMEOUT,
+                         verbose=True,
                          **settings):
         """
         Plate solve from an image, either by uploading the image to
@@ -343,10 +352,14 @@ class AstrometryNetClass(BaseQuery):
         ra_dec_units : tuple, optional
             Tuple specifying the units of the right ascension and declination in
             the header. The default value is ``('hour', 'degree')``.
+
         solve_timeout : int
             Time, in seconds, to wait for the astrometry.net solver to find
             a solution.
 
+        verbose : bool, optional
+            Whether to print out information about the solving
+
         For a list of the remaining settings, use the method
         `~AstrometryNetClass.show_allowed_settings`.
         """
@@ -386,32 +399,38 @@ class AstrometryNetClass(BaseQuery):
             else:
                 with fits.open(image_file_path) as f:
                     data = f[0].data
-
-            print("Determining background stats", flush=True)
+            if verbose:
+                print("Determining background stats", flush=True)
             mean, median, std = sigma_clipped_stats(data, sigma=3.0,
                                                     maxiters=5)
             daofind = DAOStarFinder(fwhm=fwhm,
                                     threshold=detect_threshold * std)
-            print("Finding sources", flush=True)
+            if verbose:
+                print("Finding sources", flush=True)
             sources = daofind(data - median)
-            print('Found {} sources'.format(len(sources)), flush=True)
+            if verbose:
+                print('Found {} sources'.format(len(sources)), flush=True)
             # astrometry.net wants a sorted list of sources
             # Sort first (which puts things in ascending order)
             sources.sort('flux')
             # Reverse to get descending order
             sources.reverse()
-            print(sources)
+            if verbose:
+                print(sources)
             return self.solve_from_source_list(sources['xcentroid'],
                                                sources['ycentroid'],
                                                ccd.header['naxis1'],
                                                ccd.header['naxis2'],
+                                               solve_timeout=solve_timeout,
+                                               verbose=verbose,
                                                **settings)
         if response.status_code != 200:
             raise RuntimeError('Post of job failed')
         response_d = response.json()
         submission_id = response_d['subid']
         return self.monitor_submission(submission_id,
-                                       solve_timeout=solve_timeout)
+                                       solve_timeout=solve_timeout,
+                                       verbose=verbose)
 
 
 # the default tool for users to interact with is an instance of the Class


Test patch:
diff --git a/astroquery/alma/tests/test_alma.py b/astroquery/alma/tests/test_alma.py
index dd2c2025..4a623696 100644
--- a/astroquery/alma/tests/test_alma.py
+++ b/astroquery/alma/tests/test_alma.py
@@ -243,6 +243,18 @@ def test_pol_sql():
         common_select + " WHERE (pol_states='/XX/' OR pol_states='/XX/YY/')"
 
 
+def test_unused_args():
+    alma = Alma()
+    alma._get_dataarchive_url = Mock()
+    # with patch('astroquery.alma.tapsql.coord.SkyCoord.from_name') as name_mock, pytest.raises(TypeError) as typeError:
+    with patch('astroquery.alma.tapsql.coord.SkyCoord.from_name') as name_mock:
+        with pytest.raises(TypeError) as typeError:
+            name_mock.return_value = SkyCoord(1, 2, unit='deg')
+            alma.query_object('M13', public=False, bogus=True, nope=False, band_list=[3])
+
+        assert "['bogus -> True', 'nope -> False']" in str(typeError.value)
+
+
 def test_query():
     # Tests the query and return values
     tap_mock = Mock()
diff --git a/astroquery/alma/tests/test_alma_remote.py b/astroquery/alma/tests/test_alma_remote.py
index 5c1b18e2..1f6b277c 100644
--- a/astroquery/alma/tests/test_alma_remote.py
+++ b/astroquery/alma/tests/test_alma_remote.py
@@ -87,7 +87,8 @@ class TestAlma:
 
     def test_bands(self, alma):
         payload = {'band_list': ['5', '7']}
-        result = alma.query(payload)
+        # Added maxrec here as downloading and reading the results take too long.
+        result = alma.query(payload, maxrec=1000)
         assert len(result) > 0
         for row in result:
             assert ('5' in row['band_list']) or ('7' in row['band_list'])
@@ -136,7 +137,7 @@ class TestAlma:
         assert not alma.is_proprietary('uid://A001/X12a3/Xe9')
         IVOA_DATE_FORMAT = "%Y-%m-%dT%H:%M:%S.%f"
         now = datetime.utcnow().strftime(IVOA_DATE_FORMAT)[:-3]
-        query = "select top 1 obs_id from ivoa.obscore where " \
+        query = "select top 1 member_ous_uid from ivoa.obscore where " \
                 "obs_release_date > '{}'".format(now)
         result = alma.query_tap(query)
         assert len(result.table) == 1
@@ -146,6 +147,7 @@ class TestAlma:
         with pytest.raises(AttributeError):
             alma.is_proprietary('uid://NON/EXI/STING')
 
+    @pytest.mark.xfail(reason="Depends on PR 2438 (https://github.com/astropy/astroquery/pull/2438)")
     def test_data_info(self, temp_dir, alma):
         alma.cache_location = temp_dir
 
@@ -257,6 +259,7 @@ class TestAlma:
         gc_data = alma.query_region(galactic_center, 1 * u.deg)
         # assert len(gc_data) >= 425 # Feb 8, 2016
         assert len(gc_data) >= 50  # Nov 16, 2016
+        content_length_column_name = 'content_length'
 
         uids = np.unique(m83_data['Member ous id'])
         if ASTROPY_LT_4_1:
@@ -271,11 +274,11 @@ class TestAlma:
         assert X30.sum() == 4  # Jul 13, 2020
         assert X31.sum() == 4  # Jul 13, 2020
         mous1 = alma.get_data_info('uid://A001/X11f/X30')
-        totalsize_mous1 = mous1['size'].sum() * u.Unit(mous1['size'].unit)
+        totalsize_mous1 = mous1[content_length_column_name].sum() * u.Unit(mous1[content_length_column_name].unit)
         assert (totalsize_mous1.to(u.B) > 1.9*u.GB)
 
         mous = alma.get_data_info('uid://A002/X3216af/X31')
-        totalsize_mous = mous['size'].sum() * u.Unit(mous['size'].unit)
+        totalsize_mous = mous[content_length_column_name].sum() * u.Unit(mous[content_length_column_name].unit)
         # More recent ALMA request responses do not include any information
         # about file size, so we have to allow for the possibility that all
         # file sizes are replaced with -1
@@ -313,11 +316,13 @@ class TestAlma:
         result = alma.query(payload={'pi_name': '*Bally*'}, public=False,
                             maxrec=10)
         assert result
-        result.write('/tmp/alma-onerow.txt', format='ascii')
+        # Add overwrite=True in case the test previously died unexpectedly
+        # and left the temp file.
+        result.write('/tmp/alma-onerow.txt', format='ascii', overwrite=True)
         for row in result:
             assert 'Bally' in row['obs_creator_name']
         result = alma.query(payload=dict(project_code='2016.1.00165.S'),
-                            public=False, cache=False)
+                            public=False)
         assert result
         for row in result:
             assert '2016.1.00165.S' == row['proposal_id']
@@ -336,8 +341,7 @@ class TestAlma:
 
         result = alma.query_region(
             coordinates.SkyCoord('5:35:14.461 -5:21:54.41', frame='fk5',
-                                 unit=(u.hour, u.deg)), radius=0.034 * u.deg,
-            payload={'energy.frequency-asu': '215 .. 220'})
+                                 unit=(u.hour, u.deg)), radius=0.034 * u.deg)
 
         result = alma.query(payload=dict(project_code='2012.*',
                                          public_data=True))