From 4eb250bd72c86564f11b8f5da0ba67b40601261a Mon Sep 17 00:00:00 2001 From: zemogle Date: Wed, 22 Apr 2015 13:31:51 +0100 Subject: [PATCH 1/7] Adapting the IRSA code which is also on IPAC --- astroquery/lcogt/__init__.py | 37 ++ astroquery/lcogt/core.py | 439 ++++++++++++++++++++ astroquery/lcogt/tests/__init__.py | 0 astroquery/lcogt/tests/data/Box.xml | 70 ++++ astroquery/lcogt/tests/data/Cone.xml | 72 ++++ astroquery/lcogt/tests/data/Cone_coord.xml | 72 ++++ astroquery/lcogt/tests/data/Polygon.xml | 76 ++++ astroquery/lcogt/tests/setup_package.py | 8 + astroquery/lcogt/tests/test_lcogt.py | 159 +++++++ astroquery/lcogt/tests/test_lcogt_remote.py | 53 +++ 10 files changed, 986 insertions(+) create mode 100644 astroquery/lcogt/__init__.py create mode 100644 astroquery/lcogt/core.py create mode 100644 astroquery/lcogt/tests/__init__.py create mode 100644 astroquery/lcogt/tests/data/Box.xml create mode 100644 astroquery/lcogt/tests/data/Cone.xml create mode 100644 astroquery/lcogt/tests/data/Cone_coord.xml create mode 100644 astroquery/lcogt/tests/data/Polygon.xml create mode 100644 astroquery/lcogt/tests/setup_package.py create mode 100644 astroquery/lcogt/tests/test_lcogt.py create mode 100644 astroquery/lcogt/tests/test_lcogt_remote.py diff --git a/astroquery/lcogt/__init__.py b/astroquery/lcogt/__init__.py new file mode 100644 index 0000000000..3966a577b8 --- /dev/null +++ b/astroquery/lcogt/__init__.py @@ -0,0 +1,37 @@ +# Licensed under a 3-clause BSD style license - see LICENSE.rst +""" +LCOGT public archive Query Tool +=============== + +This module contains various methods for querying +LCOGT data archive as hosted by IPAC. +""" +from astropy import config as _config + + +class Conf(_config.ConfigNamespace): + """ + Configuration parameters for `astroquery.irsa`. + """ + + server = _config.ConfigItem( + 'http://lcogtarchive.ipac.caltech.edu/cgi-bin/Gator/nph-query', + 'Name of the LCOGT archive as hosted by IPAC to use.' + ) + row_limit = _config.ConfigItem( + 500, + 'Maximum number of rows to retrieve in result' + ) + timeout = _config.ConfigItem( + 60, + 'Time limit for connecting to the LCOGT IPAC server.' + ) + +conf = Conf() + + +from .core import Lcogt, LcogtClass + +__all__ = ['Lcogt', 'LcogtClass', + 'Conf', 'conf', + ] diff --git a/astroquery/lcogt/core.py b/astroquery/lcogt/core.py new file mode 100644 index 0000000000..b9062e4ee0 --- /dev/null +++ b/astroquery/lcogt/core.py @@ -0,0 +1,439 @@ +# Licensed under a 3-clause BSD style license - see LICENSE.rst +""" +LCOGT +==== + +API from + +http://lcogtarchive.ipac.caltech.edu/docs/catsearch.html + +The URL of the LCOGT catalog query service, CatQuery, is + + http://lcogtarchive.ipac.caltech.edu/cgi-bin/Gator/nph-query + +The service accepts the following keywords, which are analogous to the search +fields on the Gator search form: + + +spatial Required Type of spatial query: Cone, Box, Polygon, and NONE + +polygon Convex polygon of ra dec pairs, separated by comma(,) + Required if spatial=polygon + +radius Cone search radius + Optional if spatial=Cone, otherwise ignore it + (default 10 arcsec) + +radunits Units of a Cone search: arcsec, arcmin, deg. + Optional if spatial=Cone + (default='arcsec') + +size Width of a box in arcsec + Required if spatial=Box. + +objstr Target name or coordinate of the center of a spatial + search center. Target names must be resolved by + SIMBAD or NED. + + Required only when spatial=Cone or spatial=Box. + + Examples: 'M31' + '00 42 44.3 -41 16 08' + '00h42m44.3s -41d16m08s' + +catalog Required Catalog name in the IRSA database management system. + +outfmt Optional Defines query's output format. + 6 - returns a program interface in XML + 3 - returns a VO Table (XML) + 2 - returns SVC message + 1 - returns an ASCII table + 0 - returns Gator Status Page in HTML (default) + +desc Optional Short description of a specific catalog, which will + appear in the result page. + +order Optional Results ordered by this column. + +selcols Optional Select specific columns to be returned. The default + action is to return all columns in the queried catalog. + To find the names of the columns in the LCOGT Archive databases, + please read Photometry Table column descriptions [http://lcogtarchive.ipac.caltech.edu/docs/lco_cat_dd.html] + and Image Table column descriptions [http://lcogtarchive.ipac.caltech.edu/docs/lco_img_dd.html]. + +constraint Optional User defined query constraint(s) + Note: The constraint should follow SQL syntax. + +""" +from __future__ import print_function, division + +import warnings +import xml.etree.ElementTree as tree + +from astropy.extern import six +import astropy.units as u +import astropy.coordinates as coord +import astropy.io.votable as votable + +from ..query import BaseQuery +from ..utils import commons +from . import conf +from ..exceptions import TableParseError + +__all__ = ['Irsa', 'IrsaClass'] + + +class IrsaClass(BaseQuery): + IRSA_URL = conf.server + GATOR_LIST_URL = conf.gator_list_catalogs + TIMEOUT = conf.timeout + ROW_LIMIT = conf.row_limit + + def query_region(self, coordinates=None, catalog=None, spatial='Cone', + radius=10 * u.arcsec, width=None, polygon=None, + get_query_payload=False, verbose=False): + """ + This function can be used to perform either cone, box, polygon or + all-sky search in the catalogs hosted by the NASA/IPAC Infrared + Science Archive (IRSA). + + Parameters + ---------- + coordinates : str, `astropy.coordinates` object + Gives the position of the center of the cone or box if + performing a cone or box search. The string can give coordinates + in various coordinate systems, or the name of a source that will + be resolved on the server (see `here + `_ for more + details). Required if spatial is ``'Cone'`` or ``'Box'``. Optional + if spatial is ``'Polygon'``. + catalog : str + The catalog to be used (see the *Notes* section below). + spatial : str + Type of spatial query: ``'Cone'``, ``'Box'``, ``'Polygon'``, and + ``'All-Sky'``. If missing then defaults to ``'Cone'``. + radius : str or `~astropy.units.Quantity` object, [optional for spatial is ``'Cone'``] + The string must be parsable by `~astropy.coordinates.Angle`. The + appropriate `~astropy.units.Quantity` object from + `astropy.units` may also be used. Defaults to 10 arcsec. + width : str, `~astropy.units.Quantity` object [Required for spatial is ``'Polygon'``.] + + The string must be parsable by `~astropy.coordinates.Angle`. The + appropriate `~astropy.units.Quantity` object from `astropy.units` + may also be used. + polygon : list, [Required for spatial is ``'Polygon'``] + A list of ``(ra, dec)`` pairs (as tuples), in decimal degrees, + outlinining the polygon to search in. It can also be a list of + `astropy.coordinates` object or strings that can be parsed by + `astropy.coordinates.ICRS`. + get_query_payload : bool, optional + If `True` then returns the dictionary sent as the HTTP request. + Defaults to `False`. + verbose : bool, optional. + If `True` then displays warnings when the returned VOTable does not + conform to the standard. Defaults to `False`. + + Returns + ------- + table : `~astropy.table.Table` + A table containing the results of the query + """ + response = self.query_region_async(coordinates, catalog=catalog, + spatial=spatial, radius=radius, + width=width, polygon=polygon, + get_query_payload=get_query_payload) + if get_query_payload: + return response + return self._parse_result(response, verbose=verbose) + + def query_region_async(self, coordinates=None, catalog=None, + spatial='Cone', radius=10 * u.arcsec, width=None, + polygon=None, get_query_payload=False): + """ + This function serves the same purpose as + :meth:`~astroquery.irsa.IrsaClass.query_region`, but returns the raw + HTTP response rather than the results in a `~astropy.table.Table`. + + Parameters + ---------- + coordinates : str, `astropy.coordinates` object + Gives the position of the center of the cone or box if + performing a cone or box search. The string can give coordinates + in various coordinate systems, or the name of a source that will + be resolved on the server (see `here + `_ for more + details). Required if spatial is ``'Cone'`` or ``'Box'``. Optional + if spatial is ``'Polygon'``. + catalog : str + The catalog to be used (see the *Notes* section below). + spatial : str + Type of spatial query: ``'Cone'``, ``'Box'``, ``'Polygon'``, and + ``'All-Sky'``. If missing then defaults to ``'Cone'``. + radius : str or `~astropy.units.Quantity` object, [optional for spatial is ``'Cone'``] + The string must be parsable by `~astropy.coordinates.Angle`. The + appropriate `~astropy.units.Quantity` object from + `astropy.units` may also be used. Defaults to 10 arcsec. + width : str, `~astropy.units.Quantity` object [Required for spatial is ``'Polygon'``.] + The string must be parsable by `~astropy.coordinates.Angle`. The + appropriate `~astropy.units.Quantity` object from `astropy.units` + may also be used. + polygon : list, [Required for spatial is ``'Polygon'``] + A list of ``(ra, dec)`` pairs (as tuples), in decimal degrees, + outlinining the polygon to search in. It can also be a list of + `astropy.coordinates` object or strings that can be parsed by + `astropy.coordinates.ICRS`. + get_query_payload : bool, optional + If `True` then returns the dictionary sent as the HTTP request. + Defaults to `False`. + + Returns + ------- + response : `requests.Response` + The HTTP response returned from the service + """ + if catalog is None: + raise Exception("Catalog name is required!") + + request_payload = self._args_to_payload(catalog) + request_payload.update(self._parse_spatial(spatial=spatial, + coordinates=coordinates, + radius=radius, width=width, + polygon=polygon)) + + if get_query_payload: + return request_payload + response = commons.send_request(Irsa.IRSA_URL, request_payload, + Irsa.TIMEOUT, request_type='GET') + return response + + def _parse_spatial(self, spatial, coordinates, radius=None, width=None, + polygon=None): + """ + Parse the spatial component of a query + + Parameters + ---------- + spatial : str + The type of spatial query. Must be one of: ``'Cone'``, ``'Box'``, + ``'Polygon'``, and ``'All-Sky'``. + coordinates : str, `astropy.coordinates` object + Gives the position of the center of the cone or box if + performing a cone or box search. The string can give coordinates + in various coordinate systems, or the name of a source that will + be resolved on the server (see `here + `_ for more + details). Required if spatial is ``'Cone'`` or ``'Box'``. Optional + if spatial is ``'Polygon'``. + radius : str or `~astropy.units.Quantity` object, [optional for spatial is ``'Cone'``] + The string must be parsable by `~astropy.coordinates.Angle`. The + appropriate `~astropy.units.Quantity` object from `astropy.units` + may also be used. Defaults to 10 arcsec. + width : str, `~astropy.units.Quantity` object [Required for spatial is ``'Polygon'``.] + The string must be parsable by `~astropy.coordinates.Angle`. The + appropriate `~astropy.units.Quantity` object from `astropy.units` + may also be used. + polygon : list, [Required for spatial is ``'Polygon'``] + A list of ``(ra, dec)`` pairs as tuples of + `astropy.coordinates.Angle`s outlinining the polygon to search in. + It can also be a list of `astropy.coordinates` object or strings + that can be parsed by `astropy.coordinates.ICRS`. + + Returns + ------- + payload_dict : dict + """ + + request_payload = {} + + if spatial == 'All-Sky': + spatial = 'NONE' + elif spatial in ['Cone', 'Box']: + if not commons._is_coordinate(coordinates): + request_payload['objstr'] = coordinates + else: + request_payload['objstr'] = _parse_coordinates(coordinates) + if spatial == 'Cone': + radius = _parse_dimension(radius) + request_payload['radius'] = radius.value + request_payload['radunits'] = radius.unit.to_string() + else: + width = _parse_dimension(width) + request_payload['size'] = width.to(u.arcsec).value + elif spatial == 'Polygon': + if coordinates is not None: + request_payload['objstr'] = coordinates if not commons._is_coordinate(coordinates) else _parse_coordinates(coordinates) + try: + coordinates_list = [_parse_coordinates(c) for c in polygon] + except (ValueError, TypeError): + coordinates_list = [_format_decimal_coords(*_pair_to_deg(pair)) for pair in polygon] + request_payload['polygon'] = ','.join(coordinates_list) + else: + raise ValueError("Unrecognized spatial query type. " + + "Must be one of `Cone`, `Box`, `Polygon`, or `All-Sky`.") + + request_payload['spatial'] = spatial + + return request_payload + + def _args_to_payload(self, catalog): + """ + Sets the common parameters for all cgi -queries + + Parameters + ---------- + catalog : str + The name of the catalog to query. + + Returns + ------- + request_payload : dict + """ + request_payload = dict(catalog=catalog, + outfmt=3, + outrows=Irsa.ROW_LIMIT) + return request_payload + + def _parse_result(self, response, verbose=False): + """ + Parses the results form the HTTP response to `~astropy.table.Table`. + + Parameters + ---------- + response : `requests.Response` + The HTTP response object + verbose : bool, optional + Defaults to `False`. When true it will display warnings whenever + the VOtable returned from the Service doesn't conform to the + standard. + + Returns + ------- + table : `~astropy.table.Table` + """ + if not verbose: + commons.suppress_vo_warnings() + + content = response.text + + # Check if results were returned + if 'The catalog is not on the list' in content: + raise Exception("Catalog not found") + + # Check that object name was not malformed + if 'Either wrong or missing coordinate/object name' in content: + raise Exception("Malformed coordinate/object name") + + # Check that the results are not of length zero + if len(content) == 0: + raise Exception("The IRSA server sent back an empty reply") + + # Read it in using the astropy VO table reader + try: + first_table = votable.parse(six.BytesIO(response.content), + pedantic=False).get_first_table() + except Exception as ex: + self.response = response + self.table_parse_error = ex + raise TableParseError("Failed to parse IRSA votable! The raw response can be found " + "in self.response, and the error in self.table_parse_error.") + + # Convert to astropy.table.Table instance + table = first_table.to_table() + + # Check if table is empty + if len(table) == 0: + warnings.warn("Query returned no results, so the table will be empty") + + return table + + def list_catalogs(self): + """ + Return a dictionary of the catalogs in the IRSA Gator tool. + + Returns + ------- + catalogs : dict + A dictionary of catalogs where the key indicates the catalog name to + be used in query functions, and the value is the verbose description + of the catalog. + """ + response = commons.send_request(Irsa.GATOR_LIST_URL, dict(mode='xml'), Irsa.TIMEOUT, request_type="GET") + root = tree.fromstring(response.content) + catalogs = {} + for catalog in root.findall('catalog'): + catname = catalog.find('catname').text + desc = catalog.find('desc').text + catalogs[catname] = desc + return catalogs + + def print_catalogs(self): + """ + Display a table of the catalogs in the IRSA Gator tool. + """ + catalogs = self.list_catalogs() + for catname in catalogs: + print("{:30s} {:s}".format(catname, catalogs[catname])) + +Irsa = IrsaClass() + + +def _parse_coordinates(coordinates): +# borrowed from commons.parse_coordinates as from_name wasn't required in this case + if isinstance(coordinates, six.string_types): + try: + c = coord.SkyCoord(coordinates, frame='icrs') + warnings.warn("Coordinate string is being interpreted as an ICRS coordinate.") + except u.UnitsError as ex: + warnings.warn("Only ICRS coordinates can be entered as strings\n" + "For other systems please use the appropriate " + "astropy.coordinates object") + raise ex + elif isinstance(coordinates, commons.CoordClasses): + c = coordinates + else: + raise TypeError("Argument cannot be parsed as a coordinate") + c_icrs = c.transform_to(coord.ICRS) + formatted_coords = _format_decimal_coords(c_icrs.ra.degree, c_icrs.dec.degree) + return formatted_coords + +def _pair_to_deg(pair): + """ Turn a pair of floats, Angles, or Quantities into pairs of float degrees """ + + # unpack + lon, lat = pair + + if hasattr(lon, 'degree') and hasattr(lat, 'degree'): + pair = (lon.degree, lat.degree) + elif hasattr(lon, 'to') and hasattr(lat, 'to'): + pair = [lon, lat] + for ii, ang in enumerate((lon, lat)): + if ang.unit.is_equivalent(u.degree): + pair[ii] = ang.to(u.degree).value + elif ang.unit.is_equivalent(u.hour): + warnings.warn("Assuming angle specified with 'hour' units means 'hourangle'. " + "This is an astropy < 0.3 warning.") + pair[ii] = (ang.value * u.hourangle).to(u.degree) + else: + warnings.warn("Polygon endpoints are being interpreted as RA/Dec pairs specified in decimal degree units.") + return tuple(pair) + + +def _format_decimal_coords(ra, dec): + """ + Print *decimal degree* RA/Dec values in an IPAC-parseable form + """ + return '{0} {1:+}'.format(ra, dec) + + +def _parse_dimension(dim): + if isinstance(dim, u.Quantity) and dim.unit in u.deg.find_equivalent_units(): + if dim.unit not in ['arcsec', 'arcmin', 'deg']: + dim = dim.to(u.degree) + # otherwise must be an Angle or be specified in hours... + else: + try: + new_dim = commons.parse_radius(dim) + dim = u.Quantity(new_dim.degree, u.Unit('degree')) + except (u.UnitsError, coord.errors.UnitsError, AttributeError): + raise u.UnitsError("Dimension not in proper units") + return dim diff --git a/astroquery/lcogt/tests/__init__.py b/astroquery/lcogt/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/astroquery/lcogt/tests/data/Box.xml b/astroquery/lcogt/tests/data/Box.xml new file mode 100644 index 0000000000..063788931a --- /dev/null +++ b/astroquery/lcogt/tests/data/Box.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
10.68473741.26903500h42m44.34s41d16m08.53s0.080.078700424433+41160859.4530.0510.0525385.68.6680.0500.0515089.98.4750.0500.0513684.8EEE22211100055665520n1997-10-248121.174-21.573000.78500.19300.97800
+
+
diff --git a/astroquery/lcogt/tests/data/Cone.xml b/astroquery/lcogt/tests/data/Cone.xml new file mode 100644 index 0000000000..d9d8537846 --- /dev/null +++ b/astroquery/lcogt/tests/data/Cone.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
10.68473741.26903500h42m44.34s41d16m08.53s0.080.078700424433+41160859.4530.0510.0525385.68.6680.0500.0515089.98.4750.0500.0513684.8EEE22211100055665520n1997-10-248121.174-21.573000.169298237.8886720.78500.19300.97800
+
+
diff --git a/astroquery/lcogt/tests/data/Cone_coord.xml b/astroquery/lcogt/tests/data/Cone_coord.xml new file mode 100644 index 0000000000..97a1c67d8e --- /dev/null +++ b/astroquery/lcogt/tests/data/Cone_coord.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
10.68473741.26903500h42m44.34s41d16m08.53s0.080.078700424433+41160859.4530.0510.0525385.68.6680.0500.0515089.98.4750.0500.0513684.8EEE22211100055665520n1997-10-248121.174-21.573000.12843098.0560100.78500.19300.97800
+
+
diff --git a/astroquery/lcogt/tests/data/Polygon.xml b/astroquery/lcogt/tests/data/Polygon.xml new file mode 100644 index 0000000000..3c0f7cfedd --- /dev/null +++ b/astroquery/lcogt/tests/data/Polygon.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
10.01569610.09922800h40m03.77s10d05m57.22s0.100.079000400376+100557215.2370.0520.05428.014.6350.0660.06722.414.4810.0940.09514.6AAA22211100066350500n2000-09-2164118.312-52.670U0.225019.2017.2010.60200.15400.75600
10.03101610.06308200h40m07.44s10d03m47.10s0.190.1811400400744+100347016.4120.1170.1179.515.6030.1100.1109.215.3120.1580.1586.8BBC22211100006060600n2000-09-2164118.332-52.707U0.67319.9018.6010.80900.29101.10001
10.03677610.06027800h40m08.83s10d03m37.00s0.110.069000400882+100337015.3540.0450.04625.214.8860.0730.07317.814.5140.0890.08914.2AAA22211100066260600n2000-09-2164118.341-52.711U0.55018.4017.0010.46800.37200.84002
10.05996410.08544500h40m14.39s10d05m07.60s0.230.209600401439+100507616.3400.1030.10410.215.6430.1310.1318.815.3700.1730.1736.4ABC22211100016060500n2000-09-2164118.382-52.687U0.69819.4018.5010.69700.27300.97003
10.01583910.03806100h40m03.80s10d02m17.02s0.090.069000400380+100217014.6620.0260.02947.614.1100.0390.04036.313.7970.0500.05127.4AAA22211100066666600n2000-09-2164118.305-52.731000.55200.31300.86504
10.01117010.09390300h40m02.68s10d05m38.05s0.230.2116700400268+100538016.3730.1260.1279.815.9950.1690.1696.415.3930.1790.1796.3BCC22211100006060500n2000-09-2164118.304-52.675U2.411119.7018.5010.37800.60200.98005
10.00554910.01840100h40m01.33s10d01m06.24s0.160.1410800400133+100106216.1730.0970.09811.815.5110.1350.13510.014.9450.1120.1139.5ABB22211100016160600n2000-09-2164118.286-52.750000.66200.56601.22806
+
+
diff --git a/astroquery/lcogt/tests/setup_package.py b/astroquery/lcogt/tests/setup_package.py new file mode 100644 index 0000000000..c084725f4d --- /dev/null +++ b/astroquery/lcogt/tests/setup_package.py @@ -0,0 +1,8 @@ +# Licensed under a 3-clause BSD style license - see LICENSE.rst +import os + + +def get_package_data(): + paths = [os.path.join('data', '*.xml'), + ] + return {'astroquery.irsa.tests': paths} diff --git a/astroquery/lcogt/tests/test_lcogt.py b/astroquery/lcogt/tests/test_lcogt.py new file mode 100644 index 0000000000..e00b7f59ff --- /dev/null +++ b/astroquery/lcogt/tests/test_lcogt.py @@ -0,0 +1,159 @@ +# Licensed under a 3-clause BSD style license - see LICENSE.rst +from __future__ import print_function +import os +import re +import requests +import numpy as np + +from astropy.tests.helper import pytest +from astropy.table import Table +import astropy.coordinates as coord +import astropy.units as u + +from ...utils.testing_tools import MockResponse +from ...utils import commons +from ... import irsa +from ...irsa import conf + +DATA_FILES = {'Cone': 'Cone.xml', + 'Box': 'Box.xml', + 'Polygon': 'Polygon.xml'} + +OBJ_LIST = ["m31", "00h42m44.330s +41d16m07.50s", + commons.GalacticCoordGenerator(l=121.1743, b=-21.5733, unit=(u.deg, u.deg))] + + +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_get(request): + mp = request.getfuncargvalue("monkeypatch") + mp.setattr(requests, 'get', get_mockreturn) + return mp + + +def get_mockreturn(url, params=None, timeout=10, **kwargs): + filename = data_path(DATA_FILES[params['spatial']]) + content = open(filename, 'rb').read() + return MockResponse(content, **kwargs) + + +@pytest.mark.parametrize(('dim'), ['5d0m0s', 0.3 * u.rad, '5h0m0s', 2 * u.arcmin]) +def test_parse_dimension(dim): + # check that the returned dimension is always in units of 'arcsec', 'arcmin' or 'deg' + new_dim = irsa.core._parse_dimension(dim) + assert new_dim.unit in ['arcsec', 'arcmin', 'deg'] + + +@pytest.mark.parametrize(('ra', 'dec', 'expected'), + [(10, 10, '10 +10'), + (10.0, -11, '10.0 -11') + ]) +def test_format_decimal_coords(ra, dec, expected): + out = irsa.core._format_decimal_coords(ra, dec) + assert out == expected + + +@pytest.mark.parametrize(('coordinates', 'expected'), + [("5h0m0s 0d0m0s", "75.0 +0.0") + ]) +def test_parse_coordinates(coordinates, expected): + out = irsa.core._parse_coordinates(coordinates) + for a, b in zip(out.split(), expected.split()): + try: + a = float(a) + b = float(b) + np.testing.assert_almost_equal(a, b) + except ValueError: + assert a == b + + +def test_args_to_payload(): + out = irsa.core.Irsa._args_to_payload("fp_psc") + assert out == dict(catalog='fp_psc', outfmt=3, outrows=conf.row_limit) + + +@pytest.mark.parametrize(("coordinates"), OBJ_LIST) +def test_query_region_cone_async(coordinates, patch_get): + response = irsa.core.Irsa.query_region_async(coordinates, catalog='fp_psc', spatial='Cone', + radius=2 * u.arcmin, get_query_payload=True) + assert response['radius'] == 2 + assert response['radunits'] == 'arcmin' + response = irsa.core.Irsa.query_region_async(coordinates, catalog='fp_psc', spatial='Cone', + radius=2 * u.arcmin) + assert response is not None + + +@pytest.mark.parametrize(("coordinates"), OBJ_LIST) +def test_query_region_cone(coordinates, patch_get): + result = irsa.core.Irsa.query_region(coordinates, catalog='fp_psc', spatial='Cone', + radius=2 * u.arcmin) + assert isinstance(result, Table) + + +@pytest.mark.parametrize(("coordinates"), OBJ_LIST) +def test_query_region_box_async(coordinates, patch_get): + response = irsa.core.Irsa.query_region_async(coordinates, catalog='fp_psc', spatial='Box', + width=2 * u.arcmin, get_query_payload=True) + assert response['size'] == 120 + response = irsa.core.Irsa.query_region_async(coordinates, catalog='fp_psc', spatial='Box', + width=2 * u.arcmin) + assert response is not None + + +@pytest.mark.parametrize(("coordinates"), OBJ_LIST) +def test_query_region_box(coordinates, patch_get): + result = irsa.core.Irsa.query_region(coordinates, catalog='fp_psc', spatial='Box', + width=2 * u.arcmin) + assert isinstance(result, Table) + +poly1 = [coord.ICRS(ra=10.1, dec=10.1, unit=(u.deg, u.deg)), + coord.ICRS(ra=10.0, dec=10.1, unit=(u.deg, u.deg)), + coord.ICRS(ra=10.0, dec=10.0, unit=(u.deg, u.deg))] +poly2 = [(10.1 * u.deg, 10.1 * u.deg), (10.0 * u.deg, 10.1 * u.deg), (10.0 * u.deg, 10.0 * u.deg)] + + +@pytest.mark.parametrize(("polygon"), + [poly1, + poly2 + ]) +def test_query_region_async_polygon(polygon, patch_get): + response = irsa.core.Irsa.query_region_async("m31", catalog="fp_psc", spatial="Polygon", + polygon=polygon, get_query_payload=True) + + for a, b in zip(re.split("[ ,]", response["polygon"]), + re.split("[ ,]", "10.1 +10.1,10.0 +10.1,10.0 +10.0")): + for a1, b1 in zip(a.split(), b.split()): + a1 = float(a1) + b1 = float(b1) + np.testing.assert_almost_equal(a1, b1) + + response = irsa.core.Irsa.query_region_async("m31", catalog="fp_psc", spatial="Polygon", + polygon=polygon) + assert response is not None + + +@pytest.mark.parametrize(("polygon"), + [poly1, + poly2, + ]) +def test_query_region_polygon(polygon, patch_get): + result = irsa.core.Irsa.query_region("m31", catalog="fp_psc", spatial="Polygon", + polygon=polygon) + assert isinstance(result, Table) + + +@pytest.mark.parametrize(('spatial', 'result'), zip(('Cone', 'Box', 'Polygon', 'All-Sky'), ('Cone', 'Box', 'Polygon', 'NONE'))) +def test_spatial_valdi(spatial, result): + out = irsa.core.Irsa._parse_spatial(spatial, coordinates='m31', radius=5 * u.deg, width=5 * u.deg, polygon=[(5 * u.hour, 5 * u.deg)] * 3) + assert out['spatial'] == result + + +@pytest.mark.parametrize(('spatial'), [('cone', 'box', 'polygon', 'all-Sky', 'All-sky', 'invalid', 'blah')]) +def test_spatial_invalid(spatial): + with pytest.raises(ValueError): + irsa.core.Irsa._parse_spatial(spatial, coordinates='m31') + diff --git a/astroquery/lcogt/tests/test_lcogt_remote.py b/astroquery/lcogt/tests/test_lcogt_remote.py new file mode 100644 index 0000000000..cbd8ae4c29 --- /dev/null +++ b/astroquery/lcogt/tests/test_lcogt_remote.py @@ -0,0 +1,53 @@ +# Licensed under a 3-clause BSD style license - see LICENSE.rst +from __future__ import print_function + +from astropy.tests.helper import remote_data +from astropy.table import Table +import astropy.coordinates as coord +import astropy.units as u + +import requests +import imp +imp.reload(requests) + +from ... import irsa + +OBJ_LIST = ["m31", "00h42m44.330s +41d16m07.50s", coord.Galactic(l=121.1743, b=-21.5733, unit=(u.deg, u.deg))] + + +@remote_data +class TestIrsa: + + def test_query_region_cone_async(self): + response = irsa.core.Irsa.query_region_async('m31', catalog='fp_psc', spatial='Cone', + radius=2 * u.arcmin) + assert response is not None + + def test_query_region_cone(self): + result = irsa.core.Irsa.query_region('m31', catalog='fp_psc', spatial='Cone', + radius=2 * u.arcmin) + assert isinstance(result, Table) + + def test_query_region_box_async(self): + response = irsa.core.Irsa.query_region_async("00h42m44.330s +41d16m07.50s", catalog='fp_psc', spatial='Box', + width=2 * u.arcmin) + assert response is not None + + def test_query_region_box(self): + result = irsa.core.Irsa.query_region("00h42m44.330s +41d16m07.50s", catalog='fp_psc', spatial='Box', + width=2 * u.arcmin) + assert isinstance(result, Table) + + def test_query_region_async_polygon(self): + polygon = [coord.ICRS(ra=10.1, dec=10.1, unit=(u.deg, u.deg)), + coord.ICRS(ra=10.0, dec=10.1, unit=(u.deg, u.deg)), + coord.ICRS(ra=10.0, dec=10.0, unit=(u.deg, u.deg))] + response = irsa.core.Irsa.query_region_async("m31", catalog="fp_psc", spatial="Polygon", + polygon=polygon) + assert response is not None + + def test_query_region_polygon(self): + polygon = [(10.1, 10.1), (10.0, 10.1), (10.0, 10.0)] + result = irsa.core.Irsa.query_region("m31", catalog="fp_psc", spatial="Polygon", + polygon=polygon) + assert isinstance(result, Table) From 54d4b33dba804b2e2dc588c46e971325b01d7dd8 Mon Sep 17 00:00:00 2001 From: zemogle Date: Thu, 23 Apr 2015 14:30:00 +0100 Subject: [PATCH 2/7] Modifications to get LCOGT support --- astroquery/lcogt/core.py | 52 ++++++++++++------------- astroquery/lcogt/tests/setup_package.py | 2 +- astroquery/lcogt/tests/test_lcogt.py | 34 ++++++++-------- 3 files changed, 43 insertions(+), 45 deletions(-) diff --git a/astroquery/lcogt/core.py b/astroquery/lcogt/core.py index b9062e4ee0..c30cdfdaea 100644 --- a/astroquery/lcogt/core.py +++ b/astroquery/lcogt/core.py @@ -41,7 +41,9 @@ '00 42 44.3 -41 16 08' '00h42m44.3s -41d16m08s' -catalog Required Catalog name in the IRSA database management system. +catalog Required Catalog name in the LCOGT Archive. The database of photometry + can be found using lco_cat and the database of image metadata is + found using lco_img. outfmt Optional Defines query's output format. 6 - returns a program interface in XML @@ -80,12 +82,11 @@ from . import conf from ..exceptions import TableParseError -__all__ = ['Irsa', 'IrsaClass'] +__all__ = ['Lcogt', 'LcogtClass'] -class IrsaClass(BaseQuery): - IRSA_URL = conf.server - GATOR_LIST_URL = conf.gator_list_catalogs +class LcogtClass(BaseQuery): + LCOGT_URL = conf.server TIMEOUT = conf.timeout ROW_LIMIT = conf.row_limit @@ -94,8 +95,8 @@ def query_region(self, coordinates=None, catalog=None, spatial='Cone', get_query_payload=False, verbose=False): """ This function can be used to perform either cone, box, polygon or - all-sky search in the catalogs hosted by the NASA/IPAC Infrared - Science Archive (IRSA). + all-sky search in the LCOGT public archive hosted by the NASA/IPAC Infrared + Science Archive. Parameters ---------- @@ -108,7 +109,8 @@ def query_region(self, coordinates=None, catalog=None, spatial='Cone', details). Required if spatial is ``'Cone'`` or ``'Box'``. Optional if spatial is ``'Polygon'``. catalog : str - The catalog to be used (see the *Notes* section below). + The catalog to be used. Either ``lco_img`` for image metadata or ``lco_cat`` + for photometry. spatial : str Type of spatial query: ``'Cone'``, ``'Box'``, ``'Polygon'``, and ``'All-Sky'``. If missing then defaults to ``'Cone'``. @@ -151,7 +153,7 @@ def query_region_async(self, coordinates=None, catalog=None, polygon=None, get_query_payload=False): """ This function serves the same purpose as - :meth:`~astroquery.irsa.IrsaClass.query_region`, but returns the raw + :meth:`~astroquery.irsa.LcogtClass.query_region`, but returns the raw HTTP response rather than the results in a `~astropy.table.Table`. Parameters @@ -165,7 +167,8 @@ def query_region_async(self, coordinates=None, catalog=None, details). Required if spatial is ``'Cone'`` or ``'Box'``. Optional if spatial is ``'Polygon'``. catalog : str - The catalog to be used (see the *Notes* section below). + The catalog to be used. Either ``lco_img`` for image metadata or ``lco_cat`` + for photometry. spatial : str Type of spatial query: ``'Cone'``, ``'Box'``, ``'Polygon'``, and ``'All-Sky'``. If missing then defaults to ``'Cone'``. @@ -202,8 +205,8 @@ def query_region_async(self, coordinates=None, catalog=None, if get_query_payload: return request_payload - response = commons.send_request(Irsa.IRSA_URL, request_payload, - Irsa.TIMEOUT, request_type='GET') + response = commons.send_request(Lcogt.LCOGT_URL, request_payload, + Lcogt.TIMEOUT, request_type='GET') return response def _parse_spatial(self, spatial, coordinates, radius=None, width=None, @@ -228,11 +231,11 @@ def _parse_spatial(self, spatial, coordinates, radius=None, width=None, The string must be parsable by `~astropy.coordinates.Angle`. The appropriate `~astropy.units.Quantity` object from `astropy.units` may also be used. Defaults to 10 arcsec. - width : str, `~astropy.units.Quantity` object [Required for spatial is ``'Polygon'``.] + width : str, `~astropy.units.Quantity` object [Required for spatial is ``'Polygon'``.] The string must be parsable by `~astropy.coordinates.Angle`. The appropriate `~astropy.units.Quantity` object from `astropy.units` may also be used. - polygon : list, [Required for spatial is ``'Polygon'``] + polygon : list, [Required for spatial is ``'Polygon'``] A list of ``(ra, dec)`` pairs as tuples of `astropy.coordinates.Angle`s outlinining the polygon to search in. It can also be a list of `astropy.coordinates` object or strings @@ -290,7 +293,7 @@ def _args_to_payload(self, catalog): """ request_payload = dict(catalog=catalog, outfmt=3, - outrows=Irsa.ROW_LIMIT) + outrows=Lcogt.ROW_LIMIT) return request_payload def _parse_result(self, response, verbose=False): @@ -325,7 +328,7 @@ def _parse_result(self, response, verbose=False): # Check that the results are not of length zero if len(content) == 0: - raise Exception("The IRSA server sent back an empty reply") + raise Exception("The LCOGT server sent back an empty reply") # Read it in using the astropy VO table reader try: @@ -334,7 +337,7 @@ def _parse_result(self, response, verbose=False): except Exception as ex: self.response = response self.table_parse_error = ex - raise TableParseError("Failed to parse IRSA votable! The raw response can be found " + raise TableParseError("Failed to parse LCOGT votable! The raw response can be found " "in self.response, and the error in self.table_parse_error.") # Convert to astropy.table.Table instance @@ -348,7 +351,7 @@ def _parse_result(self, response, verbose=False): def list_catalogs(self): """ - Return a dictionary of the catalogs in the IRSA Gator tool. + Return a dictionary of the catalogs in the LCOGT Gator tool. Returns ------- @@ -357,24 +360,19 @@ def list_catalogs(self): be used in query functions, and the value is the verbose description of the catalog. """ - response = commons.send_request(Irsa.GATOR_LIST_URL, dict(mode='xml'), Irsa.TIMEOUT, request_type="GET") - root = tree.fromstring(response.content) - catalogs = {} - for catalog in root.findall('catalog'): - catname = catalog.find('catname').text - desc = catalog.find('desc').text - catalogs[catname] = desc + catalogs = {'lco_cat' : 'Photometry archive from LCOGT', + 'lco_img' : 'Image metadata archive from LCOGT'} return catalogs def print_catalogs(self): """ - Display a table of the catalogs in the IRSA Gator tool. + Display a table of the catalogs in the LCOGT Gator tool. """ catalogs = self.list_catalogs() for catname in catalogs: print("{:30s} {:s}".format(catname, catalogs[catname])) -Irsa = IrsaClass() +Lcogt = LcogtClass() def _parse_coordinates(coordinates): diff --git a/astroquery/lcogt/tests/setup_package.py b/astroquery/lcogt/tests/setup_package.py index c084725f4d..a8ab506530 100644 --- a/astroquery/lcogt/tests/setup_package.py +++ b/astroquery/lcogt/tests/setup_package.py @@ -5,4 +5,4 @@ def get_package_data(): paths = [os.path.join('data', '*.xml'), ] - return {'astroquery.irsa.tests': paths} + return {'astroquery.lcogt.tests': paths} diff --git a/astroquery/lcogt/tests/test_lcogt.py b/astroquery/lcogt/tests/test_lcogt.py index e00b7f59ff..5c4796f778 100644 --- a/astroquery/lcogt/tests/test_lcogt.py +++ b/astroquery/lcogt/tests/test_lcogt.py @@ -12,8 +12,8 @@ from ...utils.testing_tools import MockResponse from ...utils import commons -from ... import irsa -from ...irsa import conf +from ... import lcogt +from ...lcogt import conf DATA_FILES = {'Cone': 'Cone.xml', 'Box': 'Box.xml', @@ -44,7 +44,7 @@ def get_mockreturn(url, params=None, timeout=10, **kwargs): @pytest.mark.parametrize(('dim'), ['5d0m0s', 0.3 * u.rad, '5h0m0s', 2 * u.arcmin]) def test_parse_dimension(dim): # check that the returned dimension is always in units of 'arcsec', 'arcmin' or 'deg' - new_dim = irsa.core._parse_dimension(dim) + new_dim = lcogt.core._parse_dimension(dim) assert new_dim.unit in ['arcsec', 'arcmin', 'deg'] @@ -53,7 +53,7 @@ def test_parse_dimension(dim): (10.0, -11, '10.0 -11') ]) def test_format_decimal_coords(ra, dec, expected): - out = irsa.core._format_decimal_coords(ra, dec) + out = lcogt.core._format_decimal_coords(ra, dec) assert out == expected @@ -61,7 +61,7 @@ def test_format_decimal_coords(ra, dec, expected): [("5h0m0s 0d0m0s", "75.0 +0.0") ]) def test_parse_coordinates(coordinates, expected): - out = irsa.core._parse_coordinates(coordinates) + out = lcogt.core._parse_coordinates(coordinates) for a, b in zip(out.split(), expected.split()): try: a = float(a) @@ -72,41 +72,41 @@ def test_parse_coordinates(coordinates, expected): def test_args_to_payload(): - out = irsa.core.Irsa._args_to_payload("fp_psc") + out = lcogt.core.Lcogt._args_to_payload("fp_psc") assert out == dict(catalog='fp_psc', outfmt=3, outrows=conf.row_limit) @pytest.mark.parametrize(("coordinates"), OBJ_LIST) def test_query_region_cone_async(coordinates, patch_get): - response = irsa.core.Irsa.query_region_async(coordinates, catalog='fp_psc', spatial='Cone', + response = lcogt.core.Lcogt.query_region_async(coordinates, catalog='fp_psc', spatial='Cone', radius=2 * u.arcmin, get_query_payload=True) assert response['radius'] == 2 assert response['radunits'] == 'arcmin' - response = irsa.core.Irsa.query_region_async(coordinates, catalog='fp_psc', spatial='Cone', + response = lcogt.core.Lcogt.query_region_async(coordinates, catalog='fp_psc', spatial='Cone', radius=2 * u.arcmin) assert response is not None @pytest.mark.parametrize(("coordinates"), OBJ_LIST) def test_query_region_cone(coordinates, patch_get): - result = irsa.core.Irsa.query_region(coordinates, catalog='fp_psc', spatial='Cone', + result = lcogt.core.Lcogt.query_region(coordinates, catalog='fp_psc', spatial='Cone', radius=2 * u.arcmin) assert isinstance(result, Table) @pytest.mark.parametrize(("coordinates"), OBJ_LIST) def test_query_region_box_async(coordinates, patch_get): - response = irsa.core.Irsa.query_region_async(coordinates, catalog='fp_psc', spatial='Box', + response = lcogt.core.Lcogt.query_region_async(coordinates, catalog='fp_psc', spatial='Box', width=2 * u.arcmin, get_query_payload=True) assert response['size'] == 120 - response = irsa.core.Irsa.query_region_async(coordinates, catalog='fp_psc', spatial='Box', + response = lcogt.core.Lcogt.query_region_async(coordinates, catalog='fp_psc', spatial='Box', width=2 * u.arcmin) assert response is not None @pytest.mark.parametrize(("coordinates"), OBJ_LIST) def test_query_region_box(coordinates, patch_get): - result = irsa.core.Irsa.query_region(coordinates, catalog='fp_psc', spatial='Box', + result = lcogt.core.Lcogt.query_region(coordinates, catalog='fp_psc', spatial='Box', width=2 * u.arcmin) assert isinstance(result, Table) @@ -121,7 +121,7 @@ def test_query_region_box(coordinates, patch_get): poly2 ]) def test_query_region_async_polygon(polygon, patch_get): - response = irsa.core.Irsa.query_region_async("m31", catalog="fp_psc", spatial="Polygon", + response = lcogt.core.Lcogt.query_region_async("m31", catalog="fp_psc", spatial="Polygon", polygon=polygon, get_query_payload=True) for a, b in zip(re.split("[ ,]", response["polygon"]), @@ -131,7 +131,7 @@ def test_query_region_async_polygon(polygon, patch_get): b1 = float(b1) np.testing.assert_almost_equal(a1, b1) - response = irsa.core.Irsa.query_region_async("m31", catalog="fp_psc", spatial="Polygon", + response = lcogt.core.Lcogt.query_region_async("m31", catalog="fp_psc", spatial="Polygon", polygon=polygon) assert response is not None @@ -141,19 +141,19 @@ def test_query_region_async_polygon(polygon, patch_get): poly2, ]) def test_query_region_polygon(polygon, patch_get): - result = irsa.core.Irsa.query_region("m31", catalog="fp_psc", spatial="Polygon", + result = lcogt.core.Lcogt.query_region("m31", catalog="fp_psc", spatial="Polygon", polygon=polygon) assert isinstance(result, Table) @pytest.mark.parametrize(('spatial', 'result'), zip(('Cone', 'Box', 'Polygon', 'All-Sky'), ('Cone', 'Box', 'Polygon', 'NONE'))) def test_spatial_valdi(spatial, result): - out = irsa.core.Irsa._parse_spatial(spatial, coordinates='m31', radius=5 * u.deg, width=5 * u.deg, polygon=[(5 * u.hour, 5 * u.deg)] * 3) + out = lcogt.core.Lcogt._parse_spatial(spatial, coordinates='m31', radius=5 * u.deg, width=5 * u.deg, polygon=[(5 * u.hour, 5 * u.deg)] * 3) assert out['spatial'] == result @pytest.mark.parametrize(('spatial'), [('cone', 'box', 'polygon', 'all-Sky', 'All-sky', 'invalid', 'blah')]) def test_spatial_invalid(spatial): with pytest.raises(ValueError): - irsa.core.Irsa._parse_spatial(spatial, coordinates='m31') + lcogt.core.Lcogt._parse_spatial(spatial, coordinates='m31') From 7a11c5e1076d454046125e3b980c9200bf3154b9 Mon Sep 17 00:00:00 2001 From: zemogle Date: Sat, 25 Apr 2015 10:28:28 +0100 Subject: [PATCH 3/7] All tests successful. Basic image meta data lookup --- astroquery/lcogt/core.py | 61 ++++++++++++++++++--- astroquery/lcogt/tests/test_lcogt.py | 23 ++++---- astroquery/lcogt/tests/test_lcogt_remote.py | 24 +++++--- 3 files changed, 80 insertions(+), 28 deletions(-) diff --git a/astroquery/lcogt/core.py b/astroquery/lcogt/core.py index c30cdfdaea..9de479ad42 100644 --- a/astroquery/lcogt/core.py +++ b/astroquery/lcogt/core.py @@ -71,6 +71,7 @@ import warnings import xml.etree.ElementTree as tree +import logging from astropy.extern import six import astropy.units as u @@ -90,13 +91,55 @@ class LcogtClass(BaseQuery): TIMEOUT = conf.timeout ROW_LIMIT = conf.row_limit - def query_region(self, coordinates=None, catalog=None, spatial='Cone', - radius=10 * u.arcsec, width=None, polygon=None, + def query_object(self, objstr, catalog=None, verbose=False): + """ + Queries the LCOGT public archive hosted at NASA/IPAC archive on target name + and returns the result as a `~astropy.table.Table`. + See examples below. + + Parameters + ---------- + objstr : str + name of object to be queried + catalog : str + name of the catalog to use. 'lco_img' for image meta data; 'lco_cat' for photometry. + + Returns + ------- + table : `~astropy.table.Table` + Query results table + """ + response = self.query_object_async(objstr, catalog=catalog) + return self._parse_result(response, verbose=verbose) + + def query_object_async(self, objstr, catalog=None): + """ + Serves the same function as `query_object`, but + only collects the reponse from the LCOGT IPAC archive and returns. + + Parameters + ---------- + objstr : str + name of object to be queried + + Returns + ------- + response : `requests.Response` + Response of the query from the server + """ + if catalog is None: + raise Exception("Catalogue name is required!") + request_payload = self._args_to_payload(catalog) + request_payload['objstr'] = objstr + response = commons.send_request(Lcogt.LCOGT_URL, request_payload, + Lcogt.TIMEOUT, request_type='GET') + return response + + def query_region(self, coordinates=None, catalog=None, spatial=None, radius=None, width=None, polygon=None, get_query_payload=False, verbose=False): """ This function can be used to perform either cone, box, polygon or - all-sky search in the LCOGT public archive hosted by the NASA/IPAC Infrared - Science Archive. + all-sky search in the LCOGT public archive hosted by the NASA/IPAC Archive. Parameters ---------- @@ -167,7 +210,7 @@ def query_region_async(self, coordinates=None, catalog=None, details). Required if spatial is ``'Cone'`` or ``'Box'``. Optional if spatial is ``'Polygon'``. catalog : str - The catalog to be used. Either ``lco_img`` for image metadata or ``lco_cat`` + The catalog to be used. Either ``'lco_img'`` for image metadata or ``'lco_cat'`` for photometry. spatial : str Type of spatial query: ``'Cone'``, ``'Box'``, ``'Polygon'``, and @@ -195,7 +238,7 @@ def query_region_async(self, coordinates=None, catalog=None, The HTTP response returned from the service """ if catalog is None: - raise Exception("Catalog name is required!") + raise Exception("Catalogue name is required!") request_payload = self._args_to_payload(catalog) request_payload.update(self._parse_spatial(spatial=spatial, @@ -293,6 +336,7 @@ def _args_to_payload(self, catalog): """ request_payload = dict(catalog=catalog, outfmt=3, + spatial=None, outrows=Lcogt.ROW_LIMIT) return request_payload @@ -317,10 +361,11 @@ def _parse_result(self, response, verbose=False): commons.suppress_vo_warnings() content = response.text + logging.debug(content) # Check if results were returned - if 'The catalog is not on the list' in content: - raise Exception("Catalog not found") + if 'The catalog is not in the list' in content: + raise Exception("Catalogue not found") # Check that object name was not malformed if 'Either wrong or missing coordinate/object name' in content: diff --git a/astroquery/lcogt/tests/test_lcogt.py b/astroquery/lcogt/tests/test_lcogt.py index 5c4796f778..f698843bdd 100644 --- a/astroquery/lcogt/tests/test_lcogt.py +++ b/astroquery/lcogt/tests/test_lcogt.py @@ -72,41 +72,40 @@ def test_parse_coordinates(coordinates, expected): def test_args_to_payload(): - out = lcogt.core.Lcogt._args_to_payload("fp_psc") - assert out == dict(catalog='fp_psc', outfmt=3, outrows=conf.row_limit) - + out = lcogt.core.Lcogt._args_to_payload("lco_img") + assert out == dict(catalog='lco_img', outfmt=3, outrows=conf.row_limit, spatial=None) @pytest.mark.parametrize(("coordinates"), OBJ_LIST) def test_query_region_cone_async(coordinates, patch_get): - response = lcogt.core.Lcogt.query_region_async(coordinates, catalog='fp_psc', spatial='Cone', + response = lcogt.core.Lcogt.query_region_async(coordinates, catalog='lco_img', spatial='Cone', radius=2 * u.arcmin, get_query_payload=True) assert response['radius'] == 2 assert response['radunits'] == 'arcmin' - response = lcogt.core.Lcogt.query_region_async(coordinates, catalog='fp_psc', spatial='Cone', + response = lcogt.core.Lcogt.query_region_async(coordinates, catalog='lco_img', spatial='Cone', radius=2 * u.arcmin) assert response is not None @pytest.mark.parametrize(("coordinates"), OBJ_LIST) def test_query_region_cone(coordinates, patch_get): - result = lcogt.core.Lcogt.query_region(coordinates, catalog='fp_psc', spatial='Cone', + result = lcogt.core.Lcogt.query_region(coordinates, catalog='lco_img', spatial='Cone', radius=2 * u.arcmin) assert isinstance(result, Table) @pytest.mark.parametrize(("coordinates"), OBJ_LIST) def test_query_region_box_async(coordinates, patch_get): - response = lcogt.core.Lcogt.query_region_async(coordinates, catalog='fp_psc', spatial='Box', + response = lcogt.core.Lcogt.query_region_async(coordinates, catalog='lco_img', spatial='Box', width=2 * u.arcmin, get_query_payload=True) assert response['size'] == 120 - response = lcogt.core.Lcogt.query_region_async(coordinates, catalog='fp_psc', spatial='Box', + response = lcogt.core.Lcogt.query_region_async(coordinates, catalog='lco_img', spatial='Box', width=2 * u.arcmin) assert response is not None @pytest.mark.parametrize(("coordinates"), OBJ_LIST) def test_query_region_box(coordinates, patch_get): - result = lcogt.core.Lcogt.query_region(coordinates, catalog='fp_psc', spatial='Box', + result = lcogt.core.Lcogt.query_region(coordinates, catalog='lco_img', spatial='Box', width=2 * u.arcmin) assert isinstance(result, Table) @@ -121,7 +120,7 @@ def test_query_region_box(coordinates, patch_get): poly2 ]) def test_query_region_async_polygon(polygon, patch_get): - response = lcogt.core.Lcogt.query_region_async("m31", catalog="fp_psc", spatial="Polygon", + response = lcogt.core.Lcogt.query_region_async("m31", catalog="lco_img", spatial="Polygon", polygon=polygon, get_query_payload=True) for a, b in zip(re.split("[ ,]", response["polygon"]), @@ -131,7 +130,7 @@ def test_query_region_async_polygon(polygon, patch_get): b1 = float(b1) np.testing.assert_almost_equal(a1, b1) - response = lcogt.core.Lcogt.query_region_async("m31", catalog="fp_psc", spatial="Polygon", + response = lcogt.core.Lcogt.query_region_async("m31", catalog="lco_img", spatial="Polygon", polygon=polygon) assert response is not None @@ -141,7 +140,7 @@ def test_query_region_async_polygon(polygon, patch_get): poly2, ]) def test_query_region_polygon(polygon, patch_get): - result = lcogt.core.Lcogt.query_region("m31", catalog="fp_psc", spatial="Polygon", + result = lcogt.core.Lcogt.query_region("m31", catalog="lco_img", spatial="Polygon", polygon=polygon) assert isinstance(result, Table) diff --git a/astroquery/lcogt/tests/test_lcogt_remote.py b/astroquery/lcogt/tests/test_lcogt_remote.py index cbd8ae4c29..bf90e7b708 100644 --- a/astroquery/lcogt/tests/test_lcogt_remote.py +++ b/astroquery/lcogt/tests/test_lcogt_remote.py @@ -10,31 +10,39 @@ import imp imp.reload(requests) -from ... import irsa +from ... import lcogt OBJ_LIST = ["m31", "00h42m44.330s +41d16m07.50s", coord.Galactic(l=121.1743, b=-21.5733, unit=(u.deg, u.deg))] @remote_data -class TestIrsa: +class TestLcogt: + + def test_query_object_meta(self): + response = lcogt.core.Lcogt.query_object_async('M1', catalog='lco_img') + assert response is not None + + def test_query_object_phot(self): + response = lcogt.core.Lcogt.query_object_async('M1', catalog='lco_cat') + assert response is not None def test_query_region_cone_async(self): - response = irsa.core.Irsa.query_region_async('m31', catalog='fp_psc', spatial='Cone', + response = lcogt.core.Lcogt.query_region_async('m31', catalog='lco_img', spatial='Cone', radius=2 * u.arcmin) assert response is not None def test_query_region_cone(self): - result = irsa.core.Irsa.query_region('m31', catalog='fp_psc', spatial='Cone', + result = lcogt.core.Lcogt.query_region('m31', catalog='lco_img', spatial='Cone', radius=2 * u.arcmin) assert isinstance(result, Table) def test_query_region_box_async(self): - response = irsa.core.Irsa.query_region_async("00h42m44.330s +41d16m07.50s", catalog='fp_psc', spatial='Box', + response = lcogt.core.Lcogt.query_region_async("00h42m44.330s +41d16m07.50s", catalog='lco_img', spatial='Box', width=2 * u.arcmin) assert response is not None def test_query_region_box(self): - result = irsa.core.Irsa.query_region("00h42m44.330s +41d16m07.50s", catalog='fp_psc', spatial='Box', + result = lcogt.core.Lcogt.query_region("00h42m44.330s +41d16m07.50s", catalog='lco_img', spatial='Box', width=2 * u.arcmin) assert isinstance(result, Table) @@ -42,12 +50,12 @@ def test_query_region_async_polygon(self): polygon = [coord.ICRS(ra=10.1, dec=10.1, unit=(u.deg, u.deg)), coord.ICRS(ra=10.0, dec=10.1, unit=(u.deg, u.deg)), coord.ICRS(ra=10.0, dec=10.0, unit=(u.deg, u.deg))] - response = irsa.core.Irsa.query_region_async("m31", catalog="fp_psc", spatial="Polygon", + response = lcogt.core.Lcogt.query_region_async("m31", catalog="lco_img", spatial="Polygon", polygon=polygon) assert response is not None def test_query_region_polygon(self): polygon = [(10.1, 10.1), (10.0, 10.1), (10.0, 10.0)] - result = irsa.core.Irsa.query_region("m31", catalog="fp_psc", spatial="Polygon", + result = lcogt.core.Lcogt.query_region("m31", catalog="lco_img", spatial="Polygon", polygon=polygon) assert isinstance(result, Table) From 0130d9aa82b80e63efb9c2144c61275bbae043ce Mon Sep 17 00:00:00 2001 From: "Adam Ginsburg (keflavich)" Date: Sat, 25 Apr 2015 11:49:57 +0200 Subject: [PATCH 4/7] update astropy_helpers version --- astropy_helpers | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astropy_helpers b/astropy_helpers index 5fd32d0edc..161773fa72 160000 --- a/astropy_helpers +++ b/astropy_helpers @@ -1 +1 @@ -Subproject commit 5fd32d0edc34f94de9640fd20865cfe5d605e499 +Subproject commit 161773fa72d916c498e0a2a513ecc24460244ac8 From d60842b003017b3acc23fb6b24b59c02919400f9 Mon Sep 17 00:00:00 2001 From: "Adam Ginsburg (keflavich)" Date: Sat, 25 Apr 2015 11:59:00 +0200 Subject: [PATCH 5/7] refactor to use basequery's _request and to use async_to_sync instead of duplicating code admittedly, this results in slightly less coherent docstrings, but it helps reduce code duplication, which is perhaps more important. Tests still pass! --- astroquery/lcogt/core.py | 133 ++++++++------------------- astroquery/lcogt/tests/test_lcogt.py | 7 +- 2 files changed, 43 insertions(+), 97 deletions(-) diff --git a/astroquery/lcogt/core.py b/astroquery/lcogt/core.py index 9de479ad42..b5749ddb49 100644 --- a/astroquery/lcogt/core.py +++ b/astroquery/lcogt/core.py @@ -79,38 +79,19 @@ import astropy.io.votable as votable from ..query import BaseQuery -from ..utils import commons +from ..utils import commons, async_to_sync from . import conf from ..exceptions import TableParseError __all__ = ['Lcogt', 'LcogtClass'] +@async_to_sync class LcogtClass(BaseQuery): LCOGT_URL = conf.server TIMEOUT = conf.timeout ROW_LIMIT = conf.row_limit - - def query_object(self, objstr, catalog=None, verbose=False): - """ - Queries the LCOGT public archive hosted at NASA/IPAC archive on target name - and returns the result as a `~astropy.table.Table`. - See examples below. - - Parameters - ---------- - objstr : str - name of object to be queried - catalog : str - name of the catalog to use. 'lco_img' for image meta data; 'lco_cat' for photometry. - - Returns - ------- - table : `~astropy.table.Table` - Query results table - """ - response = self.query_object_async(objstr, catalog=catalog) - return self._parse_result(response, verbose=verbose) + catalogs = ['lco_img', 'lco_cat'] def query_object_async(self, objstr, catalog=None): """ @@ -121,6 +102,9 @@ def query_object_async(self, objstr, catalog=None): ---------- objstr : str name of object to be queried + catalog : str + name of the catalog to use. 'lco_img' for image meta data; + 'lco_cat' for photometry. Returns ------- @@ -128,71 +112,20 @@ def query_object_async(self, objstr, catalog=None): Response of the query from the server """ if catalog is None: - raise Exception("Catalogue name is required!") + raise ValueError("Catalogue name is required!") + if catalog not in self.catalogs: + raise ValueError("Catalog name must be one of {0}" + .format(self.catalogs)) + request_payload = self._args_to_payload(catalog) request_payload['objstr'] = objstr - response = commons.send_request(Lcogt.LCOGT_URL, request_payload, - Lcogt.TIMEOUT, request_type='GET') + response = self._request(method='GET', url=self.LCOGT_URL, + params=request_payload, timeout=self.TIMEOUT) return response - def query_region(self, coordinates=None, catalog=None, spatial=None, radius=None, width=None, polygon=None, - get_query_payload=False, verbose=False): - """ - This function can be used to perform either cone, box, polygon or - all-sky search in the LCOGT public archive hosted by the NASA/IPAC Archive. - - Parameters - ---------- - coordinates : str, `astropy.coordinates` object - Gives the position of the center of the cone or box if - performing a cone or box search. The string can give coordinates - in various coordinate systems, or the name of a source that will - be resolved on the server (see `here - `_ for more - details). Required if spatial is ``'Cone'`` or ``'Box'``. Optional - if spatial is ``'Polygon'``. - catalog : str - The catalog to be used. Either ``lco_img`` for image metadata or ``lco_cat`` - for photometry. - spatial : str - Type of spatial query: ``'Cone'``, ``'Box'``, ``'Polygon'``, and - ``'All-Sky'``. If missing then defaults to ``'Cone'``. - radius : str or `~astropy.units.Quantity` object, [optional for spatial is ``'Cone'``] - The string must be parsable by `~astropy.coordinates.Angle`. The - appropriate `~astropy.units.Quantity` object from - `astropy.units` may also be used. Defaults to 10 arcsec. - width : str, `~astropy.units.Quantity` object [Required for spatial is ``'Polygon'``.] - - The string must be parsable by `~astropy.coordinates.Angle`. The - appropriate `~astropy.units.Quantity` object from `astropy.units` - may also be used. - polygon : list, [Required for spatial is ``'Polygon'``] - A list of ``(ra, dec)`` pairs (as tuples), in decimal degrees, - outlinining the polygon to search in. It can also be a list of - `astropy.coordinates` object or strings that can be parsed by - `astropy.coordinates.ICRS`. - get_query_payload : bool, optional - If `True` then returns the dictionary sent as the HTTP request. - Defaults to `False`. - verbose : bool, optional. - If `True` then displays warnings when the returned VOTable does not - conform to the standard. Defaults to `False`. - - Returns - ------- - table : `~astropy.table.Table` - A table containing the results of the query - """ - response = self.query_region_async(coordinates, catalog=catalog, - spatial=spatial, radius=radius, - width=width, polygon=polygon, - get_query_payload=get_query_payload) - if get_query_payload: - return response - return self._parse_result(response, verbose=verbose) def query_region_async(self, coordinates=None, catalog=None, - spatial='Cone', radius=10 * u.arcsec, width=None, + spatial='Cone', radius=10*u.arcsec, width=None, polygon=None, get_query_payload=False): """ This function serves the same purpose as @@ -210,16 +143,18 @@ def query_region_async(self, coordinates=None, catalog=None, details). Required if spatial is ``'Cone'`` or ``'Box'``. Optional if spatial is ``'Polygon'``. catalog : str - The catalog to be used. Either ``'lco_img'`` for image metadata or ``'lco_cat'`` - for photometry. + The catalog to be used. Either ``'lco_img'`` for image metadata or + ``'lco_cat'`` for photometry. spatial : str Type of spatial query: ``'Cone'``, ``'Box'``, ``'Polygon'``, and ``'All-Sky'``. If missing then defaults to ``'Cone'``. - radius : str or `~astropy.units.Quantity` object, [optional for spatial is ``'Cone'``] + radius : str or `~astropy.units.Quantity` object, [optional for \\ + spatial is ``'Cone'``] The string must be parsable by `~astropy.coordinates.Angle`. The appropriate `~astropy.units.Quantity` object from `astropy.units` may also be used. Defaults to 10 arcsec. - width : str, `~astropy.units.Quantity` object [Required for spatial is ``'Polygon'``.] + width : str, `~astropy.units.Quantity` object [Required for spatial \\ + is ``'Polygon'``.] The string must be parsable by `~astropy.coordinates.Angle`. The appropriate `~astropy.units.Quantity` object from `astropy.units` may also be used. @@ -238,7 +173,11 @@ def query_region_async(self, coordinates=None, catalog=None, The HTTP response returned from the service """ if catalog is None: - raise Exception("Catalogue name is required!") + raise ValueError("Catalogue name is required!") + if catalog not in self.catalogs: + raise ValueError("Catalog name must be one of {0}" + .format(self.catalogs)) + request_payload = self._args_to_payload(catalog) request_payload.update(self._parse_spatial(spatial=spatial, @@ -248,8 +187,8 @@ def query_region_async(self, coordinates=None, catalog=None, if get_query_payload: return request_payload - response = commons.send_request(Lcogt.LCOGT_URL, request_payload, - Lcogt.TIMEOUT, request_type='GET') + response = self._request(method='GET', url=self.LCOGT_URL, + params=request_payload, timeout=self.TIMEOUT) return response def _parse_spatial(self, spatial, coordinates, radius=None, width=None, @@ -307,15 +246,20 @@ def _parse_spatial(self, spatial, coordinates, radius=None, width=None, request_payload['size'] = width.to(u.arcsec).value elif spatial == 'Polygon': if coordinates is not None: - request_payload['objstr'] = coordinates if not commons._is_coordinate(coordinates) else _parse_coordinates(coordinates) + request_payload['objstr'] = (coordinates if not + commons._is_coordinate(coordinates) + else + _parse_coordinates(coordinates)) try: coordinates_list = [_parse_coordinates(c) for c in polygon] except (ValueError, TypeError): - coordinates_list = [_format_decimal_coords(*_pair_to_deg(pair)) for pair in polygon] + coordinates_list = [_format_decimal_coords(*_pair_to_deg(pair)) + for pair in polygon] request_payload['polygon'] = ','.join(coordinates_list) else: - raise ValueError("Unrecognized spatial query type. " + - "Must be one of `Cone`, `Box`, `Polygon`, or `All-Sky`.") + raise ValueError("Unrecognized spatial query type. " + "Must be one of `Cone`, `Box`, " + "`Polygon`, or `All-Sky`.") request_payload['spatial'] = spatial @@ -382,8 +326,9 @@ def _parse_result(self, response, verbose=False): except Exception as ex: self.response = response self.table_parse_error = ex - raise TableParseError("Failed to parse LCOGT votable! The raw response can be found " - "in self.response, and the error in self.table_parse_error.") + raise TableParseError("Failed to parse LCOGT votable! The raw " + " response can be found in self.response," + " and the error in self.table_parse_error.") # Convert to astropy.table.Table instance table = first_table.to_table() diff --git a/astroquery/lcogt/tests/test_lcogt.py b/astroquery/lcogt/tests/test_lcogt.py index f698843bdd..1635c1b3a5 100644 --- a/astroquery/lcogt/tests/test_lcogt.py +++ b/astroquery/lcogt/tests/test_lcogt.py @@ -20,7 +20,8 @@ 'Polygon': 'Polygon.xml'} OBJ_LIST = ["m31", "00h42m44.330s +41d16m07.50s", - commons.GalacticCoordGenerator(l=121.1743, b=-21.5733, unit=(u.deg, u.deg))] + commons.GalacticCoordGenerator(l=121.1743, b=-21.5733, unit=(u.deg, + u.deg))] def data_path(filename): @@ -31,11 +32,11 @@ def data_path(filename): @pytest.fixture def patch_get(request): mp = request.getfuncargvalue("monkeypatch") - mp.setattr(requests, 'get', get_mockreturn) + mp.setattr(lcogt.core.Lcogt, '_request', get_mockreturn) return mp -def get_mockreturn(url, params=None, timeout=10, **kwargs): +def get_mockreturn(method, url, params=None, timeout=10, **kwargs): filename = data_path(DATA_FILES[params['spatial']]) content = open(filename, 'rb').read() return MockResponse(content, **kwargs) From b7e6f70ec0ba6e2784313b1f5bb84889502e7617 Mon Sep 17 00:00:00 2001 From: "Adam Ginsburg (keflavich)" Date: Sat, 25 Apr 2015 12:04:38 +0200 Subject: [PATCH 6/7] cache and get query payload keywords and fix up catalog storage --- astroquery/lcogt/core.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/astroquery/lcogt/core.py b/astroquery/lcogt/core.py index b5749ddb49..417b500b86 100644 --- a/astroquery/lcogt/core.py +++ b/astroquery/lcogt/core.py @@ -91,9 +91,15 @@ class LcogtClass(BaseQuery): LCOGT_URL = conf.server TIMEOUT = conf.timeout ROW_LIMIT = conf.row_limit - catalogs = ['lco_img', 'lco_cat'] - def query_object_async(self, objstr, catalog=None): + @property + def catalogs(self): + """ immutable catalog listing """ + return {'lco_cat' : 'Photometry archive from LCOGT', + 'lco_img' : 'Image metadata archive from LCOGT'} + + def query_object_async(self, objstr, catalog=None, cache=True, + get_query_payload=False): """ Serves the same function as `query_object`, but only collects the reponse from the LCOGT IPAC archive and returns. @@ -119,14 +125,19 @@ def query_object_async(self, objstr, catalog=None): request_payload = self._args_to_payload(catalog) request_payload['objstr'] = objstr + if get_query_payload: + return request_payload + response = self._request(method='GET', url=self.LCOGT_URL, - params=request_payload, timeout=self.TIMEOUT) + params=request_payload, timeout=self.TIMEOUT, + cache=cache) return response def query_region_async(self, coordinates=None, catalog=None, spatial='Cone', radius=10*u.arcsec, width=None, - polygon=None, get_query_payload=False): + polygon=None, get_query_payload=False, cache=True, + ): """ This function serves the same purpose as :meth:`~astroquery.irsa.LcogtClass.query_region`, but returns the raw @@ -188,7 +199,8 @@ def query_region_async(self, coordinates=None, catalog=None, if get_query_payload: return request_payload response = self._request(method='GET', url=self.LCOGT_URL, - params=request_payload, timeout=self.TIMEOUT) + params=request_payload, timeout=self.TIMEOUT, + cache=cache) return response def _parse_spatial(self, spatial, coordinates, radius=None, width=None, @@ -350,16 +362,13 @@ def list_catalogs(self): be used in query functions, and the value is the verbose description of the catalog. """ - catalogs = {'lco_cat' : 'Photometry archive from LCOGT', - 'lco_img' : 'Image metadata archive from LCOGT'} - return catalogs + return self.catalogs def print_catalogs(self): """ Display a table of the catalogs in the LCOGT Gator tool. """ - catalogs = self.list_catalogs() - for catname in catalogs: + for catname in self.catalogs: print("{:30s} {:s}".format(catname, catalogs[catname])) Lcogt = LcogtClass() From 107af7c80161975b893a5ab8e7ccbfad0ce14de4 Mon Sep 17 00:00:00 2001 From: "Adam Ginsburg (keflavich)" Date: Sat, 25 Apr 2015 12:05:40 +0200 Subject: [PATCH 7/7] fix tests: need to catch the cache keyword --- astroquery/lcogt/tests/test_lcogt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astroquery/lcogt/tests/test_lcogt.py b/astroquery/lcogt/tests/test_lcogt.py index 1635c1b3a5..50aca01823 100644 --- a/astroquery/lcogt/tests/test_lcogt.py +++ b/astroquery/lcogt/tests/test_lcogt.py @@ -36,7 +36,7 @@ def patch_get(request): return mp -def get_mockreturn(method, url, params=None, timeout=10, **kwargs): +def get_mockreturn(method, url, params=None, timeout=10, cache=True, **kwargs): filename = data_path(DATA_FILES[params['spatial']]) content = open(filename, 'rb').read() return MockResponse(content, **kwargs)