p2api 1.0.6__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,304 @@
1
+ import datetime
2
+ import logging
3
+ import p2api
4
+ import sys
5
+ import time
6
+ import getpass
7
+ import keyring
8
+ import re
9
+
10
+ import astropy.units as u
11
+ from astropy.coordinates import EarthLocation
12
+
13
+ # The GPS coordinates of the UTs
14
+ # https://www.eso.org/sci/facilities/paranal/astroclimate/site.html
15
+ UT1_loc = EarthLocation.from_geodetic('-70d24m18.27s', '-24d37m39.44s', (2635.43+13.044)*u.m)
16
+ UT2_loc = EarthLocation.from_geodetic('-70d24m17.39s', '-24d37m37.80s', (2635.43+13.044)*u.m)
17
+ UT3_loc = EarthLocation.from_geodetic('-70d24m16.32s', '-24d37m36.64s', (2635.43+13.044)*u.m)
18
+ UT4_loc = EarthLocation.from_geodetic('-70d24m14.25s', '-24d37m37.36s', (2635.43+13.044)*u.m)
19
+ VST_loc = EarthLocation.from_geodetic('-70d24m14.27s', '-24d37m34.79s', (2635.43+4.5399)*u.m)
20
+ VISTA_loc = EarthLocation.from_geodetic('-70d23m51.36s', '-24d36m56.52s', (2530.)*u.m)
21
+
22
+ # https://www.eso.org/sci/facilities/lasilla/telescopes/3p6/overview.html
23
+ ## The telescope is at a geographical location of:
24
+ ## 70° 43' 54.1" W -29° 15' 39.5" S (WGS84), 2400 metres above sea level
25
+ LS3p6_loc = EarthLocation.from_geodetic('-70d43m54.1s', '-29d15m39.5s', (2400.)*u.m)
26
+
27
+ # https://www.eso.org/sci/facilities/lasilla/telescopes/ntt/overview.html
28
+ ## 70° 44' 01.5" W -29° 15' 32.1" S (WGS84), 2375m
29
+ LSntt_loc = EarthLocation.from_geodetic('-70d44m01.5s', '-29d15m32.1s', (2375.)*u.m)
30
+
31
+ # https://www.eso.org/sci/facilities/lasilla/telescopes/national/2p2.html
32
+ # 2p2 70°44'4"543 W 29°15'15"433 S
33
+ LS2p2_loc = EarthLocation.from_geodetic('-70d44m04.543s', '-29d15m15.433s', (2335.)*u.m)
34
+
35
+ ## All together as a dictionary:
36
+ telescope_locations={
37
+ 'UT1': UT1_loc,
38
+ 'UT2': UT2_loc,
39
+ 'UT3': UT3_loc,
40
+ 'UT4': UT4_loc,
41
+ 'VST': VST_loc,
42
+ 'VISTA': VISTA_loc,
43
+ 'LS3p6': LS3p6_loc,
44
+ 'LSntt': LSntt_loc,
45
+ 'LS2p2': LS2p2_loc,
46
+ }
47
+
48
+ # ---------------------------------------------------------------------------------------
49
+ def current_ESO_period():
50
+ d=datetime.datetime.utcnow()
51
+ return ((d.year-2012)+44)*2+int(float(d.month+2)/6.)
52
+ # ---------------------------------------------------------------------------------------
53
+ def ESO_period_start_end_dates(P):
54
+ ESO_period_first_last_dates=[
55
+ ['-10-01','-03-31'],
56
+ ['-04-01','-09-30'],
57
+ ]
58
+ y1=1967+(P+1)//2
59
+ md1=ESO_period_first_last_dates[P%2][0]
60
+ y2=1967+(P+2)//2
61
+ md2=ESO_period_first_last_dates[P%2][1]
62
+ return [f"{y1}{md1}",f"{y2}{md2}"]
63
+ # ---------------------------------------------------------------------------------------
64
+ def ESO_period_start_end_datetimes(P):
65
+ ESO_period_first_last_dates=[
66
+ ['-10-01T12:00:00','-04-01T12:00:00'],
67
+ ['-04-01T12:00:00','-10-01T12:00:00'],
68
+ ]
69
+ y1=1967+(P+1)//2
70
+ md1=ESO_period_first_last_dates[P%2][0]
71
+ y2=1967+(P+2)//2
72
+ md2=ESO_period_first_last_dates[P%2][1]
73
+ return [f"{y1}{md1}",f"{y2}{md2}"]
74
+ # ---------------------------------------------------------------------------------------
75
+ def get_attrs(klass):
76
+ return [k for k in klass.__dict__.keys()
77
+ if not k.startswith('__')
78
+ and not k.endswith('__')]
79
+
80
+ # ---------------------------------------------------------------------------------------
81
+ # https://gist.github.com/cynici/4084518
82
+ '''
83
+ optparse offers IndentedHelpFormatter() and TitledHelpFormatter() but they don't honor the newlines embedded in the description string.
84
+ author: Tim Chase
85
+ source: https://groups.google.com/forum/?fromgroups=#!topic/comp.lang.python/bfbmtUGhW8I
86
+ usage: parser = OptionParser(description=help_text, formatter=IndentedHelpFormatterWithNL())
87
+ '''
88
+
89
+ from optparse import OptionParser, IndentedHelpFormatter, textwrap
90
+
91
+ class IndentedHelpFormatterWithNL(IndentedHelpFormatter):
92
+ def format_description(self, description):
93
+ if not description: return ""
94
+ desc_width = self.width - self.current_indent
95
+ indent = " "*self.current_indent
96
+ # the above is still the same
97
+ bits = description.split('\n')
98
+ formatted_bits = [
99
+ textwrap.fill(bit,
100
+ desc_width,
101
+ initial_indent=indent,
102
+ subsequent_indent=indent)
103
+ for bit in bits]
104
+ result = "\n".join(formatted_bits) + "\n"
105
+ return result
106
+
107
+ def format_option(self, option):
108
+ # The help for each option consists of two parts:
109
+ # * the opt strings and metavars
110
+ # eg. ("-x", or "-fFILENAME, --file=FILENAME")
111
+ # * the user-supplied help string
112
+ # eg. ("turn on expert mode", "read data from FILENAME")
113
+ #
114
+ # If possible, we write both of these on the same line:
115
+ # -x turn on expert mode
116
+ #
117
+ # But if the opt string list is too long, we put the help
118
+ # string on a second line, indented to the same column it would
119
+ # start in if it fit on the first line.
120
+ # -fFILENAME, --file=FILENAME
121
+ # read data from FILENAME
122
+ result = []
123
+ opts = self.option_strings[option]
124
+ opt_width = self.help_position - self.current_indent - 2
125
+ if len(opts) > opt_width:
126
+ opts = "%*s%s\n" % (self.current_indent, "", opts)
127
+ indent_first = self.help_position
128
+ else: # start help on same line as opts
129
+ opts = "%*s%-*s " % (self.current_indent, "", opt_width, opts)
130
+ indent_first = 0
131
+ result.append(opts)
132
+ if option.help:
133
+ help_text = self.expand_default(option)
134
+ # Everything is the same up through here
135
+ help_lines = []
136
+ for para in help_text.split("\n"):
137
+ help_lines.extend(textwrap.wrap(para, self.help_width))
138
+ # Everything is the same after here
139
+ result.append("%*s%s\n" % (
140
+ indent_first, "", help_lines[0]))
141
+ result.extend(["%*s%s\n" % (self.help_position, "", line)
142
+ for line in help_lines[1:]])
143
+ elif opts[-1] != "\n":
144
+ result.append("\n")
145
+ return "".join(result)
146
+ # ---------------------------------------------------------------------------------------
147
+ def rgetFolderIds( api, cIds ):
148
+
149
+ ids = []
150
+ for cId in cIds :
151
+ rItems, _ = api.get('/containers/'+str(cId)+'/items')
152
+ logging.debug(rItems)
153
+ for r in rItems :
154
+ if r['itemType'] == 'Folder' :
155
+ logging.debug('Adding %d to folder list' %(r['containerId']))
156
+ ids.extend([r['containerId'],])
157
+ ids.extend(rgetFolderIds(api, [r['containerId'],]))
158
+ return ids
159
+ # ---------------------------------------------------------------------------------------
160
+ def rgetContainerIds( api, cIds, status=None ):
161
+ ids = []
162
+ for cId in cIds :
163
+ rItems, _ = api.get('/containers/'+str(cId)+'/items')
164
+ logging.debug(rItems)
165
+ for r in rItems :
166
+ if r['itemType'] == 'TimeLink' or r['itemType'] == 'Group' or r['itemType'] == 'Concatenation' :
167
+ if status is None or r['containerStatus'] in list(status) :
168
+ logging.debug('Adding %d to container list' %(r['containerId']))
169
+ ids.extend([r['containerId'],])
170
+ return ids
171
+ # ---------------------------------------------------------------------------------------
172
+ def rgetOBIds( api, cIds, status=None ):
173
+ ids = []
174
+ for cId in cIds :
175
+ logging.debug('Checking container %d' %(cId))
176
+ rItems, _ = api.get('/containers/'+str(cId)+'/items')
177
+ logging.debug(rItems)
178
+ for r in rItems :
179
+ if r['itemType'] == 'OB' or r['itemType'] == 'CB' :
180
+ if status is None or r['obStatus'] in list(status) :
181
+ logging.debug('Adding %d to OB list' %(r['obId']))
182
+ ids.extend([r['obId'],])
183
+ return ids
184
+ # ---------------------------------------------------------------------------------------
185
+ def r_get_all_OBIds( api, obj_list ) :
186
+ obIds=[]
187
+ for ID in obj_list :
188
+ nID=int(ID)
189
+ logging.info("Processing OB/Container ID = %d" %(nID))
190
+ # The containes and OBs
191
+ try:
192
+ container, containerVersion = api.getContainer(nID)
193
+ fIds = [nID,]
194
+ fIds.extend(rgetFolderIds(api, [nID,]))
195
+ logging.info('================================================================================')
196
+ cIds=rgetContainerIds(api, fIds )
197
+ fcIds = fIds+cIds
198
+ if cIds is not None :
199
+ logging.info("containerId list = %s" %(str(cIds)))
200
+ else :
201
+ logging.error("No containers found")
202
+ logging.info('================================================================================')
203
+ obIds=obIds+rgetOBIds(api, fcIds )
204
+ logging.info("obId list = %s" %(str(obIds)))
205
+ if obIds is None :
206
+ logging.error("No OBs found")
207
+
208
+ except p2api.P2Error as e :
209
+ ## Looks like this ID is not a container, assume it is an OB, below...
210
+ obIds.append(nID)
211
+ except:
212
+ raise
213
+ return obIds
214
+ # ---------------------------------------------------------------------------------------
215
+ def get_api( options ) :
216
+ password_from_keyring = None
217
+ # Login...
218
+ if options.env in ['demo',]:
219
+ user='52052'
220
+ pwstream='tutorial'
221
+ else:
222
+ user=options.user
223
+ ## from astroquery.eso
224
+ # Get password from keyring or prompt
225
+ if options.pwreenter is False:
226
+ password_from_keyring = keyring.get_password(
227
+ "usd_p2api_interactive:www.eso.org", user)
228
+
229
+ if password_from_keyring is None:
230
+ #if system_tools.in_ipynb():
231
+ # log.warning("You may be using an ipython notebook:"
232
+ # " the password form will appear in your terminal.")
233
+ # see https://pymotw.com/2/getpass/
234
+ if sys.stdin.isatty():
235
+ pwstream = getpass.getpass(user+"@<"+options.env+"> password : ", stream=sys.stderr)
236
+ else:
237
+ pwstream = sys.stdin.readline().rstrip()
238
+ else:
239
+ pwstream = password_from_keyring
240
+
241
+ i=0
242
+ api = None
243
+ while api is None and i < 10 :
244
+ try:
245
+ api = p2api.ApiConnection(options.env, user, pwstream)
246
+ except p2api.P2Error as e :
247
+ logging.error(e)
248
+ time.sleep(2)
249
+ except :
250
+ pass
251
+ i+=1
252
+ if api is None :
253
+ logging.error('Ooops, login failed...')
254
+ sys.exit(1)
255
+
256
+ ## from astroquery.eso
257
+ # When authenticated, save password in keyring if needed
258
+ if options.env not in ['demo','defidev7',] and password_from_keyring is None and options.pwstore:
259
+ keyring.set_password("usd_p2api_interactive:www.eso.org", user, pwstream)
260
+
261
+ return api
262
+ # ---------------------------------------------------------------------------------------
263
+ def decode_datetime( d ) :
264
+ if d is None :
265
+ return None
266
+ if isinstance(d,datetime.date) :
267
+ return d
268
+ if isinstance(d,datetime.datetime) :
269
+ return d.date()
270
+
271
+ # Python can't actually handle TimeZones by name, except the one that
272
+ # time.tzname is currently set to:
273
+ # see https://docs.python.org/3/library/time.html
274
+ # So we replace as needed TZ names (%Z) with the %z format...
275
+ tz_info={
276
+ 'UTC': '+0000',
277
+ 'CET': '+0100',
278
+ 'CEST': '+0200',
279
+ }
280
+ d_tz=d
281
+ for tz in tz_info :
282
+ d_tz=re.sub(
283
+ f'{tz}',
284
+ f'{tz_info[tz]}',
285
+ d_tz,
286
+ )
287
+ for s in [d_tz,d] :
288
+ for df in [
289
+ '%a, %d %b %Y %H:%M:%S %z', # 'Wed, 8 Feb 2023 21:43:40 +0000'
290
+ '%A, %d %B %Y at %H:%M %Z', # 'Thursday, 22 February 2024 at 12:00 CET'
291
+ '%A, %d %B %Y at %H:%M %z', # 'Thursday, 22 February 2024 at 12:00 +0100'
292
+ '%d %b %Y',
293
+ '%Y.%m.%d',
294
+ '%Y-%m-%d',
295
+ '%Y-%m-%dT%H:%M:%S', # 2023-04-01T16:45:59
296
+ '%Y-%m-%dT%H:%M', # 2023-04-01T16:45
297
+ ] :
298
+ try :
299
+ dt=datetime.datetime.strptime(s, df)
300
+ return dt
301
+ except :
302
+ pass
303
+ raise Exception(f"Could not decode date '{d}'.")
304
+ # ---------------------------------------------------------------------------------------
@@ -0,0 +1,61 @@
1
+ Metadata-Version: 2.3
2
+ Name: p2api
3
+ Version: 1.0.6
4
+ Summary: Binding for the ESO phase 2 programmatic API
5
+ Project-URL: Homepage, https://www.eso.org/copdemo/apidoc/index.html
6
+ Author-email: Thomas Bierwirth <thomas.bierwirth@eso.org>, John Pritchard <john.pritchard@eso.org>
7
+ License: MIT License
8
+
9
+ Copyright (c) 2018-present Thomas Bierwirth and other contributors
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+ License-File: LICENSE
29
+ Keywords: API,ESO,Observation,Phase2,Preparation,Programmatic
30
+ Classifier: Development Status :: 5 - Production/Stable
31
+ Classifier: Intended Audience :: Developers
32
+ Classifier: License :: OSI Approved :: MIT License
33
+ Classifier: Programming Language :: Python :: 3
34
+ Classifier: Programming Language :: Python :: 3.4
35
+ Classifier: Programming Language :: Python :: 3.5
36
+ Classifier: Programming Language :: Python :: 3.6
37
+ Classifier: Programming Language :: Python :: 3.7
38
+ Classifier: Programming Language :: Python :: 3.8
39
+ Classifier: Programming Language :: Python :: 3.9
40
+ Classifier: Programming Language :: Python :: 3.10
41
+ Classifier: Programming Language :: Python :: 3.11
42
+ Classifier: Programming Language :: Python :: 3.12
43
+ Requires-Dist: astroplan
44
+ Requires-Dist: astropy
45
+ Requires-Dist: keyring
46
+ Requires-Dist: requests
47
+ Description-Content-Type: text/markdown
48
+
49
+ # p2api
50
+
51
+ The [ESO](https://www.eso.org/public/) [Phase 2](https://www.eso.org/sci/observing/phase2.html) Application Programming Interface (API) uses the lightweight [JSON](<https://en.wikipedia.org/wiki/JSON>) standard as data format to exchange information between your application and the Phase 2 server at ESO. In order to program more conveniently against the API, it is helpful to create small, [language-specific bindings](<https://en.wikipedia.org/wiki/Language_binding>) to the API. We provide a simple example binding for Python, i.e. p2api.
52
+
53
+ As of version 1.0.6: Added script p2api_add_absolute_time_intervals.
54
+
55
+ As of version 1.0.3: support for \<url\>,\<access_token\> constructor.
56
+
57
+ As of version 0.97.2: support for La Silla.
58
+
59
+ Please see the [ESO-USD p2api](<http://eso.org/sci/observing/phase2/p2intro/Phase2API.html>) page for more details.
60
+
61
+ Phase 2 programmatic API documentation can be found [here](<https://www.eso.org/copdemo/apidoc/>).
@@ -0,0 +1,12 @@
1
+ p2api/__about__.py,sha256=mqMuQB3aqJVPrHHqJMLjqiMKUiJjozc7EPLcX5DpKHg,22
2
+ p2api/__init__.py,sha256=0wYyt-RvVjGdZPImG2MEk2BiZuWK8FiXBTMTw9BxRGk,146
3
+ p2api/p2api.py,sha256=xBUINJ_D1LcRI725foc0xo277wvDRaS5W5epJ1oNGRo,63571
4
+ p2api/utils/__init__.py,sha256=KkOGTFI0lZhjNLF2yRy-OtSfay7KUfU1ZZBVtC4Y0U8,58
5
+ p2api/utils/add_absolute_time_intervals.py,sha256=vvoHzCcdaR9ohNehGOZpOW6mlONW8xbqYYPg9uz60VY,18757
6
+ p2api/utils/generate_finding_charts.py,sha256=4xccJZxzLbdBBvpImANlkS2NCzSP-dAbeQlGCCmSB50,11951
7
+ p2api/utils/p2api_utils.py,sha256=5U91EGTOqw2u793mhxmwmjDQJPhMubt5mo56X8EopN0,11930
8
+ p2api-1.0.6.dist-info/METADATA,sha256=s-YOwjfLK7iuWXdXvzJT6hrXCBXQNf2oWINwNgNpFX4,3372
9
+ p2api-1.0.6.dist-info/WHEEL,sha256=fl6v0VwpzfGBVsGtkAkhILUlJxROXbA3HvRL6Fe3140,105
10
+ p2api-1.0.6.dist-info/entry_points.txt,sha256=_Wh1hYyvJwkEMARyhDA5iGeNDt-tMs4nqlDW_D4huXg,172
11
+ p2api-1.0.6.dist-info/licenses/LICENSE,sha256=ooEtWLrYbubUHqsABQYrj8uOfE4CMVgkb7WQDgZVyDw,1103
12
+ p2api-1.0.6.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.25.0
3
+ Root-Is-Purelib: true
4
+ Tag: py2-none-any
5
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ p2api_add_absolute_time_intervals = p2api.utils.add_absolute_time_intervals:main
3
+ p2api_generate_finding_charts = p2api.utils.generate_finding_charts:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018-present Thomas Bierwirth and other contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.