astropy / astroquery
1
# Licensed under a 3-clause BSD style license - see LICENSE.rst
2 1
from __future__ import print_function
3

4 1
import re
5 1
from collections import namedtuple
6 1
from xml.dom.minidom import parseString
7 1
from datetime import datetime
8

9 1
import six
10 1
import astropy.units as u
11 1
import astropy.coordinates as coord
12 1
import astropy.io.votable as votable
13

14 1
from ..query import BaseQuery
15 1
from ..utils import commons
16 1
from . import conf
17 1
from ..exceptions import TableParseError, RemoteServiceError
18

19 1
__all__ = ["Ned", "NedClass"]
20

21

22 1
class NedClass(BaseQuery):
23
    """
24
    Class for querying the NED (NASA/IPAC Extragalactic Database) system
25

26
    http://ned.ipac.caltech.edu/
27
    """
28
    # make configurable
29 1
    BASE_URL = conf.server
30 1
    OBJ_SEARCH_URL = BASE_URL + 'nph-objsearch'
31 1
    ALL_SKY_URL = BASE_URL + 'nph-allsky'
32 1
    DATA_SEARCH_URL = BASE_URL + 'nph-datasearch'
33 1
    IMG_DATA_URL = BASE_URL + 'imgdata'
34 1
    SPECTRA_URL = BASE_URL + 'NEDspectra'
35 1
    TIMEOUT = conf.timeout
36 1
    Options = namedtuple('Options', ('display_name', 'cgi_name'))
37

38 1
    PHOTOMETRY_OUT = {1: Options('Data as Published and Homogenized (mJy)',
39
                                 'bot'),
40
                      2: Options('Data as Published', 'pub'),
41
                      3: Options('Homogenized Units (mJy)', 'mjy')}
42

43 1
    def query_object(self, object_name, get_query_payload=False,
44
                     verbose=False):
45
        """
46
        Queries objects by name from the NED Service and returns the Main
47
        Source Table.
48

49
        Parameters
50
        ----------
51
        object_name : str
52
            name of the identifier to query.
53
        get_query_payload : bool, optional
54
            if set to `True` then returns the dictionary sent as the HTTP
55
            request.  Defaults to `False`.
56
        verbose : bool, optional.
57
            When set to `True` displays warnings if the returned VOTable
58
            does not conform to the standard. Defaults to `False`.
59

60
        Returns
61
        -------
62
        result : `astropy.table.Table`
63
            The result of the query as an `astropy.table.Table` object.
64

65
        """
66
        # for NED's object by name
67 1
        response = self.query_object_async(object_name,
68
                                           get_query_payload=get_query_payload)
69 1
        if get_query_payload:
70 0
            return response
71 1
        result = self._parse_result(response, verbose=verbose)
72 1
        return result
73

74 1
    def query_object_async(self, object_name, get_query_payload=False):
75
        """
76
        Serves the same purpose as `~NedClass.query_object` but returns the
77
        raw HTTP response rather than the `astropy.table.Table` object.
78

79
        Parameters
80
        ----------
81
        object_name : str
82
            name of the identifier to query.
83
        get_query_payload : bool, optional
84
            if set to `True` then returns the dictionary sent as the HTTP
85
            request.  Defaults to `False`
86

87
        Returns
88
        -------
89
        response : `requests.Response`
90
            The HTTP response returned from the service
91

92
        """
93 1
        request_payload = self._request_payload_init()
94 1
        self._set_input_options(request_payload)
95 1
        self._set_output_options(request_payload)
96 1
        request_payload['objname'] = object_name
97 1
        if get_query_payload:
98 1
            return request_payload
99 1
        response = self._request("GET", url=Ned.OBJ_SEARCH_URL,
100
                                 params=request_payload, timeout=Ned.TIMEOUT)
101

102 1
        return response
103

104 1
    def query_region(self, coordinates, radius=1 * u.arcmin, equinox='J2000.0',
105
                     get_query_payload=False, verbose=False):
106
        """
107
        Used to query a region around a known identifier or given
108
        coordinates. Equivalent to the near position and near name queries
109
        from the Ned web interface.
110

111
        Parameters
112
        ----------
113
        coordinates : str or `astropy.coordinates` object
114
            The target around which to search. It may be specified as a
115
            string in which case it is resolved using online services or as
116
            the appropriate `astropy.coordinates` object. ICRS coordinates
117
            may also be entered as strings as specified in the
118
            `astropy.coordinates` module.
119
        radius : str or `~astropy.units.Quantity` object, optional
120
            The string must be parsable by `~astropy.coordinates.Angle`. The
121
            appropriate `~astropy.units.Quantity` object from
122
            `astropy.units` may also be used. Defaults to 1 arcmin.
123
        equinox : str, optional
124
            The equinox may be either J2000.0 or B1950.0. Defaults to J2000.0
125
        get_query_payload : bool, optional
126
            if set to `True` then returns the dictionary sent as the HTTP
127
            request.  Defaults to `False`.
128
        verbose : bool, optional.
129
            When set to `True` displays warnings if the returned VOTable
130
            does not conform to the standard. Defaults to `False`.
131

132
        Returns
133
        -------
134
        result : `astropy.table.Table`
135
            The result of the query as an `astropy.table.Table` object.
136

137
        """
138
        # for NED's object near name/ near region
139 1
        response = self.query_region_async(coordinates, radius=radius,
140
                                           equinox=equinox,
141
                                           get_query_payload=get_query_payload)
142 1
        if get_query_payload:
143 0
            return response
144 1
        result = self._parse_result(response, verbose=verbose)
145 1
        return result
146

147 1
    def query_region_async(self, coordinates, radius=1 * u.arcmin,
148
                           equinox='J2000.0', get_query_payload=False):
149
        """
150
        Serves the same purpose as `~NedClass.query_region` but returns the
151
        raw HTTP response rather than the `astropy.table.Table` object.
152

153
        Parameters
154
        ----------
155
        coordinates : str or `astropy.coordinates` object
156
            The target around which to search. It may be specified as a
157
            string in which case it is resolved using online services or as
158
            the appropriate `astropy.coordinates` object. ICRS coordinates
159
            may also be entered as strings as specified in the
160
            `astropy.coordinates` module.
161
        radius : str or `~astropy.units.Quantity` object, optional
162
            The string must be parsable by `astropy.coordinates.Angle`. The
163
            appropriate `~astropy.units.Quantity` object from
164
            `astropy.units` may also be used. Defaults to 1 arcmin.
165
        equinox : str, optional
166
            The equinox may be either J2000.0 or B1950.0. Defaults to J2000.0
167
        get_query_payload : bool, optional
168
            if set to `True` then returns the dictionary sent as the HTTP
169
            request.  Defaults to `False`.
170

171
        Returns
172
        -------
173
        response : `requests.Response`
174
            The HTTP response returned from the service
175

176
        """
177 1
        request_payload = self._request_payload_init()
178 1
        self._set_input_options(request_payload)
179 1
        self._set_output_options(request_payload)
180
        # if its a name then query near name
181 1
        if not commons._is_coordinate(coordinates):
182 1
            request_payload['objname'] = coordinates
183 1
            request_payload['search_type'] = 'Near Name Search'
184 1
            request_payload['radius'] = coord.Angle(radius).arcmin
185
        else:
186 1
            try:
187 1
                c = commons.parse_coordinates(coordinates)
188 1
                if c.frame.name == 'galactic':
189 1
                    request_payload['in_csys'] = 'Galactic'
190 1
                    request_payload['lon'] = c.l.degree
191 1
                    request_payload['lat'] = c.b.degree
192
                # for any other, convert to ICRS and send
193
                else:
194 1
                    request_payload['in_csys'] = 'Equatorial'
195 1
                    ra, dec = commons.coord_to_radec(c)
196 1
                    request_payload['lon'] = ra
197 1
                    request_payload['lat'] = dec
198 1
                request_payload['search_type'] = 'Near Position Search'
199 1
                request_payload['in_equinox'] = equinox
200 1
                request_payload['radius'] = coord.Angle(radius).arcmin
201 0
            except (u.UnitsError, TypeError):
202 0
                raise TypeError("Coordinates not specified correctly")
203 1
        if get_query_payload:
204 1
            return request_payload
205 1
        response = self._request("GET", url=Ned.OBJ_SEARCH_URL,
206
                                 params=request_payload, timeout=Ned.TIMEOUT)
207 1
        return response
208

209 1
    def query_region_iau(self, iau_name, frame='Equatorial', equinox='B1950.0',
210
                         get_query_payload=False, verbose=False):
211
        """
212
        Used to query the Ned service via the IAU name. Equivalent to the
213
        IAU format queries of the Web interface.
214

215
        Parameters
216
        ----------
217
        iau_name : str
218
            IAU coordinate-based name of target on which search is
219
            centered. Definition of IAU coordinates at
220
            http://cdsweb.u-strasbg.fr/Dic/iau-spec.html.
221
        frame : str, optional
222
            May be one of 'Equatorial', 'Ecliptic', 'Galactic',
223
            'SuperGalactic'.  Defaults to 'Equatorial'.
224
        equinox : str, optional
225
            The equinox may be one of J2000.0 or B1950.0. Defaults to B1950.0
226
        get_query_payload : bool, optional
227
            if set to `True` then returns the dictionary sent as the HTTP
228
            request.  Defaults to `False`
229

230
        verbose : bool, optional.
231
            When set to `True` displays warnings if the returned VOTable
232
            does not conform to the standard. Defaults to `False`.
233

234
        Returns
235
        -------
236
        result : `astropy.table.Table`
237
            The result of the query as an `astropy.table.Table` object.
238

239
        """
240 1
        response = self.query_region_iau_async(
241
            iau_name, frame='Equatorial', equinox='B1950.0',
242
            get_query_payload=get_query_payload)
243 1
        if get_query_payload:
244 0
            return response
245 1
        result = self._parse_result(response, verbose=verbose)
246 1
        return result
247

248 1
    def query_region_iau_async(self, iau_name, frame='Equatorial',
249
                               equinox='B1950.0', get_query_payload=False):
250
        """
251
        Serves the same purpose as `~NedClass.query_region_iau` but returns
252
        the raw HTTP response rather than the `astropy.table.Table` object.
253

254
        Parameters
255
        ----------
256
        iau_name : str
257
            IAU coordinate-based name of target on which search is
258
            centered. Definition of IAU coordinates at
259
            http://cdsweb.u-strasbg.fr/Dic/iau-spec.html.
260
        frame : str, optional
261
            May be one of 'Equatorial', 'Ecliptic', 'Galactic',
262
            'SuperGalactic'.  Defaults to 'Equatorial'.
263
        equinox : str, optional
264
            The equinox may be one of J2000.0 or B1950.0. Defaults to B1950.0
265
        get_query_payload : bool, optional
266
            if set to `True` then returns the dictionary sent as the HTTP
267
            request.  Defaults to `False`
268

269
        Returns
270
        -------
271
        response : `requests.Response`
272
            The HTTP response returned from the service.
273

274
        """
275 1
        request_payload = self._request_payload_init()
276 1
        self._set_input_options(request_payload)
277 1
        self._set_output_options(request_payload)
278 1
        request_payload['search_type'] = 'IAU Search'
279 1
        request_payload['iau_name'] = iau_name
280 1
        request_payload['in_csys'] = frame
281 1
        request_payload['in_equinox'] = equinox
282 1
        if get_query_payload:
283 1
            return request_payload
284 1
        response = self._request("GET", url=Ned.OBJ_SEARCH_URL,
285
                                 params=request_payload, timeout=Ned.TIMEOUT)
286 1
        return response
287

288 1
    def query_refcode(self, refcode, get_query_payload=False, verbose=False):
289
        """
290
        Used to retrieve all objects contained in a particular
291
        reference. Equivalent to by refcode queries of the web interface.
292

293
        Parameters
294
        ----------
295
        refcode : str
296
            19 digit reference code.  Example: 1997A&A...323...31K.
297
        get_query_payload : bool, optional
298
            if set to `True` then returns the dictionary sent as the HTTP
299
            request.  Defaults to `False`.
300
        verbose : bool, optional.
301
            When set to `True` displays warnings if the returned VOTable
302
            does not conform to the standard. Defaults to `False`.
303

304
        Returns
305
        -------
306
        result : `astropy.table.Table`
307
            The result of the query as an `astropy.table.Table` object.
308

309
        """
310 1
        response = self.query_refcode_async(
311
            refcode, get_query_payload=get_query_payload)
312 1
        if get_query_payload:
313 0
            return response
314 1
        result = self._parse_result(response, verbose=verbose)
315 1
        return result
316

317 1
    def query_refcode_async(self, refcode, get_query_payload=False):
318
        """
319
        Serves the same purpose as `~NedClass.query_region` but returns the
320
        raw HTTP response rather than the `astropy.table.Table` object.
321

322
        Parameters
323
        ----------
324
        refcode : str
325
            19 digit reference code.  Example: 1997A&A...323...31K.
326
        get_query_payload : bool, optional
327
            if set to `True` then returns the dictionary sent as the HTTP
328
            request.  Defaults to `False`.
329

330
        Returns
331
        -------
332
        response : `requests.Response`
333
            The HTTP response returned from the service.
334

335
        """
336 1
        request_payload = self._request_payload_init()
337 1
        self._set_input_options(request_payload)
338 1
        self._set_output_options(request_payload)
339 1
        request_payload['search_type'] = 'Search'
340 1
        request_payload['refcode'] = refcode
341 1
        if get_query_payload:
342 1
            return request_payload
343 1
        response = self._request("GET", url=Ned.OBJ_SEARCH_URL,
344
                                 params=request_payload, timeout=Ned.TIMEOUT)
345 1
        return response
346

347 1
    def get_images(self, object_name, get_query_payload=False,
348
                   show_progress=True):
349
        """
350
        Query function to fetch FITS images for a given identifier.
351

352
        Parameters
353
        ----------
354
        object_name : str
355
            name of the identifier to query.
356
        get_query_payload : bool, optional
357
            if set to `True` then returns the dictionary sent as the HTTP
358
            request.  Defaults to `False`
359

360
        Returns
361
        -------
362
        A list of `~astropy.io.fits.HDUList` objects
363

364
        """
365 1
        readable_objs = self.get_images_async(
366
            object_name, get_query_payload=get_query_payload,
367
            show_progress=show_progress)
368

369 1
        if get_query_payload:
370 0
            return readable_objs
371 1
        return [obj.get_fits() for obj in readable_objs]
372

373 1
    def get_images_async(self, object_name, get_query_payload=False,
374
                         show_progress=True):
375
        """
376
        Serves the same purpose as `~NedClass.get_images` but returns
377
        file-handlers to the remote files rather than downloading them.
378

379
        Parameters
380
        ----------
381
        object_name : str
382
            name of the identifier to query.
383
        get_query_payload : bool, optional
384
            if set to `True` then returns the dictionary sent as the HTTP
385
            request.  Defaults to `False`
386

387
        Returns
388
        --------
389
        A list of context-managers that yield readable file-like objects
390

391
        """
392 1
        image_urls = self.get_image_list(object_name,
393
                                         get_query_payload=get_query_payload)
394 1
        if get_query_payload:
395 0
            return image_urls
396 1
        return [commons.FileContainer(U, encoding='binary',
397
                                      show_progress=show_progress)
398
                for U in image_urls]
399

400 1
    def get_spectra(self, object_name, get_query_payload=False,
401
                    show_progress=True):
402
        """
403
        Query function to fetch FITS files of spectra for a given identifier.
404

405
        Parameters
406
        ----------
407
        object_name : str
408
            name of the identifier to query.
409
        get_query_payload : bool, optional
410
            if set to `True` then returns the dictionary sent as the HTTP
411
            request.  Defaults to `False`
412

413
        Returns
414
        -------
415
        A list of `~astropy.io.fits.HDUList` objects
416

417
        """
418 0
        readable_objs = self.get_spectra_async(
419
            object_name, get_query_payload=get_query_payload,
420
            show_progress=show_progress)
421

422 0
        if get_query_payload:
423 0
            return readable_objs
424 0
        return [obj.get_fits() for obj in readable_objs]
425

426 1
    def get_spectra_async(self, object_name, get_query_payload=False,
427
                          show_progress=True):
428
        """
429
        Serves the same purpose as `~NedClass.get_spectra` but returns
430
        file-handlers to the remote files rather than downloading them.
431

432
        Parameters
433
        ----------
434
        object_name : str
435
            name of the identifier to query.
436
        get_query_payload : bool, optional
437
            if set to `True` then returns the dictionary sent as the HTTP
438
            request.  Defaults to `False`
439

440
        Returns
441
        --------
442
        A list of context-managers that yield readable file-like objects
443

444
        """
445 0
        image_urls = self.get_image_list(object_name, item='spectra',
446
                                         get_query_payload=get_query_payload)
447 0
        if get_query_payload:
448 0
            return image_urls
449 0
        return [commons.FileContainer(U, encoding='binary',
450
                                      show_progress=show_progress)
451
                for U in image_urls]
452

453 1
    def get_image_list(self, object_name, item='image',
454
                       get_query_payload=False):
455
        """
456
        Helper function that returns a list of urls from which to download
457
        the FITS images.
458

459
        Parameters
460
        ----------
461
        object_name : str
462
            name of the identifier to query.
463
        get_query_payload : bool, optional
464
            if set to `True` then returns the dictionary sent as the HTTP
465
            request.  Defaults to `False`
466
        item : str, optional
467
            Can be either 'image' or 'spectra'. Defaults to 'image'.
468
            Required to decide the right URL to query.
469

470
        Returns
471
        -------
472
        list of image urls
473

474
        """
475 1
        request_payload = dict(objname=object_name)
476 1
        if item == 'spectra':
477 0
            request_payload['extend'] = 'multi'
478 0
            request_payload['detail'] = 0
479 0
            request_payload['numpp'] = 50
480 0
            request_payload['preview'] = 0
481 1
        if get_query_payload:
482 1
            return request_payload
483 1
        url = Ned.SPECTRA_URL if item == 'spectra' else Ned.IMG_DATA_URL
484 1
        response = self._request("GET", url=url, params=request_payload,
485
                                 timeout=Ned.TIMEOUT)
486 1
        return self.extract_image_urls(response.text)
487

488 1
    def extract_image_urls(self, html_in):
489
        """
490
        Helper function that uses regexps to extract the image urls from the
491
        given HTML.
492

493
        Parameters
494
        ----------
495
        html_in : str
496
            source from which the urls are to be extracted
497

498
        """
499 1
        base_url = 'http://ned.ipac.caltech.edu'
500 1
        pattern = re.compile(
501
            r'<a\s+href\s*?="?\s*?(.+?fits.gz)"?\s*?>\s*?(?:Retrieve|FITS)</a>',
502
            re.IGNORECASE)
503 1
        matched_urls = pattern.findall(html_in)
504 1
        url_list = [base_url + img_url for img_url in matched_urls]
505 1
        return url_list
506

507 1
    def get_table(self, object_name, table='photometry',
508
                  get_query_payload=False, verbose=False, **kwargs):
509
        """
510
        Fetches the specified data table for the object from NED and returns
511
        it as an `astropy.table.Table`.
512

513
        Parameters
514
        ----------
515
        object_name : str
516
            name of the identifier to query.
517
        table : str, optional
518
            Must be one of
519
            ['photometry'|'positions'|'diameters'|'redshifts'|'references'|'object_notes'].
520
            Specifies the type of data-table that must be fetched for the
521
            given object. Defaults to 'photometry'.
522
        output_table_format : int, [optional for photometry]
523
            specifies the format of the output table. Must be 1, 2 or 3.
524
            Defaults to 1. These options stand for:
525
            (1) Data as Published and Homogenized (mJy)
526
            (2) Data as Published
527
            (3) Homogenized Units (mJy)
528
        from_year : int, [optional for references]
529
            4 digit year from which to get the references. Defaults to 1800
530
        to_year : int, [optional for references]
531
            4 digit year upto which to fetch the references. Defaults to the
532
            current year.
533
        extended_search : bool, [optional for references]
534
            If set to `True`, returns all objects beginning with the same
535
            identifier name.  Defaults to `False`.
536
        get_query_payload : bool, optional
537
            if set to `True` then returns the dictionary sent as the HTTP
538
            request.  Defaults to `False`.
539
        verbose : bool, optional.
540
            When set to `True` displays warnings if the returned VOTable
541
            does not conform to the standard. Defaults to `False`.
542

543
        Returns
544
        -------
545
        result : `astropy.table.Table`
546
            The result of the query as an `astropy.table.Table` object.
547

548
        """
549 1
        response = self.get_table_async(object_name, table=table,
550
                                        get_query_payload=get_query_payload,
551
                                        **kwargs)
552 1
        if get_query_payload:
553 0
            return response
554 1
        result = self._parse_result(response, verbose=verbose)
555 1
        return result
556

557 1
    def get_table_async(self, object_name, table='photometry',
558
                        get_query_payload=False, **kwargs):
559
        """
560
        Serves the same purpose as `~NedClass.query_region` but returns the
561
        raw HTTP response rather than the `astropy.table.Table` object.
562

563
        Parameters
564
        ----------
565
        object_name : str
566
            name of the identifier to query.
567
        table : str, optional
568
            Must be one of
569
            ['photometry'|'positions'|'diameters'|'redshifts'|'references'|'object_notes'].
570
            Specifies the type of data-table that must be fetched for the
571
            given object. Defaults to 'photometry'.
572
        from_year : int, [optional for references]
573
            4 digit year from which to get the references. Defaults to 1800
574
        to_year : int, [optional for references]
575
            4 digit year upto which to fetch the references. Defaults to the
576
            current year.
577
        extended_search : bool, [optional for references]
578
            If set to `True`, returns all objects beginning with the same
579
            identifier name.  Defaults to `False`.
580
        get_query_payload : bool, optional
581
            if set to `True` then returns the dictionary sent as the HTTP
582
            request.  Defaults to `False`.
583

584
        Returns
585
        -------
586
        response : `requests.Response`
587
            The HTTP response returned from the service.
588

589
        """
590 1
        SEARCH_TYPE = dict(photometry='Photometry',
591
                           diameters='Diameters',
592
                           positions='Positions',
593
                           redshifts='Redshifts',
594
                           references='Reference',
595
                           object_notes='Notes')
596 1
        request_payload = dict(of='xml_main')
597 1
        request_payload['objname'] = object_name
598 1
        request_payload['search_type'] = SEARCH_TYPE[table]
599 1
        if table == 'photometry':
600 1
            output_table_format = 1
601 1
            request_payload['meas_type'] = Ned.PHOTOMETRY_OUT[
602
                output_table_format].cgi_name
603 1
        if table == 'references':
604 1
            request_payload['ref_extend'] = (
605
                'yes' if kwargs.get('extended_search') else 'no')
606 1
            request_payload['begin_year'] = kwargs.get('from_year', 1800)
607 1
            request_payload['end_year'] = kwargs.get('to_year',
608
                                                     datetime.now().year)
609 1
        if get_query_payload:
610 1
            return request_payload
611 1
        response = self._request("GET", url=Ned.DATA_SEARCH_URL,
612
                                 params=request_payload, timeout=Ned.TIMEOUT)
613 1
        return response
614

615 1
    def _request_payload_init(self):
616
        """
617
        Initializes common cgi-parameters for all queries.
618

619
        Returns
620
        -------
621
        request_payload : dict
622

623
        """
624 1
        request_payload = dict(of='xml_main')
625
        # common settings for all queries as per NED guidelines
626
        # for more see <http://ned.ipac.caltech.edu/help/guidelines_auto.html>
627 1
        request_payload['img_stamp'] = 'NO'
628 1
        request_payload['extend'] = 'no'
629 1
        request_payload['list_limit'] = 0
630 1
        return request_payload
631

632 1
    def _set_input_options(self, request_payload):
633
        """
634
        Supports setting of input options for certain queries
635

636
        Parameters
637
        ----------
638
        request_payload : dict
639

640
        """
641 1
        request_payload['hconst'] = conf.hubble_constant
642 1
        request_payload['omegam'] = 0.27
643 1
        request_payload['omegav'] = 0.73
644 1
        request_payload['corr_z'] = conf.correct_redshift
645

646 1
    def _set_output_options(self, request_payload):
647
        """
648
        Supports setting of output options for certain queries
649

650
        Parameters
651
        ----------
652
        request_payload : dict
653

654
        """
655 1
        request_payload['out_csys'] = conf.output_coordinate_frame
656 1
        request_payload['out_equinox'] = conf.output_equinox
657 1
        request_payload['obj_sort'] = conf.sort_output_by
658

659 1
    def _parse_result(self, response, verbose=False):
660
        """
661
        Parses the raw HTTP response and returns it as an
662
        `astropy.table.Table`.
663

664
        Parameters
665
        ----------
666
        response : `requests.Response`
667
            The HTTP response object
668
        verbose : bool, optional
669
            Defaults to false. When true it will display warnings whenever
670
            the VOtable returned from the service doesn't conform to the
671
            standard.
672

673
        Returns
674
        -------
675
        table : `astropy.table.Table`
676

677
        """
678 1
        if not verbose:
679 1
            commons.suppress_vo_warnings()
680 1
        try:
681 1
            tf = six.BytesIO(response.content)
682 1
            first_table = votable.parse(tf, pedantic=False).get_first_table()
683 1
            table = first_table.to_table(use_names_over_ids=True)
684 1
            return table
685 1
        except Exception as ex:
686 1
            (is_valid, err_msg) = _check_ned_valid(response.content)
687 1
            if not is_valid:
688 1
                if err_msg:
689 1
                    raise RemoteServiceError(
690
                        "The remote service returned the following error "
691
                        "message.\nERROR: {err_msg}".format(err_msg=err_msg))
692
                else:
693 0
                    raise RemoteServiceError(
694
                        "The remote service returned an error, but with no "
695
                        "message.")
696
            else:
697 0
                self.response = response
698 0
                self.table_parse_error = ex
699 0
                raise TableParseError(
700
                    "Failed to parse NED result! The raw response can be "
701
                    "found in self.response, and the error in "
702
                    "self.table_parse_error.")
703

704

705 1
Ned = NedClass()
706

707

708 1
def _check_ned_valid(string):
709
    """
710
    Checks if the VOTable returned has an error parameter
711

712
    Parameters
713
    ---------
714
    string : The VOTable as a string
715

716
    Returns
717
    -------
718
    retval : bool
719
        `False` if error parameter found, `True` otherwise.
720
    errmsg : str
721
        The string containing the error message if it exists, `None` otherwise.
722

723
    """
724
    # Routine assumes input is valid Table unless error parameter is found.
725 1
    retval = True
726 1
    errmsg = None
727 1
    strdom = parseString(string)
728 1
    p = strdom.getElementsByTagName('PARAM')
729

730 1
    if len(p) > 1:
731 1
        if 'name' in p[1].attributes.keys():
732 1
            n = p[1].attributes['name']
733 1
            errstr = n.value
734

735 1
            if errstr == 'Error':
736 1
                if 'value' in p[1].attributes.keys():
737 1
                    m = p[1].attributes['value']
738 1
                    errmsg = m.value
739 1
                retval = False
740

741 1
    return (retval, errmsg)

Read our documentation on viewing source code .

Loading