12
12
13
13
"""
14
14
import re
15
+ from getpass import getpass
15
16
from ...utils .tap .core import TapPlus
16
- from ...query import BaseQuery
17
+ from ...query import BaseQuery , QueryWithLogin
17
18
import shutil
18
19
import cgi
19
20
from pathlib import Path
20
21
import tarfile
21
22
import os
23
+ import configparser
22
24
23
25
from astropy .io import fits
24
26
from . import conf
25
27
from astroquery import log
26
28
from astropy .coordinates import SkyCoord
27
29
from ...exceptions import LoginError
28
30
29
-
30
31
__all__ = ['XMMNewton' , 'XMMNewtonClass' ]
31
32
32
33
33
34
class XMMNewtonClass (BaseQuery ):
34
-
35
35
data_url = conf .DATA_ACTION
36
36
data_aio_url = conf .DATA_ACTION_AIO
37
37
metadata_url = conf .METADATA_ACTION
38
38
TIMEOUT = conf .TIMEOUT
39
39
40
40
def __init__ (self , tap_handler = None ):
41
41
super (XMMNewtonClass , self ).__init__ ()
42
+ self .configuration = configparser .ConfigParser ()
42
43
43
44
if tap_handler is None :
44
- self ._tap = TapPlus (url = "http://nxsa.esac.esa.int"
45
- "/tap-server/tap/" )
45
+ self ._tap = TapPlus (url = "https://nxsa.esac.esa.int/tap-server/tap" )
46
46
else :
47
47
self ._tap = tap_handler
48
48
self ._rmf_ftp = str ("http://sasdev-xmm.esac.esa.int/pub/ccf/constituents/extras/responses/" )
49
49
50
50
def download_data (self , observation_id , * , filename = None , verbose = False ,
51
- cache = True , ** kwargs ):
51
+ cache = True , prop = False , credentials_file = None , ** kwargs ):
52
52
"""
53
53
Download data from XMM-Newton
54
54
@@ -63,6 +63,13 @@ def download_data(self, observation_id, *, filename=None, verbose=False,
63
63
verbose : bool
64
64
optional, default 'False'
65
65
flag to display information about the process
66
+ prop: boolean
67
+ optional, default 'False'
68
+ flag to download proprietary data, the method will then ask the user to
69
+ input their username and password either manually or using the credentials_file
70
+ credentials_file: string
71
+ optional, default None
72
+ path to where the users config.ini file is stored with their username and password
66
73
level : string
67
74
level to download, optional, by default everything is downloaded
68
75
values: ODF, PPS
@@ -94,48 +101,44 @@ def download_data(self, observation_id, *, filename=None, verbose=False,
94
101
file format, optional, by default all formats
95
102
values: ASC, ASZ, FTZ, HTM, IND, PDF, PNG
96
103
97
-
98
104
Returns
99
105
-------
100
106
None if not verbose. It downloads the observation indicated
101
107
If verbose returns the filename
102
108
"""
103
- if filename is not None :
104
- filename = os .path .splitext (filename )[0 ]
105
109
106
- link = self .data_aio_url + "obsno=" + observation_id
110
+ # create url to access the aio
111
+ link = self ._create_link (observation_id , ** kwargs )
107
112
108
- link = link + "" .join ("&{0}={1}" .format (key , val )
109
- for key , val in kwargs .items ())
113
+ # If the user wants to access proprietary data, ask them for their credentials
114
+ if prop :
115
+ username , password = self ._get_username_and_password (credentials_file )
116
+ link = f"{ link } &AIOUSER={ username } &AIOPWD={ password } "
110
117
111
118
if verbose :
112
119
log .info (link )
113
120
114
- # we can cache this HEAD request - the _download_file one will check
115
- # the file size and will never cache
116
- response = self ._request ('HEAD' , link , save = False , cache = cache )
117
-
118
- # Get original extension
119
- if 'Content-Type' in response .headers and 'text' not in response .headers ['Content-Type' ]:
120
- _ , params = cgi .parse_header (response .headers ['Content-Disposition' ])
121
- else :
122
- if response .status_code == 401 :
123
- error = "Data protected by proprietary rights. Please check your credentials"
124
- raise LoginError (error )
125
- response .raise_for_status ()
126
-
121
+ # get response of created url
122
+ params = self ._request_link (link , cache )
127
123
r_filename = params ["filename" ]
128
124
suffixes = Path (r_filename ).suffixes
129
125
130
- if filename is None :
131
- filename = observation_id
132
-
133
- filename += "" .join (suffixes )
134
-
135
- self ._download_file (link , filename , head_safe = True , cache = cache )
126
+ # get desired filename
127
+ filename = self ._create_filename (filename , observation_id , suffixes )
128
+ """
129
+ If prop we change the log level so that it is above 20, this is to stop a log.debug (line 431) in query.py.
130
+ This debug reveals the url being sent which in turn reveals the users username and password
131
+ """
132
+ if prop :
133
+ previouslevel = log .getEffectiveLevel ()
134
+ log .setLevel (21 )
135
+ self ._download_file (link , filename , head_safe = True , cache = cache )
136
+ log .setLevel (previouslevel )
137
+ else :
138
+ self ._download_file (link , filename , head_safe = True , cache = cache )
136
139
137
140
if verbose :
138
- log .info ("Wrote {0 } to {1}" . format ( link , filename ) )
141
+ log .info (f "Wrote { link } to { filename } " )
139
142
140
143
def get_postcard (self , observation_id , * , image_type = "OBS_EPIC" ,
141
144
filename = None , verbose = False ):
@@ -186,12 +189,12 @@ def get_postcard(self, observation_id, *, image_type="OBS_EPIC",
186
189
else :
187
190
filename = observation_id + ".png"
188
191
189
- log .info ("Copying file to {0 }..." . format ( filename ) )
192
+ log .info (f "Copying file to { filename } ..." )
190
193
191
194
shutil .move (local_filepath , filename )
192
195
193
196
if verbose :
194
- log .info ("Wrote {0 } to {1}" . format ( link , filename ) )
197
+ log .info (f "Wrote { link } to { filename } " )
195
198
196
199
return filename
197
200
@@ -273,14 +276,54 @@ def get_columns(self, table_name, *, only_names=True, verbose=False):
273
276
break
274
277
275
278
if columns is None :
276
- raise ValueError ("table name specified is not found in "
277
- "XSA TAP service" )
279
+ raise ValueError ("table name specified is not found in XSA TAP service" )
278
280
279
281
if only_names :
280
282
return [c .name for c in columns ]
281
283
else :
282
284
return columns
283
285
286
+ def _create_link (self , observation_id , ** kwargs ):
287
+ link = f"{ self .data_aio_url } obsno={ observation_id } "
288
+ link = link + "" .join ("&{0}={1}" .format (key , val )
289
+ for key , val in kwargs .items ())
290
+ return link
291
+
292
+ def _request_link (self , link , cache ):
293
+ # we can cache this HEAD request - the _download_file one will check
294
+ # the file size and will never cache
295
+ response = self ._request ('HEAD' , link , save = False , cache = cache )
296
+ # Get original extension
297
+ if 'Content-Type' in response .headers and 'text' not in response .headers ['Content-Type' ]:
298
+ _ , params = cgi .parse_header (response .headers ['Content-Disposition' ])
299
+ elif response .status_code == 401 :
300
+ error = "Data protected by proprietary rights. Please check your credentials"
301
+ raise LoginError (error )
302
+ elif 'Content-Type' not in response .headers :
303
+ error = "Incorrect credentials"
304
+ raise LoginError (error )
305
+ response .raise_for_status ()
306
+ return params
307
+
308
+ def _get_username_and_password (self , credentials_file ):
309
+ if credentials_file is not None :
310
+ self .configuration .read (credentials_file )
311
+ xmm_username = self .configuration .get ("xmm_newton" , "username" )
312
+ password = self .configuration .get ("xmm_newton" , "password" )
313
+ else :
314
+ xmm_username = input ("Username: " )
315
+ password , password_from_keyring = QueryWithLogin ._get_password (self , service_name = "xmm_newton" ,
316
+ username = xmm_username , reenter = False )
317
+ return xmm_username , password
318
+
319
+ def _create_filename (self , filename , observation_id , suffixes ):
320
+ if filename is not None :
321
+ filename = os .path .splitext (filename )[0 ]
322
+ else :
323
+ filename = observation_id
324
+ filename += "" .join (suffixes )
325
+ return filename
326
+
284
327
def _parse_filename (self , filename ):
285
328
"""Parses the file's name of a product
286
329
@@ -572,9 +615,9 @@ def get_epic_metadata(self, *, target_name=None,
572
615
Tables containing the metadata of the target
573
616
"""
574
617
if not target_name and not coordinates :
575
- raise Exception ("Input parameters needed, "
576
- "please provide the name "
577
- "or the coordinates of the target" )
618
+ raise ValueError ("Input parameters needed, "
619
+ "please provide the name "
620
+ "or the coordinates of the target" )
578
621
579
622
epic_source = {"table" : "xsa.v_epic_source" ,
580
623
"column" : "epic_source_equatorial_spoint" }
@@ -592,37 +635,37 @@ def get_epic_metadata(self, *, target_name=None,
592
635
c = SkyCoord .from_name (target_name , parse = True )
593
636
594
637
if type (c ) is not SkyCoord :
595
- raise Exception ("The coordinates must be an "
638
+ raise TypeError ("The coordinates must be an "
596
639
"astroquery.coordinates.SkyCoord object" )
597
640
if not radius :
598
641
radius = 0.1
599
642
600
643
query_fmt = ("select {} from {} "
601
644
"where 1=contains({}, circle('ICRS', {}, {}, {}));" )
602
645
epic_source_table = self .query_xsa_tap (query_fmt .format (cols ,
603
- epic_source ["table" ],
604
- epic_source ["column" ],
605
- c .ra .degree ,
606
- c .dec .degree ,
607
- radius ))
646
+ epic_source ["table" ],
647
+ epic_source ["column" ],
648
+ c .ra .degree ,
649
+ c .dec .degree ,
650
+ radius ))
608
651
cat_4xmm_table = self .query_xsa_tap (query_fmt .format (cols ,
609
- cat_4xmm ["table" ],
610
- cat_4xmm ["column" ],
611
- c .ra .degree ,
612
- c .dec .degree ,
613
- radius ))
652
+ cat_4xmm ["table" ],
653
+ cat_4xmm ["column" ],
654
+ c .ra .degree ,
655
+ c .dec .degree ,
656
+ radius ))
614
657
stack_4xmm_table = self .query_xsa_tap (query_fmt .format (cols ,
615
- stack_4xmm ["table" ],
616
- stack_4xmm ["column" ],
617
- c .ra .degree ,
618
- c .dec .degree ,
619
- radius ))
658
+ stack_4xmm ["table" ],
659
+ stack_4xmm ["column" ],
660
+ c .ra .degree ,
661
+ c .dec .degree ,
662
+ radius ))
620
663
slew_source_table = self .query_xsa_tap (query_fmt .format (cols ,
621
- slew_source ["table" ],
622
- slew_source ["column" ],
623
- c .ra .degree ,
624
- c .dec .degree ,
625
- radius ))
664
+ slew_source ["table" ],
665
+ slew_source ["column" ],
666
+ c .ra .degree ,
667
+ c .dec .degree ,
668
+ radius ))
626
669
return epic_source_table , cat_4xmm_table , stack_4xmm_table , slew_source_table
627
670
628
671
def get_epic_lightcurve (self , filename , source_number , * ,
0 commit comments