diff --git a/CHANGES.rst b/CHANGES.rst index 042102723f..13c9e8a3db 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -42,6 +42,51 @@ vizier - Change the type of raised error when the catalog is not found in ``Vizier.get_catalog_metadata`` from ``IndexError`` to ``EmptyResponseError`` [#2980] +simbad +^^^^^^ + +- The ``ROW_LIMIT`` value to have the maximum number of rows is now -1. + Use ``ROW_LIMIT = 0`` to retrieve the output's meta-data. [#2954] + +- ``ROW_LIMIT`` can now be set at instantiation + (e.g.: ``simbad = Simbad(ROW_LIMIT=10))``). [#2954] + +- ``list_votable_fields`` now return an astropy Table with added fields + information instead of a list of strings. [#2954] + +- ``list_votable_fields`` is now queried directly from SIMBAD instead of reading + a file in astroquery. This prevents it from being outdated. [#2954] + +- ``get_votable_fields`` now prints the table name and column name instead of + just the column name. [#2954] + +- The ``verbose`` and ``cache`` kwargs have been deprecated from all methods + as they have no effect with with the new query interface. [#2954] + +- ``get_adql`` is deprecated and replaced by ``get_query_payload`` in + ``list_columns`` and ``list_table``. + The payload output contains the ADQL under the ``QUERY`` key. [#2954] + +- all query methods except ``query_tap`` and ``query_criteria`` now accept a + ``criteria`` argument to restrict the results with custom criteria. [#2954] + +- ``query_objects`` outputs now have an additional column ``user_specified_id`` + containing the objects' name as specified by the user. + The ``votable_field`` option ``typed_id`` is removed. [#2954] + +- The ``equinox`` and ``epoch`` kwargs are deprecated in ``query_region``, + use astropy.coordinates.SkyCoord directly instead. [#2954] + +- ``query_bibcode`` has a new option ``abstract`` that allows to also + retrieve the article's abstract. [#2954] + +- ``query_bibcode`` output is now in an astropy Table with distinct columns + instead of a single one in which all the information was a string. [#2954] + +- ``query_criteria`` is now deprecated and should be replaced by either custom + TAP queries or by the ``criteria`` argument added in the other query methods. + A helper method was added ``astroquery.simbad.utils.CriteriaTranslator`` to + translate between the sim-script syntax and the TAP/ADQL syntax. [#2954] skyview ^^^^^^^ diff --git a/README.rst b/README.rst index cb49e800a4..b82ae55410 100644 --- a/README.rst +++ b/README.rst @@ -35,9 +35,10 @@ website `_, use the ``simbad`` sub-packag >>> from astroquery.simbad import Simbad >>> theta1c = Simbad.query_object('tet01 Ori C') >>> theta1c.pprint() - MAIN_ID RA DEC ... COO_QUAL COO_WAVELENGTH COO_BIBCODE - ------------- ------------- ------------- ... -------- -------------- ------------------- - * tet01 Ori C 05 35 16.4637 -05 23 22.848 ... A O 2007A&A...474..653V + main_id ra dec ... coo_wavelength coo_bibcode matched_id + deg deg ... + ------------- ------------- ------------- ... -------------- ------------------- ------------- + * tet01 Ori C 83.8186095697 -5.3897005033 ... O 2020yCat.1350....0G * tet01 Ori C Installation and Requirements ----------------------------- diff --git a/astroquery/esa/jwst/core.py b/astroquery/esa/jwst/core.py index 55a873d14c..8a1db48bdf 100644 --- a/astroquery/esa/jwst/core.py +++ b/astroquery/esa/jwst/core.py @@ -571,9 +571,12 @@ def resolve_target_coordinates(self, target_name, target_resolver): if target_resolver == "ALL" or target_resolver == "SIMBAD": try: result_table = Simbad.query_object(target_name) - return SkyCoord((f'{result_table["RA"][0]} ' - f'{result_table["DEC"][0]}'), - unit=(units.hourangle, + # new simbad behavior does not return None but an empty table + if len(result_table) == 0: + result_table = None + return SkyCoord((f'{result_table["ra"][0]} ' + f'{result_table["dec"][0]}'), + unit=(units.deg, units.deg), frame="icrs") except (KeyError, TypeError, ConnectionError): log.info("SIMBAD could not resolve this target") diff --git a/astroquery/esa/jwst/tests/data/simbad_M1.vot b/astroquery/esa/jwst/tests/data/simbad_M1.vot new file mode 100644 index 0000000000..43eb502e5e --- /dev/null +++ b/astroquery/esa/jwst/tests/data/simbad_M1.vot @@ -0,0 +1,73 @@ + + + + + + SIMBAD TAP Service + + + + + Main identifier for an object + + + + + Right ascension + + + + + Declination + + + + + Coordinate error major axis + + + + + Coordinate error minor axis + + + + + Coordinate error angle + + + + + + Wavelength class for the origin of the coordinates (R,I,V,U,X,G) + + + + + Coordinate reference + + + + + Identifier + + + + + + + + + + + + + + + + + +
M 183.628722.014718500185000R1995AuJPh..48..143SM 1
+
+
diff --git a/astroquery/esa/jwst/tests/data/simbad_TEST.vot b/astroquery/esa/jwst/tests/data/simbad_TEST.vot new file mode 100644 index 0000000000..5161a52981 --- /dev/null +++ b/astroquery/esa/jwst/tests/data/simbad_TEST.vot @@ -0,0 +1,58 @@ + + + + + + SIMBAD TAP Service + + + + + Main identifier for an object + + + + + Right ascension + + + + + Declination + + + + + Coordinate error major axis + + + + + Coordinate error minor axis + + + + + Coordinate error angle + + + + + + Wavelength class for the origin of the coordinates (R,I,V,U,X,G) + + + + + Coordinate reference + + + + + Identifier + + +
+
+
diff --git a/astroquery/esa/jwst/tests/test_jwsttap.py b/astroquery/esa/jwst/tests/test_jwsttap.py index c7144d907c..dfb99935a6 100644 --- a/astroquery/esa/jwst/tests/test_jwsttap.py +++ b/astroquery/esa/jwst/tests/test_jwsttap.py @@ -11,7 +11,7 @@ import os import shutil from pathlib import Path -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch import sys import io @@ -21,6 +21,7 @@ from astropy import units from astropy.coordinates.name_resolve import NameResolveError from astropy.coordinates.sky_coordinate import SkyCoord +from astropy.io.votable import parse_single_table from astropy.table import Table from astropy.units import Quantity from astroquery.exceptions import TableParseError @@ -28,7 +29,7 @@ from astroquery.esa.jwst import JwstClass from astroquery.esa.jwst.tests.DummyTapHandler import DummyTapHandler from astroquery.ipac.ned import Ned -from astroquery.simbad import Simbad +from astroquery.simbad import SimbadClass from astroquery.utils.tap.conn.tests.DummyConnHandler import DummyConnHandler from astroquery.utils.tap.conn.tests.DummyResponse import DummyResponse from astroquery.utils.tap.core import TapPlus @@ -914,53 +915,58 @@ def __check_extracted_files(self, files_expected, files_returned): raise ValueError(f"Not found expected file: {f}") def test_query_target_error(self): - jwst = JwstClass(show_messages=False) - simbad = Simbad() - ned = Ned() - vizier = Vizier() - # Testing default parameters - with pytest.raises((ValueError, TableParseError)) as err: - jwst.query_target(target_name="M1", target_resolver="") - assert "This target resolver is not allowed" in err.value.args[0] - with pytest.raises((ValueError, TableParseError)) as err: - jwst.query_target("TEST") - assert ('This target name cannot be determined with this ' - 'resolver: ALL' in err.value.args[0] or 'Failed to parse' in err.value.args[0]) - with pytest.raises((ValueError, TableParseError)) as err: - jwst.query_target(target_name="M1", target_resolver="ALL") - assert err.value.args[0] in ["This target name cannot be determined " - "with this resolver: ALL", "Missing " - "required argument: 'width'"] - - # Testing no valid coordinates from resolvers - simbad_file = data_path('test_query_by_target_name_simbad_ned_error.vot') - simbad_table = Table.read(simbad_file) - simbad.query_object = MagicMock(return_value=simbad_table) - ned_file = data_path('test_query_by_target_name_simbad_ned_error.vot') - ned_table = Table.read(ned_file) - ned.query_object = MagicMock(return_value=ned_table) - vizier_file = data_path('test_query_by_target_name_vizier_error.vot') - vizier_table = Table.read(vizier_file) - vizier.query_object = MagicMock(return_value=vizier_table) - - # coordinate_error = 'coordinate must be either a string or astropy.coordinates' - with pytest.raises((ValueError, TableParseError)) as err: - jwst.query_target(target_name="test", target_resolver="SIMBAD", - radius=units.Quantity(5, units.deg)) - assert ('This target name cannot be determined with this ' - 'resolver: SIMBAD' in err.value.args[0] or 'Failed to parse' in err.value.args[0]) - - with pytest.raises((ValueError, TableParseError)) as err: - jwst.query_target(target_name="test", target_resolver="NED", - radius=units.Quantity(5, units.deg)) - assert ('This target name cannot be determined with this ' - 'resolver: NED' in err.value.args[0] or 'Failed to parse' in err.value.args[0]) - - with pytest.raises((ValueError, TableParseError)) as err: - jwst.query_target(target_name="test", target_resolver="VIZIER", - radius=units.Quantity(5, units.deg)) - assert ('This target name cannot be determined with this resolver: ' - 'VIZIER' in err.value.args[0] or 'Failed to parse' in err.value.args[0]) + # need to patch simbad query object here + with patch("astroquery.simbad.SimbadClass.query_object", + side_effect=lambda object_name: parse_single_table( + Path(__file__).parent / "data" / f"simbad_{object_name}.vot" + ).to_table()): + jwst = JwstClass(show_messages=False) + simbad = SimbadClass() + ned = Ned() + vizier = Vizier() + # Testing default parameters + with pytest.raises((ValueError, TableParseError)) as err: + jwst.query_target(target_name="M1", target_resolver="") + assert "This target resolver is not allowed" in err.value.args[0] + with pytest.raises((ValueError, TableParseError)) as err: + jwst.query_target("TEST") + assert ('This target name cannot be determined with this ' + 'resolver: ALL' in err.value.args[0] or 'Failed to parse' in err.value.args[0]) + with pytest.raises((ValueError, TableParseError)) as err: + jwst.query_target(target_name="M1", target_resolver="ALL") + assert err.value.args[0] in ["This target name cannot be determined " + "with this resolver: ALL", "Missing " + "required argument: 'width'"] + + # Testing no valid coordinates from resolvers + simbad_file = data_path('test_query_by_target_name_simbad_ned_error.vot') + simbad_table = Table.read(simbad_file) + simbad.query_object = MagicMock(return_value=simbad_table) + ned_file = data_path('test_query_by_target_name_simbad_ned_error.vot') + ned_table = Table.read(ned_file) + ned.query_object = MagicMock(return_value=ned_table) + vizier_file = data_path('test_query_by_target_name_vizier_error.vot') + vizier_table = Table.read(vizier_file) + vizier.query_object = MagicMock(return_value=vizier_table) + + # coordinate_error = 'coordinate must be either a string or astropy.coordinates' + with pytest.raises((ValueError, TableParseError)) as err: + jwst.query_target(target_name="TEST", target_resolver="SIMBAD", + radius=units.Quantity(5, units.deg)) + assert ('This target name cannot be determined with this ' + 'resolver: SIMBAD' in err.value.args[0] or 'Failed to parse' in err.value.args[0]) + + with pytest.raises((ValueError, TableParseError)) as err: + jwst.query_target(target_name="TEST", target_resolver="NED", + radius=units.Quantity(5, units.deg)) + assert ('This target name cannot be determined with this ' + 'resolver: NED' in err.value.args[0] or 'Failed to parse' in err.value.args[0]) + + with pytest.raises((ValueError, TableParseError)) as err: + jwst.query_target(target_name="TEST", target_resolver="VIZIER", + radius=units.Quantity(5, units.deg)) + assert ('This target name cannot be determined with this resolver: ' + 'VIZIER' in err.value.args[0] or 'Failed to parse' in err.value.args[0]) def test_remove_jobs(self): dummyTapHandler = DummyTapHandler() diff --git a/astroquery/query.py b/astroquery/query.py index ca24008efa..44ff36ddce 100644 --- a/astroquery/query.py +++ b/astroquery/query.py @@ -203,6 +203,10 @@ def __init__(self): self.name = self.__class__.__name__.split("Class")[0] + def __call__(self, *args, **kwargs): + """ init a fresh copy of self """ + return self.__class__(*args, **kwargs) + class BaseQuery(metaclass=LoginABCMeta): """ diff --git a/astroquery/simbad/__init__.py b/astroquery/simbad/__init__.py index 12618e3a9f..55c09db211 100644 --- a/astroquery/simbad/__init__.py +++ b/astroquery/simbad/__init__.py @@ -3,10 +3,9 @@ SIMBAD Query Tool ================= -The SIMBAD query tool creates a `script query -`__ that returns VOtable XML -data that is then parsed into a SimbadResult object. This object then -parses the data and returns a table parsed with `astropy.io.votable.parse`. +The SIMBAD query tool creates `TAP ADQL queries +`__ that return VOtable XML +data. This is then parsed into a `~astropy.table.Table` object. """ from astropy import config as _config @@ -27,16 +26,17 @@ class Conf(_config.ConfigNamespace): 'Time limit for connecting to Simbad server.') row_limit = _config.ConfigItem( - # O defaults to the maximum limit - 0, + # defaults to the maximum limit + -1, 'Maximum number of rows that will be fetched from the result.') + # should be columns of 'basic' + default_columns = ["main_id", "ra", "dec", "coo_err_maj", "coo_err_min", + "coo_err_angle", "coo_wavelength", "coo_bibcode"] + conf = Conf() -from .core import Simbad, SimbadClass, SimbadBaseQuery +from .core import Simbad, SimbadClass -__all__ = ['Simbad', 'SimbadClass', - 'SimbadBaseQuery', - 'Conf', 'conf', - ] +__all__ = ['Simbad', 'SimbadClass', 'Conf', 'conf'] diff --git a/astroquery/simbad/core.py b/astroquery/simbad/core.py index d8385b55b8..9a4026e78f 100644 --- a/astroquery/simbad/core.py +++ b/astroquery/simbad/core.py @@ -1,96 +1,33 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -""" -Simbad query class for accessing the Simbad Service -""" +"""SIMBAD query class for accessing the SIMBAD Service""" -import re -import requests -import json -import os -from collections import namedtuple -from io import BytesIO +import copy +from dataclasses import dataclass, field +from difflib import get_close_matches from functools import lru_cache +import gc +import re +from typing import Any import warnings -import astropy.units as u -from astropy.utils import isiterable -from astropy.utils.data import get_pkg_data_filename + import astropy.coordinates as coord -from astropy.table import Table -import astropy.io.votable as votable +from astropy.table import Table, Column, vstack +import astropy.units as u +from astropy.utils import isiterable, deprecated +from astropy.utils.decorators import deprecated_renamed_argument -from astroquery.query import BaseQuery, BaseVOQuery -from astroquery.utils import commons, async_to_sync -from astroquery.exceptions import TableParseError, LargeQueryWarning, BlankResponseWarning +from astroquery.query import BaseVOQuery +from astroquery.utils import commons +from astroquery.exceptions import LargeQueryWarning +from astroquery.simbad.utils import (_catch_deprecated_fields_with_arguments, + _wildcard_to_regexp, CriteriaTranslator, + query_criteria_fields) -from pyvo.dal import TAPService +from pyvo.dal import TAPService, TAPQuery from . import conf -__all__ = ['Simbad', 'SimbadClass', 'SimbadBaseQuery'] - - -def validate_epoch(value): - pattern = re.compile(r'^[JB]\d+[.]?\d+$', re.IGNORECASE) - if pattern.match(value) is None: - raise ValueError("Epoch must be specified as [J|B].\n" - "Example: epoch='J2000'") - return value - - -def validate_equinox(value): - try: - return float(value) - except (ValueError, TypeError): - raise ValueError("Equinox must be a number") - - -def validate_epoch_decorator(func): - """ - A method decorator that checks if the epoch value entered by the user - is acceptable. - """ - def wrapper(*args, **kwargs): - if kwargs.get('epoch'): - value = kwargs['epoch'] - validate_epoch(value) - return func(*args, **kwargs) - return wrapper - - -def validate_equinox_decorator(func): - """ - A method decorator that checks if the equinox value entered by the user - is acceptable. - """ - def wrapper(*args, **kwargs): - if kwargs.get('equinox'): - value = kwargs['equinox'] - validate_equinox(value) - return func(*args, **kwargs) - return wrapper - - -def strip_field(field, keep_filters=False): - """Helper tool: remove parameters from VOTABLE fields - However, this should only be applied to a subset of VOTABLE fields: - - * ra - * dec - * otype - * id - * coo - * bibcodelist - - *if* keep_filters is specified - """ - if '(' in field: - root = field[:field.find('(')] - if (root in ('ra', 'dec', 'otype', 'id', 'coo', 'bibcodelist') - or not keep_filters): - return root - - # the overall else (default option) - return field +__all__ = ['Simbad', 'SimbadClass'] def _adql_parameter(entry: str): @@ -111,227 +48,94 @@ def _adql_parameter(entry: str): return entry.replace("'", "''") -error_regex = re.compile(r'(?ms)\[(?P\d+)\]\s?(?P.+?)(\[|\Z)') -SimbadError = namedtuple('SimbadError', ('line', 'msg')) -VersionInfo = namedtuple('VersionInfo', ('major', 'minor', 'micro', 'patch')) - - -class SimbadResult: - __sections = ('script', 'console', 'error', 'data') - - def __init__(self, txt, verbose=False): - self.__txt = txt - self.__stringio = None - self.__indexes = {} - self.verbose = verbose - self.exectime = None - self.sim_version = None - self.__split_sections() - self.__parse_console_section() - self.__warn() - - def __split_sections(self): - for section in self.__sections: - match = re.search(r'(?ims)^::%s:+?\r?$(?P.*?)(^::|\Z)' % - section, self.__txt) - if match: - self.__indexes[section] = (match.start('content'), - match.end('content')) - - def __parse_console_section(self): - if self.console is None: - return - match = re.search(r'(?ims)total execution time: ([.\d]+?)\s*?secs', - self.console) - if match: - self.exectime = float(match.group(1)) - - match = re.search(r'(?ms)SIMBAD(\d) rel (\d)[.](\d+)([^\d^\s])?', - self.console) - if match: - self.sim_version = VersionInfo(*match.groups(None)) - - def __warn(self): - for error in self.errors: - warnings.warn("Warning: The script line number %i raised " - "an error (recorded in the `errors` attribute " - "of the result table): %s" % - (error.line, error.msg), - BlankResponseWarning - ) - - def __get_section(self, section_name): - if section_name in self.__indexes: - return self.__txt[self.__indexes[section_name][0]: - self.__indexes[section_name][1]].strip() - - @property - def script(self): - return self.__get_section('script') - - @property - def console(self): - return self.__get_section('console') - - @property - def error_raw(self): - return self.__get_section('error') - - @property - def data(self): - return self.__get_section('data') - - @property - def errors(self): - result = [] - if self.error_raw is None: - return result - for err in error_regex.finditer(self.error_raw): - result.append(SimbadError(int(err.group('line')), - err.group('msg').replace('\n', ' '))) - return result - - @property - def nb_errors(self): - if self.error_raw is None: - return 0 - return len(self.errors) - - -class SimbadVOTableResult(SimbadResult): - """VOTable-type Simbad result""" - - def __init__(self, txt, verbose=False, pedantic=False): - self.__pedantic = pedantic - self.__table = None - if not verbose: - commons.suppress_vo_warnings() - super().__init__(txt, verbose=verbose) - - @property - def table(self): - if self.__table is None: - self.bytes = BytesIO(self.data.encode('utf8')) - tbl = votable.parse_single_table(self.bytes, verify='warn') - self.__table = tbl.to_table() - self.__table.convert_bytestring_to_unicode() - return self.__table - - -bibcode_regex = re.compile(r'query\s+bibcode\s+(wildcard)?\s+([\w]*)') - +@lru_cache(256) +def _cached_query_tap(tap, query: str, *, maxrec=10000): + """Cache version of query TAP. -class SimbadBibcodeResult(SimbadResult): - """Bibliography-type Simbad result""" - @property - def table(self): - splitter = bibcode_regex.search(self.script).group(2) - ref_list = [[splitter + ref] for ref in self.data.split(splitter)[1:]] - max_len = max(len(r[0]) for r in ref_list) - return Table(rows=ref_list, names=['References'], dtype=[f"U{max_len}"]) + This private function is called when query_tap is executed without an + ``uploads`` extra keyword argument. This is a work around because + `~astropy.table.Table` objects are not hashable and thus cannot + be used as arguments for a function decorated with lru_cache. + Parameters + ---------- + tap : `~pyvo.dal.TAPService` + The TAP service to query SIMBAD. + query : str + A string containing the query written in the + Astronomical Data Query Language (ADQL). + maxrec : int, optional + The number of records to be returned. Its maximum value is 2000000. -class SimbadObjectIDsResult(SimbadResult): - """Object identifier list Simbad result""" - @property - def table(self): - split_lines = self.data.splitlines() - ids = [[id.strip()] for id in split_lines] - max_len = max(map(len, split_lines)) - return Table(rows=ids, names=['ID'], dtype=[f"S{max_len}"]) + Returns + ------- + `~astropy.table.Table` + The response returned by SIMBAD. + """ + return tap.search(query, maxrec=maxrec).to_table() -class SimbadBaseQuery(BaseQuery): - """ - SimbadBaseQuery overloads the base query because we know that SIMBAD will - sometimes blacklist users for exceeding rate limits. This warning results - in a "connection refused" error (error 61) instead of a more typical "error - 8" that you would get from not having an internet connection at all. - """ +@dataclass(frozen=True) +class _Column: + """A class to define a column in a SIMBAD query.""" + table: str + name: str + alias: str = field(default=None) - def _request(self, *args, **kwargs): - try: - response = super()._request(*args, **kwargs) - except requests.exceptions.ConnectionError as ex: - if 'Errno 61' in str(ex): - extratext = ("\n\n" - "************************* \n" - "ASTROQUERY ADDED WARNING: \n" - "************************* \n" - "Error 61 received from SIMBAD server. " - "This may indicate that you have been " - "blacklisted for exceeding the query rate limit." - " See the astroquery SIMBAD documentation. " - "Blacklists are generally cleared after ~1 hour. " - "Please reconsider your approach, you may want " - "to use vectorized queries." - ) - ex.args[0].args = (ex.args[0].args[0] + extratext,) - raise ex - - if response.status_code == 403: - errmsg = ("Error 403: Forbidden. You may get this error if you " - "exceed the SIMBAD server's rate limits. Try again in " - "a few seconds or minutes.") - raise requests.exceptions.HTTPError(errmsg) - else: - response.raise_for_status() - return response +@dataclass(frozen=True) +class _Join: + """A class to define a join between two tables.""" + table: str + column_left: Any + column_right: Any + join_type: str = field(default="JOIN") -@async_to_sync -class SimbadClass(BaseVOQuery, SimbadBaseQuery): - """ - The class for querying the Simbad web service. +class SimbadClass(BaseVOQuery): + """The class for querying the SIMBAD web service. Note that SIMBAD suggests submitting no more than 6 queries per second; if you submit more than that, your IP may be temporarily blacklisted (https://simbad.cds.unistra.fr/guide/sim-url.htx) - """ SIMBAD_URL = 'https://' + conf.server + '/simbad/sim-script' - TIMEOUT = conf.timeout - WILDCARDS = { - '*': 'Any string of characters (including an empty one)', - '?': 'Any character (exactly one character)', - '[abc]': ('Exactly one character taken in the list. ' - 'Can also be defined by a range of characters: [A-Z]' - ), - '[^0-9]': 'Any (one) character not in the list.'} - - # query around not included since this is a subcase of query_region - _function_to_command = { - 'query_object_async': 'query id', - 'query_region_async': 'query coo', - 'query_catalog_async': 'query cat', - 'query_criteria_async': 'query sample', - 'query_bibcode_async': 'query bibcode', - 'query_bibobj_async': 'query bibobj' - } - - ROW_LIMIT = conf.row_limit - - # also find a way to fetch the votable fields table from - # - # tried something for this in this ipython nb - # - _VOTABLE_FIELDS = ['main_id', 'coordinates'] - - def __init__(self): + + def __init__(self, ROW_LIMIT=None): super().__init__() - self._VOTABLE_FIELDS = self._VOTABLE_FIELDS.copy() + # to create the TAPService self._server = conf.server self._tap = None + self._hardlimit = None + # attributes to construct ADQL queries + self._columns_in_output = None # a list of _Column + self.joins = [] # a list of _Join + self.criteria = [] # a list of strings + self.ROW_LIMIT = ROW_LIMIT + + @property + def ROW_LIMIT(self): + return self._ROW_LIMIT + + @ROW_LIMIT.setter + def ROW_LIMIT(self, ROW_LIMIT): + if ROW_LIMIT is None: + self._ROW_LIMIT = conf.row_limit + elif isinstance(ROW_LIMIT, int) and ROW_LIMIT >= -1: + self._ROW_LIMIT = ROW_LIMIT + else: + raise ValueError("ROW_LIMIT can be either -1 to set the limit to SIMBAD's " + "maximum capability, 0 to retrieve an empty table, " + "or a positive integer.") @property def server(self): - """The Simbad mirror to use.""" + """The SIMBAD mirror to use.""" return self._server @server.setter def server(self, server: str): - """Allows to switch server between Simbad mirrors. + """Allows to switch server between SIMBAD mirrors. Parameters ---------- @@ -341,12 +145,12 @@ def server(self, server: str): if server in conf.servers_list: self._server = server else: - raise ValueError(f"'{server}' does not correspond to a Simbad server, " + raise ValueError(f"'{server}' does not correspond to a SIMBAD server, " f"the two existing ones are {conf.servers_list}.") @property def tap(self): - """A `~pyvo.dal.TAPService` service for Simbad.""" + """A `~pyvo.dal.TAPService` service for SIMBAD.""" tap_url = f"https://{self.server}/simbad/sim-tap" # only creates a new tap instance if there are no existing one # or if the server property changed since the last getter call. @@ -355,316 +159,429 @@ def tap(self): return self._tap @property - @lru_cache(1) def hardlimit(self): - """The maximum number of lines for Simbad's output. + """The maximum number of lines for SIMBAD's output.""" + if self._hardlimit is None: + self._hardlimit = self.tap.hardlimit + return self._hardlimit + + @property + def columns_in_output(self): + """A list of _Column. + + They will be included in the output of the following methods: + + - `query_object`, + - `query_objects`, + - `query_region`, + - `query_catalog`, + - `query_bibobj`, + - `query_criteria`. - This property is cached to avoid calls to simbad's capability - webpage each time the getter is called. """ - # replace stack of property and lru_cache by functools.cache_property when - # astroquery drops python 3.7 support - return self.tap.hardlimit + if self._columns_in_output is None: + self._columns_in_output = [_Column("basic", item) + for item in conf.default_columns] + return self._columns_in_output - def list_wildcards(self): + @columns_in_output.setter + def columns_in_output(self, list_columns): + self._columns_in_output = list_columns + + @staticmethod + def list_wildcards(): """ - Displays the available wildcards that may be used in Simbad queries and + Displays the available wildcards that may be used in SIMBAD queries and their usage. Examples -------- >>> from astroquery.simbad import Simbad >>> Simbad.list_wildcards() - * : Any string of characters (including an empty one)... + *: Any string of characters (including an empty one) + ?: Any character (exactly one character) + [abc]: Exactly one character taken in the list. Can also be defined by a range of characters: [A-Z] + [^0-9]: Any (one) character not in the list. + """ + WILDCARDS = {'*': 'Any string of characters (including an empty one)', + '?': 'Any character (exactly one character)', + '[abc]': ('Exactly one character taken in the list. ' + 'Can also be defined by a range of characters: [A-Z]'), + '[^0-9]': 'Any (one) character not in the list.'} + print("\n".join(f"{k}: {v}" for k, v in WILDCARDS.items())) - [^0-9] : Any (one) character not in the list. + # --------------------------------- + # Methods to define SIMBAD's output + # --------------------------------- - ? : Any character (exactly one character) + def list_votable_fields(self): + """List all options to add columns to SIMBAD's output. - [abc] : Exactly one character taken in the list. - Can also be defined by a range of characters: [A-Z] - """ - print("\n\n".join(f"{k} : {v}" for k, v in self.WILDCARDS.items())) + They are of three types: - def list_votable_fields(self): - """ - Lists all the fields that can be fetched for a VOTable. + - "column of basic": a column of the basic table. There fields can also be explored with + `~astroquery.simbad.SimbadClass.list_columns`. + - "table": a table other than basic that has a declared direct join + - "bundle of basic columns": a pre-selected bundle of columns of basic. Ex: "parallax" will add all + columns relevant to parallax Examples -------- >>> from astroquery.simbad import Simbad - >>> Simbad.list_votable_fields() - --NOTES--... + >>> options = Simbad.list_votable_fields() # doctest: +REMOTE_DATA + >>> # to print only the available bundles of columns + >>> options[options["type"] == "bundle of basic columns"][["name", "description"]] # doctest: +REMOTE_DATA + + name description + object object + ------------- ---------------------------------------------------- + coordinates all fields related with coordinates + dim major and minor axis, angle and inclination + dimensions all fields related to object dimensions + morphtype all fields related to the morphological type + parallax all fields related to parallaxes + propermotions all fields related with the proper motions + sp all fields related with the spectral type + velocity all fields related with radial velocity and redshift """ - # display additional notes: - notes_file = get_pkg_data_filename( - os.path.join('data', 'votable_fields_notes.json')) - with open(notes_file, "r") as f: - notes = json.load(f) - print("--NOTES--\n") - for i, line in list(enumerate(notes)): - print("{lineno}. {msg}\n".format(lineno=i + 1, msg=line)) - - dict_file = get_pkg_data_filename( - os.path.join('data', 'votable_fields_dict.json')) - with open(dict_file, "r") as f: - fields_dict = json.load(f) - - print("Available VOTABLE fields:\n") - for field in sorted(fields_dict.keys()): - print(str(field)) - print("For more information on a field:\n" - "Simbad.get_field_description ('field_name') \n" - "Currently active VOTABLE fields:\n {0}" - .format(self._VOTABLE_FIELDS)) - - def get_field_description(self, field_name): + # get the tables with a simple link to basic + query_tables = """SELECT DISTINCT table_name AS name, tables.description + FROM TAP_SCHEMA.keys JOIN TAP_SCHEMA.key_columns USING (key_id) + JOIN TAP_SCHEMA.tables ON TAP_SCHEMA.keys.from_table = TAP_SCHEMA.tables.table_name + OR TAP_SCHEMA.keys.target_table = TAP_SCHEMA.tables.table_name + WHERE TAP_SCHEMA.tables.schema_name = 'public' + AND (from_table = 'basic' OR target_table = 'basic') + AND from_table != 'h_link' """ - Displays a description of the VOTable field. + tables = self.query_tap(query_tables) + tables["type"] = Column(["table"] * len(tables), dtype="object") + # the columns of basic are also valid options + basic_columns = self.list_columns("basic")[["column_name", "description"]] + basic_columns["column_name"].info.name = "name" + basic_columns["type"] = Column(["column of basic"] * len(basic_columns), dtype="object") + # get the bundles of columns from file + bundle_entries = {key: value for key, value in query_criteria_fields.items() + if value["type"] == "bundle"} + bundles = Table({"name": list(bundle_entries.keys()), + "description": [value["description"] for _, value in bundle_entries.items()], + "type": ["bundle of basic columns"] * len(bundle_entries)}, + dtype=["object", "object", "object"]) + # vstack the three types of options + return vstack([tables, basic_columns, bundles], metadata_conflicts="silent") + + def _get_bundle_columns(self, bundle_name): + """Get the list of columns in the preselected bundles. Parameters ---------- - field_name : str - the name of the field to describe. Must be one of those listed - by `list_votable_fields`. + bundle_name : str + The possible values can be listed with `~astroquery.simbad.SimbadClass.list_votable_fields` - Examples - -------- - >>> from astroquery.simbad import Simbad - >>> Simbad.get_field_description('main_id') - main identifier of an astronomical object. It is the same as id(1) - >>> Simbad.get_field_description('bibcodelist(y1-y2)') - number of references. The parameter is optional and limit the count to - the references between the years y1 and y2 + Returns + ------- + list[simbad._Column] + The list of columns corresponding to the selected bundle. """ - # first load the dictionary from json - dict_file = get_pkg_data_filename( - os.path.join('data', 'votable_fields_dict.json')) - with open(dict_file, "r") as f: - fields_dict = json.load(f) + basic_columns = set(map(str.casefold, set(self.list_columns("basic")["column_name"]))) - try: - print(fields_dict[field_name]) - except KeyError: - raise KeyError("No such field_name") + bundle_entries = {key: value for key, value in query_criteria_fields.items() + if value["type"] == "bundle"} - def get_votable_fields(self): - """ - Display votable fields + if bundle_name in bundle_entries: + bundle = bundle_entries[bundle_name] + columns = [_Column("basic", column) for column in basic_columns + if column.startswith(bundle["tap_startswith"])] + if "tap_column" in bundle: + columns = [_Column("basic", column) for column in bundle["tap_column"]] + columns + return columns - Examples - -------- - >>> from astroquery.simbad import Simbad - >>> Simbad.get_votable_fields() - ['main_id', 'coordinates'] - """ - return self._VOTABLE_FIELDS + def _add_table_to_output(self, table): + """Add all fields of a 'table' to the output of queries. - def add_votable_fields(self, *args): - """ - Sets fields to be fetched in the VOTable. Must be one of those listed - by `list_votable_fields`. + This handles the join from the table and the naming of the columns. + It only takes in account tables with an explicit link to basic. Other + cases should be added manually in an ADQL query string. Parameters ---------- - list of field_names + table : str + name of the table to add """ - dict_file = get_pkg_data_filename( - os.path.join('data', 'votable_fields_dict.json')) + table = table.casefold() - with open(dict_file, "r") as f: - fields_dict = {strip_field(k): v for k, v in json.load(f).items()} + if table == "basic": + self.columns_in_output.append(_Column(table, "*")) + return - for field in args: - sf = strip_field(field) - if sf not in fields_dict: - raise KeyError("{field}: no such field".format(field=field)) - else: - self._VOTABLE_FIELDS.append(field) + linked_to_basic = self.list_linked_tables("basic") + # list of accepted tables + linked_to_basic["from_table"] = [table.casefold() for table in linked_to_basic["from_table"]] + # the actual link to construct the join + link = linked_to_basic[linked_to_basic["from_table"] == table][0] + + if table not in linked_to_basic["from_table"] or table == "h_link": + raise ValueError(f"'{table}' has no explicit link to 'basic'. These cases require a custom ADQL " + "query to be written and called with 'SimbadClass.query_tap'.") + + columns = list(self.list_columns(table)["column_name"]) + columns = [column.casefold() for column in columns if column not in {"oidref", "oidbibref"}] + + # the alias is mandatory to be able to distinguish between duplicates like + # mesDistance.bibcode and mesDiameter.bibcode. + alias = [f'"{table}.{column}"' if not column.startswith(table) else None for column in columns] + + # modify the attributes here + self.columns_in_output += [_Column(table, column, alias) + for column, alias in zip(columns, alias)] + self.joins += [_Join(table, _Column("basic", link["target_column"]), + _Column(table, link["from_column"]))] + + def add_votable_fields(self, *args): + """Add columns to the output of a SIMBAD query. + + The list of possible arguments and their description for this method + can be printed with `~astroquery.simbad.SimbadClass.list_votable_fields`. + + The methods affected by this: + + - `query_object`, + - `query_objects`, + - `query_region`, + - `query_catalog`, + - `query_bibobj`, + - `query_criteria`. - def remove_votable_fields(self, *args, strip_params=False): - """ - Removes the specified field names from ``SimbadClass._VOTABLE_FIELDS`` Parameters ---------- - list of field_names to be removed - strip_params: bool, optional - If true, strip the specified keywords before removing them: - e.g., ra(foo) would remove ra(bar) if this is True + args : str + The arguments to be added. + + Examples + -------- + >>> from astroquery.simbad import Simbad + >>> simbad = Simbad() + >>> simbad.add_votable_fields('sp_type', 'sp_qual', 'sp_bibcode') # doctest: +REMOTE_DATA + >>> simbad.get_votable_fields() # doctest: +REMOTE_DATA + ['basic.main_id', 'basic.ra', 'basic.dec', 'basic.coo_err_maj', 'basic.coo_err_min', ... """ - if strip_params: - sargs = {strip_field(a) for a in args} - sfields = [strip_field(a) for a in self._VOTABLE_FIELDS] - else: - sargs = set(args) - sfields = self._VOTABLE_FIELDS - for field in sargs.difference(sfields): - warnings.warn("{field}: this field is not set".format(field=field)) + # the legacy way of adding fluxes is the only case-dependant option + args = list(args) + for arg in args: + if re.match(r"^flux.*\(.+\)$", arg): + warnings.warn("The notation 'flux(X)' is deprecated since 0.4.8. " + "See section on filters in " + "https://astroquery.readthedocs.io/en/latest/simbad/simbad_evolution.html " + "to see how it can be replaced.", DeprecationWarning, stacklevel=2) + flux_filter = re.findall(r"\((\w+)\)", arg)[0] + if len(flux_filter) == 1 and flux_filter.islower(): + flux_filter = flux_filter + "_" + self.joins.append(_Join("allfluxes", _Column("basic", "oid"), + _Column("allfluxes", "oidref"))) + self.columns_in_output.append(_Column("allfluxes", flux_filter)) + args.remove(arg) + + # casefold args + args = set(map(str.casefold, args)) + + # output options + output_options = self.list_votable_fields() + output_options["name"] = list(map(str.casefold, list(output_options["name"]))) + basic_columns = output_options[output_options["type"] == "column of basic"]["name"] + all_tables = output_options[output_options["type"] == "table"]["name"] + bundles = output_options[output_options["type"] == "bundle of basic columns"]["name"] + + # Add columns from basic + self.columns_in_output += [_Column("basic", column) for column in args if column in basic_columns] + + # Add tables + tables_to_add = [table for table in args if table in all_tables] + for table in tables_to_add: + self._add_table_to_output(table) + + # Add bundles + bundles_to_add = [bundle for bundle in args if bundle in bundles] + for bundle in bundles_to_add: + self.columns_in_output += self._get_bundle_columns(bundle) + + if args.issubset(set(output_options["name"])): + return - zipped_fields = zip(sfields, self._VOTABLE_FIELDS) - self._VOTABLE_FIELDS = [f for b, f in zipped_fields if b not in sargs] + # The other possible values are from the deprecated votable fields + remaining_arguments = args - set(output_options["name"]) + for votable_field in remaining_arguments: + if votable_field in query_criteria_fields: + field_data = query_criteria_fields[votable_field] + field_type = field_data["type"] + # some columns are still there but under a new name + if field_type == "alias": + tap_column = field_data["tap_column"] + self.columns_in_output.append(_Column("basic", tap_column)) + warning_message = (f"'{votable_field}' has been renamed '{tap_column}'. You'll see it " + "appearing with its new name in the output table") + warnings.warn(warning_message, DeprecationWarning, stacklevel=2) + # some tables are still there but under a new name + elif field_type == "alias table": + # add all columns of the desired measurement table + tap_table = field_data["tap_table"] + self._add_table_to_output(tap_table) + warning_message = (f"'{votable_field}' has been renamed '{tap_table}'. You will see " + "this new name in the result.") + warnings.warn(warning_message, DeprecationWarning, stacklevel=2) + # some tables have been moved to VizieR. This is broken since years + # but they were still appearing in list_votable_fields. + elif field_type == "historical measurement": + raise ValueError(f"'{votable_field}' is no longer a part of SIMBAD. It was moved " + "into a separate VizieR catalog. It is possible to query " + "it with the `astroquery.vizier` module.") + else: + # raise a ValueError on fields with arguments + _catch_deprecated_fields_with_arguments(votable_field) + # or a typo + close_match = get_close_matches(votable_field, set(output_options["name"])) + error_message = (f"'{votable_field}' is not one of the accepted options " + "which can be listed with 'list_votable_fields'.") + if close_match != []: + close_matches = "' or '".join(close_match) + error_message += f" Did you mean '{close_matches}'?" + raise ValueError(error_message) - # check if all fields are removed - if not self._VOTABLE_FIELDS: - warnings.warn("All fields have been removed. " - "Resetting to defaults.") - self.reset_votable_fields() + def get_votable_fields(self): + """Display votable fields.""" + return [f"{column.table}.{column.name}" for column in self.columns_in_output] def reset_votable_fields(self): - """ - resets VOTABLE_FIELDS to defaults - """ - self._VOTABLE_FIELDS = ['main_id', 'coordinates'] + """Reset the output of the query_*** methods to default. - def query_criteria(self, *args, **kwargs): - """ - Query SIMBAD based on any criteria. + They will be included in the output of the following methods: - Parameters - ---------- - args: - String arguments passed directly to SIMBAD's script - (e.g., 'region(box, GAL, 10.5 -10.5, 0.5d 0.5d)') - kwargs: - Keyword / value pairs passed to SIMBAD's script engine - (e.g., {'otype':'SNR'} will be rendered as otype=SNR) + - `query_object`, + - `query_objects`, + - `query_region`, + - `query_catalog`, + - `query_bibobj`, + - `query_criteria`. - Returns - ------- - table : `~astropy.table.Table` - Query results table """ - verbose = kwargs.pop('verbose', False) - result = self.query_criteria_async(*args, **kwargs) - return self._parse_result(result, SimbadVOTableResult, verbose=verbose) + self.columns_in_output = [_Column("basic", item) + for item in conf.default_columns] + self.joins = [] + self.criteria = [] - def query_criteria_async(self, *args, cache=True, **kwargs): - """ - Query SIMBAD based on any criteria. + def get_field_description(self, field_name): + """Displays a description of the VOTable field. - Parameters - ---------- - args: - String arguments passed directly to SIMBAD's script - (e.g., 'region(box, GAL, 10.5 -10.5, 0.5d 0.5d)') - kwargs: - Keyword / value pairs passed to SIMBAD's script engine - (e.g., {'otype':'SNR'} will be rendered as otype=SNR) - cache : bool - Defaults to True. If set overrides global caching behavior. - See :ref:`caching documentation `. + This can be replaced by the output of `~astroquery.simbad.SimbadClass.list_votable_fields`. + + Examples + -------- + >>> from astroquery.simbad import Simbad + >>> options = Simbad.list_votable_fields() # doctest: +REMOTE_DATA + >>> description_dimensions = options[options["name"] == "dimensions"]["description"] # doctest: +REMOTE_DATA + >>> description_dimensions.data.data[0] # doctest: +REMOTE_DATA + 'all fields related to object dimensions' - Returns - ------- - response : `requests.Response` - Response of the query from the server """ + options = self.list_votable_fields() + description = options[options["name"] == field_name]["description"] + return description.data.data[0] - request_payload = self._args_to_payload(caller='query_criteria_async', - *args, **kwargs) - response = self._request("POST", self.SIMBAD_URL, data=request_payload, - timeout=self.TIMEOUT, cache=cache) - return response + # ------------- + # Query methods + # ------------- - def query_object(self, object_name, *, wildcard=False, verbose=False, - get_query_payload=False): - """ - Queries Simbad for the given object and returns the result as a - `~astropy.table.Table`. Object names may also be specified with - wildcard. See examples below. + @deprecated_renamed_argument(["verbose"], new_name=[None], + since=['0.4.8'], relax=True) + def query_object(self, object_name, *, wildcard=False, + criteria=None, get_query_payload=False, verbose=False): + """Query SIMBAD for the given object. + + Object names may also be specified with wildcards. See examples below. Parameters ---------- object_name : str - name of object to be queried + name of object to be queried. wildcard : boolean, optional When it is set to `True` it implies that the object is specified - with wildcards. Defaults to `False`. + with wildcards. This parameter will render the query case-sensitive. + Defaults to `False`. + criteria : str + Criteria to be applied to the query. These should be written in the ADQL + syntax in a single string. See example. get_query_payload : bool, optional - When set to `True` the method returns the HTTP request parameters. + When set to `True` the method returns the HTTP request parameters without + querying SIMBAD. The ADQL string is in the 'QUERY' key of the payload. Defaults to `False`. Returns ------- table : `~astropy.table.Table` - Query results table - """ - response = self.query_object_async(object_name, wildcard=wildcard, - get_query_payload=get_query_payload) - if get_query_payload: - return response + The result of the query to SIMBAD. - return self._parse_result(response, SimbadVOTableResult, - verbose=verbose) + Examples + -------- - def query_object_async(self, object_name, *, wildcard=False, cache=True, - get_query_payload=False): - """ - Serves the same function as `query_object`, but - only collects the response from the Simbad server and returns. + Get the dimensions of a specific galaxy - Parameters - ---------- - object_name : str - name of object to be queried - wildcard : boolean, optional - When it is set to `True` it implies that the object is specified - with wildcards. Defaults to `False`. - get_query_payload : bool, optional - When set to `True` the method returns the HTTP request parameters. - Defaults to `False`. - cache : bool - Defaults to True. If set overrides global caching behavior. - See :ref:`caching documentation `. + >>> from astroquery.simbad import Simbad + >>> simbad = Simbad() + >>> simbad.add_votable_fields("dim") # doctest: +REMOTE_DATA + >>> result = simbad.query_object("m101") # doctest: +REMOTE_DATA + >>> result["main_id", "ra", "dec", "galdim_majaxis", "galdim_minaxis", "galdim_bibcode"] # doctest: +REMOTE_DATA +
+ main_id ra dec ... galdim_minaxis galdim_bibcode + deg deg ... arcmin + object float64 float64 ... float32 object + ------- ------------------ -------- ... -------------- ------------------- + M 101 210.80242916666668 54.34875 ... 20.89 2003A&A...412...45P + + Get 5 NGC objects that are clusters of stars - Returns - ------- - response : `requests.Response` - Response of the query from the server + >>> from astroquery.simbad import Simbad + >>> simbad = Simbad() + >>> simbad.ROW_LIMIT = 5 + >>> ngc_clusters = simbad.query_object("NGC*", wildcard=True, criteria="otype='Cl*..'") # doctest: +REMOTE_DATA + >>> ngc_clusters # doctest: +REMOTE_DATA +IGNORE_OUTPUT +
+ main_id ra ... coo_bibcode matched_id + deg ... + object float64 ... object object + --------- ----------------- ... ------------------- ---------- + NGC 2024 85.42916666666667 ... 2003A&A...397..177B NGC 2024 + NGC 1826 76.39174999999999 ... 2011AJ....142...48W NGC 1826 + NGC 2121 87.05495833333332 ... 2011AJ....142...48W NGC 2121 + NGC 2019 82.98533333333333 ... 1999AcA....49..521P NGC 2019 + NGC 1777 73.95 ... 2008MNRAS.389..678B NGC 1777 """ - request_payload = self._args_to_payload(object_name, wildcard=wildcard, - caller='query_object_async') + top, columns, joins, instance_criteria = self._get_query_parameters() - if get_query_payload: - return request_payload + columns.append(_Column("ident", "id", "matched_id")) - response = self._request("POST", self.SIMBAD_URL, data=request_payload, - timeout=self.TIMEOUT, cache=cache) - return response + joins.append(_Join("ident", _Column("basic", "oid"), _Column("ident", "oidref"))) - def query_objects(self, object_names, *, wildcard=False, verbose=False, - get_query_payload=False): - """ - Queries Simbad for the specified list of objects and returns the - results as a `~astropy.table.Table`. Object names may be specified - with wildcards if desired. + if wildcard: + instance_criteria.append(rf" regexp(id, '{_wildcard_to_regexp(object_name)}') = 1") + else: + instance_criteria.append(rf"id = '{_adql_parameter(object_name)}'") - Parameters - ---------- - object_names : sequence of strs - names of objects to be queried - wildcard : boolean, optional - When `True`, the names may have wildcards in them. Defaults to - `False`. - get_query_payload : bool, optional - When set to `True` the method returns the HTTP request parameters. - Defaults to `False`. + if criteria: + instance_criteria.append(f"({criteria})") - Returns - ------- - table : `~astropy.table.Table` - Query results table - """ - return self.query_object('\n'.join(object_names), wildcard=wildcard, - get_query_payload=get_query_payload) + return self._query(top, columns, joins, instance_criteria, + get_query_payload=get_query_payload) - def query_objects_async(self, object_names, *, wildcard=False, cache=True, - get_query_payload=False): - """ - Same as `query_objects`, but only collects the response from the - Simbad server and returns. + @deprecated_renamed_argument(["verbose", "cache"], new_name=[None, None], + since=['0.4.8', '0.4.8'], relax=True) + def query_objects(self, object_names, *, wildcard=False, criteria=None, + get_query_payload=False, verbose=False, cache=False): + """Query SIMBAD for the specified list of objects. + + Object names may be specified with wildcards. + If one of the ``object_names`` is not found in SIMBAD, the corresponding line is + returned empty in the output (see ``Giga Cluster`` in the example). + In the output, the column ``user_specified_id`` is the input object name. Parameters ---------- @@ -673,181 +590,256 @@ def query_objects_async(self, object_names, *, wildcard=False, cache=True, wildcard : boolean, optional When `True`, the names may have wildcards in them. Defaults to `False`. + criteria : str + Criteria to be applied to the query. These should be written in the ADQL + syntax in a single string. See example. get_query_payload : bool, optional - When set to `True` the method returns the HTTP request parameters. + When set to `True` the method returns the HTTP request parameters without + querying SIMBAD. The ADQL string is in the 'QUERY' key of the payload. Defaults to `False`. - cache : bool - Defaults to True. If set overrides global caching behavior. - See :ref:`caching documentation `. + cache : Deprecated since 0.4.8. The cache is now automatically emptied at the + end of the python session. It can also be emptied manually with + `~astroquery.simbad.SimbadClass.clear_cache` but cannot be deactivated. Returns ------- - response : `requests.Response` - Response of the query from the server - """ - return self.query_object_async('\n'.join(object_names), - wildcard=wildcard, cache=cache, - get_query_payload=get_query_payload) + table : `~astropy.table.Table` + The result of the query to SIMBAD. - def query_region_async(self, coordinates, radius=2*u.arcmin, *, - equinox=2000.0, epoch='J2000', cache=True, - get_query_payload=False): + Examples + -------- + >>> from astroquery.simbad import Simbad + >>> clusters = Simbad.query_objects(["Boss Great Wall", "Great Attractor", + ... "Giga Cluster", "Coma Supercluster"]) # doctest: +REMOTE_DATA + >>> clusters[["main_id", "ra", "dec", "user_specified_id"]] # doctest: +REMOTE_DATA +
+ main_id ra dec user_specified_id + deg deg + object float64 float64 object + ---------------------- ------- ------- ----------------- + NAME Boss Great Wall 163.0 52.0 Boss Great Wall + NAME Great Attractor 158.0 -46.0 Great Attractor + -- -- Giga Cluster + NAME Coma Supercluster 170.75 23.9 Coma Supercluster """ - Serves the same function as `query_region`, but - only collects the response from the Simbad server and returns. + top, columns, joins, instance_criteria = self._get_query_parameters() + + if criteria: + instance_criteria.append(f"({criteria})") + + if wildcard: + columns.append(_Column("ident", "id", "matched_id")) + joins += [_Join("ident", _Column("basic", "oid"), _Column("ident", "oidref"))] + list_criteria = [f"regexp(id, '{_wildcard_to_regexp(object_name)}') = 1" + for object_name in object_names] + instance_criteria += [f'({" OR ".join(list_criteria)})'] + + return self._query(top, columns, joins, instance_criteria, + get_query_payload=get_query_payload) + + # There is a faster way to do the query if there is no wildcard: the first table + # can be the uploaded one and we use a LEFT JOIN for the other ones. + upload = Table({"user_specified_id": object_names, + "object_number_id": list(range(1, len(object_names) + 1))}) + upload_name = "TAP_UPLOAD.script_infos" + columns.append(_Column(upload_name, "*")) + + left_joins = [_Join("ident", _Column(upload_name, "user_specified_id"), + _Column("ident", "id"), "LEFT JOIN"), + _Join("basic", _Column("basic", "oid"), + _Column("ident", "oidref"), "LEFT JOIN")] + for join in joins: + left_joins.append(_Join(join.table, join.column_left, + join.column_right, "LEFT JOIN")) + return self._query(top, columns, left_joins, instance_criteria, + from_table=upload_name, + get_query_payload=get_query_payload, + script_infos=upload) + + @deprecated_renamed_argument(["equinox", "epoch", "cache"], + new_name=[None]*3, + alternative=["astropy.coordinates.SkyCoord"]*3, + since=['0.4.8']*3, relax=True) + def query_region(self, coordinates, radius=2*u.arcmin, *, + criteria=None, get_query_payload=False, + equinox=None, epoch=None, cache=None): + """Query SIMBAD in a cone around the specified coordinates. Parameters ---------- coordinates : str or `astropy.coordinates` object - the identifier or coordinates around which to query. + The identifier or coordinates around which to query. radius : str or `~astropy.units.Quantity` the radius of the region. Defaults to 2 arcmin. - equinox : float, optional - the equinox of the coordinates. If missing set to - default 2000.0. - epoch : str, optional - the epoch of the input coordinates. Must be specified as - [J|B] . If missing, set to default J2000. + criteria : str + Criteria to be applied to the query. These should be written in the ADQL + syntax in a single string. get_query_payload : bool, optional - When set to `True` the method returns the HTTP request parameters. + When set to `True` the method returns the HTTP request parameters without + querying SIMBAD. The ADQL string is in the 'QUERY' key of the payload. Defaults to `False`. - cache : bool - Defaults to True. If set overrides global caching behavior. - See :ref:`caching documentation `. + cache : Deprecated since 0.4.8. The cache is now automatically emptied at the + end of the python session. It can also be emptied manually with + `~astroquery.simbad.SimbadClass.clear_cache` but cannot be deactivated. Returns ------- - response : `requests.Response` - Response of the query from the server. - """ + table : `~astropy.table.Table` + The result of the query to SIMBAD. - if radius is None: - # this message is specifically for deprecated use of 'None' to mean 'Default' - raise ValueError("Radius must be specified as an angle-equivalent quantity, not None") + Examples + -------- - equinox = validate_equinox(equinox) - epoch = validate_epoch(epoch) + Look for large galaxies in two cones - base_query_str = "query coo {ra} {dec} radius={rad} frame={frame} equi={equinox}" + >>> from astroquery.simbad import Simbad + >>> from astropy.coordinates import SkyCoord + >>> simbad = Simbad() + >>> simbad.ROW_LIMIT = 5 + >>> simbad.add_votable_fields("otype") # doctest: +REMOTE_DATA + >>> coordinates = SkyCoord([SkyCoord(186.6, 12.7, unit=("deg", "deg")), + ... SkyCoord(170.75, 23.9, unit=("deg", "deg"))]) + >>> result = simbad.query_region(coordinates, radius="2d5m", + ... criteria="otype = 'Galaxy..' AND galdim_majaxis>8") # doctest: +REMOTE_DATA + >>> result.sort("main_id") # doctest: +REMOTE_DATA + >>> result["main_id", "otype"] # doctest: +REMOTE_DATA +
+ main_id otype + object object + ------------ ------ + LEDA 40577 GiG + LEDA 41362 GiC + M 86 GiG + M 87 AGN + NGC 4438 LIN - header = self._get_query_header() - footer = self._get_query_footer() + Notes + ----- + It is very inefficient to call this within a loop. Creating an `~astropy.coordinates.SkyCoord` + object with a list of coordinates will be way faster. - ra, dec, frame = _parse_coordinates(coordinates) + """ + if radius is None: + # this message is specifically for deprecated use of 'None' to mean 'Default' + raise TypeError("The cone radius must be specified as an angle-equivalent quantity") - # handle the vector case - if isinstance(ra, list): - if len(ra) > 10000: - warnings.warn("For very large queries, you may receive a " - "timeout error. SIMBAD suggests splitting " - "queries with >10000 entries into multiple " - "threads", LargeQueryWarning) + center = commons.parse_coordinates(coordinates) + center = center.transform_to("icrs") - if len(set(frame)) > 1: - raise ValueError("Coordinates have different frames") - else: - frame = frame[0] + top, columns, joins, instance_criteria = self._get_query_parameters() + + if center.isscalar: + radius = coord.Angle(radius) + instance_criteria.append(f"CONTAINS(POINT('ICRS', basic.ra, basic.dec), " + f"CIRCLE('ICRS', {center.ra.deg}, {center.dec.deg}, " + f"{radius.to(u.deg).value})) = 1") + + else: + if len(center) > 10000: + warnings.warn( + "For very large queries, you may receive a timeout error. SIMBAD suggests" + " splitting queries with >10000 entries into multiple threads", + LargeQueryWarning, stacklevel=2 + ) # `radius` as `str` is iterable, but contains only one value. if isiterable(radius) and not isinstance(radius, str): - if len(radius) != len(ra): - raise ValueError("Mismatch between radii and coordinates") + if len(radius) != len(center): + raise ValueError(f"Mismatch between radii of length {len(radius)}" + f" and center coordinates of length {len(center)}.") + radius = [coord.Angle(item) for item in radius] else: - radius = [_parse_radius(radius)] * len(ra) + radius = [coord.Angle(radius)] * len(center) - query_str = "\n".join(base_query_str - .format(ra=ra_, dec=dec_, rad=rad_, - frame=frame, equinox=equinox) - for ra_, dec_, rad_ in zip(ra, dec, radius)) + cone_criteria = [(f"CONTAINS(POINT('ICRS', basic.ra, basic.dec), CIRCLE('ICRS', " + f"{center.ra.deg}, {center.dec.deg}, {radius.to(u.deg).value})) = 1 ") + for center, radius in zip(center, radius)] - else: - radius = _parse_radius(radius) - query_str = base_query_str.format(ra=ra, dec=dec, frame=frame, - rad=radius, equinox=equinox) + cone_criteria = f" ({'OR '.join(cone_criteria)})" + instance_criteria.append(cone_criteria) - request_payload = {'script': "\n".join([header, query_str, footer])} + if criteria: + instance_criteria.append(f"({criteria})") - if get_query_payload: - return request_payload + return self._query(top, columns, joins, instance_criteria, + get_query_payload=get_query_payload) - response = self._request("POST", self.SIMBAD_URL, data=request_payload, - timeout=self.TIMEOUT, cache=cache) - return response - - def query_catalog(self, catalog, *, verbose=False, cache=True, - get_query_payload=False): - """ - Queries a whole catalog. - - Results may be very large -number of rows - should be controlled by configuring `SimbadClass.ROW_LIMIT`. + @deprecated_renamed_argument(["verbose", "cache"], new_name=[None, None], + since=['0.4.8', '0.4.8'], relax=True) + def query_catalog(self, catalog, *, criteria=None, get_query_payload=False, + verbose=False, cache=True): + """Query a whole catalog. Parameters ---------- catalog : str - the name of the catalog. + The name of the catalog. This is case-dependant. + criteria : str + Criteria to be applied to the query. These should be written in the ADQL + syntax in a single string. See example. get_query_payload : bool, optional - When set to `True` the method returns the HTTP request parameters. + When set to `True` the method returns the HTTP request parameters without + querying SIMBAD. The ADQL string is in the 'QUERY' key of the payload. Defaults to `False`. - cache : bool - Defaults to True. If set overrides global caching behavior. - See :ref:`caching documentation `. + cache : Deprecated since 0.4.8. The cache is now automatically emptied at the + end of the python session. It can also be emptied manually with + `~astroquery.simbad.SimbadClass.clear_cache` but cannot be deactivated. Returns ------- table : `~astropy.table.Table` - Query results table - """ - response = self.query_catalog_async(catalog, cache=cache, - get_query_payload=get_query_payload) - if get_query_payload: - return response + The result of the query to SIMBAD. + + Examples + -------- + >>> from astroquery.simbad import Simbad + >>> simbad = Simbad() + >>> simbad.ROW_LIMIT = 5 + >>> simbad.query_catalog("GSC", criteria="pmra > 50 and pmra < 100") # doctest: +REMOTE_DATA +
+ main_id ra ... coo_bibcode catalog_id + deg ... + object float64 ... object object + --------------- --------------- ... ------------------- --------------- + HD 26053 61.84326890626 ... 2020yCat.1350....0G GSC 04725-00973 + TYC 8454-1081-1 345.11163189562 ... 2020yCat.1350....0G GSC 08454-01081 + HD 10158 24.86286094434 ... 2020yCat.1350....0G GSC 00624-00340 + CD-22 1862 73.17988827324 ... 2020yCat.1350....0G GSC 05911-00222 + BD+02 4434 327.90220788982 ... 2020yCat.1350....0G GSC 00548-00194 - return self._parse_result(response, SimbadVOTableResult, - verbose=verbose) + Notes + ----- + Catalogs can be very large. Configuring ``SimbadClass.ROW_LIMIT`` allows to + restrict the output. - def query_catalog_async(self, catalog, *, cache=True, get_query_payload=False): """ - Serves the same function as `query_catalog`, but - only collects the response from the Simbad server and returns. + top, columns, joins, instance_criteria = self._get_query_parameters() - Parameters - ---------- - catalog : str - the name of the catalog. - get_query_payload : bool, optional - When set to `True` the method returns the HTTP request parameters. - Defaults to `False`. - cache : bool - Defaults to True. If set overrides global caching behavior. - See :ref:`caching documentation `. + columns.append(_Column("ident", "id", "catalog_id")) - Returns - ------- - response : `requests.Response` - Response of the query from the server. + joins += [_Join("ident", _Column("basic", "oid"), _Column("ident", "oidref"))] - """ - request_payload = self._args_to_payload(catalog, - caller='query_catalog_async') - if get_query_payload: - return request_payload + instance_criteria.append(fr"id LIKE '{catalog} %'") + if criteria: + instance_criteria.append(f"({criteria})") - response = self._request("POST", self.SIMBAD_URL, data=request_payload, - timeout=self.TIMEOUT, cache=cache) - return response + return self._query(top, columns, joins, instance_criteria, + get_query_payload=get_query_payload) - def query_bibobj(self, bibcode, *, verbose=False, get_query_payload=False): - """ - Query all the objects that are contained in the article specified by - the bibcode, and return results as a `~astropy.table.Table`. + @deprecated_renamed_argument(["verbose"], new_name=[None], + since=['0.4.8'], relax=True) + def query_bibobj(self, bibcode, *, criteria=None, + get_query_payload=False, + verbose=False): + """Query all the objects mentioned in an article. Parameters ---------- bibcode : str the bibcode of the article get_query_payload : bool, optional - When set to `True` the method returns the HTTP request parameters. + When set to `True` the method returns the HTTP request parameters without + querying SIMBAD. The ADQL string is in the 'QUERY' key of the payload. Defaults to `False`. Returns @@ -855,189 +847,251 @@ def query_bibobj(self, bibcode, *, verbose=False, get_query_payload=False): table : `~astropy.table.Table` Query results table """ - response = self.query_bibobj_async(bibcode, - get_query_payload=get_query_payload) - if get_query_payload: - return response + top, columns, joins, instance_criteria = self._get_query_parameters() - return self._parse_result(response, SimbadVOTableResult, - verbose=verbose) + joins += [_Join("has_ref", _Column("basic", "oid"), _Column("has_ref", "oidref")), + _Join("ref", _Column("has_ref", "oidbibref"), _Column("ref", "oidbib"))] - def query_bibobj_async(self, bibcode, *, cache=True, get_query_payload=False): - """ - Serves the same function as `query_bibobj`, but only collects the - response from the Simbad server and returns. + columns += [_Column("ref", "bibcode"), + _Column("has_ref", "obj_freq")] - Parameters - ---------- - bibcode : str - the bibcode of the article - get_query_payload : bool, optional - When set to `True` the method returns the HTTP request parameters. - Defaults to `False`. - cache : bool - Defaults to True. If set overrides global caching behavior. - See :ref:`caching documentation `. + instance_criteria.append(f"bibcode = '{_adql_parameter(bibcode)}'") + if criteria: + instance_criteria.append(f"({criteria})") - Returns - ------- - response : `requests.Response` - Response of the query from the server. + return self._query(top, columns, joins, instance_criteria, + get_query_payload=get_query_payload) - """ - request_payload = self._args_to_payload(bibcode, caller='query_bibobj_async') + @deprecated_renamed_argument(["verbose", "cache"], new_name=[None, None], + since=['0.4.8', '0.4.8'], relax=True) + def query_bibcode(self, bibcode, *, wildcard=False, + abstract=False, get_query_payload=False, criteria=None, + verbose=None, cache=None, ): + """Query the references corresponding to a given bibcode. - if get_query_payload: - return request_payload - - response = self._request("POST", self.SIMBAD_URL, data=request_payload, - timeout=self.TIMEOUT, cache=cache) - return response - - def query_bibcode(self, bibcode, *, wildcard=False, verbose=False, - cache=True, get_query_payload=False): - """ - Queries the references corresponding to a given bibcode, and returns - the results in a `~astropy.table.Table`. Wildcards may be used to - specify bibcodes. + Wildcards may be used to specify bibcodes. Parameters ---------- bibcode : str - the bibcode of the article - wildcard : boolean, optional + The bibcode of the article to be queried + wildcard : bool, defaults to False When it is set to `True` it implies that the object is specified - with wildcards. Defaults to `False`. + with wildcards. + abstract : bool, defaults to False + When this is set to `True`, the abstract of the article is also included + to the result. + criteria : str + Criteria to be applied to the query. These should be written in the ADQL + syntax in a single string. See example. get_query_payload : bool, optional - When set to `True` the method returns the HTTP request parameters. + When set to `True` the method returns the HTTP request parameters without + querying SIMBAD. The ADQL string is in the 'QUERY' key of the payload. Defaults to `False`. - cache : bool - Defaults to True. If set overrides global caching behavior. - See :ref:`caching documentation `. + cache : Deprecated since 0.4.8. The cache is now automatically emptied at the + end of the python session. It can also be emptied manually with + `~astroquery.simbad.SimbadClass.clear_cache` but cannot be deactivated. Returns ------- table : `~astropy.table.Table` Query results table + Examples + -------- + >>> from astroquery.simbad import Simbad + >>> simbad = Simbad() + >>> simbad.ROW_LIMIT = 5 + >>> simbad.query_bibcode("2016PhRvL.*", wildcard=True, + ... criteria="title LIKE '%gravitational wave%coalescence.'") # doctest: +REMOTE_DATA +
+ bibcode doi journal ... volume year + object object object ... int32 int16 + ------------------- ------------------------------ ------- ... ------ ----- + 2016PhRvL.116x1103A 10.1103/PhysRevLett.116.241103 PhRvL ... 116 2016 """ - response = self.query_bibcode_async(bibcode, wildcard=wildcard, - cache=cache, - get_query_payload=get_query_payload) - - if get_query_payload: - return response - - return self._parse_result(response, SimbadBibcodeResult, - verbose=verbose) - - def query_bibcode_async(self, bibcode, *, wildcard=False, cache=True, - get_query_payload=False): - """ - Serves the same function as `query_bibcode`, but - only collects the response from the Simbad server and returns. - - Parameters - ---------- - bibcode : str - the bibcode of the article - wildcard : boolean, optional - When it is set to `True` it implies that the object is specified - with wildcards. Defaults to `False`. - get_query_payload : bool, optional - When set to `True` the method returns the HTTP request parameters. - Defaults to `False`. - cache : bool - Defaults to True. If set overrides global caching behavior. - See :ref:`caching documentation `. + ref_columns = ["bibcode", "doi", "journal", "nbobject", "page", "last_page", + "title", "volume", "year"] + # abstract option + if abstract: + ref_columns.append("abstract") + ref_columns = str(ref_columns).replace("'", '"')[1:-1] + + # take row limit + if self.ROW_LIMIT != -1: + query = f"SELECT TOP {self.ROW_LIMIT} {ref_columns} FROM ref WHERE " + else: + query = f"SELECT {ref_columns} FROM ref WHERE " - Returns - ------- - response : `requests.Response` - Response of the query from the server. + if wildcard: + query += f"regexp(lowercase(bibcode), '{_wildcard_to_regexp(bibcode.casefold())}') = 1" + else: + query += f"bibcode = '{_adql_parameter(bibcode)}'" - """ - request_payload = self._args_to_payload( - bibcode, wildcard=wildcard, - caller='query_bibcode_async', get_raw=True) + if criteria: + query += f" AND {criteria}" - if get_query_payload: - return request_payload + query += " ORDER BY bibcode" - response = self._request("POST", self.SIMBAD_URL, cache=cache, - data=request_payload, timeout=self.TIMEOUT) + return self.query_tap(query, get_query_payload=get_query_payload) - return response + @deprecated_renamed_argument(["verbose", "cache"], new_name=[None, None], + since=['0.4.8', '0.4.8'], relax=True) + def query_objectids(self, object_name, *, verbose=None, cache=None, + get_query_payload=False, criteria=None): + """Query SIMBAD with an object name. - def query_objectids(self, object_name, *, verbose=False, cache=True, - get_query_payload=False): - """ - Query Simbad with an object name, and return a table of all - names associated with that object in a `~astropy.table.Table`. + This returns a table of all names associated with that object. Parameters ---------- object_name : str name of object to be queried + criteria : str + an additional criteria to constrain the result. As the output of this + method has only one column, these criteria can only be imposed on + the column ``ident.id``. get_query_payload : bool, optional - When set to `True` the method returns the HTTP request parameters. + When set to `True` the method returns the HTTP request parameters without + querying SIMBAD. The ADQL string is in the 'QUERY' key of the payload. Defaults to `False`. - cache : bool - Defaults to True. If set overrides global caching behavior. - See :ref:`caching documentation `. + cache : Deprecated since 0.4.8. The cache is now automatically emptied at the + end of the python session. It can also be emptied manually with + `~astroquery.simbad.SimbadClass.clear_cache` but cannot be deactivated. Returns ------- table : `~astropy.table.Table` - Query results table + The result of the query to SIMBAD. - """ - response = self.query_objectids_async(object_name, cache=cache, - get_query_payload=get_query_payload) - if get_query_payload: - return response + Examples + -------- + Get all the names of the Bubble Nebula - return self._parse_result(response, SimbadObjectIDsResult, - verbose=verbose) + >>> from astroquery.simbad import Simbad + >>> Simbad.query_objectids("bubble nebula") # doctest: +REMOTE_DATA +
+ id + object + ------------------------------ + [ABB2014] WISE G112.212+00.229 + LBN 548 + NGC 7635 + SH 2-162 + [KC97c] G112.2+00.2b + [L89b] 112.237+00.226 + GRS G112.22 +00.22 + NAME Bubble Nebula + + Get the NGC name of M101 - def query_objectids_async(self, object_name, *, cache=True, - get_query_payload=False): + >>> from astroquery.simbad import Simbad + >>> Simbad.query_objectids("m101", criteria="ident.id LIKE 'NGC%'") # doctest: +REMOTE_DATA +
+ id + object + --------- + NGC 5457 """ - Serves the same function as `query_objectids`, but - only collects the response from the Simbad server and returns. + query = ("SELECT ident.id FROM ident AS id_typed JOIN ident USING(oidref)" + f"WHERE id_typed.id = '{_adql_parameter(object_name)}'") + if criteria is not None: + query += f" AND {criteria}" + return self.query_tap(query, get_query_payload=get_query_payload) + + @deprecated(since="v0.4.8", + message=("'query_criteria' is deprecated. It uses the former sim-script " + "(SIMBAD specific) syntax " + "(see https://simbad.cds.unistra.fr/simbad/sim-fsam). " + "Possible replacements are the 'criteria' argument in the other " + "query methods or custom 'query_tap' queries. " + "These two replacements use the standard ADQL syntax.")) + def query_criteria(self, *args, get_query_payload=False, **kwargs): + """Query SIMBAD based on any criteria [deprecated]. + + This method is deprecated as it uses the former SIMBAD-specific sim-script syntax. + There are two possible replacements that have been added with astroquery v0.4.8 + and that use the standard ADQL syntax. See the examples section. Parameters ---------- - object_name : str - name of object to be queried - cache : bool - Defaults to True. If set overrides global caching behavior. - See :ref:`caching documentation `. + args: + String arguments passed directly to SIMBAD's script + (e.g., 'region(box, GAL, 10.5 -10.5, 0.5d 0.5d)') + get_query_payload : bool, optional + When set to `True` the method returns the HTTP request parameters without + querying SIMBAD. The ADQL string is in the 'QUERY' key of the payload. + Defaults to `False`. + kwargs: + Keyword / value pairs passed to SIMBAD's script engine + (e.g., {'otype'='SNR'} will be rendered as otype=SNR) Returns ------- - response : `requests.Response` - Response of the query from the server. - - """ - request_payload = dict(script="\n".join(('format object "%IDLIST"', - 'query id %s' % object_name))) + table : `~astropy.table.Table` + Query results table - if get_query_payload: - return request_payload + Examples + -------- - response = self._request("POST", self.SIMBAD_URL, data=request_payload, - timeout=self.TIMEOUT, cache=cache) + Can be replaced by the ``criteria`` argument that was added in the + other query_*** methods - return response + >>> from astroquery.simbad import Simbad + >>> Simbad(ROW_LIMIT=5).query_region('M1', '2d', criteria="otype='G..'") # doctest: +REMOTE_DATA +IGNORE_OUTPUT +
+ main_id ra ... coo_wavelength coo_bibcode + deg ... + object float64 ... str1 object + ------------ ----------------- ... -------------- ------------------- + LEDA 136099 85.48166666666667 ... 1996A&AS..117....1S + LEDA 136047 83.66958333333332 ... 1996A&AS..117....1S + LEDA 136057 84.64499999999998 ... 1996A&AS..117....1S + LEDA 1630996 83.99208333333333 ... O 2003A&A...412...45P + 2MFGC 4574 84.37534166666669 ... I 2006AJ....131.1163S + + Or by custom-written ADQL queries - def list_tables(self, *, get_adql=False): - """The names and descriptions of the tables in SIMBAD. + >>> from astroquery.simbad import Simbad + >>> Simbad.query_tap("SELECT TOP 5 main_id, sp_type" + ... " FROM basic WHERE sp_type < 'F3'") # doctest: +REMOTE_DATA +IGNORE_OUTPUT +
+ main_id sp_type + object object + ----------- ------- + HD 24033B (A) + HD 70218B (A) + HD 128284B (A/F) + CD-34 5319 (A/F) + HD 80593 (A0)V + """ + top, columns, joins, instance_criteria = self._get_query_parameters() + list_kwargs = [f"{key}='{argument}'" for key, argument in kwargs.items()] + added_criteria = f"({CriteriaTranslator.parse(' & '.join(list(list(args) + list_kwargs)))})" + instance_criteria.append(added_criteria) + if "otypes." in added_criteria: + joins.append(_Join("otypes", _Column("basic", "oid"), + _Column("otypes", "oidref"))) + if "allfluxes." in added_criteria: + joins.append(_Join("allfluxes", _Column("basic", "oid"), + _Column("allfluxes", "oidref"))) + return self._query(top, columns, joins, instance_criteria, + get_query_payload=get_query_payload) + + @deprecated_renamed_argument("get_adql", new_name="get_query_payload", + since='0.4.8', relax=True) + def list_tables(self, *, get_query_payload=False): + """List the names and descriptions of the tables in SIMBAD. Parameters ---------- + get_query_payload : bool, optional + When set to `True` the method returns the HTTP request parameters without + querying SIMBAD. The ADQL string is in the 'QUERY' key of the payload. + Defaults to `False`. get_adql : bool, optional - Returns the ADQL string instead of querying SIMBAD. + Deprecated since '0.4.8'. This is replaced by get_query_payload that contain + more information than just the ADQL string Returns ------- @@ -1046,27 +1100,36 @@ def list_tables(self, *, get_adql=False): query = ("SELECT table_name, description" " FROM TAP_SCHEMA.tables" " WHERE schema_name = 'public'") - if get_adql: - return query - return self.query_tap(query) + return self.query_tap(query, get_query_payload=get_query_payload) - def list_columns(self, *tables: str, keyword=None, get_adql=False): - """ - Get the list of SIMBAD columns. + @deprecated_renamed_argument("get_adql", new_name="get_query_payload", + since='0.4.8', relax=True) + def list_columns(self, *tables: str, keyword=None, get_query_payload=False): + """Get the list of SIMBAD columns. Add tables names to restrict to some tables. Call the function without any parameter to get all columns names from all tables. The keyword argument - looks for columns in the selected Simbad tables that contain the + looks for columns in the selected SIMBAD tables that contain the given keyword. The keyword search is not case-sensitive. Parameters ---------- *tables : str, optional Add tables names as strings to restrict to these tables columns. + This is not case-sensitive. keyword : str, optional A keyword to look for in column names, table names, or descriptions. + get_query_payload : bool, optional + When set to `True` the method returns the HTTP request parameters without + querying SIMBAD. The ADQL string is in the 'QUERY' key of the payload. + Defaults to `False`. get_adql : bool, optional - Returns the ADQL string instead of querying SIMBAD. + Deprecated since '0.4.8'. This is replaced by get_query_payload that contain + more information than just the ADQL string + + Returns + ------- + `~astropy.table.Table` Examples -------- @@ -1109,10 +1172,11 @@ def list_columns(self, *tables: str, keyword=None, get_adql=False): " FROM TAP_SCHEMA.columns" " WHERE table_name NOT LIKE 'TAP_SCHEMA.%'") # select the tables + tables = tuple(map(str.casefold, tables)) if len(tables) == 1: - query += f" AND table_name = '{tables[0]}'" + query += f" AND LOWERCASE(table_name) = '{tables[0]}'" elif len(tables) > 1: - query += f" AND table_name IN {tables}" + query += f" AND LOWERCASE(table_name) IN {tables}" # add the keyword condition if keyword is not None: condition = f"LIKE LOWERCASE('%{_adql_parameter(keyword)}%')" @@ -1120,13 +1184,10 @@ def list_columns(self, *tables: str, keyword=None, get_adql=False): f" OR (LOWERCASE(description) {condition})" f" OR (LOWERCASE(table_name) {condition}))") query += " ORDER BY table_name, principal DESC, column_name" - if get_adql: - return query - return self.query_tap(query) + return self.query_tap(query, get_query_payload=get_query_payload) - def list_linked_tables(self, table: str, *, get_adql=False): - """ - Expose the tables that can be non-obviously linked with the given table. + def list_linked_tables(self, table: str, *, get_query_payload=False): + """Expose the tables that can be non-obviously linked with the given table. This list contains only the links where the column names are not the same in the two tables. For example every ``oidref`` column of any table can be joined with @@ -1137,8 +1198,10 @@ def list_linked_tables(self, table: str, *, get_adql=False): ---------- table : str One of SIMBAD's tables name - get_adql : bool, optional - Returns the ADQL string instead of querying SIMBAD. + get_query_payload : bool, optional + When set to `True` the method returns the HTTP request parameters without + querying SIMBAD. The ADQL string is in the 'QUERY' key of the payload. + Defaults to `False`. Returns ------- @@ -1160,37 +1223,10 @@ def list_linked_tables(self, table: str, *, get_adql=False): " FROM TAP_SCHEMA.key_columns JOIN TAP_SCHEMA.keys USING (key_id)" f" WHERE (from_table = '{_adql_parameter(table)}')" f" OR (target_table = '{_adql_parameter(table)}')") - if get_adql: - return query - return self.query_tap(query) - - @lru_cache(256) - def _cached_query_tap(self, query: str, *, maxrec=10000): - """Cache version of query TAP - - This private method is called when query_tap is executed without an - ``uploads`` extra keyword argument. This is a work around because - `~astropy.table.Table` objects are not hashable and thus cannot - be used as arguments for a function decorated with lru_cache. - - Parameters - ---------- - query : str - A string containing the query written in the - Astronomical Data Query Language (ADQL). - maxrec : int, optional - The number of records to be returned. Its maximum value is 2000000. + return self.query_tap(query, get_query_payload=get_query_payload) - Returns - ------- - `~astropy.table.Table` - The response returned by Simbad. - """ - return self.tap.run_async(query, maxrec=maxrec).to_table() - - def query_tap(self, query: str, *, maxrec=10000, **uploads): - """ - Query Simbad TAP service. + def query_tap(self, query: str, *, maxrec=10000, get_query_payload=False, **uploads): + """Query SIMBAD TAP service. Parameters ---------- @@ -1204,6 +1240,10 @@ def query_tap(self, query: str, *, maxrec=10000, **uploads): Any number of local tables to be used in the *query*. In the *query*, these tables are referred as *TAP_UPLOAD.table_alias* where *TAP_UPLOAD* is imposed and *table_alias* is the keyword name you chose. The maximum number of lines for the uploaded tables is 200000. + get_query_payload : bool, optional + When set to `True` the method returns the HTTP request parameters without + querying SIMBAD. The ADQL string is in the 'QUERY' key of the payload. + Defaults to `False`. Returns ------- @@ -1221,7 +1261,7 @@ def query_tap(self, query: str, *, maxrec=10000, **uploads): See also: a `graphic representation of Simbad's tables and their relations `__. - See also + See Also -------- list_tables : The list of SIMBAD's tables. list_columns : SIMBAD's columns list, can be restricted to some tables and some keyword. @@ -1230,7 +1270,7 @@ def query_tap(self, query: str, *, maxrec=10000, **uploads): Examples -------- - To see the five oldest papers referenced in Simbad + To see the five oldest papers referenced in SIMBAD >>> from astroquery.simbad import Simbad >>> Simbad.query_tap("SELECT top 5 bibcode, title " @@ -1272,180 +1312,96 @@ def query_tap(self, query: str, *, maxrec=10000, **uploads): b c """ - if maxrec > Simbad.hardlimit: - raise ValueError(f"The maximum number of records cannot exceed {Simbad.hardlimit}.") + if maxrec > self.hardlimit: + raise ValueError(f"The maximum number of records cannot exceed {self.hardlimit}.") if query.count("'") % 2: raise ValueError("Query string contains an odd number of single quotes." " Escape the unpaired single quote by doubling it.\n" "ex: 'Barnard's galaxy' -> 'Barnard''s galaxy'.") + if get_query_payload: + return dict(TAPQuery(self.SIMBAD_URL, query, maxrec=maxrec, uploads=uploads)) + # without uploads we call the version with cache if uploads == {}: - return self._cached_query_tap(query, maxrec=maxrec) + return _cached_query_tap(self.tap, query, maxrec=maxrec) + # with uploads it has to be without cache return self.tap.run_async(query, maxrec=maxrec, uploads=uploads).to_table() - def _get_query_header(self, get_raw=False): - # if get_raw is set then don't fetch as votable - if get_raw: - return "" - row_limit = f"set limit {self.ROW_LIMIT}\n" if self.ROW_LIMIT > 0 else "" - return f"{row_limit}votable {{{','.join(self.get_votable_fields())}}}\nvotable open" + @staticmethod + def clear_cache(): + """Clear the cache of SIMBAD.""" + _cached_query_tap.cache_clear() + gc.collect() - def _get_query_footer(self, get_raw=False): - return "" if get_raw else "votable close" + # ----------------------------- + # Utility methods for query TAP + # ----------------------------- - @validate_epoch_decorator - @validate_equinox_decorator - def _args_to_payload(self, *args, **kwargs): - """ - Takes the arguments from any of the query functions and returns a - dictionary that can be used as the data for an HTTP POST request. - """ + def _get_query_parameters(self): + """Get the current building blocks of an ADQL query.""" + return tuple(map(copy.deepcopy, (self.ROW_LIMIT, self.columns_in_output, self.joins, self.criteria))) - script = "" - caller = kwargs['caller'] - del kwargs['caller'] - get_raw = kwargs.pop('get_raw', False) - command = self._function_to_command[caller] - - votable_header = self._get_query_header(get_raw) - votable_footer = self._get_query_footer(get_raw) - - script = "\n".join([script, votable_header, command]) - using_wildcard = False - if kwargs.get('wildcard'): - # necessary to have a space at the beginning and end - script += " wildcard " - del kwargs['wildcard'] - using_wildcard = True - # now append args and kwds as per the caller - # if caller is query_region_async write coordinates as separate ra dec - # rename equinox to equi as required by SIMBAD script - if kwargs.get('equinox'): - kwargs['equi'] = kwargs['equinox'] - del kwargs['equinox'] - # remove default None from kwargs - kwargs = {key: value for key, value in kwargs.items() if value is not None} - # join in the order specified otherwise results in error - all_keys = ['radius', 'frame', 'equi', 'epoch'] - present_keys = [key for key in all_keys if key in kwargs] - if caller == 'query_criteria_async': - present_keys.extend(kwargs) - # need ampersands to join args - args_str = '&'.join([str(val) for val in args]) - if args and present_keys: - args_str += " & " - else: - args_str = ' '.join([str(val) for val in args]) - kwargs_str = ' '.join(f"{key}={kwargs[key]}" for key in present_keys) + def _query(self, top, columns, joins, criteria, from_table="basic", + get_query_payload=False, **uploads): + """Generate an ADQL string from the given query parameters and executes the query. - # For the record, I feel dirty for writing this wildcard-case hack. - # This entire function should be refactored when someone has time. - allargs_str = ' '.join([" ", args_str, kwargs_str, "\n"]) - if using_wildcard: - allargs_str = allargs_str.lstrip() - - script += allargs_str - script += votable_footer - return dict(script=script) + Parameters + ---------- + top : int + number of lines to be returned + columns : List[SimbadClass.Column] + The list of columns to be included in the output. + joins : List[SimbadClass.Join] + The list of joins to be made with basic. + criteria : List[str] + A list of strings. These criteria will be joined + with an AND clause. + from_table : str, optional + The table after 'FROM' in the ADQL string. Defaults to "basic". + get_query_payload : bool, optional + When set to `True` the method returns the HTTP request parameters without + querying SIMBAD. The ADQL string is in the 'QUERY' key of the payload. + Defaults to `False`. + uploads : `~astropy.table.Table` + Any number of local tables to be used in the *query*. In the *query*, these tables + are referred as *TAP_UPLOAD.table_alias* where *TAP_UPLOAD* is imposed and *table_alias* + is the keyword name you chose. The maximum number of lines for the uploaded tables is 200000. - def _parse_result(self, result, resultclass=SimbadVOTableResult, - verbose=False): - """ - Instantiate a Simbad*Result class and try to parse the - response with the .table property/method, then return the - resulting table. If data is not retrieved or the resulting - table is empty, return None. In case of problems, save - intermediate results for further debugging. + Returns + ------- + `~astropy.table.Table` + The result of the query to SIMBAD. """ - self.last_response = result - try: - content = result.content.decode('utf-8') - self.last_parsed_result = resultclass(content, verbose=verbose) - if self.last_parsed_result.data is None: - return None - resulttable = self.last_parsed_result.table - if len(resulttable) == 0: - return None - except Exception as ex: - self.last_table_parse_error = ex - try: - self._last_query.remove_cache_file(self.cache_location) - except OSError: - # this is allowed: if `cache` was set to False, this - # won't be needed - pass - raise TableParseError("Failed to parse SIMBAD result! The raw " - "response can be found in " - "self.last_response, and the error in " - "self.last_table_parse_error. The attempted" - " parsed result is in " - "self.last_parsed_result.\n " - "Exception: " + str(ex)) - resulttable.errors = self.last_parsed_result.errors - return resulttable - - -def _parse_coordinates(coordinates): - try: - coordinates = commons.parse_coordinates(coordinates) - # now c has some subclass of astropy.coordinate - # get ra, dec and frame - return _get_frame_coords(coordinates) - except (u.UnitsError, TypeError): - raise ValueError("Coordinates not specified correctly") - - -def _get_frame_coords(coordinates): - if isiterable(coordinates): - # deal with vectors differently - parsed = [_get_frame_coords(cc) for cc in coordinates] - return ([ra for ra, dec, frame in parsed], - [dec for ra, dec, frame in parsed], - [frame for ra, dec, frame in parsed]) - if coordinates.frame.name == 'icrs': - ra, dec = _to_simbad_format(coordinates.ra, coordinates.dec) - return (ra, dec, 'ICRS') - elif coordinates.frame.name == 'galactic': - lon, lat = (str(coordinates.l.degree), str(coordinates.b.degree)) - if lat[0] not in ['+', '-']: - lat = '+' + lat - return (lon, lat, 'GAL') - elif coordinates.frame.name == 'fk4': - ra, dec = _to_simbad_format(coordinates.ra, coordinates.dec) - return (ra, dec, 'FK4') - elif coordinates.frame.name == 'fk5': - ra, dec = _to_simbad_format(coordinates.ra, coordinates.dec) - return (ra, dec, 'FK5') - else: - raise ValueError("%s is not a valid coordinate" % coordinates) - - -def _to_simbad_format(ra, dec): - # This irrelevantly raises the exception - # "AttributeError: Angle instance has no attribute 'hour'" - ra = ra.to_string(u.hour, sep=':') - dec = dec.to_string(u.degree, sep=':', alwayssign='True') - return (ra.lstrip(), dec.lstrip()) - - -def _parse_radius(radius): - try: - angle = coord.Angle(radius) - # find the most appropriate unit - d, m or s - nonzero_indices = [i for (i, val) in enumerate(angle.dms) - if int(val) > 0] - if len(nonzero_indices) > 0: - index = min(nonzero_indices) + top = f" TOP {top}" if top != -1 else "" + + # columns + input_columns = [f'{column.table}."{column.name}" AS {column.alias}' if column.alias is not None + else f'{column.table}."{column.name}"' for column in columns] + # remove possible duplicates + unique_columns = [] + [unique_columns.append(column) for column in input_columns if column not in unique_columns] + columns = " " + ", ".join(unique_columns) + # selecting all columns is the only case where this should not be a string + columns = columns.replace('"*"', "*") + + # joins + if joins == []: + join = "" + else: + unique_joins = [] + [unique_joins.append(join) for join in joins if join not in unique_joins] + join = " " + " ".join([(f'{join.join_type} {join.table} ON {join.column_left.table}."' + f'{join.column_left.name}" = {join.column_right.table}."' + f'{join.column_right.name}"') for join in joins]) + + # criteria + if criteria != []: + criteria = f" WHERE {' AND '.join(criteria)}" else: - index = 2 # use arcseconds when radius smaller than 1 arcsecond - unit = ('d', 'm', 's')[index] - if unit == 'd': - return str(angle.degree) + unit - if unit == 'm': - return str(angle.arcmin) + unit - if unit == 's': - return str(angle.arcsec) + unit - except (coord.errors.UnitsError, AttributeError): - raise ValueError("Radius specified incorrectly") + criteria = "" + + query = f"SELECT{top}{columns} FROM {from_table}{join}{criteria}" + + return self.query_tap(query, get_query_payload=get_query_payload, **uploads) Simbad = SimbadClass() diff --git a/astroquery/simbad/criteria_lextab.py b/astroquery/simbad/criteria_lextab.py new file mode 100644 index 0000000000..5166b08e76 --- /dev/null +++ b/astroquery/simbad/criteria_lextab.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Licensed under a 3-clause BSD style license - see LICENSE.rst + +# This file was automatically generated from ply. To re-generate this file, +# remove it from this folder, then build astropy and run the tests in-place: +# +# python setup.py build_ext --inplace +# pytest astroquery/simbad +# +# You can then commit the changes to this file. + +# criteria_lextab.py. This file automatically created by PLY (version 3.11). Don't edit! +_tabversion = '3.10' +_lextokens = set(('BINARY_OPERATOR', 'COLUMN', 'IN', 'LIKE', 'LIST', 'NOTLIKE', 'NUMBER', 'REGION', 'STRING')) +_lexreflags = 34 +_lexliterals = '&\\|\\(\\)' +_lexstateinfo = {'INITIAL': 'inclusive'} +_lexstatere = {'INITIAL': [("(?Pin\\b)|(?P\\( *'[^\\)]*\\))|(?P>=|<=|!=|>|<|=)|(?P~|∼)|(?P!~|!∼)|(?P'[^']*')|(?Pregion\\([^\\)]*\\))|(?P[a-zA-Z_*][a-zA-Z_0-9*]*)|(?P\\d*\\.?\\d+)", [None, ('t_IN', 'IN'), ('t_LIST', 'LIST'), ('t_BINARY_OPERATOR', 'BINARY_OPERATOR'), ('t_LIKE', 'LIKE'), ('t_NOTLIKE', 'NOTLIKE'), ('t_STRING', 'STRING'), ('t_REGION', 'REGION'), ('t_COLUMN', 'COLUMN'), (None, 'NUMBER')])]} +_lexstateignore = {'INITIAL': ', \t\n'} +_lexstateerrorf = {'INITIAL': 't_error'} +_lexstateeoff = {} diff --git a/astroquery/simbad/criteria_parsetab.py b/astroquery/simbad/criteria_parsetab.py new file mode 100644 index 0000000000..0e00ea4fc4 --- /dev/null +++ b/astroquery/simbad/criteria_parsetab.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# Licensed under a 3-clause BSD style license - see LICENSE.rst + +# This file was automatically generated from ply. To re-generate this file, +# remove it from this folder, then build astropy and run the tests in-place: +# +# python setup.py build_ext --inplace +# pytest astroquery/simbad +# +# You can then commit the changes to this file. + + +# criteria_parsetab.py +# This file is automatically generated. Do not edit. +# pylint: disable=W,C,R +_tabversion = '3.10' + +_lr_method = 'LALR' + +_lr_signature = "BINARY_OPERATOR COLUMN IN LIKE LIST NOTLIKE NUMBER REGION STRINGcriteria : criteria '|' criteriacriteria : criteria '&' criteriacriteria : '(' criteria ')'criteria : COLUMN BINARY_OPERATOR STRING\n | COLUMN BINARY_OPERATOR NUMBER\n | COLUMN IN LIST\n criteria : COLUMN BINARY_OPERATOR COLUMN\n criteria : COLUMN LIKE STRINGcriteria : COLUMN NOTLIKE STRINGcriteria : REGION" + +_lr_action_items = {'(':([0,2,5,6,],[2,2,2,2,]),'COLUMN':([0,2,5,6,8,],[3,3,3,3,15,]),'REGION':([0,2,5,6,],[4,4,4,4,]),'$end':([1,4,12,13,14,15,16,17,18,19,20,],[0,-10,-1,-2,-3,-7,-4,-5,-6,-8,-9,]),'|':([1,4,7,12,13,14,15,16,17,18,19,20,],[5,-10,5,5,5,-3,-7,-4,-5,-6,-8,-9,]),'&':([1,4,7,12,13,14,15,16,17,18,19,20,],[6,-10,6,6,6,-3,-7,-4,-5,-6,-8,-9,]),'BINARY_OPERATOR':([3,],[8,]),'IN':([3,],[9,]),'LIKE':([3,],[10,]),'NOTLIKE':([3,],[11,]),')':([4,7,12,13,14,15,16,17,18,19,20,],[-10,14,-1,-2,-3,-7,-4,-5,-6,-8,-9,]),'STRING':([8,10,11,],[16,19,20,]),'NUMBER':([8,],[17,]),'LIST':([9,],[18,]),} + +_lr_action = {} +for _k, _v in _lr_action_items.items(): + for _x,_y in zip(_v[0],_v[1]): + if not _x in _lr_action: _lr_action[_x] = {} + _lr_action[_x][_k] = _y +del _lr_action_items + +_lr_goto_items = {'criteria':([0,2,5,6,],[1,7,12,13,]),} + +_lr_goto = {} +for _k, _v in _lr_goto_items.items(): + for _x, _y in zip(_v[0], _v[1]): + if not _x in _lr_goto: _lr_goto[_x] = {} + _lr_goto[_x][_k] = _y +del _lr_goto_items +_lr_productions = [ + ("S' -> criteria","S'",1,None,None,None), + ('criteria -> criteria | criteria','criteria',3,'p_criteria_OR','utils.py',298), + ('criteria -> criteria & criteria','criteria',3,'p_criteria_AND','utils.py',302), + ('criteria -> ( criteria )','criteria',3,'p_criteria_parenthesis','utils.py',306), + ('criteria -> COLUMN BINARY_OPERATOR STRING','criteria',3,'p_criteria_string','utils.py',310), + ('criteria -> COLUMN BINARY_OPERATOR NUMBER','criteria',3,'p_criteria_string','utils.py',311), + ('criteria -> COLUMN IN LIST','criteria',3,'p_criteria_string','utils.py',312), + ('criteria -> COLUMN BINARY_OPERATOR COLUMN','criteria',3,'p_criteria_string_no_ticks','utils.py',317), + ('criteria -> COLUMN LIKE STRING','criteria',3,'p_criteria_like','utils.py',323), + ('criteria -> COLUMN NOTLIKE STRING','criteria',3,'p_criteria_notlike','utils.py',327), + ('criteria -> REGION','criteria',1,'p_criteria_region','utils.py',331), +] diff --git a/astroquery/simbad/data/query_criteria_fields.json b/astroquery/simbad/data/query_criteria_fields.json new file mode 100644 index 0000000000..c62b5b1a91 --- /dev/null +++ b/astroquery/simbad/data/query_criteria_fields.json @@ -0,0 +1,291 @@ +{ + "all_fluxdata": { + "description": "All fluxes referenced for this object.", + "tap_table": "flux", + "type": "alias table" + }, + "bibcodelist": { + "description": "List of references for an object", + "tap_table": "biblio", + "type": "alias table" + }, + "cel": { + "description": "Celescope catalog of ultra-violet photometry", + "type": "historical measurement" + }, + "cl.g": { + "description": "Cluster of Galaxies: Abell & Corwin,Astrophys. J. Suppl.,70,1 (1989)", + "type": "historical measurement" + }, + "coo_err_maja": { + "description": "major axis of the error ellipse", + "tap_column": "coo_err_maj", + "type": "alias" + }, + "coo_err_mina": { + "description": "minor axis of the error ellipse", + "tap_column": "coo_err_min", + "type": "alias" + }, + "coordinates": { + "description": "all fields related with coordinates", + "tap_startswith": "coo_", + "tap_column": ["ra", "dec", "ra_prec", "dec_prec"], + "type": "bundle" + }, + "dim": { + "description": "major and minor axis, angle and inclination", + "tap_startswith": "galdim_", + "type": "bundle" + }, + "dim_angle": { + "description": "angle of the object", + "tap_column": "galdim_angle", + "type": "alias" + }, + "dim_bibcode": { + "description": "Bibliographical reference", + "tap_column": "galdim_bibcode", + "type": "alias" + }, + "dim_incl": { + "description": "inclination (unit of 15d: value from 0 to 6)", + "type": "alias", + "tap_column": "galdim_angle" + }, + "dim_majaxis": { + "description": "Major axis", + "tap_column": "galdim_majaxis", + "type": "alias" + }, + "dim_minaxis": { + "description": "Minor axis", + "tap_column": "galdim_min_axis", + "type": "alias" + }, + "dim_qual": { + "description": "quality (A: best, .., E: worst)", + "tap_column": "galdim_qual", + "type": "alias" + }, + "dim_wavelength": { + "description": "wavelength type in which these dimensions were measured (Radio, IR, Visible, UV, X, Gamma)", + "tap_column": "galdim_wavelength", + "type": "alias" + }, + "dimensions": { + "description": "all fields related to object dimensions", + "tap_startswith": "galdim_", + "type": "bundle" + }, + "distance": { + "description": "Measure of distances by several means", + "tap_table": "mesdistance", + "type": "alias table" + }, + "einstein": { + "description": "The Einstein Observatory Soft X-ray Source List", + "type": "historical measurement" + }, + "fe_h": { + "description": "Stellar parameters (Teff, log(g) and [Fe/H]) taken from the literature.", + "type": "alias table", + "tap_table": "mesfe_h" + }, + "flux": { + "description": "value of the flux for the given filter", + "type": "alias table", + "tap_table": "flux" + }, + "gcrv": { + "description": "General Catalogue of Radial Velocities", + "type": "historical measurement" + }, + "gen": { + "description": "Geneva Photometric System Catalogue", + "type": "historical measurement" + }, + "gj": { + "description": "Gliese Jahreiss Nearby Stars (Catalog of stars within 20 parsecs of the Sun)", + "type": "historical measurement" + }, + "hbet": { + "description": "The Hbeta photometric system (Crawford and Mander,1966,Astron. J. 7,114)", + "type": "historical measurement" + }, + "hbet1": { + "description": "The Hbeta photometric system (Crawford and Mander,1966,Astron. J. 7,114)", + "type": "historical measurement" + }, + "hgam": { + "description": "Catalogue of equivalent width of Hgamma line from Petrie et al. (1973PDAO...14..151P)", + "type": "historical measurement" + }, + "id(1)": { + "description": "1 : display the first (main identifier) of the object. Is the same as main_id.", + "type": "alias", + "tap_column": "main_id" + }, + "id(cat)": { + "description": "Creates as much columns as cat entries with the identifier in this nomenclature if it exists. Example: 'id(Gaia|NGC)'. This is case-sensitive.", + "type": "criteria" + }, + "iras": { + "description": "InfraRed Astronomical Satellite Measurements extracted from the IRAS Point Source Catalog, 2nd Edition", + "type": "historical measurement" + }, + "irc": { + "description": "Infra-red measurements by Neugebauer and Leighton, Caltech, NASA, 1969", + "type": "historical measurement" + }, + "iso": { + "description": "Infrared Space Observatory (ISO) log", + "type": "alias table", + "tap_table": "mesiso" + }, + "iue": { + "description": "International Ultraviolet Explorer", + "type": "alias table", + "tap_table": "mesiue" + }, + "jp11": { + "description": "UBVRIJKLMNH Johnson s photometry", + "type": "alias table", + "tap_table": "flux" + }, + "maintype": { + "type": "alias", + "tap_column": "otype" + }, + "mk": { + "description": "MK classifications in the Morgan-Keenan system and the Michigan Catalogues of Two-Dimensional Spectral Types for the HD stars (Houk N.,1975,and seq.)", + "type": "alias table", + "tap_table": "messpt" + }, + "modifdate": { + "description": "date of last modification", + "type": "alias", + "tap_column": "date_modif" + }, + "morphtype": { + "description": "all fields related to the morphological type", + "type": "bundle", + "tap_startswith": "morph_" + }, + "mt": { + "description": "morphological type value", + "tap_column": "morph_type", + "type": "alias" + }, + "mt_bibcode": { + "description": "Bibliographical reference", + "tap_column": "morph_bibcode", + "type": "alias" + }, + "mt_qual": { + "description": "morphological type quality (A: best, .., E: worst)", + "tap_column": "morph_type", + "type": "alias" + }, + "otype(V)": { + "tap_table": "otypedef", + "type": "alias table" + }, + "otype(S)": { + "tap_table": "otypedef", + "type": "alias table" + }, + "parallax": { + "description": "all fields related to parallaxes", + "tap_startswith": "plx_", + "type": "bundle" + }, + "plx": { + "description": "parallax value", + "tap_column": "plx_value", + "type": "alias" + }, + "plx_error": { + "description": "parallax error", + "tap_column": "plx_err", + "type": "alias" + }, + "pm_err_maja": { + "description": "major axis of the error ellipse", + "tap_column": "pm_err_maj", + "type": "alias" + }, + "pm_err_mina": { + "description": "minor axis of the error ellipse", + "tap_column": "pm_err_min", + "type": "alias" + }, + "propermotions": { + "description": "all fields related with the proper motions", + "tap_startswith": "pm", + "type": "bundle" + }, + "radvel": { + "description": "Radial velocity", + "tap_column": "rvz_radvel", + "type": "alias" + }, + "redshift": { + "type": "alias", + "tap_column": "rvz_redshift" + }, + "rot": { + "description": "Stellar Rotational Velocities", + "type": "alias table", + "tap_table": "mesrot" + }, + "rv_value": { + "description": "Radial velocity value. Eventually translated from a redshift", + "type": "alias", + "tap_column": "rvz_value" + }, + "rvz_error": { + "description": "Error. In the same unit as rvz_radvel", + "type": "alias", + "tap_column": "rvz_err" + }, + "rvz_radvel": { + "description": "stored value. Either a radial velocity, or a redshift,\ndepending on the rvz_type field", + "type": "alias", + "tap_column": "rvz_value" + }, + "sao": { + "description": "SAO catalogue (Star catalog of 258997 stars for the epoch and equinox 1950.0,1966)", + "type": "historical measurement" + }, + "sp": { + "description": "all fields related with the spectral type", + "type": "bundle", + "tap_startswith": "sp_" + }, + "sptype": { + "description": "spectral type value", + "type": "alias", + "tap_column": "sp_type" + }, + "v*": { + "description": "variable stars parameters extracted mainly from the General Catalog of Variable Stars by Kukarkin et al. USSR Academy of Sciences (3rd edition in 1969,and continuations)", + "type": "alias table", + "tap_table": "mesvar" + }, + "velocity": { + "description": "all fields related with radial velocity and redshift", + "type": "bundle", + "tap_startswith": "rvz_" + }, + "xmm": { + "description": "XMM log", + "type": "alias table", + "tap_table": "mesxmm" + }, + "z_value": { + "description": "Redshift value. Eventually translated from a radial velocity", + "type": "alias", + "tap_column": "rvz_redshift" + } +} \ No newline at end of file diff --git a/astroquery/simbad/data/votable_fields_dict.json b/astroquery/simbad/data/votable_fields_dict.json deleted file mode 100644 index a8dafc6eb6..0000000000 --- a/astroquery/simbad/data/votable_fields_dict.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "bibcodelist(y1-y2)": "number of references. The parameter is optional and limit the count to\nthe references between the years y1 and y2", - "biblio": "all bibcodes of an astronomical object separated with a ipipe", - "cel": "Celescope catalog of ultra-violet photometry", - "cl.g": "Cluster of Galaxies: Abell & Corwin,Astrophys. J. Suppl.,70,1 (1989)", - "coo(opt)": "d : decimal display\ns : sexagesimal display\nframe : ICRS, FK5, FK4, GAL, SGAL, ECL\nepoch : epoch (decimal value) for the coordinates display\nequinox : equinox (decimal value) for the coordinates display", - "coo_bibcode": "bibliographical reference", - "coo_err_angle": "angle of the error ellipse", - "coo_err_maja": "major axis of the error ellipse", - "coo_err_mina": "minor axis of the error ellipse", - "coo_qual": "quality (A:astrometric, .., E:unknown)", - "coo_wavelength": "wavelength type of the measure (Radio, IR, Visible, UV, X, Gamma)", - "coordinates": "all fields related with coordinates", - "dec(opt)": "declination. The options are the same as for coo above.", - "dec_prec": "declination precision code (0:1/10deg, ..., 8: 1/1000 arcsec)", - "diameter": "Stellar diameters, see 2001A&A...367..521P for an explanation of the coding \n of the methods of measurements.", - "dim": "main fields related to object dimensions: major and minor axis, angle and inclination", - "dim_angle": "angle of the object", - "dim_bibcode": "Bibliographical reference", - "dim_incl": "inclination (unit of 15d: value from 0 to 6)", - "dim_majaxis": "Major axis", - "dim_minaxis": "Minor axis", - "dim_qual": "quality (A: best, .., E: worst)", - "dim_wavelength": "wavelength type in which these dimensions were measured (Radio, IR, Visible, UV, X, Gamma)", - "dimensions": "all fields related to object dimensions", - "distance": "Measure of distances by several means", - "distance_result": "Angular distance from the center of the query (arcsec) (if relevant)", - "einstein": "The Einstein Observatory Soft X-ray Source List", - "fe_h": "Stellar parameters (Teff, log(g) and [Fe/H]) taken from the literature.", - "flux(filtername)": "value of the flux for the given filter", - "flux_bibcode(filtername)": "bibcode", - "flux_error(filtername)": "error value of the flux measurement", - "flux_name(filtername)": "name of the filter", - "flux_qual(filtername)": "quality (A:best, E:worst) of the flux", - "flux_system(filtername)": "flux unit (mag, jy, ...)", - "flux_unit(filtername)": "flux unit (mag, jy, ...)", - "fluxdata(filtername)": "all fields related with a particular filter.", - "gcrv": "General Catalogue of Radial Velocities", - "gen": "Geneva Photometric System Catalogue", - "gj": "Gliese Jahreiss Nearby Stars (Catalog of stars within 20 parsecs of the Sun)", - "hbet": "The Hbeta photometric system (Crawford and Mander,1966,Astron. J. 7,114)", - "hbet1": "The Hbeta photometric system (Crawford and Mander,1966,Astron. J. 7,114)", - "hgam": "Catalogue of equivalent width of Hgamma line from Petrie et al. (1973PDAO...14..151P)", - "id(opt)": "1 : display the first (main identifier) of the object. Is the same as main_id\ncat : display the identifier from the given catalog. If it doesn't exist, the\n field is left empty\ncat1|cat2|...[|1] : display the identifier from the first catalog, or, if it doesn't exits\n the one from the second identifier and so on. If none is found, the\n\t\t\t\tfield is left empty, except if |1 closes the list: then the\n\t\t\t\tfirst identifier is used.", - "ids": "all identifiers of an astronomical object separated with a ipipe", - "iras": "InfraRed Astronomical Satellite Measurements extracted from the \nIRAS Point Source Catalog, 2nd Edition", - "irc": "Infra-red measurements by Neugebauer and Leighton, Caltech, NASA, 1969", - "iso": "Infrared Space Observatory (ISO) log", - "iue": "International Ultraviolet Explorer", - "jp11": "UBVRIJKLMNH Johnson s photometry", - "link_bibcode": "Bibcode used to link the object in the hierarchy", - "main_id": "main identifier of an astronomical object. It is the same as id(1)", - "measurements": "display all fields from all measurements (291 fields)", - "membership": "Hierarchy probability of membership", - "mesplx": "Parallax measurements", - "mespm": "compilation of measurements of stellar proper motions (except SAO catalogue).\n These data are presently given at equinox and epoch 1950, in the FK4 system.", - "mk": "MK classifications in the Morgan-Keenan system and \nthe Michigan Catalogues of Two-Dimensional Spectral Types for\nthe HD stars (Houk N.,1975,and seq.)", - "morphtype": "all fields related to the morphological type", - "mt": "morphological type value", - "mt_bibcode": "Bibliographical reference", - "mt_qual": "morphological type quality (A: best, .., E: worst)", - "otype": "standard name of the object type", - "otype(opt)": "N : numerical display\nS : standard name (max 6 chars)\n3 : short name (max 3 chars)\nV : full description", - "otypes": "list of (secondary) object types for one object", - "parallax": "all fields related to parallaxes", - "plx": "parallax value", - "plx_bibcode": "bibliographical reference", - "plx_error": "parallax error", - "plx_prec": "precision code (0:1/10deg, ..., 8: 1/1000 arcsec)", - "plx_qual": "parallax quality (A:astrometric, .., E:unknown)", - "pm": "proper motion values in right ascension and declination", - "pm_bibcode": "bibliographical reference", - "pm_err_angle": "angle of the error ellipse", - "pm_err_maja": "major axis of the error ellipse", - "pm_err_mina": "minor axis of the error ellipse", - "pm_qual": "quality (A:astrometric, .., E:unknown)", - "pmdec": "proper motion in declination", - "pmdec_prec": "precision code (0:1/10deg, ..., 8: 1/1000 arcsec)", - "pmra": "proper motion in right ascension", - "pmra_prec": "precision code (0:1/10deg, ..., 8: 1/1000 arcsec)", - "pos": "compilation of measurements of stellar positions (except SAO data)", - "posa": "Measurements of Position (since June 1998)", - "propermotions": "all fields related with the proper motions", - "ra(opt)": "right ascension. The options are the same as for coo above.", - "ra_prec": "right ascension precision code (0:1/10deg, ..., 8: 1/1000 arcsec)", - "rot": "Stellar Rotational Velocities", - "rv_value": "Radial velocity value. Eventually translated from a redshift", - "rvz_bibcode": "Bibliographical reference", - "rvz_error": "Error. In the same unit as rvz_radvel", - "rvz_qual": "Quality code (A: best, .., E: worst)", - "rvz_radvel": "stored value. Either a radial velocity, or a redshift,\ndepending on the rvz_type field", - "rvz_type": "stored type of velocity: 'v'=radial velocity, 'z'=redshift", - "rvz_wavelength": "wavelength type of the measure (Radio, IR, Visible, UV, X, Gamma)", - "sao": "SAO catalogue (Star catalog of 258997 stars for the epoch and equinox 1950.0,1966)", - "sp": "spectral type value", - "sp_bibcode": "Bibliographical reference", - "sp_nature": "spectral type nature ('s'pectroscopic, 'a'bsorbtion, 'e'mmission", - "sp_qual": "spectral type quality (A: best, .., E: worst)", - "sptype": "all fields related with the spectral type", - "td1": "UV fluxes from TD1 satellite,by Thompson et al.", - "typed_id": "identifier given by the user.", - "ubv": "UBV data in Johnson's UBV system \n compiled by J.-Cl. Mermilliod from Institut d Astronomie de Lausanne (1973A&AS...71..413M)", - "uvby": "The Str\u00f6mgren uvby photometric system", - "uvby1": "The Str\u00f6mgren uvby photometric system", - "v*": "variable stars parameters extracted mainly from the \n General Catalog of Variable Stars by Kukarkin et al.\n USSR Academy of Sciences (3rd edition in 1969,and continuations)", - "velocity": "all fields related with radial velocity and redshift", - "xmm": "XMM log", - "z_value": "Redshift value. Eventually translated from a radial velocity" -} \ No newline at end of file diff --git a/astroquery/simbad/data/votable_fields_notes.json b/astroquery/simbad/data/votable_fields_notes.json deleted file mode 100644 index 7111cf670e..0000000000 --- a/astroquery/simbad/data/votable_fields_notes.json +++ /dev/null @@ -1,5 +0,0 @@ -[ - "The parameter filtername must correspond to an existing filter. Filters include: B,V,R,I,J,K. They are checked by SIMBAD but not astroquery.simbad", - "Fields beginning with rvz display the data as it is in the database. Fields beginning with rv force the display as a radial velocity. Fields beginning with z force the display as a redshift", - "For each measurement catalog, the VOTable contains all fields of the first measurement. When applicable, the first measurement is the mean one. " -] diff --git a/astroquery/simbad/get_votable_fields.py b/astroquery/simbad/get_votable_fields.py deleted file mode 100644 index 4c597212c1..0000000000 --- a/astroquery/simbad/get_votable_fields.py +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: utf-8 -*- -# Licensed under a 3-clause BSD style license - see LICENSE.rst - -import re -import json -import astropy.utils.data as aud - - -def reload_votable_fields_json(): - content = aud.get_file_contents("https://simbad.cds.unistra.fr/guide/sim-fscript.htx#VotableFields") - - import bs4 - htmldoc = bs4.BeautifulSoup(content, 'html5lib') - search_text = re.compile(r'Field names for VOTable output', re.IGNORECASE) - foundtext = htmldoc.find('h2', text=search_text) - - # Find the first
tag that follows it - table = foundtext.findNext('table') - outd = {} - for row in table.find_all('tr'): - cols = row.findChildren('td') - if len(cols) > 1: - smallest_child = cols[0].find_all()[-1] - if cols[0].findChild("ul"): - text1 = cols[0].findChild('ul').getText() - elif cols[0].find_all(): - text1 = smallest_child.getText() - else: - text1 = cols[0].getText() - if cols[1].findChild("ul"): - text2 = cols[1].findChild('ul').getText() - else: - text2 = cols[1].getText() - # ignore blank entries & headers - if (text2.strip() != '' - and not (smallest_child.name == 'font' and 'size' in smallest_child.attrs - and smallest_child.attrs['size'] == '+2')): - outd[text1.strip()] = text2.strip() - - with open('data/votable_fields_dict.json', 'w') as f: - json.dump(outd, f, indent=2, sort_keys=True) diff --git a/astroquery/simbad/setup_package.py b/astroquery/simbad/setup_package.py index 2aea743840..1f63c9662f 100644 --- a/astroquery/simbad/setup_package.py +++ b/astroquery/simbad/setup_package.py @@ -1,24 +1,14 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst - -import os +from pathlib import Path def get_package_data(): - paths_test = [os.path.join('data', 'query_bibcode.data'), - os.path.join('data', 'query_bibobj.data'), - os.path.join('data', 'query_cat.data'), - os.path.join('data', 'query_coo.data'), - os.path.join('data', 'query_id.data'), - os.path.join('data', 'query_error.data'), - os.path.join('data', 'query_*.data'), - os.path.join('data', 'm1.data'), - ] + paths_test = [str(Path('data') / 'simbad_output_options.xml'), + str(Path("data") / "simbad_basic_columns.xml"), + str(Path("data") / "simbad_linked_to_basic.xml")] - paths_core = [os.path.join('data', 'votable_fields_notes.json'), - os.path.join('data', 'votable_fields_table.txt'), - os.path.join('data', 'votable_fields_dict.json'), - ] + paths_core = [str(Path('data') / 'query_criteria_fields.json')] return {'astroquery.simbad.tests': paths_test, 'astroquery.simbad': paths_core, diff --git a/astroquery/simbad/tests/data/m1.data b/astroquery/simbad/tests/data/m1.data deleted file mode 100644 index cfe1315228..0000000000 --- a/astroquery/simbad/tests/data/m1.data +++ /dev/null @@ -1,67 +0,0 @@ -::script:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - -votable {main_id,coordinates} -votable open -query id m1 -votable close - -::console::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - -C.D.S. - SIMBAD4 rel 1.223 - 2014.08.09CEST11:17:58 -total execution time: 0.123 secs -simbatch done - -::data:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - - - - - - -
Simbad script executed on 2014.08.09CEST11:17:58 - -Main identifier for an object - - - -Right ascension - - -Declination - - -Right ascension precision - - -Declination precision - - -Coordinate error major axis - - -Coordinate error minor axis - - -Coordinate error angle - - -Coordinate quality - - -Wavelength class for the origin of the coordinates (R,I,V,U,X,G) - - -Coordinate reference - - - - - - - - -
M 105 34 31.94+22 00 52.266CRad2011A&A...533A..10L
- - - diff --git a/astroquery/simbad/tests/data/query_bibcode.data b/astroquery/simbad/tests/data/query_bibcode.data deleted file mode 100644 index 9de2568dfb..0000000000 --- a/astroquery/simbad/tests/data/query_bibcode.data +++ /dev/null @@ -1,22 +0,0 @@ -::script:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - -query bibcode wildcard 2006ApJ* - -::console::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - -C.D.S. - SIMBAD4 rel 1.207 - 2013.06.28CEST19:58:02 -total execution time: 2.191 secs -simbatch done - -::data:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - -2006ApJ...636....1K -- ? -Astrophys. J., 636, 1-7 (2006) -KOBAYASHI M.A.R., KAMAYA H. and YONEHARA A. -Ly{alpha} line spectra of the first galaxies: dependence on observed direction to the underlying cold dark matter filament. -Files: (abstract) (no object) -2006ApJ...636....8H -- ? -Astrophys. J., 636, 8-20 (2006) -HARKO T. and CHENG K.S. -Galactic metric, dark radiation, dark pressure, and gravitational lensing in brane world models. -Files: (abstract) diff --git a/astroquery/simbad/tests/data/query_bibobj.data b/astroquery/simbad/tests/data/query_bibobj.data deleted file mode 100644 index 09397103e7..0000000000 --- a/astroquery/simbad/tests/data/query_bibobj.data +++ /dev/null @@ -1,71 +0,0 @@ -::script:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - -votable {main_id, coordinates} -votable open -query bibobj 2013A&A...549A..23A -votable close - -::console::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - -C.D.S. - SIMBAD4 rel 1.207 - 2013.06.30CEST10:58:50 -total execution time: 0.407 secs -simbatch done - -::data:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - - - - - - -Simbad script executed on 2013.06.30CEST10:58:50 - -Main identifier for an object - - - -Right ascension - - -Declination - - -Right ascension precision - - -Declination precision - - -Coordinate error major axis - - -Coordinate error minor axis - - -Coordinate error angle - - -Coordinate quality - - -Wavelength class for the origin of the coordinates (R,I,V,U,X,G) - - -Coordinate reference - - - - - - - - - - - - -
PSR J0146+614501 46 22.407+61 45 03.1977D2004A&A...416.1037H
M 105 34 31.94+22 00 52.266CRad2011A&A...533A..10L
PSR J1808-202418 08 39.32-20 24 39.566D2002ApJ...564..935K
SNR G109.1-01.023 01 08+58 52.74418000180003E
2E 467323 01 08.295+58 52 44.4577D2002IAUC.7924....3K
-
-
- diff --git a/astroquery/simbad/tests/data/query_cat.data b/astroquery/simbad/tests/data/query_cat.data deleted file mode 100644 index 9b3f58c778..0000000000 --- a/astroquery/simbad/tests/data/query_cat.data +++ /dev/null @@ -1,176 +0,0 @@ -::script:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - -votable {main_id,coordinates} -votable open -query cat m -votable close - -::console::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - -C.D.S. - SIMBAD4 rel 1.207 - 2013.06.30CEST06:42:38 -total execution time: 47.407 secs -simbatch done - -::data:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - - - - - - -Simbad script executed on 2013.06.30CEST06:42:38 - -Main identifier for an object - - - -Right ascension - - -Declination - - -Right ascension precision - - -Declination precision - - -Coordinate error major axis - - -Coordinate error minor axis - - -Coordinate error angle - - -Coordinate quality - - -Wavelength class for the origin of the coordinates (R,I,V,U,X,G) - - -Coordinate reference - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
M 105 34 31.94+22 00 52.266CRad2011A&A...533A..10L
M 221 33 27.02-00 49 23.7661001000COpt2010AJ....140.1830G
M 313 42 11.62+28 22 38.2662002000COpt2010AJ....140.1830G
M 416 23 35.22-26 31 32.7664004000COpt2010AJ....140.1830G
M 515 18 33.22+02 04 51.766COpt2010AJ....140.1830G
M 617 40 20-32 15.244EOpt2009MNRAS.399.2146W
M 717 53 51-34 47.644EOpt2009MNRAS.399.2146W
M 818 03 37-24 23.2441800018000179E
M 917 19 11.78-18 30 58.566D2002MNRAS.332..441F
M 1016 57 09.05-04 06 01.16610010090COpt2010AJ....140.1830G
M 1118 51 05-06 16.244EOpt2009MNRAS.399.2146W
M 1216 47 14.18-01 56 54.76680080090COpt2010AJ....140.1830G
M 1316 41 41.634+36 27 40.7577BIR2006AJ....131.1163S
M 1417 37 36.15-03 14 45.366D2006MNRAS.365.1357D
M 1521 29 58.33+12 10 01.2662002000COpt2010AJ....140.1830G
M 1618 18 48-13 48.444EOpt2009MNRAS.399.2146W
M 1718 20 47-16 10.344EOpt2009MNRAS.399.2146W
M 1818 19 58-17 06.144EOpt2009MNRAS.399.2146W
M 1917 02 37.69-26 16 04.666D2006MNRAS.365.1357D
M 2018 02 42-22 58.344EOpt2009MNRAS.399.2146W
M 2118 04 13-22 29.444EOpt2009MNRAS.399.2146W
M 2218 36 23.94-23 54 17.16680080082COpt2010AJ....140.1830G
M 2317 57 04-18 59.144EOpt2009MNRAS.399.2146W
M 2418 16.8-18 3333EOpt2004yCat.7239....0C
M 2518 31 47-19 07.044EOpt2009MNRAS.399.2146W
M 2618 45 18-09 23.044EOpt2009MNRAS.399.2146W
M 2719 59 36.379+22 43 15.7577BIR2006AJ....131.1163S
M 2818 24 32.89-24 52 11.466D2006MNRAS.365.1357D
M 2920 23 56+38 31.444E2002A&A...392..869L
M 3021 40 22.12-23 10 47.5661001000COpt2010AJ....140.1830G
M 3100 42 44.330+41 16 07.5077BIR2006AJ....131.1163S
M 3200 42 41.87+40 51 57.26610800.002200.0090D1999ApJS..125..409C
M 3301 33 50.904+30 39 35.7977BIR2006AJ....131.1163S
M 3402 42 05+42 45.744EOpt2009MNRAS.399.2146W
M 3506 08 54+24 20.044EOpt2009MNRAS.399.2146W
M 3605 36 18+34 08.444EOpt2009MNRAS.399.2146W
M 3705 52 18+32 33.244EOpt2009MNRAS.399.2146W
M 3805 28 43+35 51.344E2002A&A...390..103D
M 3921 31 48+48 26.044EOpt2009MNRAS.399.2146W
NAME WINNECKE 412 22 12.53+58 04 58.666DOpt2001AJ....122.3466M
M 4106 46 01-20 45.444EOpt2009MNRAS.399.2146W
M 4205 35 17.3-05 23 28557500750091D1981MNRAS.194..693L
M 4305 35 31-05 16.2441800018000175E
M 4408 40 24+19 40.044EOpt2009MNRAS.399.2146W
M 4503 47 00+24 07.044EOpt2009MNRAS.399.2146W
M 4607 41 46-14 48.644EOpt2009MNRAS.399.2146W
M 4707 36 35-14 29.044EOpt2009MNRAS.399.2146W
M 4808 13 43-05 45.044EOpt2009MNRAS.399.2146W
M 4912 29 46.798+08 00 01.4877BIR2006AJ....131.1163S
M 5007 02 47.5-08 20 1655D2012AstL...38...74F
M 5113 29 52.698+47 11 42.9377BIR2006AJ....131.1163S
M 5223 24 48+61 35.644EOpt2009MNRAS.399.2146W
M 5313 12 55.25+18 10 05.46610010091COpt2010AJ....140.1830G
M 5418 55 03.33-30 28 47.5661001000COpt2010AJ....140.1830G
M 5519 39 59.71-30 57 53.1668008000COpt2010AJ....140.1830G
M 5619 16 35.57+30 11 00.5662002000COpt2010AJ....140.1830G
NAME RING NEBULA18 53 35.079+33 01 45.0377D2003A&A...408.1029K
M 5812 37 43.527+11 49 05.467758420COpt2009yCat.2294....0A
M 5912 42 02.322+11 38 48.9577BIR2006AJ....131.1163S
M 6012 43 40.008+11 33 09.4077BIR2006AJ....131.1163S
M 6112 21 54.950+04 28 24.9277BIR2006AJ....131.1163S
M 6217 01 12.60-30 06 44.566D2006MNRAS.365.1357D
M 6313 15 49.329+42 01 45.4477BIR2006AJ....131.1163S
M 6412 56 43.696+21 40 57.5777BIR2006AJ....131.1163S
M 6511 18 55.957+13 05 31.9677BIR2006AJ....131.1163S
M 6611 20 15.026+12 59 28.6477BIR2006AJ....131.1163S
M 6708 51 18+11 48.044E2005ApJ...619..824X
M 6812 39 27.98-26 44 38.6662002000COpt2010AJ....140.1830G
M 6918 31 23.10-32 20 53.1661001000COpt2010AJ....140.1830G
M 7018 43 12.76-32 17 31.66610010086COpt2010AJ....140.1830G
M 7119 53 46.49+18 46 45.1665005000COpt2010AJ....140.1830G
M 7220 53 27.70-12 32 14.36610010090COpt2010AJ....140.1830G
M 7320 59.0-12 3833180000180000170E
M 7401 36 41.772+15 47 00.4677BIR2006AJ....131.1163S
M 7520 06 04.841-21 55 20.1477BIR2006AJ....131.1163S
M 7601 42 19.948+51 34 31.1577D2003A&A...408.1029K
M 7702 42 40.771-00 00 47.8477BIR2006AJ....131.1163S
M 7805 46 46.7+00 00 505530003000176D
M 7905 24 10.59-24 31 27.366D2006MNRAS.365.1357D
M 8016 17 02.41-22 58 33.9662002000COpt2010AJ....140.1830G
M 8109 55 33.17306+69 03 55.0610990.6200.0420ARad2004AJ....127.3587F
M 8209 55 52.19+69 40 48.86610800.0010800.0090D1999ApJS..125..409C
M 8313 37 00.919-29 51 56.7477BIR2006AJ....131.1163S
M 8412 25 03.74333+12 53 13.1393990.4300.01990ARad2009A&A...493..317L
M 8512 25 24.053+18 11 27.8977BIR2006AJ....131.1163S
M 8612 26 11.814+12 56 45.4977BIR2006AJ....131.1163S
M 8712 30 49.42338+12 23 28.0439990.2500.01790ARad2009A&A...493..317L
M 8812 31 59.216+14 25 13.4877BIR2006AJ....131.1163S
M 8912 35 39.884+12 33 21.7477BIR2006AJ....131.1163S
M 9012 36 49.816+13 09 46.3377BIR2006AJ....131.1163S
M 9112 35 26.430+14 29 46.7577BIR2006AJ....131.1163S
M 9217 17 07.39+43 08 09.4663003000COpt2010AJ....140.1830G
M 9307 44 30-23 51.444EOpt2009MNRAS.399.2146W
M 9412 50 53.148+41 07 12.5577BIR2006AJ....131.1163S
M 9510 43 57.733+11 42 13.0077BIR2006AJ....131.1163S
M 9610 46 45.744+11 49 11.7877BIR2006AJ....131.1163S
M 9711 14 47.734+55 01 08.5077D2003A&A...408.1029K
M 9812 13 48.292+14 54 01.6977BIR2006AJ....131.1163S
M 9912 18 49.625+14 24 59.3677BIR2006AJ....131.1163S
M 10012 22 54.899+15 49 20.5777BIR2006AJ....131.1163S
M 10114 03 12.51+54 20 53.16610800.0010800.0090D1999ApJS..125..409C
M 10215 06 29.561+55 45 47.9177BIR2006AJ....131.1163S
M 10301 33 23+60 39.044EOpt2009MNRAS.399.2146W
M 10412 39 59.43185-11 37 22.9954992.3100.0540ARad2004AJ....127.3587F
M 10510 47 49.600+12 34 53.8777BIR2006AJ....131.1163S
M 10612 18 57.620+47 18 13.3977BIR2006AJ....131.1163S
M 10716 32 31.86-13 03 13.66610010090COpt2010AJ....140.1830G
M 10811 11 30.967+55 40 26.8477BIR2006AJ....131.1163S
M 10911 57 35.984+53 22 28.2777BIR2006AJ....131.1163S
M 11000 40 22.075+41 41 07.0877BIR2006AJ....131.1163S
-
-
- diff --git a/astroquery/simbad/tests/data/query_coo.data b/astroquery/simbad/tests/data/query_coo.data deleted file mode 100644 index 92e707ff74..0000000000 --- a/astroquery/simbad/tests/data/query_coo.data +++ /dev/null @@ -1,131 +0,0 @@ -::script:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - -votable {main_id, coordinates} -votable open -query coo 184.5575 -05.7844 frame=GAL -votable close - -::console::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - -C.D.S. - SIMBAD4 rel 1.207 - 2013.06.30CEST10:46:51 -total execution time: 0.877 secs -simbatch done - -::data:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - - - - - - -Simbad script executed on 2013.06.30CEST10:46:51 - -Main identifier for an object - - - -Right ascension - - -Declination - - -Right ascension precision - - -Declination precision - - -Coordinate error major axis - - -Coordinate error minor axis - - -Coordinate error angle - - -Coordinate quality - - -Wavelength class for the origin of the coordinates (R,I,V,U,X,G) - - -Coordinate reference - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
V* CM Tau05 34 31.93830+22 00 52.1758990.220.180ARad2011A&A...533A..10L
M 105 34 31.94+22 00 52.266CRad2011A&A...533A..10L
Trimble 2805 34 32.1+22 00 565530003000179D
2MASS J05343217+220056005 34 32.18+22 00 56.066606087BIR2003yCat.2246....0C
JCMTSF J053431.3+22004505 34 31.3+22 00 4555100001000088Emm2008ApJS..175..277D
RX J0534.4+220005 34 32.5+22 00 405530003000179D
[LBC2011] 5505 34 32.880+22 00 38.7077DIR2011ApJS..194...30L
2MASS J05343342+220058405 34 33.420+22 00 58.457760600BIR2003yCat.2246....0C
2MASS J05343187+220116105 34 31.873+22 01 16.167780800BIR2003yCat.2246....0C
[LBC2011] 5405 34 31.020+22 00 25.4977DIR2011ApJS..194...30L
[LBC2011] 5305 34 29.280+22 00 29.9477DIR2011ApJS..194...30L
2MASS J05343561+220037205 34 35.620+22 00 37.2677606091BIR2003yCat.2246....0C
[LBC2011] 4405 34 32.330+22 01 45.5077DIR2011ApJS..194...30L
[LBC2011] 3005 34 35.770+22 01 05.9077DIR2011ApJS..194...30L
2MASS J05342822+220030705 34 28.228+22 00 30.787770700BIR2003yCat.2246....0C
[LBC2011] 4505 34 31.170+22 01 47.0377DIR2011ApJS..194...30L
[LBC2011] 2905 34 36.020+22 01 06.9177DIR2011ApJS..194...30L
[LBC2011] 405 34 34.200+21 59 59.8977DIR2011ApJS..194...30L
[LBC2011] 3105 34 35.970+22 01 19.4577DIR2011ApJS..194...30L
JCMTSF J053430.4+22015105 34 30.4+22 01 515510000100000Emm2008ApJS..175..277D
[LBC2011] 2605 34 36.510+22 01 01.2177DIR2011ApJS..194...30L
[LBC2011] 2805 34 36.510+22 01 05.6277DIR2011ApJS..194...30L
[LBC2011] 2705 34 36.750+22 01 02.4177DIR2011ApJS..194...30L
2MASS J05342840+220138705 34 28.407+22 01 38.767760600BIR2003yCat.2246....0C
[LBC2011] 4305 34 32.990+22 01 58.4577DIR2011ApJS..194...30L
[LBC2011] 605 34 34.100+21 59 50.6977DIR2011ApJS..194...30L
[LBC2011] 4605 34 31.210+22 02 00.3177DIR2011ApJS..194...30L
[LBC2011] 3205 34 35.550+22 01 40.7377DIR2011ApJS..194...30L
[LBC2011] 2505 34 37.010+22 00 57.8077DIR2011ApJS..194...30L
[LBC2011] 505 34 34.370+21 59 49.7377DIR2011ApJS..194...30L
[LBC2011] 305 34 34.650+21 59 48.1777DIR2011ApJS..194...30L
[LBC2011] 705 34 34.290+21 59 44.9577DIR2011ApJS..194...30L
[LBC2011] 4205 34 33.190+22 02 05.9477DIR2011ApJS..194...30L
[LBC2011] 3505 34 35.080+22 01 56.1277DIR2011ApJS..194...30L
[LBC2011] 3305 34 35.500+22 01 51.9977DIR2011ApJS..194...30L
[LBC2011] 3605 34 34.810+22 01 59.3377DIR2011ApJS..194...30L
[LBC2011] 105 34 34.310+21 59 40.6977DIR2011ApJS..194...30L
[LBC2011] 3405 34 35.430+22 01 53.8877DIR2011ApJS..194...30L
[LBC2011] 805 34 32.010+21 59 32.4577DIR2011ApJS..194...30L
[LBF2010] Knot 105 34 34.39+21 59 40.066DNIR2010ApJ...716L...9L
[LBC2011] 205 34 34.220+21 59 38.1777DIR2011ApJS..194...30L
[LBC2011] 5005 34 27.760+22 01 49.5377DIR2011ApJS..194...30L
[LBC2011] 3705 34 34.110+22 02 08.0777DIR2011ApJS..194...30L
[LBC2011] 3905 34 33.170+22 02 12.9977DIR2011ApJS..194...30L
[LBC2011] 4005 34 32.380+22 02 15.1277DIR2011ApJS..194...30L
2MASS J05343759+220122205 34 37.590+22 01 22.2777707088BIR2003yCat.2246....0C
[LBC2011] 5105 34 27.600+22 01 51.5677DIR2011ApJS..194...30L
[LBC2011] 3805 34 34.100+22 02 12.1177DIR2011ApJS..194...30L
[LBC2011] 4105 34 32.180+22 02 18.2877DIR2011ApJS..194...30L
1RXS J053431.2+22021805 34 31.3+22 02 185530003000179D
2MASS J05343821+220048605 34 38.217+22 00 48.707760600BIR2003yCat.2246....0C
[LBC2011] 4705 34 28.640+22 02 07.7977DIR2011ApJS..194...30L
[LBC2011] 5205 34 27.080+22 01 52.1677DIR2011ApJS..194...30L
2MASS J05343755+220001005 34 37.55+22 00 01.066606089BIR2003yCat.2246....0C
[LBC2011] 4805 34 28.400+22 02 11.4977DIR2011ApJS..194...30L
[LBC2011] 4905 34 28.140+22 02 12.4877DIR2011ApJS..194...30L
[LBC2011] 1605 34 37.090+21 59 28.4677DIR2011ApJS..194...30L
[LBC2011] 2005 34 38.900+21 59 57.2877DIR2011ApJS..194...30L
[LBC2011] 1905 34 38.910+21 59 53.6377DIR2011ApJS..194...30L
[LBC2011] 2405 34 39.860+22 00 24.2877DIR2011ApJS..194...30L
JCMTSF J053427.4+22022705 34 27.4+22 02 275510000100000Emm2008ApJS..175..277D
[SPB96] 88905 34 29.95+21 59 00.066300300179D
JCMTSF J053439.5+22000205 34 39.5+22 00 025510000100000Emm2008ApJS..175..277D
[LBC2011] 1505 34 37.380+21 59 22.3377DIR2011ApJS..194...30L
[LBC2011] 2105 34 39.350+21 59 53.5477DIR2011ApJS..194...30L
-
-
- diff --git a/astroquery/simbad/tests/data/query_error.data b/astroquery/simbad/tests/data/query_error.data deleted file mode 100644 index e07daa91ce..0000000000 --- a/astroquery/simbad/tests/data/query_error.data +++ /dev/null @@ -1,28 +0,0 @@ -::script:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - -votable {main_id,coordinates} -votable open -query coo -67.02084 -29.75447 radius=5.0d frame=GAL -votable close - -::console::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - -C.D.S. - SIMBAD4 rel 1.207 - 2013.06.30CEST18:55:01 -total execution time: 48.562 secs -simbatch done - -::error::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - -[3] IO error while adding the object list in the VOTable: null -[4] IO Error while closing the VOTable: null - -::data:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - - - - - - -Simbad script executed on 2013.06.30CEST18:55:01 - diff --git a/astroquery/simbad/tests/data/query_id.data b/astroquery/simbad/tests/data/query_id.data deleted file mode 100644 index fb7582fe61..0000000000 --- a/astroquery/simbad/tests/data/query_id.data +++ /dev/null @@ -1,67 +0,0 @@ -::script:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - -votable {main_id, coordinates} -votable open -query id m1 -votable close - -::console::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - -C.D.S. - SIMBAD4 rel 1.207 - 2013.06.28CEST05:56:24 -total execution time: 0.143 secs -simbatch done - -::data:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - - - - - - -
Simbad script executed on 2013.06.28CEST05:56:24 - -Main identifier for an object - - - -Right ascension - - -Declination - - -Right ascension precision - - -Declination precision - - -Coordinate error major axis - - -Coordinate error minor axis - - -Coordinate error angle - - -Coordinate quality - - -Wavelength class for the origin of the coordinates (R,I,V,U,X,G) - - -Coordinate reference - - - - - - - - -
M 105 34 31.94+22 00 52.266CRad2011A&A...533A..10L
-
-
- diff --git a/astroquery/simbad/tests/data/query_objectids.data b/astroquery/simbad/tests/data/query_objectids.data deleted file mode 100644 index da33ffe7b2..0000000000 --- a/astroquery/simbad/tests/data/query_objectids.data +++ /dev/null @@ -1,57 +0,0 @@ -::script:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - -format object "%IDLIST" -query id Polaris - -::console::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - -C.D.S. - SIMBAD4 rel 1.223 - 2014.07.01CEST17:29:50 -total execution time: 0.031 secs -simbatch done - -::data:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - -ADS 1477 AP -** STF 93A -WDS J02318+8916A -** WRH 39 -NAME Lodestar -PLX 299 -SBC9 76 -* 1 UMi -* alf UMi -AAVSO 0122+88 -ADS 1477 A -AG+89 4 -BD+88 8 -CCDM J02319+8915A -CSI+88 8 1 -FK5 907 -GC 2243 -GCRV 1037 -GEN# +1.00008890A -GSC 04628-00237 -HD 8890 -HIC 11767 -HIP 11767 -HR 424 -IDS 01226+8846 A -IRAS 01490+8901 -JP11 498 -N30 381 -NAME NORTH STAR -NAME POLARIS -PMC 90-93 640 -PPM 431 -ROT 3491 -SAO 308 -SBC7 51 -SKY# 3738 -TD1 835 -TYC 4628-237-1 -UBV 21589 -UBV M 8201 -V* alf UMi -PLX 299.00 -WDS J02318+8916Aa,Ab - diff --git a/astroquery/simbad/tests/data/query_sample.data b/astroquery/simbad/tests/data/query_sample.data deleted file mode 100644 index 7d28ae7df7..0000000000 --- a/astroquery/simbad/tests/data/query_sample.data +++ /dev/null @@ -1,48 +0,0 @@ -::script:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - -votable {main_id,ra(d),dec(d)} -votable open -query sample otype=SNR -votable close - -::console::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - -C.D.S. - SIMBAD4 rel 1.209 - 2013.09.18CEST08:02:13 -total execution time: 5.766 secs -simbatch done - -::data:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - - - - - - -Simbad script executed on 2013.09.18CEST08:02:13 - -Main identifier for an object - - - -Right ascension - - -Declination - - - - - - - - - - - - - -
[AU88] 5.95-37.9011.88896-25.28775
SNR G315.0-02.3220.767-62.462
[DD88] 14192.7167+41.1211
SNR B013152+30281023.67096+30.72439
GAL 296.59-00.98178.7125-63.1483
MFBL 16114.2342+65.5683
-
-
- diff --git a/astroquery/simbad/tests/data/query_sample_region.data b/astroquery/simbad/tests/data/query_sample_region.data deleted file mode 100644 index f4d69615fa..0000000000 --- a/astroquery/simbad/tests/data/query_sample_region.data +++ /dev/null @@ -1,75 +0,0 @@ -::script:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - -votable {main_id,coordinates} -votable open -query sample region(box, GAL, 49.89 -0.3, 0.5d 0.5d) & otype=HII -votable close - -::console::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - -C.D.S. - SIMBAD4 rel 1.209 - 2013.09.18CEST08:03:57 -total execution time: 0.178 secs -simbatch done - -::data:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - - - - - - -Simbad script executed on 2013.09.18CEST08:03:57 - -Main identifier for an object - - - -Right ascension - - -Declination - - -Right ascension precision - - -Declination precision - - -Coordinate error major axis - - -Coordinate error minor axis - - -Coordinate error angle - - -Coordinate quality - - -Wavelength class for the origin of the coordinates (R,I,V,U,X,G) - - -Coordinate reference - - - - - - - - - - - - - - - - -
IRAS 19220+143219 24 20.05+14 38 03.666Dmm2002ApJ...566..945B
HRDS G049.998-0.12519 23 46.1+15 04 5455ERad2011ApJS..194...32A
[ABJ2009] C50.02-0.0819 23 39+15 07.344ERad2009ApJS..181..255A
[WAM82] 049.704-0.17219 23 21.9+14 48 015530003000175D
[ABJ2009] C49.70-0.1719 23 21+14 47.844ERad2009ApJS..181..255A
[KB94] 6519 23 38.4+15 07 3955D1989ApJS...71..469L
[LPH96] 049.704-0.17219 23 21.9+14 48 015530003000175D
IRAS 19214+145819 23 46.1+15 04 51553000300097D
HRDS G050.039-0.27419 24 23.5+15 02 4955D2012ApJ...759...96B
-
-
- diff --git a/astroquery/simbad/tests/data/simbad_basic_columns.xml b/astroquery/simbad/tests/data/simbad_basic_columns.xml new file mode 100644 index 0000000000..2a20853c80 --- /dev/null +++ b/astroquery/simbad/tests/data/simbad_basic_columns.xml @@ -0,0 +1,579 @@ + + + + + + + + table name from TAP_SCHEMA.tables + + + + + column name + + + + + ADQL datatype as in section 2.5 + + + + + brief description of column + + + + + unit in VO standard format + + + + + UCD of column if any + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
basicdecDOUBLEDeclinationdegpos.eq.dec;meta.main
basicmain_idVARCHARMain identifier for an object + meta.id;meta.main
basicotype_txtVARCHARObject type + src.class
basicraDOUBLERight ascensiondegpos.eq.ra;meta.main
basiccoo_bibcodeCHARCoordinate reference + meta.bib.bibcode;pos.eq
basiccoo_err_angleSMALLINTCoordinate error angledegpos.posAng;pos.errorEllipse;pos.eq
basiccoo_err_majREALCoordinate error major axismasphys.angSize.smajAxis;pos.errorEllipse;pos.eq
basiccoo_err_maj_precSMALLINTCoordinate error major axis precision + +
basiccoo_err_minREALCoordinate error minor axismasphys.angSize.sminAxis;pos.errorEllipse;pos.eq
basiccoo_err_min_precSMALLINTCoordinate error minor axis precision + +
basiccoo_qualCHARCoordinate quality + meta.code.qual;pos.eq
basiccoo_wavelengthCHARWavelength class for the origin of the coordinates (R,I,V,U,X,G) + instr.bandpass;pos.eq
basicdec_precSMALLINTDeclination precision + +
basicgaldim_angleSMALLINTGalaxy ellipse angledegpos.posAng
basicgaldim_bibcodeCHARGalaxy dimension reference + meta.bib.bibcode;phys.angSize
basicgaldim_majaxisREALAngular size major axisarcminphys.angSize.smajAxis
basicgaldim_majaxis_precSMALLINTAngular size major axis precision + +
basicgaldim_minaxisREALAngular size minor axisarcminphys.angSize.sminAxis
basicgaldim_minaxis_precSMALLINTAngular size minor axis precision + +
basicgaldim_qualCHARGalaxy dimension quality + meta.code.qual;phys.angSize
basicgaldim_wavelengthCHARWavelength class for the origin of the Galaxy dimension + instr.bandpass;phys.angSize
basichpxBIGINTHealpix number at ORDER=10 + meta.id
basicmorph_bibcodeCHARmorphological type reference + meta.bib.bibcode;src.morph.type
basicmorph_qualCHARMorphological type quality + meta.code.qual;src.morph.type
basicmorph_typeVARCHARMorphological type + src.morph.type
basicnbrefINTEGERnumber of references + meta.bib;meta.number
basicoidBIGINTObject internal identifier + meta.record;meta.id
basicotypeVARCHARObject type + src.class
basicplx_bibcodeCHARParallax reference + meta.bib.bibcode;pos.parallax.trig
basicplx_errREALParallax errormasstat.error;pos.parallax.trig
basicplx_err_precSMALLINTParallax error precision + +
basicplx_precSMALLINTParallax precision + +
basicplx_qualCHARParallax quality + meta.code.qual;pos.parallax.trig
basicplx_valueDOUBLEParallaxmaspos.parallax.trig
basicpm_bibcodeCHARProper motion reference + meta.bib.bibcode;pos.pm
basicpm_err_angleSMALLINTProper motion error angledegpos.posAng;pos.errorEllipse;pos.pm
basicpm_err_majREALProper motion error major axismas.yr-1phys.angSize.smajAxis;pos.errorEllipse;pos.pm
basicpm_err_maj_precSMALLINTProper motion error major axis precision + +
basicpm_err_minREALProper motion error minor axismas.yr-1phys.angSize.sminAxis;pos.errorEllipse;pos.pm
basicpm_err_min_precSMALLINTProper motion error minor axis precision + +
basicpm_qualCHARProper motion quality + meta.code.qual;pos.pm
basicpmdecDOUBLEProper motion in DECmas.yr-1pos.pm;pos.eq.dec
basicpmdec_precSMALLINTProper motion in DEC precision + +
basicpmraDOUBLEProper motion in RAmas.yr-1pos.pm;pos.eq.ra
basicpmra_precSMALLINTProper motion in RA precision + +
basicra_precSMALLINTRight ascension precision + +
basicrvz_bibcodeCHARRadial velocity / redshift reference + meta.bib.bibcode;spect.dopplerVeloc
basicrvz_errREALRadial velocity / redshift errorkm.s-1stat.error;spect.dopplerVeloc
basicrvz_err_precSMALLINTRadial velocity / redshift error precision + +
basicrvz_natureCHARvelocity / redshift nature + meta.code;spect.dopplerVeloc
basicrvz_qualCHARRadial velocity / redshift quality + meta.code.qual;spect.dopplerVeloc
basicrvz_radvelDOUBLERadial Velocitykm.s-1spect.dopplerVeloc.opt
basicrvz_radvel_precSMALLINTRadial velocity precision + +
basicrvz_redshiftDOUBLEredshift + src.redshift
basicrvz_redshift_precSMALLINTredshift precision + +
basicrvz_typeCHARRadial velocity / redshift type + +
basicrvz_wavelengthCHARWavelength class for the origin of the radial velocity/reshift + instr.bandpass;spect.dopplerVeloc.opt
basicsp_bibcodeCHARspectral type reference + meta.bib.bibcode;src.spType
basicsp_qualCHARSpectral type quality + meta.code.qual;src.spType
basicsp_typeVARCHARMK spectral type + src.spType
basicupdate_datedateDate of last modification + time.processing
basicvlsrDOUBLEvelocity in Local Standard of Rest reference framekm.s-1phys.veloc;pos.lsr;stat.mean
basicvlsr_bibcodeCHARReference for the origin of the LSR velocity + meta.bib.bibcode;phys.veloc;pos.lsr
basicvlsr_errDOUBLEError incertainty of the VLSR velocitykm.s-1stat.error;phys.veloc;pos.lsr
basicvlsr_maxREALMaximum for the mean value of the LSR velocitykm.s-1phys.veloc;pos.lsr;stat.max
basicvlsr_minREALMinimum for the mean value of the LSR velocitykm.s-1phys.veloc;pos.lsr;stat.min
basicvlsr_wavelengthCHARWavelength class for the origin of the LSR velocity + instr.bandpass;phys.veloc;pos.lsr
+
+
diff --git a/astroquery/simbad/tests/data/simbad_linked_to_basic.xml b/astroquery/simbad/tests/data/simbad_linked_to_basic.xml new file mode 100644 index 0000000000..9372985aa1 --- /dev/null +++ b/astroquery/simbad/tests/data/simbad_linked_to_basic.xml @@ -0,0 +1,177 @@ + + + + + + + + fully qualified table name + + + + + key column name in the from_table + + + + + fully qualified table name + + + + + key column name in the target_table + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
idsoidrefbasicoid
otypedefotypebasicotype
identoidrefbasicoid
fluxoidrefbasicoid
allfluxesoidrefbasicoid
has_refoidrefbasicoid
mesDistanceoidrefbasicoid
mesDiameteroidrefbasicoid
mesFe_hoidrefbasicoid
mesISOoidrefbasicoid
mesIUEoidrefbasicoid
mesPLXoidrefbasicoid
mesPMoidrefbasicoid
mesRotoidrefbasicoid
mesVaroidrefbasicoid
mesVelocitiesoidrefbasicoid
mesXmmoidrefbasicoid
h_linkparentbasicoid
h_linkchildbasicoid
mesHerscheloidrefbasicoid
bibliooidrefbasicoid
alltypesoidrefbasicoid
otypesoidrefbasicoid
mesSpToidrefbasicoid
+
+
diff --git a/astroquery/simbad/tests/data/simbad_output_options.xml b/astroquery/simbad/tests/data/simbad_output_options.xml new file mode 100644 index 0000000000..45ac029e43 --- /dev/null +++ b/astroquery/simbad/tests/data/simbad_output_options.xml @@ -0,0 +1,514 @@ + + + + + + + + column name + + + + + brief description of column + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
mesDiameterCollection of stellar diameters.table
mesPMCollection of proper motions.table
mesISOInfrared Space Observatory (ISO) observing log.table
mesSpTCollection of spectral types.table
allfluxesall flux/magnitudes U,B,V,I,J,H,K,u_,g_,r_,i_,z_table
identIdentifiers of an astronomical objecttable
fluxMagnitude/Flux information about an astronomical objecttable
mesPLXCollection of trigonometric parallaxes.table
otypedefall names and definitions for the object typestable
mesDistanceCollection of distances (pc, kpc or Mpc) by several means.table
otypesList of all object types associated with an objecttable
mesVarCollection of stellar variability types and periods.table
mesXmmXMM observing log.table
mesVelocitiesCollection of HRV, Vlsr, cz and redshifts.table
has_refAssociations between astronomical objects and their bibliographic referencestable
mesRotStellar Rotational Velocities.table
biblioBibliographytable
idsall names concatenated with pipetable
mesHerschelThe Herschel observing Logtable
mesIUEInternational Ultraviolet Explorer observing log.table
mesFe_hCollection of metallicity, as well as Teff, logg for stars.table
alltypesall object types concatenated with pipetable
basicGeneral data about an astronomical objecttable
decDeclinationcolumn of basic
main_idMain identifier for an objectcolumn of basic
otype_txtObject typecolumn of basic
raRight ascensioncolumn of basic
coo_bibcodeCoordinate referencecolumn of basic
coo_err_angleCoordinate error anglecolumn of basic
coo_err_majCoordinate error major axiscolumn of basic
coo_err_maj_precCoordinate error major axis precisioncolumn of basic
coo_err_minCoordinate error minor axiscolumn of basic
coo_err_min_precCoordinate error minor axis precisioncolumn of basic
coo_qualCoordinate qualitycolumn of basic
coo_wavelengthWavelength class for the origin of the coordinates (R,I,V,U,X,G)column of basic
dec_precDeclination precisioncolumn of basic
galdim_angleGalaxy ellipse anglecolumn of basic
galdim_bibcodeGalaxy dimension referencecolumn of basic
galdim_majaxisAngular size major axiscolumn of basic
galdim_majaxis_precAngular size major axis precisioncolumn of basic
galdim_minaxisAngular size minor axiscolumn of basic
galdim_minaxis_precAngular size minor axis precisioncolumn of basic
galdim_qualGalaxy dimension qualitycolumn of basic
galdim_wavelengthWavelength class for the origin of the Galaxy dimensioncolumn of basic
hpxHealpix number at ORDER=10column of basic
morph_bibcodemorphological type referencecolumn of basic
morph_qualMorphological type qualitycolumn of basic
morph_typeMorphological typecolumn of basic
nbrefnumber of referencescolumn of basic
oidObject internal identifiercolumn of basic
otypeObject typecolumn of basic
plx_bibcodeParallax referencecolumn of basic
plx_errParallax errorcolumn of basic
plx_err_precParallax error precisioncolumn of basic
plx_precParallax precisioncolumn of basic
plx_qualParallax qualitycolumn of basic
plx_valueParallaxcolumn of basic
pm_bibcodeProper motion referencecolumn of basic
pm_err_angleProper motion error anglecolumn of basic
pm_err_majProper motion error major axiscolumn of basic
pm_err_maj_precProper motion error major axis precisioncolumn of basic
pm_err_minProper motion error minor axiscolumn of basic
pm_err_min_precProper motion error minor axis precisioncolumn of basic
pm_qualProper motion qualitycolumn of basic
pmdecProper motion in DECcolumn of basic
pmdec_precProper motion in DEC precisioncolumn of basic
pmraProper motion in RAcolumn of basic
pmra_precProper motion in RA precisioncolumn of basic
ra_precRight ascension precisioncolumn of basic
rvz_bibcodeRadial velocity / redshift referencecolumn of basic
rvz_errRadial velocity / redshift errorcolumn of basic
rvz_err_precRadial velocity / redshift error precisioncolumn of basic
rvz_naturevelocity / redshift naturecolumn of basic
rvz_qualRadial velocity / redshift qualitycolumn of basic
rvz_radvelRadial Velocitycolumn of basic
rvz_radvel_precRadial velocity precisioncolumn of basic
rvz_redshiftredshiftcolumn of basic
rvz_redshift_precredshift precisioncolumn of basic
rvz_typeRadial velocity / redshift typecolumn of basic
rvz_wavelengthWavelength class for the origin of the radial velocity/reshiftcolumn of basic
sp_bibcodespectral type referencecolumn of basic
sp_qualSpectral type qualitycolumn of basic
sp_typeMK spectral typecolumn of basic
update_dateDate of last modificationcolumn of basic
vlsrvelocity in Local Standard of Rest reference framecolumn of basic
vlsr_bibcodeReference for the origin of the LSR velocitycolumn of basic
vlsr_errError incertainty of the VLSR velocitycolumn of basic
vlsr_maxMaximum for the mean value of the LSR velocitycolumn of basic
vlsr_minMinimum for the mean value of the LSR velocitycolumn of basic
vlsr_wavelengthWavelength class for the origin of the LSR velocitycolumn of basic
coordinatesall fields related with coordinatesbundle of basic columns
dimmajor and minor axis, angle and inclinationbundle of basic columns
dimensionsall fields related to object dimensionsbundle of basic columns
morphtypeall fields related to the morphological typebundle of basic columns
parallaxall fields related to parallaxesbundle of basic columns
propermotionsall fields related with the proper motionsbundle of basic columns
spall fields related with the spectral typebundle of basic columns
velocityall fields related with radial velocity and redshiftbundle of basic columns
+
+
diff --git a/astroquery/simbad/tests/test_simbad.py b/astroquery/simbad/tests/test_simbad.py index 45047ba725..d040979c9f 100644 --- a/astroquery/simbad/tests/test_simbad.py +++ b/astroquery/simbad/tests/test_simbad.py @@ -1,77 +1,92 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -import os +from pathlib import Path import re -import pytest -import astropy.units as u from astropy.coordinates import SkyCoord +from astropy.io.votable import parse_single_table from astropy.table import Table -import numpy as np +import astropy.units as u +from astropy.utils.exceptions import AstropyDeprecationWarning +from pyvo.dal.tap import TAPService + +import pytest from ... import simbad -from astroquery.utils.mocks import MockResponse -from ...query import AstroQuery -from ...exceptions import TableParseError from .test_simbad_remote import multicoords +from astroquery.exceptions import LargeQueryWarning + GALACTIC_COORDS = SkyCoord(l=-67.02084 * u.deg, b=-29.75447 * u.deg, frame="galactic") ICRS_COORDS = SkyCoord("05h35m17.3s -05h23m28s", frame="icrs") FK4_COORDS = SkyCoord(ra=84.90759 * u.deg, dec=-80.89403 * u.deg, frame="fk4") FK5_COORDS = SkyCoord(ra=83.82207 * u.deg, dec=-80.86667 * u.deg, frame="fk5") -DATA_FILES = { - 'id': 'query_id.data', - 'coo': 'query_coo.data', - 'cat': 'query_cat.data', - 'bibobj': 'query_bibobj.data', - 'bibcode': 'query_bibcode.data', - 'objectids': 'query_objectids.data', - 'error': 'query_error.data', - 'sample': 'query_sample.data', - 'region': 'query_sample_region.data', -} - - -class MockResponseSimbad(MockResponse): - query_regex = re.compile(r'query\s+([a-z]+)\s+') - - def __init__(self, script, cache=False, **kwargs): - # preserve, e.g., headers - super().__init__(**kwargs) - self.content = self.get_content(script) - - def get_content(self, script): - match = self.query_regex.search(script) - if match: - filename = DATA_FILES[match.group(1)] - with open(data_path(filename), "rb") as infile: - content = infile.read() - return content +@pytest.fixture() +def _mock_simbad_class(monkeypatch): + """Avoid a TAP request for properties in the tests.""" + + with open(Path(__file__).parent / "data" / "simbad_output_options.xml", "rb") as f: + table = parse_single_table(f).to_table() + # This should not change too often, to regenerate this file, do: + # >>> from astroquery.simbad import Simbad + # >>> options = Simbad.list_votable_fields() + # >>> options.write("simbad_output_options.xml", format="votable") + monkeypatch.setattr(simbad.SimbadClass, "hardlimit", 2000000) + monkeypatch.setattr(simbad.SimbadClass, "list_votable_fields", lambda self: table) + + +@pytest.fixture() +def _mock_basic_columns(monkeypatch): + """Avoid a request to get the columns of basic.""" + with open(Path(__file__).parent / "data" / "simbad_basic_columns.xml", "rb") as f: + table = parse_single_table(f).to_table() + # This should not change too often, to regenerate this file, do: + # >>> from astroquery.simbad import Simbad + # >>> columns = Simbad.list_columns("basic") + # >>> columns.write("simbad_basic_columns.xml", format="votable") + + def _mock_list_columns(self, table_name=None): + """Patch a call with basic as an argument only.""" + if table_name == "basic": + return table + # to test in add_votable_fields + if table_name == "mesdistance": + return Table( + [["bibcode"]], names=["column_name"] + ) + return simbad.SimbadClass().list_columns(table_name) + + monkeypatch.setattr(simbad.SimbadClass, "list_columns", _mock_list_columns) + + +@pytest.fixture() +def _mock_linked_to_basic(monkeypatch): + """Avoid a request to get the columns of basic.""" + with open(Path(__file__).parent / "data" / "simbad_linked_to_basic.xml", "rb") as f: + table = parse_single_table(f).to_table() + # This should not change too often, to regenerate this file, do: + # >>> from astroquery.simbad import Simbad + # >>> linked = Simbad.list_linked_tables("basic") + # >>> linked.write("simbad_linked_to_basic.xml", format="votable") + + def _mock_linked_to_basic(self, table_name=None): + """Patch a call with basic as an argument only.""" + if table_name == "basic": + return table + return simbad.SimbadClass().list_linked_tables(table_name) + + monkeypatch.setattr(simbad.SimbadClass, "list_linked_tables", _mock_linked_to_basic) -def data_path(filename): - data_dir = os.path.join(os.path.dirname(__file__), 'data') - return os.path.join(data_dir, filename) +def test_adql_parameter(): + # escape single quotes + assert simbad.core._adql_parameter("Barnard's galaxy") == "Barnard''s galaxy" -@pytest.fixture -def patch_post(request): - mp = request.getfixturevalue("monkeypatch") - - mp.setattr(simbad.SimbadClass, '_request', post_mockreturn) - - return mp - - -def post_mockreturn(self, method, url, data, timeout, **kwargs): - response = MockResponseSimbad(data['script'], **kwargs) - - class last_query: - pass - self._last_query = last_query() - self._last_query.data = data - return response +# ------------------ +# Testing properties +# ------------------ def test_simbad_mirror(): @@ -83,10 +98,27 @@ def test_simbad_mirror(): assert simbad_instance.server == "simbad.harvard.edu" # but not to an invalid mirror with pytest.raises(ValueError, - match="'test' does not correspond to a Simbad server, *"): + match="'test' does not correspond to a SIMBAD server, *"): simbad_instance.server = "test" +def test_simbad_row_limit(): + simbad_instance = simbad.SimbadClass() + # default value is -1 + assert simbad_instance.ROW_LIMIT == -1 + # we can assign afterward + simbad_instance.ROW_LIMIT = 5 + assert simbad_instance.ROW_LIMIT == 5 + # or from the beginning + simbad_instance = simbad.SimbadClass(ROW_LIMIT=10) + assert simbad_instance.ROW_LIMIT == 10 + # non-valid values trigger an error + with pytest.raises(ValueError, match="ROW_LIMIT can be either -1 to set the limit " + "to SIMBAD's maximum capability, 0 to retrieve an empty table, " + "or a positive integer."): + simbad_instance = simbad.SimbadClass(ROW_LIMIT='test') + + def test_simbad_create_tap_service(): simbad_instance = simbad.Simbad() # newly created should have no tap service @@ -96,400 +128,381 @@ def test_simbad_create_tap_service(): assert 'simbad/sim-tap' in simbadtap.baseurl -def test_adql_parameter(): - # escape single quotes - assert simbad.core._adql_parameter("Barnard's galaxy") == "Barnard''s galaxy" - - -@pytest.mark.parametrize(('radius', 'expected_radius'), - [('5d0m0s', '5.0d'), - ('5d', '5.0d'), - ('5.0d', '5.0d'), - (5 * u.deg, '5.0d'), - (5.0 * u.deg, '5.0d'), - (1.2 * u.deg, '1.2d'), - (0.5 * u.deg, '30.0m'), - ('0d1m12s', '1.2m'), - (0.003 * u.deg, '10.8s'), - ('0d0m15s', '15.0s') - ]) -def test_parse_radius(radius, expected_radius): - actual = simbad.core._parse_radius(radius) - assert actual == expected_radius - - -@pytest.mark.parametrize(('ra', 'dec', 'expected_ra', 'expected_dec'), - [(ICRS_COORDS.ra, ICRS_COORDS.dec, u'5:35:17.3', - u'-80:52:00') - ]) -def test_to_simbad_format(ra, dec, expected_ra, expected_dec): - actual_ra, actual_dec = simbad.core._to_simbad_format(ra, dec) - assert (actual_ra, actual_dec) == (expected_ra, expected_dec) - - -@pytest.mark.parametrize(('coordinates', 'expected_frame'), - [(GALACTIC_COORDS, 'GAL'), - (ICRS_COORDS, 'ICRS'), - (FK4_COORDS, 'FK4'), - (FK5_COORDS, 'FK5') - ]) -def test_get_frame_coordinates(coordinates, expected_frame): - actual_frame = simbad.core._get_frame_coords(coordinates)[2] - assert actual_frame == expected_frame - if actual_frame == 'GAL': - l_gal, b_gal = simbad.core._get_frame_coords(coordinates)[:2] - np.testing.assert_almost_equal(float(l_gal) % 360, -67.02084 % 360) - np.testing.assert_almost_equal(float(b_gal), -29.75447) - - -def test_parse_result(): - sb = simbad.core.Simbad() - # need _last_query to be defined - sb._last_query = AstroQuery('GET', 'http://dummy') - result1 = sb._parse_result( - MockResponseSimbad('query id '), simbad.core.SimbadVOTableResult) - assert isinstance(result1, Table) - expected_exception = 'Failed to parse SIMBAD result! The raw response can be found in self.last_response, ' - - with pytest.raises(TableParseError, match=expected_exception): - sb._parse_result(MockResponseSimbad('query error '), - simbad.core.SimbadVOTableResult) - - assert isinstance(sb.last_response.text, str) - assert isinstance(sb.last_response.content, bytes) - - -votable_fields = ",".join(simbad.core.Simbad.get_votable_fields()) - - -@pytest.mark.parametrize(('args', 'kwargs', 'expected_script'), - [(["m [0-9]"], dict(wildcard=True, - caller='query_object_async'), - ("\nvotable {" + votable_fields + "}\n" - "votable open\n" - "query id wildcard m [0-9] \n" - "votable close" - )), - (["2006ApJ"], dict(caller='query_bibcode_async', - get_raw=True), - ("\n\nquery bibcode 2006ApJ \n")) - ]) -def test_args_to_payload(args, kwargs, expected_script): - script = simbad.Simbad._args_to_payload(*args, **kwargs)['script'] - assert script == expected_script - - -@pytest.mark.parametrize(('epoch', 'equinox'), - [(2000, 'thousand'), - ('J-2000', None), - (None, '10e3b') - ]) -def test_validation(epoch, equinox): - with pytest.raises(ValueError): - # only one of these has to raise an exception - if equinox is not None: - simbad.core.validate_equinox(equinox) - if epoch is not None: - simbad.core.validate_epoch(epoch) - - -@pytest.mark.parametrize(('bibcode', 'wildcard'), - [('2006ApJ*', True), - ('2005A&A.430.165F', None) - ]) -def test_query_bibcode_async(patch_post, bibcode, wildcard): - response1 = simbad.core.Simbad.query_bibcode_async(bibcode, - wildcard=wildcard) - response2 = simbad.core.Simbad().query_bibcode_async(bibcode, - wildcard=wildcard) - assert response1 is not None and response2 is not None - assert response1.content == response2.content - - -def test_query_bibcode_class(patch_post): - result1 = simbad.core.Simbad.query_bibcode("2006ApJ*", wildcard=True) - assert isinstance(result1, Table) - - -def test_query_bibcode_instance(patch_post): - S = simbad.core.Simbad() - result2 = S.query_bibcode("2006ApJ*", wildcard=True) - assert isinstance(result2, Table) - - -def test_query_objectids_async(patch_post): - response1 = simbad.core.Simbad.query_objectids_async('Polaris') - response2 = simbad.core.Simbad().query_objectids_async('Polaris') - assert response1 is not None and response2 is not None - assert response1.content == response2.content - - -def test_query_objectids(patch_post): - result1 = simbad.core.Simbad.query_objectids('Polaris') - result2 = simbad.core.Simbad().query_objectids('Polaris') - assert isinstance(result1, Table) - assert isinstance(result2, Table) - - -def test_query_bibobj_async(patch_post): - response1 = simbad.core.Simbad.query_bibobj_async('2005A&A.430.165F') - response2 = simbad.core.Simbad().query_bibobj_async('2005A&A.430.165F') - assert response1 is not None and response2 is not None - assert response1.content == response2.content - - -def test_query_bibobj(patch_post): - result1 = simbad.core.Simbad.query_bibobj('2005A&A.430.165F') - result2 = simbad.core.Simbad().query_bibobj('2005A&A.430.165F') - assert isinstance(result1, Table) - assert isinstance(result2, Table) - - -def test_query_catalog_async(patch_post): - response1 = simbad.core.Simbad.query_catalog_async('m') - response2 = simbad.core.Simbad().query_catalog_async('m') - assert response1 is not None and response2 is not None - assert response1.content == response2.content - - -def test_query_catalog(patch_post): - result1 = simbad.core.Simbad.query_catalog('m') - result2 = simbad.core.Simbad().query_catalog('m') - assert isinstance(result1, Table) - assert isinstance(result2, Table) - - -@pytest.mark.parametrize(('coordinates', 'radius', 'equinox', 'epoch'), - [(ICRS_COORDS, 2*u.arcmin, 2000.0, 'J2000'), - (GALACTIC_COORDS, 5 * u.deg, 2000.0, 'J2000'), - (FK4_COORDS, '5d0m0s', 2000.0, 'J2000'), - (FK5_COORDS, 2*u.arcmin, 2000.0, 'J2000'), - (multicoords, 0.5*u.arcsec, 2000.0, 'J2000'), - (multicoords, "0.5s", 2000.0, 'J2000'), - ]) -def test_query_region_async(patch_post, coordinates, radius, equinox, epoch): - response1 = simbad.core.Simbad.query_region_async( - coordinates, radius=radius, equinox=equinox, epoch=epoch) +def test_simbad_hardlimit(monkeypatch): + simbad_instance = simbad.Simbad() + monkeypatch.setattr(TAPService, "hardlimit", 2) + assert simbad_instance.hardlimit == 2 - response2 = simbad.core.Simbad().query_region_async( - coordinates, radius=radius, equinox=equinox, epoch=epoch) - assert response1 is not None and response2 is not None - assert response1.content == response2.content +def test_initcolumns_in_output(): + simbad_instance = simbad.Simbad() + default_columns = simbad_instance.columns_in_output + # main_id from basic should be there + assert simbad.core._Column("basic", "main_id") in default_columns + # there are 8 default columns + assert len(default_columns) == 8 -@pytest.mark.parametrize(('coordinates', 'radius', 'equinox', 'epoch'), - [(ICRS_COORDS, 2*u.arcmin, 2000.0, 'J2000'), - (GALACTIC_COORDS, 5 * u.deg, 2000.0, 'J2000'), - (FK4_COORDS, '5d0m0s', 2000.0, 'J2000'), - (FK5_COORDS, 2*u.arcmin, 2000.0, 'J2000') +@pytest.mark.usefixtures("_mock_simbad_class") +def test_mocked_simbad(): + simbad_instance = simbad.Simbad() + # this mocks the list_votable_fields + options = simbad_instance.list_votable_fields() + assert len(options) > 90 + # this mocks the hardlimit + assert simbad_instance.hardlimit == 2000000 + +# ---------------------------- +# Test output options settings +# ---------------------------- + + +@pytest.mark.usefixtures("_mock_basic_columns") +def test_votable_fields_utils(monkeypatch): + monkeypatch.setattr(simbad.SimbadClass, "query_tap", + lambda self, _: Table([["biblio"], ["biblio description"]], + names=["name", "description"], + dtype=["object", "object"])) + options = simbad.SimbadClass().list_votable_fields() + assert set(options.group_by("type").groups.keys["type"]) == {"table", + "column of basic", + "bundle of basic columns"} + + description = simbad.SimbadClass().get_field_description("velocity") + assert description == 'all fields related with radial velocity and redshift' + fields = simbad.SimbadClass().get_votable_fields() + expected_fields = [ + 'basic.main_id', 'basic.ra', 'basic.dec', 'basic.coo_err_maj', + 'basic.coo_err_min', 'basic.coo_err_angle', 'basic.coo_wavelength', + 'basic.coo_bibcode' + ] + assert fields == expected_fields + + +@pytest.mark.usefixtures("_mock_simbad_class") +@pytest.mark.usefixtures("_mock_basic_columns") +@pytest.mark.usefixtures("_mock_linked_to_basic") +def test_reset_votable_fields(): + simbad_instance = simbad.Simbad() + # add one + simbad_instance.add_votable_fields("otype") + assert simbad.core._Column("basic", "otype") in simbad_instance.columns_in_output + # reset + simbad_instance.reset_votable_fields() + assert not simbad.core._Column("basic", "otype") in simbad_instance.columns_in_output + + +@pytest.mark.usefixtures("_mock_basic_columns") +@pytest.mark.parametrize(("bundle_name", "column"), + [("coordinates", simbad.core._Column("basic", "ra")), + ("coordinates", simbad.core._Column("basic", "coo_bibcode")), + ("dim", simbad.core._Column("basic", "galdim_wavelength"))]) +def test_get_bundle_columns(bundle_name, column): + assert column in simbad.SimbadClass()._get_bundle_columns(bundle_name) + + +@pytest.mark.usefixtures("_mock_linked_to_basic") +def test_add_table_to_output(monkeypatch): + # if table = basic, no need to add a join + simbad_instance = simbad.Simbad() + simbad_instance._add_table_to_output("basic") + assert simbad.core._Column("basic", "*") in simbad_instance.columns_in_output + # cannot add h_link (two ways to join it, it's not a simple link) + with pytest.raises(ValueError, match="'h_link' has no explicit link to 'basic'.*"): + simbad_instance._add_table_to_output("h_link") + # add a table with a link and an alias needed + monkeypatch.setattr(simbad.SimbadClass, "list_columns", lambda self, _: Table([["oidref", "bibcode"]], + names=["column_name"])) + simbad_instance._add_table_to_output("mesDiameter") + assert simbad.core._Join("mesdiameter", + simbad.core._Column("basic", "oid"), + simbad.core._Column("mesdiameter", "oidref") + ) in simbad_instance.joins + assert simbad.core._Column("mesdiameter", "bibcode", '"mesdiameter.bibcode"' + ) in simbad_instance.columns_in_output + assert simbad.core._Column("mesdiameter", "oidref", '"mesdiameter.oidref"' + ) not in simbad_instance.columns_in_output + + +@pytest.mark.usefixtures("_mock_simbad_class") +@pytest.mark.usefixtures("_mock_basic_columns") +@pytest.mark.usefixtures("_mock_linked_to_basic") +def test_add_votable_fields(): + simbad_instance = simbad.Simbad() + # add columns from basic (one value) + simbad_instance.add_votable_fields("pmra") + assert simbad.core._Column("basic", "pmra") in simbad_instance.columns_in_output + # add two columns from basic + simbad_instance.add_votable_fields("pmdec", "pm_bibcodE") # also test case insensitive + expected = [simbad.core._Column("basic", "pmdec"), + simbad.core._Column("basic", "pm_bibcode")] + assert all(column in simbad_instance.columns_in_output for column in expected) + # add a table + simbad_instance.columns_in_output = [] + simbad_instance.add_votable_fields("basic") + assert [simbad.core._Column("basic", "*")] == simbad_instance.columns_in_output + # add a bundle + simbad_instance.add_votable_fields("dimensions") + assert simbad.core._Column("basic", "galdim_majaxis") in simbad_instance.columns_in_output + # a column which name has changed should raise a warning but still + # be added under its new name + simbad_instance.columns_in_output = [] + with pytest.warns(DeprecationWarning, match=r"'id\(1\)' has been renamed 'main_id'. You'll see it " + "appearing with its new name in the output table"): + simbad_instance.add_votable_fields("id(1)") + assert simbad.core._Column("basic", "main_id") in simbad_instance.columns_in_output + # a table which name has changed should raise a warning too + with pytest.warns(DeprecationWarning, match="'distance' has been renamed 'mesdistance'*"): + simbad_instance.add_votable_fields("distance") + # errors are raised for the deprecated fields with options + simbad_instance = simbad.SimbadClass() + with pytest.warns(DeprecationWarning, match=r"The notation \'flux\(X\)\' is deprecated since 0.4.8. *"): + simbad_instance.add_votable_fields("flux(u)") + assert "u_" in str(simbad_instance.columns_in_output) + with pytest.raises(ValueError, match="Coordinates conversion and formatting is no longer supported*"): + simbad_instance.add_votable_fields("coo(s)", "dec(d)") + with pytest.raises(ValueError, match="Catalog Ids are no longer supported as an output option.*"): + simbad_instance.add_votable_fields("ID(Gaia)") + with pytest.raises(ValueError, match="Selecting a range of years for bibcode is removed.*"): + simbad_instance.add_votable_fields("bibcodelist(2042-2050)") + # historical measurements + with pytest.raises(ValueError, match="'einstein' is no longer a part of SIMBAD.*"): + simbad_instance.add_votable_fields("einstein") + # typos should have suggestions + with pytest.raises(ValueError, match="'alltype' is not one of the accepted options which can be " + "listed with 'list_votable_fields'. Did you mean 'alltypes' or 'otype' or 'otypes'?"): + simbad_instance.add_votable_fields("ALLTYPE") + # bundles and tables require a connection to the tap_schema and are thus tested in test_simbad_remote + + +def test_list_wildcards(capsys): + simbad.SimbadClass.list_wildcards() + wildcards = capsys.readouterr() + assert "*: Any string of characters (including an empty one)" in wildcards.out + + +# ------------------------------------------ +# Test query_*** methods that call query_tap +# ------------------------------------------ + +@pytest.mark.usefixtures("_mock_simbad_class") +def test_query_bibcode_class(): + simbad_instance = simbad.Simbad() + # wildcard + adql = simbad_instance.query_bibcode("????LASP.*", wildcard=True, get_query_payload=True)["QUERY"] + assert "WHERE regexp(lowercase(bibcode), '^....lasp\\\\..*$') = 1" in adql + # with row limit and abstract + simbad_instance.ROW_LIMIT = 5 + adql = simbad_instance.query_bibcode("1968ZA.....68..366D", abstract=True, get_query_payload=True)["QUERY"] + assert adql == ('SELECT TOP 5 "bibcode", "doi", "journal", "nbobject", "page", "last_page",' + ' "title", "volume", "year", "abstract" FROM ref WHERE bibcode =' + ' \'1968ZA.....68..366D\' ORDER BY bibcode') + # with a criteria + adql = simbad_instance.query_bibcode("200*", wildcard=True, + criteria="abstract LIKE '%exoplanet%'", + get_query_payload=True)["QUERY"] + assert adql == ('SELECT TOP 5 "bibcode", "doi", "journal", "nbobject", "page", "last_page", ' + '"title", "volume", "year" FROM ref ' + 'WHERE regexp(lowercase(bibcode), \'^200.*$\') = 1 ' + 'AND abstract LIKE \'%exoplanet%\' ORDER BY bibcode') + + +@pytest.mark.usefixtures("_mock_simbad_class") +def test_query_objectids(): + adql = simbad.core.Simbad.query_objectids('Polaris', criteria="ident.id LIKE 'HD%'", + get_query_payload=True)["QUERY"] + expected = ("SELECT ident.id FROM ident AS id_typed JOIN ident USING(oidref)" + "WHERE id_typed.id = 'Polaris' AND ident.id LIKE 'HD%'") + assert adql == expected + + +@pytest.mark.usefixtures("_mock_simbad_class") +def test_query_bibobj(): + bibcode = '2005A&A.430.165F' + adql = simbad.core.Simbad.query_bibobj(bibcode, get_query_payload=True, + criteria="dec < 5")["QUERY"] + # test condition + assert f"WHERE bibcode = '{bibcode}' AND (dec < 5)" in adql + # test join + assert 'basic JOIN has_ref ON basic."oid" = has_ref."oidref"' in adql + + +@pytest.mark.usefixtures("_mock_simbad_class") +def test_query_catalog(): + simbad_instance = simbad.Simbad() + adql = simbad_instance.query_catalog('Gaia DR2', get_query_payload=True, + criteria="update_date < '2010-01-01'")["QUERY"] + where_clause = "WHERE id LIKE 'Gaia DR2 %' AND (update_date < '2010-01-01')" + assert adql.endswith(where_clause) + + +@pytest.mark.parametrize(('coordinates', 'radius', 'where'), + [(ICRS_COORDS, 2*u.arcmin, + r"WHERE CONTAINS\(POINT\('ICRS', basic\.ra, basic\.dec\), " + r"CIRCLE\('ICRS', 83\.\d*, -80\.\d*, 0\.\d*\)\) = 1"), + (GALACTIC_COORDS, 5 * u.deg, + r"WHERE CONTAINS\(POINT\(\'ICRS\', basic\.ra, basic\.dec\), " + r"CIRCLE\(\'ICRS\', 83\.\d*, -80\.\d*, 5\.0\)\) = 1"), + (FK4_COORDS, '5d0m0s', + r"WHERE CONTAINS\(POINT\(\'ICRS\', basic.ra, basic.dec\), " + r"CIRCLE\(\'ICRS\', 83.\d*, -80.\d*, 5.0\)\) = 1"), + (FK5_COORDS, 2*u.arcmin, + r"WHERE CONTAINS\(POINT\(\'ICRS\', basic.ra, basic.dec\), " + r"CIRCLE\(\'ICRS\', 83.\d*, -80.\d*, 0.\d*\)\) = 1"), + (multicoords, 0.5*u.arcsec, + r"WHERE \(CONTAINS\(POINT\(\'ICRS\', basic.ra, basic.dec\), " + r"CIRCLE\(\'ICRS\', 266.835, -28.38528, 0.\d*\)\) " + r"= 1 OR CONTAINS\(POINT\(\'ICRS\', basic.ra, basic.dec\), " + r"CIRCLE\(\'ICRS\', 266.835, -28.38528, 0.\d*\)\) = 1 \)"), + (multicoords, ["0.5s", "0.2s"], + r"WHERE \(CONTAINS\(POINT\(\'ICRS\', basic.ra, basic.dec\), " + r"CIRCLE\(\'ICRS\', 266.835, -28.38528, 0.\d*\)\) " + r"= 1 OR CONTAINS\(POINT\(\'ICRS\', basic.ra, basic.dec\), " + r"CIRCLE\(\'ICRS\', 266.835, -28.38528, 5.\d*e-05\)\) = 1 \)"), ]) -def test_query_region(patch_post, coordinates, radius, equinox, epoch): - result1 = simbad.core.Simbad.query_region(coordinates, radius=radius, - equinox=equinox, epoch=epoch) - result2 = simbad.core.Simbad().query_region(coordinates, radius=radius, - equinox=equinox, epoch=epoch) - assert isinstance(result1, Table) - assert isinstance(result2, Table) - - -@pytest.mark.parametrize(('coordinates', 'radius', 'equinox', 'epoch'), - [(ICRS_COORDS, 0, 2000.0, 'J2000')]) -def test_query_region_radius_error(patch_post, coordinates, radius, - equinox, epoch): - with pytest.raises(u.UnitsError): - simbad.core.Simbad.query_region( - coordinates, radius=radius, equinox=equinox, epoch=epoch) - +@pytest.mark.usefixtures("_mock_simbad_class") +def test_query_region(coordinates, radius, where): + adql = simbad.core.Simbad.query_region(coordinates, radius=radius, + get_query_payload=True)["QUERY"] + adql_2 = simbad.core.Simbad().query_region(coordinates, radius=radius, + get_query_payload=True)["QUERY"] + assert adql == adql_2 + assert re.search(where, adql) is not None + + +@pytest.mark.usefixtures("_mock_simbad_class") +def test_query_region_with_criteria(): + adql = simbad.core.Simbad.query_region(ICRS_COORDS, radius="0.1s", + criteria="galdim_majaxis>0.2", + get_query_payload=True)["QUERY"] + assert adql.endswith("AND (galdim_majaxis>0.2)") + + +# transform large query warning into error to save execution time +@pytest.mark.filterwarnings("error:For very large queries") +@pytest.mark.usefixtures("_mock_simbad_class") +def test_query_region_errors(): with pytest.raises(u.UnitsError): - simbad.core.Simbad().query_region( - coordinates, radius=radius, equinox=equinox, epoch=epoch) - - -def test_query_region_coord_radius_mismatch(): - with pytest.raises(ValueError, match="^Mismatch between radii and coordinates$"): + simbad.core.Simbad().query_region(ICRS_COORDS, radius=0) + with pytest.raises(TypeError, match="The cone radius must be specified as an angle-equivalent quantity"): + simbad.SimbadClass().query_region(ICRS_COORDS, radius=None) + with pytest.raises(ValueError, match="Mismatch between radii of length 3 " + "and center coordinates of length 2."): simbad.SimbadClass().query_region(multicoords, radius=[1, 2, 3] * u.deg) + centers = SkyCoord([0] * 10001, [0] * 10001, unit="deg", frame="icrs") + with pytest.raises(LargeQueryWarning, match="For very large queries, you may receive a timeout error.*"): + simbad.core.Simbad.query_region(centers, radius="2m", get_query_payload=True)["QUERY"] + + +@pytest.mark.usefixtures("_mock_simbad_class") +def test_query_objects(): + # no wildcard and additional criteria + adql = simbad.core.Simbad.query_objects(("m1", "m2"), criteria="otype = 'Galaxy..'", + get_query_payload=True)["QUERY"] + expected = ('FROM TAP_UPLOAD.script_infos LEFT JOIN ident ON TAP_UPLOAD.script_infos.' + '"user_specified_id" = ident."id" LEFT JOIN basic ON basic."oid" = ident."oidref"' + ' WHERE (otype = \'Galaxy..\')') + assert adql.endswith(expected) + # with wildcard + adql = simbad.core.Simbad.query_objects(("M *", "NGC *"), wildcard=True, get_query_payload=True)["QUERY"] + expected = ('FROM basic JOIN ident ON basic."oid" = ident."oidref" WHERE ' + '(regexp(id, \'^M +.*$\') = 1 OR regexp(id, \'^NGC +.*$\') = 1)') + assert adql.endswith(expected) + + +@pytest.mark.usefixtures("_mock_simbad_class") +def test_query_object(): + # no wildcard + adql = simbad.core.Simbad.query_object("m1", wildcard=False, get_query_payload=True)["QUERY"] + expected = r'SELECT .* FROM basic JOIN ident ON basic\."oid" = ident\."oidref" WHERE id = \'m1\'' + assert re.match(expected, adql) is not None + # with wildcard + adql = simbad.core.Simbad.query_object("m [0-9]", wildcard=True, get_query_payload=True)["QUERY"] + end = "WHERE regexp(id, '^m +[0-9]$') = 1" + assert adql.endswith(end) + # with criteria + adql = simbad.core.Simbad.query_object("NGC*", wildcard=True, criteria="otype = 'G..'", + get_query_payload=True)["QUERY"] + end = "AND (otype = 'G..')" + assert adql.endswith(end) + +# ------------------------ +# Tests for query_criteria +# ------------------------ + + +@pytest.mark.usefixtures("_mock_simbad_class") +def test_query_criteria(): + with pytest.warns(AstropyDeprecationWarning, match="'query_criteria' is deprecated*"): + # with a region and otype criteria + adql = simbad.core.Simbad.query_criteria("region(box, ICRS, 49.89 -0.3, 0.5d 0.5d)", + otype='HII', get_query_payload=True)["QUERY"] + expected = ("SELECT basic.\"main_id\", basic.\"ra\", basic.\"dec\", " + "basic.\"coo_err_maj\", basic.\"coo_err_min\", " + "basic.\"coo_err_angle\", basic.\"coo_wavelength\", " + "basic.\"coo_bibcode\" FROM basic JOIN otypes ON basic.\"oid\" = " + "otypes.\"oidref\" WHERE (CONTAINS(POINT('ICRS', ra, dec), " + "BOX('ICRS', 49.89, -0.3, 0.5, 0.5)) = 1 " + "AND otypes.otype = 'HII')") + assert adql == expected + # with a flux criteria + adql = simbad.core.Simbad.query_criteria("Umag < 9", get_query_payload=True)["QUERY"] + expected = ( + 'SELECT basic."main_id", basic."ra", basic."dec", basic."coo_err_maj", ' + 'basic."coo_err_min", basic."coo_err_angle", basic."coo_wavelength", ' + 'basic."coo_bibcode" FROM basic JOIN allfluxes ON basic."oid" = ' + 'allfluxes."oidref" WHERE (allfluxes.U < 9)' + ) + assert adql == expected + +# ------------------------- +# Test query_tap exceptions +# ------------------------- + + +@pytest.mark.usefixtures("_mock_simbad_class") +def test_query_tap_errors(): + # test the hardlimit + with pytest.raises(ValueError, match="The maximum number of records cannot exceed 2000000."): + simbad.Simbad.query_tap("select top 5 * from basic", maxrec=10e10) + # test the escape of single quotes + with pytest.raises(ValueError, match="Query string contains an odd number of single quotes.*"): + simbad.Simbad.query_tap("'''") + + +@pytest.mark.usefixtures("_mock_simbad_class") +def test_query_tap_cache_call(monkeypatch): + msg = "called_cached_query_tap" + monkeypatch.setattr(simbad.core, "_cached_query_tap", lambda tap, query, maxrec: msg) + assert simbad.Simbad.query_tap("select top 1 * from basic") == msg -@pytest.mark.parametrize(('coordinates', 'radius', 'equinox', 'epoch'), - [(ICRS_COORDS, "0d", 2000.0, 'J2000'), - (GALACTIC_COORDS, 1.0 * u.marcsec, 2000.0, 'J2000') - ]) -def test_query_region_small_radius(patch_post, coordinates, radius, - equinox, epoch): - result1 = simbad.core.Simbad.query_region(coordinates, radius=radius, - equinox=equinox, epoch=epoch) - result2 = simbad.core.Simbad().query_region(coordinates, radius=radius, - equinox=equinox, epoch=epoch) - assert isinstance(result1, Table) - assert isinstance(result2, Table) - - -@pytest.mark.parametrize(('object_name', 'wildcard'), - [("m1", None), - ("m [0-9]", True) - ]) -def test_query_object_async(patch_post, object_name, wildcard): - response1 = simbad.core.Simbad.query_object_async(object_name, - wildcard=wildcard) - response2 = simbad.core.Simbad().query_object_async(object_name, - wildcard=wildcard) - assert response1 is not None and response2 is not None - assert response1.content == response2.content - - -@pytest.mark.parametrize(('object_name', 'wildcard'), - [("m1", None), - ("m [0-9]", True), - ]) -def test_query_object(patch_post, object_name, wildcard): - payload = simbad.core.Simbad.query_object( - object_name, wildcard=wildcard, get_query_payload=True) - expected_payload = {'script': '\nvotable {main_id,coordinates}\nvotable' - + ' open\nquery id {} {} \nvotable close'. - format('wildcard' if wildcard else '', object_name)} - assert payload == expected_payload - result1 = simbad.core.Simbad.query_object(object_name, - wildcard=wildcard) - result2 = simbad.core.Simbad().query_object(object_name, - wildcard=wildcard) - assert isinstance(result1, Table) - assert isinstance(result2, Table) - - -def test_list_votable_fields(): - simbad.core.Simbad.list_votable_fields() - simbad.core.Simbad().list_votable_fields() - - -def test_get_field_description(): - simbad.core.Simbad.get_field_description('bibcodelist(y1-y2)') - simbad.core.Simbad().get_field_description('bibcodelist(y1-y2)') - with pytest.raises(Exception): - simbad.core.Simbad.get_field_description('xyz') - - -def test_votable_fields(): - sb = simbad.core.Simbad() - sb.add_votable_fields('rot', 'z_value', 'velocity') - assert (set(sb.get_votable_fields()) - == set(['main_id', 'coordinates', 'rot', 'z_value', 'velocity'])) - - assert (set(sb.get_votable_fields()) - == set(['main_id', 'coordinates', 'rot', 'z_value', 'velocity'])) - sb.remove_votable_fields('rot', 'main_id', 'coordinates') - assert set(sb.get_votable_fields()) == set(['z_value', 'velocity']) - # Warning is expected as we removed the 'coordinates' field above: - with pytest.warns(UserWarning, match="coordinates: this field is not set"): - sb.remove_votable_fields('coordinates') - assert set(sb.get_votable_fields()) == set(['z_value', 'velocity']) - with pytest.warns(UserWarning, match="All fields have been removed. Resetting"): - sb.remove_votable_fields('z_value', 'velocity') - assert set(sb.get_votable_fields()) == set(['main_id', 'coordinates']) - sb.add_votable_fields('rot', 'z_value', 'velocity') - assert (set(sb.get_votable_fields()) - == set(['main_id', 'coordinates', 'rot', 'z_value', 'velocity'])) - sb.reset_votable_fields() - assert set(sb.get_votable_fields()) == set(['main_id', 'coordinates']) - - -def test_query_criteria1(patch_post): - Simbad = simbad.core.Simbad() - result = Simbad.query_criteria( - "region(box, GAL, 49.89 -0.3, 0.5d 0.5d)", otype='HII') - assert isinstance(result, Table) - assert "region(box, GAL, 49.89 -0.3, 0.5d 0.5d)" in Simbad._last_query.data['script'] - - -def test_query_criteria2(patch_post): - S = simbad.core.Simbad() - S.add_votable_fields('ra(d)', 'dec(d)') - S.remove_votable_fields('coordinates') - assert S.get_votable_fields() == ['main_id', 'ra(d)', 'dec(d)'] - result = S.query_criteria(otype='SNR') - assert isinstance(result, Table) - assert 'otype=SNR' in S._last_query.data['script'] - - -def test_simbad_settings1(): - sb = simbad.core.Simbad() - assert sb.get_votable_fields() == ['main_id', 'coordinates'] - sb.add_votable_fields('ra', 'dec(5)') - with pytest.warns(UserWarning, match="dec: this field is not set"): - sb.remove_votable_fields('ra', 'dec') - assert sb.get_votable_fields() == ['main_id', 'coordinates', 'dec(5)'] - sb.reset_votable_fields() - - -def test_simbad_settings2(): - sb = simbad.core.Simbad() - assert sb.get_votable_fields() == ['main_id', 'coordinates'] - sb.add_votable_fields('ra', 'dec(5)') - sb.remove_votable_fields('ra', 'dec', strip_params=True) - assert sb.get_votable_fields() == ['main_id', 'coordinates'] - - -def test_regression_votablesettings(): - sb = simbad.Simbad() - assert sb.get_votable_fields() == ['main_id', 'coordinates'] - sb.add_votable_fields('ra', 'dec(5)') - # this is now allowed: - sb.add_votable_fields('ra(d)', 'dec(d)') - assert sb.get_votable_fields() == ['main_id', 'coordinates', 'ra', - 'dec(5)', 'ra(d)', 'dec(d)'] - # cleanup - sb.remove_votable_fields('ra', 'dec', strip_params=True) - assert sb.get_votable_fields() == ['main_id', 'coordinates'] - - -def test_regression_votablesettings2(): - sb = simbad.Simbad() - assert sb.get_votable_fields() == ['main_id', 'coordinates'] - sb.add_votable_fields('fluxdata(J)') - sb.add_votable_fields('fluxdata(H)') - sb.add_votable_fields('fluxdata(K)') - assert (sb.get_votable_fields() - == ['main_id', 'coordinates', - 'fluxdata(J)', 'fluxdata(H)', 'fluxdata(K)']) - sb.remove_votable_fields('fluxdata', strip_params=True) - assert sb.get_votable_fields() == ['main_id', 'coordinates'] - - -def test_regression_issue388(): - # This is a python-3 issue: content needs to be decoded? - response = MockResponseSimbad('\nvotable {main_id,coordinates}\nvotable ' - 'open\nquery id m1 \nvotable close') - with open(data_path('m1.data'), "rb") as f: - response.content = f.read() - parsed_table = simbad.Simbad._parse_result(response, - simbad.core.SimbadVOTableResult) - truth = 'M 1' - assert parsed_table['MAIN_ID'][0] == truth - assert len(parsed_table) == 1 - # --------------------------------------------------- # Test the adql string for query_tap helper functions # --------------------------------------------------- +@pytest.mark.usefixtures("_mock_simbad_class") def test_simbad_list_tables(): tables_adql = "SELECT table_name, description FROM TAP_SCHEMA.tables WHERE schema_name = 'public'" - assert simbad.Simbad.list_tables(get_adql=True) == tables_adql + assert simbad.Simbad.list_tables(get_query_payload=True)["QUERY"] == tables_adql +@pytest.mark.usefixtures("_mock_simbad_class") def test_simbad_list_columns(): # with three table names columns_adql = ("SELECT table_name, column_name, datatype, description, unit, ucd" " FROM TAP_SCHEMA.columns " "WHERE table_name NOT LIKE 'TAP_SCHEMA.%'" - " AND table_name IN ('mesPM', 'otypedef', 'journals')" + " AND LOWERCASE(table_name) IN ('mespm', 'otypedef', 'journals')" " ORDER BY table_name, principal DESC, column_name") - assert simbad.Simbad.list_columns("mesPM", "otypedef", "journals", get_adql=True) == columns_adql + assert simbad.Simbad.list_columns("mesPM", "otypedef", + "journals", get_query_payload=True)["QUERY"] == columns_adql # with only one columns_adql = ("SELECT table_name, column_name, datatype, description, unit, ucd " "FROM TAP_SCHEMA.columns WHERE table_name NOT LIKE 'TAP_SCHEMA.%' " - "AND table_name = 'basic' ORDER BY table_name, principal DESC, column_name") - assert simbad.Simbad.list_columns("basic", get_adql=True) == columns_adql + "AND LOWERCASE(table_name) = 'basic' ORDER BY table_name, principal DESC, column_name") + assert simbad.Simbad.list_columns("basic", get_query_payload=True)["QUERY"] == columns_adql # with only a keyword list_columns_adql = ("SELECT table_name, column_name, datatype, description, unit, ucd " "FROM TAP_SCHEMA.columns WHERE table_name NOT LIKE 'TAP_SCHEMA.%' " @@ -497,11 +510,58 @@ def test_simbad_list_columns(): "LIKE LOWERCASE('%stellar%')) OR (LOWERCASE(description) " "LIKE LOWERCASE('%stellar%')) OR (LOWERCASE(table_name) " "LIKE LOWERCASE('%stellar%'))) ORDER BY table_name, principal DESC, column_name") - assert simbad.Simbad.list_columns(keyword="stellar", get_adql=True) == list_columns_adql + assert simbad.Simbad.list_columns(keyword="stellar", get_query_payload=True)["QUERY"] == list_columns_adql +@pytest.mark.usefixtures("_mock_simbad_class") def test_list_linked_tables(): list_linked_tables_adql = ("SELECT from_table, from_column, target_table, target_column " "FROM TAP_SCHEMA.key_columns JOIN TAP_SCHEMA.keys USING (key_id) " "WHERE (from_table = 'basic') OR (target_table = 'basic')") - assert simbad.Simbad.list_linked_tables("basic", get_adql=True) == list_linked_tables_adql + assert simbad.Simbad.list_linked_tables("basic", get_query_payload=True)["QUERY"] == list_linked_tables_adql + + +@pytest.mark.usefixtures("_mock_simbad_class") +def test_query(): + column = simbad.core._Column("basic", "*") + # bare minimum with an alias + expected = 'SELECT basic."main_id" AS my_id FROM basic' + assert simbad.Simbad._query(-1, [simbad.core._Column("basic", "main_id", "my_id")], [], + [], get_query_payload=True)["QUERY"] == expected + # with top + # and duplicated columns are dropped + expected = "SELECT TOP 1 basic.* FROM basic" + assert simbad.Simbad._query(1, [column, column], [], [], + get_query_payload=True)["QUERY"] == expected + # with a join + expected = 'SELECT basic.*, ids."ids" FROM basic JOIN ids ON basic."oid" = ids."oidref"' + assert simbad.Simbad._query(-1, [column, simbad.core._Column("ids", "ids")], + [simbad.core._Join("ids", simbad.core._Column("basic", "oid"), + simbad.core._Column("ids", "oidref"))], + [], get_query_payload=True)["QUERY"] == expected + # with a condition + expected = "SELECT basic.* FROM basic WHERE ra < 6 AND ra > 5" + assert simbad.Simbad._query(-1, [column], [], + ["ra < 6", "ra > 5"], + get_query_payload=True)["QUERY"] == expected + + +@pytest.mark.usefixtures("_mock_simbad_class") +@pytest.mark.parametrize( + ("query_method", "args", "deprecated_kwargs"), + [ + (simbad.Simbad.query_objectids, ["M1"], {"verbose", "cache"}), + (simbad.Simbad.query_bibcode, ["1992AJ....103..983B"], {"verbose", "cache"}), + (simbad.Simbad.query_bibobj, ["1992AJ....103..983B"], {"verbose"}), + (simbad.Simbad.query_catalog, ["M"], {"verbose", "cache"}), + (simbad.Simbad.query_region, [ICRS_COORDS, "2d"], {"equinox", "epoch", "cache"}), + (simbad.Simbad.query_objects, [["M1", "M2"]], {"verbose"}), + (simbad.Simbad.query_object, ["M1"], {"verbose"}), + ] +) +def test_deprecated_arguments(query_method, args, deprecated_kwargs): + for argument in deprecated_kwargs: + with pytest.warns(AstropyDeprecationWarning, + match=f'"{argument}" was deprecated in version 0.4.8 and will be ' + 'removed in a future version.*'): + query_method(*args, get_query_payload=True, **{argument: True})["QUERY"] diff --git a/astroquery/simbad/tests/test_simbad_remote.py b/astroquery/simbad/tests/test_simbad_remote.py index fa9567c9e2..ed530c0562 100644 --- a/astroquery/simbad/tests/test_simbad_remote.py +++ b/astroquery/simbad/tests/test_simbad_remote.py @@ -1,271 +1,132 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst import pytest -import shutil -import tempfile from astropy.coordinates import SkyCoord import astropy.units as u +from astropy.utils.exceptions import AstropyDeprecationWarning from astropy.table import Table -from astroquery.utils.mocks import MockResponse + from astroquery.simbad import Simbad -# Maybe we need to expose SimbadVOTableResult to be in the public API? -from astroquery.simbad.core import SimbadVOTableResult -from astroquery.exceptions import BlankResponseWarning +from astroquery.simbad.core import _cached_query_tap, _Column, _Join from pyvo.dal.exceptions import DALOverflowWarning +# this is for the very slow tests +SKIP_SLOW = True # M42 coordinates ICRS_COORDS_M42 = SkyCoord("05h35m17.3s -05d23m28s", frame='icrs') ICRS_COORDS_SgrB2 = SkyCoord(266.835*u.deg, -28.38528*u.deg, frame='icrs') -multicoords = SkyCoord([ICRS_COORDS_M42, ICRS_COORDS_SgrB2]) +multicoords = SkyCoord([ICRS_COORDS_SgrB2, ICRS_COORDS_SgrB2]) -@pytest.mark.remote_data +@pytest.mark.remote_data() class TestSimbad: - @pytest.fixture() - def temp_dir(self, request): - my_temp_dir = tempfile.mkdtemp() - - def fin(): - shutil.rmtree(my_temp_dir) - request.addfinalizer(fin) - return my_temp_dir - - def test_query_criteria1(self, temp_dir): - simbad = Simbad() - simbad.cache_location = temp_dir - result = simbad.query_criteria( - "region(box, GAL, 49.89 -0.3, 0.5d 0.5d)", otype='HII') - assert isinstance(result, Table) - - def test_query_criteria2(self, temp_dir): - simbad = Simbad() - simbad.cache_location = temp_dir - result = simbad.query_criteria(otype='SNR') - assert isinstance(result, Table) - - def test_query_bibcode_async(self, temp_dir): - simbad = Simbad() - simbad.cache_location = temp_dir - response = simbad.query_bibcode_async( - '2006ApJ*', wildcard=True) - assert response is not None - response.raise_for_status() - # make sure requests has *NOT* been monkeypatched - assert hasattr(response, 'connection') - assert hasattr(response, 'close') - assert hasattr(response, 'status_code') - assert hasattr(response, 'request') - assert not isinstance(response, MockResponse) - assert not issubclass(response.__class__, MockResponse) + simbad = Simbad() - def test_query_bibcode(self, temp_dir): - simbad = Simbad() - simbad.cache_location = temp_dir - result = simbad.query_bibcode('2006ApJ*', wildcard=True) - assert isinstance(result, Table) + def test_query_bibcode(self): + self.simbad.ROW_LIMIT = 20 + # wildcard option + refs = self.simbad.query_bibcode('2006ApJ*', wildcard=True) + assert set(refs["journal"]).issubset({"ApJ", "ApJS"}) + assert "abstract" not in refs.colnames + assert len(refs) == 20 # we applied the ROW_LIMIT - def test_non_ascii_bibcode(self, temp_dir): + def test_non_ascii_bibcode(self): # regression test for #1775 - simbad = Simbad() - simbad.cache_location = temp_dir - result = simbad.query_bibcode('2019PASJ...71...55K') - assert len(result) > 0 - - def test_query_bibobj_async(self, temp_dir): - simbad = Simbad() - simbad.cache_location = temp_dir - response = simbad.query_bibobj_async('2006AJ....131.1163S') - assert response is not None - - def test_query_bibobj(self, temp_dir): - simbad = Simbad() - simbad.ROW_LIMIT = 5 - simbad.cache_location = temp_dir - result = simbad.query_bibobj('2005A&A.430.165F') - assert isinstance(result, Table) - assert len(result) == 5 - - def test_query_catalog_async(self, temp_dir): - simbad = Simbad() - simbad.cache_location = temp_dir - response = simbad.query_catalog_async('m') - assert response is not None - - def test_query_catalog(self, temp_dir): - simbad = Simbad() - simbad.cache_location = temp_dir - result = simbad.query_catalog('m') - assert isinstance(result, Table) - - def test_query_region_async(self, temp_dir): - simbad = Simbad() - simbad.ROW_LIMIT = 100 - simbad.cache_location = temp_dir - response = simbad.query_region_async( - ICRS_COORDS_M42, radius=5 * u.arcsec, equinox=2000.0, epoch='J2000') - # A correct response code - assert response.status_code == 200 - # Check that Orion was found - assert "NAME Ori Region" in response.text - - @pytest.mark.parametrize("radius", (0.5 * u.arcsec, "0.5s")) - def test_query_region_async_vector(self, temp_dir, radius): - simbad = Simbad() - simbad.cache_location = temp_dir - response1 = simbad.query_region_async(multicoords, radius=radius) - assert response1.request.body == 'script=votable+%7Bmain_id%2Ccoordinates%7D%0Avotable+open%0Aquery+coo+5%3A35%3A17.3+-5%3A23%3A28+radius%3D0.5s+frame%3DICRS+equi%3D2000.0%0Aquery+coo+17%3A47%3A20.4+-28%3A23%3A07.008+radius%3D0.5s+frame%3DICRS+equi%3D2000.0%0Avotable+close' # noqa - - def test_query_region(self, temp_dir): - simbad = Simbad() - simbad.TIMEOUT = 100 - simbad.ROW_LIMIT = 100 - simbad.cache_location = temp_dir - result = simbad.query_region(ICRS_COORDS_M42, radius=2 * u.deg, - equinox=2000.0, epoch='J2000') - assert isinstance(result, Table) - - def test_query_regions(self, temp_dir): - simbad = Simbad() - simbad.cache_location = temp_dir - result = simbad.query_region(multicoords, radius=1 * u.arcmin, - equinox=2000.0, epoch='J2000') - assert isinstance(result, Table) - - def test_query_object_async(self, temp_dir): - simbad = Simbad() - simbad.cache_location = temp_dir - response = simbad.query_object_async("m [0-9]", wildcard=True) - assert response is not None - - def test_query_object(self, temp_dir): - simbad = Simbad() - simbad.cache_location = temp_dir - result = simbad.query_object("m [0-9]", wildcard=True) - assert isinstance(result, Table) - - def test_query_multi_object(self, temp_dir): - simbad = Simbad() - simbad.cache_location = temp_dir - result = simbad.query_objects(['M32', 'M81']) - assert len(result) == 2 - assert len(result.errors) == 0 - - with pytest.warns(BlankResponseWarning): - result = simbad.query_objects(['M32', 'M81', 'gHer']) - # 'gHer' is not a valid Simbad identifier - it should be 'g Her' to - # get the star - assert len(result) == 2 - assert len(result.errors) == 1 - - # test async - s = Simbad() - response = s.query_objects_async(['M32', 'M81']) - - result = s._parse_result(response, SimbadVOTableResult) - assert len(result) == 2 - - def test_query_object_ids(self, temp_dir): - simbad = Simbad() - simbad.cache_location = temp_dir - result = simbad.query_objectids("Polaris") - + ref = self.simbad.query_bibcode('2019PASJ...71...55K', abstract=True) + assert ref["title"][0].startswith("The dominant origin of diffuse Lyα halos") + # we also check the abstract option here + assert "abstract" in ref.colnames + + def test_query_bibobj(self): + self.simbad.ROW_LIMIT = 5 + self.simbad.add_votable_fields("otype") + bibcode = '2005A&A...430..165F' + result = self.simbad.query_bibobj(bibcode, criteria="otype='*..'") + assert all((bibcode == code) for code in result["bibcode"].data.data) + assert all(('*' in otype) for otype in result["otype"].data.data) + self.simbad.reset_votable_fields() + + def test_query_catalog(self): + self.simbad.ROW_LIMIT = -1 + result = self.simbad.query_catalog('M') + assert len(result) == 110 + + def test_query_region(self): + self.simbad.ROW_LIMIT = 10 + result = self.simbad.query_region(ICRS_COORDS_M42, radius="1d") + assert all(ra > 83 and ra < 85 for ra in result["ra"].data.data) + + def test_query_regions(self): + self.simbad.ROW_LIMIT = 10 + result = self.simbad.query_region(SkyCoord([SkyCoord.from_name('m81'), + SkyCoord.from_name('m10')]), + radius=1 * u.arcmin, criteria="main_id LIKE 'M %'") + # filtering on main_id to retrieve the two cone centers + assert {"M 81", "M 10"} == set(result["main_id"].data.data) + + def test_query_object_ids(self): + self.simbad.ROW_LIMIT = -1 + result = self.simbad.query_objectids("Polaris") # Today, there are 42 names. There could be more in the future assert len(result) >= 42 - # Test multiple functions correctly return "None" when SIMBAD has no - # data for the query - @pytest.mark.parametrize('function', [ - ('query_criteria'), - ('query_object'), - ('query_catalog'), - ('query_bibobj'), - ('query_bibcode'), - ('query_objectids')]) - def test_null_response(self, temp_dir, function): - simbad = Simbad() - simbad.cache_location = temp_dir - with pytest.warns(BlankResponseWarning): - assert (simbad.__getattribute__(function)('idonotexist') - is None) - - # Special case of null test: list of nonexistent parameters - def test_query_objects_null(self, temp_dir): - simbad = Simbad() - simbad.cache_location = temp_dir - with pytest.warns(BlankResponseWarning): - assert simbad.query_objects(['idonotexist', 'idonotexisteither']) is None - - # Special case of null test: zero-size and very small region - @pytest.mark.parametrize('radius', ["0d", 1.0*u.marcsec]) - def test_query_region_null(self, temp_dir, radius): - simbad = Simbad() - simbad.cache_location = temp_dir - with pytest.warns(BlankResponseWarning): - result = simbad.query_region(SkyCoord("00h01m0.0s 00h00m0.0s"), radius=1.0 * u.marcsec, - equinox=2000.0, epoch='J2000') - assert result is None + def test_query_multi_object(self): + result = self.simbad.query_objects(['M32', 'M81']) + assert len(result) == 2 + # check that adding fields preserves the left join + self.simbad.add_votable_fields("mesdistance", "otype") + result = self.simbad.query_objects(['M32', 'M81', 'gHer']) + # 'gHer' is not a valid Simbad identifier - it should be 'g Her' to + # get the star. This appears as an empty line corresponding to + # 'object_number_id' = 3 + assert max(result["object_number_id"]) == 3 + self.simbad.reset_votable_fields() - # Special case : zero-sized region with one object - def test_query_zero_sized_region(self, temp_dir): - simbad = Simbad() - simbad.cache_location = temp_dir - result = simbad.query_region(SkyCoord("20h54m05.6889s 37d01m17.380s"), radius="1s", - equinox=2000.0, epoch='J2000') - # This should find a single star, BD+36 4308 - assert len(result) == 1 + @pytest.mark.skipif("SKIP_SLOW") # ~300 seconds (has to regexp all the database twice!) + def test_query_multi_object_wildcard(self): + # with wildcards + result = self.simbad.query_objects(['*Crab*', '*Bubble*'], wildcard=True) + assert len(result) >= 23 def test_simbad_flux_qual(self): '''Regression test for issue 680''' - request = Simbad() - request.add_votable_fields("flux_qual(V)") - response = request.query_object('algol') - assert ("FLUX_QUAL_V" in response.keys()) - - def test_multi_vo_fields(self): - '''Regression test for issue 820''' - request = Simbad() - - request.add_votable_fields("flux_qual(V)") - request.add_votable_fields("flux_qual(R)") - request.add_votable_fields("coo(s)") # sexagesimal coordinates - request.add_votable_fields("coo(d)") # degree coordinates - request.add_votable_fields("ra(:;A;ICRS;J2000)") - request.add_votable_fields("ra(:;A;fk5;J2000)") - request.add_votable_fields("bibcodelist(2000-2006)") - request.add_votable_fields("bibcodelist(1990-2000)") - request.add_votable_fields("otype(S)") - request.add_votable_fields("otype(3)") - request.add_votable_fields("id(1)") - request.add_votable_fields("id(2mass)") - request.add_votable_fields("id(s)") - - response = request.query_object('algol') - assert ("FLUX_QUAL_V" in response.keys()) - assert ("FLUX_QUAL_R" in response.keys()) - assert ("RA_d" in response.keys()) - assert ("RA_s" in response.keys()) - assert ("RA___A_ICRS_J2000" in response.keys()) - assert ("RA___A_fk5_J2000" in response.keys()) - assert ("OTYPE_S" in response.keys()) - assert ("OTYPE_3" in response.keys()) - assert ("ID_1" in response.keys()) - assert ("ID_2mass" in response.keys()) - assert ("ID_s" in response.keys()) + simbad_instance = Simbad() + simbad_instance.add_votable_fields("flux") + response = simbad_instance.query_object('algol', criteria="filter='V'") + # this is bugged, it should be "flux.qual", see https://github.com/gmantele/vollt/issues/154 + # when the issue upstream in vollt (the TAP software used in SIMBAD) is fixed we can rewrite this test + assert "qual" in response.colnames + # replace "filter" by "flux.filter" when upstream bug is fixed + assert response["filter"][0] == "V" + + def test_query_object(self): + self.simbad.ROW_LIMIT = 5 + result = self.simbad.query_object("NGC [0-9]*", wildcard=True) + assert all(matched_id.startswith("NGC") for matched_id in result["matched_id"].data.data) + + def test_query_criteria(self): + simbad_instance = Simbad() + simbad_instance.add_votable_fields("otype") + with pytest.warns(AstropyDeprecationWarning, match="'query_criteria' is deprecated*"): + result = simbad_instance.query_criteria("region(Galactic Center, 10s)", maintype="X") + assert all(result["otype"].data.data == "X") + assert len(result) >= 16 # there could be more measurements, there are 16 sources in 2024 def test_query_tap(self): # a robust query about something that should not change in Simbad - filtername = Simbad.query_tap("select filtername from filter where filtername='B'") - assert 'B' == filtername["filtername"][0] + filtername = self.simbad.query_tap("select filtername from filter where filtername='B'") + assert filtername["filtername"][0] == 'B' # test uploads by joining two local tables table_letters = Table([["a", "b", "c"]], names=["letters"]) table_numbers = Table([[1, 2, 3], ["a", "b", "c"]], names=["numbers", "letters"]) - result = Simbad.query_tap("SELECT * FROM TAP_UPLOAD.numbers " - "JOIN TAP_UPLOAD.letters USING(letters)", - numbers=table_numbers, letters=table_letters) - expect = "letters numbers\n------- -------\n a 1\n b 2\n c 3" + result = self.simbad.query_tap("SELECT * FROM TAP_UPLOAD.numbers " + "JOIN TAP_UPLOAD.letters USING(letters)", + numbers=table_numbers, letters=table_letters) + expect = ("letters numbers\n------- -------\n a 1\n b 2\n" + " c 3") assert expect == str(result) # Test query_tap raised errors with pytest.raises(DALOverflowWarning, match="Partial result set *"): @@ -275,6 +136,12 @@ def test_query_tap(self): Simbad.query_tap("select top 5 * from basic", maxrec=10e10) with pytest.raises(ValueError, match="Query string contains an odd number of single quotes.*"): Simbad.query_tap("'''") + # test the cache + assert _cached_query_tap.cache_info().currsize != 0 + Simbad.clear_cache() + assert _cached_query_tap.cache_info().currsize == 0 + + # ---------------------------------- def test_simbad_list_tables(self): tables = Simbad.list_tables() @@ -284,7 +151,7 @@ def test_simbad_list_tables(self): assert len(tables) >= 30 def test_simbad_list_columns(self): - columns = Simbad.list_columns("ident", "biblio") + columns = Simbad.list_columns("ident", "bibliO") # this should be case insensitive assert len(columns) == 4 assert "oidref" in str(columns) columns = Simbad.list_columns(keyword="herschel") @@ -293,3 +160,34 @@ def test_simbad_list_columns(self): def test_list_linked_tables(self): links = Simbad.list_linked_tables("h_link") assert {"basic"} == set(links["target_table"]) + + # ---------------------------------- + + def test_add_bundle_to_output(self): + simbad_instance = Simbad() + # empty before the test + simbad_instance.columns_in_output = [] + # add a bundle + simbad_instance.add_votable_fields("dim") + # check the length + assert len(simbad_instance.columns_in_output) == 8 + assert _Column("basic", "galdim_majaxis") in simbad_instance.columns_in_output + + def test_add_table_to_output(self): + simbad_instance = Simbad() + # empty before the test + simbad_instance.columns_in_output = [] + simbad_instance.add_votable_fields("otypes") + assert _Column("otypes", "otype", '"otypes.otype"') in simbad_instance.columns_in_output + # tables also require a join + assert _Join("otypes", + _Column("basic", "oid"), + _Column("otypes", "oidref")) == simbad_instance.joins[0] + # tables that have been renamed should warn + with pytest.warns(DeprecationWarning, match="'iue' has been renamed 'mesiue'.*"): + simbad_instance.add_votable_fields("IUE") + # empty before the test + simbad_instance.columns_in_output = [] + # mixed columns bundles and tables + simbad_instance.add_votable_fields("flux", "velocity", "update_date") + assert len(simbad_instance.columns_in_output) == 19 diff --git a/astroquery/simbad/tests/test_utils.py b/astroquery/simbad/tests/test_utils.py new file mode 100644 index 0000000000..ce9e6dc455 --- /dev/null +++ b/astroquery/simbad/tests/test_utils.py @@ -0,0 +1,110 @@ + +import pytest + +from astroquery.simbad.utils import (CriteriaTranslator, _parse_coordinate_and_convert_to_icrs, + _region_to_contains, _wildcard_to_regexp) + +from astropy.coordinates.builtin_frames.icrs import ICRS +from astropy.coordinates import SkyCoord + + +@pytest.mark.parametrize("coord_string, frame, epoch, equinox", [ + ("12 34 56.78 +12 34 56.78", None, None, None), + ("10 +20", "galactic", None, None), + ("10 20", "fk4", "J2000", "1950") +]) +def test_parse_coordinates_and_convert_to_icrs(coord_string, frame, epoch, equinox): + coord = _parse_coordinate_and_convert_to_icrs(coord_string, frame=frame, equinox=equinox, epoch=epoch) + assert isinstance(coord.frame, ICRS) + + +def test_wildcard_to_regexp(): + # should add beginning and end operators, and translate * into .* + assert _wildcard_to_regexp("test*") == "^test.*$" + # should not replace escaped characters + assert _wildcard_to_regexp(r"test\*") == "^test\\*$" + # wildcard ? is regexp . + assert _wildcard_to_regexp("test?") == "^test.$" + + +@pytest.mark.remote_data() +def test_parse_coordinates_and_convert_to_icrs_sesame(): + coord = _parse_coordinate_and_convert_to_icrs("m1") + assert isinstance(coord.frame, ICRS) + + +def test_region_to_contains(monkeypatch): + # default shape is a circle, default frame is ICRS + assert "CIRCLE" in _region_to_contains("0 0, 1d") + assert "ICRS" in _region_to_contains("0 0,1d") + # invalid shapes + with pytest.raises(ValueError, match="'rotatedbox' shape cannot be translated in ADQL for SIMBAD."): + _region_to_contains("rotatedbox, 0 0, 1d") + # shapes should not be case-sensitive + box = "CONTAINS(POINT('ICRS', ra, dec), BOX('ICRS', 0.0, 0.0, 2.0, 0.025)) = 1" + assert _region_to_contains("BoX, J2000, 2024, 0 0, 2d 1.5m") == box + # polygons can have a lot of points + polygon = "CONTAINS(POINT('ICRS', ra, dec), POLYGON('ICRS', 0.0, 0.0, 1.0, 2.0, 65.0, 25.0, 10.0, -9.0)) = 1" + assert _region_to_contains("PolyGon, 0 0, 01 +02, 65 25, 10 -9") == polygon + # here we mock a case where the coordinates are given through a name + + def mockreturn(name): + return SkyCoord(0, 0, unit="deg") + monkeypatch.setattr(SkyCoord, "from_name", mockreturn) + assert _region_to_contains("zero_zero, 2d") == ("CONTAINS(POINT('ICRS', ra, dec), " + "CIRCLE('ICRS', 0.0, 0.0, 2.0)) = 1") + + +def test_tokenizer(): + # to regenerate tokenizer after a change in utils.py, delete `criteria_lextab.py` and run this test file again. + lexer = CriteriaTranslator._make_lexer() + test = "indec > 85 & (cat in ('hd','hip','ppm') | author ~ 'egret*') & otype != 'galaxy' & region(m1, 5d)" + lexer.input(test) + assert lexer.token().type == 'COLUMN' + assert lexer.token().type == 'BINARY_OPERATOR' + assert lexer.token().type == 'NUMBER' + assert lexer.token().type == '&' + assert lexer.token().type == '(' + assert lexer.token().type == 'COLUMN' + assert lexer.token().type == 'IN' + assert lexer.token().type == 'LIST' + assert lexer.token().type == '|' + assert lexer.token().type == 'COLUMN' + assert lexer.token().type == 'LIKE' + assert lexer.token().type == 'STRING' + assert lexer.token().type == ')' + assert lexer.token().type == '&' + assert lexer.token().type == 'COLUMN' + assert lexer.token().type == 'BINARY_OPERATOR' + assert lexer.token().type == 'STRING' + assert lexer.token().type == '&' + assert lexer.token().type == 'REGION' + + +@pytest.mark.parametrize("test, result", [ + ("region(GAL,180 0,2d) & otype = 'G' & (nbref >= 10|bibyear >= 2000)", + ("CONTAINS(POINT('ICRS', ra, dec), CIRCLE('ICRS', 86.40498828654475, 28.93617776179148, 2.0)) = 1" + " AND otypes.otype = 'G' AND (nbref >= 10 OR bibyear >= 2000)")), + ("author ∼ 'egret*'", "regexp(author, '^egret.*$') = 1"), + ("cat in ('hd','hip','ppm')", "cat IN ('hd','hip','ppm')"), + ("author !~ 'test'", "regexp(author, '^test$') = 0"), + ("sptype < F4", "sp_type < 'F4'"), + ("umag < 1", "allfluxes.u_ < 1"), + ("Vmag = 10", "allfluxes.V = 10"), + ("otypes != 'Galaxy'", "otypes.otype != 'Galaxy..'"), + ("maintype=SNR", "basic.otype = 'SNR'"), + ("maintypes=SNR", "basic.otype = 'SNR..'") +]) # these are the examples from http://simbad.cds.unistra.fr/guide/sim-fsam.htx +# plus added examples +def test_transpiler(test, result): + # to regenerate transpiler after a change in utils.py, delete `criteria_parsetab.py` + # and run this test file again. + translated = CriteriaTranslator.parse(test) + assert translated == result + + +def test_transpiler_errors(capsys): + with pytest.raises(ValueError, match="Syntax error for sim-script criteria"): + CriteriaTranslator.parse("otype % 'G'") + printed = capsys.readouterr().out + assert printed == "Unrecognized character '%' at position 6 for a sim-script criteria." diff --git a/astroquery/simbad/utils.py b/astroquery/simbad/utils.py new file mode 100644 index 0000000000..675f183647 --- /dev/null +++ b/astroquery/simbad/utils.py @@ -0,0 +1,358 @@ +"""Contains utility functions to support legacy Simbad interface.""" + +from collections import deque +import json +from pathlib import Path +import re + +from astropy.coordinates import SkyCoord, Angle +from astropy.utils.parsing import lex, yacc +from astropy.utils import classproperty +from astropy.utils.data import get_pkg_data_filename + +with open(get_pkg_data_filename(str(Path("data") / "query_criteria_fields.json"))) as f: + query_criteria_fields = json.load(f) + + +def _catch_deprecated_fields_with_arguments(votable_field): + """Raise informative errors for deprecated votable fields. + + These fields are a mix between selecting columns and applying a criteria. + This could be mimicked if there is a huge demand when query criteria is really + removed from codebase and no longer deprecated. For now, we'll give pointers + on how they can be replaced. + + Parameters + ---------- + votable_field : str + one of the former votable fields (see `~astroquery.simbad.SimbadClass.list_votable_fields`) + """ + if re.match(r"^(ra|dec|coo)\(.+\)$", votable_field): + raise ValueError("Coordinates conversion and formatting is no longer supported within the " + "SIMBAD module. This can be done with the `~astropy.coordinates` module." + "Coordinates are now per default in degrees and in the ICRS frame.") + if votable_field.startswith("id("): + raise ValueError("Catalog Ids are no longer supported as an output option. " + "A good replacement can be `~astroquery.simbad.SimbadClass.query_cat`") + if votable_field.startswith("bibcodelist("): + raise ValueError("Selecting a range of years for bibcode is removed. You can still use " + "bibcodelist without parenthesis and get the full list of bibliographic references.") + +# ---------------------------- +# Support wildcard argument +# ---------------------------- + + +def _wildcard_to_regexp(wildcard_string): + r"""Translate a wildcard string into a regexp. + + It prepends a ``^`` and appends a ``$`` to denote the start + and end of string that are implicit in the wildcard language. + + It replaces ``*`` by its regex equivalent ``.*`` but does not + replace the escaped ``\\*`` that corresponds to the short name + of the star otype. + + A whitespace `` `` is replaced by `` +``. + + The single character match ``?`` is ``.`` in regex. + + Parameters + ---------- + wildcard_string : str + A string containing wildcard characters. + + Returns + ------- + str + A regexp that reproduces the wildcard expression. Works on a + best approximation basis. Note that wildcards are case insensitive + while regexp are not. + + Examples + -------- + >>> from astroquery.simbad.utils import _wildcard_to_regexp + >>> _wildcard_to_regexp("hd *1") + '^hd +.*1$' + """ + # escape regexp characters that are not wildcard characters + wildcard_string = re.sub(r"([.+^${}()|])", r"\\\\\1", wildcard_string) + # replaces "*" by its regex equivalent ".*" + # but not "\*" that refers to the otype "*" + wildcard_string = re.sub(r"(?_` string. + + Returns + ------- + string + An ADQL CONTAINS clause. + """ + contains = "CONTAINS(POINT('ICRS', ra, dec), " + region_params = deque(re.split(r", *", region_string)) + legacy_shapes = {"ellipse", "zone", "rotatedbox"} + valid_shapes = {"circle", "box", "polygon"} + + # this part is a bit awkward because the optional parameters come first + # so we read shape_type, frame, epoch, and equinox while popping them out + + region_type = "circle" + frame = "ICRS" + epoch = None + equinox = None # default values + + if region_params[0].casefold() in legacy_shapes: + raise ValueError(f"'{region_params[0]}' shape cannot be translated in ADQL for SIMBAD.") + elif region_params[0].casefold() in valid_shapes: + region_type = region_params.popleft().casefold() + + # translates from simbad to astropy frames names + frame_translate = { + "GAL": "galactic", + "ICRS": "icrs", + "FK4": "fk4", + "FK5": "fk5", + "SGAL": "supergalactic", + "ECL": "CustomBarycentricEcliptic", # as described in 1977A&A....58....1L + } + + if region_params[0].upper() in frame_translate.keys(): + frame = region_params.popleft().upper() + frame = frame_translate[frame] + + if re.match(r"[B|J](\d{4}|\d{4}.\d*)", region_params[0]): + epoch = region_params.popleft() + + if re.match(r"(\d{4}|\d{4}.\d*)", region_params[0]): + equinox = region_params.popleft() + + if region_type == "circle": + center = _parse_coordinate_and_convert_to_icrs(region_params[0], + frame=frame, epoch=epoch, equinox=equinox) + radius = Angle(region_params[1]) + contains += ( + f"CIRCLE('ICRS', {center.ra.value}," + f" {center.dec.value}, {radius.to('deg').value})) = 1" + ) + + elif region_type == "box": + center = _parse_coordinate_and_convert_to_icrs(region_params[0], + frame=frame, epoch=epoch, equinox=equinox) + dimensions = region_params[1].split(" ") + width = Angle(dimensions[0]).to("deg").value + height = Angle(dimensions[1]).to("deg").value + contains += f"BOX('ICRS', {center.ra.value}, {center.dec.value}, {width}, {height})) = 1" + + elif region_type == "polygon": + contains += "POLYGON('ICRS'" + for token in region_params: + coordinates = _parse_coordinate_and_convert_to_icrs(token, + frame=frame, epoch=epoch, equinox=equinox) + contains += f", {coordinates.ra.value}, {coordinates.dec.value}" + contains += ")) = 1" + + return contains + + +def _parse_coordinate_and_convert_to_icrs(string_coordinate, *, + frame="icrs", epoch=None, equinox=None): + """Convert from sim-script string to SkyCoord. + + Parameters + ---------- + string_coordinate : str + Should be in the sim-script syntax defined here + http://simbad.cds.unistra.fr/guide/sim-fsam.htx + frame : str + epoch : str + equinox : str + + Returns + ------- + `~astropy.coordinates.SkyCoord` + """ + if re.search(r"\d+ *[\+\- ]\d+", string_coordinate): + if equinox: + equinox = f"J{equinox}" + center = SkyCoord(string_coordinate, unit="deg", frame=frame, obstime=epoch, equinox=equinox) + else: + center = SkyCoord.from_name(string_coordinate) + return center.transform_to("icrs") + + +def _convert_column(column, operator=None, value=None): + """Convert columns from the sim-script language into ADQL. + + This checks the criteria names for fields that changed names between + sim-script and SIMBAD TAP (the old and new SIMBAD APIs). There are two exceptions + for magnitudes and fluxes where in sim-script the argument that was used in the criteria + was different from the name that wes used in votable_field (ex: flux(V) to add the + column and Vmag to add in a criteria). + """ + # handle the change of syntax on otypes manually because they are difficult to automatize + if column == "maintype": + column = "basic.otype" + elif column == "otype": + column = "otypes.otype" + elif column == "maintypes": + column = "basic.otype" + value = f"{value[:-1]}..'" + elif column == "otypes": + column = "otypes.otype" + value = f"{value[:-1]}..'" + # magnitudes are also an exception + elif "mag" in column: + column = column.replace("mag", "") + if len(column) == 1 and column.islower(): + column = column + "_" + column = "allfluxes." + column + # the other cases are a simple replacement by the new name + elif column in query_criteria_fields: + if query_criteria_fields[column]["type"] == "alias": + column = query_criteria_fields[column]["tap_column"] + if operator and value: + return column + " " + operator + " " + value + return column + + +class CriteriaTranslator: + + _tokens = [ + "REGION", + "BINARY_OPERATOR", + "IN", + "LIST", + "LIKE", + "NOTLIKE", + "NUMBER", + "STRING", + "COLUMN" + ] + + @classproperty(lazy=True) + def _parser(cls): + return cls._make_parser() + + @classproperty(lazy=True) + def _lexer(cls): + return cls._make_lexer() + + @classmethod + def _make_lexer(cls): + tokens = cls._tokens # noqa: F841 + + t_NUMBER = r"\d*\.?\d+" # noqa: F841 + + literals = ["&", r"\|", r"\(", r"\)"] # noqa: F841 + + def t_IN(t): + r"in\b" + t.value = "IN" + return t + + def t_LIST(t): + r"\( *'[^\)]*\)" + return t + + def t_BINARY_OPERATOR(t): + r">=|<=|!=|>|<|=" + return t + + def t_LIKE(t): + r"~|∼" # the examples in SIMBAD documentation use this glyph '∼' + t.value = "LIKE" + return t + + def t_NOTLIKE(t): + r"!~|!∼" + t.value = "NOT LIKE" + return t + + def t_STRING(t): + r"'[^']*'" + return t + + def t_REGION(t): + r"region\([^\)]*\)" + t.value = t.value.replace("region(", "")[:-1] + return t + + def t_COLUMN(t): + r'[a-zA-Z_*][a-zA-Z_0-9*]*' + return t + + t_ignore = ", \t\n" # noqa: F841 + + def t_error(t): + r"." + print(f"Unrecognized character '{t.value[0]}' at position {t.lexpos} for a sim-script criteria.") + t.lexer.skip(1) # skip the illegal token (don't process it) + + return lex(lextab="criteria_lextab", package="astroquery/simbad", reflags=re.I | re.UNICODE) + + @classmethod + def _make_parser(cls): + + tokens = cls._tokens # noqa: F841 + + def p_criteria_OR(p): + r"""criteria : criteria '|' criteria""" + p[0] = p[1] + " OR " + p[3] + + def p_criteria_AND(p): + """criteria : criteria '&' criteria""" + p[0] = p[1] + " AND " + p[3] + + def p_criteria_parenthesis(p): + """criteria : '(' criteria ')'""" + p[0] = "(" + p[2] + ")" + + def p_criteria_string(p): + """criteria : COLUMN BINARY_OPERATOR STRING + | COLUMN BINARY_OPERATOR NUMBER + | COLUMN IN LIST + """ + p[0] = _convert_column(p[1], p[2], p[3]) + + def p_criteria_string_no_ticks(p): + """criteria : COLUMN BINARY_OPERATOR COLUMN + """ + # sim-script also tolerates omitting the '' at the right side of operators + p[0] = _convert_column(p[1], p[2], f"'{p[3]}'") + + def p_criteria_like(p): + """criteria : COLUMN LIKE STRING""" + p[0] = "regexp(" + _convert_column(p[1]) + ", '" + _wildcard_to_regexp(p[3][1:-1]) + "') = 1" + + def p_criteria_notlike(p): + """criteria : COLUMN NOTLIKE STRING""" + p[0] = "regexp(" + _convert_column(p[1]) + ", '" + _wildcard_to_regexp(p[3][1:-1]) + "') = 0" + + def p_criteria_region(p): + """criteria : REGION""" + p[0] = _region_to_contains(p[1]) + + def p_error(p): + raise ValueError(f"Syntax error for sim-script criteria at line {p.lineno}" + f" character {p.lexpos - 1}") + + return yacc(tabmodule="criteria_parsetab", package="astroquery/simbad") + + @classmethod + def parse(cls, criteria): + return cls._parser.parse(criteria, lexer=cls._lexer) diff --git a/docs/esa/iso/iso.rst b/docs/esa/iso/iso.rst index 99c805b69f..e19dad93c8 100644 --- a/docs/esa/iso/iso.rst +++ b/docs/esa/iso/iso.rst @@ -247,11 +247,11 @@ All these tables can be queried using the TAP interface and allow geometrical qu >>> # First we obtain the coordinates of a certain object (M31) in degrees >>> result_table = Simbad.query_object("M31") >>> print(result_table) # doctest: +IGNORE_OUTPUT - MAIN_ID RA DEC ... COO_WAVELENGTH COO_BIBCODE - "h:m:s" "d:m:s" ... - ------- ------------ ------------ ... -------------- ------------------- - M 31 00 42 44.330 +41 16 07.50 ... I 2006AJ....131.1163S - >>> c = SkyCoord(result_table['RA'], result_table['DEC'], unit=(u.hourangle, u.deg), + main_id ra dec ... coo_bibcode matched_id + deg deg ... + ------- ------------------ ------------------ ... ------------------- ---------- + M 31 10.684708333333333 41.268750000000004 ... 2006AJ....131.1163S M 31 + >>> c = SkyCoord(result_table['ra'], result_table['dec'], unit=(u.deg, u.deg), ... frame='icrs') >>> ra = str(c.ra.degree[0]) >>> dec = str(c.dec.degree[0]) diff --git a/docs/index.rst b/docs/index.rst index 401cbbf0fd..ffe7836d23 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -141,10 +141,10 @@ queries based on coordinates or object names. Some simple examples, using SIMBA >>> from astroquery.simbad import Simbad >>> result_table = Simbad.query_object("m1") >>> result_table.pprint() - MAIN_ID RA DEC ... COO_BIBCODE SCRIPT_NUMBER_ID - "h:m:s" "d:m:s" ... - ------- ---------- --------- ... ------------------- ---------------- - M 1 05 34 30.9 +22 00 53 ... 1995AuJPh..48..143S 1 + main_id ra dec coo_err_maj ... coo_err_angle coo_wavelength coo_bibcode matched_id + deg deg mas ... deg + ------- ------- ------- ----------- ... ------------- -------------- ------------------- ---------- + M 1 83.6287 22.0147 18500.0 ... 0 R 1995AuJPh..48..143S M 1 All query tools allow coordinate-based queries: @@ -152,19 +152,18 @@ All query tools allow coordinate-based queries: >>> from astropy import coordinates >>> import astropy.units as u - >>> # works only for ICRS coordinates: >>> c = coordinates.SkyCoord("05h35m17.3s -05d23m28s", frame='icrs') >>> r = 5 * u.arcminute >>> result_table = Simbad.query_region(c, radius=r) >>> result_table.pprint(show_unit=True, max_width=80, max_lines=5) - MAIN_ID RA ... COO_BIBCODE SCRIPT_NUMBER_ID - "h:m:s" ... - ----------------------- ------------- ... ------------------- ---------------- - NAME Ori Region 05 35 17.30 ... 1 - ... ... ... ... ... - 2MASS J05353573-0525256 05 35 35.7755 ... 2020yCat.1350....0G 1 - V* V2114 Ori 05 35 01.6720 ... 2020yCat.1350....0G 1 - Length = 3272 rows + main_id ra ... coo_wavelength coo_bibcode + deg ... + --------------------- ----------------- ... -------------- ------------------- + COUP J053515.3-052225 83.81426666666665 ... O 1999AJ....117.1375S + ... ... ... ... ... + [OW94] 135-356 83.80624999999998 ... O 2007AJ....133.2192H + [H97b] 9009 83.79990004111 ... O 2020yCat.1350....0G + Length = 3271 rows For additional guidance and examples, read the documentation for the individual services below. @@ -191,34 +190,34 @@ By default Astroquery employs query caching with a timeout of 1 week. The user can clear their cache at any time, as well as suspend cache usage, and change the cache location. Caching persists between Astroquery sessions. If you know the service you are using has released new data recently, or if you believe you are -not recieving the newest data, try clearing the cache. +not receiving the newest data, try clearing the cache. Service specific settings ^^^^^^^^^^^^^^^^^^^^^^^^^ The Astroquery cache location is specific to individual services, -so each service's cache should be managed invidually. +so each service's cache should be managed individually. The cache location can be viewed with the following command -(using :ref:`Simbad ` as an example): +(using :ref:`VizieR ` as an example): .. code-block:: python - >>> from astroquery.simbad import Simbad - >>> print(Simbad.cache_location) # doctest: +IGNORE_OUTPUT - /Users/username/.astropy/cache/astroquery/Simbad + >>> from astroquery.vizier import Vizier + >>> print(Vizier.cache_location) # doctest: +IGNORE_OUTPUT + /Users/username/.astropy/cache/astroquery/Vizier Each service's cache is cleared with the ``clear_cache`` function within that service. .. code-block:: python >>> import os - >>> from astroquery.simbad import Simbad + >>> from astroquery.vizier import Vizier ... - >>> os.listdir(Simbad.cache_location) # doctest: +IGNORE_OUTPUT + >>> os.listdir(Vizier.cache_location) # doctest: +IGNORE_OUTPUT ['8abafe54f49661237bdbc2707179df53b6ee0d74ca6b7679c0e4fac0.pickle', '0e4766a7673ddfa4adaee2cfa27a924ed906badbfae8cc4a4a04256c.pickle'] - >>> Simbad.clear_cache() - >>> os.listdir(Simbad.cache_location) # doctest: +IGNORE_OUTPUT + >>> Vizier.clear_cache() + >>> os.listdir(Vizier.cache_location) # doctest: +IGNORE_OUTPUT [] Astroquery-wide settings @@ -232,7 +231,7 @@ temporarily or permanently changing configuration values can be found .. code-block:: python - >>> from astroquery import cache_conf + >>> from astroquery import cache_conf ... >>> # Is the cache active? >>> print(cache_conf.cache_active) @@ -242,7 +241,6 @@ temporarily or permanently changing configuration values can be found 604800 - Available Services ================== diff --git a/docs/simbad/query_tap.rst b/docs/simbad/query_tap.rst index 37185bcf0e..72542b16db 100644 --- a/docs/simbad/query_tap.rst +++ b/docs/simbad/query_tap.rst @@ -1,3 +1,5 @@ +.. _query-tap-documentation: + `~astroquery.simbad.SimbadClass.query_tap` (for Table Access Protocol) is the one query to rule them all. It allows one to access all the information in SIMBAD with the Astronomical Data Query Language (ADQL). ADQL is a flavor of the Structured @@ -6,7 +8,7 @@ see the `ADQL documentation `__ or the `Simbad ADQL cheat sheet `__. Structure of an ADQL query -^^^^^^^^^^^^^^^^^^^^^^^^^^ +-------------------------- The method `~astroquery.simbad.SimbadClass.query_tap` is called with a string containing the ADQL query. @@ -29,7 +31,7 @@ ADQL query. the objects cited by the largest number of papers.*/ ORDER BY nbref DESC -This ADQL query can be called with `~astroquery.simbad.SimbadClass.query_tap`: +This ADQL query can be called with `~astroquery.simbad.SimbadClass.query_tap`: .. nbref changes often so we ignore the output here .. doctest-remote-data:: @@ -42,7 +44,7 @@ This ADQL query can be called with `~astroquery.simbad.SimbadClass.query_tap`: ... ORDER BY nbref DESC""") # doctest: +IGNORE_OUTPUT ra dec main_id nbref - deg deg + deg deg float64 float64 object int32 ------------------ ------------------ -------- ----- 10.684708333333333 41.268750000000004 M 31 12412 @@ -56,7 +58,7 @@ of stars, and have a redshift < 1. The following sections cover methods that hel queries. A showcase of more complex queries comes after. Available tables -^^^^^^^^^^^^^^^^ +---------------- SIMBAD is a relational database. This means that it is a collection of tables with links between them. You can access a `graphic representation of Simbad's tables and @@ -69,8 +71,8 @@ the names and descriptions of each table with the >>> from astroquery.simbad import Simbad >>> Simbad.list_tables() # doctest: +IGNORE_OUTPUT
- table_name description - object object + table_name description + object object ------------- ---------------------------------------------------------------------------- basic General data about an astronomical object ids all names concatenated with pipe @@ -126,7 +128,7 @@ join statement: ``[...] mesDiameter JOIN basic ON mesDiameter.oidref = basic.oid :alt: This interactive graph summarizes the information that can be obtained with `~astroquery.simbad.SimbadClass.list_tables` and `~astroquery.simbad.SimbadClass.list_linked_tables`. Available columns -^^^^^^^^^^^^^^^^^ +----------------- `~astroquery.simbad.SimbadClass.list_columns` lists the columns in all or a subset of SIMBAD tables. Calling it with no argument returns the 289 columns of SIMBAD. To restrict the output to @@ -137,8 +139,8 @@ some tables, add their name. To get the columns of the tables ``ref`` and ``bibl >>> from astroquery.simbad import Simbad >>> Simbad.list_columns("ref", "biblio")
- table_name column_name datatype ... unit ucd - object object object ... object object + table_name column_name datatype ... unit ucd + object object object ... object object ---------- ----------- ----------- ... ------ -------------------- biblio biblio TEXT ... meta.record;meta.bib biblio oidref BIGINT ... meta.record;meta.id @@ -177,12 +179,12 @@ in the column name or in its description. This is not case-sensitive. mesVelocities origin ... meta.note Example TAP queries -^^^^^^^^^^^^^^^^^^^ +------------------- This section lists more complex queries by looking at use cases from former astroquery issues. Getting all bibcodes containing a certain type of measurement for a given object -"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The measurement tables -- the ones with names starting with ``mes``-- have a bibcode column that corresponds to the paper in which the information was found. @@ -198,21 +200,22 @@ that is the measurement table for rotations. Their common column is ``oidref``. ... WHERE id = 'Sirius'; ... """ >>> Simbad.query_tap(query) -
+
Rotation Measurements Bibcodes - object + object ------------------------------ - 2016A&A...589A..83G - 2002A&A...393..897R - 1995ApJS...99..135A - 1970CoKwa.189....0U - 1970CoAsi.239....1B - 2011A&A...531A.143D + 2023ApJS..266...11B + 2016A&A...589A..83G + 2002A&A...393..897R + 1995ApJS...99..135A + 1970CoKwa.189....0U + 1970CoAsi.239....1B + 2011A&A...531A.143D This returns six papers in which the SIMBAD team found rotation data for Sirius. Criteria on region, measurements and object types -""""""""""""""""""""""""""""""""""""""""""""""""" +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Here we search for objects that are not stars and have a redshift<0.4 in a cone search. All this information is in the ``basic`` column. The ``star..`` syntax refers to any type of star. @@ -221,14 +224,14 @@ is in the ``basic`` column. The ``star..`` syntax refers to any type of star. >>> from astroquery.simbad import Simbad >>> query = """SELECT ra, dec, main_id, rvz_redshift, otype - ... FROM basic + ... FROM basic ... WHERE otype != 'star..' ... AND CONTAINS(POINT('ICRS', basic.ra, basic.dec), CIRCLE('ICRS', 331.92, +12.44 , 0.25)) = 1 ... AND rvz_redshift <= 0.4""" >>> Simbad.query_tap(query)
- ra dec main_id rvz_redshift otype - deg deg + ra dec main_id rvz_redshift otype + deg deg float64 float64 object float64 object --------------- ------------------ ------------------------ ------------ ------ 331.86493815752 12.61105991847 SDSS J220727.58+123639.8 0.11816 EmG @@ -242,11 +245,11 @@ is in the ``basic`` column. The ``star..`` syntax refers to any type of star. 331.951995 12.431051 SDSS J220748.47+122551.7 0.16484 G 332.171805 12.430204 SDSS J220841.23+122548.7 0.14762 G 332.084711 12.486509 SDSS J220820.33+122911.4 0.12246 G - + This returns a few galaxies 'G' and emission-line galaxies 'EmG'. Get the members of a galaxy cluster -""""""""""""""""""""""""""""""""""" +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ All membership information is in the ``h_link`` table. We first need to retrieve the ``oidref`` corresponding to the parent cluster SDSSCGB 350. This is done is the sub-query between parenthesis. @@ -265,8 +268,8 @@ Then, the ``basic`` table is joined with ``h_link`` and the sub-query result. >>> Simbad.query_tap(query)
child id otype ra ... membership parent cluster - deg ... percent - object object float64 ... int16 object + deg ... percent + object object float64 ... int16 object ------------------------ ------ ------------------ ... ---------- -------------- SDSSCGB 350.4 G 243.18303333333336 ... 75 SDSSCGB 350 SDSS J161245.36+281652.4 G 243.18900464937997 ... 75 SDSSCGB 350 @@ -276,8 +279,8 @@ Then, the ``basic`` table is joined with ``h_link`` and the sub-query result. SDSSCGB 350.1 G 243.18618110644002 ... 100 SDSSCGB 350 LEDA 1831614 G 243.189153 ... 100 SDSSCGB 350 -Query a long list of object -""""""""""""""""""""""""""" +Query a long list of objects +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ To query a list of objects (or coordinates, of bibliographic references), we can use the ADQL criteria ``IN`` like so: @@ -305,10 +308,10 @@ an `~astropy.table.Table` containing the desired list and use it in a ``JOIN`` t >>> from astropy.table import Table >>> list_of_objects = Table([["M1", "M2", "M3"]], names=["Messier_objects"]) >>> query = """SELECT main_id, otype FROM basic - ... JOIN TAP_UPLOAD.messiers + ... JOIN TAP_UPLOAD.messiers ... ON basic.main_id = TAP_UPLOAD.messiers.Messier_objects ... """ - >>> Simbad.query_tap(query, messiers=list_of_objects) + >>> Simbad.query_tap(query, messiers=list_of_objects)
main_id otype object object diff --git a/docs/simbad/simbad.rst b/docs/simbad/simbad.rst index 117ecc0dcd..2d64d29413 100644 --- a/docs/simbad/simbad.rst +++ b/docs/simbad/simbad.rst @@ -1,5 +1,3 @@ -.. doctest-skip-all - .. _astroquery_simbad: ************************************ @@ -9,34 +7,44 @@ SIMBAD Queries (`astroquery.simbad`) Getting started =============== -This module can be used to query the Simbad service. Presented below are +This module can be used to query the SIMBAD service. Presented below are examples that illustrate the different types of queries that can be formulated. If successful all the queries will return the results in a `~astropy.table.Table`. -A warning about big queries ---------------------------- +A warning about query rate +-------------------------- The SIMBAD database is widely used and has to limit the rate of incoming queries. If you spam the server with more that ~5-10 queries per second you will be -blacklisted for an hour. If it happens to you, you can use the section about -:ref:`vectorized queries ` below. You can pass -`~astroquery.simbad.SimbadClass.query_region` -a vector of coordinates or `~astroquery.simbad.SimbadClass.query_objects` -a list of object names, and SIMBAD will treat this submission as a single -query. - -Different ways to access Simbad +blacklisted for up to an hour. This can happen when a query method is called within a loop. +There is always a way to send the information in a bigger query rather than in a lot of +smaller ones. Frequent use cases are that you can pass a vector of coordinates to +`~astroquery.simbad.SimbadClass.query_region` or a list of names to +`~astroquery.simbad.SimbadClass.query_objects`, and SIMBAD will treat this submission as +a single query. If this does not fit your use case, then you'll need to either use +`Wildcards`_ or a custom :ref:`query TAP `. + +Simbad Evolutions +----------------- + +The SIMBAD module follows evolutions of the SIMBAD database. +Some of these changes are documented into more details here: + +.. toctree:: + :maxdepth: 2 + + /simbad/simbad_evolution + +Different ways to access SIMBAD ------------------------------- -The Simbad tool described here provides a number of convenient methods that -internally creates a `script query -`__ to the Simbad server, which -is also how the `Simbad web interface `__ -operates. +The SIMBAD module described here provides methods that write ADQL queries. These +methods are described in the next sections. -A more versatile option is to query SIMBAD directly via Table Access Protocol -(TAP) with the `~astroquery.simbad.SimbadClass.query_tap` method. +A more versatile option is to query SIMBAD directly with your own ADQL queries via +Table Access Protocol (TAP) with the `~astroquery.simbad.SimbadClass.query_tap` method. +This is described in this section: :ref:`query TAP `. Query modes =========== @@ -47,689 +55,555 @@ Objects queries Query by an Identifier ^^^^^^^^^^^^^^^^^^^^^^ -This is useful if you want to query a known identifier (name). For instance to query -the messier object M1: +This is useful if you want to query an object by a known identifier (name). For instance +to query the messier object M1: -.. code-block:: python +.. doctest-remote-data:: >>> from astroquery.simbad import Simbad - >>> result_table = Simbad.query_object("M1") + >>> result_table = Simbad.query_object("m1") >>> print(result_table) + main_id ra dec ... coo_wavelength coo_bibcode matched_id + deg deg ... + ------- ------- ------- ... -------------- ------------------- ---------- + M 1 83.6287 22.0147 ... R 1995AuJPh..48..143S M 1 + +`Wildcards`_ are supported. Note that this makes the query case-sensitive. +This allows, for instance, to query messier objects from 1 through 9: - MAIN_ID RA DEC RA_PREC DEC_PREC COO_ERR_MAJA COO_ERR_MINA COO_ERR_ANGLE COO_QUAL COO_WAVELENGTH COO_BIBCODE - ------- ----------- ----------- ------- -------- ------------ ------------ ------------- -------- -------------- ------------------- - M 1 05 34 31.94 +22 00 52.2 6 6 nan nan 0 C R 2011A&A...533A..10L - -Wildcards are supported. So for instance to query messier objects from 1 -through 9: +.. + The following example is very slow ~20s due to a current (2024) bug in + the SIMBAD regexp. This should be removed from the skipped tests + once the bug is fixed upstream. -.. code-block:: python +.. doctest-remote-data:: >>> from astroquery.simbad import Simbad - >>> result_table = Simbad.query_object("m [1-9]", wildcard=True) - >>> print(result_table) - - MAIN_ID RA DEC RA_PREC DEC_PREC COO_ERR_MAJA COO_ERR_MINA COO_ERR_ANGLE COO_QUAL COO_WAVELENGTH COO_BIBCODE - ------- ----------- ----------- ------- -------- ------------ ------------ ------------- -------- -------------- ------------------- - M 1 05 34 31.94 +22 00 52.2 6 6 nan nan 0 C R 2011A&A...533A..10L - M 2 21 33 27.02 -00 49 23.7 6 6 100.000 100.000 0 C O 2010AJ....140.1830G - M 3 13 42 11.62 +28 22 38.2 6 6 200.000 200.000 0 C O 2010AJ....140.1830G - M 4 16 23 35.22 -26 31 32.7 6 6 400.000 400.000 0 C O 2010AJ....140.1830G - M 5 15 18 33.22 +02 04 51.7 6 6 nan nan 0 C O 2010AJ....140.1830G - M 6 17 40 20 -32 15.2 4 4 nan nan 0 E O 2009MNRAS.399.2146W - M 7 17 53 51 -34 47.6 4 4 nan nan 0 E O 2009MNRAS.399.2146W - M 8 18 03 37 -24 23.2 4 4 18000.000 18000.000 179 E - M 9 17 19 11.78 -18 30 58.5 6 6 nan nan 0 D 2002MNRAS.332..441F - -Wildcards are supported by other queries as well - where this is the case, -examples are presented to this end. The wildcards that are supported and their -usage across all these queries is the same. To see the available wildcards and -their functions: + >>> result_table = Simbad.query_object("M [1-9]", wildcard=True) # doctest: +SKIP + >>> print(result_table) # doctest: +SKIP + main_id ra ... coo_bibcode matched_id + deg ... + --------- ------------------ ... ------------------- ---------- + M 1 83.6287 ... 1995AuJPh..48..143S M 1 + M 2 323.36258333333336 ... 2010AJ....140.1830G M 2 + M 3 205.5484166666666 ... 2010AJ....140.1830G M 3 + NGC 6475 268.44699999999995 ... 2021A&A...647A..19T M 7 + NGC 6405 265.06899999999996 ... 2021A&A...647A..19T M 6 + M 4 245.89675000000003 ... 2010AJ....140.1830G M 4 + M 8 270.90416666666664 ... M 8 + M 9 259.79908333333333 ... 2002MNRAS.332..441F M 9 + M 5 229.63841666666673 ... 2010AJ....140.1830G M 5 + + +The messier objects from 1 to 9 are found. Their main identifier ``main_id`` is not +necessarily the one corresponding to the wildcard expression. +The column ``matched_id`` contains the identifier that was matched. + +Note that in this example, the wildcard parameter could have been replaced by a way +faster query done with `~astroquery.simbad.SimbadClass.query_objects`. + +Wildcards +""""""""" + +Wildcards are supported in these methods: + - `~astroquery.simbad.SimbadClass.query_object` + - `~astroquery.simbad.SimbadClass.query_objects` + - `~astroquery.simbad.SimbadClass.query_bibcode` + +They allow to provide a pattern that the query will match. To see the available +wildcards and their meaning: .. code-block:: python >>> from astroquery.simbad import Simbad - >>> Simbad.list_wildcards() - - * : Any string of characters (including an empty one) - - [^0-9] : Any (one) character not in the list. - - ? : Any character (exactly one character) - - [abc] : Exactly one character taken in the list. Can also be defined by a range of characters: [A-Z] + >>> Simbad().list_wildcards() + *: Any string of characters (including an empty one) + ?: Any character (exactly one character) + [abc]: Exactly one character taken in the list. Can also be defined by a range of characters: [A-Z] + [^0-9]: Any (one) character not in the list. -Query to get all names (identifiers) for an object -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Query to get all names (identifiers) of an object +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ These queries can be used to retrieve all of the names (identifiers) associated with an object. -.. code-block:: python +.. + This could change (each time someone invents a new name for Polaris). + +.. doctest-remote-data:: >>> from astroquery.simbad import Simbad >>> result_table = Simbad.query_objectids("Polaris") - >>> print(result_table) - ID + >>> result_table +
+ id + object ----------------------- - NAME Polaris - NAME North Star + HIP 11767 + TIC 303256075 NAME Lodestar PLX 299 SBC9 76 * 1 UMi - * alf UMi - AAVSO 0122+88 - ADS 1477 A - AG+89 4 - BD+88 8 - CCDM J02319+8915A - CSI+88 8 1 - FK5 907 - GC 2243 - GCRV 1037 ... - PPM 431 - ROT 3491 - SAO 308 - SBC7 51 - SKY# 3738 - TD1 835 - TYC 4628-237-1 - UBV 21589 - UBV M 8201 - V* alf UMi - PLX 299.00 - WDS J02318+8916Aa,Ab ADS 1477 AP ** WRH 39 WDS J02318+8916A ** STF 93A 2MASS J02314822+8915503 + NAME North Star + WEB 2438 Query a region ^^^^^^^^^^^^^^ -Queries that support a cone search with a specified radius - around an -identifier or given coordinates are also supported. If an identifier is used -then it will be resolved to coordinates using the `Sesame name resolver -`__. - -.. code-block:: python - - >>> from astroquery.simbad import Simbad - >>> result_table = Simbad.query_region("m81") - >>> print(result_table) +Query in a cone with a specified radius. The center can be a string with an +identifier, a string representing coordinates, or a `~astropy.coordinates.SkyCoord`. - MAIN_ID RA DEC RA_PREC DEC_PREC ... COO_ERR_ANGLE COO_QUAL COO_WAVELENGTH COO_BIBCODE - -------------------------- ------------- ------------- ------- -------- ... ------------- -------- -------------- ------------------- - [VV2006c] J095534.0+043546 09 55 33.9854 +04 35 46.438 8 8 ... 0 B O 2009A&A...505..385A - ... +.. + This output will also change often. -When no radius is specified, the radius defaults to 20 arcmin. A radius may -also be explicitly specified - it can be entered either as a string that is -acceptable by `~astropy.coordinates.Angle` or by using -the `~astropy.units.Quantity` object: - -.. code-block:: python +.. doctest-remote-data:: >>> from astroquery.simbad import Simbad - >>> import astropy.units as u - >>> result_table = Simbad.query_region("m81", radius=0.1 * u.deg) - >>> # another way to specify the radius. - >>> result_table = Simbad.query_region("m81", radius='0d6m0s') - >>> print(result_table) - - MAIN_ID RA ... COO_BIBCODE - ----------------------- ------------- ... ------------------- - M 81 09 55 33.1730 ... 2004AJ....127.3587F - [SPZ2011] ML2 09 55 32.97 ... 2011ApJ...735...26S - [F88] X-5 09 55 33.32 ... 2001ApJ...554..202I - [SPZ2011] 264 09 55 32.618 ... 2011ApJ...735...26S - [SPZ2011] ML1 09 55 33.10 ... 2011ApJ...735...26S - [SPZ2011] ML3 09 55 33.99 ... 2011ApJ...735...26S - [SPZ2011] ML5 09 55 33.39 ... 2011ApJ...735...26S - [SPZ2011] ML6 09 55 32.47 ... 2011ApJ...735...26S - ... ... ... ... - [MPC2001] 8 09 54 45.50 ... 2001A&A...379...90M - 2MASS J09561112+6859003 09 56 11.13 ... 2003yCat.2246....0C - [PR95] 50721 09 56 36.460 ... - PSK 72 09 54 54.1 ... - PSK 353 09 56 03.7 ... - [BBC91] S02S 09 56 07.1 ... - PSK 489 09 56 36.55 ... 2003AJ....126.1286L - PSK 7 09 54 37.0 ... - - - - -If coordinates are used, then they should be entered using an `astropy.coordinates.SkyCoord` -object. - -.. code-block:: python - - >>> from astroquery.simbad import Simbad - >>> import astropy.coordinates as coord - >>> result_table = Simbad.query_region(coord.SkyCoord("05h35m17.3s -05h23m28s", frame='icrs'), radius='1d0m0s') - >>> print(result_table) - - MAIN_ID RA ... COO_BIBCODE - ----------------------- ------------- ... ------------------- - HD 38875 05 34 59.7297 ... 2007A&A...474..653V - TYC 9390-799-1 05 33 58.2222 ... 1998A&A...335L..65H - TYC 9390-646-1 05 35 02.830 ... 2000A&A...355L..27H - TYC 9390-629-1 05 35 20.419 ... 2000A&A...355L..27H - TYC 9390-857-1 05 30 58.989 ... 2000A&A...355L..27H - TYC 9390-1171-1 05 37 35.9623 ... 1998A&A...335L..65H - TYC 9390-654-1 05 35 27.395 ... 2000A&A...355L..27H - TYC 9390-656-1 05 30 43.665 ... 2000A&A...355L..27H - ... ... ... ... - TYC 9373-779-1 05 11 57.788 ... 2000A&A...355L..27H - TYC 9377-513-1 05 10 43.0669 ... 1998A&A...335L..65H - TYC 9386-135-1 05 28 24.988 ... 2000A&A...355L..27H - TYC 9390-1786-1 05 56 34.801 ... 2000A&A...355L..27H - 2MASS J05493730-8141270 05 49 37.30 ... 2003yCat.2246....0C - TYC 9390-157-1 05 35 55.233 ... 2000A&A...355L..27H - PKS J0557-8122 05 57 26.80 ... 2003MNRAS.342.1117M - PKS 0602-813 05 57 30.7 ... - - -.. code-block:: python + >>> # get 10 objects in a radius of 0.5° around M81 + >>> simbad = Simbad() + >>> simbad.ROW_LIMIT = 10 + >>> result_table = simbad.query_region("m81", radius="0.5d") + >>> print(result_table) # doctest: +IGNORE_OUTPUT + main_id ra dec ... coo_err_angle coo_wavelength coo_bibcode + deg deg ... deg + ---------------------------------- ------------------ ----------------- ... ------------- -------------- ------------------- + [PR95] 40298 149.14159166666667 69.19170000000001 ... -- + [GTK91b] 19 149.03841666666668 69.21222222222222 ... -- + [GTK91b] 15 149.26095833333332 69.22230555555556 ... -- + PSK 212 148.86083333333332 69.15333333333334 ... -- + PSK 210 148.8595833333333 69.20111111111112 ... -- + [BBC91] N06 148.84166666666664 69.14222222222223 ... -- + [GKP2011] M81C J095534.66+691213.7 148.89441666666667 69.20380555555556 ... -- O 2011ApJ...743..176G + [PR95] 51153 148.89568749999998 69.1995888888889 ... -- O 2012ApJ...747...15K + PSK 300 148.96499999999997 69.16638888888889 ... -- + PSK 234 148.9008333333333 69.19944444444445 ... -- + +When no radius is specified, the radius defaults to 2 arcmin. When the radius is +explicitly specified it can be either a string accepted by +`~astropy.coordinates.Angle` (ex: ``radius='0d6m0s'``) or directly a +`~astropy.units.Quantity` object. + +If the center is defined by coordinates, then the best solution is to use a +`astropy.coordinates.SkyCoord` object. + +.. doctest-remote-data:: >>> from astroquery.simbad import Simbad - >>> import astropy.coordinates as coord + >>> from astropy.coordinates import SkyCoord >>> import astropy.units as u - >>> result_table = Simbad.query_region(coord.SkyCoord(31.0087, 14.0627, - ... unit=(u.deg, u.deg), frame='galactic'), - ... radius='0d0m2s') - >>> print(result_table) - - MAIN_ID RA ... COO_WAVELENGTH COO_BIBCODE - ------------------- ------------- ... -------------- ------------------- - NAME Barnard's star 17 57 48.4980 ... O 2007A&A...474..653V + >>> Simbad.query_region(SkyCoord(31.0087, 14.0627, unit=(u.deg, u.deg), + ... frame='galactic'), radius=2 * u.arcsec) +
+ main_id ra ... coo_wavelength coo_bibcode + deg ... + object float64 ... str1 object + ------------------- ----------------- ... -------------- ------------------- + GJ 699 b 269.4520769586187 ... O 2020yCat.1350....0G + NAME Barnard's star 269.4520769586187 ... O 2020yCat.1350....0G +.. Note:: + Calling `~astroquery.simbad.SimbadClass.query_region` within a loop is **very** + inefficient. If you need to query many regions, use a multi-coordinate + `~astropy.coordinates.SkyCoord` and a list of radii. It looks like this: -Two other options can also be specified - the epoch and the equinox. If these -are not explicitly mentioned, then the epoch defaults to J2000 and the equinox -to 2000.0. So here is a query with all the options utilized: - -.. code-block:: python +.. doctest-remote-data:: >>> from astroquery.simbad import Simbad - >>> import astropy.coordinates as coord + >>> from astropy.coordinates import SkyCoord >>> import astropy.units as u - >>> result_table = Simbad.query_region(coord.SkyCoord(ra=11.70, dec=10.90, - ... unit=(u.deg, u.deg), frame='fk5'), - ... radius=0.5 * u.deg, - ... epoch='B1950', - ... equinox=1950) - >>> print(result_table) - - MAIN_ID RA ... COO_BIBCODE - ----------------------- ------------- ... ------------------- - PHL 6696 00 49.4 ... - BD+10 97 00 49 25.4553 ... 2007A&A...474..653V - TYC 607-238-1 00 48 53.302 ... 2000A&A...355L..27H - PHL 2998 00 49.3 ... - 2MASS J00492121+1121094 00 49 21.219 ... 2003yCat.2246....0C - TYC 607-1135-1 00 48 46.5838 ... 1998A&A...335L..65H - 2MASX J00495215+1118527 00 49 52.154 ... 2006AJ....131.1163S - BD+10 98 00 50 03.4124 ... 1998A&A...335L..65H - ... ... ... ... - TYC 607-971-1 00 47 38.0430 ... 1998A&A...335L..65H - TYC 607-793-1 00 50 35.545 ... 2000A&A...355L..27H - USNO-A2.0 0975-00169117 00 47 55.351 ... 2007ApJ...664...53A - TYC 607-950-1 00 50 51.875 ... 2000A&A...355L..27H - BD+10 100 00 51 15.0789 ... 1998A&A...335L..65H - TYC 608-60-1 00 51 13.314 ... 2000A&A...355L..27H - TYC 608-432-1 00 51 05.289 ... 2000A&A...355L..27H - TYC 607-418-1 00 49 09.636 ... 2000A&A...355L..27H - + >>> Simbad.query_region(SkyCoord(ra=[10, 11], dec=[10, 11], + ... unit=(u.deg, u.deg), frame='fk5'), + ... radius=[0.1 * u.deg, 2* u.arcmin]) # doctest: +IGNORE_OUTPUT +
+ main_id ra ... coo_bibcode + deg ... + object float64 ... object + ------------------------ ------------------ ... ------------------- + SDSS J004014.26+095527.0 10.059442999999998 ... 2020ApJS..250....8L + LEDA 1387229 10.988333333333335 ... 2003A&A...412...45P + IRAS 00371+0946 9.92962860161661 ... 1988NASAR1190....1B + IRAS 00373+0947 9.981768085280164 ... 1988NASAR1190....1B + PLCKECC G118.25-52.70 9.981250000000001 ... 2011A&A...536A...7P + GALEX J004011.0+095752 10.045982309580001 ... 2020yCat.1350....0G + +If the radius is the same in every cone, you can also just give this single radius without +having to create the list (ex: ``radius = "5arcmin"``). Query a catalogue ^^^^^^^^^^^^^^^^^ -Queries can also be formulated to return all the objects from a catalogue. For -instance to query the ESO catalog: +Queries can also return all the objects from a catalogue. For instance to query +the ESO catalog: -.. code-block:: python +.. doctest-remote-data:: >>> from astroquery.simbad import Simbad - >>> limitedSimbad = Simbad() - >>> limitedSimbad.ROW_LIMIT = 6 - >>> result_table = limitedSimbad.query_catalog('eso') - >>> print(result_table) + >>> simbad = Simbad(ROW_LIMIT=6) + >>> simbad.query_catalog('ESO') +
+ main_id ra ... coo_bibcode catalog_id + deg ... + object float64 ... object object + --------- ------------------ ... ------------------- ---------- + NGC 2573 25.40834109527 ... 2020yCat.1350....0G ESO 1-1 + ESO 1-2 76.15327 ... 2020MNRAS.494.1784A ESO 1-2 + ESO 1-3 80.65212083333333 ... 2006AJ....131.1163S ESO 1-3 + ESO 1-4 117.37006325383999 ... 2020yCat.1350....0G ESO 1-4 + ESO 1-5 133.2708583333333 ... 2006AJ....131.1163S ESO 1-5 + ESO 1-6 216.83122280179 ... 2020yCat.1350....0G ESO 1-6 + +Note that the name in ``main_id`` is not necessarily from the queried catalog. This +information is in the ``catalog_id`` column. + +To see the available catalogues, you can write a custom ADQL query +(see :ref:`query_tap `.) on the ``cat`` table. +For example to get the 10 biggest catalogs in SIMBAD, it looks like this: + +.. + This changes quite often. Hence the doctest skip + +.. doctest-remote-data:: - MAIN_ID RA ... COO_WAVELENGTH COO_BIBCODE - ----------------------- ------------ ... -------------- ------------------- - 2MASS J08300740-4325465 08 30 07.41 ... I 2003yCat.2246....0C - NGC 2573 01 41 35.091 ... I 2006AJ....131.1163S - ESO 1-2 05 04 36.8 ... 1982ESO...C......0L - ESO 1-3 05 22 36.509 ... I 2006AJ....131.1163S - ESO 1-4 07 49 28.813 ... I 2006AJ....131.1163S - ESO 1-5 08 53 05.006 ... I 2006AJ....131.1163S + >>> from astroquery.simbad import Simbad + >>> Simbad.query_tap('SELECT TOP 10 cat_name, description FROM cat ORDER BY "size" DESC') # doctest: +IGNORE_OUTPUT +
+ cat_name description + object object + -------- ---------------------------------------------------------------- + Gaia Gaia + 2MASS 2 Micron Sky Survey, Point Sources + TIC TESS Input Catalog + SDSS Sloan Digital Sky Survey + TYC Tycho mission + OGLE Optical Gravitational Lensing Event + UCAC4 Fourth USNO CCD Astrograph Catalog + WISE Wide-field Infrared Survey Explorer Final Release Source Catalog + GSC Guide Star Catalogue + LEDA Lyon-Meudon Extragalactic DatabaseA + +Where you can remove ``TOP 10`` to get **all** the catalogues (there's a lot of them). Bibliographic queries --------------------- Query a bibcode -^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^ -This retrieves the reference corresponding to a bibcode. +This retrieves information about the article corresponding to a bibcode. -.. code-block:: python +.. doctest-remote-data:: >>> from astroquery.simbad import Simbad - >>> result_table = Simbad.query_bibcode('2005A&A.430.165F') - >>> print(result_table) + >>> Simbad.query_bibcode('2005A&A.430.165F') +
+ bibcode doi journal ... volume year + object object object ... int32 int16 + ------------------- -------------------------- ------- ... ------ ----- + 2005A&A...430..165F 10.1051/0004-6361:20041272 A&A ... 430 2005 - References - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - 2005A&A...430..165F -- ? - Astron. Astrophys., 430, 165-186 (2005) - FAMAEY B., JORISSEN A., LURI X., MAYOR M., UDRY S., DEJONGHE H. and TURON C. - Local kinematics of K and M giants from CORAVEL/Hipparcos/Tycho-2 data. Revisiting the concept of superclusters. - Files: (abstract) - Notes: +The abstract of the reference can also be added as an other column in the output by +setting the ``abstract`` parameter to ``True``. -Wildcards can be used in these queries as well. So to retrieve all the bibcodes -from a given journal in a given year: +`Wildcards`_ can be used in these queries as well. This can be useful to retrieve all +the bibcodes from a given journal in a given year: -.. code-block:: python +.. doctest-remote-data:: >>> from astroquery.simbad import Simbad - >>> result_table = Simbad.query_bibcode('2013A&A*', wildcard=True) - >>> print(result_table) + >>> biblio = Simbad.query_bibcode('2013A&ARv.*', wildcard=True) + >>> biblio.sort("bibcode") + >>> biblio +
+ bibcode doi journal ... volume year + object object object ... int32 int16 + ------------------- ------------------------- ------- ... ------ ----- + 2013A&ARv..21...59I 10.1007/s00159-013-0059-2 A&ARv ... 21 2013 + 2013A&ARv..21...61R 10.1007/s00159-013-0061-8 A&ARv ... 21 2013 + 2013A&ARv..21...62D 10.1007/s00159-013-0062-7 A&ARv ... 21 2013 + 2013A&ARv..21...63T 10.1007/s00159-013-0063-6 A&ARv ... 21 2013 + 2013A&ARv..21...64D 10.1007/s00159-013-0064-5 A&ARv ... 21 2013 + 2013A&ARv..21...67B 10.1007/s00159-013-0067-2 A&ARv ... 21 2013 + 2013A&ARv..21...68G 10.1007/s00159-013-0068-1 A&ARv ... 21 2013 + 2013A&ARv..21...69R 10.1007/s00159-013-0069-0 A&ARv ... 21 2013 + 2013A&ARv..21...70B 10.1007/s00159-013-0070-7 A&ARv ... 21 2013 + +or to look for articles published between 2010 and 2012 with a given keyword: + +.. doctest-remote-data:: - References - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - 2013A&A...549A...1G -- ? - Astron. Astrophys., 549A, 1-1 (2013) - GENTILE M., COURBIN F. and MEYLAN G. - Interpolating point spread function anisotropy. - Files: (abstract) (no object) - - 2013A&A...549A...2L -- ? - Astron. Astrophys., 549A, 2-2 (2013) - LEE B.-C., HAN I. and PARK M.-G. - Planetary companions orbiting M giants HD 208527 and HD 220074. - Files: (abstract) - - 2013A&A...549A...3C -- ? - Astron. Astrophys., 549A, 3-3 (2013) - COCCATO L., MORELLI L., PIZZELLA A., CORSINI E.M., BUSON L.M. and DALLA BONTA E. - Spectroscopic evidence of distinct stellar populations in the counter-rotating stellar disks of NGC 3593 and NGC 4550. - Files: (abstract) - - 2013A&A...549A...4S -- ? - Astron. Astrophys., 549A, 4-4 (2013) - SCHAERER D., DE BARROS S. and SKLIAS P. - Properties of z ~ 3-6 Lyman break galaxies. I. Testing star formation histories and the SFR-mass relation with ALMA and near-IR spectroscopy. - Files: (abstract) - - 2013A&A...549A...5R -- ? - Astron. Astrophys., 549A, 5-5 (2013) - RYGL K.L.J., WYROWSKI F., SCHULLER F. and MENTEN K.M. - Initial phases of massive star formation in high infrared extinction clouds. II. Infall and onset of star formation. - Files: (abstract) - - 2013A&A...549A...6K -- ? - Astron. Astrophys., 549A, 6-6 (2013) - KAMINSKI T., SCHMIDT M.R. and MENTEN K.M. - Aluminium oxide in the optical spectrum of VY Canis Majoris. - Files: (abstract) + >>> from astroquery.simbad import Simbad + >>> simbad = Simbad() + >>> simbad.ROW_LIMIT = 5 + >>> simbad.query_bibcode('20??A&A.*', wildcard=True, + ... criteria=("\"year\" >= 2010 and \"year\" <= 2012" + ... " and \"abstract\" like '%exoplanet%'")) +
+ bibcode doi journal ... volume year + object object object ... int32 int16 + ------------------- --------------------------- ------- ... ------ ----- + 2010A&A...509A..31G 10.1051/0004-6361/200912902 A&A ... 509 2010 + 2010A&A...510A..21S 10.1051/0004-6361/200913675 A&A ... 510 2010 + 2010A&A...510A.107M 10.1051/0004-6361/200912910 A&A ... 510 2010 + 2010A&A...511A..36C 10.1051/0004-6361/200913629 A&A ... 511 2010 + 2010A&A...511L...1M 10.1051/0004-6361/201014139 A&A ... 511 2010 + +As you can see, some wildcards can be replaced by a criteria (ex: we could also +write: ``"journal" = 'A&A'`` in the criteria string). It is often faster to avoid +wildcards and use a criteria instead. Query a bibobj ^^^^^^^^^^^^^^ -These queries can be used to retrieve all the objects that are contained in the -article specified by the bibcode: - -.. code-block:: python - - >>> from astroquery.simbad import Simbad - >>> result_table = Simbad.query_bibobj('2006AJ....131.1163S') - >>> print(result_table) - - MAIN_ID RA DEC RA_PREC DEC_PREC ... COO_ERR_MINA COO_ERR_ANGLE COO_QUAL COO_WAVELENGTH COO_BIBCODE - "h:m:s" "d:m:s" ... mas deg - ----------------------- ------------ ------------ ------- -------- ... ------------ ------------- -------- -------------- ------------------- - M 32 00 42 41.825 +40 51 54.61 7 7 ... -- 0 B I 2006AJ....131.1163S - M 31 00 42 44.330 +41 16 07.50 7 7 ... -- 0 B I 2006AJ....131.1163S - NAME SMC 00 52 38.0 -72 48 01 5 5 ... -- 0 D O 2003A&A...412...45P - Cl Melotte 22 03 47 00 +24 07.0 4 4 ... -- 0 E O 2009MNRAS.399.2146W - 2MASX J04504846-7531580 04 50 48.462 -75 31 58.08 7 7 ... -- 0 B I 2006AJ....131.1163S - NAME LMC 05 23 34.6 -69 45 22 5 5 ... -- 0 D O 2003A&A...412...45P - NAME Lockman Hole 10 45 00.0 +58 00 00 5 5 ... -- 0 E 2011ApJ...734...99H - NAME Gal Center 17 45 40.04 -29 00 28.1 6 6 ... -- 0 E - -Query TAP ---------- - -.. include:: query_tap.rst - -Query based on any criteria ---------------------------- - -Anything done in SIMBAD's `criteria interface`_ can be done via astroquery. -See that link for details of how these queries are formed. - -.. code-block:: python - - >>> from astroquery.simbad import Simbad - >>> result = Simbad.query_criteria('region(box, GAL, 0 +0, 3d 1d)', otype='SNR') - >>> print(result) - MAIN_ID RA DEC RA_PREC DEC_PREC COO_ERR_MAJA COO_ERR_MINA COO_ERR_ANGLE COO_QUAL COO_WAVELENGTH COO_BIBCODE - --------------------- ----------- ----------- ------- -------- ------------ ------------ ------------- -------- -------------- ------------------- - EQ J174702.6-282733 17 47 02.6 -28 27 33 5 5 nan nan 0 D 2002ApJ...565.1017S - [L92] 174535.0-280410 17 48 44.4 -28 05 06 5 5 3000.000 3000.000 0 D - [GWC93] 19 17 42 04.9 -30 04 04 5 5 3000.000 3000.000 1 D - SNR G359.1-00.2 17 43 29 -29 45.9 4 4 nan nan 0 E 2000AJ....119..207L - SNR G000.1-00.2 17 48 42.5 -28 09 11 5 5 nan nan 0 D 2008ApJS..177..255L - SNR G359.9-00.9 17 45.8 -29 03 3 3 nan nan 0 - SNR G359.4-00.1 17 44 37 -29 27.2 4 4 18000.000 18000.000 1 E - NAME SGR D 17 48 42 -28 01.4 4 4 18000.000 18000.000 0 E - SNR G359.1-00.5 17 45 25 -29 57.9 4 4 18000.000 18000.000 1 E - NAME SGR D SNR 17 48.7 -28 07 3 3 nan nan 0 E - Suzaku J1747-2824 17 47 00 -28 24.5 4 4 nan nan 0 E 2007ApJ...666..934C - SNR G000.4+00.2 17 46 27.65 -28 36 05.6 6 6 300.000 300.000 1 D - SNR G001.4-00.1 17 49 28.1 -27 47 45 5 5 nan nan 0 D 1999ApJ...527..172Y - GAL 000.61+00.01 17 47.0 -28 25 3 3 nan nan 0 D - SNR G000.9+00.1 17 47.3 -28 09 3 3 nan nan 0 E R 2009BASI...37...45G - SNR G000.3+00.0 17 46 14.9 -28 37 15 5 5 3000.000 3000.000 1 D - SNR G001.0-00.1 17 48.5 -28 09 3 3 nan nan 0 E R 2009BASI...37...45G - NAME SGR A EAST 17 45 47 -29 00.2 4 4 18000.000 18000.000 1 E - - -Object type criteria -^^^^^^^^^^^^^^^^^^^^ - -SIMBAD sets a ``maintype`` for each astronomical object that is related to the real type classification. Other object types (``otypes``) are given, which are related to some types coming from some surveys/observations. Depending on your needs, ``maintype`` or ``otype`` fields can be used. -To use all subcategories of an object type, ``maintypes`` or ``otypes`` fields can also be used. -See the dedicated SIMBAD `documentation on object types `__. - -.. code-block:: python - - >>> from astroquery.simbad import Simbad - >>> result = Simbad.query_criteria('region(CIRCLE, Trapezium Nebula, 3m)', maintypes='YSO') - >>> print(result) - MAIN_ID RA DEC RA_PREC DEC_PREC ... COO_ERR_ANGLE COO_QUAL COO_WAVELENGTH COO_BIBCODE SCRIPT_NUMBER_ID - ----------------------- ------------- ------------- ------- -------- ... ------------- -------- -------------- ------------------- ---------------- - * tet01 Ori D 05 35 17.2574 -05 23 16.570 14 14 ... 90 A O 2020yCat.1350....0G 0 - * tet01 Ori A 05 35 15.8254 -05 23 14.334 14 14 ... 90 A O 2020yCat.1350....0G 0 - V* MR Ori 05 35 16.9783 -05 21 45.337 14 14 ... 90 A O 2020yCat.1350....0G 0 - V* V377 Ori 05 35 21.2917 -05 24 57.399 14 14 ... 90 A O 2020yCat.1350....0G 0 - V* AF Ori 05 35 18.6664 -05 23 13.946 14 14 ... 90 A O 2020yCat.1350....0G 0 - V* V1228 Ori 05 35 12.2788 -05 23 48.027 14 14 ... 90 A O 2020yCat.1350....0G 0 - V* V2228 Ori 05 35 12.8166 -05 20 43.608 14 14 ... 90 A O 2020yCat.1350....0G 0 - Parenago 1820 05 35 13.5189 -05 22 19.552 14 14 ... 90 A O 2020yCat.1350....0G 0 - ... ... ... ... ... ... ... ... ... ... ... - HH 998 05 35 16.0 -05 23 54 5 5 ... 0 E 2015AJ....150..108O 0 - Parenago 1823 05 35 14.0513 -05 23 38.466 14 14 ... 90 A O 2020yCat.1350....0G 0 - 2MASS J05351884-0522229 05 35 18.8454 -05 22 22.996 14 14 ... 90 C O 2020yCat.1350....0G 0 - [SRB2015] p132 05 35 25.9362 -05 22 24.404 9 9 ... 0 E s 2015MNRAS.449.1769S 0 - [SRB2015] p136 05 35 14.3406 -05 22 26.643 9 9 ... 0 E s 2015MNRAS.449.1769S 0 - [SRB2015] p142 05 35 25.9653 -05 21 24.460 9 9 ... 0 E s 2015MNRAS.449.1769S 0 - [OW94] 183-405 05 35 18.3314 -05 24 04.844 14 14 ... 90 A O 2020yCat.1350....0G 0 - [H97b] 511b 05 35 16.2800 -05 22 10.420 8 8 ... 0 C R 2016ApJ...831..155S 0 - - -.. _vectorqueries: - -Vectorized Queries -^^^^^^^^^^^^^^^^^^ - -You can query multiple regions at once using vectorized queries. -Each region must have the same radius. - -.. code-block:: python - - >>> from astroquery.simbad import Simbad - >>> import astropy.coordinates as coord - >>> import astropy.units as u - >>> result_table = Simbad.query_region(coord.SkyCoord(ra=[10, 11], dec=[10, 11], - ... unit=(u.deg, u.deg), frame='fk5'), - ... radius=0.1 * u.deg) - >>> print(result_table) - MAIN_ID RA DEC RA_PREC DEC_PREC COO_ERR_MAJA COO_ERR_MINA COO_ERR_ANGLE COO_QUAL COO_WAVELENGTH COO_BIBCODE - "h:m:s" "d:m:s" mas mas deg - ------------------------- ------------- ------------- ------- -------- ------------ ------------ ------------- -------- -------------- ------------------- - PLCKECC G118.25-52.70 00 39 55.5 +10 03 42 5 5 -- -- 0 E m 2011A&A...536A...7P - IRAS 00373+0947 00 39 55.6 +10 04 15 5 5 39000.000 29000.000 67 E F 1988NASAR1190....1B - IRAS 00371+0946 00 39 43.1 +10 03 21 5 5 88000.000 32000.000 67 E F 1988NASAR1190....1B - LEDA 1387229 00 43 57.2 +10 58 54 5 5 -- -- 0 D O 2003A&A...412...45P - LEDA 1387610 00 43 50.3 +11 00 32 5 5 -- -- 0 D O 2003A&A...412...45P - LEDA 1386801 00 43 53.1 +10 56 59 5 5 -- -- 0 D O 2003A&A...412...45P - LEDA 1387466 00 43 41.3 +10 59 57 5 5 -- -- 0 D O 2003A&A...412...45P - NVSS J004420+110010 00 44 20.74 +11 00 10.8 6 6 2800.000 1200.000 90 D 1996AJ....111.1945D - SDSS J004340.18+105815.6 00 43 40.1841 +10 58 15.602 14 14 0.207 0.124 90 A O 2018yCat.1345....0G - GALEX 2675641789401008459 00 43 57.698 +10 54 46.15 7 7 -- -- 0 D 2007ApJ...664...53A - SDSS J004422.75+110104.3 00 44 22.753 +11 01 04.34 7 7 -- -- 0 C O 2017A&A...597A..79P - TYC 607-628-1 00 44 05.6169 +11 05 41.195 14 14 0.047 0.033 90 A O 2018yCat.1345....0G - -You can do the same based on IDs. If you add the votable field ``typed_id``, a -column showing your input identifier will be added: - -.. code-block:: python - - >>> from astroquery.simbad import Simbad - >>> Simbad.add_votable_fields('typed_id') - >>> result_table = Simbad.query_objects(["M1", "M2", "M3", "M4"]) - >>> print(result_table) - MAIN_ID RA DEC RA_PREC DEC_PREC COO_ERR_MAJA COO_ERR_MINA COO_ERR_ANGLE COO_QUAL COO_WAVELENGTH COO_BIBCODE TYPED_ID - "h:m:s" "d:m:s" mas mas deg - ------- ----------- ----------- ------- -------- ------------ ------------ ------------- -------- -------------- ------------------- -------- - M 1 05 34 31.94 +22 00 52.2 6 6 -- -- 0 C R 2011A&A...533A..10L M1 - M 2 21 33 27.02 -00 49 23.7 6 6 100.000 100.000 90 C O 2010AJ....140.1830G M2 - M 3 13 42 11.62 +28 22 38.2 6 6 200.000 200.000 90 C O 2010AJ....140.1830G M3 - M 4 16 23 35.22 -26 31 32.7 6 6 400.000 400.000 90 C O 2010AJ....140.1830G M4 +These queries can be used to retrieve all the objects that are discussed in the +article specified by a bibcode: -However, note that missing data will result in missing lines: - -.. code-block:: python +.. doctest-remote-data:: >>> from astroquery.simbad import Simbad - >>> result_table = Simbad.query_objects(["M1", "notanobject", "m2", "m1000"]) - >>> print(result_table) - UserWarning: Warning: The script line number 4 raised an error (recorded in the `errors` attribute of the result table): 'notanobject': No known catalog could be found - (error.line, error.msg)) - MAIN_ID RA DEC RA_PREC DEC_PREC COO_ERR_MAJA COO_ERR_MINA COO_ERR_ANGLE COO_QUAL COO_WAVELENGTH COO_BIBCODE - "h:m:s" "d:m:s" mas mas deg - ------- ----------- ----------- ------- -------- ------------ ------------ ------------- -------- -------------- ------------------- - M 1 05 34 31.94 +22 00 52.2 6 6 -- -- 0 C R 2011A&A...533A..10L - M 2 21 33 27.02 -00 49 23.7 6 6 100.000 100.000 90 C O 2010AJ....140.1830G - -Only the results for M1 and M2 are included. As of May 2019, there is a -feature request in place with SIMBAD to return blank rows with the queried -identifier indicated. - -You can also stitch together region queries by writing a sophisticated script: - -.. code-block:: python - - >>> from astroquery.simbad import Simbad - >>> script = '(region(box, GAL, 0 +0, 0.5d 0.5d) | region(box, GAL, 43.3 -0.2, 0.25d 0.25d))' - >>> result = Simbad.query_criteria(script, otype='SNR') - >>> print(result) -
- MAIN_ID RA DEC RA_PREC DEC_PREC COO_ERR_MAJA COO_ERR_MINA COO_ERR_ANGLE COO_QUAL COO_WAVELENGTH COO_BIBCODE - "h:m:s" "d:m:s" mas mas deg - object str13 str13 int16 int16 float32 float32 int16 str1 str1 object - ----------------- ------------ ------------ ------- -------- ------------ ------------ ------------- -------- -------------- ------------------- - SNR G359.9-00.9 17 45.8 -29 03 3 3 -- -- 0 - NAME Sgr A East 17 45 41 -29 00.8 4 4 -- -- 0 D 2010ApJS..188..405A - W 49b 19 11 09.000 +09 06 24.00 7 7 -- -- 0 D 2015ApJS..217....2M - SNR G000.13-00.12 17 46.4 -28 53 3 3 -- -- 0 E 2013MNRAS.434.1339H - + >>> Simbad.query_bibobj('2006AJ....131.1163S') +
+ main_id ra ... bibcode obj_freq + deg ... + object float64 ... object int16 + ----------------------- ------------------ ... ------------------- -------- + NAME Lockman Hole 161.25 ... 2006AJ....131.1163S -- + Cl Melotte 22 56.60099999999999 ... 2006AJ....131.1163S -- + M 32 10.67427 ... 2006AJ....131.1163S -- + M 31 10.684708333333333 ... 2006AJ....131.1163S -- + NAME Galactic Center 266.41500889 ... 2006AJ....131.1163S -- + NAME LMC 80.89416666666666 ... 2006AJ....131.1163S -- + NAME SMC 13.158333333333333 ... 2006AJ....131.1163S -- + 2MASX J04504846-7531580 72.701925 ... 2006AJ....131.1163S -- Customizing the default settings ================================ -There may be times when you wish to change the defaults that have been set for -the Simbad queries. +This section describe how the default output for the SIMBAD queries can be changed. Changing the row limit ---------------------- - -To fetch all the rows in the result, the row limit must be set to 0. However for some -queries, results are likely to be very large, in such cases it may be best to -limit the rows to a smaller number. If you want to do this only for the current -python session then: +To fetch all the rows in the result, the row limit must be set to -1. This is the default +behavior. However if you're only interested in a certain number of objects, or if +the result would be too large, you can change this behavior. +If you want to do this only for the current python session then: .. code-block:: python >>> from astroquery.simbad import Simbad - >>> Simbad.ROW_LIMIT = 15 # now any query fetches at most 15 rows + >>> Simbad.ROW_LIMIT = 15 # now any query except query_tap fetches at most 15 rows If you would like to make your choice persistent, then you can do this by modifying the setting in the Astroquery configuration file. -Changing the timeout --------------------- +.. Note:: + This works with every ``query_***`` method, except + `~astroquery.simbad.SimbadClass.query_tap` as the number of returned rows is fixed + in the ADQL string with the ``TOP`` instruction. -The timeout is the time limit in seconds for establishing connection with the -Simbad server and by default it is set to 100 seconds. You may want to modify -this - again you can do this at run-time if you want to adjust it only for the -current session. To make it persistent, you must modify the setting in the -Astroquery configuration file. +Choosing the columns in the output tables +----------------------------------------- -.. code-block:: python +Some query methods outputs can be customized. This is the case for: - >>> from astroquery.simbad import Simbad - >>> Simbad.TIMEOUT = 60 # sets the timeout to 60s +- `~astroquery.simbad.SimbadClass.query_object` +- `~astroquery.simbad.SimbadClass.query_objects` +- `~astroquery.simbad.SimbadClass.query_region` +- `~astroquery.simbad.SimbadClass.query_catalog` +- `~astroquery.simbad.SimbadClass.query_bibobj` -Specifying which VOTable fields to include in the result --------------------------------------------------------- +For these methods, the default columns in the output are: +- main_id +- ra +- dec +- coo_err_maj +- coo_err_min +- coo_err_angle +- coo_wavelength +- coo_bibcode -The VOTable fields that are currently returned in the result are set to -``main_id`` and ``coordinates``. However you can specify other fields that you -also want to be fetched in the result. To see the list of the fields: +.. Note:: -.. code-block:: python + The columns that will appear in the output can be printed with the + `~astroquery.simbad.SimbadClass.get_votable_fields` method - >>> from astroquery.simbad import Simbad - >>> Simbad.list_votable_fields() + .. code-block:: python - col0 col1 col2 - ------------------------ -------------------- -------------- - bibcodelist(y1-y2) fluxdata(filtername) plx_qual - cel gcrv pm - cl.g gen pm_bibcode - coo(opt) gj pm_err_angle - coo_bibcode hbet pm_err_maja - coo_err_angle hbet1 pm_err_mina - coo_err_maja hgam pm_qual + >>> from astroquery.simbad import Simbad + >>> simbad = Simbad() + >>> simbad.get_votable_fields() + ['basic.main_id', 'basic.ra', 'basic.dec', 'basic.coo_err_maj', 'basic.coo_err_min', 'basic.coo_err_angle', 'basic.coo_wavelength', 'basic.coo_bibcode'] + Here we see the lists of columns that are selected per default. They are all from + the table of basic information (``basic``). -The above shows just a small snippet of the table that is returned and has all -the fields sorted lexicographically column-wise. For more information on a -particular field: +This can be permanently changed in astroquery's configuration files. To do this within +a session or for a single query, use `~astroquery.simbad.SimbadClass.add_votable_fields`: -.. code-block:: python +.. doctest-remote-data:: >>> from astroquery.simbad import Simbad - >>> Simbad.get_field_description('ra_prec') - - right ascension precision code (0:1/10deg, ..., 8: 1/1000 arcsec) - -To set additional fields to be returned in the VOTable: - -.. code-block:: python - - >>> from astroquery.simbad import Simbad - >>> customSimbad = Simbad() - - # see which fields are currently set - - >>> customSimbad.get_votable_fields() - - ['main_id', 'coordinates'] + >>> simbad = Simbad() + >>> simbad.add_votable_fields("otype") # here we add a single column about the main object type - # To set other fields +Some options add a single column and others add a bunch of columns that are relevant +for a theme (ex: fluxes, proper motions...). The list of possible options is printed +with: - >>> customSimbad.add_votable_fields('mk', 'rot', 'bibcodelist(1800-2014)') - >>> customSimbad.get_votable_fields() +.. doctest-remote-data:: - ['main_id', 'coordinates', 'mk', 'rot', 'bibcodelist(1800-2014')] - -You can also remove a field you have set or -:meth:`astroquery.simbad.SimbadClass.reset_votable_fields`. Continuing from -the above example: - -.. code-block:: python - - >>> customSimbad.remove_votable_fields('mk', 'coordinates') - >>> customSimbad.get_votable_fields() - - ['main_id', 'rot', 'bibcodelist(1800-2014)'] + >>> from astroquery.simbad import Simbad + >>> Simbad.list_votable_fields()[["name", "description"]] +
+ name description + object object + --------------- ------------------------------------------------------- + mesDiameter Collection of stellar diameters. + mesPM Collection of proper motions. + mesISO Infrared Space Observatory (ISO) observing log. + mesSpT Collection of spectral types. + allfluxes all flux/magnitudes U,B,V,I,J,H,K,u_,g_,r_,i_,z_ + ident Identifiers of an astronomical object + flux Magnitude/Flux information about an astronomical object + mesPLX Collection of trigonometric parallaxes. + otypedef all names and definitions for the object types + ... ... + vlsr_min Minimum for the mean value of the LSR velocity + vlsr_wavelength Wavelength class for the origin of the LSR velocity + coordinates all fields related with coordinates + dim major and minor axis, angle and inclination + dimensions all fields related to object dimensions + morphtype all fields related to the morphological type + parallax all fields related to parallaxes + propermotions all fields related with the proper motions + sp all fields related with the spectral type + velocity all fields related with radial velocity and redshift + +You can also access a single field description with +`~astroquery.simbad.SimbadClass.get_field_description` + +.. doctest-remote-data:: - # reset back to defaults + >>> from astroquery.simbad import Simbad + >>> Simbad.get_field_description("rvz_type") + 'Radial velocity / redshift type' - >>> customSimbad.reset_votable_fields() - >>> customSimbad.get_votable_fields() +And the columns in the output can be reset to their default value with +`~astroquery.simbad.SimbadClass.reset_votable_fields`. - ['main_id', 'coordinates'] +Additional criteria +------------------- +.. Warning:: -Returning the queried name in the return table -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Before astroquery v0.4.8, criteria could only be used with the method ``query_criteria``. + This method is now deprecated and is replaced by the criteria argument in every + other methods. See :ref:`SIMBAD evolutions `. -You can include the name(s) queried in the output table by adding ``typed_id`` to -the votable fields. This was also mentioned in :ref:`vectorized queries -` above, but we emphasize here that it works for all queries. +Most query methods take a ``criteria`` argument. They are listed here: - >>> Simbad.add_votable_fields('typed_id') - >>> Simbad.query_objects(['M31', 'Eta Carinae', 'Alpha Centauri']) -
- MAIN_ID RA DEC RA_PREC DEC_PREC COO_ERR_MAJA COO_ERR_MINA COO_ERR_ANGLE COO_QUAL COO_WAVELENGTH COO_BIBCODE TYPED_ID - "h:m:s" "d:m:s" mas mas deg - object str13 str13 int16 int16 float32 float32 int16 str1 str1 object object - --------- ------------- ------------- ------- -------- ------------ ------------ ------------- -------- -------------- ------------------- -------------- - M 31 00 42 44.330 +41 16 07.50 7 7 -- -- 0 C I 2006AJ....131.1163S M31 - + eta Car 10 45 03.5455 -59 41 03.951 11 11 11.000 10.000 90 B O 2000A&A...355L..27H Eta Carinae - + alf Cen 14 39 29.7199 -60 49 55.999 9 9 -- -- 0 C O 2016A&A...589A.115S Alpha Centauri +- `~astroquery.simbad.SimbadClass.query_object` +- `~astroquery.simbad.SimbadClass.query_objects` +- `~astroquery.simbad.SimbadClass.query_region` +- `~astroquery.simbad.SimbadClass.query_catalog` +- `~astroquery.simbad.SimbadClass.query_bibobj` +- `~astroquery.simbad.SimbadClass.query_bibcode` +- `~astroquery.simbad.SimbadClass.query_objectids` - >>> Simbad.query_object('M31') -
- MAIN_ID RA DEC RA_PREC DEC_PREC COO_ERR_MAJA COO_ERR_MINA COO_ERR_ANGLE COO_QUAL COO_WAVELENGTH COO_BIBCODE TYPED_ID - "h:m:s" "d:m:s" mas mas deg - object str13 str13 int16 int16 float32 float32 int16 str1 str1 object object - ------- ------------ ------------ ------- -------- ------------ ------------ ------------- -------- -------------- ------------------- -------- - M 31 00 42 44.330 +41 16 07.50 7 7 -- -- 0 C I 2006AJ....131.1163S M31 +The criteria argument expect a string written in the syntax of the ``WHERE`` clause of +an ADQL query. Some examples can be found in the +`Simbad ADQL cheat sheet `__. -Specifying the format of the included VOTable fields ----------------------------------------------------- +To help writing criteria, a good tip is to inspect the columns that the query would +return by querying a blank table (of zero rows). +This allows to inspect the columns the method would return: -The output for several of the VOTable fields can be formatted in many -different ways described in the help page of the SIMBAD query interface (see -Sect. 4.3 of `this page -`__). As an -example, the epoch and equinox for the Right Ascension and Declination can -be specified as follows (e.g. epoch of J2017.5 and equinox of 2000): +.. doctest-remote-data:: -.. code-block:: python + >>> from astroquery.simbad import Simbad + >>> simbad = Simbad() + >>> simbad.ROW_LIMIT = 0 # get no lines, just the table structure + >>> # add the table about proper motion measurements, and the object type column + >>> simbad.add_votable_fields("mesPM", "otype") + >>> peek = simbad.query_object("BD+30 2512") # a query on an object + >>> peek.info +
+ name dtype unit description + ------------------- ------- -------- ---------------------------------------------------------------------- + main_id object Main identifier for an object + ra float64 deg Right ascension + dec float64 deg Declination + coo_err_maj float32 mas Coordinate error major axis + coo_err_min float32 mas Coordinate error minor axis + coo_err_angle int16 deg Coordinate error angle + coo_wavelength str1 Wavelength class for the origin of the coordinates (R,I,V,U,X,G) + coo_bibcode object Coordinate reference + otype object Object type + mespm.bibcode object measurement bibcode + mespm.coosystem object coordinates system designation + mespm.mespos int16 Position of a measurement in a list of measurements + mespm.pmde float32 mas / yr Proper motion DEC. + mespm.pmde_err float32 mas / yr sigma{pm-de} + mespm.pmde_err_prec int16 Precision (# of decimal positions) associated with the column pmde_err + mespm.pmde_prec int16 Precision (# of decimal positions) associated with the column pmde + mespm.pmra float32 mas / yr Proper motion R.A. + mespm.pmra_err float32 mas / yr sigma{pm-ra} + mespm.pmra_err_prec int16 Precision (# of decimal positions) associated with the column pmra_err + mespm.pmra_prec int16 Precision (# of decimal positions) associated with the column pmra + matched_id object Identifier + +Now that we know which columns will be returned by the query, we can edit the number of +returned rows and add a criteria. + +For example, to get only proper motion measurements more recent than 2000, we can add a +constraint on the first character of the ``mespm.bibcode`` column +(the first 4 digits of a bibcode are the year of publication of the article): + +.. doctest-remote-data:: - >>> customSimbad.add_votable_fields('ra(2;A;ICRS;J2017.5;2000)', 'dec(2;D;ICRS;J2017.5;2000)') - >>> customSimbad.remove_votable_fields('coordinates') - >>> customSimbad.query_object("HD189733") -
- MAIN_ID RA_2_A_ICRS_J2017_5_2000 DEC_2_D_ICRS_2017_5_2000 - "h:m:s" "d:m:s" - object str13 str13 - --------- ------------------------ ------------------------ - HD 189733 20 00 43.7107 +22 42 39.064 + >>> from astroquery.simbad import Simbad + >>> criteria = "mespm.bibcode LIKE '2%'" # starts with 2, anything after + >>> simbad = Simbad() + >>> simbad.add_votable_fields("mesPM", "otype") + >>> pm_measurements = simbad.query_object("BD+30 2512", criteria=criteria) + >>> pm_measurements[["main_id", "mespm.pmra", "mespm.pmde", "mespm.bibcode"]] +
+ main_id mespm.pmra mespm.pmde mespm.bibcode + mas / yr mas / yr + object float32 float32 object + ----------- ---------- ---------- ------------------- + BD+30 2512 -631.662 -308.469 2020yCat.1350....0G + BD+30 2512 -631.6 -289.5 2016ApJS..224...36K + BD+30 2512 -631.625 -308.495 2018yCat.1345....0G + BD+30 2512 -631.36 -306.88 2007A&A...474..653V + BD+30 2512 -631.0 -307.0 2005AJ....129.1483L + BD+30 2512 -630.0 -306.0 2002ApJS..141..187B + + +.. _query-tap: +Query TAP +========= +.. include:: query_tap.rst Troubleshooting =============== -If you are repeatedly getting failed queries, or bad/out-of-date results, try clearing your cache: +If you are repeatedly getting failed queries, or bad/out-of-date results, try clearing +your cache: .. code-block:: python @@ -739,11 +613,15 @@ If you are repeatedly getting failed queries, or bad/out-of-date results, try cl If this function is unavailable, upgrade your version of astroquery. The ``clear_cache`` function was introduced in version 0.4.7.dev8479. +Citation +======== + +If SIMBAD was useful for your research, you can +`read its acknowledgement page `__. + Reference/API ============= .. automodapi:: astroquery.simbad :no-inheritance-diagram: - -.. _criteria interface: https://simbad.cds.unistra.fr/simbad/sim-fsam diff --git a/docs/simbad/simbad_evolution.rst b/docs/simbad/simbad_evolution.rst new file mode 100644 index 0000000000..6d4b6de6bd --- /dev/null +++ b/docs/simbad/simbad_evolution.rst @@ -0,0 +1,262 @@ +.. _simbad-evolutions: + +######################## +Simbad module evolutions +######################## + +**************************************** +Translating query_criteria into criteria +**************************************** + +The method ``query_criteria`` is now deprecated in the SIMBAD module. It is replaced by +a ``criteria`` argument in the following methods: + +- `~astroquery.simbad.SimbadClass.query_object` +- `~astroquery.simbad.SimbadClass.query_objects` +- `~astroquery.simbad.SimbadClass.query_region` +- `~astroquery.simbad.SimbadClass.query_catalog` +- `~astroquery.simbad.SimbadClass.query_bibobj` +- `~astroquery.simbad.SimbadClass.query_bibcode` +- `~astroquery.simbad.SimbadClass.query_objectids` + +This new argument expects a criteria formatted as a ``WHERE`` clause in an ADQL string, +it consists of: + +- logical operators ``AND``, ``OR``, ``NOT``, +- comparison operators ``=``, ``!=`` (or its alternative notation ``<>``), ``<``, + ``>``, ``<=``, ``>=``, +- range comparison ``BETWEEN`` (ex: range of spectral types ``sp_type BETWEEN 'F3' AND 'F5'``), +- membership check ``IN`` (ex: either active galaxy nucleus, or active galaxy + nucleus candidate ``otype IN ('AGN', 'AG?')``), +- case-sensitive string comparison: ``LIKE``, +- null value check ``IS NULL``, ``IS NOT NULL``. + +This list is extracted from the section 2.2.3 of the +`ADQL specification `__. + +Here, for example, we add a criteria on the desired type of objects to a region query +in 2° around M11: + +.. doctest-remote-data:: + + >>> from astroquery.simbad import Simbad + >>> Simbad(ROW_LIMIT=20).query_region("M11", "2d", + ... criteria="otype = 'Star..'")[["main_id", "ra", "dec"]] # doctest: +IGNORE_OUTPUT +
+ main_id ra dec + deg deg + object float64 float64 + ---------------------------- ------------------ ------------------ + ATO J283.9680-04.7022 283.9680584302754 -4.702210643396667 + 2MASS J18553904-0441576 283.912704 -4.699355 + 2MASS J18434437-0532364 280.934909910062 -5.543481872277222 + IRAS 18410-0535 280.93514361343995 -5.534989946319999 + Gaia DR3 4255168174887091584 281.9199370352171 -4.607529439915277 + ... ... ... + Gaia DR3 4255168415418002432 281.8446179288175 -4.647683641737222 + OGLE GD-RRLYR-8997 281.93337499999996 -4.824888888888889 + 2MASS J18471362-0444391 281.80677257235084 -4.744217391975 + IRAS 18449-0454 281.89339967202 -4.85617020367 + * bet Sct 281.79364256535 -4.74787304458 + Gaia DR2 4255222566334653952 282.4900966404579 -4.434419082480556 + +We only retrieve objects which main type (``otype``) is a star (``Star``) +or its descendants (``..``) -- see section on `Object types`_ --. + +If you already have a string that was a valid criteria in ``query_criteria``, +there is a helper method to translate a criteria from ``query_criteria`` into a string +that will work as ``criteria`` in the other query methods cited above: + +.. code-block:: python + + >>> from astroquery.simbad.utils import CriteriaTranslator + >>> CriteriaTranslator.parse("region(box, ICRS, 0 +0, 3d 1d) & otype='SNR'") + "CONTAINS(POINT('ICRS', ra, dec), BOX('ICRS', 0.0, 0.0, 3.0, 1.0)) = 1 AND otypes.otype = 'SNR'" + +This string can now be incorporated in any of the query methods that accept a ``criteria`` argument. + +See a more elaborated example: + +.. this test will fail when upstream issue https://github.com/gmantele/vollt/issues/154 is solved +.. then we'll have to replace "otypes" by "alltypes.otypes" + +.. doctest-remote-data:: + + >>> from astroquery.simbad import Simbad + >>> from astroquery.simbad.utils import CriteriaTranslator + >>> # not a galaxy, and not a globular cluster + >>> old_criteria = "maintype != 'Galaxy..' & maintype != 'Cl*..'" + >>> simbad = Simbad() + >>> # we add the main type and all the types that have historically been attributed to the object + >>> simbad.add_votable_fields("otype", "alltypes") + >>> result = simbad.query_catalog("M", criteria=CriteriaTranslator.parse(old_criteria)) + >>> result.sort("catalog_id") + >>> result[["main_id", "catalog_id", "otype", "otypes"]] +
+ main_id catalog_id otype otypes + object object object object + --------- ---------- ------ ---------------------------------------- + M 1 M 1 SNR HII|IR|Psr|Rad|SNR|X|gam + M 24 M 24 As* As*|Cl*|GNe + M 27 M 27 PN *|G|HS?|IR|PN|UV|WD*|WD?|X|blu + M 40 M 40 ? ? + M 42 M 42 HII C?*|Cl*|HII|OpC|Rad|X + M 43 M 43 HII HII|IR|Rad + M 57 M 57 PN *|HS?|IR|PN|Rad|WD*|WD?|blu + NGC 6994 M 73 err Cl*|err + M 76 M 76 PN *|IR|PN|Rad|WD* + M 78 M 78 RNe C?*|Cl*|ISM|RNe + M 97 M 97 PN *|HS?|IR|NIR|Opt|PN|Rad|UV|WD*|WD?|X|blu + +And we indeed get objects from the Messier catalog (as `~astroquery.simbad.SimbadClass.query_catalog` is +meant to return), but with the additional criteria that these objects should be neither galaxies +nor clusters of stars. + +************ +Object types +************ + +The example above highlights the subtlety of assigning a type for every object. The SIMBAD database +evolves with the literature and the ``otype`` value reflects the most precise type that was +identified through a literature review at the moment at which the query is done. +All the former ``otype`` assignations are also stored in the ``otypes`` column. These can be either less +precise or false. See in the previous example M27 that is now classified as ``PN`` (Planetary Nebula) +and was in the past thought to be a ``G`` (Galaxy). + +The definitions of object types can be found either in SIMBAD's +`documentation on object types `_ +or with TAP queries. For example, to see the definition of ``PN``, one can do: + +.. doctest-remote-data:: + + >>> from astroquery.simbad import Simbad + >>> result = Simbad.query_tap("SELECT * FROM otypedef WHERE otype = 'PN'") + >>> result[["otype", "label", "description", "is_candidate", "path"]] +
+ otype label description is_candidate path + object object object int16 object + ------ ------------ ---------------- ------------ ------------ + PN PlanetaryNeb Planetary Nebula 0 * > Ev* > PN + +Where ``otypedef`` is the table of SIMBAD containing the definitions of object types. + +The ``label`` can also be used in a query if you want your code to be easier to read. + +.. doctest-remote-data:: + + >>> from astroquery.simbad import Simbad + >>> Simbad.query_tap("SELECT top 5 main_id, otype FROM basic WHERE otype = 'PlanetaryNeb'") # doctest: +IGNORE_OUTPUT +
+ main_id otype + object object + ---------- ------ + IC 4634 PN + PN H 2-40 PN + PN PC 12 PN + NGC 6543 PN + NGC 7027 PN + +The ``path`` column in ``otypedef`` is a representation of the hierarchy of objects. +Here ``PN`` (Planetary Nebula) derives from ``Ev*`` (Evolved Star) which itself derives +from ``*`` (Star). This is the classification of objects in place in SIMBAD since 2020. +If you don't find an object type you used to look for in SIMBAD, you might be interested +in this `table of correspondence `_ +between old and new labels for object types. + +An interesting feature brought by the hierarchy of objects is the ``..`` notation. For example, +``Ev*..`` means any object type that derives from evolved star. + +.. doctest-remote-data:: + + >>> from astroquery.simbad import Simbad + >>> Simbad.query_tap("SELECT top 5 main_id, otype " + ... "FROM basic WHERE otype = 'Ev*..'") # doctest: +IGNORE_OUTPUT +
+ main_id otype + object object + ---------------------- ------ + IRAS 07506-0345 pA* + D33 J013331.3+302946.9 cC* + D33 J013253.5+303810.2 Ce* + [SC83] G4 Ce* + SSTGC 444055 LP* + +This return objects which types are indeed among the 17 types deriving from ``Ev*`` +(Evolved Star). For example, ``pA*`` is a post-AGB Star. + +******* +Filters +******* + +.. Note:: + + This section explains the deprecated ``ubv``, ``flux(u)``, and ``fluxdata(u)`` notations. + +Historically, there were only three filters in SIMBAD, ``U``, ``B``, and ``V``. This is why +one could add these columns to SIMBAD's output with ``ubv``. This is not +the case anymore, and a suggested workflow now looks like this: + +1. Get the list of filters currently in Simbad +============================================== + +.. doctest-remote-data:: + + >>> from astroquery.simbad import Simbad + >>> Simbad.query_tap("SELECT * FROM filter") +
+ description filtername unit + object object object + ----------------- ---------- ------ + Magnitude U U mag + Magnitude B B mag + Magnitude V V mag + Magnitude R R mag + Magnitude I I mag + Magnitude J J mag + Magnitude H H mag + Magnitude K K mag + Magnitude SDSS u u mag + Magnitude SDSS g g mag + Magnitude SDSS r r mag + Magnitude SDSS i i mag + Magnitude SDSS z z mag + Magnitude Gaia G G mag + JWST NIRCam F150W F150W mag + JWST NIRCam F200W F200W mag + JWST NIRCan F444W F444W mag + +There are currently 17 filters, but more are added as new data is ingested. +The important information is in the column ``filtername``. + +2. Apply a criteria in your query +================================= + +You can now use this filter name in a criteria string. For example, to get +fluxes for a specific object, one can use `~astroquery.simbad.SimbadClass.query_object` +as a first base (it selects a single object by its name), add different fields to +the output with `~astroquery.simbad.SimbadClass.add_votable_fields` (here ``flux`` adds all +columns about fluxes) and then select only the interesting filters with a ``criteria`` +argument: + +.. this will fail when upstream bug https://github.com/gmantele/vollt/issues/154 is fixed. +.. "filter" should be replaced by "flux.filter" and "bibcode" by "flux.bibcode". + +.. doctest-remote-data:: + + >>> from astroquery.simbad import Simbad + >>> simbad = Simbad() + >>> simbad.add_votable_fields("flux") + >>> result = simbad.query_object("BD-16 5701", criteria="filter IN ('U', 'B', 'G')") + >>> result[["main_id", "flux", "flux_err", "filter", "bibcode"]] +
+ main_id flux flux_err filter bibcode + object float32 float32 object object + ----------- --------- -------- ------ ------------------- + BD-16 5701 11.15 0.07 B 2000A&A...355L..27H + BD-16 5701 10.322191 0.002762 G 2020yCat.1350....0G + +Here, we looked for flux measurements for ``BD-16 5701`` with three filters. There was no +match for ``U``, but the information is there for ``B`` and ``G``. The ``bibcode`` +column is the source of the flux information. + +.. replace ``bibcode`` by ``flux.bibcode`` here when https://github.com/gmantele/vollt/issues/154 is fixed. \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index ac99c4eb69..6c31bafb0b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -111,7 +111,7 @@ exclude = _astropy_init.py,version.py [flake8] max-line-length = 120 ignore = E226,E402,W503 -exclude = _astropy_init.py,version.py,astroquery/template_module +exclude = _astropy_init.py,version.py,astroquery/template_module,astroquery/simbad/criteria_*.py [coverage:run] omit =