From 6de7ce5de09b41bca88205326566abf358d007d2 Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Tue, 20 Feb 2024 15:00:59 +0100 Subject: [PATCH 01/28] refactor: simbad query methods now call query_tap internally - add construct_query method that reads the columns_in_output, join, and criteria attirbutes - support the removed query_criteria method functionnalities by adding a criteria attribute that should be a valid adql clause. The utils CriteriaTranslator can translate between the old and new syntax. - make ROW_LIMIT = -1 to return all lines because TOP 0 or maxrec = 0 are the dedicated way to retrieve table metadata in TAP - fix usage of lru_cache on class methods that can cause memory leaks (see bugbear rule B019) --- astroquery/simbad/core.py | 1671 +++++++---------- astroquery/simbad/criteria_lextab.py | 21 + astroquery/simbad/criteria_parsetab.py | 50 + .../simbad/data/query_criteria_fields.json | 286 +++ .../tests/data/simbad_output_options.xml | 509 +++++ astroquery/simbad/tests/test_simbad.py | 606 +++--- astroquery/simbad/tests/test_simbad_remote.py | 346 ++-- astroquery/simbad/tests/test_utils.py | 78 + astroquery/simbad/utils.py | 324 ++++ 9 files changed, 2307 insertions(+), 1584 deletions(-) create mode 100644 astroquery/simbad/criteria_lextab.py create mode 100644 astroquery/simbad/criteria_parsetab.py create mode 100644 astroquery/simbad/data/query_criteria_fields.json create mode 100644 astroquery/simbad/tests/data/simbad_output_options.xml create mode 100644 astroquery/simbad/tests/test_utils.py create mode 100644 astroquery/simbad/utils.py diff --git a/astroquery/simbad/core.py b/astroquery/simbad/core.py index d8385b55b8..2afbbf9547 100644 --- a/astroquery/simbad/core.py +++ b/astroquery/simbad/core.py @@ -1,96 +1,36 @@ # 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 json +from typing import Any +from pathlib import Path import warnings + +import astropy.coordinates as coord +from astropy.table import Table, Column, vstack 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 astroquery.query import BaseQuery, BaseVOQuery +from astroquery.query import BaseVOQuery from astroquery.utils import commons, async_to_sync -from astroquery.exceptions import TableParseError, LargeQueryWarning, BlankResponseWarning +from astroquery.exceptions import LargeQueryWarning +from astroquery.simbad.utils import (_catch_deprecated_fields_with_arguments, + _wildcard_to_regexp) from pyvo.dal import TAPService from . import conf -__all__ = ['Simbad', 'SimbadClass', 'SimbadBaseQuery'] - +__all__ = ['Simbad', 'SimbadClass'] -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 +with open(get_pkg_data_filename(str(Path("data") / "query_criteria_fields.json"))) as f: + query_criteria_fields = json.load(f) def _adql_parameter(entry: str): @@ -111,227 +51,78 @@ 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]*)') - - -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}"]) +@lru_cache(256) +def _cached_query_tap(tap, query: str, *, maxrec=10000): + """Cache version of query TAP. + 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. -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}"]) - + 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 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. + Returns + ------- + `~astropy.table.Table` + The response returned by SIMBAD. """ - - 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 + return tap.search(query, maxrec=maxrec).to_table() @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'] + @dataclass + class Column: + """A class to define a column in a SIMBAD query.""" + table: str + name: str + alias: str = field(default=None) + + @dataclass + 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") def __init__(self): 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 Simbad.Column + self.joins = [] # a list of Simbad.Join + self.criteria = [] # a list of strings @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 +132,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,291 +146,332 @@ 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 - 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 - - def list_wildcards(self): - """ - Displays the available wildcards that may be used in Simbad queries and - their usage. + @property + def columns_in_output(self): + """A list of Simbad.Column.""" + if self._columns_in_output is None: + self._columns_in_output = [Simbad.Column("basic", item) + for item in conf.default_columns] + return self._columns_in_output - Examples - -------- - >>> from astroquery.simbad import Simbad - >>> Simbad.list_wildcards() - * : Any string of characters (including an empty one)... + @columns_in_output.setter + def columns_in_output(self, list_columns): + self._columns_in_output = list_columns - [^0-9] : Any (one) character not in the list. + # --------------------------------- + # Methods to define SIMBAD's output + # --------------------------------- - ? : Any character (exactly one character) + def list_output_options(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_output_options() # doctest: +REMOTE_DATA + >>> # to print only the available bundles of columns + >>> options[options["type"] == "bundle of basic columns"][["name", "description"]] # doctest: +REMOTE_DATA + + name ... + object ... + ------------- ... + coordinates ... + dim ... + dimensions ... + morphtype ... + parallax ... + propermotions ... + sp ... + velocity ... """ - # 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 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 + WHERE TAP_SCHEMA.tables.schema_name = 'public' + AND 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_output_options` - 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 - """ - # 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) - - try: - print(fields_dict[field_name]) - except KeyError: - raise KeyError("No such field_name") - - def get_votable_fields(self): + Returns + ------- + list[Simbad.Column] + The list of columns corresponding to the selected bundle. """ - Display votable fields + basic_columns = set(map(str.casefold, set(self.list_columns("basic")["column_name"]))) - Examples - -------- - >>> from astroquery.simbad import Simbad - >>> Simbad.get_votable_fields() - ['main_id', 'coordinates'] - """ - return self._VOTABLE_FIELDS + bundle_entries = {key: value for key, value in query_criteria_fields.items() + if value["type"] == "bundle"} - def add_votable_fields(self, *args): - """ - Sets fields to be fetched in the VOTable. Must be one of those listed - by `list_votable_fields`. + if bundle_name in bundle_entries: + bundle = bundle_entries[bundle_name] + columns = [Simbad.Column("basic", column) for column in basic_columns + if column.startswith(bundle["tap_startswith"])] + if "tap_column" in bundle: + columns = [Simbad.Column("basic", column) for column in bundle["tap_column"]] + columns + return columns + + def _add_table_to_output(self, table): + """Add all fields of a 'table' to the output of queries. + + 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(Simbad.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] - def remove_votable_fields(self, *args, strip_params=False): - """ - Removes the specified field names from ``SimbadClass._VOTABLE_FIELDS`` + 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'.") - 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 - """ - 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 + columns = list(self.list_columns(table)["column_name"]) + columns = [column.casefold() for column in columns if column not in {"oidref", "oidbibref"}] - for field in sargs.difference(sfields): - warnings.warn("{field}: this field is not set".format(field=field)) + # 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] - zipped_fields = zip(sfields, self._VOTABLE_FIELDS) - self._VOTABLE_FIELDS = [f for b, f in zipped_fields if b not in sargs] + # modify the attributes here + self.columns_in_output += [Simbad.Column(table, column, alias) + for column, alias in zip(columns, alias)] + self.joins += [Simbad.Join(table, Simbad.Column("basic", link["target_column"]), + Simbad.Column(table, link["from_column"]))] - # 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 add_to_output(self, *args): + """Add columns to the output of a SIMBAD query. - def reset_votable_fields(self): - """ - resets VOTABLE_FIELDS to defaults - """ - self._VOTABLE_FIELDS = ['main_id', 'coordinates'] + The list of possible arguments and their description for this method + can be printed with `~astroquery.simbad.SimbadClass.list_output_options`. - def query_criteria(self, *args, **kwargs): - """ - Query SIMBAD based on any criteria. + The methods affected by this property are: - 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) - - 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) + - `~astroquery.simbad.SimbadClass.query_object` + - `~astroquery.simbad.SimbadClass.query_objects` + - `~astroquery.simbad.SimbadClass.query_region` + - `~astroquery.simbad.SimbadClass.query_bibobj` - def query_criteria_async(self, *args, cache=True, **kwargs): - """ - Query SIMBAD based on any criteria. 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 `. + args : str + The arguments to be added. - Returns - ------- - response : `requests.Response` - Response of the query from the server + Examples + -------- + >>> from astroquery.simbad import Simbad + >>> simbad = Simbad() + >>> simbad.add_to_output('sp_type', 'sp_qual', 'sp_bibcode') # doctest: +REMOTE_DATA + >>> simbad.columns_in_output[0] # doctest: +REMOTE_DATA + SimbadClass.Column(table='basic', name='main_id', alias=None) """ + # casefold args + args = set(map(str.casefold, args)) + + # output options + output_options = self.list_output_options() + 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 += [Simbad.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 - 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 - - 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. + # 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(Simbad.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: + # it could be also be one of the 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_output_options'.") + if close_match != []: + close_matches = "' or '".join(close_match) + error_message += f" Did you mean '{close_matches}'?" + raise ValueError(error_message) + + # ------------- + # Query methods + # ------------- + + def query_object(self, object_name, *, wildcard=False, + criteria=None, get_adql=False, + verbose=False, get_query_payload=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`. - get_query_payload : bool, optional - When set to `True` the method returns the HTTP request parameters. + 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_adql : bool, defaults to False + Returns the ADQL string instead of querying SIMBAD. + verbose : deprecated since 0.4.7 + get_query_payload : deprecated since 0.4.7 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_to_output("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(Simbad.Column("ident", "id", "matched_id")) - response = self._request("POST", self.SIMBAD_URL, data=request_payload, - timeout=self.TIMEOUT, cache=cache) - return response + joins.append(Simbad.Join("ident", Simbad.Column("basic", "oid"), Simbad.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)}'") + + if criteria: + instance_criteria.append(f"({criteria})") + + return self._construct_query(top, columns, joins, instance_criteria, get_adql) + + def query_objects(self, object_names, *, wildcard=False, criteria=None, + get_adql=False, verbose=False, get_query_payload=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). + The column ``typed_id`` is the input object name. Parameters ---------- @@ -648,199 +480,225 @@ def query_objects(self, object_names, *, wildcard=False, verbose=False, 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`. + criteria : str + Criteria to be applied to the query. These should be written in the ADQL + syntax in a single string. See example. + get_adql : bool, defaults to False + Returns the ADQL string instead of querying SIMBAD. + verbose : deprecated since 0.4.7 + get_query_payload : deprecated since 0.4.7 Returns ------- table : `~astropy.table.Table` - Query results table - """ - return self.query_object('\n'.join(object_names), wildcard=wildcard, - get_query_payload=get_query_payload) + The result of the query to SIMBAD. - def query_objects_async(self, object_names, *, wildcard=False, 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", "typed_id"]] # doctest: +REMOTE_DATA +
+ main_id ra dec typed_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 """ - Same as `query_objects`, but only collects the response from the - Simbad server and returns. + top, columns, joins, instance_criteria = self._get_query_parameters() - 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`. - cache : bool - Defaults to True. If set overrides global caching behavior. - See :ref:`caching documentation `. + upload = Table({"typed_id": object_names, + "object_number_id": list(range(1, len(object_names) + 1))}) - 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) + upload_name = "TAP_UPLOAD.script_infos" - def query_region_async(self, coordinates, radius=2*u.arcmin, *, - equinox=2000.0, epoch='J2000', cache=True, - get_query_payload=False): - """ - Serves the same function as `query_region`, but - only collects the response from the Simbad server and returns. + columns.append(Simbad.Column(upload_name, "*")) + + joins += [Simbad.Join("ident", Simbad.Column("basic", "oid"), Simbad.Column("ident", "oidref")), + Simbad.Join(upload_name, Simbad.Column("ident", "id"), + Simbad.Column(upload_name, "typed_id"), "RIGHT JOIN")] + + if wildcard: + list_criteria = [f"regexp(id, '{_wildcard_to_regexp(object_name)}') = 1" for object_name in object_names] + instance_criteria += [f'(({" OR ".join(list_criteria)}) OR typed_id IS NOT NULL)'] + else: + instance_criteria.append(f"(id IN ({str(object_names)[1:-1]}) OR typed_id IS NOT NULL)") + + if criteria: + instance_criteria.append(f"({criteria})") + + return self._construct_query(top, columns, joins, instance_criteria, get_adql, script_infos=upload) + + def query_region(self, coordinates, radius=2*u.arcmin, *, + criteria=None, get_adql=False, + equinox=None, epoch=None, cache=None, + get_query_payload=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. - 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 `. + criteria : str + Criteria to be applied to the query. These should be written in the ADQL + syntax in a single string. + get_adql : bool, defaults to False + Returns the ADQL string instead of querying SIMBAD. + equinox : deprecated since 0.4.7 + Use `~astropy.coordinates` objects instead + epoch : deprecated since 0.4.7 + Use `~astropy.coordinates` objects instead + get_query_payload : deprecated since 0.4.7 + cache : deprecated since 0.4.7 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_to_output("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) - - 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)) + radius = [coord.Angle(radius)] * len(center) - else: - radius = _parse_radius(radius) - query_str = base_query_str.format(ra=ra, dec=dec, frame=frame, - rad=radius, equinox=equinox) + 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)] - request_payload = {'script': "\n".join([header, query_str, footer])} + cone_criteria = f" ({'OR '.join(cone_criteria)})" + instance_criteria.append(cone_criteria) - if get_query_payload: - return request_payload + if criteria: + instance_criteria.append(f"({criteria})") - 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. + return self._construct_query(top, columns, joins, instance_criteria, get_adql) - Results may be very large -number of rows - should be controlled by configuring `SimbadClass.ROW_LIMIT`. + def query_catalog(self, catalog, *, criteria=None, get_adql=False, + verbose=False, cache=True, get_query_payload=False): + """Query a whole catalog. 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 `. + 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_adql : bool, defaults to False + Returns the ADQL string instead of querying SIMBAD. + verbose : deprecated since 0.4.7 + get_query_payload : deprecated since 0.4.7 + cache : deprecated since 0.4.7 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(Simbad.Column("ident", "id", "catalog_id")) - Returns - ------- - response : `requests.Response` - Response of the query from the server. + joins += [Simbad.Join("ident", Simbad.Column("basic", "oid"), Simbad.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._construct_query(top, columns, joins, instance_criteria, get_adql) - 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`. + def query_bibobj(self, bibcode, *, criteria=None, + get_adql=False, + verbose=False, get_query_payload=False): + """Query all the objects mentioned in an article. Parameters ---------- @@ -849,195 +707,170 @@ def query_bibobj(self, bibcode, *, verbose=False, get_query_payload=False): get_query_payload : bool, optional When set to `True` the method returns the HTTP request parameters. Defaults to `False`. + verbose : deprecated since 0.4.7 + get_query_payload : deprecated since 0.4.7 Returns ------- 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 += [Simbad.Join("has_ref", Simbad.Column("basic", "oid"), + Simbad.Column("has_ref", "oidref")), + Simbad.Join("ref", Simbad.Column("has_ref", "oidbibref"), + Simbad.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 += [Simbad.Column("ref", "bibcode"), + Simbad.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. - - """ - request_payload = self._args_to_payload(bibcode, caller='query_bibobj_async') - - if get_query_payload: - return request_payload + return self._construct_query(top, columns, joins, instance_criteria, get_adql) - response = self._request("POST", self.SIMBAD_URL, data=request_payload, - timeout=self.TIMEOUT, cache=cache) - return response + def query_bibcode(self, bibcode, *, wildcard=False, + abstract=False, get_adql=False, criteria=None, + verbose=None, + cache=None, get_query_payload=None): + """Query the references corresponding to a given bibcode. - 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`. - 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 `. + 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. + verbose : deprecated since 0.4.7 + get_query_payload : deprecated since 0.4.7 + cache : deprecated since 0.4.7 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) + if get_adql: + return query + return self.query_tap(query) - return response + def query_objectids(self, object_name, *, verbose=None, cache=None, + get_query_payload=None, criteria=None, + get_adql=False): + """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 - 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 `. + 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_adql : bool, optional + Returns the ADQL string instead of querying SIMBAD, by default False. + verbose : deprecated since 0.4.7 + get_query_payload : deprecated since 0.4.7 + cache : deprecated since 0.4.7 Returns ------- table : `~astropy.table.Table` - Query results table - - """ - response = self.query_objectids_async(object_name, cache=cache, - get_query_payload=get_query_payload) - if get_query_payload: - return response - - return self._parse_result(response, SimbadObjectIDsResult, - verbose=verbose) - - def query_objectids_async(self, object_name, *, cache=True, - get_query_payload=False): - """ - Serves the same function as `query_objectids`, but - only collects the response from the Simbad server and returns. + The result of the query to SIMBAD. - 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 `. + Examples + -------- + Get all the names of the Bubble Nebula - Returns - ------- - response : `requests.Response` - Response of the query from the server. + >>> 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 + >>> from astroquery.simbad import Simbad + >>> Simbad.query_objectids("m101", criteria="ident.id LIKE 'NGC%'") # doctest: +REMOTE_DATA +
+ id + object + --------- + NGC 5457 """ - request_payload = dict(script="\n".join(('format object "%IDLIST"', - 'query id %s' % object_name))) - - if get_query_payload: - return request_payload - - response = self._request("POST", self.SIMBAD_URL, data=request_payload, - timeout=self.TIMEOUT, cache=cache) - - return response + 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}" + if get_adql: + return query + return self.query_tap(query) def list_tables(self, *, get_adql=False): - """The names and descriptions of the tables in SIMBAD. + """List the names and descriptions of the tables in SIMBAD. Parameters ---------- get_adql : bool, optional - Returns the ADQL string instead of querying SIMBAD. + Return the ADQL string instead of querying SIMBAD. Returns ------- @@ -1051,23 +884,27 @@ def list_tables(self, *, get_adql=False): return self.query_tap(query) def list_columns(self, *tables: str, keyword=None, get_adql=False): - """ - Get the list of SIMBAD columns. + """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_adql : bool, optional Returns the ADQL string instead of querying SIMBAD. + Returns + ------- + `~astropy.table.Table` + Examples -------- >>> from astroquery.simbad import Simbad @@ -1109,10 +946,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)}%')" @@ -1125,8 +963,7 @@ def list_columns(self, *tables: str, keyword=None, get_adql=False): return self.query_tap(query) def list_linked_tables(self, table: str, *, get_adql=False): - """ - Expose the tables that can be non-obviously linked with the given table. + """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 @@ -1164,33 +1001,8 @@ def list_linked_tables(self, table: str, *, get_adql=False): 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. - - 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. + """Query SIMBAD TAP service. Parameters ---------- @@ -1221,7 +1033,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 +1042,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 +1084,87 @@ 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 uploads == {}: - return self._cached_query_tap(query, maxrec=maxrec) + return _cached_query_tap(self.tap, query, maxrec=maxrec) 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 _construct_query(self, top, columns, joins, criteria, get_adql=False, **uploads): + """Generate and ADQL string from the given query parameters. - # 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() + This assumes that the query is for data around the basic table. It is thus only + useful for the queries that return astronomical objects (every query except query_bibcode + and query_ids). - 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. + get_adql : bool, optional + Returns the ADQL string instead of querying SIMBAD, by default False. - 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: + 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 basic{join}{criteria}" + + if get_adql: + return query + return self.query_tap(query, **uploads) Simbad = SimbadClass() diff --git a/astroquery/simbad/criteria_lextab.py b/astroquery/simbad/criteria_lextab.py new file mode 100644 index 0000000000..00b2fb3ef2 --- /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..ea3cb64864 --- /dev/null +++ b/astroquery/simbad/criteria_parsetab.py @@ -0,0 +1,50 @@ +# -*- 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 criteria : COLUMN LIKE STRINGcriteria : COLUMN NOTLIKE STRINGcriteria : COLUMN IN LISTcriteria : REGION" + +_lr_action_items = {'(':([0,2,5,6,],[2,2,2,2,]),'COLUMN':([0,2,5,6,],[3,3,3,3,]),'REGION':([0,2,5,6,],[4,4,4,4,]),'$end':([1,4,12,13,14,15,16,17,18,19,],[0,-9,-1,-2,-3,-4,-5,-6,-7,-8,]),'|':([1,4,7,12,13,14,15,16,17,18,19,],[5,-9,5,5,5,-3,-4,-5,-6,-7,-8,]),'&':([1,4,7,12,13,14,15,16,17,18,19,],[6,-9,6,6,6,-3,-4,-5,-6,-7,-8,]),'BINARY_OPERATOR':([3,],[8,]),'LIKE':([3,],[9,]),'NOTLIKE':([3,],[10,]),'IN':([3,],[11,]),')':([4,7,12,13,14,15,16,17,18,19,],[-9,14,-1,-2,-3,-4,-5,-6,-7,-8,]),'STRING':([8,9,10,],[15,17,18,]),'NUMBER':([8,],[16,]),'LIST':([11,],[19,]),} + +_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',374), + ('criteria -> criteria & criteria','criteria',3,'p_criteria_AND','utils.py',378), + ('criteria -> ( criteria )','criteria',3,'p_criteria_parenthesis','utils.py',382), + ('criteria -> COLUMN BINARY_OPERATOR STRING','criteria',3,'p_criteria_string','utils.py',386), + ('criteria -> COLUMN BINARY_OPERATOR NUMBER','criteria',3,'p_criteria_string','utils.py',387), + ('criteria -> COLUMN LIKE STRING','criteria',3,'p_criteria_like','utils.py',392), + ('criteria -> COLUMN NOTLIKE STRING','criteria',3,'p_criteria_notlike','utils.py',396), + ('criteria -> COLUMN IN LIST','criteria',3,'p_criteria_in','utils.py',400), + ('criteria -> REGION','criteria',1,'p_criteria_region','utils.py',404), +] diff --git a/astroquery/simbad/data/query_criteria_fields.json b/astroquery/simbad/data/query_criteria_fields.json new file mode 100644 index 0000000000..3140dc4af4 --- /dev/null +++ b/astroquery/simbad/data/query_criteria_fields.json @@ -0,0 +1,286 @@ +{ + "all_fluxdata": { + "description": "All fluxes referenced for this object.", + "tap_table": "flux", + "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/tests/data/simbad_output_options.xml b/astroquery/simbad/tests/data/simbad_output_options.xml new file mode 100644 index 0000000000..459e88015c --- /dev/null +++ b/astroquery/simbad/tests/data/simbad_output_options.xml @@ -0,0 +1,509 @@ + + + + +
+ + + column name + + + + + brief description of column + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
idsall names concatenated with pipetable
otypedefall names and definitions for the object typestable
identIdentifiers of an astronomical objecttable
fluxMagnitude/Flux information about an astronomical objecttable
allfluxesall flux/magnitudes U,B,V,I,J,H,K,u_,g_,r_,i_,z_table
has_refAssociations between astronomical objects and their bibliographic referencestable
mesDistanceCollection of distances (pc, kpc or Mpc) by several means.table
mesDiameterCollection of stellar diameters.table
mesFe_hCollection of metallicity, as well as Teff, logg for stars.table
mesISOInfrared Space Observatory (ISO) observing log.table
mesIUEInternational Ultraviolet Explorer observing log.table
mesPLXCollection of trigonometric parallaxes.table
mesPMCollection of proper motions.table
mesRotStellar Rotational Velocities.table
mesVarCollection of stellar variability types and periods.table
mesVelocitiesCollection of HRV, Vlsr, cz and redshifts.table
mesXmmXMM observing log.table
mesHerschelThe Herschel observing Logtable
biblioBibliographytable
alltypesall object types concatenated with pipetable
otypesList of all object types associated with an objecttable
mesSpTCollection of spectral types.table
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
dimmain fields related to object dimensions: major 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..479d4bac3c 100644 --- a/astroquery/simbad/tests/test_simbad.py +++ b/astroquery/simbad/tests/test_simbad.py @@ -1,18 +1,18 @@ # 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.table import Table -import numpy as np +from astropy.io.votable import parse_single_table +import astropy.units as u + +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.utils.mocks import MockResponse + GALACTIC_COORDS = SkyCoord(l=-67.02084 * u.deg, b=-29.75447 * u.deg, frame="galactic") ICRS_COORDS = SkyCoord("05h35m17.3s -05h23m28s", frame="icrs") @@ -20,13 +20,6 @@ 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', } @@ -74,6 +67,33 @@ class last_query: return response +@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_output_options() + # >>> options.write("simbad_output_options.xml", format="votable") + + def mock_output_options(self): + return table + monkeypatch.setattr(simbad.SimbadClass, "hardlimit", 2000000) + monkeypatch.setattr(simbad.SimbadClass, "list_output_options", mock_output_options) + + +def test_adql_parameter(): + # escape single quotes + assert simbad.core._adql_parameter("Barnard's galaxy") == "Barnard''s galaxy" + + +# ------------------ +# Testing properties +# ------------------ + + def test_simbad_mirror(): simbad_instance = simbad.SimbadClass() # default value should be set at instantiation @@ -83,7 +103,7 @@ 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" @@ -96,376 +116,182 @@ 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_init_columns_in_output(): + simbad_instance = simbad.Simbad() + default_columns = simbad_instance.columns_in_output + # main_id from basic should be there + assert simbad.SimbadClass.Column("basic", "main_id") in default_columns + # there are 8 default columns + assert len(default_columns) == 8 -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 +@pytest.mark.usefixtures("_mock_simbad_class") +def test_mocked_simbad(): + simbad_instance = simbad.Simbad() + # this mocks the list_output_options + options = simbad_instance.list_output_options() + assert len(options) > 90 + # this mocks the hardlimit + assert simbad_instance.hardlimit == 2000000 -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) +# ---------------------------- +# Test output options settings +# ---------------------------- -@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'), +@pytest.mark.usefixtures("_mock_simbad_class") +def test_add_to_output(): + simbad_instance = simbad.Simbad() + # add columns from basic (one value) + simbad_instance.add_to_output("pmra") + assert simbad.SimbadClass.Column("basic", "pmra") in simbad_instance.columns_in_output + # add two columns from basic + simbad_instance.add_to_output("pmdec", "pm_bibcodE") # also test case insensitive + expected = [simbad.SimbadClass.Column("basic", "pmdec"), + simbad.SimbadClass.Column("basic", "pm_bibcode")] + assert all(column in simbad_instance.columns_in_output for column in expected) + # 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_to_output("id(1)") + assert simbad.SimbadClass.Column("basic", "main_id") in simbad_instance.columns_in_output + # errors are raised for the deprecated fields with options + with pytest.raises(ValueError, match="Criteria on filters are deprecated when defining Simbad's output.*"): + simbad_instance.add_to_output("fluxdata(V)") + with pytest.raises(ValueError, match="Coordinates conversion and formatting is no longer supported.*"): + simbad_instance.add_to_output("coo(s)", "dec(d)") + with pytest.raises(ValueError, match="Catalog Ids are no longer supported as an output option.*"): + simbad_instance.add_to_output("ID(Gaia)") + with pytest.raises(ValueError, match="Selecting a range of years for bibcode is now a criteria.*"): + simbad_instance.add_to_output("bibcodelist(2042-2050)") + # historical measurements + with pytest.raises(ValueError, match="'einstein' is no longer a part of SIMBAD.*"): + simbad_instance.add_to_output("einstein") + # typos should have suggestions + with pytest.raises(ValueError, match="'alltype' is not one of the accepted options which can be " + "listed with 'list_output_options'. Did you mean 'alltypes' or 'otype' or 'otypes'?"): + simbad_instance.add_to_output("ALLTYPE") + # bundles and tables require a connection to the tap_schema and are thus tested in test_simbad_remote + + +# ------------------------------------------ +# 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_adql=True) + 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_adql=True) + 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') + + +@pytest.mark.usefixtures("_mock_simbad_class") +def test_query_objectids(): + adql = simbad.core.Simbad.query_objectids('Polaris', + criteria="ident.id LIKE 'HD%'", + get_adql=True) + 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_adql=True, + criteria="dec < 5") + # 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_adql=True, + criteria="update_date < '2010-01-01'") + where_clause = "WHERE id LIKE 'Gaia DR2 %' AND (update_date < '2010-01-01')" + assert adql.endswith(where_clause) + + +@pytest.mark.parametrize(('coordinates', 'radius'), + [(ICRS_COORDS, 2*u.arcmin), + (GALACTIC_COORDS, 5 * u.deg), + (FK4_COORDS, '5d0m0s'), + (FK5_COORDS, 2*u.arcmin), + (multicoords, 0.5*u.arcsec), + (multicoords, "0.5s"), ]) -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) +@pytest.mark.usefixtures("_mock_simbad_class") +def test_query_region(coordinates, radius): + # looks like this also tests class as Simbad or Simbad() + adql = simbad.core.Simbad.query_region(coordinates, radius=radius, get_adql=True) + adql_2 = simbad.core.Simbad().query_region(coordinates, radius=radius, get_adql=True) + assert adql == adql_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 - - -@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') - ]) -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_errors(): with pytest.raises(u.UnitsError): - simbad.core.Simbad().query_region( - coordinates, radius=radius, equinox=equinox, epoch=epoch) - + simbad.core.Simbad().query_region(ICRS_COORDS, radius=0) -def test_query_region_coord_radius_mismatch(): - with pytest.raises(ValueError, match="^Mismatch between radii and coordinates$"): + 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) -@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 +@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_adql=True) + expected = ('FROM basic JOIN ident ON basic."oid" = ident."oidref" RIGHT JOIN TAP_UPLOAD.script_infos' + ' ON ident."id" = TAP_UPLOAD.script_infos."typed_id" WHERE (id IN (\'m1\', \'m2\') OR ' + 'typed_id IS NOT NULL) AND (otype = \'Galaxy..\')') + assert adql.endswith(expected) + # with wildcard + adql = simbad.core.Simbad.query_objects(("M *", "NGC *"), wildcard=True, get_adql=True) + expected = (r'SELECT .* TAP_UPLOAD\.script_infos\.\* FROM basic JOIN ident ' + r'ON basic\."oid" = ident\."oidref" RIGHT JOIN TAP_UPLOAD\.script_infos ON' + r' ident\."id" = TAP_UPLOAD\.script_infos\."typed_id" WHERE \(\(regexp\(id, \'\^M \+\.\*\$\'\)' + r' = 1 OR regexp\(id, \'\^NGC \+\.\*\$\'\) = 1\) OR typed_id IS NOT NULL\)') + assert re.match(expected, adql) is not None + + +@pytest.mark.usefixtures("_mock_simbad_class") +def test_query_object(): + # no wildcard + adql = simbad.core.Simbad.query_object("m1", wildcard=False, get_adql=True) + 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_adql=True) + end = "WHERE regexp(id, '^m +[0-9]$') = 1" + assert adql.endswith(end) + +# ------------------------- +# 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("'''") + # --------------------------------------------------- # Test the adql string for query_tap helper functions @@ -482,13 +308,13 @@ def test_simbad_list_columns(): 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 # 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") + "AND LOWERCASE(table_name) = 'basic' ORDER BY table_name, principal DESC, column_name") assert simbad.Simbad.list_columns("basic", get_adql=True) == columns_adql # with only a keyword list_columns_adql = ("SELECT table_name, column_name, datatype, description, unit, ucd " @@ -505,3 +331,35 @@ def test_list_linked_tables(): "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 + + +@pytest.mark.usefixtures("_mock_simbad_class") +def test_construct_query(): + column = simbad.Simbad.Column("basic", "*") + # bare minimum with an alias + expected = 'SELECT basic."main_id" AS my_id FROM basic' + assert simbad.Simbad._construct_query(-1, + [simbad.Simbad.Column("basic", "main_id", "my_id")], + [], + [], get_adql=True) == expected + # with top + # and duplicated columns are dropped + expected = "SELECT TOP 1 basic.* FROM basic" + assert simbad.Simbad._construct_query(1, + [column, column], + [], + [], get_adql=True) == expected + # with a join + expected = 'SELECT basic.*, ids."ids" FROM basic JOIN ids ON basic."oid" = ids."oidref"' + assert simbad.Simbad._construct_query(-1, + [column, simbad.Simbad.Column("ids", "ids")], + [simbad.Simbad.Join("ids", + simbad.Simbad.Column("basic", "oid"), + simbad.Simbad.Column("ids", "oidref"))], + [], get_adql=True) == expected + # with a condition + expected = "SELECT basic.* FROM basic WHERE ra < 6 AND ra > 5" + assert simbad.Simbad._construct_query(-1, + [column], + [], + ["ra < 6", "ra > 5"], get_adql=True) == expected diff --git a/astroquery/simbad/tests/test_simbad_remote.py b/astroquery/simbad/tests/test_simbad_remote.py index fa9567c9e2..6b6e0c119a 100644 --- a/astroquery/simbad/tests/test_simbad_remote.py +++ b/astroquery/simbad/tests/test_simbad_remote.py @@ -1,15 +1,12 @@ # 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.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.simbad.core import _cached_query_tap from astroquery.exceptions import BlankResponseWarning from pyvo.dal.exceptions import DALOverflowWarning @@ -18,254 +15,98 @@ # 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_to_output("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) + + 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"] == list(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 - # 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 + 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. + assert len(result) == 3 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_to_output("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_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 +116,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 +131,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 +140,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_to_output("dim") + # check the length + assert len(simbad_instance.columns_in_output) == 8 + assert Simbad.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_to_output("otypes") + assert Simbad.Column("otypes", "otype", '"otypes.otype"') in simbad_instance.columns_in_output + # tables also require a join + assert Simbad.Join("otypes", + Simbad.Column("basic", "oid"), + Simbad.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_to_output("IUE") + # empty before the test + simbad_instance.columns_in_output = [] + # mixed columns bundles and tables + simbad_instance.add_to_output("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..0bf7dde9d4 --- /dev/null +++ b/astroquery/simbad/tests/test_utils.py @@ -0,0 +1,78 @@ + +import pytest + +from astroquery.simbad.utils import (CriteriaTranslator, _parse_coordinate_and_convert_to_icrs, + _region_to_contains) + +from astropy.coordinates.builtin_frames.icrs import ICRS + + +@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", "B1950") +]) +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) + + +@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(): + # 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, 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 + + +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 otype = 'G' AND (nbref >= 10 OR bibyear >= 2000)")), + ("otype != 'Galaxy..'", "otype != 'Galaxy..'"), + ("author ∼ 'egret*'", "regexp(author, '^egret.*$') = 1"), + ("cat in ('hd','hip','ppm')", "cat IN ('hd','hip','ppm')") +]) # these are the examples from http://simbad.cds.unistra.fr/guide/sim-fsam.htx +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 diff --git a/astroquery/simbad/utils.py b/astroquery/simbad/utils.py new file mode 100644 index 0000000000..c2081270a3 --- /dev/null +++ b/astroquery/simbad/utils.py @@ -0,0 +1,324 @@ +"""Contains utility functions to support legacy Simbad interface.""" + +from collections import deque +import re + +from astropy.coordinates import SkyCoord, Angle +from astropy.utils.parsing import lex, yacc +from astropy.utils import classproperty + + +def list_wildcards(): + """ + Displays the available wildcards that may be used in SIMBAD queries and + their usage. + + Examples + -------- + >>> from astroquery.simbad.utils import list_wildcards + >>> 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. + """ + 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())) + + +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"^flux.*\(.+\)$", votable_field): + raise ValueError("Criteria on filters are deprecated when defining Simbad's output. " + "See section on filters in " + "https://astroquery.readthedocs.io/en/latest/simbad/simbad_evolution.html") + if re.match(r"^(ra|dec|coo)\(.+\)$", votable_field): + raise ValueError("Coordinates conversion and formatting is no longer supported. 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`. " + "See section on catalogs in " + "https://astroquery.readthedocs.io/en/latest/simbad/simbad_evolution.html") + if votable_field.startswith("bibcodelist("): + raise ValueError("Selecting a range of years for bibcode is now a criteria. " + "See section on bibcodelist in" + "https://astroquery.readthedocs.io/en/latest/simbad/simbad_evolution.html") + +# ---------------------------- +# To 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" + + else: + raise ValueError("Simbad TAP supports regions of types 'circle', 'box', or 'polygon'.") + return contains + + +def _parse_coordinate_and_convert_to_icrs(string_coordinate, *, + frame="icrs", epoch=None, equinox=None): + """Convert a string into a SkyCoord object in the ICRS frame.""" + if re.search(r"\d+ *[\+\- ]\d+", string_coordinate): + center = SkyCoord(string_coordinate, unit="deg", frame=frame, obstime=epoch, equinox=equinox) + else: + center = SkyCoord.from_name(string_coordinate) + return center.transform_to("icrs") + + +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 the strange long ∼ + 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 + """ + p[0] = p[1] + " " + p[2] + " " + p[3] + + def p_criteria_like(p): + """criteria : COLUMN LIKE STRING""" + p[0] = "regexp(" + p[1] + ", '" + _wildcard_to_regexp(p[3][1:-1]) + "') = 1" + + def p_criteria_notlike(p): + """criteria : COLUMN NOTLIKE STRING""" + p[0] = "regexp(" + p[1] + ", '" + _wildcard_to_regexp(p[3][1:-1]) + "') = 0" + + def p_criteria_in(p): + """criteria : COLUMN IN LIST""" + p[0] = p[1] + " IN " + p[3] + + def p_criteria_region(p): + """criteria : REGION""" + p[0] = _region_to_contains(p[1]) + + def p_error(p): + raise ValueError("Syntax error for sim-script criteria") + + return yacc(tabmodule="criteria_parsetab", package="astroquery/simbad") + + @classmethod + def parse(cls, criteria): + return cls._parser.parse(criteria, lexer=cls._lexer) From e1330426f0ea51cd6d3faa04ea60db152e21c42a Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Tue, 20 Feb 2024 15:09:49 +0100 Subject: [PATCH 02/28] lint: add yacc and lex automatically generated files to flake8 ignore these are the files of the simbad.utils.CriteriaTranslator parser --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 = From 506fe36fd309ceb72080e20916861a94d10d19e7 Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Tue, 20 Feb 2024 15:11:33 +0100 Subject: [PATCH 03/28] fix: add a __call__ definition to make BaseVOQuery behave as BaseQuery --- astroquery/query.py | 4 ++++ 1 file changed, 4 insertions(+) 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): """ From 879bceff0154f51059bc58ddea67613c6d0ce1a0 Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Tue, 20 Feb 2024 15:12:42 +0100 Subject: [PATCH 04/28] add query_criteria_fialds.json and move from os to pathlib --- astroquery/simbad/setup_package.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/astroquery/simbad/setup_package.py b/astroquery/simbad/setup_package.py index 2aea743840..ac8af7dbfe 100644 --- a/astroquery/simbad/setup_package.py +++ b/astroquery/simbad/setup_package.py @@ -1,23 +1,23 @@ # 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') / 'query_bibcode.data'), + str(Path('data') / 'query_bibobj.data'), + str(Path('data') / 'query_cat.data'), + str(Path('data') / 'query_coo.data'), + str(Path('data') / 'query_id.data'), + str(Path('data') / 'query_error.data'), + str(Path('data') / 'query_*.data'), + str(Path('data') / 'm1.data'), ] - 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'), + str(Path('data') / 'votable_fields_notes.json'), + str(Path('data') / 'votable_fields_table.txt'), + str(Path('data') / 'votable_fields_dict.json'), ] return {'astroquery.simbad.tests': paths_test, From 43a791d305427677476938376145162d5ebb7373 Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Tue, 20 Feb 2024 15:13:18 +0100 Subject: [PATCH 05/28] docs: document simbad refactoring --- docs/simbad/query_tap.rst | 18 +- docs/simbad/simbad.rst | 970 +++++++++++++------------------ docs/simbad/simbad_evolution.rst | 237 ++++++++ 3 files changed, 655 insertions(+), 570 deletions(-) create mode 100644 docs/simbad/simbad_evolution.rst diff --git a/docs/simbad/query_tap.rst b/docs/simbad/query_tap.rst index 37185bcf0e..f32f293950 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. @@ -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 @@ -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 @@ -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. @@ -212,7 +214,7 @@ that is the measurement table for rotations. Their common column is ``oidref``. 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. @@ -246,7 +248,7 @@ is in the ``basic`` column. The ``star..`` syntax refers to any type of star. 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. @@ -277,7 +279,7 @@ Then, the ``basic`` table is joined with ``h_link`` and the sub-query result. LEDA 1831614 G 243.189153 ... 100 SDSSCGB 350 Query a long list of object -""""""""""""""""""""""""""" +^^^^^^^^^^^^^^^^^^^^^^^^^^^ To query a list of objects (or coordinates, of bibliographic references), we can use the ADQL criteria ``IN`` like so: diff --git a/docs/simbad/simbad.rst b/docs/simbad/simbad.rst index 117ecc0dcd..bc4287b7b6 100644 --- a/docs/simbad/simbad.rst +++ b/docs/simbad/simbad.rst @@ -1,5 +1,3 @@ -.. doctest-skip-all - .. _astroquery_simbad: ************************************ @@ -9,34 +7,42 @@ 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 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. 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. + +About deprecation warnings +-------------------------- + +The SIMBAD module has been rewritten with astroquery > 0.4.7. If you're here following a +deprecation warning, this is your go-to page: + +.. 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 pre-defined 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 query via +Table Access Protocol (TAP) with the `~astroquery.simbad.SimbadClass.query_tap` method. +This is described in :ref:`query TAP `. Query modes =========== @@ -50,243 +56,197 @@ Query by an Identifier This is useful if you want to query 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, but they render the query case-sensitive. So 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 ~6s due to a current (early 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 + + +We can see that the messier objects are indeed found. Their ``main_id`` is not necessarly +the one corresponding to the wildcard expression. The column ``matched_id`` will return which +identifier was matched. The wildcard parameter can often be replaced by a way faster query +done with `~astroquery.simbad.SimbadClass.query_objects`. + +Wildcards are supported by: + - `~astroquery.simbad.SimbadClass.query_object` + - `~astroquery.simbad.SimbadClass.query_objects` + - `~astroquery.simbad.SimbadClass.query_bibcode` + +The wildcards that are supported and their usage across all these queries is the same. +To see the available wildcards and their functions: .. code-block:: python - >>> from astroquery.simbad import Simbad - >>> Simbad.list_wildcards() - + >>> from astroquery.simbad.utils import list_wildcards + >>> 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] + [^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 often (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 + >>> print(result_table) # doctest: +IGNORE_OUTPUT + + 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 -`__. +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`. -.. code-block:: python +.. doctest-remote-data:: >>> from astroquery.simbad import Simbad - >>> result_table = Simbad.query_region("m81") - >>> print(result_table) - - 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 - ... - -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 - - >>> 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 ... - - - + >>> 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 coordinates are used, then they should be entered using an `astropy.coordinates.SkyCoord` object. -.. code-block:: python +.. doctest-remote-data:: >>> 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 + >>> from astropy.coordinates import SkyCoord + >>> simbad = Simbad() + >>> simbad.ROW_LIMIT = 10 + >>> coordinate = SkyCoord("05h35m17.3s -05h23m28s", frame='icrs') + >>> simbad.query_region(coordinate, radius='1d0m0s') # doctest: +IGNORE_OUTPUT +
+ main_id ra dec ... coo_err_angle coo_wavelength coo_bibcode + deg deg ... deg + object float64 float64 ... int16 str1 object + ---------------------------- ----------------- ------------------ ... ------------- -------------- ------------------- + TYC 9390-1857-1 89.18327041666667 -81.31254972222223 ... 71 O 2016A&A...595A...2G + PKS 0602-813 89.37791666666668 -81.37027777777777 ... -- + TYC 9390-1786-1 89.14477451271 -81.41309658604 ... 90 O 2020yCat.1350....0G + TYC 9390-1878-1 88.75276112064 -81.44513030144 ... 90 O 2020yCat.1350....0G + Gaia DR3 4621434189736118144 89.2148880241 -81.42047989066 ... 90 O 2020yCat.1350....0G + Gaia DR3 4621443844823809536 89.77428052292792 -81.21112425878056 ... 90 O 2020yCat.1350....0G + CD-81 190 89.17676879332167 -81.38640963209807 ... 90 O 2020yCat.1350....0G + PKS J0557-8122 89.3624 -81.3742 ... 0 R 2012MNRAS.422.1527M + UCAC4 044-003417 89.50051776702 -81.21953460424 ... 90 O 2020yCat.1350....0G + IRAS F05246-8120 80.00253626975339 -81.29356823821976 ... 100 F 1990IRASF.C......0M + + +.. 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 - - - -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 + >>> 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 + +Calling `~astroquery.simbad.SimbadClass.query_region` within a loop is *very* +inefficient. If you need to query many regions, use a `~astropy.coordinates.SkyCoord` +with a list of centers and a list of radii. It looks like this: + +.. 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]) +
+ 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 Query a catalogue ^^^^^^^^^^^^^^^^^ @@ -294,92 +254,112 @@ Query a catalogue Queries can also be formulated to 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) + >>> limitedSimbad.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 + +To see the available catalogues, you can write a custom ADQL query on the ``cat`` table. +For example to get the 10 biggest catalogs in SIMBAD, it looks like this: + +.. doctest-remote-data:: + + >>> from astroquery.simbad import Simbad + >>> Simbad.query_tap('SELECT TOP 10 cat_name, description FROM cat ORDER BY "size" DESC') +
+ 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 + GSC Guide Star Catalogue + WISE Wide-field Infrared Survey Explorer Final Release Source Catalog + LEDA Lyon-Meudon Extragalactic DatabaseA - 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 Bibliographic queries --------------------- Query a bibcode -^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^ This retrieves the reference 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 article 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: -.. code-block:: python +.. doctest-remote-data:: >>> from astroquery.simbad import Simbad - >>> result_table = Simbad.query_bibcode('2013A&A*', wildcard=True) - >>> print(result_table) + >>> Simbad.query_bibcode('2013A&ARv.*', wildcard=True) # doctest: +IGNORE_OUTPUT +
+ bibcode doi journal ... volume year + object object object ... int32 int16 + ------------------- ------------------------- ------- ... ------ ----- + 2013A&ARv..21...62D 10.1007/s00159-013-0062-7 A&ARv ... 21 2013 + 2013A&ARv..21...59I 10.1007/s00159-013-0059-2 A&ARv ... 21 2013 + 2013A&ARv..21...70B 10.1007/s00159-013-0070-7 A&ARv ... 21 2013 + 2013A&ARv..21...69R 10.1007/s00159-013-0069-0 A&ARv ... 21 2013 + 2013A&ARv..21...61R 10.1007/s00159-013-0061-8 A&ARv ... 21 2013 + 2013A&ARv..21...64D 10.1007/s00159-013-0064-5 A&ARv ... 21 2013 + 2013A&ARv..21...68G 10.1007/s00159-013-0068-1 A&ARv ... 21 2013 + 2013A&ARv..21...63T 10.1007/s00159-013-0063-6 A&ARv ... 21 2013 + 2013A&ARv..21...67B 10.1007/s00159-013-0067-2 A&ARv ... 21 2013 + +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 ^^^^^^^^^^^^^^ @@ -387,182 +367,23 @@ 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 +.. doctest-remote-data:: >>> 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 - -However, note that missing data will result in missing lines: - -.. code-block:: python - - >>> 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 ================================ @@ -573,8 +394,7 @@ the Simbad queries. Changing the row limit ---------------------- - -To fetch all the rows in the result, the row limit must be set to 0. However for some +To fetch all the rows in the result, the row limit must be set to -1. 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: @@ -582,147 +402,173 @@ 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:: + `~astroquery.simbad.SimbadClass.query_tap` is an exception 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 - - >>> from astroquery.simbad import Simbad - >>> Simbad.TIMEOUT = 60 # sets the timeout to 60s +.. Warning:: -Specifying which VOTable fields to include in the result --------------------------------------------------------- + Before astroquery v0.4.7, this was done with ``votable_fields``. This is not + the case anymore. See :ref:`SIMBAD evolutions `. +Some query methods outputs can be customized. This is the case for: -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: - -.. code-block:: python +- `~astroquery.simbad.SimbadClass.query_criteria` +- `~astroquery.simbad.SimbadClass.query_object` +- `~astroquery.simbad.SimbadClass.query_objects` +- `~astroquery.simbad.SimbadClass.query_region` +- `~astroquery.simbad.SimbadClass.query_bibobj` - >>> from astroquery.simbad import Simbad - >>> Simbad.list_votable_fields() +Their default columns are: - 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 +- main_id +- ra +- dec +- coo_err_maj +- coo_err_min +- coo_err_angle +- coo_wavelength +- coo_bibcode' +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_to_output`: -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: - -.. 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'] - - # To set other fields - - >>> customSimbad.add_votable_fields('mk', 'rot', 'bibcodelist(1800-2014)') - >>> customSimbad.get_votable_fields() + >>> simbad = Simbad() + >>> simbad.add_to_output("otype") # here we add a single column about object type - ['main_id', 'coordinates', 'mk', 'rot', 'bibcodelist(1800-2014')] +Some options add a single column and others add columns that are relevant for a theme (ex: fluxes, +proper motions...). +The list of possible options is printed with: -You can also remove a field you have set or -:meth:`astroquery.simbad.SimbadClass.reset_votable_fields`. Continuing from -the above example: +.. doctest-remote-data:: -.. code-block:: python - - >>> customSimbad.remove_votable_fields('mk', 'coordinates') - >>> customSimbad.get_votable_fields() - - ['main_id', 'rot', 'bibcodelist(1800-2014)'] - - # reset back to defaults - - >>> customSimbad.reset_votable_fields() - >>> customSimbad.get_votable_fields() - - ['main_id', 'coordinates'] - - -Returning the queried name in the return table -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -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. - - >>> 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 - - >>> 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 + >>> from astroquery.simbad import Simbad + >>> Simbad.list_output_options()[["name", "description"]] +
+ name ... + object ... + --------------- ... + ids ... + otypedef ... + ident ... + flux ... + allfluxes ... + has_ref ... + mesDistance ... + mesDiameter ... + mesFe_h ... + mesISO ... + ... ... + vlsr_min ... + vlsr_wavelength ... + coordinates ... + dim ... + dimensions ... + morphtype ... + parallax ... + propermotions ... + sp ... + velocity ... + +Additional criteria +------------------- + +.. Warning:: + + Before astroquery v0.4.7, this was only possible within `~astroquery.simbad.SimbadClass.query_criteria`. This is not + the case anymore, and a lot of query methods now admit criteria strings. See :ref:`SIMBAD evolutions `. + +Some query methods take a ``criteria`` argument. They are listed here: + +- `~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` + +A the criteria argument expect a string that fits in the ``WHERE`` clause of an ADQL query. Some examples can +be found in the `Simbad ADQL cheat sheet `__. A way +of writing them is to first query a blank table to inspect the columns the method will return: + +.. doctest-remote-data:: -Specifying the format of the included VOTable fields ----------------------------------------------------- + >>> 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_to_output("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 + +With the information on the columns that will be returned by the query, it is now possible to write a criteria. +For example, to get only proper motions measurements more recent than 2000, we can add a constraint on the +first character of the bibcode (the first 4 digits of a bibcode are the year of publication of the article): + +.. doctest-remote-data:: -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): + >>> from astroquery.simbad import Simbad + >>> criteria = "mespm.bibcode LIKE '2%'" # starts with 2, anything after + >>> simbad = Simbad() + >>> simbad.add_to_output("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: -.. code-block:: python +Query TAP +========= - >>> 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 +.. include:: query_tap.rst diff --git a/docs/simbad/simbad_evolution.rst b/docs/simbad/simbad_evolution.rst new file mode 100644 index 0000000000..f35e6011ec --- /dev/null +++ b/docs/simbad/simbad_evolution.rst @@ -0,0 +1,237 @@ +.. _simbad-evolutions: + +######################## +Simbad module evolutions +######################## + +********************************* +Votable fields and Output options +********************************* + +Votable fields are deprecated in favor of output options. Most of the former votable +fields can now be added to the output of Simbad queries with +`~astroquery.simbad.SimbadClass.add_to_output`. The columns and tables that have a new name +under the TAP interface will be recognized by `~astroquery.simbad.SimbadClass.add_to_output`. +Some Votable Fields that supported options in parenthesis are no +longer supported. These can be replaced by criteria in query_methods or by a custom ADQL +query called with `~astroquery.simbad.SimbadClass.query_tap`. The documentation and former +issues on astroquery's repository contain examples, but don't hesitate to open a new issue +if there is some missing information. + +**************************************** +Translating query_criteria into criteria +**************************************** + +The method `~astroquery.simbad.SimbadClass.query_criteria` is now deprecated in SIMBAD. +It is still possible to use it from astroquery for now, but any existing bug will not +be fixed. There are also a number of missing features. +This page shows how the former functionalities of `~astroquery.simbad.SimbadClass.query_criteria` +can be replaced. + +The new interface to connect to SIMBAD is build on TAP and ADQL. +To learn more about this, you can have a look at the +`~astroquery.simbad.SimbadClass.query_tap` :ref:`documentation `. + +Since astroquery > 0.4.7, most of the ``query_***`` methods in the simbad module accept +a ``criteria`` argument, it concerns: + +- `~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` + +There is a helper method to translate a criteria from +`~astroquery.simbad.SimbadClass.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, GAL, 0 +0, 3d 1d) & otype='SNR'") + "CONTAINS(POINT('ICRS', ra, dec), BOX('ICRS', 266.4049882865447, -28.936177761791473, 3.0, 1.0)) = 1 AND otype = 'SNR'" + +This string can then either be incorporated in a custom ADQL query called with +`~astroquery.simbad.SimbadClass.query_tap` or in any of the query methods that accept a ``criteria`` argument. +See for 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 = "otype != 'Galaxy..' & otype != 'Cl*..'" + >>> simbad = Simbad() + >>> # we add the main type and all the types that have historically been attributed to the object + >>> simbad.add_to_output("otype", "alltypes") + >>> result = simbad.query_catalog("M", criteria=CriteriaTranslator.parse(old_criteria)) + >>> result[["main_id", "catalog_id", "otype", "otypes"]] +
+ main_id catalog_id otype otypes + object object object object + --------- ---------- ------ ---------------------------------------- + M 27 M 27 PN *|G|HS?|IR|PN|UV|WD*|WD?|X|blu + M 24 M 24 As* As*|Cl*|GNe + M 40 M 40 ? ? + M 78 M 78 RNe C?*|Cl*|ISM|RNe + NGC 6994 M 73 err Cl*|err + M 43 M 43 HII HII|IR|Rad + M 42 M 42 HII C?*|Cl*|HII|OpC|Rad|X + M 1 M 1 SNR HII|IR|Rad|SNR|X|gam + M 76 M 76 PN *|IR|PN|Rad|WD* + M 97 M 97 PN *|HS?|IR|NIR|Opt|PN|Rad|UV|WD*|WD?|X|blu + M 57 M 57 PN *|HS?|IR|PN|Rad|WD*|WD?|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. +But 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 for object types can be found either in SIMBAD's +`documentation on object types `_ or with TAP queries. +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. + +.. 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 + +And the ``path`` column 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 see with +`~astroquery.simbad.SimbadClass.query_criteria`, 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). + +******* +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 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_to_output` (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_to_output("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 From bfbbc77d2c488c69b1d6b576ba4fc2af277983d7 Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Wed, 21 Feb 2024 15:37:33 +0100 Subject: [PATCH 06/28] fix: propagate simbad changes to jwst module this commit also adds a patch to simbad's query_objects in the tests --- astroquery/esa/jwst/core.py | 9 +- astroquery/esa/jwst/tests/data/simbad_M1.vot | 73 ++++++++++++ .../esa/jwst/tests/data/simbad_TEST.vot | 58 ++++++++++ astroquery/esa/jwst/tests/test_jwsttap.py | 104 +++++++++--------- 4 files changed, 192 insertions(+), 52 deletions(-) create mode 100644 astroquery/esa/jwst/tests/data/simbad_M1.vot create mode 100644 astroquery/esa/jwst/tests/data/simbad_TEST.vot 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() From afaddfc8af6c7609bc8946ecb34599edb393cdc3 Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Wed, 21 Feb 2024 15:38:47 +0100 Subject: [PATCH 07/28] docs: propagate simbad changes into the general documentation --- README.rst | 7 ++++--- docs/esa/iso/iso.rst | 10 +++++----- docs/index.rst | 25 ++++++++++++------------- docs/simbad/simbad.rst | 4 ++-- docs/simbad/simbad_evolution.rst | 8 ++++---- 5 files changed, 27 insertions(+), 27 deletions(-) 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/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..5efe450a0e 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. diff --git a/docs/simbad/simbad.rst b/docs/simbad/simbad.rst index bc4287b7b6..b23940e2e1 100644 --- a/docs/simbad/simbad.rst +++ b/docs/simbad/simbad.rst @@ -422,7 +422,7 @@ Choosing the columns in the output tables Some query methods outputs can be customized. This is the case for: -- `~astroquery.simbad.SimbadClass.query_criteria` +- `~astroquery.simbad.DeprecatedSimbadClass.query_criteria` - `~astroquery.simbad.SimbadClass.query_object` - `~astroquery.simbad.SimbadClass.query_objects` - `~astroquery.simbad.SimbadClass.query_region` @@ -487,7 +487,7 @@ Additional criteria .. Warning:: - Before astroquery v0.4.7, this was only possible within `~astroquery.simbad.SimbadClass.query_criteria`. This is not + Before astroquery v0.4.7, this was only possible within `~astroquery.simbad.DeprecatedSimbadClass.query_criteria`. This is not the case anymore, and a lot of query methods now admit criteria strings. See :ref:`SIMBAD evolutions `. Some query methods take a ``criteria`` argument. They are listed here: diff --git a/docs/simbad/simbad_evolution.rst b/docs/simbad/simbad_evolution.rst index f35e6011ec..e057f93d7c 100644 --- a/docs/simbad/simbad_evolution.rst +++ b/docs/simbad/simbad_evolution.rst @@ -22,10 +22,10 @@ if there is some missing information. Translating query_criteria into criteria **************************************** -The method `~astroquery.simbad.SimbadClass.query_criteria` is now deprecated in SIMBAD. +The method `~astroquery.simbad.DeprecatedSimbadClass.query_criteria` is now deprecated in SIMBAD. It is still possible to use it from astroquery for now, but any existing bug will not be fixed. There are also a number of missing features. -This page shows how the former functionalities of `~astroquery.simbad.SimbadClass.query_criteria` +This page shows how the former functionalities of `~astroquery.simbad.DeprecatedSimbadClass.query_criteria` can be replaced. The new interface to connect to SIMBAD is build on TAP and ADQL. @@ -44,7 +44,7 @@ a ``criteria`` argument, it concerns: - `~astroquery.simbad.SimbadClass.query_objectids` There is a helper method to translate a criteria from -`~astroquery.simbad.SimbadClass.query_criteria` into a string that will work as ``criteria`` +`~astroquery.simbad.DeprecatedSimbadClass.query_criteria` into a string that will work as ``criteria`` in the other query methods cited above: .. code-block:: python @@ -137,7 +137,7 @@ The label can also be used in a query. And the ``path`` column 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 see with -`~astroquery.simbad.SimbadClass.query_criteria`, you might be interested in this +`~astroquery.simbad.DeprecatedSimbadClass.query_criteria`, you might be interested in this `table of correspondence `_ between old and new labels for object types. From cf88d47b3e6848add9522a61d402fbfb901a929d Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Wed, 21 Feb 2024 17:18:59 +0100 Subject: [PATCH 08/28] docs: simbad cache works differently now simbad calls lru_cache from python core library, so no cache_location --- docs/index.rst | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 5efe450a0e..739f3cfed5 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -190,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 @@ -231,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) @@ -241,7 +241,6 @@ temporarily or permanently changing configuration values can be found 604800 - Available Services ================== From c961fd7f8dfd562f10f31e32834076cf72cb6ece Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Thu, 22 Feb 2024 17:00:28 +0100 Subject: [PATCH 09/28] tests: add simbad_output_options to path_tests --- astroquery/simbad/setup_package.py | 1 + 1 file changed, 1 insertion(+) diff --git a/astroquery/simbad/setup_package.py b/astroquery/simbad/setup_package.py index ac8af7dbfe..424c72cb20 100644 --- a/astroquery/simbad/setup_package.py +++ b/astroquery/simbad/setup_package.py @@ -12,6 +12,7 @@ def get_package_data(): str(Path('data') / 'query_error.data'), str(Path('data') / 'query_*.data'), str(Path('data') / 'm1.data'), + str(Path('data') / 'simbad_output_options.xml'), ] paths_core = [str(Path('data') / 'query_criteria_fields.json'), From d892a45089378dec328abfbb870255543e2c3faa Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Mon, 25 Mar 2024 09:49:03 +0100 Subject: [PATCH 10/28] refactor: remove utilities for the former sim script queries --- astroquery/simbad/__init__.py | 8 +- .../simbad/data/votable_fields_dict.json | 109 ----------- .../simbad/data/votable_fields_notes.json | 5 - astroquery/simbad/get_votable_fields.py | 41 ---- astroquery/simbad/setup_package.py | 17 +- astroquery/simbad/tests/data/m1.data | 67 ------- .../simbad/tests/data/query_bibcode.data | 22 --- .../simbad/tests/data/query_bibobj.data | 71 ------- astroquery/simbad/tests/data/query_cat.data | 176 ------------------ astroquery/simbad/tests/data/query_coo.data | 131 ------------- astroquery/simbad/tests/data/query_error.data | 28 --- astroquery/simbad/tests/data/query_id.data | 67 ------- .../simbad/tests/data/query_objectids.data | 57 ------ .../simbad/tests/data/query_sample.data | 48 ----- .../tests/data/query_sample_region.data | 75 -------- 15 files changed, 8 insertions(+), 914 deletions(-) delete mode 100644 astroquery/simbad/data/votable_fields_dict.json delete mode 100644 astroquery/simbad/data/votable_fields_notes.json delete mode 100644 astroquery/simbad/get_votable_fields.py delete mode 100644 astroquery/simbad/tests/data/m1.data delete mode 100644 astroquery/simbad/tests/data/query_bibcode.data delete mode 100644 astroquery/simbad/tests/data/query_bibobj.data delete mode 100644 astroquery/simbad/tests/data/query_cat.data delete mode 100644 astroquery/simbad/tests/data/query_coo.data delete mode 100644 astroquery/simbad/tests/data/query_error.data delete mode 100644 astroquery/simbad/tests/data/query_id.data delete mode 100644 astroquery/simbad/tests/data/query_objectids.data delete mode 100644 astroquery/simbad/tests/data/query_sample.data delete mode 100644 astroquery/simbad/tests/data/query_sample_region.data diff --git a/astroquery/simbad/__init__.py b/astroquery/simbad/__init__.py index 12618e3a9f..ffb79d6d90 100644 --- a/astroquery/simbad/__init__.py +++ b/astroquery/simbad/__init__.py @@ -28,13 +28,17 @@ class Conf(_config.ConfigNamespace): row_limit = _config.ConfigItem( # O defaults to the maximum limit - 0, + -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', 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 424c72cb20..ba92842b61 100644 --- a/astroquery/simbad/setup_package.py +++ b/astroquery/simbad/setup_package.py @@ -4,22 +4,9 @@ def get_package_data(): - paths_test = [str(Path('data') / 'query_bibcode.data'), - str(Path('data') / 'query_bibobj.data'), - str(Path('data') / 'query_cat.data'), - str(Path('data') / 'query_coo.data'), - str(Path('data') / 'query_id.data'), - str(Path('data') / 'query_error.data'), - str(Path('data') / 'query_*.data'), - str(Path('data') / 'm1.data'), - str(Path('data') / 'simbad_output_options.xml'), - ] + paths_test = [str(Path('data') / 'simbad_output_options.xml')] - paths_core = [str(Path('data') / 'query_criteria_fields.json'), - str(Path('data') / 'votable_fields_notes.json'), - str(Path('data') / 'votable_fields_table.txt'), - str(Path('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
-
-
- From 198cdeb3ef7471670e317967767e44e69397a34c Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Mon, 25 Mar 2024 11:10:24 +0100 Subject: [PATCH 11/28] feat: add bibcodelist to possible outputs --- astroquery/simbad/data/query_criteria_fields.json | 5 +++++ astroquery/simbad/tests/test_simbad.py | 2 +- astroquery/simbad/utils.py | 7 ++++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/astroquery/simbad/data/query_criteria_fields.json b/astroquery/simbad/data/query_criteria_fields.json index 3140dc4af4..c62b5b1a91 100644 --- a/astroquery/simbad/data/query_criteria_fields.json +++ b/astroquery/simbad/data/query_criteria_fields.json @@ -4,6 +4,11 @@ "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" diff --git a/astroquery/simbad/tests/test_simbad.py b/astroquery/simbad/tests/test_simbad.py index 479d4bac3c..72f6f34ebc 100644 --- a/astroquery/simbad/tests/test_simbad.py +++ b/astroquery/simbad/tests/test_simbad.py @@ -164,7 +164,7 @@ def test_add_to_output(): simbad_instance.add_to_output("coo(s)", "dec(d)") with pytest.raises(ValueError, match="Catalog Ids are no longer supported as an output option.*"): simbad_instance.add_to_output("ID(Gaia)") - with pytest.raises(ValueError, match="Selecting a range of years for bibcode is now a criteria.*"): + with pytest.raises(ValueError, match="Selecting a range of years for bibcode is removed.*"): simbad_instance.add_to_output("bibcodelist(2042-2050)") # historical measurements with pytest.raises(ValueError, match="'einstein' is no longer a part of SIMBAD.*"): diff --git a/astroquery/simbad/utils.py b/astroquery/simbad/utils.py index c2081270a3..876fd4a9e5 100644 --- a/astroquery/simbad/utils.py +++ b/astroquery/simbad/utils.py @@ -57,9 +57,10 @@ def _catch_deprecated_fields_with_arguments(votable_field): "See section on catalogs in " "https://astroquery.readthedocs.io/en/latest/simbad/simbad_evolution.html") if votable_field.startswith("bibcodelist("): - raise ValueError("Selecting a range of years for bibcode is now a criteria. " - "See section on bibcodelist in" - "https://astroquery.readthedocs.io/en/latest/simbad/simbad_evolution.html") + 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. " + "See https://astroquery.readthedocs.io/en/latest/simbad/simbad_evolution.html for " + "more details.") # ---------------------------- # To support wildcard argument From 6088f2553ee5c2983f1ad4d98579cdaf4cda740a Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Mon, 25 Mar 2024 16:57:26 +0100 Subject: [PATCH 12/28] test: complete coverage of simbad utils --- astroquery/simbad/tests/test_utils.py | 49 ++++++++++++++++++++++++--- astroquery/simbad/utils.py | 48 +++++++++++++------------- docs/simbad/simbad.rst | 8 ++--- 3 files changed, 72 insertions(+), 33 deletions(-) diff --git a/astroquery/simbad/tests/test_utils.py b/astroquery/simbad/tests/test_utils.py index 0bf7dde9d4..0b3e4ad7d5 100644 --- a/astroquery/simbad/tests/test_utils.py +++ b/astroquery/simbad/tests/test_utils.py @@ -1,29 +1,53 @@ import pytest +from astroquery.simbad.setup_package import get_package_data + from astroquery.simbad.utils import (CriteriaTranslator, _parse_coordinate_and_convert_to_icrs, - _region_to_contains) + _region_to_contains, list_wildcards, _wildcard_to_regexp) from astropy.coordinates.builtin_frames.icrs import ICRS +from astropy.coordinates import SkyCoord + + +def test_setup_package(): + data = get_package_data() + assert data["astroquery.simbad.tests"] == ["data/simbad_output_options.xml"] + assert data["astroquery.simbad"] == ["data/query_criteria_fields.json"] @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", "B1950") + ("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_list_wildcards(capsys): + list_wildcards() + wildcards = capsys.readouterr() + assert "*: Any string of characters (including an empty one)" in wildcards.out + + +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(): +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") @@ -32,10 +56,17 @@ def test_region_to_contains(): _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, 0 0, 2d 1.5m") == box + 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(): @@ -70,9 +101,17 @@ def test_tokenizer(): " AND otype = 'G' AND (nbref >= 10 OR bibyear >= 2000)")), ("otype != 'Galaxy..'", "otype != 'Galaxy..'"), ("author ∼ 'egret*'", "regexp(author, '^egret.*$') = 1"), - ("cat in ('hd','hip','ppm')", "cat IN ('hd','hip','ppm')") + ("cat in ('hd','hip','ppm')", "cat IN ('hd','hip','ppm')"), + ("author !~ 'test'", "regexp(author, '^test$') = 0") ]) # these are the examples from http://simbad.cds.unistra.fr/guide/sim-fsam.htx 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 index 876fd4a9e5..8bcec086db 100644 --- a/astroquery/simbad/utils.py +++ b/astroquery/simbad/utils.py @@ -8,28 +8,6 @@ from astropy.utils import classproperty -def list_wildcards(): - """ - Displays the available wildcards that may be used in SIMBAD queries and - their usage. - - Examples - -------- - >>> from astroquery.simbad.utils import list_wildcards - >>> 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. - """ - 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())) - - def _catch_deprecated_fields_with_arguments(votable_field): """Raise informative errors for deprecated votable fields. @@ -67,6 +45,28 @@ def _catch_deprecated_fields_with_arguments(votable_field): # ---------------------------- +def list_wildcards(): + """ + Displays the available wildcards that may be used in SIMBAD queries and + their usage. + + Examples + -------- + >>> from astroquery.simbad.utils import list_wildcards + >>> 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. + """ + 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())) + + def _wildcard_to_regexp(wildcard_string): r"""Translate a wildcard string into a regexp. @@ -186,8 +186,6 @@ def _region_to_contains(region_string): contains += f", {coordinates.ra.value}, {coordinates.dec.value}" contains += ")) = 1" - else: - raise ValueError("Simbad TAP supports regions of types 'circle', 'box', or 'polygon'.") return contains @@ -195,6 +193,8 @@ def _parse_coordinate_and_convert_to_icrs(string_coordinate, *, frame="icrs", epoch=None, equinox=None): """Convert a string into a SkyCoord object in the ICRS frame.""" 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) diff --git a/docs/simbad/simbad.rst b/docs/simbad/simbad.rst index b23940e2e1..24672e3f60 100644 --- a/docs/simbad/simbad.rst +++ b/docs/simbad/simbad.rst @@ -110,10 +110,10 @@ To see the available wildcards and their functions: >>> from astroquery.simbad.utils import list_wildcards >>> 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. + *: 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) of an object ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From a7c07b416c0ed4298885512f00fba6f0dbd28004 Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Wed, 27 Mar 2024 16:39:56 +0100 Subject: [PATCH 13/28] tests: improve coverage for the tests without remote access --- astroquery/simbad/core.py | 9 +- astroquery/simbad/setup_package.py | 4 +- .../tests/data/simbad_basic_columns.xml | 579 ++++++++++++++++++ .../tests/data/simbad_linked_to_basic.xml | 177 ++++++ .../tests/data/simbad_output_options.xml | 89 +-- astroquery/simbad/tests/test_simbad.py | 227 +++++-- astroquery/simbad/tests/test_simbad_remote.py | 2 +- astroquery/simbad/tests/test_utils.py | 8 - docs/simbad/simbad.rst | 50 +- docs/simbad/simbad_evolution.rst | 15 +- 10 files changed, 1010 insertions(+), 150 deletions(-) create mode 100644 astroquery/simbad/tests/data/simbad_basic_columns.xml create mode 100644 astroquery/simbad/tests/data/simbad_linked_to_basic.xml diff --git a/astroquery/simbad/core.py b/astroquery/simbad/core.py index 2afbbf9547..0578578f03 100644 --- a/astroquery/simbad/core.py +++ b/astroquery/simbad/core.py @@ -89,14 +89,14 @@ class SimbadClass(BaseVOQuery): SIMBAD_URL = 'https://' + conf.server + '/simbad/sim-script' ROW_LIMIT = conf.row_limit - @dataclass + @dataclass(frozen=True) class Column: """A class to define a column in a SIMBAD query.""" table: str name: str alias: str = field(default=None) - @dataclass + @dataclass(frozen=True) class Join: """A class to define a join between two tables.""" table: str @@ -199,11 +199,12 @@ def list_output_options(self): velocity ... """ # get the tables with a simple link to basic - query_tables = """SELECT table_name AS name, tables.description + 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 target_table = 'basic' + AND (from_table = 'basic' OR target_table = 'basic') AND from_table != 'h_link' """ tables = self.query_tap(query_tables) diff --git a/astroquery/simbad/setup_package.py b/astroquery/simbad/setup_package.py index ba92842b61..1f63c9662f 100644 --- a/astroquery/simbad/setup_package.py +++ b/astroquery/simbad/setup_package.py @@ -4,7 +4,9 @@ def get_package_data(): - paths_test = [str(Path('data') / 'simbad_output_options.xml')] + 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 = [str(Path('data') / 'query_criteria_fields.json')] 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 index 459e88015c..45ac029e43 100644 --- a/astroquery/simbad/tests/data/simbad_output_options.xml +++ b/astroquery/simbad/tests/data/simbad_output_options.xml @@ -3,7 +3,7 @@ http://www.astropy.org/ --> - +
column name @@ -18,23 +18,23 @@ - - + + - - + + - - + + - - + + @@ -43,63 +43,68 @@ - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + @@ -108,23 +113,23 @@ - - + + - - + + - - + + - - + + @@ -469,7 +474,7 @@ - + diff --git a/astroquery/simbad/tests/test_simbad.py b/astroquery/simbad/tests/test_simbad.py index 72f6f34ebc..d880226714 100644 --- a/astroquery/simbad/tests/test_simbad.py +++ b/astroquery/simbad/tests/test_simbad.py @@ -1,17 +1,18 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -import os from pathlib import Path import re from astropy.coordinates import SkyCoord from astropy.io.votable import parse_single_table +from astropy.table import Table import astropy.units as u +from pyvo.dal.tap import TAPService import pytest from ... import simbad from .test_simbad_remote import multicoords -from astroquery.utils.mocks import MockResponse +from astroquery.exceptions import LargeQueryWarning GALACTIC_COORDS = SkyCoord(l=-67.02084 * u.deg, b=-29.75447 * u.deg, frame="galactic") @@ -19,69 +20,62 @@ 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 = { - 'sample': 'query_sample.data', - 'region': 'query_sample_region.data', -} +@pytest.fixture() +def _mock_simbad_class(monkeypatch): + """Avoid a TAP request for properties in the tests.""" -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 - - -def data_path(filename): - data_dir = os.path.join(os.path.dirname(__file__), 'data') - return os.path.join(data_dir, filename) - - -@pytest.fixture -def patch_post(request): - mp = request.getfixturevalue("monkeypatch") - - mp.setattr(simbad.SimbadClass, '_request', post_mockreturn) - - return mp + 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_output_options() + # >>> options.write("simbad_output_options.xml", format="votable") + monkeypatch.setattr(simbad.SimbadClass, "hardlimit", 2000000) + monkeypatch.setattr(simbad.SimbadClass, "list_output_options", lambda self: table) -def post_mockreturn(self, method, url, data, timeout, **kwargs): - response = MockResponseSimbad(data['script'], **kwargs) +@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") - class last_query: - pass + 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_to_output + if table_name == "mesdistance": + return Table( + [["bibcode"]], names=["column_name"] + ) + return simbad.SimbadClass().list_columns(table_name) - self._last_query = last_query() - self._last_query.data = data - return response + monkeypatch.setattr(simbad.SimbadClass, "list_columns", _mock_list_columns) @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: +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 - # >>> options = Simbad.list_output_options() - # >>> options.write("simbad_output_options.xml", format="votable") + # >>> linked = Simbad.list_linked_tables("basic") + # >>> linked.write("simbad_linked_to_basic.xml", format="votable") - def mock_output_options(self): - return table - monkeypatch.setattr(simbad.SimbadClass, "hardlimit", 2000000) - monkeypatch.setattr(simbad.SimbadClass, "list_output_options", mock_output_options) + 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 test_adql_parameter(): @@ -116,6 +110,12 @@ def test_simbad_create_tap_service(): assert 'simbad/sim-tap' in simbadtap.baseurl +def test_simbad_hardlimit(monkeypatch): + simbad_instance = simbad.Simbad() + monkeypatch.setattr(TAPService, "hardlimit", 2) + assert simbad_instance.hardlimit == 2 + + def test_init_columns_in_output(): simbad_instance = simbad.Simbad() default_columns = simbad_instance.columns_in_output @@ -139,7 +139,53 @@ def test_mocked_simbad(): # ---------------------------- +@pytest.mark.usefixtures("_mock_basic_columns") +def test_list_output_options(monkeypatch): + monkeypatch.setattr(simbad.SimbadClass, "query_tap", + lambda self, _: Table([["biblio"], ["biblio description"]], + names=["name", "description"], + dtype=["object", "object"])) + options = simbad.SimbadClass().list_output_options() + assert set(options.group_by("type").groups.keys["type"]) == {"table", + "column of basic", + "bundle of basic columns"} + + +@pytest.mark.usefixtures("_mock_basic_columns") +@pytest.mark.parametrize(("bundle_name", "column"), + [("coordinates", simbad.SimbadClass.Column("basic", "ra")), + ("coordinates", simbad.SimbadClass.Column("basic", "coo_bibcode")), + ("dim", simbad.SimbadClass.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.SimbadClass.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.SimbadClass.Join("mesdiameter", + simbad.SimbadClass.Column("basic", "oid"), + simbad.SimbadClass.Column("mesdiameter", "oidref") + ) in simbad_instance.joins + assert simbad.SimbadClass.Column("mesdiameter", "bibcode", '"mesdiameter.bibcode"' + ) in simbad_instance.columns_in_output + assert simbad.SimbadClass.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_to_output(): simbad_instance = simbad.Simbad() # add columns from basic (one value) @@ -150,6 +196,13 @@ def test_add_to_output(): expected = [simbad.SimbadClass.Column("basic", "pmdec"), simbad.SimbadClass.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_to_output("basic") + assert [simbad.SimbadClass.Column("basic", "*")] == simbad_instance.columns_in_output + # add a bundle + simbad_instance.add_to_output("dimensions") + assert simbad.SimbadClass.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 = [] @@ -157,6 +210,9 @@ def test_add_to_output(): "appearing with its new name in the output table"): simbad_instance.add_to_output("id(1)") assert simbad.SimbadClass.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_to_output("distance") # errors are raised for the deprecated fields with options with pytest.raises(ValueError, match="Criteria on filters are deprecated when defining Simbad's output.*"): simbad_instance.add_to_output("fluxdata(V)") @@ -192,6 +248,13 @@ def test_query_bibcode_class(): 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_adql=True) + 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") @@ -224,30 +287,59 @@ def test_query_catalog(): assert adql.endswith(where_clause) -@pytest.mark.parametrize(('coordinates', 'radius'), - [(ICRS_COORDS, 2*u.arcmin), - (GALACTIC_COORDS, 5 * u.deg), - (FK4_COORDS, '5d0m0s'), - (FK5_COORDS, 2*u.arcmin), - (multicoords, 0.5*u.arcsec), - (multicoords, "0.5s"), +@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 \)"), ]) @pytest.mark.usefixtures("_mock_simbad_class") -def test_query_region(coordinates, radius): - # looks like this also tests class as Simbad or Simbad() +def test_query_region(coordinates, radius, where): adql = simbad.core.Simbad.query_region(coordinates, radius=radius, get_adql=True) adql_2 = simbad.core.Simbad().query_region(coordinates, radius=radius, get_adql=True) 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_adql=True) + 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(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_adql=True) @pytest.mark.usefixtures("_mock_simbad_class") @@ -277,6 +369,10 @@ def test_query_object(): adql = simbad.core.Simbad.query_object("m [0-9]", wildcard=True, get_adql=True) 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_adql=True) + end = "AND (otype = 'G..')" + assert adql.endswith(end) # ------------------------- # Test query_tap exceptions @@ -293,6 +389,13 @@ def test_query_tap_errors(): 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 + + # --------------------------------------------------- # Test the adql string for query_tap helper functions # --------------------------------------------------- diff --git a/astroquery/simbad/tests/test_simbad_remote.py b/astroquery/simbad/tests/test_simbad_remote.py index 6b6e0c119a..0f21524d96 100644 --- a/astroquery/simbad/tests/test_simbad_remote.py +++ b/astroquery/simbad/tests/test_simbad_remote.py @@ -62,7 +62,7 @@ def test_query_regions(self): 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"] == list(result["main_id"].data.data) + assert {"M 81", "M 10"} == set(result["main_id"].data.data) def test_query_object_ids(self): self.simbad.ROW_LIMIT = -1 diff --git a/astroquery/simbad/tests/test_utils.py b/astroquery/simbad/tests/test_utils.py index 0b3e4ad7d5..1857037aeb 100644 --- a/astroquery/simbad/tests/test_utils.py +++ b/astroquery/simbad/tests/test_utils.py @@ -1,8 +1,6 @@ import pytest -from astroquery.simbad.setup_package import get_package_data - from astroquery.simbad.utils import (CriteriaTranslator, _parse_coordinate_and_convert_to_icrs, _region_to_contains, list_wildcards, _wildcard_to_regexp) @@ -10,12 +8,6 @@ from astropy.coordinates import SkyCoord -def test_setup_package(): - data = get_package_data() - assert data["astroquery.simbad.tests"] == ["data/simbad_output_options.xml"] - assert data["astroquery.simbad"] == ["data/query_criteria_fields.json"] - - @pytest.mark.parametrize("coord_string, frame, epoch, equinox", [ ("12 34 56.78 +12 34 56.78", None, None, None), ("10 +20", "galactic", None, None), diff --git a/docs/simbad/simbad.rst b/docs/simbad/simbad.rst index 24672e3f60..40c5d6902c 100644 --- a/docs/simbad/simbad.rst +++ b/docs/simbad/simbad.rst @@ -456,31 +456,31 @@ The list of possible options is printed with: >>> from astroquery.simbad import Simbad >>> Simbad.list_output_options()[["name", "description"]] -
idsall names concatenated with pipemesDiameterCollection of stellar diameters. table
otypedefall names and definitions for the object typesmesPMCollection of proper motions. table
identIdentifiers of an astronomical objectmesISOInfrared Space Observatory (ISO) observing log. table
fluxMagnitude/Flux information about an astronomical objectmesSpTCollection of spectral types. table
table
has_refAssociations between astronomical objects and their bibliographic referencesidentIdentifiers of an astronomical object table
mesDistanceCollection of distances (pc, kpc or Mpc) by several means.fluxMagnitude/Flux information about an astronomical object table
mesDiameterCollection of stellar diameters.mesPLXCollection of trigonometric parallaxes. table
mesFe_hCollection of metallicity, as well as Teff, logg for stars.otypedefall names and definitions for the object types table
mesISOInfrared Space Observatory (ISO) observing log.mesDistanceCollection of distances (pc, kpc or Mpc) by several means. table
mesIUEInternational Ultraviolet Explorer observing log.otypesList of all object types associated with an object table
mesPLXCollection of trigonometric parallaxes.mesVarCollection of stellar variability types and periods. table
mesPMCollection of proper motions.mesXmmXMM observing log. table
mesRotStellar Rotational Velocities.mesVelocitiesCollection of HRV, Vlsr, cz and redshifts. table
mesVarCollection of stellar variability types and periods.has_refAssociations between astronomical objects and their bibliographic references table
mesVelocitiesCollection of HRV, Vlsr, cz and redshifts.mesRotStellar Rotational Velocities. table
mesXmmXMM observing log.biblioBibliographytable
idsall names concatenated with pipe table
table
biblioBibliographymesIUEInternational Ultraviolet Explorer observing log. table
alltypesall object types concatenated with pipemesFe_hCollection of metallicity, as well as Teff, logg for stars. table
otypesList of all object types associated with an objectalltypesall object types concatenated with pipe table
mesSpTCollection of spectral types.basicGeneral data about an astronomical object table
dimmain fields related to object dimensions: major and minor axis, angle and inclinationmajor and minor axis, angle and inclination bundle of basic columns
- name ... - object ... - --------------- ... - ids ... - otypedef ... - ident ... - flux ... - allfluxes ... - has_ref ... - mesDistance ... - mesDiameter ... - mesFe_h ... - mesISO ... - ... ... - vlsr_min ... - vlsr_wavelength ... - coordinates ... - dim ... - dimensions ... - morphtype ... - parallax ... - propermotions ... - sp ... - velocity ... +
+ 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 + mesDistance Collection of distances (pc, kpc or Mpc) by several means. + ... ... + 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 Additional criteria ------------------- diff --git a/docs/simbad/simbad_evolution.rst b/docs/simbad/simbad_evolution.rst index e057f93d7c..6af2796fb0 100644 --- a/docs/simbad/simbad_evolution.rst +++ b/docs/simbad/simbad_evolution.rst @@ -70,22 +70,23 @@ See for example: >>> # we add the main type and all the types that have historically been attributed to the object >>> simbad.add_to_output("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 + main_id catalog_id otype otypes object object object object --------- ---------- ------ ---------------------------------------- - M 27 M 27 PN *|G|HS?|IR|PN|UV|WD*|WD?|X|blu + 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 78 M 78 RNe C?*|Cl*|ISM|RNe - NGC 6994 M 73 err Cl*|err - M 43 M 43 HII HII|IR|Rad M 42 M 42 HII C?*|Cl*|HII|OpC|Rad|X - M 1 M 1 SNR HII|IR|Rad|SNR|X|gam + 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 - M 57 M 57 PN *|HS?|IR|PN|Rad|WD*|WD?|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 From 650595f5840f55a4f88b34ecbc7bf961d900fcbd Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Thu, 28 Mar 2024 17:14:12 +0100 Subject: [PATCH 14/28] docs: update simbad's doc --- astroquery/simbad/__init__.py | 12 +- docs/simbad/query_tap.rst | 34 ++--- docs/simbad/simbad.rst | 238 +++++++++++++++++-------------- docs/simbad/simbad_evolution.rst | 134 +++++++++++------ 4 files changed, 238 insertions(+), 180 deletions(-) diff --git a/astroquery/simbad/__init__.py b/astroquery/simbad/__init__.py index ffb79d6d90..bb8fead6a2 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 @@ -40,7 +39,4 @@ class Conf(_config.ConfigNamespace): from .core import Simbad, SimbadClass -__all__ = ['Simbad', 'SimbadClass', - 'SimbadBaseQuery', - 'Conf', 'conf', - ] +__all__ = ['Simbad', 'SimbadClass', 'Conf', 'conf'] diff --git a/docs/simbad/query_tap.rst b/docs/simbad/query_tap.rst index f32f293950..98c3710392 100644 --- a/docs/simbad/query_tap.rst +++ b/docs/simbad/query_tap.rst @@ -31,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:: @@ -44,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 @@ -71,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 @@ -139,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 @@ -202,7 +202,7 @@ that is the measurement table for rotations. Their common column is ``oidref``. >>> Simbad.query_tap(query)
Rotation Measurements Bibcodes - object + object ------------------------------ 2016A&A...589A..83G 2002A&A...393..897R @@ -223,14 +223,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 @@ -244,7 +244,7 @@ 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 @@ -267,8 +267,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 @@ -278,8 +278,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: @@ -307,10 +307,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 40c5d6902c..13dd353eb2 100644 --- a/docs/simbad/simbad.rst +++ b/docs/simbad/simbad.rst @@ -17,17 +17,18 @@ 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. 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. 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. +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. -About deprecation warnings --------------------------- +Simbad Evolutions +----------------- -The SIMBAD module has been rewritten with astroquery > 0.4.7. If you're here following a -deprecation warning, this is your go-to page: +The SIMBAD module follows evolutions of the SIMBAD database. +Some of these changes are documented into more details here: .. toctree:: :maxdepth: 2 @@ -37,10 +38,10 @@ deprecation warning, this is your go-to page: Different ways to access SIMBAD ------------------------------- -The SIMBAD module described here provides methods that write pre-defined ADQL queries. These +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 with your own ADQL query via +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 :ref:`query TAP `. @@ -66,11 +67,11 @@ the messier object M1: ------- ------- ------- ... -------------- ------------------- ---------- M 1 83.6287 22.0147 ... R 1995AuJPh..48..143S M 1 -Wildcards are supported, but they render the query case-sensitive. So for instance to query messier -objects from 1 through 9: +`Wildcards`_ are supported. Note that this makes the query case-sensitive. +This allows, for instance, to query messier objects from 1 through 9: .. - The following example is very slow ~6s due to a current (early 2024) bug in + 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. @@ -93,18 +94,22 @@ objects from 1 through 9: M 5 229.63841666666673 ... 2010AJ....140.1830G M 5 -We can see that the messier objects are indeed found. Their ``main_id`` is not necessarly -the one corresponding to the wildcard expression. The column ``matched_id`` will return which -identifier was matched. The wildcard parameter can often be replaced by a way faster query -done with `~astroquery.simbad.SimbadClass.query_objects`. +The messier objects from 1 to 9 are found. Their ``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 by: +Wildcards are supported in these methods: - `~astroquery.simbad.SimbadClass.query_object` - `~astroquery.simbad.SimbadClass.query_objects` - `~astroquery.simbad.SimbadClass.query_bibcode` -The wildcards that are supported and their usage across all these queries is the same. -To see the available wildcards and their functions: +To see the available wildcards and their meaning: .. code-block:: python @@ -122,13 +127,13 @@ These queries can be used to retrieve all of the names (identifiers) associated with an object. .. - This could change often (each time someone invents a new name for Polaris). + 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) # doctest: +IGNORE_OUTPUT + >>> result_table
id object @@ -155,9 +160,13 @@ Query a region 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`. +.. + This output will also change often. + .. doctest-remote-data:: >>> from astroquery.simbad import Simbad + >>> # 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") @@ -181,41 +190,16 @@ explicitly specified it can be either a string accepted by `~astropy.coordinates.Angle` (ex: ``radius='0d6m0s'``)or directly a `~astropy.units.Quantity` object. -If coordinates are used, then they should be entered using an `astropy.coordinates.SkyCoord` -object. - -.. doctest-remote-data:: - - >>> from astroquery.simbad import Simbad - >>> from astropy.coordinates import SkyCoord - >>> simbad = Simbad() - >>> simbad.ROW_LIMIT = 10 - >>> coordinate = SkyCoord("05h35m17.3s -05h23m28s", frame='icrs') - >>> simbad.query_region(coordinate, radius='1d0m0s') # doctest: +IGNORE_OUTPUT -
- main_id ra dec ... coo_err_angle coo_wavelength coo_bibcode - deg deg ... deg - object float64 float64 ... int16 str1 object - ---------------------------- ----------------- ------------------ ... ------------- -------------- ------------------- - TYC 9390-1857-1 89.18327041666667 -81.31254972222223 ... 71 O 2016A&A...595A...2G - PKS 0602-813 89.37791666666668 -81.37027777777777 ... -- - TYC 9390-1786-1 89.14477451271 -81.41309658604 ... 90 O 2020yCat.1350....0G - TYC 9390-1878-1 88.75276112064 -81.44513030144 ... 90 O 2020yCat.1350....0G - Gaia DR3 4621434189736118144 89.2148880241 -81.42047989066 ... 90 O 2020yCat.1350....0G - Gaia DR3 4621443844823809536 89.77428052292792 -81.21112425878056 ... 90 O 2020yCat.1350....0G - CD-81 190 89.17676879332167 -81.38640963209807 ... 90 O 2020yCat.1350....0G - PKS J0557-8122 89.3624 -81.3742 ... 0 R 2012MNRAS.422.1527M - UCAC4 044-003417 89.50051776702 -81.21953460424 ... 90 O 2020yCat.1350....0G - IRAS F05246-8120 80.00253626975339 -81.29356823821976 ... 100 F 1990IRASF.C......0M - +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 >>> from astropy.coordinates import SkyCoord >>> import astropy.units as u - >>> Simbad.query_region(SkyCoord(31.0087, 14.0627, unit=(u.deg, u.deg), frame='galactic'), - ... radius=2 * u.arcsec) + >>> 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 ... @@ -224,9 +208,11 @@ object. GJ 699 b 269.4520769586187 ... O 2020yCat.1350....0G NAME Barnard's star 269.4520769586187 ... O 2020yCat.1350....0G -Calling `~astroquery.simbad.SimbadClass.query_region` within a loop is *very* -inefficient. If you need to query many regions, use a `~astropy.coordinates.SkyCoord` -with a list of centers and a list of radii. It looks like this: +.. 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: .. doctest-remote-data:: @@ -248,18 +234,20 @@ with a list of centers and a list of radii. It looks like this: 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: .. doctest-remote-data:: >>> from astroquery.simbad import Simbad - >>> limitedSimbad = Simbad() - >>> limitedSimbad.ROW_LIMIT = 6 - >>> limitedSimbad.query_catalog('ESO') + >>> simbad = Simbad(ROW_LIMIT=6) + >>> simbad.query_catalog('ESO')
main_id ra ... coo_bibcode catalog_id deg ... @@ -272,16 +260,23 @@ instance to query the ESO catalog: ESO 1-5 133.2708583333333 ... 2006AJ....131.1163S ESO 1-5 ESO 1-6 216.83122280179 ... 2020yCat.1350....0G ESO 1-6 -To see the available catalogues, you can write a custom ADQL query on the ``cat`` table. +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:: >>> from astroquery.simbad import Simbad - >>> Simbad.query_tap('SELECT TOP 10 cat_name, description FROM cat ORDER BY "size" DESC') + >>> Simbad.query_tap('SELECT TOP 10 cat_name, description FROM cat ORDER BY "size" DESC') # doctest: +IGNORE_OUTPUT
cat_name description - object object + object object -------- ---------------------------------------------------------------- Gaia Gaia 2MASS 2 Micron Sky Survey, Point Sources @@ -290,10 +285,11 @@ For example to get the 10 biggest catalogs in SIMBAD, it looks like this: TYC Tycho mission OGLE Optical Gravitational Lensing Event UCAC4 Fourth USNO CCD Astrograph Catalog - GSC Guide Star Catalogue 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 --------------------- @@ -301,7 +297,7 @@ Bibliographic queries Query a bibcode ^^^^^^^^^^^^^^^ -This retrieves the reference corresponding to a bibcode. +This retrieves information about the article corresponding to a bibcode. .. doctest-remote-data:: @@ -313,31 +309,33 @@ This retrieves the reference corresponding to a bibcode. ------------------- -------------------------- ------- ... ------ ----- 2005A&A...430..165F 10.1051/0004-6361:20041272 A&A ... 430 2005 -The abstract of the article can also be added as an other column in the output by setting -the ``abstract`` parameter to ``True``. +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: .. doctest-remote-data:: >>> from astroquery.simbad import Simbad - >>> Simbad.query_bibcode('2013A&ARv.*', wildcard=True) # doctest: +IGNORE_OUTPUT + >>> 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...62D 10.1007/s00159-013-0062-7 A&ARv ... 21 2013 2013A&ARv..21...59I 10.1007/s00159-013-0059-2 A&ARv ... 21 2013 - 2013A&ARv..21...70B 10.1007/s00159-013-0070-7 A&ARv ... 21 2013 - 2013A&ARv..21...69R 10.1007/s00159-013-0069-0 A&ARv ... 21 2013 2013A&ARv..21...61R 10.1007/s00159-013-0061-8 A&ARv ... 21 2013 - 2013A&ARv..21...64D 10.1007/s00159-013-0064-5 A&ARv ... 21 2013 - 2013A&ARv..21...68G 10.1007/s00159-013-0068-1 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 -To look for articles published between 2010 and 2012 with a given keyword: +or to look for articles published between 2010 and 2012 with a given keyword: .. doctest-remote-data:: @@ -357,15 +355,15 @@ To look for articles published between 2010 and 2012 with a given keyword: 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 +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: +These queries can be used to retrieve all the objects that are discussed in the +article specified by a bibcode: .. doctest-remote-data:: @@ -388,16 +386,15 @@ article specified by the bibcode: 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 -1. 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 @@ -409,6 +406,7 @@ modifying the setting in the Astroquery configuration file. .. Note:: + This works with every ``query_***`` method, but `~astroquery.simbad.SimbadClass.query_tap` is an exception as the number of returned rows is fixed in the ADQL string with the ``TOP`` instruction. @@ -417,18 +415,17 @@ Choosing the columns in the output tables .. Warning:: - Before astroquery v0.4.7, this was done with ``votable_fields``. This is not + Before astroquery v0.4.8, this was done with ``votable_fields``. This is not the case anymore. See :ref:`SIMBAD evolutions `. Some query methods outputs can be customized. This is the case for: -- `~astroquery.simbad.DeprecatedSimbadClass.query_criteria` - `~astroquery.simbad.SimbadClass.query_object` - `~astroquery.simbad.SimbadClass.query_objects` - `~astroquery.simbad.SimbadClass.query_region` - `~astroquery.simbad.SimbadClass.query_bibobj` -Their default columns are: +For these methods, the default columns in the output are: - main_id - ra @@ -437,20 +434,32 @@ Their default columns are: - coo_err_min - coo_err_angle - coo_wavelength -- coo_bibcode' +- coo_bibcode + +.. Note:: + + The columns that will appear in the output are in the + `~astroquery.simbad.SimbadClass.columns_in_output` attribute + + .. code-block:: python -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_to_output`: + >>> from astroquery.simbad import Simbad + >>> simbad = Simbad() + >>> simbad.columns_in_output[0] + SimbadClass.Column(table='basic', name='main_id', alias=None) + +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_to_output`: .. doctest-remote-data:: >>> from astroquery.simbad import Simbad >>> simbad = Simbad() - >>> simbad.add_to_output("otype") # here we add a single column about object type + >>> simbad.add_to_output("otype") # here we add a single column about the main object type -Some options add a single column and others add columns that are relevant for a theme (ex: fluxes, -proper motions...). -The list of possible options is printed with: +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: .. doctest-remote-data:: @@ -487,10 +496,11 @@ Additional criteria .. Warning:: - Before astroquery v0.4.7, this was only possible within `~astroquery.simbad.DeprecatedSimbadClass.query_criteria`. This is not - the case anymore, and a lot of query methods now admit criteria strings. See :ref:`SIMBAD evolutions `. + Before astroquery v0.4.8, criteria could only be used with the method ``query_criteria``. + This method does not exist anymore and is replaced by the criteria argument in every + other methods. See :ref:`SIMBAD evolutions `. -Some query methods take a ``criteria`` argument. They are listed here: +Most query methods take a ``criteria`` argument. They are listed here: - `~astroquery.simbad.SimbadClass.query_object` - `~astroquery.simbad.SimbadClass.query_objects` @@ -500,9 +510,13 @@ Some query methods take a ``criteria`` argument. They are listed here: - `~astroquery.simbad.SimbadClass.query_bibcode` - `~astroquery.simbad.SimbadClass.query_objectids` -A the criteria argument expect a string that fits in the ``WHERE`` clause of an ADQL query. Some examples can -be found in the `Simbad ADQL cheat sheet `__. A way -of writing them is to first query a blank table to inspect the columns the method will return: +The criteria argument expect a string that fits in the ``WHERE`` clause of an ADQL query. +Some examples can be found in the +`Simbad ADQL cheat sheet `__. + +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: .. doctest-remote-data:: @@ -538,9 +552,12 @@ of writing them is to first query a blank table to inspect the columns the metho mespm.pmra_prec int16 Precision (# of decimal positions) associated with the column pmra matched_id object Identifier -With the information on the columns that will be returned by the query, it is now possible to write a criteria. -For example, to get only proper motions measurements more recent than 2000, we can add a constraint on the -first character of the bibcode (the first 4 digits of a bibcode are the year of publication of the article): +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:: @@ -570,12 +587,11 @@ 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 @@ -585,11 +601,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 index 6af2796fb0..c264602ef8 100644 --- a/docs/simbad/simbad_evolution.rst +++ b/docs/simbad/simbad_evolution.rst @@ -10,30 +10,25 @@ Votable fields and Output options Votable fields are deprecated in favor of output options. Most of the former votable fields can now be added to the output of Simbad queries with -`~astroquery.simbad.SimbadClass.add_to_output`. The columns and tables that have a new name -under the TAP interface will be recognized by `~astroquery.simbad.SimbadClass.add_to_output`. -Some Votable Fields that supported options in parenthesis are no -longer supported. These can be replaced by criteria in query_methods or by a custom ADQL -query called with `~astroquery.simbad.SimbadClass.query_tap`. The documentation and former -issues on astroquery's repository contain examples, but don't hesitate to open a new issue -if there is some missing information. +`~astroquery.simbad.SimbadClass.add_to_output`. The full list of options is available +with the `~astroquery.simbad.SimbadClass.list_output_options` method. + +Some columns and tables have a new name under the TAP interface. The old name will be +recognized by `~astroquery.simbad.SimbadClass.add_to_output`, but only the new name will +appear in the output. + +A few ``votable_fields`` had options in parenthesis. This is no longer supported and can +be replaced by the ``criteria`` argument in ``query_***`` methods or by a custom ADQL +query called with `~astroquery.simbad.SimbadClass.query_tap`. The documentation and +former issues on astroquery's repository contain examples, but don't hesitate to open +a new issue if there is some missing information. **************************************** Translating query_criteria into criteria **************************************** -The method `~astroquery.simbad.DeprecatedSimbadClass.query_criteria` is now deprecated in SIMBAD. -It is still possible to use it from astroquery for now, but any existing bug will not -be fixed. There are also a number of missing features. -This page shows how the former functionalities of `~astroquery.simbad.DeprecatedSimbadClass.query_criteria` -can be replaced. - -The new interface to connect to SIMBAD is build on TAP and ADQL. -To learn more about this, you can have a look at the -`~astroquery.simbad.SimbadClass.query_tap` :ref:`documentation `. - -Since astroquery > 0.4.7, most of the ``query_***`` methods in the simbad module accept -a ``criteria`` argument, it concerns: +The method ``query_criteria`` does not exist anymore in SIMBAD, but is replaced by a +``criteria`` argument in the following methods: - `~astroquery.simbad.SimbadClass.query_object` - `~astroquery.simbad.SimbadClass.query_objects` @@ -43,9 +38,53 @@ a ``criteria`` argument, it concerns: - `~astroquery.simbad.SimbadClass.query_bibcode` - `~astroquery.simbad.SimbadClass.query_objectids` -There is a helper method to translate a criteria from -`~astroquery.simbad.DeprecatedSimbadClass.query_criteria` into a string that will work as ``criteria`` -in the other query methods cited above: +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 @@ -53,9 +92,9 @@ in the other query methods cited above: >>> CriteriaTranslator.parse("region(box, GAL, 0 +0, 3d 1d) & otype='SNR'") "CONTAINS(POINT('ICRS', ra, dec), BOX('ICRS', 266.4049882865447, -28.936177761791473, 3.0, 1.0)) = 1 AND otype = 'SNR'" -This string can then either be incorporated in a custom ADQL query called with -`~astroquery.simbad.SimbadClass.query_tap` or in any of the query methods that accept a ``criteria`` argument. -See for example: +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" @@ -73,8 +112,8 @@ See for example: >>> result.sort("catalog_id") >>> result[["main_id", "catalog_id", "otype", "otypes"]]
- main_id catalog_id otype otypes - object object object object + 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 @@ -103,9 +142,9 @@ But all the former ``otype`` assignations are also stored in the ``otypes`` colu 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 for object types can be found either in SIMBAD's -`documentation on object types `_ or with TAP queries. -To see the definition of ``PN``, one can do: +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:: @@ -119,14 +158,15 @@ To see the definition of ``PN``, one can do: 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. + +The ``label`` can also be used in a query. .. 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 + main_id otype object object ---------- ------ IC 4634 PN @@ -135,12 +175,12 @@ The label can also be used in a query. NGC 6543 PN NGC 7027 PN -And the ``path`` column 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 see with -`~astroquery.simbad.DeprecatedSimbadClass.query_criteria`, you might be interested in this -`table of correspondence `_ between old and new labels -for object types. +And the ``path`` column 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. @@ -148,9 +188,10 @@ An interesting feature brought by the hierarchy of objects is the ``..`` notatio .. 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 + >>> Simbad.query_tap("SELECT top 5 main_id, otype " + ... "FROM basic WHERE otype = 'Ev*..'") # doctest: +IGNORE_OUTPUT
- main_id otype + main_id otype object object ---------------------- ------ IRAS 07506-0345 pA* @@ -169,7 +210,8 @@ Filters 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 not +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 @@ -180,7 +222,7 @@ the case anymore, and a suggested workflow now looks like this: >>> from astroquery.simbad import Simbad >>> Simbad.query_tap("SELECT * FROM filter")
- description filtername unit + description filtername unit object object object ----------------- ---------- ------ Magnitude U U mag @@ -207,9 +249,9 @@ 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 +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 +as a first base (it selects a single object by its name), add different fields to the output with `~astroquery.simbad.SimbadClass.add_to_output` (here ``flux`` adds all columns about fluxes) and then select only the interesting filters with a ``criteria`` argument: @@ -225,8 +267,8 @@ argument: >>> 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 + 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 From 2a5eff36336087ec4cad8e0165f1290399192f4e Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Thu, 28 Mar 2024 17:14:34 +0100 Subject: [PATCH 15/28] fix: typo in vizier link --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 739f3cfed5..ffe7836d23 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -198,7 +198,7 @@ Service specific settings The Astroquery cache location is specific to individual services, so each service's cache should be managed individually. The cache location can be viewed with the following command -(using :ref:`VizieR ` as an example): +(using :ref:`VizieR ` as an example): .. code-block:: python From 95232f262049a00f53246068c4641193957efc7e Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Mon, 8 Apr 2024 16:40:42 +0200 Subject: [PATCH 16/28] feat: add ROW_LIMIT as a settable property --- astroquery/simbad/core.py | 19 +++++++++++++++++-- astroquery/simbad/tests/test_simbad.py | 17 +++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/astroquery/simbad/core.py b/astroquery/simbad/core.py index 0578578f03..6e47578977 100644 --- a/astroquery/simbad/core.py +++ b/astroquery/simbad/core.py @@ -87,7 +87,6 @@ class SimbadClass(BaseVOQuery): (https://simbad.cds.unistra.fr/guide/sim-url.htx) """ SIMBAD_URL = 'https://' + conf.server + '/simbad/sim-script' - ROW_LIMIT = conf.row_limit @dataclass(frozen=True) class Column: @@ -104,7 +103,7 @@ class Join: column_right: Any join_type: str = field(default="JOIN") - def __init__(self): + def __init__(self, ROW_LIMIT=None): super().__init__() # to create the TAPService self._server = conf.server @@ -114,6 +113,22 @@ def __init__(self): self._columns_in_output = None # a list of Simbad.Column self.joins = [] # a list of Simbad.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): diff --git a/astroquery/simbad/tests/test_simbad.py b/astroquery/simbad/tests/test_simbad.py index d880226714..88f3a30470 100644 --- a/astroquery/simbad/tests/test_simbad.py +++ b/astroquery/simbad/tests/test_simbad.py @@ -101,6 +101,23 @@ def test_simbad_mirror(): 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 From 91789f1c52680ae2184e45f3d096666ecd68d8b4 Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Tue, 16 Apr 2024 10:49:10 +0200 Subject: [PATCH 17/28] rename 'add_to_output' into 'add_output_columns' --- astroquery/simbad/core.py | 8 +++---- astroquery/simbad/tests/test_simbad.py | 24 +++++++++---------- astroquery/simbad/tests/test_simbad_remote.py | 12 +++++----- docs/simbad/simbad.rst | 8 +++---- docs/simbad/simbad_evolution.rst | 10 ++++---- 5 files changed, 31 insertions(+), 31 deletions(-) diff --git a/astroquery/simbad/core.py b/astroquery/simbad/core.py index 6e47578977..8f2aa6313e 100644 --- a/astroquery/simbad/core.py +++ b/astroquery/simbad/core.py @@ -305,7 +305,7 @@ def _add_table_to_output(self, table): self.joins += [Simbad.Join(table, Simbad.Column("basic", link["target_column"]), Simbad.Column(table, link["from_column"]))] - def add_to_output(self, *args): + def add_output_columns(self, *args): """Add columns to the output of a SIMBAD query. The list of possible arguments and their description for this method @@ -328,7 +328,7 @@ def add_to_output(self, *args): -------- >>> from astroquery.simbad import Simbad >>> simbad = Simbad() - >>> simbad.add_to_output('sp_type', 'sp_qual', 'sp_bibcode') # doctest: +REMOTE_DATA + >>> simbad.add_output_columns('sp_type', 'sp_qual', 'sp_bibcode') # doctest: +REMOTE_DATA >>> simbad.columns_in_output[0] # doctest: +REMOTE_DATA SimbadClass.Column(table='basic', name='main_id', alias=None) """ @@ -436,7 +436,7 @@ def query_object(self, object_name, *, wildcard=False, >>> from astroquery.simbad import Simbad >>> simbad = Simbad() - >>> simbad.add_to_output("dim") # doctest: +REMOTE_DATA + >>> simbad.add_output_columns("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
@@ -587,7 +587,7 @@ def query_region(self, coordinates, radius=2*u.arcmin, *, >>> from astropy.coordinates import SkyCoord >>> simbad = Simbad() >>> simbad.ROW_LIMIT = 5 - >>> simbad.add_to_output("otype") # doctest: +REMOTE_DATA + >>> simbad.add_output_columns("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", diff --git a/astroquery/simbad/tests/test_simbad.py b/astroquery/simbad/tests/test_simbad.py index 88f3a30470..7e9c0d7b23 100644 --- a/astroquery/simbad/tests/test_simbad.py +++ b/astroquery/simbad/tests/test_simbad.py @@ -49,7 +49,7 @@ 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_to_output + # to test in add_output_columns if table_name == "mesdistance": return Table( [["bibcode"]], names=["column_name"] @@ -203,49 +203,49 @@ def test_add_table_to_output(monkeypatch): @pytest.mark.usefixtures("_mock_simbad_class") @pytest.mark.usefixtures("_mock_basic_columns") @pytest.mark.usefixtures("_mock_linked_to_basic") -def test_add_to_output(): +def test_add_output_columns(): simbad_instance = simbad.Simbad() # add columns from basic (one value) - simbad_instance.add_to_output("pmra") + simbad_instance.add_output_columns("pmra") assert simbad.SimbadClass.Column("basic", "pmra") in simbad_instance.columns_in_output # add two columns from basic - simbad_instance.add_to_output("pmdec", "pm_bibcodE") # also test case insensitive + simbad_instance.add_output_columns("pmdec", "pm_bibcodE") # also test case insensitive expected = [simbad.SimbadClass.Column("basic", "pmdec"), simbad.SimbadClass.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_to_output("basic") + simbad_instance.add_output_columns("basic") assert [simbad.SimbadClass.Column("basic", "*")] == simbad_instance.columns_in_output # add a bundle - simbad_instance.add_to_output("dimensions") + simbad_instance.add_output_columns("dimensions") assert simbad.SimbadClass.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_to_output("id(1)") + simbad_instance.add_output_columns("id(1)") assert simbad.SimbadClass.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_to_output("distance") + simbad_instance.add_output_columns("distance") # errors are raised for the deprecated fields with options with pytest.raises(ValueError, match="Criteria on filters are deprecated when defining Simbad's output.*"): simbad_instance.add_to_output("fluxdata(V)") with pytest.raises(ValueError, match="Coordinates conversion and formatting is no longer supported.*"): simbad_instance.add_to_output("coo(s)", "dec(d)") with pytest.raises(ValueError, match="Catalog Ids are no longer supported as an output option.*"): - simbad_instance.add_to_output("ID(Gaia)") + simbad_instance.add_output_columns("ID(Gaia)") with pytest.raises(ValueError, match="Selecting a range of years for bibcode is removed.*"): - simbad_instance.add_to_output("bibcodelist(2042-2050)") + simbad_instance.add_output_columns("bibcodelist(2042-2050)") # historical measurements with pytest.raises(ValueError, match="'einstein' is no longer a part of SIMBAD.*"): - simbad_instance.add_to_output("einstein") + simbad_instance.add_output_columns("einstein") # typos should have suggestions with pytest.raises(ValueError, match="'alltype' is not one of the accepted options which can be " "listed with 'list_output_options'. Did you mean 'alltypes' or 'otype' or 'otypes'?"): - simbad_instance.add_to_output("ALLTYPE") + simbad_instance.add_output_columns("ALLTYPE") # bundles and tables require a connection to the tap_schema and are thus tested in test_simbad_remote diff --git a/astroquery/simbad/tests/test_simbad_remote.py b/astroquery/simbad/tests/test_simbad_remote.py index 0f21524d96..2a43ca6482 100644 --- a/astroquery/simbad/tests/test_simbad_remote.py +++ b/astroquery/simbad/tests/test_simbad_remote.py @@ -40,7 +40,7 @@ def test_non_ascii_bibcode(self): def test_query_bibobj(self): self.simbad.ROW_LIMIT = 5 - self.simbad.add_to_output("otype") + self.simbad.add_output_columns("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) @@ -82,7 +82,7 @@ def test_query_multi_object(self): def test_simbad_flux_qual(self): '''Regression test for issue 680''' simbad_instance = Simbad() - simbad_instance.add_to_output("flux") + simbad_instance.add_output_columns("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 @@ -148,7 +148,7 @@ def test_add_bundle_to_output(self): # empty before the test simbad_instance.columns_in_output = [] # add a bundle - simbad_instance.add_to_output("dim") + simbad_instance.add_output_columns("dim") # check the length assert len(simbad_instance.columns_in_output) == 8 assert Simbad.Column("basic", "galdim_majaxis") in simbad_instance.columns_in_output @@ -157,7 +157,7 @@ def test_add_table_to_output(self): simbad_instance = Simbad() # empty before the test simbad_instance.columns_in_output = [] - simbad_instance.add_to_output("otypes") + simbad_instance.add_output_columns("otypes") assert Simbad.Column("otypes", "otype", '"otypes.otype"') in simbad_instance.columns_in_output # tables also require a join assert Simbad.Join("otypes", @@ -165,9 +165,9 @@ def test_add_table_to_output(self): Simbad.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_to_output("IUE") + simbad_instance.add_output_columns("IUE") # empty before the test simbad_instance.columns_in_output = [] # mixed columns bundles and tables - simbad_instance.add_to_output("flux", "velocity", "update_date") + simbad_instance.add_output_columns("flux", "velocity", "update_date") assert len(simbad_instance.columns_in_output) == 19 diff --git a/docs/simbad/simbad.rst b/docs/simbad/simbad.rst index 13dd353eb2..60a4733cd8 100644 --- a/docs/simbad/simbad.rst +++ b/docs/simbad/simbad.rst @@ -449,13 +449,13 @@ For these methods, the default columns in the output are: SimbadClass.Column(table='basic', name='main_id', alias=None) 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_to_output`: +a session or for a single query, use `~astroquery.simbad.SimbadClass.add_output_columns`: .. doctest-remote-data:: >>> from astroquery.simbad import Simbad >>> simbad = Simbad() - >>> simbad.add_to_output("otype") # here we add a single column about the main object type + >>> simbad.add_output_columns("otype") # here we add a single column about the main object type 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 @@ -524,7 +524,7 @@ This allows to inspect the columns the method would return: >>> 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_to_output("mesPM", "otype") + >>> simbad.add_output_columns("mesPM", "otype") >>> peek = simbad.query_object("BD+30 2512") # a query on an object >>> peek.info
@@ -564,7 +564,7 @@ constraint on the first character of the ``mespm.bibcode`` column >>> from astroquery.simbad import Simbad >>> criteria = "mespm.bibcode LIKE '2%'" # starts with 2, anything after >>> simbad = Simbad() - >>> simbad.add_to_output("mesPM", "otype") + >>> simbad.add_output_columns("mesPM", "otype") >>> pm_measurements = simbad.query_object("BD+30 2512", criteria=criteria) >>> pm_measurements[["main_id", "mespm.pmra", "mespm.pmde", "mespm.bibcode"]]
diff --git a/docs/simbad/simbad_evolution.rst b/docs/simbad/simbad_evolution.rst index c264602ef8..ae153ccbea 100644 --- a/docs/simbad/simbad_evolution.rst +++ b/docs/simbad/simbad_evolution.rst @@ -10,11 +10,11 @@ Votable fields and Output options Votable fields are deprecated in favor of output options. Most of the former votable fields can now be added to the output of Simbad queries with -`~astroquery.simbad.SimbadClass.add_to_output`. The full list of options is available +`~astroquery.simbad.SimbadClass.add_output_columns`. The full list of options is available with the `~astroquery.simbad.SimbadClass.list_output_options` method. Some columns and tables have a new name under the TAP interface. The old name will be -recognized by `~astroquery.simbad.SimbadClass.add_to_output`, but only the new name will +recognized by `~astroquery.simbad.SimbadClass.add_output_columns`, but only the new name will appear in the output. A few ``votable_fields`` had options in parenthesis. This is no longer supported and can @@ -107,7 +107,7 @@ See a more elaborated example: >>> old_criteria = "otype != 'Galaxy..' & otype != 'Cl*..'" >>> simbad = Simbad() >>> # we add the main type and all the types that have historically been attributed to the object - >>> simbad.add_to_output("otype", "alltypes") + >>> simbad.add_output_columns("otype", "alltypes") >>> result = simbad.query_catalog("M", criteria=CriteriaTranslator.parse(old_criteria)) >>> result.sort("catalog_id") >>> result[["main_id", "catalog_id", "otype", "otypes"]] @@ -252,7 +252,7 @@ The important information is in the column ``filtername``. 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_to_output` (here ``flux`` adds all +the output with `~astroquery.simbad.SimbadClass.add_output_columns` (here ``flux`` adds all columns about fluxes) and then select only the interesting filters with a ``criteria`` argument: @@ -263,7 +263,7 @@ argument: >>> from astroquery.simbad import Simbad >>> simbad = Simbad() - >>> simbad.add_to_output("flux") + >>> simbad.add_output_columns("flux") >>> result = simbad.query_object("BD-16 5701", criteria="filter IN ('U', 'B', 'G')") >>> result[["main_id", "flux", "flux_err", "filter", "bibcode"]]
From 3f3d52bb330aa0a8c8a94d10f2906fd6cf9a9388 Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Tue, 16 Apr 2024 10:52:19 +0200 Subject: [PATCH 18/28] docs: edit version of deprecation --- astroquery/simbad/core.py | 81 ++++++++++++++++++++------ astroquery/simbad/tests/test_simbad.py | 22 +++++++ 2 files changed, 84 insertions(+), 19 deletions(-) diff --git a/astroquery/simbad/core.py b/astroquery/simbad/core.py index 8f2aa6313e..4311bb2afd 100644 --- a/astroquery/simbad/core.py +++ b/astroquery/simbad/core.py @@ -16,6 +16,7 @@ import astropy.units as u from astropy.utils import isiterable from astropy.utils.data import get_pkg_data_filename +from astropy.utils.decorators import deprecated_renamed_argument from astroquery.query import BaseVOQuery from astroquery.utils import commons, async_to_sync @@ -401,6 +402,8 @@ def add_output_columns(self, *args): # Query methods # ------------- + @deprecated_renamed_argument(["verbose", "get_query_payload"], new_name=[None, "get_adql"], + since=['0.4.8', '0.4.8'], relax=True) def query_object(self, object_name, *, wildcard=False, criteria=None, get_adql=False, verbose=False, get_query_payload=False): @@ -421,8 +424,8 @@ def query_object(self, object_name, *, wildcard=False, syntax in a single string. See example. get_adql : bool, defaults to False Returns the ADQL string instead of querying SIMBAD. - verbose : deprecated since 0.4.7 - get_query_payload : deprecated since 0.4.7 + verbose : deprecated since 0.4.8 + get_query_payload : deprecated since 0.4.8 Returns ------- @@ -478,8 +481,13 @@ def query_object(self, object_name, *, wildcard=False, if criteria: instance_criteria.append(f"({criteria})") + if get_query_payload: + get_adql = True + return self._construct_query(top, columns, joins, instance_criteria, get_adql) + @deprecated_renamed_argument(["verbose", "get_query_payload"], new_name=[None, "get_adql"], + since=['0.4.8', '0.4.8'], relax=True) def query_objects(self, object_names, *, wildcard=False, criteria=None, get_adql=False, verbose=False, get_query_payload=False): """Query SIMBAD for the specified list of objects. @@ -501,8 +509,8 @@ def query_objects(self, object_names, *, wildcard=False, criteria=None, syntax in a single string. See example. get_adql : bool, defaults to False Returns the ADQL string instead of querying SIMBAD. - verbose : deprecated since 0.4.7 - get_query_payload : deprecated since 0.4.7 + verbose : deprecated since 0.4.8 + get_query_payload : deprecated since 0.4.8 Returns ------- @@ -547,8 +555,14 @@ def query_objects(self, object_names, *, wildcard=False, criteria=None, if criteria: instance_criteria.append(f"({criteria})") + if get_query_payload: + get_adql = True + return self._construct_query(top, columns, joins, instance_criteria, get_adql, script_infos=upload) + @deprecated_renamed_argument(["get_query_payload", "equinox", "epoch", "cache"], + new_name=["get_adql", None, None, None], + since=['0.4.8', '0.4.8', '0.4.8', '0.4.8'], relax=True) def query_region(self, coordinates, radius=2*u.arcmin, *, criteria=None, get_adql=False, equinox=None, epoch=None, cache=None, @@ -566,12 +580,12 @@ def query_region(self, coordinates, radius=2*u.arcmin, *, syntax in a single string. get_adql : bool, defaults to False Returns the ADQL string instead of querying SIMBAD. - equinox : deprecated since 0.4.7 + equinox : deprecated since 0.4.8 Use `~astropy.coordinates` objects instead - epoch : deprecated since 0.4.7 + epoch : deprecated since 0.4.8 Use `~astropy.coordinates` objects instead - get_query_payload : deprecated since 0.4.7 - cache : deprecated since 0.4.7 + get_query_payload : deprecated since 0.4.8 + cache : deprecated since 0.4.8 Returns ------- @@ -652,8 +666,14 @@ def query_region(self, coordinates, radius=2*u.arcmin, *, if criteria: instance_criteria.append(f"({criteria})") + if get_query_payload: + get_adql = True + return self._construct_query(top, columns, joins, instance_criteria, get_adql) + @deprecated_renamed_argument(["verbose", "cache", "get_query_payload"], + new_name=[None, None, "get_adql"], + since=['0.4.8', '0.4.8', '0.4.8'], relax=True) def query_catalog(self, catalog, *, criteria=None, get_adql=False, verbose=False, cache=True, get_query_payload=False): """Query a whole catalog. @@ -667,9 +687,9 @@ def query_catalog(self, catalog, *, criteria=None, get_adql=False, syntax in a single string. See example. get_adql : bool, defaults to False Returns the ADQL string instead of querying SIMBAD. - verbose : deprecated since 0.4.7 - get_query_payload : deprecated since 0.4.7 - cache : deprecated since 0.4.7 + verbose : deprecated since 0.4.8 + get_query_payload : deprecated since 0.4.8 + cache : deprecated since 0.4.8 Returns ------- @@ -709,8 +729,13 @@ def query_catalog(self, catalog, *, criteria=None, get_adql=False, if criteria: instance_criteria.append(f"({criteria})") + if get_query_payload: + get_adql = True + return self._construct_query(top, columns, joins, instance_criteria, get_adql) + @deprecated_renamed_argument(["verbose", "get_query_payload"], new_name=[None, "get_adql"], + since=['0.4.8', '0.4.8'], relax=True) def query_bibobj(self, bibcode, *, criteria=None, get_adql=False, verbose=False, get_query_payload=False): @@ -723,8 +748,8 @@ def query_bibobj(self, bibcode, *, criteria=None, get_query_payload : bool, optional When set to `True` the method returns the HTTP request parameters. Defaults to `False`. - verbose : deprecated since 0.4.7 - get_query_payload : deprecated since 0.4.7 + verbose : deprecated since 0.4.8 + get_query_payload : deprecated since 0.4.8 Returns ------- @@ -745,8 +770,14 @@ def query_bibobj(self, bibcode, *, criteria=None, if criteria: instance_criteria.append(f"({criteria})") + if get_query_payload: + get_adql = True + return self._construct_query(top, columns, joins, instance_criteria, get_adql) + @deprecated_renamed_argument(["verbose", "get_query_payload", "cache"], + new_name=[None, "get_adql", None], + since=['0.4.8', '0.4.8', '0.4.8'], relax=True) def query_bibcode(self, bibcode, *, wildcard=False, abstract=False, get_adql=False, criteria=None, verbose=None, @@ -768,9 +799,11 @@ def query_bibcode(self, bibcode, *, wildcard=False, criteria : str Criteria to be applied to the query. These should be written in the ADQL syntax in a single string. See example. - verbose : deprecated since 0.4.7 - get_query_payload : deprecated since 0.4.7 - cache : deprecated since 0.4.7 + verbose : deprecated since 0.4.8 + get_query_payload : deprecated since 0.4.8 + cache : The cache is now bound to the python session. It can be emptied with + `~astroquery.simbad.SimbadClass.empty_cache()` but cannot be deactivated + from here. Deprecated since 0.4.8 Returns ------- @@ -813,10 +846,16 @@ def query_bibcode(self, bibcode, *, wildcard=False, query += " ORDER BY bibcode" + if get_query_payload: + get_adql = True + if get_adql: return query return self.query_tap(query) + @deprecated_renamed_argument(["verbose", "get_query_payload", "cache"], + new_name=[None, "get_adql", None], + since=['0.4.8', '0.4.8', '0.4.8'], relax=True) def query_objectids(self, object_name, *, verbose=None, cache=None, get_query_payload=None, criteria=None, get_adql=False): @@ -834,9 +873,9 @@ def query_objectids(self, object_name, *, verbose=None, cache=None, the column ``ident.id``. get_adql : bool, optional Returns the ADQL string instead of querying SIMBAD, by default False. - verbose : deprecated since 0.4.7 - get_query_payload : deprecated since 0.4.7 - cache : deprecated since 0.4.7 + verbose : deprecated since 0.4.8 + get_query_payload : deprecated since 0.4.8 + cache : deprecated since 0.4.8 Returns ------- @@ -876,6 +915,10 @@ def query_objectids(self, object_name, *, verbose=None, cache=None, f"WHERE id_typed.id = '{_adql_parameter(object_name)}'") if criteria is not None: query += f" AND {criteria}" + + if get_query_payload: + get_adql = True + if get_adql: return query return self.query_tap(query) diff --git a/astroquery/simbad/tests/test_simbad.py b/astroquery/simbad/tests/test_simbad.py index 7e9c0d7b23..14464caf06 100644 --- a/astroquery/simbad/tests/test_simbad.py +++ b/astroquery/simbad/tests/test_simbad.py @@ -6,6 +6,7 @@ from astropy.io.votable import parse_single_table from astropy.table import Table import astropy.units as u +from astropy.utils.exceptions import AstropyDeprecationWarning from pyvo.dal.tap import TAPService import pytest @@ -483,3 +484,24 @@ def test_construct_query(): [column], [], ["ra < 6", "ra > 5"], get_adql=True) == expected + + +@pytest.mark.usefixtures("_mock_simbad_class") +@pytest.mark.parametrize( + ("query_method", "args", "deprecated_kwargs"), + [ + (simbad.Simbad.query_objectids, ["M1"], {"verbose", "get_query_payload", "cache"}), + (simbad.Simbad.query_bibcode, ["1992AJ....103..983B"], {"verbose", "get_query_payload", "cache"}), + (simbad.Simbad.query_bibobj, ["1992AJ....103..983B"], {"verbose", "get_query_payload"}), + (simbad.Simbad.query_catalog, ["M"], {"verbose", "get_query_payload", "cache"}), + (simbad.Simbad.query_region, ["M1", "2d"], {"get_query_payload", "equinox", "epoch", "cache"}), + (simbad.Simbad.query_objects, [["M1", "M2"]], {"verbose", "get_query_payload"}), + (simbad.Simbad.query_object, ["M1"], {"verbose", "get_query_payload"}), + ] +) +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_adql=True, **{argument: True}) From a795f82beb5192201d24d1039271b18d281c26f4 Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Mon, 3 Jun 2024 15:10:00 +0200 Subject: [PATCH 19/28] feat: reduce API changes --- astroquery/simbad/core.py | 276 ++++++++++++++---- astroquery/simbad/criteria_lextab.py | 2 +- astroquery/simbad/criteria_parsetab.py | 23 +- astroquery/simbad/tests/test_simbad.py | 122 ++++++-- astroquery/simbad/tests/test_simbad_remote.py | 22 +- astroquery/simbad/tests/test_utils.py | 23 +- astroquery/simbad/utils.py | 109 ++++--- docs/simbad/simbad.rst | 63 ++-- docs/simbad/simbad_evolution.rst | 18 +- 9 files changed, 462 insertions(+), 196 deletions(-) diff --git a/astroquery/simbad/core.py b/astroquery/simbad/core.py index 4311bb2afd..7adefcd054 100644 --- a/astroquery/simbad/core.py +++ b/astroquery/simbad/core.py @@ -6,23 +6,22 @@ from difflib import get_close_matches from functools import lru_cache import gc -import json +import re from typing import Any -from pathlib import Path import warnings import astropy.coordinates as coord from astropy.table import Table, Column, vstack import astropy.units as u -from astropy.utils import isiterable -from astropy.utils.data import get_pkg_data_filename +from astropy.utils import isiterable, deprecated from astropy.utils.decorators import deprecated_renamed_argument from astroquery.query import BaseVOQuery -from astroquery.utils import commons, async_to_sync +from astroquery.utils import commons from astroquery.exceptions import LargeQueryWarning from astroquery.simbad.utils import (_catch_deprecated_fields_with_arguments, - _wildcard_to_regexp) + _wildcard_to_regexp, CriteriaTranslator, + query_criteria_fields) from pyvo.dal import TAPService from . import conf @@ -30,9 +29,6 @@ __all__ = ['Simbad', 'SimbadClass'] -with open(get_pkg_data_filename(str(Path("data") / "query_criteria_fields.json"))) as f: - query_criteria_fields = json.load(f) - def _adql_parameter(entry: str): """Replace single quotes by two single quotes. @@ -79,7 +75,6 @@ def _cached_query_tap(tap, query: str, *, maxrec=10000): return tap.search(query, maxrec=maxrec).to_table() -@async_to_sync class SimbadClass(BaseVOQuery): """The class for querying the SIMBAD web service. @@ -170,7 +165,16 @@ def hardlimit(self): @property def columns_in_output(self): - """A list of Simbad.Column.""" + """A list of Simbad.Column. + + They will be included in the output of 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_criteria`. + """ if self._columns_in_output is None: self._columns_in_output = [Simbad.Column("basic", item) for item in conf.default_columns] @@ -180,11 +184,33 @@ def columns_in_output(self): 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 + their usage. + + Examples + -------- + >>> from astroquery.simbad import Simbad + >>> 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. + """ + 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())) + # --------------------------------- # Methods to define SIMBAD's output # --------------------------------- - def list_output_options(self): + def list_votable_fields(self): """List all options to add columns to SIMBAD's output. They are of three types: @@ -198,7 +224,7 @@ def list_output_options(self): Examples -------- >>> from astroquery.simbad import Simbad - >>> options = Simbad.list_output_options() # doctest: +REMOTE_DATA + >>> 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
@@ -245,7 +271,7 @@ def _get_bundle_columns(self, bundle_name): Parameters ---------- bundle_name : str - The possible values can be listed with `~astroquery.simbad.SimbadClass.list_output_options` + The possible values can be listed with `~astroquery.simbad.SimbadClass.list_votable_fields` Returns ------- @@ -306,18 +332,20 @@ def _add_table_to_output(self, table): self.joins += [Simbad.Join(table, Simbad.Column("basic", link["target_column"]), Simbad.Column(table, link["from_column"]))] - def add_output_columns(self, *args): + 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_output_options`. + can be printed with `~astroquery.simbad.SimbadClass.list_votable_fields`. The methods affected by this property are: - - `~astroquery.simbad.SimbadClass.query_object` - - `~astroquery.simbad.SimbadClass.query_objects` - - `~astroquery.simbad.SimbadClass.query_region` - - `~astroquery.simbad.SimbadClass.query_bibobj` + - `~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_criteria`. Parameters @@ -329,15 +357,32 @@ def add_output_columns(self, *args): -------- >>> from astroquery.simbad import Simbad >>> simbad = Simbad() - >>> simbad.add_output_columns('sp_type', 'sp_qual', 'sp_bibcode') # doctest: +REMOTE_DATA + >>> simbad.add_votable_fields('sp_type', 'sp_qual', 'sp_bibcode') # doctest: +REMOTE_DATA >>> simbad.columns_in_output[0] # doctest: +REMOTE_DATA SimbadClass.Column(table='basic', name='main_id', alias=None) """ + + # 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(self.Join("allfluxes", self.Column("basic", "oid"), + self.Column("allfluxes", "oidref"))) + self.columns_in_output.append(self.Column("allfluxes", flux_filter)) + args.remove(arg) + # casefold args args = set(map(str.casefold, args)) # output options - output_options = self.list_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"] @@ -387,17 +432,55 @@ def add_output_columns(self, *args): "into a separate VizieR catalog. It is possible to query " "it with the `astroquery.vizier` module.") else: - # it could be also be one of the fields with arguments + # 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_output_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) + 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): + """Reset the output of the query_*** methods to default. + + They will be included in the output of 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_criteria`. + """ + self.columns_in_output = [Simbad.Column("basic", item) + for item in conf.default_columns] + self.joins = [] + self.criteria = [] + + def get_field_description(self, field_name): + """Displays a description of the VOTable field. + + 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' + + """ + options = self.list_votable_fields() + description = options[options["name"] == field_name]["description"] + return description.data.data[0] + # ------------- # Query methods # ------------- @@ -424,8 +507,10 @@ def query_object(self, object_name, *, wildcard=False, syntax in a single string. See example. get_adql : bool, defaults to False Returns the ADQL string instead of querying SIMBAD. - verbose : deprecated since 0.4.8 - get_query_payload : deprecated since 0.4.8 + verbose : Deprecated since 0.4.8 + get_query_payload : Deprecated since 0.4.8. The query payload is not available + anymore, but the ADQL string can be returned instead with the ``get_adql`` + argument. Returns ------- @@ -439,7 +524,7 @@ def query_object(self, object_name, *, wildcard=False, >>> from astroquery.simbad import Simbad >>> simbad = Simbad() - >>> simbad.add_output_columns("dim") # doctest: +REMOTE_DATA + >>> 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
@@ -509,8 +594,10 @@ def query_objects(self, object_names, *, wildcard=False, criteria=None, syntax in a single string. See example. get_adql : bool, defaults to False Returns the ADQL string instead of querying SIMBAD. - verbose : deprecated since 0.4.8 - get_query_payload : deprecated since 0.4.8 + verbose : Deprecated since 0.4.8 + get_query_payload : Deprecated since 0.4.8. The query payload is not available + anymore, but the ADQL string can be returned instead with the ``get_adql`` + argument. Returns ------- @@ -580,12 +667,16 @@ def query_region(self, coordinates, radius=2*u.arcmin, *, syntax in a single string. get_adql : bool, defaults to False Returns the ADQL string instead of querying SIMBAD. - equinox : deprecated since 0.4.8 + equinox : Deprecated since 0.4.8 Use `~astropy.coordinates` objects instead - epoch : deprecated since 0.4.8 + epoch : Deprecated since 0.4.8 Use `~astropy.coordinates` objects instead - get_query_payload : deprecated since 0.4.8 - cache : deprecated since 0.4.8 + get_query_payload : Deprecated since 0.4.8. The query payload is not available + anymore, but the ADQL string can be returned instead with the ``get_adql`` + argument. + 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 ------- @@ -601,7 +692,7 @@ def query_region(self, coordinates, radius=2*u.arcmin, *, >>> from astropy.coordinates import SkyCoord >>> simbad = Simbad() >>> simbad.ROW_LIMIT = 5 - >>> simbad.add_output_columns("otype") # doctest: +REMOTE_DATA + >>> 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", @@ -687,9 +778,13 @@ def query_catalog(self, catalog, *, criteria=None, get_adql=False, syntax in a single string. See example. get_adql : bool, defaults to False Returns the ADQL string instead of querying SIMBAD. - verbose : deprecated since 0.4.8 - get_query_payload : deprecated since 0.4.8 - cache : deprecated since 0.4.8 + verbose : Deprecated since 0.4.8 + get_query_payload : Deprecated since 0.4.8. The query payload is not available + anymore, but the ADQL string can be returned instead with the ``get_adql`` + argument. + 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 ------- @@ -745,11 +840,10 @@ def query_bibobj(self, bibcode, *, criteria=None, ---------- 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`. - verbose : deprecated since 0.4.8 - get_query_payload : deprecated since 0.4.8 + get_query_payload : Deprecated since 0.4.8. The query payload is not available + anymore, but the ADQL string can be returned instead with the ``get_adql`` + argument. + verbose : Deprecated since 0.4.8 Returns ------- @@ -799,11 +893,13 @@ def query_bibcode(self, bibcode, *, wildcard=False, criteria : str Criteria to be applied to the query. These should be written in the ADQL syntax in a single string. See example. - verbose : deprecated since 0.4.8 - get_query_payload : deprecated since 0.4.8 - cache : The cache is now bound to the python session. It can be emptied with - `~astroquery.simbad.SimbadClass.empty_cache()` but cannot be deactivated - from here. Deprecated since 0.4.8 + verbose : Deprecated since 0.4.8 + get_query_payload : Deprecated since 0.4.8. The query payload is not available + anymore, but the ADQL string can be returned instead with the ``get_adql`` + argument. + 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 ------- @@ -873,9 +969,13 @@ def query_objectids(self, object_name, *, verbose=None, cache=None, the column ``ident.id``. get_adql : bool, optional Returns the ADQL string instead of querying SIMBAD, by default False. - verbose : deprecated since 0.4.8 - get_query_payload : deprecated since 0.4.8 - cache : deprecated since 0.4.8 + verbose : Deprecated since 0.4.8 + get_query_payload : Deprecated since 0.4.8. The query payload is not available + anymore, but the ADQL string can be returned instead with the ``get_adql`` + argument. + 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 ------- @@ -923,6 +1023,80 @@ def query_objectids(self, object_name, *, verbose=None, cache=None, return query return self.query_tap(query) + @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_adql=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 + ---------- + 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) + + Returns + ------- + table : `~astropy.table.Table` + Query results table + + Examples + -------- + + Can be replaced by the ``criteria`` argument that was added in the + other query_*** methods + + >>> 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 + + >>> from astroquery.simbad import Simbad + >>> Simbad.query_tap("SELECT TOP 5 main_id, sp_type" + ... " FROM basic WHERE sp_type < 'F3'") # doctest: +REMOTE_DATA +
+ 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(self.Join("otypes", self.Column("basic", "oid"), + self.Column("otypes", "oidref"))) + if "allfluxes." in added_criteria: + joins.append(self.Join("allfluxes", self.Column("basic", "oid"), + self.Column("allfluxes", "oidref"))) + return self._construct_query(top, columns, joins, instance_criteria, get_adql) + def list_tables(self, *, get_adql=False): """List the names and descriptions of the tables in SIMBAD. @@ -1209,6 +1383,8 @@ def _construct_query(self, top, columns, joins, criteria, get_adql=False, **uplo 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]) diff --git a/astroquery/simbad/criteria_lextab.py b/astroquery/simbad/criteria_lextab.py index 00b2fb3ef2..5166b08e76 100644 --- a/astroquery/simbad/criteria_lextab.py +++ b/astroquery/simbad/criteria_lextab.py @@ -15,7 +15,7 @@ _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')])]} +_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 index ea3cb64864..0e00ea4fc4 100644 --- a/astroquery/simbad/criteria_parsetab.py +++ b/astroquery/simbad/criteria_parsetab.py @@ -17,9 +17,9 @@ _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 criteria : COLUMN LIKE STRINGcriteria : COLUMN NOTLIKE STRINGcriteria : COLUMN IN LISTcriteria : REGION" +_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,],[3,3,3,3,]),'REGION':([0,2,5,6,],[4,4,4,4,]),'$end':([1,4,12,13,14,15,16,17,18,19,],[0,-9,-1,-2,-3,-4,-5,-6,-7,-8,]),'|':([1,4,7,12,13,14,15,16,17,18,19,],[5,-9,5,5,5,-3,-4,-5,-6,-7,-8,]),'&':([1,4,7,12,13,14,15,16,17,18,19,],[6,-9,6,6,6,-3,-4,-5,-6,-7,-8,]),'BINARY_OPERATOR':([3,],[8,]),'LIKE':([3,],[9,]),'NOTLIKE':([3,],[10,]),'IN':([3,],[11,]),')':([4,7,12,13,14,15,16,17,18,19,],[-9,14,-1,-2,-3,-4,-5,-6,-7,-8,]),'STRING':([8,9,10,],[15,17,18,]),'NUMBER':([8,],[16,]),'LIST':([11,],[19,]),} +_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(): @@ -38,13 +38,14 @@ del _lr_goto_items _lr_productions = [ ("S' -> criteria","S'",1,None,None,None), - ('criteria -> criteria | criteria','criteria',3,'p_criteria_OR','utils.py',374), - ('criteria -> criteria & criteria','criteria',3,'p_criteria_AND','utils.py',378), - ('criteria -> ( criteria )','criteria',3,'p_criteria_parenthesis','utils.py',382), - ('criteria -> COLUMN BINARY_OPERATOR STRING','criteria',3,'p_criteria_string','utils.py',386), - ('criteria -> COLUMN BINARY_OPERATOR NUMBER','criteria',3,'p_criteria_string','utils.py',387), - ('criteria -> COLUMN LIKE STRING','criteria',3,'p_criteria_like','utils.py',392), - ('criteria -> COLUMN NOTLIKE STRING','criteria',3,'p_criteria_notlike','utils.py',396), - ('criteria -> COLUMN IN LIST','criteria',3,'p_criteria_in','utils.py',400), - ('criteria -> REGION','criteria',1,'p_criteria_region','utils.py',404), + ('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/tests/test_simbad.py b/astroquery/simbad/tests/test_simbad.py index 14464caf06..0f757bb23d 100644 --- a/astroquery/simbad/tests/test_simbad.py +++ b/astroquery/simbad/tests/test_simbad.py @@ -30,10 +30,10 @@ def _mock_simbad_class(monkeypatch): 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_output_options() + # >>> options = Simbad.list_votable_fields() # >>> options.write("simbad_output_options.xml", format="votable") monkeypatch.setattr(simbad.SimbadClass, "hardlimit", 2000000) - monkeypatch.setattr(simbad.SimbadClass, "list_output_options", lambda self: table) + monkeypatch.setattr(simbad.SimbadClass, "list_votable_fields", lambda self: table) @pytest.fixture() @@ -50,7 +50,7 @@ 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_output_columns + # to test in add_votable_fields if table_name == "mesdistance": return Table( [["bibcode"]], names=["column_name"] @@ -146,8 +146,8 @@ def test_init_columns_in_output(): @pytest.mark.usefixtures("_mock_simbad_class") def test_mocked_simbad(): simbad_instance = simbad.Simbad() - # this mocks the list_output_options - options = simbad_instance.list_output_options() + # 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 @@ -158,16 +158,39 @@ def test_mocked_simbad(): @pytest.mark.usefixtures("_mock_basic_columns") -def test_list_output_options(monkeypatch): +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_output_options() + 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.Simbad.Column("basic", "otype") in simbad_instance.columns_in_output + # reset + simbad_instance.reset_votable_fields() + assert not simbad.Simbad.Column("basic", "otype") in simbad_instance.columns_in_output + @pytest.mark.usefixtures("_mock_basic_columns") @pytest.mark.parametrize(("bundle_name", "column"), @@ -204,52 +227,60 @@ def test_add_table_to_output(monkeypatch): @pytest.mark.usefixtures("_mock_simbad_class") @pytest.mark.usefixtures("_mock_basic_columns") @pytest.mark.usefixtures("_mock_linked_to_basic") -def test_add_output_columns(): +def test_add_votable_fields(): simbad_instance = simbad.Simbad() # add columns from basic (one value) - simbad_instance.add_output_columns("pmra") + simbad_instance.add_votable_fields("pmra") assert simbad.SimbadClass.Column("basic", "pmra") in simbad_instance.columns_in_output # add two columns from basic - simbad_instance.add_output_columns("pmdec", "pm_bibcodE") # also test case insensitive + simbad_instance.add_votable_fields("pmdec", "pm_bibcodE") # also test case insensitive expected = [simbad.SimbadClass.Column("basic", "pmdec"), simbad.SimbadClass.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_output_columns("basic") + simbad_instance.add_votable_fields("basic") assert [simbad.SimbadClass.Column("basic", "*")] == simbad_instance.columns_in_output # add a bundle - simbad_instance.add_output_columns("dimensions") + simbad_instance.add_votable_fields("dimensions") assert simbad.SimbadClass.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_output_columns("id(1)") + simbad_instance.add_votable_fields("id(1)") assert simbad.SimbadClass.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_output_columns("distance") + simbad_instance.add_votable_fields("distance") # errors are raised for the deprecated fields with options - with pytest.raises(ValueError, match="Criteria on filters are deprecated when defining Simbad's output.*"): - simbad_instance.add_to_output("fluxdata(V)") - with pytest.raises(ValueError, match="Coordinates conversion and formatting is no longer supported.*"): - simbad_instance.add_to_output("coo(s)", "dec(d)") + 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_output_columns("ID(Gaia)") + simbad_instance.add_votable_fields("ID(Gaia)") with pytest.raises(ValueError, match="Selecting a range of years for bibcode is removed.*"): - simbad_instance.add_output_columns("bibcodelist(2042-2050)") + 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_output_columns("einstein") + 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_output_options'. Did you mean 'alltypes' or 'otype' or 'otypes'?"): - simbad_instance.add_output_columns("ALLTYPE") + "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 # ------------------------------------------ @@ -277,12 +308,13 @@ def test_query_bibcode_class(): @pytest.mark.usefixtures("_mock_simbad_class") def test_query_objectids(): - adql = simbad.core.Simbad.query_objectids('Polaris', - criteria="ident.id LIKE 'HD%'", - get_adql=True) - 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 + with pytest.raises(AstropyDeprecationWarning, match='"get_query_payload"*'): + adql = simbad.core.Simbad.query_objectids('Polaris', + criteria="ident.id LIKE 'HD%'", + get_query_payload=True) + 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") @@ -392,6 +424,35 @@ def test_query_object(): 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_adql=True) + 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_adql=True) + 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 # ------------------------- @@ -486,6 +547,7 @@ def test_construct_query(): ["ra < 6", "ra > 5"], get_adql=True) == expected +@pytest.mark.filterwarnings("ignore:\"get_query_payload\" and \"get_adql\" keywords were set*") @pytest.mark.usefixtures("_mock_simbad_class") @pytest.mark.parametrize( ("query_method", "args", "deprecated_kwargs"), @@ -494,7 +556,7 @@ def test_construct_query(): (simbad.Simbad.query_bibcode, ["1992AJ....103..983B"], {"verbose", "get_query_payload", "cache"}), (simbad.Simbad.query_bibobj, ["1992AJ....103..983B"], {"verbose", "get_query_payload"}), (simbad.Simbad.query_catalog, ["M"], {"verbose", "get_query_payload", "cache"}), - (simbad.Simbad.query_region, ["M1", "2d"], {"get_query_payload", "equinox", "epoch", "cache"}), + (simbad.Simbad.query_region, [ICRS_COORDS, "2d"], {"get_query_payload", "equinox", "epoch", "cache"}), (simbad.Simbad.query_objects, [["M1", "M2"]], {"verbose", "get_query_payload"}), (simbad.Simbad.query_object, ["M1"], {"verbose", "get_query_payload"}), ] diff --git a/astroquery/simbad/tests/test_simbad_remote.py b/astroquery/simbad/tests/test_simbad_remote.py index 2a43ca6482..5ac96075a5 100644 --- a/astroquery/simbad/tests/test_simbad_remote.py +++ b/astroquery/simbad/tests/test_simbad_remote.py @@ -3,11 +3,11 @@ from astropy.coordinates import SkyCoord import astropy.units as u +from astropy.utils.exceptions import AstropyDeprecationWarning from astropy.table import Table from astroquery.simbad import Simbad from astroquery.simbad.core import _cached_query_tap -from astroquery.exceptions import BlankResponseWarning from pyvo.dal.exceptions import DALOverflowWarning @@ -40,7 +40,7 @@ def test_non_ascii_bibcode(self): def test_query_bibobj(self): self.simbad.ROW_LIMIT = 5 - self.simbad.add_output_columns("otype") + 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) @@ -82,7 +82,7 @@ def test_query_multi_object(self): def test_simbad_flux_qual(self): '''Regression test for issue 680''' simbad_instance = Simbad() - simbad_instance.add_output_columns("flux") + 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 @@ -95,6 +95,14 @@ def test_query_object(self): 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 = self.simbad.query_tap("select filtername from filter where filtername='B'") @@ -148,7 +156,7 @@ def test_add_bundle_to_output(self): # empty before the test simbad_instance.columns_in_output = [] # add a bundle - simbad_instance.add_output_columns("dim") + simbad_instance.add_votable_fields("dim") # check the length assert len(simbad_instance.columns_in_output) == 8 assert Simbad.Column("basic", "galdim_majaxis") in simbad_instance.columns_in_output @@ -157,7 +165,7 @@ def test_add_table_to_output(self): simbad_instance = Simbad() # empty before the test simbad_instance.columns_in_output = [] - simbad_instance.add_output_columns("otypes") + simbad_instance.add_votable_fields("otypes") assert Simbad.Column("otypes", "otype", '"otypes.otype"') in simbad_instance.columns_in_output # tables also require a join assert Simbad.Join("otypes", @@ -165,9 +173,9 @@ def test_add_table_to_output(self): Simbad.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_output_columns("IUE") + simbad_instance.add_votable_fields("IUE") # empty before the test simbad_instance.columns_in_output = [] # mixed columns bundles and tables - simbad_instance.add_output_columns("flux", "velocity", "update_date") + 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 index 1857037aeb..ce9e6dc455 100644 --- a/astroquery/simbad/tests/test_utils.py +++ b/astroquery/simbad/tests/test_utils.py @@ -2,7 +2,7 @@ import pytest from astroquery.simbad.utils import (CriteriaTranslator, _parse_coordinate_and_convert_to_icrs, - _region_to_contains, list_wildcards, _wildcard_to_regexp) + _region_to_contains, _wildcard_to_regexp) from astropy.coordinates.builtin_frames.icrs import ICRS from astropy.coordinates import SkyCoord @@ -18,12 +18,6 @@ def test_parse_coordinates_and_convert_to_icrs(coord_string, frame, epoch, equin assert isinstance(coord.frame, ICRS) -def test_list_wildcards(capsys): - list_wildcards() - wildcards = capsys.readouterr() - assert "*: Any string of characters (including an empty one)" in wildcards.out - - def test_wildcard_to_regexp(): # should add beginning and end operators, and translate * into .* assert _wildcard_to_regexp("test*") == "^test.*$" @@ -90,14 +84,21 @@ def test_tokenizer(): @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 otype = 'G' AND (nbref >= 10 OR bibyear >= 2000)")), - ("otype != 'Galaxy..'", "otype != 'Galaxy..'"), + " 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") + ("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. + # 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 diff --git a/astroquery/simbad/utils.py b/astroquery/simbad/utils.py index 8bcec086db..10586aa1d6 100644 --- a/astroquery/simbad/utils.py +++ b/astroquery/simbad/utils.py @@ -1,11 +1,17 @@ """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): @@ -21,52 +27,22 @@ def _catch_deprecated_fields_with_arguments(votable_field): votable_field : str one of the former votable fields (see `~astroquery.simbad.SimbadClass.list_votable_fields`) """ - if re.match(r"^flux.*\(.+\)$", votable_field): - raise ValueError("Criteria on filters are deprecated when defining Simbad's output. " - "See section on filters in " - "https://astroquery.readthedocs.io/en/latest/simbad/simbad_evolution.html") if re.match(r"^(ra|dec|coo)\(.+\)$", votable_field): - raise ValueError("Coordinates conversion and formatting is no longer supported. This " - "can be done with the `~astropy.coordinates` module." + 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`. " - "See section on catalogs in " - "https://astroquery.readthedocs.io/en/latest/simbad/simbad_evolution.html") + "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. " - "See https://astroquery.readthedocs.io/en/latest/simbad/simbad_evolution.html for " - "more details.") + "bibcodelist without parenthesis and get the full list of bibliographic references.") # ---------------------------- -# To support wildcard argument +# Support wildcard argument # ---------------------------- -def list_wildcards(): - """ - Displays the available wildcards that may be used in SIMBAD queries and - their usage. - - Examples - -------- - >>> from astroquery.simbad.utils import list_wildcards - >>> 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. - """ - 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())) - - def _wildcard_to_regexp(wildcard_string): r"""Translate a wildcard string into a regexp. @@ -109,6 +85,10 @@ def _wildcard_to_regexp(wildcard_string): # start and end of string + whitespace means any number of whitespaces return f"^{wildcard_string.replace(' ', ' +')}$" +# ---------------------------------------- +# Support legacy sim-script query language +# ---------------------------------------- + def _region_to_contains(region_string): """Translate a region string into an ADQL CONTAINS clause. @@ -201,6 +181,41 @@ def _parse_coordinate_and_convert_to_icrs(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 = [ @@ -245,7 +260,7 @@ def t_BINARY_OPERATOR(t): return t def t_LIKE(t): - r"~|∼" # the examples in SIMBAD documentation use the strange long ∼ + r"~|∼" # the examples in SIMBAD documentation use this glyph '∼' t.value = "LIKE" return t @@ -264,7 +279,7 @@ def t_REGION(t): return t def t_COLUMN(t): - r'[a-zA-Z_][a-zA-Z_0-9]*' + r'[a-zA-Z_*][a-zA-Z_0-9*]*' return t t_ignore = ", \t\n" # noqa: F841 @@ -296,27 +311,31 @@ def p_criteria_parenthesis(p): 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 """ - p[0] = p[1] + " " + p[2] + " " + p[3] + # 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(" + p[1] + ", '" + _wildcard_to_regexp(p[3][1:-1]) + "') = 1" + 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(" + p[1] + ", '" + _wildcard_to_regexp(p[3][1:-1]) + "') = 0" - - def p_criteria_in(p): - """criteria : COLUMN IN LIST""" - p[0] = p[1] + " IN " + p[3] + 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("Syntax error for sim-script criteria") + 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") diff --git a/docs/simbad/simbad.rst b/docs/simbad/simbad.rst index 60a4733cd8..529c4c7a13 100644 --- a/docs/simbad/simbad.rst +++ b/docs/simbad/simbad.rst @@ -113,8 +113,8 @@ To see the available wildcards and their meaning: .. code-block:: python - >>> from astroquery.simbad.utils import list_wildcards - >>> list_wildcards() + >>> from astroquery.simbad import Simbad + >>> 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] @@ -221,7 +221,7 @@ If the center is defined by coordinates, then the best solution is to use a >>> import astropy.units as u >>> 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]) + ... radius=[0.1 * u.deg, 2* u.arcmin]) # doctest: +IGNORE_OUTPUT
main_id ra ... coo_bibcode deg ... @@ -449,13 +449,13 @@ For these methods, the default columns in the output are: SimbadClass.Column(table='basic', name='main_id', alias=None) 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_output_columns`: +a session or for a single query, use `~astroquery.simbad.SimbadClass.add_votable_fields`: .. doctest-remote-data:: >>> from astroquery.simbad import Simbad >>> simbad = Simbad() - >>> simbad.add_output_columns("otype") # here we add a single column about the main object type + >>> simbad.add_votable_fields("otype") # here we add a single column about the main object type 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 @@ -464,32 +464,31 @@ with: .. doctest-remote-data:: >>> from astroquery.simbad import Simbad - >>> Simbad.list_output_options()[["name", "description"]] + >>> 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 - mesDistance Collection of distances (pc, kpc or Mpc) by several means. - ... ... - 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 + 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 Additional criteria ------------------- @@ -524,7 +523,7 @@ This allows to inspect the columns the method would return: >>> 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_output_columns("mesPM", "otype") + >>> simbad.add_votable_fields("mesPM", "otype") >>> peek = simbad.query_object("BD+30 2512") # a query on an object >>> peek.info
@@ -564,7 +563,7 @@ constraint on the first character of the ``mespm.bibcode`` column >>> from astroquery.simbad import Simbad >>> criteria = "mespm.bibcode LIKE '2%'" # starts with 2, anything after >>> simbad = Simbad() - >>> simbad.add_output_columns("mesPM", "otype") + >>> 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"]]
diff --git a/docs/simbad/simbad_evolution.rst b/docs/simbad/simbad_evolution.rst index ae153ccbea..fabe1677ae 100644 --- a/docs/simbad/simbad_evolution.rst +++ b/docs/simbad/simbad_evolution.rst @@ -10,11 +10,11 @@ Votable fields and Output options Votable fields are deprecated in favor of output options. Most of the former votable fields can now be added to the output of Simbad queries with -`~astroquery.simbad.SimbadClass.add_output_columns`. The full list of options is available -with the `~astroquery.simbad.SimbadClass.list_output_options` method. +`~astroquery.simbad.SimbadClass.add_votable_fields`. The full list of options is available +with the `~astroquery.simbad.SimbadClass.list_votable_fields` method. Some columns and tables have a new name under the TAP interface. The old name will be -recognized by `~astroquery.simbad.SimbadClass.add_output_columns`, but only the new name will +recognized by `~astroquery.simbad.SimbadClass.add_votable_fields`, but only the new name will appear in the output. A few ``votable_fields`` had options in parenthesis. This is no longer supported and can @@ -89,8 +89,8 @@ 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, GAL, 0 +0, 3d 1d) & otype='SNR'") - "CONTAINS(POINT('ICRS', ra, dec), BOX('ICRS', 266.4049882865447, -28.936177761791473, 3.0, 1.0)) = 1 AND otype = 'SNR'" + >>> 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. @@ -104,10 +104,10 @@ See a more elaborated example: >>> from astroquery.simbad import Simbad >>> from astroquery.simbad.utils import CriteriaTranslator >>> # not a galaxy, and not a globular cluster - >>> old_criteria = "otype != 'Galaxy..' & otype != 'Cl*..'" + >>> 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_output_columns("otype", "alltypes") + >>> 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"]] @@ -252,7 +252,7 @@ The important information is in the column ``filtername``. 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_output_columns` (here ``flux`` adds all +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: @@ -263,7 +263,7 @@ argument: >>> from astroquery.simbad import Simbad >>> simbad = Simbad() - >>> simbad.add_output_columns("flux") + >>> 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"]]
From 34000cf30ab77678f875acd5825accab7dc93f0e Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Mon, 3 Jun 2024 17:52:51 +0200 Subject: [PATCH 20/28] docs: edits on votable fields utils --- astroquery/simbad/__init__.py | 2 +- docs/simbad/simbad.rst | 63 +++++++++++++++++++------------- docs/simbad/simbad_evolution.rst | 48 ++++++++---------------- 3 files changed, 54 insertions(+), 59 deletions(-) diff --git a/astroquery/simbad/__init__.py b/astroquery/simbad/__init__.py index bb8fead6a2..55c09db211 100644 --- a/astroquery/simbad/__init__.py +++ b/astroquery/simbad/__init__.py @@ -26,7 +26,7 @@ class Conf(_config.ConfigNamespace): 'Time limit for connecting to Simbad server.') row_limit = _config.ConfigItem( - # O defaults to the maximum limit + # defaults to the maximum limit -1, 'Maximum number of rows that will be fetched from the result.') diff --git a/docs/simbad/simbad.rst b/docs/simbad/simbad.rst index 529c4c7a13..2d64d29413 100644 --- a/docs/simbad/simbad.rst +++ b/docs/simbad/simbad.rst @@ -22,7 +22,8 @@ There is always a way to send the information in a bigger query rather than in a 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. +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 ----------------- @@ -43,7 +44,7 @@ methods are described in the next sections. 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 :ref:`query TAP `. +This is described in this section: :ref:`query TAP `. Query modes =========== @@ -54,8 +55,8 @@ 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: .. doctest-remote-data:: @@ -94,12 +95,12 @@ This allows, for instance, to query messier objects from 1 through 9: M 5 229.63841666666673 ... 2010AJ....140.1830G M 5 -The messier objects from 1 to 9 are found. Their ``main_id`` is not necessarily -the one corresponding to the wildcard expression. The column ``matched_id`` contains -the identifier that was matched. +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`. +faster query done with `~astroquery.simbad.SimbadClass.query_objects`. Wildcards """"""""" @@ -109,7 +110,8 @@ Wildcards are supported in these methods: - `~astroquery.simbad.SimbadClass.query_objects` - `~astroquery.simbad.SimbadClass.query_bibcode` -To see the available wildcards and their meaning: +They allow to provide a pattern that the query will match. To see the available +wildcards and their meaning: .. code-block:: python @@ -187,7 +189,7 @@ identifier, a string representing coordinates, or a `~astropy.coordinates.SkyCoo 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.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 @@ -406,23 +408,19 @@ modifying the setting in the Astroquery configuration file. .. Note:: - This works with every ``query_***`` method, but - `~astroquery.simbad.SimbadClass.query_tap` is an exception as the number - of returned rows is fixed in the ADQL string with the ``TOP`` instruction. + 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. Choosing the columns in the output tables ----------------------------------------- -.. Warning:: - - Before astroquery v0.4.8, this was done with ``votable_fields``. This is not - the case anymore. See :ref:`SIMBAD evolutions `. - Some query methods outputs can be customized. This is the case for: - `~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` For these methods, the default columns in the output are: @@ -438,15 +436,18 @@ For these methods, the default columns in the output are: .. Note:: - The columns that will appear in the output are in the - `~astroquery.simbad.SimbadClass.columns_in_output` attribute + The columns that will appear in the output can be printed with the + `~astroquery.simbad.SimbadClass.get_votable_fields` method .. code-block:: python >>> from astroquery.simbad import Simbad >>> simbad = Simbad() - >>> simbad.columns_in_output[0] - SimbadClass.Column(table='basic', name='main_id', alias=None) + >>> 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``). 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`: @@ -490,13 +491,25 @@ with: 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:: + + >>> from astroquery.simbad import Simbad + >>> Simbad.get_field_description("rvz_type") + 'Radial velocity / redshift type' + +And the columns in the output can be reset to their default value with +`~astroquery.simbad.SimbadClass.reset_votable_fields`. + Additional criteria ------------------- .. Warning:: Before astroquery v0.4.8, criteria could only be used with the method ``query_criteria``. - This method does not exist anymore and is replaced by the criteria argument in every + This method is now deprecated and is replaced by the criteria argument in every other methods. See :ref:`SIMBAD evolutions `. Most query methods take a ``criteria`` argument. They are listed here: @@ -509,8 +522,8 @@ Most query methods take a ``criteria`` argument. They are listed here: - `~astroquery.simbad.SimbadClass.query_bibcode` - `~astroquery.simbad.SimbadClass.query_objectids` -The criteria argument expect a string that fits in the ``WHERE`` clause of an ADQL query. -Some examples can be found in the +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 `__. To help writing criteria, a good tip is to inspect the columns that the query would diff --git a/docs/simbad/simbad_evolution.rst b/docs/simbad/simbad_evolution.rst index fabe1677ae..6d4b6de6bd 100644 --- a/docs/simbad/simbad_evolution.rst +++ b/docs/simbad/simbad_evolution.rst @@ -4,31 +4,12 @@ Simbad module evolutions ######################## -********************************* -Votable fields and Output options -********************************* - -Votable fields are deprecated in favor of output options. Most of the former votable -fields can now be added to the output of Simbad queries with -`~astroquery.simbad.SimbadClass.add_votable_fields`. The full list of options is available -with the `~astroquery.simbad.SimbadClass.list_votable_fields` method. - -Some columns and tables have a new name under the TAP interface. The old name will be -recognized by `~astroquery.simbad.SimbadClass.add_votable_fields`, but only the new name will -appear in the output. - -A few ``votable_fields`` had options in parenthesis. This is no longer supported and can -be replaced by the ``criteria`` argument in ``query_***`` methods or by a custom ADQL -query called with `~astroquery.simbad.SimbadClass.query_tap`. The documentation and -former issues on astroquery's repository contain examples, but don't hesitate to open -a new issue if there is some missing information. - **************************************** Translating query_criteria into criteria **************************************** -The method ``query_criteria`` does not exist anymore in SIMBAD, but is replaced by a -``criteria`` argument in the following methods: +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` @@ -137,10 +118,10 @@ 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. -But 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). +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 `_ @@ -159,7 +140,7 @@ or with TAP queries. For example, to see the definition of ``PN``, one can do: Where ``otypedef`` is the table of SIMBAD containing the definitions of object types. -The ``label`` can also be used in a query. +The ``label`` can also be used in a query if you want your code to be easier to read. .. doctest-remote-data:: @@ -175,12 +156,12 @@ The ``label`` can also be used in a query. NGC 6543 PN NGC 7027 PN -And the ``path`` column 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. +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. @@ -200,7 +181,8 @@ An interesting feature brought by the hierarchy of objects is the ``..`` notatio [SC83] G4 Ce* SSTGC 444055 LP* -This return objects which types are indeed among the 17 types deriving from ``Ev*`` (Evolved Star). +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 From aacd4ae5d460c4e1014001b8ed3ab9f40764e094 Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Mon, 3 Jun 2024 17:53:14 +0200 Subject: [PATCH 21/28] docs: add changelog Co-authored-by: Adam Ginsburg --- CHANGES.rst | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 042102723f..9d0ddabb35 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -42,6 +42,47 @@ 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 instead of 0. This + allows to retrieve an empty table with the output's meta-data [#2954] + +- ``ROW_LIMIT`` can now be set at instantiation (ex: ``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] + +- all query methods now accept ``get_adql`` boolean argument that returns the ADQL string + instead of sending the request to SIMBAD. The ``verbose`` and ``get_query_payload`` + are removed from all methods [#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 ``typed_id`` that contains + the requested object's name as typed within astroquery. The ``votable_field`` option + ``typed_id`` is removed [#2954] + +- ``query_region`` does not accept ``equinox`` and ``epoch`` anymore, as this functionality + is handled by the astropy coordinates SkyCoord object [#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 + of the ``criteria`` argument added in the other query methods. A helper method was added + ``astroquery.simbad.utils.CriteriaTranslator`` to translate automatically between the sim-script + syntax and the TAP/ADQL syntax [#2954] skyview ^^^^^^^ From e8041399bba2e7b89b7d711365a4263a5e3c604c Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Tue, 4 Jun 2024 10:08:12 +0200 Subject: [PATCH 22/28] fix: rename typed_id into user_specified_id --- CHANGES.rst | 2 +- astroquery/simbad/core.py | 16 ++++++++-------- astroquery/simbad/tests/test_simbad.py | 8 ++++---- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 9d0ddabb35..209938f392 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -66,7 +66,7 @@ simbad - 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 ``typed_id`` that contains +- ``query_objects`` outputs now have an additional column ``user_specified_id`` that contains the requested object's name as typed within astroquery. The ``votable_field`` option ``typed_id`` is removed [#2954] diff --git a/astroquery/simbad/core.py b/astroquery/simbad/core.py index 7adefcd054..c42ca229b8 100644 --- a/astroquery/simbad/core.py +++ b/astroquery/simbad/core.py @@ -580,7 +580,7 @@ def query_objects(self, object_names, *, wildcard=False, criteria=None, 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). - The column ``typed_id`` is the input object name. + In the output, the column ``user_specified_id`` is the input object name. Parameters ---------- @@ -609,9 +609,9 @@ def query_objects(self, object_names, *, wildcard=False, criteria=None, >>> 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", "typed_id"]] # doctest: +REMOTE_DATA + >>> clusters[["main_id", "ra", "dec", "user_specified_id"]] # doctest: +REMOTE_DATA
- main_id ra dec typed_id + main_id ra dec user_specified_id deg deg object float64 float64 object ---------------------- ------- ------- ----------------- @@ -622,7 +622,7 @@ def query_objects(self, object_names, *, wildcard=False, criteria=None, """ top, columns, joins, instance_criteria = self._get_query_parameters() - upload = Table({"typed_id": object_names, + upload = Table({"user_specified_id": object_names, "object_number_id": list(range(1, len(object_names) + 1))}) upload_name = "TAP_UPLOAD.script_infos" @@ -631,13 +631,13 @@ def query_objects(self, object_names, *, wildcard=False, criteria=None, joins += [Simbad.Join("ident", Simbad.Column("basic", "oid"), Simbad.Column("ident", "oidref")), Simbad.Join(upload_name, Simbad.Column("ident", "id"), - Simbad.Column(upload_name, "typed_id"), "RIGHT JOIN")] + Simbad.Column(upload_name, "user_specified_id"), "RIGHT JOIN")] if wildcard: list_criteria = [f"regexp(id, '{_wildcard_to_regexp(object_name)}') = 1" for object_name in object_names] - instance_criteria += [f'(({" OR ".join(list_criteria)}) OR typed_id IS NOT NULL)'] + instance_criteria += [f'(({" OR ".join(list_criteria)}) OR user_specified_id IS NOT NULL)'] else: - instance_criteria.append(f"(id IN ({str(object_names)[1:-1]}) OR typed_id IS NOT NULL)") + instance_criteria.append(f"(id IN ({str(object_names)[1:-1]}) OR user_specified_id IS NOT NULL)") if criteria: instance_criteria.append(f"({criteria})") @@ -1074,7 +1074,7 @@ def query_criteria(self, *args, get_adql=False, **kwargs): >>> from astroquery.simbad import Simbad >>> Simbad.query_tap("SELECT TOP 5 main_id, sp_type" - ... " FROM basic WHERE sp_type < 'F3'") # doctest: +REMOTE_DATA + ... " FROM basic WHERE sp_type < 'F3'") # doctest: +REMOTE_DATA +IGNORE_OUTPUT
main_id sp_type object object diff --git a/astroquery/simbad/tests/test_simbad.py b/astroquery/simbad/tests/test_simbad.py index 0f757bb23d..d209a32dfc 100644 --- a/astroquery/simbad/tests/test_simbad.py +++ b/astroquery/simbad/tests/test_simbad.py @@ -397,15 +397,15 @@ def test_query_objects(): # no wildcard and additional criteria adql = simbad.core.Simbad.query_objects(("m1", "m2"), criteria="otype = 'Galaxy..'", get_adql=True) expected = ('FROM basic JOIN ident ON basic."oid" = ident."oidref" RIGHT JOIN TAP_UPLOAD.script_infos' - ' ON ident."id" = TAP_UPLOAD.script_infos."typed_id" WHERE (id IN (\'m1\', \'m2\') OR ' - 'typed_id IS NOT NULL) AND (otype = \'Galaxy..\')') + ' ON ident."id" = TAP_UPLOAD.script_infos."user_specified_id" WHERE (id IN (\'m1\', \'m2\') OR ' + 'user_specified_id IS NOT NULL) AND (otype = \'Galaxy..\')') assert adql.endswith(expected) # with wildcard adql = simbad.core.Simbad.query_objects(("M *", "NGC *"), wildcard=True, get_adql=True) expected = (r'SELECT .* TAP_UPLOAD\.script_infos\.\* FROM basic JOIN ident ' r'ON basic\."oid" = ident\."oidref" RIGHT JOIN TAP_UPLOAD\.script_infos ON' - r' ident\."id" = TAP_UPLOAD\.script_infos\."typed_id" WHERE \(\(regexp\(id, \'\^M \+\.\*\$\'\)' - r' = 1 OR regexp\(id, \'\^NGC \+\.\*\$\'\) = 1\) OR typed_id IS NOT NULL\)') + r' ident\."id" = TAP_UPLOAD\.script_infos\."user_specified_id" WHERE \(\(regexp\(id, \'\^M \+\.\*\$\'\)' + r' = 1 OR regexp\(id, \'\^NGC \+\.\*\$\'\) = 1\) OR user_specified_id IS NOT NULL\)') assert re.match(expected, adql) is not None From 82d6d2ebd368c1972445d363237efd62c00b609c Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Wed, 5 Jun 2024 15:27:12 +0200 Subject: [PATCH 23/28] feat: add get_query_payload for the methods using TAP --- CHANGES.rst | 8 +- astroquery/simbad/core.py | 236 ++++++++++++------------- astroquery/simbad/tests/test_simbad.py | 93 +++++----- 3 files changed, 168 insertions(+), 169 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 209938f392..827a9cb709 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -59,9 +59,11 @@ simbad - ``get_votable_fields`` now prints the table name and column name instead of just the column name [#2954] -- all query methods now accept ``get_adql`` boolean argument that returns the ADQL string - instead of sending the request to SIMBAD. The ``verbose`` and ``get_query_payload`` - are removed from all methods [#2954] +- The ``verbose`` and ``cache`` arguments are removed from all methods as they don't work + with the new query interface [#2954] + +- ``get_adql`` is 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] diff --git a/astroquery/simbad/core.py b/astroquery/simbad/core.py index c42ca229b8..ceee96a6a2 100644 --- a/astroquery/simbad/core.py +++ b/astroquery/simbad/core.py @@ -23,7 +23,7 @@ _wildcard_to_regexp, CriteriaTranslator, query_criteria_fields) -from pyvo.dal import TAPService +from pyvo.dal import TAPService, TAPQuery from . import conf @@ -485,11 +485,10 @@ def get_field_description(self, field_name): # Query methods # ------------- - @deprecated_renamed_argument(["verbose", "get_query_payload"], new_name=[None, "get_adql"], - since=['0.4.8', '0.4.8'], relax=True) + @deprecated_renamed_argument(["verbose"], new_name=[None], + since=['0.4.8'], relax=True) def query_object(self, object_name, *, wildcard=False, - criteria=None, get_adql=False, - verbose=False, get_query_payload=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. @@ -505,12 +504,11 @@ def query_object(self, object_name, *, wildcard=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_adql : bool, defaults to False - 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`. verbose : Deprecated since 0.4.8 - get_query_payload : Deprecated since 0.4.8. The query payload is not available - anymore, but the ADQL string can be returned instead with the ``get_adql`` - argument. Returns ------- @@ -566,15 +564,13 @@ def query_object(self, object_name, *, wildcard=False, if criteria: instance_criteria.append(f"({criteria})") - if get_query_payload: - get_adql = True - - return self._construct_query(top, columns, joins, instance_criteria, get_adql) + return self._construct_query(top, columns, joins, + instance_criteria, get_query_payload) - @deprecated_renamed_argument(["verbose", "get_query_payload"], new_name=[None, "get_adql"], - since=['0.4.8', '0.4.8'], relax=True) + @deprecated_renamed_argument(["verbose"], new_name=[None], + since=['0.4.8'], relax=True) def query_objects(self, object_names, *, wildcard=False, criteria=None, - get_adql=False, verbose=False, get_query_payload=False): + get_query_payload=False, verbose=False): """Query SIMBAD for the specified list of objects. Object names may be specified with wildcards. @@ -592,12 +588,11 @@ def query_objects(self, object_names, *, wildcard=False, criteria=None, criteria : str Criteria to be applied to the query. These should be written in the ADQL syntax in a single string. See example. - get_adql : bool, defaults to False - 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`. verbose : Deprecated since 0.4.8 - get_query_payload : Deprecated since 0.4.8. The query payload is not available - anymore, but the ADQL string can be returned instead with the ``get_adql`` - argument. Returns ------- @@ -642,18 +637,15 @@ def query_objects(self, object_names, *, wildcard=False, criteria=None, if criteria: instance_criteria.append(f"({criteria})") - if get_query_payload: - get_adql = True - - return self._construct_query(top, columns, joins, instance_criteria, get_adql, script_infos=upload) + return self._construct_query(top, columns, joins, instance_criteria, + get_query_payload, script_infos=upload) - @deprecated_renamed_argument(["get_query_payload", "equinox", "epoch", "cache"], - new_name=["get_adql", None, None, None], - since=['0.4.8', '0.4.8', '0.4.8', '0.4.8'], relax=True) + @deprecated_renamed_argument(["equinox", "epoch", "cache"], + new_name=[None, None, None], + since=['0.4.8', '0.4.8', '0.4.8'], relax=True) def query_region(self, coordinates, radius=2*u.arcmin, *, - criteria=None, get_adql=False, - equinox=None, epoch=None, cache=None, - get_query_payload=None): + criteria=None, get_query_payload=False, + equinox=None, epoch=None, cache=None): """Query SIMBAD in a cone around the specified coordinates. Parameters @@ -665,15 +657,14 @@ def query_region(self, coordinates, radius=2*u.arcmin, *, criteria : str Criteria to be applied to the query. These should be written in the ADQL syntax in a single string. - get_adql : bool, defaults to False - 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`. equinox : Deprecated since 0.4.8 Use `~astropy.coordinates` objects instead epoch : Deprecated since 0.4.8 Use `~astropy.coordinates` objects instead - get_query_payload : Deprecated since 0.4.8. The query payload is not available - anymore, but the ADQL string can be returned instead with the ``get_adql`` - argument. 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. @@ -757,16 +748,13 @@ def query_region(self, coordinates, radius=2*u.arcmin, *, if criteria: instance_criteria.append(f"({criteria})") - if get_query_payload: - get_adql = True + return self._construct_query(top, columns, joins, instance_criteria, + get_query_payload) - return self._construct_query(top, columns, joins, instance_criteria, get_adql) - - @deprecated_renamed_argument(["verbose", "cache", "get_query_payload"], - new_name=[None, None, "get_adql"], - since=['0.4.8', '0.4.8', '0.4.8'], relax=True) - def query_catalog(self, catalog, *, criteria=None, get_adql=False, - verbose=False, cache=True, get_query_payload=False): + @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 @@ -776,12 +764,11 @@ def query_catalog(self, catalog, *, criteria=None, get_adql=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_adql : bool, defaults to False - 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`. verbose : Deprecated since 0.4.8 - get_query_payload : Deprecated since 0.4.8. The query payload is not available - anymore, but the ADQL string can be returned instead with the ``get_adql`` - argument. 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. @@ -824,25 +811,23 @@ def query_catalog(self, catalog, *, criteria=None, get_adql=False, if criteria: instance_criteria.append(f"({criteria})") - if get_query_payload: - get_adql = True - - return self._construct_query(top, columns, joins, instance_criteria, get_adql) + return self._construct_query(top, columns, joins, instance_criteria, get_query_payload) - @deprecated_renamed_argument(["verbose", "get_query_payload"], new_name=[None, "get_adql"], - since=['0.4.8', '0.4.8'], relax=True) + @deprecated_renamed_argument(["verbose"], new_name=[None], + since=['0.4.8'], relax=True) def query_bibobj(self, bibcode, *, criteria=None, - get_adql=False, - verbose=False, get_query_payload=False): + 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 : Deprecated since 0.4.8. The query payload is not available - anymore, but the ADQL string can be returned instead with the ``get_adql`` - argument. + 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`. verbose : Deprecated since 0.4.8 Returns @@ -864,18 +849,13 @@ def query_bibobj(self, bibcode, *, criteria=None, if criteria: instance_criteria.append(f"({criteria})") - if get_query_payload: - get_adql = True - - return self._construct_query(top, columns, joins, instance_criteria, get_adql) + return self._construct_query(top, columns, joins, instance_criteria, get_query_payload) - @deprecated_renamed_argument(["verbose", "get_query_payload", "cache"], - new_name=[None, "get_adql", None], - since=['0.4.8', '0.4.8', '0.4.8'], relax=True) + @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_adql=False, criteria=None, - verbose=None, - cache=None, get_query_payload=None): + abstract=False, get_query_payload=False, criteria=None, + verbose=None, cache=None, ): """Query the references corresponding to a given bibcode. Wildcards may be used to specify bibcodes. @@ -893,10 +873,11 @@ def query_bibcode(self, bibcode, *, wildcard=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 without + querying SIMBAD. The ADQL string is in the 'QUERY' key of the payload. + Defaults to `False`. verbose : Deprecated since 0.4.8 - get_query_payload : Deprecated since 0.4.8. The query payload is not available - anymore, but the ADQL string can be returned instead with the ``get_adql`` - argument. 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. @@ -942,19 +923,12 @@ def query_bibcode(self, bibcode, *, wildcard=False, query += " ORDER BY bibcode" - if get_query_payload: - get_adql = True - - if get_adql: - return query - return self.query_tap(query) + return self.query_tap(query, get_query_payload=get_query_payload) - @deprecated_renamed_argument(["verbose", "get_query_payload", "cache"], - new_name=[None, "get_adql", None], - since=['0.4.8', '0.4.8', '0.4.8'], relax=True) + @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=None, criteria=None, - get_adql=False): + get_query_payload=False, criteria=None): """Query SIMBAD with an object name. This returns a table of all names associated with that object. @@ -967,12 +941,11 @@ def query_objectids(self, object_name, *, verbose=None, cache=None, 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_adql : bool, optional - Returns the ADQL string instead of querying SIMBAD, by default False. verbose : Deprecated since 0.4.8 - get_query_payload : Deprecated since 0.4.8. The query payload is not available - anymore, but the ADQL string can be returned instead with the ``get_adql`` - argument. + 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`. 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. @@ -1015,13 +988,7 @@ def query_objectids(self, object_name, *, verbose=None, cache=None, f"WHERE id_typed.id = '{_adql_parameter(object_name)}'") if criteria is not None: query += f" AND {criteria}" - - if get_query_payload: - get_adql = True - - if get_adql: - return query - return self.query_tap(query) + 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 " @@ -1030,7 +997,7 @@ def query_objectids(self, object_name, *, verbose=None, cache=None, "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_adql=False, **kwargs): + 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. @@ -1042,6 +1009,10 @@ def query_criteria(self, *args, get_adql=False, **kwargs): 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) @@ -1095,15 +1066,23 @@ def query_criteria(self, *args, get_adql=False, **kwargs): if "allfluxes." in added_criteria: joins.append(self.Join("allfluxes", self.Column("basic", "oid"), self.Column("allfluxes", "oidref"))) - return self._construct_query(top, columns, joins, instance_criteria, get_adql) + return self._construct_query(top, columns, joins, instance_criteria, + get_query_payload=get_query_payload) - def list_tables(self, *, get_adql=False): + @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 - Return 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 ------- @@ -1112,11 +1091,11 @@ 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): + @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 @@ -1131,8 +1110,13 @@ def list_columns(self, *tables: str, keyword=None, get_adql=False): 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 ------- @@ -1191,11 +1175,9 @@ 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): + 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 @@ -1207,8 +1189,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 ------- @@ -1230,11 +1214,9 @@ 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) + return self.query_tap(query, get_query_payload=get_query_payload) - def query_tap(self, query: str, *, maxrec=10000, **uploads): + def query_tap(self, query: str, *, maxrec=10000, get_query_payload=False, **uploads): """Query SIMBAD TAP service. Parameters @@ -1249,6 +1231,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 ------- @@ -1323,8 +1309,12 @@ def query_tap(self, query: str, *, maxrec=10000, **uploads): 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 _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() @staticmethod @@ -1341,7 +1331,7 @@ 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))) - def _construct_query(self, top, columns, joins, criteria, get_adql=False, **uploads): + def _construct_query(self, top, columns, joins, criteria, get_query_payload=False, **uploads): """Generate and ADQL string from the given query parameters. This assumes that the query is for data around the basic table. It is thus only @@ -1359,8 +1349,10 @@ def _construct_query(self, top, columns, joins, criteria, get_adql=False, **uplo criteria : List[str] A list of strings. These criteria will be joined with an AND clause. - get_adql : bool, optional - Returns the ADQL string instead of querying SIMBAD, by default False. + 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 ------- @@ -1397,9 +1389,7 @@ def _construct_query(self, top, columns, joins, criteria, get_adql=False, **uplo query = f"SELECT{top}{columns} FROM basic{join}{criteria}" - if get_adql: - return query - return self.query_tap(query, **uploads) + return self.query_tap(query, get_query_payload=get_query_payload, **uploads) Simbad = SimbadClass() diff --git a/astroquery/simbad/tests/test_simbad.py b/astroquery/simbad/tests/test_simbad.py index d209a32dfc..aed345296f 100644 --- a/astroquery/simbad/tests/test_simbad.py +++ b/astroquery/simbad/tests/test_simbad.py @@ -289,17 +289,18 @@ def test_list_wildcards(capsys): def test_query_bibcode_class(): simbad_instance = simbad.Simbad() # wildcard - adql = simbad_instance.query_bibcode("????LASP.*", wildcard=True, get_adql=True) + 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_adql=True) + 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_adql=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 ' @@ -308,20 +309,18 @@ def test_query_bibcode_class(): @pytest.mark.usefixtures("_mock_simbad_class") def test_query_objectids(): - with pytest.raises(AstropyDeprecationWarning, match='"get_query_payload"*'): - adql = simbad.core.Simbad.query_objectids('Polaris', - criteria="ident.id LIKE 'HD%'", - get_query_payload=True) - 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 + 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_adql=True, - criteria="dec < 5") + 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 @@ -331,8 +330,8 @@ def test_query_bibobj(): @pytest.mark.usefixtures("_mock_simbad_class") def test_query_catalog(): simbad_instance = simbad.Simbad() - adql = simbad_instance.query_catalog('Gaia DR2', get_adql=True, - criteria="update_date < '2010-01-01'") + 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) @@ -363,8 +362,10 @@ def test_query_catalog(): ]) @pytest.mark.usefixtures("_mock_simbad_class") def test_query_region(coordinates, radius, where): - adql = simbad.core.Simbad.query_region(coordinates, radius=radius, get_adql=True) - adql_2 = simbad.core.Simbad().query_region(coordinates, radius=radius, get_adql=True) + 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 @@ -372,7 +373,8 @@ def test_query_region(coordinates, radius, where): @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_adql=True) + criteria="galdim_majaxis>0.2", + get_query_payload=True)["QUERY"] assert adql.endswith("AND (galdim_majaxis>0.2)") @@ -389,19 +391,20 @@ def test_query_region_errors(): 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_adql=True) + 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_adql=True) + adql = simbad.core.Simbad.query_objects(("m1", "m2"), criteria="otype = 'Galaxy..'", + get_query_payload=True)["QUERY"] expected = ('FROM basic JOIN ident ON basic."oid" = ident."oidref" RIGHT JOIN TAP_UPLOAD.script_infos' ' ON ident."id" = TAP_UPLOAD.script_infos."user_specified_id" WHERE (id IN (\'m1\', \'m2\') OR ' 'user_specified_id IS NOT NULL) AND (otype = \'Galaxy..\')') assert adql.endswith(expected) # with wildcard - adql = simbad.core.Simbad.query_objects(("M *", "NGC *"), wildcard=True, get_adql=True) + adql = simbad.core.Simbad.query_objects(("M *", "NGC *"), wildcard=True, get_query_payload=True)["QUERY"] expected = (r'SELECT .* TAP_UPLOAD\.script_infos\.\* FROM basic JOIN ident ' r'ON basic\."oid" = ident\."oidref" RIGHT JOIN TAP_UPLOAD\.script_infos ON' r' ident\."id" = TAP_UPLOAD\.script_infos\."user_specified_id" WHERE \(\(regexp\(id, \'\^M \+\.\*\$\'\)' @@ -412,15 +415,16 @@ def test_query_objects(): @pytest.mark.usefixtures("_mock_simbad_class") def test_query_object(): # no wildcard - adql = simbad.core.Simbad.query_object("m1", wildcard=False, get_adql=True) + 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_adql=True) + 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_adql=True) + 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) @@ -434,7 +438,7 @@ 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_adql=True) + 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\", " @@ -444,7 +448,7 @@ def test_query_criteria(): "AND otypes.otype = 'HII')") assert adql == expected # with a flux criteria - adql = simbad.core.Simbad.query_criteria("Umag < 9", get_adql=True) + 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", ' @@ -480,11 +484,13 @@ def test_query_tap_cache_call(monkeypatch): # --------------------------------------------------- +@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" @@ -492,12 +498,13 @@ def test_simbad_list_columns(): "WHERE table_name NOT LIKE 'TAP_SCHEMA.%'" " 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 LOWERCASE(table_name) = 'basic' ORDER BY table_name, principal DESC, column_name") - assert simbad.Simbad.list_columns("basic", get_adql=True) == columns_adql + 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.%' " @@ -505,14 +512,15 @@ 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") @@ -523,14 +531,14 @@ def test_construct_query(): assert simbad.Simbad._construct_query(-1, [simbad.Simbad.Column("basic", "main_id", "my_id")], [], - [], get_adql=True) == expected + [], get_query_payload=True)["QUERY"] == expected # with top # and duplicated columns are dropped expected = "SELECT TOP 1 basic.* FROM basic" assert simbad.Simbad._construct_query(1, [column, column], [], - [], get_adql=True) == expected + [], 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._construct_query(-1, @@ -538,27 +546,26 @@ def test_construct_query(): [simbad.Simbad.Join("ids", simbad.Simbad.Column("basic", "oid"), simbad.Simbad.Column("ids", "oidref"))], - [], get_adql=True) == expected + [], get_query_payload=True)["QUERY"] == expected # with a condition expected = "SELECT basic.* FROM basic WHERE ra < 6 AND ra > 5" assert simbad.Simbad._construct_query(-1, [column], [], - ["ra < 6", "ra > 5"], get_adql=True) == expected + ["ra < 6", "ra > 5"], get_query_payload=True)["QUERY"] == expected -@pytest.mark.filterwarnings("ignore:\"get_query_payload\" and \"get_adql\" keywords were set*") @pytest.mark.usefixtures("_mock_simbad_class") @pytest.mark.parametrize( ("query_method", "args", "deprecated_kwargs"), [ - (simbad.Simbad.query_objectids, ["M1"], {"verbose", "get_query_payload", "cache"}), - (simbad.Simbad.query_bibcode, ["1992AJ....103..983B"], {"verbose", "get_query_payload", "cache"}), - (simbad.Simbad.query_bibobj, ["1992AJ....103..983B"], {"verbose", "get_query_payload"}), - (simbad.Simbad.query_catalog, ["M"], {"verbose", "get_query_payload", "cache"}), - (simbad.Simbad.query_region, [ICRS_COORDS, "2d"], {"get_query_payload", "equinox", "epoch", "cache"}), - (simbad.Simbad.query_objects, [["M1", "M2"]], {"verbose", "get_query_payload"}), - (simbad.Simbad.query_object, ["M1"], {"verbose", "get_query_payload"}), + (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): @@ -566,4 +573,4 @@ def test_deprecated_arguments(query_method, args, 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_adql=True, **{argument: True}) + query_method(*args, get_query_payload=True, **{argument: True})["QUERY"] From b87c23d721f6eee8a7078e5d7c7a02c6295683d8 Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Fri, 14 Jun 2024 18:03:03 +0200 Subject: [PATCH 24/28] fix: wildcards in query_objects This was not tested remotely. And for now, this is extremely slow. Thus, the remote test for the wildcards in 'query_objects' is skipped except if SKIP_SLOW is set to False. On the other hand, when there is no wildcard in 'query_objects' there is a way faster method (join the small table on the left). So this PR also implements this. To use this faster method, the 'construct_query' had to become a bit general and now accepts a 'from_table' argument that defaults to 'basic'. --- astroquery/simbad/core.py | 79 +++++++++++-------- astroquery/simbad/tests/test_simbad.py | 45 +++++------ astroquery/simbad/tests/test_simbad_remote.py | 18 ++++- 3 files changed, 80 insertions(+), 62 deletions(-) diff --git a/astroquery/simbad/core.py b/astroquery/simbad/core.py index ceee96a6a2..e93c84e50e 100644 --- a/astroquery/simbad/core.py +++ b/astroquery/simbad/core.py @@ -564,8 +564,8 @@ def query_object(self, object_name, *, wildcard=False, if criteria: instance_criteria.append(f"({criteria})") - return self._construct_query(top, columns, joins, - instance_criteria, get_query_payload) + return self._query(top, columns, joins, instance_criteria, + get_query_payload=get_query_payload) @deprecated_renamed_argument(["verbose"], new_name=[None], since=['0.4.8'], relax=True) @@ -617,28 +617,38 @@ def query_objects(self, object_names, *, wildcard=False, criteria=None, """ top, columns, joins, instance_criteria = self._get_query_parameters() - 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(Simbad.Column(upload_name, "*")) - - joins += [Simbad.Join("ident", Simbad.Column("basic", "oid"), Simbad.Column("ident", "oidref")), - Simbad.Join(upload_name, Simbad.Column("ident", "id"), - Simbad.Column(upload_name, "user_specified_id"), "RIGHT JOIN")] + if criteria: + instance_criteria.append(f"({criteria})") if wildcard: + columns.append(Simbad.Column("ident", "id", "matched_id")) + joins += [Simbad.Join("ident", Simbad.Column("basic", "oid"), + Simbad.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)}) OR user_specified_id IS NOT NULL)'] - else: - instance_criteria.append(f"(id IN ({str(object_names)[1:-1]}) OR user_specified_id IS NOT NULL)") + instance_criteria += [f'({" OR ".join(list_criteria)})'] - if criteria: - instance_criteria.append(f"({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(Simbad.Column(upload_name, "*")) - return self._construct_query(top, columns, joins, instance_criteria, - get_query_payload, script_infos=upload) + left_joins = [Simbad.Join("ident", Simbad.Column(upload_name, "user_specified_id"), + Simbad.Column("ident", "id"), "LEFT JOIN"), + Simbad.Join("basic", Simbad.Column("basic", "oid"), + Simbad.Column("ident", "oidref"), "LEFT JOIN")] + for join in joins: + left_joins.append(Simbad.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, None, None], @@ -748,8 +758,8 @@ def query_region(self, coordinates, radius=2*u.arcmin, *, if criteria: instance_criteria.append(f"({criteria})") - return self._construct_query(top, columns, joins, instance_criteria, - get_query_payload) + return self._query(top, columns, joins, instance_criteria, + get_query_payload=get_query_payload) @deprecated_renamed_argument(["verbose", "cache"], new_name=[None, None], since=['0.4.8', '0.4.8'], relax=True) @@ -811,7 +821,8 @@ def query_catalog(self, catalog, *, criteria=None, get_query_payload=False, if criteria: instance_criteria.append(f"({criteria})") - return self._construct_query(top, columns, joins, instance_criteria, get_query_payload) + return self._query(top, columns, joins, instance_criteria, + get_query_payload=get_query_payload) @deprecated_renamed_argument(["verbose"], new_name=[None], since=['0.4.8'], relax=True) @@ -849,7 +860,8 @@ def query_bibobj(self, bibcode, *, criteria=None, if criteria: instance_criteria.append(f"({criteria})") - return self._construct_query(top, columns, joins, instance_criteria, get_query_payload) + return self._query(top, columns, joins, instance_criteria, + get_query_payload=get_query_payload) @deprecated_renamed_argument(["verbose", "cache"], new_name=[None, None], since=['0.4.8', '0.4.8'], relax=True) @@ -1066,8 +1078,8 @@ def query_criteria(self, *args, get_query_payload=False, **kwargs): if "allfluxes." in added_criteria: joins.append(self.Join("allfluxes", self.Column("basic", "oid"), self.Column("allfluxes", "oidref"))) - return self._construct_query(top, columns, joins, instance_criteria, - get_query_payload=get_query_payload) + 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) @@ -1331,12 +1343,9 @@ 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))) - def _construct_query(self, top, columns, joins, criteria, get_query_payload=False, **uploads): - """Generate and ADQL string from the given query parameters. - - This assumes that the query is for data around the basic table. It is thus only - useful for the queries that return astronomical objects (every query except query_bibcode - and query_ids). + 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. Parameters ---------- @@ -1349,10 +1358,16 @@ def _construct_query(self, top, columns, joins, criteria, get_query_payload=Fals 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. Returns ------- @@ -1387,7 +1402,7 @@ def _construct_query(self, top, columns, joins, criteria, get_query_payload=Fals else: criteria = "" - query = f"SELECT{top}{columns} FROM basic{join}{criteria}" + query = f"SELECT{top}{columns} FROM {from_table}{join}{criteria}" return self.query_tap(query, get_query_payload=get_query_payload, **uploads) diff --git a/astroquery/simbad/tests/test_simbad.py b/astroquery/simbad/tests/test_simbad.py index aed345296f..1f02c63a8f 100644 --- a/astroquery/simbad/tests/test_simbad.py +++ b/astroquery/simbad/tests/test_simbad.py @@ -399,17 +399,15 @@ 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 basic JOIN ident ON basic."oid" = ident."oidref" RIGHT JOIN TAP_UPLOAD.script_infos' - ' ON ident."id" = TAP_UPLOAD.script_infos."user_specified_id" WHERE (id IN (\'m1\', \'m2\') OR ' - 'user_specified_id IS NOT NULL) AND (otype = \'Galaxy..\')') + 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 = (r'SELECT .* TAP_UPLOAD\.script_infos\.\* FROM basic JOIN ident ' - r'ON basic\."oid" = ident\."oidref" RIGHT JOIN TAP_UPLOAD\.script_infos ON' - r' ident\."id" = TAP_UPLOAD\.script_infos\."user_specified_id" WHERE \(\(regexp\(id, \'\^M \+\.\*\$\'\)' - r' = 1 OR regexp\(id, \'\^NGC \+\.\*\$\'\) = 1\) OR user_specified_id IS NOT NULL\)') - assert re.match(expected, adql) is not None + 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") @@ -524,35 +522,28 @@ def test_list_linked_tables(): @pytest.mark.usefixtures("_mock_simbad_class") -def test_construct_query(): +def test_query(): column = simbad.Simbad.Column("basic", "*") # bare minimum with an alias expected = 'SELECT basic."main_id" AS my_id FROM basic' - assert simbad.Simbad._construct_query(-1, - [simbad.Simbad.Column("basic", "main_id", "my_id")], - [], - [], get_query_payload=True)["QUERY"] == expected + assert simbad.Simbad._query(-1, [simbad.Simbad.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._construct_query(1, - [column, column], - [], - [], get_query_payload=True)["QUERY"] == expected + 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._construct_query(-1, - [column, simbad.Simbad.Column("ids", "ids")], - [simbad.Simbad.Join("ids", - simbad.Simbad.Column("basic", "oid"), - simbad.Simbad.Column("ids", "oidref"))], - [], get_query_payload=True)["QUERY"] == expected + assert simbad.Simbad._query(-1, [column, simbad.Simbad.Column("ids", "ids")], + [simbad.Simbad.Join("ids", simbad.Simbad.Column("basic", "oid"), + simbad.Simbad.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._construct_query(-1, - [column], - [], - ["ra < 6", "ra > 5"], get_query_payload=True)["QUERY"] == expected + assert simbad.Simbad._query(-1, [column], [], + ["ra < 6", "ra > 5"], + get_query_payload=True)["QUERY"] == expected @pytest.mark.usefixtures("_mock_simbad_class") diff --git a/astroquery/simbad/tests/test_simbad_remote.py b/astroquery/simbad/tests/test_simbad_remote.py index 5ac96075a5..c2405d9449 100644 --- a/astroquery/simbad/tests/test_simbad_remote.py +++ b/astroquery/simbad/tests/test_simbad_remote.py @@ -11,6 +11,8 @@ 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') @@ -45,6 +47,7 @@ def test_query_bibobj(self): 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 @@ -73,11 +76,20 @@ def test_query_object_ids(self): 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. - assert len(result) == 3 + # 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() + + @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''' From 4534b22d4051a1e01409c3ca298a0b5e558d8017 Mon Sep 17 00:00:00 2001 From: Manon Marchand Date: Mon, 17 Jun 2024 10:10:19 +0200 Subject: [PATCH 25/28] docs: edit docstring and changelog from review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Brigitta Sipőcz --- CHANGES.rst | 2 +- astroquery/simbad/core.py | 88 +++++++++++++++++++------------------- astroquery/simbad/utils.py | 16 ++++++- 3 files changed, 59 insertions(+), 47 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 827a9cb709..52dc2357c7 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -45,7 +45,7 @@ vizier simbad ^^^^^^ -- The ``ROW_LIMIT`` value to have the maximum number of rows is now -1 instead of 0. This +- The ``ROW_LIMIT`` value to have the maximum number of rows is now -1. ``ROW_LIMIT = 0`` now allows to retrieve an empty table with the output's meta-data [#2954] - ``ROW_LIMIT`` can now be set at instantiation (ex: ``simbad = Simbad(ROW_LIMIT=10))``) [#2954] diff --git a/astroquery/simbad/core.py b/astroquery/simbad/core.py index e93c84e50e..c4cb452a4b 100644 --- a/astroquery/simbad/core.py +++ b/astroquery/simbad/core.py @@ -168,12 +168,14 @@ def columns_in_output(self): """A list of Simbad.Column. They will be included in the output of 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_criteria`. + + - `query_object`, + - `query_objects`, + - `query_region`, + - `query_catalog`, + - `query_bibobj`, + - `query_criteria`. + """ if self._columns_in_output is None: self._columns_in_output = [Simbad.Column("basic", item) @@ -228,17 +230,17 @@ def list_votable_fields(self): >>> # to print only the available bundles of columns >>> options[options["type"] == "bundle of basic columns"][["name", "description"]] # doctest: +REMOTE_DATA
- name ... - object ... - ------------- ... - coordinates ... - dim ... - dimensions ... - morphtype ... - parallax ... - propermotions ... - sp ... - velocity ... + 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 """ # get the tables with a simple link to basic query_tables = """SELECT DISTINCT table_name AS name, tables.description @@ -338,14 +340,14 @@ def add_votable_fields(self, *args): 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 property are: + The methods affected by this: - - `~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_criteria`. + - `query_object`, + - `query_objects`, + - `query_region`, + - `query_catalog`, + - `query_bibobj`, + - `query_criteria`. Parameters @@ -451,12 +453,14 @@ def reset_votable_fields(self): """Reset the output of the query_*** methods to default. They will be included in the output of 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_criteria`. + + - `query_object`, + - `query_objects`, + - `query_region`, + - `query_catalog`, + - `query_bibobj`, + - `query_criteria`. + """ self.columns_in_output = [Simbad.Column("basic", item) for item in conf.default_columns] @@ -508,7 +512,6 @@ def query_object(self, object_name, *, wildcard=False, 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`. - verbose : Deprecated since 0.4.8 Returns ------- @@ -567,10 +570,10 @@ def query_object(self, object_name, *, wildcard=False, return self._query(top, columns, joins, instance_criteria, get_query_payload=get_query_payload) - @deprecated_renamed_argument(["verbose"], new_name=[None], - since=['0.4.8'], relax=True) + @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): + get_query_payload=False, verbose=False, cache=False): """Query SIMBAD for the specified list of objects. Object names may be specified with wildcards. @@ -592,7 +595,9 @@ def query_objects(self, object_names, *, wildcard=False, criteria=None, 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`. - verbose : Deprecated since 0.4.8 + 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 ------- @@ -651,8 +656,9 @@ def query_objects(self, object_names, *, wildcard=False, criteria=None, script_infos=upload) @deprecated_renamed_argument(["equinox", "epoch", "cache"], - new_name=[None, None, None], - since=['0.4.8', '0.4.8', '0.4.8'], relax=True) + 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): @@ -671,10 +677,6 @@ def query_region(self, coordinates, radius=2*u.arcmin, *, 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`. - equinox : Deprecated since 0.4.8 - Use `~astropy.coordinates` objects instead - epoch : Deprecated since 0.4.8 - Use `~astropy.coordinates` objects instead 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. @@ -778,7 +780,6 @@ def query_catalog(self, catalog, *, criteria=None, get_query_payload=False, 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`. - verbose : Deprecated since 0.4.8 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. @@ -839,7 +840,6 @@ def query_bibobj(self, bibcode, *, criteria=None, 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`. - verbose : Deprecated since 0.4.8 Returns ------- @@ -889,7 +889,6 @@ def query_bibcode(self, bibcode, *, wildcard=False, 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`. - verbose : Deprecated since 0.4.8 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. @@ -953,7 +952,6 @@ def query_objectids(self, object_name, *, verbose=None, cache=None, 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``. - verbose : Deprecated since 0.4.8 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. diff --git a/astroquery/simbad/utils.py b/astroquery/simbad/utils.py index 10586aa1d6..675f183647 100644 --- a/astroquery/simbad/utils.py +++ b/astroquery/simbad/utils.py @@ -171,7 +171,21 @@ def _region_to_contains(region_string): def _parse_coordinate_and_convert_to_icrs(string_coordinate, *, frame="icrs", epoch=None, equinox=None): - """Convert a string into a SkyCoord object in the ICRS frame.""" + """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}" From 7c51ec908a1e67e0a4e7206e4ec7934df3fe4b6a Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Thu, 20 Jun 2024 14:37:04 +0200 Subject: [PATCH 26/28] tests: new measurement for sirius rotation --- docs/simbad/query_tap.rst | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/simbad/query_tap.rst b/docs/simbad/query_tap.rst index 98c3710392..72542b16db 100644 --- a/docs/simbad/query_tap.rst +++ b/docs/simbad/query_tap.rst @@ -200,16 +200,17 @@ that is the measurement table for rotations. Their common column is ``oidref``. ... WHERE id = 'Sirius'; ... """ >>> Simbad.query_tap(query) -
+
Rotation Measurements Bibcodes 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. From 86ed0d750133af1f12bc5675d2803f6383bdf6c1 Mon Sep 17 00:00:00 2001 From: MARCHAND MANON Date: Thu, 20 Jun 2024 17:12:42 +0200 Subject: [PATCH 27/28] refactor: remove Column and Join from SimbadClass --- astroquery/simbad/core.py | 119 +++++++++--------- astroquery/simbad/tests/test_simbad.py | 54 ++++---- astroquery/simbad/tests/test_simbad_remote.py | 12 +- 3 files changed, 92 insertions(+), 93 deletions(-) diff --git a/astroquery/simbad/core.py b/astroquery/simbad/core.py index c4cb452a4b..9a4026e78f 100644 --- a/astroquery/simbad/core.py +++ b/astroquery/simbad/core.py @@ -75,6 +75,23 @@ def _cached_query_tap(tap, query: str, *, maxrec=10000): return tap.search(query, maxrec=maxrec).to_table() +@dataclass(frozen=True) +class _Column: + """A class to define a column in a SIMBAD query.""" + table: str + name: str + alias: str = field(default=None) + + +@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") + + class SimbadClass(BaseVOQuery): """The class for querying the SIMBAD web service. @@ -84,21 +101,6 @@ class SimbadClass(BaseVOQuery): """ SIMBAD_URL = 'https://' + conf.server + '/simbad/sim-script' - @dataclass(frozen=True) - class Column: - """A class to define a column in a SIMBAD query.""" - table: str - name: str - alias: str = field(default=None) - - @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") - def __init__(self, ROW_LIMIT=None): super().__init__() # to create the TAPService @@ -106,8 +108,8 @@ def __init__(self, ROW_LIMIT=None): self._tap = None self._hardlimit = None # attributes to construct ADQL queries - self._columns_in_output = None # a list of Simbad.Column - self.joins = [] # a list of Simbad.Join + 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 @@ -165,7 +167,7 @@ def hardlimit(self): @property def columns_in_output(self): - """A list of Simbad.Column. + """A list of _Column. They will be included in the output of the following methods: @@ -178,7 +180,7 @@ def columns_in_output(self): """ if self._columns_in_output is None: - self._columns_in_output = [Simbad.Column("basic", item) + self._columns_in_output = [_Column("basic", item) for item in conf.default_columns] return self._columns_in_output @@ -277,7 +279,7 @@ def _get_bundle_columns(self, bundle_name): Returns ------- - list[Simbad.Column] + list[simbad._Column] The list of columns corresponding to the selected bundle. """ basic_columns = set(map(str.casefold, set(self.list_columns("basic")["column_name"]))) @@ -287,10 +289,10 @@ def _get_bundle_columns(self, bundle_name): if bundle_name in bundle_entries: bundle = bundle_entries[bundle_name] - columns = [Simbad.Column("basic", column) for column in basic_columns + columns = [_Column("basic", column) for column in basic_columns if column.startswith(bundle["tap_startswith"])] if "tap_column" in bundle: - columns = [Simbad.Column("basic", column) for column in bundle["tap_column"]] + columns + columns = [_Column("basic", column) for column in bundle["tap_column"]] + columns return columns def _add_table_to_output(self, table): @@ -308,7 +310,7 @@ def _add_table_to_output(self, table): table = table.casefold() if table == "basic": - self.columns_in_output.append(Simbad.Column(table, "*")) + self.columns_in_output.append(_Column(table, "*")) return linked_to_basic = self.list_linked_tables("basic") @@ -329,10 +331,10 @@ def _add_table_to_output(self, table): alias = [f'"{table}.{column}"' if not column.startswith(table) else None for column in columns] # modify the attributes here - self.columns_in_output += [Simbad.Column(table, column, alias) + self.columns_in_output += [_Column(table, column, alias) for column, alias in zip(columns, alias)] - self.joins += [Simbad.Join(table, Simbad.Column("basic", link["target_column"]), - Simbad.Column(table, link["from_column"]))] + 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. @@ -360,8 +362,8 @@ def add_votable_fields(self, *args): >>> from astroquery.simbad import Simbad >>> simbad = Simbad() >>> simbad.add_votable_fields('sp_type', 'sp_qual', 'sp_bibcode') # doctest: +REMOTE_DATA - >>> simbad.columns_in_output[0] # doctest: +REMOTE_DATA - SimbadClass.Column(table='basic', name='main_id', alias=None) + >>> simbad.get_votable_fields() # doctest: +REMOTE_DATA + ['basic.main_id', 'basic.ra', 'basic.dec', 'basic.coo_err_maj', 'basic.coo_err_min', ... """ # the legacy way of adding fluxes is the only case-dependant option @@ -375,9 +377,9 @@ def add_votable_fields(self, *args): flux_filter = re.findall(r"\((\w+)\)", arg)[0] if len(flux_filter) == 1 and flux_filter.islower(): flux_filter = flux_filter + "_" - self.joins.append(self.Join("allfluxes", self.Column("basic", "oid"), - self.Column("allfluxes", "oidref"))) - self.columns_in_output.append(self.Column("allfluxes", 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 @@ -391,7 +393,7 @@ def add_votable_fields(self, *args): bundles = output_options[output_options["type"] == "bundle of basic columns"]["name"] # Add columns from basic - self.columns_in_output += [Simbad.Column("basic", column) for column in args if column in basic_columns] + 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] @@ -415,7 +417,7 @@ def add_votable_fields(self, *args): # 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(Simbad.Column("basic", 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) @@ -462,7 +464,7 @@ def reset_votable_fields(self): - `query_criteria`. """ - self.columns_in_output = [Simbad.Column("basic", item) + self.columns_in_output = [_Column("basic", item) for item in conf.default_columns] self.joins = [] self.criteria = [] @@ -555,9 +557,9 @@ def query_object(self, object_name, *, wildcard=False, """ top, columns, joins, instance_criteria = self._get_query_parameters() - columns.append(Simbad.Column("ident", "id", "matched_id")) + columns.append(_Column("ident", "id", "matched_id")) - joins.append(Simbad.Join("ident", Simbad.Column("basic", "oid"), Simbad.Column("ident", "oidref"))) + joins.append(_Join("ident", _Column("basic", "oid"), _Column("ident", "oidref"))) if wildcard: instance_criteria.append(rf" regexp(id, '{_wildcard_to_regexp(object_name)}') = 1") @@ -626,10 +628,10 @@ def query_objects(self, object_names, *, wildcard=False, criteria=None, instance_criteria.append(f"({criteria})") if wildcard: - columns.append(Simbad.Column("ident", "id", "matched_id")) - joins += [Simbad.Join("ident", Simbad.Column("basic", "oid"), - Simbad.Column("ident", "oidref"))] - list_criteria = [f"regexp(id, '{_wildcard_to_regexp(object_name)}') = 1" for object_name in object_names] + 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, @@ -640,16 +642,15 @@ def query_objects(self, object_names, *, wildcard=False, criteria=None, 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(Simbad.Column(upload_name, "*")) + columns.append(_Column(upload_name, "*")) - left_joins = [Simbad.Join("ident", Simbad.Column(upload_name, "user_specified_id"), - Simbad.Column("ident", "id"), "LEFT JOIN"), - Simbad.Join("basic", Simbad.Column("basic", "oid"), - Simbad.Column("ident", "oidref"), "LEFT JOIN")] + 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(Simbad.Join(join.table, - join.column_left, - join.column_right, "LEFT JOIN")) + 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, @@ -814,9 +815,9 @@ def query_catalog(self, catalog, *, criteria=None, get_query_payload=False, """ top, columns, joins, instance_criteria = self._get_query_parameters() - columns.append(Simbad.Column("ident", "id", "catalog_id")) + columns.append(_Column("ident", "id", "catalog_id")) - joins += [Simbad.Join("ident", Simbad.Column("basic", "oid"), Simbad.Column("ident", "oidref"))] + joins += [_Join("ident", _Column("basic", "oid"), _Column("ident", "oidref"))] instance_criteria.append(fr"id LIKE '{catalog} %'") if criteria: @@ -848,13 +849,11 @@ def query_bibobj(self, bibcode, *, criteria=None, """ top, columns, joins, instance_criteria = self._get_query_parameters() - joins += [Simbad.Join("has_ref", Simbad.Column("basic", "oid"), - Simbad.Column("has_ref", "oidref")), - Simbad.Join("ref", Simbad.Column("has_ref", "oidbibref"), - Simbad.Column("ref", "oidbib"))] + joins += [_Join("has_ref", _Column("basic", "oid"), _Column("has_ref", "oidref")), + _Join("ref", _Column("has_ref", "oidbibref"), _Column("ref", "oidbib"))] - columns += [Simbad.Column("ref", "bibcode"), - Simbad.Column("has_ref", "obj_freq")] + columns += [_Column("ref", "bibcode"), + _Column("has_ref", "obj_freq")] instance_criteria.append(f"bibcode = '{_adql_parameter(bibcode)}'") if criteria: @@ -1071,11 +1070,11 @@ def query_criteria(self, *args, get_query_payload=False, **kwargs): added_criteria = f"({CriteriaTranslator.parse(' & '.join(list(list(args) + list_kwargs)))})" instance_criteria.append(added_criteria) if "otypes." in added_criteria: - joins.append(self.Join("otypes", self.Column("basic", "oid"), - self.Column("otypes", "oidref"))) + joins.append(_Join("otypes", _Column("basic", "oid"), + _Column("otypes", "oidref"))) if "allfluxes." in added_criteria: - joins.append(self.Join("allfluxes", self.Column("basic", "oid"), - self.Column("allfluxes", "oidref"))) + joins.append(_Join("allfluxes", _Column("basic", "oid"), + _Column("allfluxes", "oidref"))) return self._query(top, columns, joins, instance_criteria, get_query_payload=get_query_payload) diff --git a/astroquery/simbad/tests/test_simbad.py b/astroquery/simbad/tests/test_simbad.py index 1f02c63a8f..d040979c9f 100644 --- a/astroquery/simbad/tests/test_simbad.py +++ b/astroquery/simbad/tests/test_simbad.py @@ -134,11 +134,11 @@ def test_simbad_hardlimit(monkeypatch): assert simbad_instance.hardlimit == 2 -def test_init_columns_in_output(): +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.SimbadClass.Column("basic", "main_id") in default_columns + assert simbad.core._Column("basic", "main_id") in default_columns # there are 8 default columns assert len(default_columns) == 8 @@ -186,17 +186,17 @@ def test_reset_votable_fields(): simbad_instance = simbad.Simbad() # add one simbad_instance.add_votable_fields("otype") - assert simbad.Simbad.Column("basic", "otype") in simbad_instance.columns_in_output + assert simbad.core._Column("basic", "otype") in simbad_instance.columns_in_output # reset simbad_instance.reset_votable_fields() - assert not simbad.Simbad.Column("basic", "otype") in simbad_instance.columns_in_output + 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.SimbadClass.Column("basic", "ra")), - ("coordinates", simbad.SimbadClass.Column("basic", "coo_bibcode")), - ("dim", simbad.SimbadClass.Column("basic", "galdim_wavelength"))]) + [("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) @@ -206,7 +206,7 @@ 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.SimbadClass.Column("basic", "*") in simbad_instance.columns_in_output + 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") @@ -214,14 +214,14 @@ def test_add_table_to_output(monkeypatch): monkeypatch.setattr(simbad.SimbadClass, "list_columns", lambda self, _: Table([["oidref", "bibcode"]], names=["column_name"])) simbad_instance._add_table_to_output("mesDiameter") - assert simbad.SimbadClass.Join("mesdiameter", - simbad.SimbadClass.Column("basic", "oid"), - simbad.SimbadClass.Column("mesdiameter", "oidref") - ) in simbad_instance.joins - assert simbad.SimbadClass.Column("mesdiameter", "bibcode", '"mesdiameter.bibcode"' - ) in simbad_instance.columns_in_output - assert simbad.SimbadClass.Column("mesdiameter", "oidref", '"mesdiameter.oidref"' - ) not in simbad_instance.columns_in_output + 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") @@ -231,26 +231,26 @@ def test_add_votable_fields(): simbad_instance = simbad.Simbad() # add columns from basic (one value) simbad_instance.add_votable_fields("pmra") - assert simbad.SimbadClass.Column("basic", "pmra") in simbad_instance.columns_in_output + 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.SimbadClass.Column("basic", "pmdec"), - simbad.SimbadClass.Column("basic", "pm_bibcode")] + 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.SimbadClass.Column("basic", "*")] == simbad_instance.columns_in_output + assert [simbad.core._Column("basic", "*")] == simbad_instance.columns_in_output # add a bundle simbad_instance.add_votable_fields("dimensions") - assert simbad.SimbadClass.Column("basic", "galdim_majaxis") in simbad_instance.columns_in_output + 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.SimbadClass.Column("basic", "main_id") in simbad_instance.columns_in_output + 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") @@ -523,10 +523,10 @@ def test_list_linked_tables(): @pytest.mark.usefixtures("_mock_simbad_class") def test_query(): - column = simbad.Simbad.Column("basic", "*") + 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.Simbad.Column("basic", "main_id", "my_id")], [], + 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 @@ -535,9 +535,9 @@ def test_query(): 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.Simbad.Column("ids", "ids")], - [simbad.Simbad.Join("ids", simbad.Simbad.Column("basic", "oid"), - simbad.Simbad.Column("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" diff --git a/astroquery/simbad/tests/test_simbad_remote.py b/astroquery/simbad/tests/test_simbad_remote.py index c2405d9449..ed530c0562 100644 --- a/astroquery/simbad/tests/test_simbad_remote.py +++ b/astroquery/simbad/tests/test_simbad_remote.py @@ -7,7 +7,7 @@ from astropy.table import Table from astroquery.simbad import Simbad -from astroquery.simbad.core import _cached_query_tap +from astroquery.simbad.core import _cached_query_tap, _Column, _Join from pyvo.dal.exceptions import DALOverflowWarning @@ -171,18 +171,18 @@ def test_add_bundle_to_output(self): simbad_instance.add_votable_fields("dim") # check the length assert len(simbad_instance.columns_in_output) == 8 - assert Simbad.Column("basic", "galdim_majaxis") in simbad_instance.columns_in_output + 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 Simbad.Column("otypes", "otype", '"otypes.otype"') in simbad_instance.columns_in_output + assert _Column("otypes", "otype", '"otypes.otype"') in simbad_instance.columns_in_output # tables also require a join - assert Simbad.Join("otypes", - Simbad.Column("basic", "oid"), - Simbad.Column("otypes", "oidref")) == simbad_instance.joins[0] + 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") From 6594ebb72bcb155b1d0229e3cbb544e823095978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Brigitta=20Sip=C5=91cz?= Date: Thu, 20 Jun 2024 15:15:36 -0700 Subject: [PATCH 28/28] DOC: minor changelog rephrase and reformat --- CHANGES.rst | 58 +++++++++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 52dc2357c7..13c9e8a3db 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -45,46 +45,48 @@ vizier simbad ^^^^^^ -- The ``ROW_LIMIT`` value to have the maximum number of rows is now -1. ``ROW_LIMIT = 0`` now - allows to retrieve an empty table with the output's meta-data [#2954] +- 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 (ex: ``simbad = Simbad(ROW_LIMIT=10))``) [#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`` 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] +- ``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] +- ``get_votable_fields`` now prints the table name and column name instead of + just the column name. [#2954] -- The ``verbose`` and ``cache`` arguments are removed from all methods as they don't work - with the new query interface [#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 replaced by ``get_query_payload`` in ``list_columns`` and ``list_table``. - The payload output contains the ADQL under the ``QUERY`` key [#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] +- 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`` that contains - the requested object's name as typed within astroquery. The ``votable_field`` option - ``typed_id`` is removed [#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] -- ``query_region`` does not accept ``equinox`` and ``epoch`` anymore, as this functionality - is handled by the astropy coordinates SkyCoord object [#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`` 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_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 - of the ``criteria`` argument added in the other query methods. A helper method was added - ``astroquery.simbad.utils.CriteriaTranslator`` to translate automatically between the sim-script - syntax and the TAP/ADQL syntax [#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 ^^^^^^^