Repository: astropy/astroquery

Issue:
ALMA: Cannot select multiple science keywords
I attempted this query:
```python
result = Alma().query(payload={'spatial_resolution':'<0.1', 'science_keyword':
                               ['High-mass star formation',
                                'Disks around high-mass stars']},
                      public=True)
```
resulting in an exception `DALQueryError: ADQL syntax error: Encountered " <S_IDENTIFIER> "High "" at line 1, column 81.`

The problem was a malformed ADQL query:
```
TAP query: select * from ivoa.obscore WHERE spatial_resolution<=0.1 AND science_keyword='['High-mass star formation', 'Disks around high-mass stars']' AND science_observation='T' AND data_rights='Public'
```

The solution is to do the query correctly, e.g.:
```python
AA.tap.search("select * from ivoa.obscore WHERE spatial_resolution<=0.1 AND science_keyword in ('High-mass star formation', 'Disks around high-mass stars') AND science_observation='T' AND data_rights='Public'")
```

Discussion:

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index df9d6e56..d951b9a5 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -20,6 +20,11 @@ hsa
 Service fixes and enhancements
 ------------------------------
 
+alma
+^^^^
+
+- Fixed a regression to handle arrays of string input for the ``query`` methods. [#2094]
+
 cadc
 ^^^^
 
diff --git a/astroquery/alma/tapsql.py b/astroquery/alma/tapsql.py
index 9af44306..a000f066 100644
--- a/astroquery/alma/tapsql.py
+++ b/astroquery/alma/tapsql.py
@@ -279,6 +279,8 @@ def _val_parse(value, val_type=float):
         except Exception as e:
             raise ValueError(
                 'Error parsing {}. Details: {}'.format(value, str(e)))
+    elif isinstance(value, list):
+        result = value
     else:
         result.append(value)
     return result
diff --git a/docs/astrometry_net/astrometry_net.rst b/docs/astrometry_net/astrometry_net.rst
index 98a6fc77..2a49276d 100644
--- a/docs/astrometry_net/astrometry_net.rst
+++ b/docs/astrometry_net/astrometry_net.rst
@@ -18,65 +18,8 @@ To use `astroquery.astrometry_net` you will need to set up an account at
 `astrometry.net`_ and get your API key. The API key is available under your
 profile at `astrometry.net`_ when you are logged in.
 
-Setting API key for astroquery.astrometry_net
----------------------------------------------
+TODO: explain how to set config item for api key or how to get it into keyring.
 
-Adding API key to astroquery.cfg
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-1. Go to https://nova.astrometry.net and sign in.
-2. Copy your API key
-3. Add the following to your config file located at ``$HOME/.astropy/config/astroquery.cfg``
-
-.. code-block:: python
-
-    [astrometry_net]
-
-    ## The Astrometry.net API key.
-    api_key =
-
-    ## Name of server
-    server = http://nova.astrometry.net
-
-    ## Default timeout for connecting to server
-    timeout = 120
-
-4. Add your API key to the config file!
-
-For more information about `astropy.config`, see: https://docs.astropy.org/en/stable/config/index.html
-
-Using keyring to store API key
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-You can use the `keyring <https://pypi.python.org/pypi/keyring>`_ module to store your API key.
-
-.. code-block:: python
-
-    >>> import keyring
-    >>> keyring.set_password('astroquery:astrometry_net', None, 'apikeyhere')
-
-If you have multiple users and keys you can change ``None`` to another name to assign usernames to different keys.
-
-Now you can include the `keyring <https://pypi.python.org/pypi/keyring>`_ module to set your API key.
-
-.. code-block:: python
-
-    >>> import keyring
-    >>> from astroquery.astrometry_net import AstrometryNet
-    WARNING: Astrometry.net API key not found in configuration file [astroquery.astrometry_net.core]
-    WARNING: You need to manually edit the configuration file and add it [astroquery.astrometry_net.core]
-    WARNING: You may also register it for this session with AstrometryNet.key = 'XXXXXXXX' [astroquery.astrometry_net.core]
-    >>> ast = AstrometryNet()
-    WARNING: Astrometry.net API key not found in configuration file [astroquery.astrometry_net.core]
-    WARNING: You need to manually edit the configuration file and add it [astroquery.astrometry_net.core]
-    WARNING: You may also register it for this session with AstrometryNet.key = 'XXXXXXXX' [astroquery.astrometry_net.core]
-    >>> ast.api_key = keyring.get_password('astroquery:astrometry_net', '')
-
-For multiple users, replace the empty argument with the desired username.
-
-.. code-block:: python
-
-    >>> ast.api_key = keyring.get_password('astroquery:astrometry_net', 'username')
-    
 .. note::
 
     Be aware that some information you submit to `astrometry.net`_ is publicly


Test patch:
diff --git a/astroquery/alma/tests/test_alma.py b/astroquery/alma/tests/test_alma.py
index 5198230a..dd2c2025 100644
--- a/astroquery/alma/tests/test_alma.py
+++ b/astroquery/alma/tests/test_alma.py
@@ -173,6 +173,16 @@ def test_gen_str_sql():
         "(proposal_id LIKE '2012.%' OR proposal_id LIKE '2013._3%')"
 
 
+def test_gen_array_sql():
+    # test string array input (regression in #2094)
+    # string arrays should be OR'd together
+    common_select = "select * from ivoa.obscore WHERE "
+    test_keywords = ["High-mass star formation", "Disks around high-mass stars"]
+    assert _gen_sql({"spatial_resolution": "<0.1",
+        "science_keyword": test_keywords}) == common_select + \
+            "spatial_resolution<=0.1 AND (science_keyword='High-mass star formation' OR science_keyword='Disks around high-mass stars')"
+
+
 def test_gen_datetime_sql():
     common_select = 'select * from ivoa.obscore WHERE '
     assert _gen_sql({'start_date': '01-01-2020'}) == common_select + \
diff --git a/astroquery/esa/hsa/tests/test_hsa.py b/astroquery/esa/hsa/tests/test_hsa.py
index 5a318918..c7e1b234 100644
--- a/astroquery/esa/hsa/tests/test_hsa.py
+++ b/astroquery/esa/hsa/tests/test_hsa.py
@@ -1,6 +1,4 @@
 # Licensed under a 3-clause BSD style license - see LICENSE.rst
-import os
-
 import pytest
 
 from astropy import units as u
@@ -19,7 +17,6 @@ class TestHSA:
                        'verbose': False}
         dummyTapHandler = DummyHSATapHandler("launch_job", parameterst)
         return dummyTapHandler
-        os.remove("test.vot")
 
     def test_query_hsa_tap(self):
         parameters = {'query': "select top 10 * from hsa.v_active_observation",
diff --git a/astroquery/esa/hubble/tests/test_esa_hubble.py b/astroquery/esa/hubble/tests/test_esa_hubble.py
index 86ee0d32..107bff87 100644
--- a/astroquery/esa/hubble/tests/test_esa_hubble.py
+++ b/astroquery/esa/hubble/tests/test_esa_hubble.py
@@ -95,7 +95,7 @@ class TestESAHubble:
     def test_download_product_by_calibration(self):
         parameters = {'observation_id': "J6FL25S4Q",
                       'cal_level': "RAW",
-                      'filename': "J6FL25S4Q.vot.test",
+                      'filename': "J6FL25S4Q.vot",
                       'verbose': True}
         ehst = ESAHubbleClass(self.get_dummy_tap_handler())
         ehst.download_product(observation_id=parameters['observation_id'],
@@ -106,7 +106,7 @@ class TestESAHubble:
     def test_download_product_by_product_type(self):
         parameters = {'observation_id': "J6FL25S4Q",
                       'product_type': "SCIENCE_PRODUCT",
-                      'filename': "J6FL25S4Q.vot.test",
+                      'filename': "J6FL25S4Q.vot",
                       'verbose': True}
         ehst = ESAHubbleClass(self.get_dummy_tap_handler())
         ehst.download_product(observation_id=parameters['observation_id'],
@@ -131,7 +131,6 @@ class TestESAHubble:
         ehst.get_postcard(observation_id="X0MC5101T",
                           filename="X0MC5101T.vot",
                           verbose=True)
-        os.remove("X0MC5101T.vot")
 
     @patch.object(ESAHubbleClass, 'cone_search')
     @patch.object(ESAHubbleClass, '_query_tap_target')
diff --git a/astroquery/esa/hubble/tests/test_esa_hubble_remote.py b/astroquery/esa/hubble/tests/test_esa_hubble_remote.py
index e69a71f4..79c385c5 100644
--- a/astroquery/esa/hubble/tests/test_esa_hubble_remote.py
+++ b/astroquery/esa/hubble/tests/test_esa_hubble_remote.py
@@ -79,6 +79,7 @@ class TestEsaHubbleRemoteData:
         c = coordinates.SkyCoord("00h42m44.51s +41d16m08.45s", frame='icrs')
         temp_file = self.temp_folder.name + "/cone_search_m31_5.vot"
         table = esa_hubble.cone_search(c, 7, temp_file, verbose=True)
+        assert os.path.exists(temp_file)
         assert 'observation_id' in table.columns
         assert len(table) > 0
         remove_last_job()
@@ -102,7 +103,7 @@ class TestEsaHubbleRemoteData:
     def test_hap_simple_to_hap_composite(self):
         esa_hubble = ESAHubble()
         result = esa_hubble.get_member_observations('hst_16316_71_acs_sbc_f150lp_jec071i9')
-        assert result == [' hst_16316_71_acs_sbc_total_jec071', 'hst_16316_71_acs_sbc_f150lp_jec071']
+        assert result == ['hst_16316_71_acs_sbc_f150lp_jec071']
 
     def test_hap_simple_to_hst_simple(self):
         esa_hubble = ESAHubble()
@@ -117,4 +118,5 @@ class TestEsaHubbleRemoteData:
     def test_query_target(self):
         temp_file = self.temp_folder.name + "/" + "m31_query.xml"
         table = esa_hubble.query_target(name="m3", filename=temp_file)
+        assert os.path.exists(temp_file)
         assert 'observation_id' in table.columns
diff --git a/astroquery/esa/xmm_newton/tests/test_xmm_newton.py b/astroquery/esa/xmm_newton/tests/test_xmm_newton.py
index ba5580a8..5fa58e7c 100644
--- a/astroquery/esa/xmm_newton/tests/test_xmm_newton.py
+++ b/astroquery/esa/xmm_newton/tests/test_xmm_newton.py
@@ -109,7 +109,6 @@ class TestXMMNewton:
                     continue
                 fname_info = xsa._parse_filename(fname)
                 assert fname_info["X"] == "P"
-        os.remove("filename.tar")
 
     _files = {
         "0405320501": {
diff --git a/astroquery/esasky/tests/test_esasky_remote.py b/astroquery/esasky/tests/test_esasky_remote.py
index 33be1a3a..fca90a63 100755
--- a/astroquery/esasky/tests/test_esasky_remote.py
+++ b/astroquery/esasky/tests/test_esasky_remote.py
@@ -42,6 +42,10 @@ class TestESASky:
         assert "2CXO J090341.1-322609" in result["CHANDRA-SC2"].columns["name"]
         assert "2CXO J090353.8-322642" in result["CHANDRA-SC2"].columns["name"]
 
+        result = ESASkyClass.query_ids_catalogs(source_ids=["2CXO J090341.1-322609"])
+        assert isinstance(result, TableList)
+        assert "2CXO J090341.1-322609" in result["CHANDRA-SC2"].columns["name"]
+
         result = ESASkyClass.query_ids_catalogs(source_ids=["2CXO J090341.1-322609", "2CXO J090353.8-322642", "44899",
                                                             "45057"],
                                                 catalogs=["CHANDRA-SC2", "Hipparcos-2"])
@@ -162,7 +166,7 @@ class TestESASky:
 
         for mission in missions:
             file_path = os.path.join(download_directory, mission)
-            assert file_path[:16] == "ESASkyRemoteTest"
+            assert os.path.exists(file_path)
 
         shutil.rmtree(download_directory)
 
@@ -228,7 +232,7 @@ class TestESASky:
 
         for mission in missions:
             file_path = os.path.join(download_directory, mission)
-            assert file_path[:16] == "ESASkyRemoteTest"
+            assert os.path.exists(file_path)
 
         shutil.rmtree(download_directory)