Skip to content

Commit 091e6b3

Browse files
authored
Merge pull request #2954 from cds-astro/refactor_Simbad
Simbad: refactor to use TAP
2 parents f64b997 + 6594ebb commit 091e6b3

39 files changed

+4680
-3241
lines changed

CHANGES.rst

+45
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,51 @@ vizier
4242
- Change the type of raised error when the catalog is not found in ``Vizier.get_catalog_metadata``
4343
from ``IndexError`` to ``EmptyResponseError`` [#2980]
4444

45+
simbad
46+
^^^^^^
47+
48+
- The ``ROW_LIMIT`` value to have the maximum number of rows is now -1.
49+
Use ``ROW_LIMIT = 0`` to retrieve the output's meta-data. [#2954]
50+
51+
- ``ROW_LIMIT`` can now be set at instantiation
52+
(e.g.: ``simbad = Simbad(ROW_LIMIT=10))``). [#2954]
53+
54+
- ``list_votable_fields`` now return an astropy Table with added fields
55+
information instead of a list of strings. [#2954]
56+
57+
- ``list_votable_fields`` is now queried directly from SIMBAD instead of reading
58+
a file in astroquery. This prevents it from being outdated. [#2954]
59+
60+
- ``get_votable_fields`` now prints the table name and column name instead of
61+
just the column name. [#2954]
62+
63+
- The ``verbose`` and ``cache`` kwargs have been deprecated from all methods
64+
as they have no effect with with the new query interface. [#2954]
65+
66+
- ``get_adql`` is deprecated and replaced by ``get_query_payload`` in
67+
``list_columns`` and ``list_table``.
68+
The payload output contains the ADQL under the ``QUERY`` key. [#2954]
69+
70+
- all query methods except ``query_tap`` and ``query_criteria`` now accept a
71+
``criteria`` argument to restrict the results with custom criteria. [#2954]
72+
73+
- ``query_objects`` outputs now have an additional column ``user_specified_id``
74+
containing the objects' name as specified by the user.
75+
The ``votable_field`` option ``typed_id`` is removed. [#2954]
76+
77+
- The ``equinox`` and ``epoch`` kwargs are deprecated in ``query_region``,
78+
use astropy.coordinates.SkyCoord directly instead. [#2954]
79+
80+
- ``query_bibcode`` has a new option ``abstract`` that allows to also
81+
retrieve the article's abstract. [#2954]
82+
83+
- ``query_bibcode`` output is now in an astropy Table with distinct columns
84+
instead of a single one in which all the information was a string. [#2954]
85+
86+
- ``query_criteria`` is now deprecated and should be replaced by either custom
87+
TAP queries or by the ``criteria`` argument added in the other query methods.
88+
A helper method was added ``astroquery.simbad.utils.CriteriaTranslator`` to
89+
translate between the sim-script syntax and the TAP/ADQL syntax. [#2954]
4590

4691
skyview
4792
^^^^^^^

README.rst

+4-3
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,10 @@ website <https://simbad.cds.unistra.fr/simbad/>`_, use the ``simbad`` sub-packag
3535
>>> from astroquery.simbad import Simbad
3636
>>> theta1c = Simbad.query_object('tet01 Ori C')
3737
>>> theta1c.pprint()
38-
MAIN_ID RA DEC ... COO_QUAL COO_WAVELENGTH COO_BIBCODE
39-
------------- ------------- ------------- ... -------- -------------- -------------------
40-
* tet01 Ori C 05 35 16.4637 -05 23 22.848 ... A O 2007A&A...474..653V
38+
main_id ra dec ... coo_wavelength coo_bibcode matched_id
39+
deg deg ...
40+
------------- ------------- ------------- ... -------------- ------------------- -------------
41+
* tet01 Ori C 83.8186095697 -5.3897005033 ... O 2020yCat.1350....0G * tet01 Ori C
4142
4243
Installation and Requirements
4344
-----------------------------

astroquery/esa/jwst/core.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -571,9 +571,12 @@ def resolve_target_coordinates(self, target_name, target_resolver):
571571
if target_resolver == "ALL" or target_resolver == "SIMBAD":
572572
try:
573573
result_table = Simbad.query_object(target_name)
574-
return SkyCoord((f'{result_table["RA"][0]} '
575-
f'{result_table["DEC"][0]}'),
576-
unit=(units.hourangle,
574+
# new simbad behavior does not return None but an empty table
575+
if len(result_table) == 0:
576+
result_table = None
577+
return SkyCoord((f'{result_table["ra"][0]} '
578+
f'{result_table["dec"][0]}'),
579+
unit=(units.deg,
577580
units.deg), frame="icrs")
578581
except (KeyError, TypeError, ConnectionError):
579582
log.info("SIMBAD could not resolve this target")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!-- Produced with astropy.io.votable version 6.0.0
3+
http://www.astropy.org/ -->
4+
<VOTABLE version="1.3" xmlns="http://www.ivoa.net/xml/VOTable/v1.3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.ivoa.net/xml/VOTable/v1.3 http://www.ivoa.net/xml/VOTable/VOTable-1.3.xsd">
5+
<RESOURCE type="results">
6+
<INFO ID="QUERY_STATUS" name="QUERY_STATUS" value="OK"/>
7+
<INFO ID="PROVIDER" name="PROVIDER" value="CDS">SIMBAD TAP Service</INFO>
8+
<INFO ID="QUERY" name="QUERY" value="SELECT basic.&quot;main_id&quot;, basic.&quot;ra&quot;, basic.&quot;dec&quot;, basic.&quot;coo_err_maj&quot;, basic.&quot;coo_err_min&quot;, basic.&quot;coo_err_angle&quot;, basic.&quot;coo_wavelength&quot;, basic.&quot;coo_bibcode&quot;, ident.&quot;id&quot; AS matched_id FROM basic JOIN ident ON basic.&quot;oid&quot; = ident.&quot;oidref&quot; WHERE id = &apos;M1&apos;"/>
9+
<TABLE ID="result_S1708521282485" name="result_S1708521282485">
10+
<FIELD ID="main_id" arraysize="*" datatype="char" name="main_id" ucd="meta.id;meta.main">
11+
<DESCRIPTION>
12+
Main identifier for an object
13+
</DESCRIPTION>
14+
</FIELD>
15+
<FIELD ID="ra" datatype="double" name="ra" ucd="pos.eq.ra;meta.main" unit="deg">
16+
<DESCRIPTION>
17+
Right ascension
18+
</DESCRIPTION>
19+
</FIELD>
20+
<FIELD ID="dec" datatype="double" name="dec" ucd="pos.eq.dec;meta.main" unit="deg">
21+
<DESCRIPTION>
22+
Declination
23+
</DESCRIPTION>
24+
</FIELD>
25+
<FIELD ID="coo_err_maj" datatype="float" name="coo_err_maj" ucd="phys.angSize.smajAxis;pos.errorEllipse;pos.eq" unit="mas">
26+
<DESCRIPTION>
27+
Coordinate error major axis
28+
</DESCRIPTION>
29+
</FIELD>
30+
<FIELD ID="coo_err_min" datatype="float" name="coo_err_min" ucd="phys.angSize.sminAxis;pos.errorEllipse;pos.eq" unit="mas">
31+
<DESCRIPTION>
32+
Coordinate error minor axis
33+
</DESCRIPTION>
34+
</FIELD>
35+
<FIELD ID="coo_err_angle" datatype="short" name="coo_err_angle" ucd="pos.posAng;pos.errorEllipse;pos.eq" unit="deg">
36+
<DESCRIPTION>
37+
Coordinate error angle
38+
</DESCRIPTION>
39+
<VALUES null="-32768"/>
40+
</FIELD>
41+
<FIELD ID="coo_wavelength" arraysize="1" datatype="char" name="coo_wavelength" ucd="instr.bandpass;pos.eq">
42+
<DESCRIPTION>
43+
Wavelength class for the origin of the coordinates (R,I,V,U,X,G)
44+
</DESCRIPTION>
45+
</FIELD>
46+
<FIELD ID="coo_bibcode" arraysize="*" datatype="char" name="coo_bibcode" ucd="meta.bib.bibcode;pos.eq">
47+
<DESCRIPTION>
48+
Coordinate reference
49+
</DESCRIPTION>
50+
</FIELD>
51+
<FIELD ID="matched_id" arraysize="*" datatype="char" name="matched_id" ucd="meta.id">
52+
<DESCRIPTION>
53+
Identifier
54+
</DESCRIPTION>
55+
</FIELD>
56+
<DATA>
57+
<TABLEDATA>
58+
<TR>
59+
<TD>M 1</TD>
60+
<TD>83.6287</TD>
61+
<TD>22.0147</TD>
62+
<TD>18500</TD>
63+
<TD>18500</TD>
64+
<TD>0</TD>
65+
<TD>R</TD>
66+
<TD>1995AuJPh..48..143S</TD>
67+
<TD>M 1</TD>
68+
</TR>
69+
</TABLEDATA>
70+
</DATA>
71+
</TABLE>
72+
</RESOURCE>
73+
</VOTABLE>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!-- Produced with astropy.io.votable version 6.0.0
3+
http://www.astropy.org/ -->
4+
<VOTABLE version="1.3" xmlns="http://www.ivoa.net/xml/VOTable/v1.3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.ivoa.net/xml/VOTable/v1.3 http://www.ivoa.net/xml/VOTable/VOTable-1.3.xsd">
5+
<RESOURCE type="results">
6+
<INFO ID="QUERY_STATUS" name="QUERY_STATUS" value="OK"/>
7+
<INFO ID="PROVIDER" name="PROVIDER" value="CDS">SIMBAD TAP Service</INFO>
8+
<INFO ID="QUERY" name="QUERY" value="SELECT basic.&quot;main_id&quot;, basic.&quot;ra&quot;, basic.&quot;dec&quot;, basic.&quot;coo_err_maj&quot;, basic.&quot;coo_err_min&quot;, basic.&quot;coo_err_angle&quot;, basic.&quot;coo_wavelength&quot;, basic.&quot;coo_bibcode&quot;, ident.&quot;id&quot; AS matched_id FROM basic JOIN ident ON basic.&quot;oid&quot; = ident.&quot;oidref&quot; WHERE id = &apos;TEST&apos;"/>
9+
<TABLE ID="result_S1708521454265" name="result_S1708521454265">
10+
<FIELD ID="main_id" arraysize="*" datatype="char" name="main_id" ucd="meta.id;meta.main">
11+
<DESCRIPTION>
12+
Main identifier for an object
13+
</DESCRIPTION>
14+
</FIELD>
15+
<FIELD ID="ra" datatype="double" name="ra" ucd="pos.eq.ra;meta.main" unit="deg">
16+
<DESCRIPTION>
17+
Right ascension
18+
</DESCRIPTION>
19+
</FIELD>
20+
<FIELD ID="dec" datatype="double" name="dec" ucd="pos.eq.dec;meta.main" unit="deg">
21+
<DESCRIPTION>
22+
Declination
23+
</DESCRIPTION>
24+
</FIELD>
25+
<FIELD ID="coo_err_maj" datatype="float" name="coo_err_maj" ucd="phys.angSize.smajAxis;pos.errorEllipse;pos.eq" unit="mas">
26+
<DESCRIPTION>
27+
Coordinate error major axis
28+
</DESCRIPTION>
29+
</FIELD>
30+
<FIELD ID="coo_err_min" datatype="float" name="coo_err_min" ucd="phys.angSize.sminAxis;pos.errorEllipse;pos.eq" unit="mas">
31+
<DESCRIPTION>
32+
Coordinate error minor axis
33+
</DESCRIPTION>
34+
</FIELD>
35+
<FIELD ID="coo_err_angle" datatype="short" name="coo_err_angle" ucd="pos.posAng;pos.errorEllipse;pos.eq" unit="deg">
36+
<DESCRIPTION>
37+
Coordinate error angle
38+
</DESCRIPTION>
39+
<VALUES null="-32768"/>
40+
</FIELD>
41+
<FIELD ID="coo_wavelength" arraysize="1" datatype="char" name="coo_wavelength" ucd="instr.bandpass;pos.eq">
42+
<DESCRIPTION>
43+
Wavelength class for the origin of the coordinates (R,I,V,U,X,G)
44+
</DESCRIPTION>
45+
</FIELD>
46+
<FIELD ID="coo_bibcode" arraysize="*" datatype="char" name="coo_bibcode" ucd="meta.bib.bibcode;pos.eq">
47+
<DESCRIPTION>
48+
Coordinate reference
49+
</DESCRIPTION>
50+
</FIELD>
51+
<FIELD ID="matched_id" arraysize="*" datatype="char" name="matched_id" ucd="meta.id">
52+
<DESCRIPTION>
53+
Identifier
54+
</DESCRIPTION>
55+
</FIELD>
56+
</TABLE>
57+
</RESOURCE>
58+
</VOTABLE>

astroquery/esa/jwst/tests/test_jwsttap.py

+55-49
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import os
1212
import shutil
1313
from pathlib import Path
14-
from unittest.mock import MagicMock
14+
from unittest.mock import MagicMock, patch
1515
import sys
1616
import io
1717

@@ -21,14 +21,15 @@
2121
from astropy import units
2222
from astropy.coordinates.name_resolve import NameResolveError
2323
from astropy.coordinates.sky_coordinate import SkyCoord
24+
from astropy.io.votable import parse_single_table
2425
from astropy.table import Table
2526
from astropy.units import Quantity
2627
from astroquery.exceptions import TableParseError
2728

2829
from astroquery.esa.jwst import JwstClass
2930
from astroquery.esa.jwst.tests.DummyTapHandler import DummyTapHandler
3031
from astroquery.ipac.ned import Ned
31-
from astroquery.simbad import Simbad
32+
from astroquery.simbad import SimbadClass
3233
from astroquery.utils.tap.conn.tests.DummyConnHandler import DummyConnHandler
3334
from astroquery.utils.tap.conn.tests.DummyResponse import DummyResponse
3435
from astroquery.utils.tap.core import TapPlus
@@ -914,53 +915,58 @@ def __check_extracted_files(self, files_expected, files_returned):
914915
raise ValueError(f"Not found expected file: {f}")
915916

916917
def test_query_target_error(self):
917-
jwst = JwstClass(show_messages=False)
918-
simbad = Simbad()
919-
ned = Ned()
920-
vizier = Vizier()
921-
# Testing default parameters
922-
with pytest.raises((ValueError, TableParseError)) as err:
923-
jwst.query_target(target_name="M1", target_resolver="")
924-
assert "This target resolver is not allowed" in err.value.args[0]
925-
with pytest.raises((ValueError, TableParseError)) as err:
926-
jwst.query_target("TEST")
927-
assert ('This target name cannot be determined with this '
928-
'resolver: ALL' in err.value.args[0] or 'Failed to parse' in err.value.args[0])
929-
with pytest.raises((ValueError, TableParseError)) as err:
930-
jwst.query_target(target_name="M1", target_resolver="ALL")
931-
assert err.value.args[0] in ["This target name cannot be determined "
932-
"with this resolver: ALL", "Missing "
933-
"required argument: 'width'"]
934-
935-
# Testing no valid coordinates from resolvers
936-
simbad_file = data_path('test_query_by_target_name_simbad_ned_error.vot')
937-
simbad_table = Table.read(simbad_file)
938-
simbad.query_object = MagicMock(return_value=simbad_table)
939-
ned_file = data_path('test_query_by_target_name_simbad_ned_error.vot')
940-
ned_table = Table.read(ned_file)
941-
ned.query_object = MagicMock(return_value=ned_table)
942-
vizier_file = data_path('test_query_by_target_name_vizier_error.vot')
943-
vizier_table = Table.read(vizier_file)
944-
vizier.query_object = MagicMock(return_value=vizier_table)
945-
946-
# coordinate_error = 'coordinate must be either a string or astropy.coordinates'
947-
with pytest.raises((ValueError, TableParseError)) as err:
948-
jwst.query_target(target_name="test", target_resolver="SIMBAD",
949-
radius=units.Quantity(5, units.deg))
950-
assert ('This target name cannot be determined with this '
951-
'resolver: SIMBAD' in err.value.args[0] or 'Failed to parse' in err.value.args[0])
952-
953-
with pytest.raises((ValueError, TableParseError)) as err:
954-
jwst.query_target(target_name="test", target_resolver="NED",
955-
radius=units.Quantity(5, units.deg))
956-
assert ('This target name cannot be determined with this '
957-
'resolver: NED' in err.value.args[0] or 'Failed to parse' in err.value.args[0])
958-
959-
with pytest.raises((ValueError, TableParseError)) as err:
960-
jwst.query_target(target_name="test", target_resolver="VIZIER",
961-
radius=units.Quantity(5, units.deg))
962-
assert ('This target name cannot be determined with this resolver: '
963-
'VIZIER' in err.value.args[0] or 'Failed to parse' in err.value.args[0])
918+
# need to patch simbad query object here
919+
with patch("astroquery.simbad.SimbadClass.query_object",
920+
side_effect=lambda object_name: parse_single_table(
921+
Path(__file__).parent / "data" / f"simbad_{object_name}.vot"
922+
).to_table()):
923+
jwst = JwstClass(show_messages=False)
924+
simbad = SimbadClass()
925+
ned = Ned()
926+
vizier = Vizier()
927+
# Testing default parameters
928+
with pytest.raises((ValueError, TableParseError)) as err:
929+
jwst.query_target(target_name="M1", target_resolver="")
930+
assert "This target resolver is not allowed" in err.value.args[0]
931+
with pytest.raises((ValueError, TableParseError)) as err:
932+
jwst.query_target("TEST")
933+
assert ('This target name cannot be determined with this '
934+
'resolver: ALL' in err.value.args[0] or 'Failed to parse' in err.value.args[0])
935+
with pytest.raises((ValueError, TableParseError)) as err:
936+
jwst.query_target(target_name="M1", target_resolver="ALL")
937+
assert err.value.args[0] in ["This target name cannot be determined "
938+
"with this resolver: ALL", "Missing "
939+
"required argument: 'width'"]
940+
941+
# Testing no valid coordinates from resolvers
942+
simbad_file = data_path('test_query_by_target_name_simbad_ned_error.vot')
943+
simbad_table = Table.read(simbad_file)
944+
simbad.query_object = MagicMock(return_value=simbad_table)
945+
ned_file = data_path('test_query_by_target_name_simbad_ned_error.vot')
946+
ned_table = Table.read(ned_file)
947+
ned.query_object = MagicMock(return_value=ned_table)
948+
vizier_file = data_path('test_query_by_target_name_vizier_error.vot')
949+
vizier_table = Table.read(vizier_file)
950+
vizier.query_object = MagicMock(return_value=vizier_table)
951+
952+
# coordinate_error = 'coordinate must be either a string or astropy.coordinates'
953+
with pytest.raises((ValueError, TableParseError)) as err:
954+
jwst.query_target(target_name="TEST", target_resolver="SIMBAD",
955+
radius=units.Quantity(5, units.deg))
956+
assert ('This target name cannot be determined with this '
957+
'resolver: SIMBAD' in err.value.args[0] or 'Failed to parse' in err.value.args[0])
958+
959+
with pytest.raises((ValueError, TableParseError)) as err:
960+
jwst.query_target(target_name="TEST", target_resolver="NED",
961+
radius=units.Quantity(5, units.deg))
962+
assert ('This target name cannot be determined with this '
963+
'resolver: NED' in err.value.args[0] or 'Failed to parse' in err.value.args[0])
964+
965+
with pytest.raises((ValueError, TableParseError)) as err:
966+
jwst.query_target(target_name="TEST", target_resolver="VIZIER",
967+
radius=units.Quantity(5, units.deg))
968+
assert ('This target name cannot be determined with this resolver: '
969+
'VIZIER' in err.value.args[0] or 'Failed to parse' in err.value.args[0])
964970

965971
def test_remove_jobs(self):
966972
dummyTapHandler = DummyTapHandler()

astroquery/query.py

+4
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,10 @@ def __init__(self):
203203

204204
self.name = self.__class__.__name__.split("Class")[0]
205205

206+
def __call__(self, *args, **kwargs):
207+
""" init a fresh copy of self """
208+
return self.__class__(*args, **kwargs)
209+
206210

207211
class BaseQuery(metaclass=LoginABCMeta):
208212
"""

astroquery/simbad/__init__.py

+11-11
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@
33
SIMBAD Query Tool
44
=================
55
6-
The SIMBAD query tool creates a `script query
7-
<https://simbad.cds.unistra.fr/simbad/sim-fscript>`__ that returns VOtable XML
8-
data that is then parsed into a SimbadResult object. This object then
9-
parses the data and returns a table parsed with `astropy.io.votable.parse`.
6+
The SIMBAD query tool creates `TAP ADQL queries
7+
<https://cds.unistra.fr/help/documentation/simbad-more/adql-simbad/>`__ that return VOtable XML
8+
data. This is then parsed into a `~astropy.table.Table` object.
109
"""
1110
from astropy import config as _config
1211

@@ -27,16 +26,17 @@ class Conf(_config.ConfigNamespace):
2726
'Time limit for connecting to Simbad server.')
2827

2928
row_limit = _config.ConfigItem(
30-
# O defaults to the maximum limit
31-
0,
29+
# defaults to the maximum limit
30+
-1,
3231
'Maximum number of rows that will be fetched from the result.')
3332

33+
# should be columns of 'basic'
34+
default_columns = ["main_id", "ra", "dec", "coo_err_maj", "coo_err_min",
35+
"coo_err_angle", "coo_wavelength", "coo_bibcode"]
36+
3437

3538
conf = Conf()
3639

37-
from .core import Simbad, SimbadClass, SimbadBaseQuery
40+
from .core import Simbad, SimbadClass
3841

39-
__all__ = ['Simbad', 'SimbadClass',
40-
'SimbadBaseQuery',
41-
'Conf', 'conf',
42-
]
42+
__all__ = ['Simbad', 'SimbadClass', 'Conf', 'conf']

0 commit comments

Comments
 (0)