p2api 1.0.5__tar.gz → 1.0.7__tar.gz

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.
p2api-1.0.7/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ p2api.egg-info/
2
+ dist/
3
+ p2api/__pycache__/
4
+ p2api/utils/__pycache__/
5
+ *.pyc
p2api-1.0.7/PKG-INFO ADDED
@@ -0,0 +1,61 @@
1
+ Metadata-Version: 2.3
2
+ Name: p2api
3
+ Version: 1.0.7
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/>).
@@ -1,7 +1,13 @@
1
+ # p2api
2
+
1
3
  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.
2
4
 
5
+ As of version 1.0.6: Added script p2api_add_absolute_time_intervals.
6
+
3
7
  As of version 1.0.3: support for \<url\>,\<access_token\> constructor.
4
8
 
5
9
  As of version 0.97.2: support for La Silla.
6
10
 
7
- Please see the [ESO-USD p2api](<http://eso.org/sci/observing/phase2/p2intro/Phase2API.html>) page for more details.
11
+ Please see the [ESO-USD p2api](<http://eso.org/sci/observing/phase2/p2intro/Phase2API.html>) page for more details.
12
+
13
+ Phase 2 programmatic API documentation can be found [here](<https://www.eso.org/copdemo/apidoc/>).
@@ -0,0 +1 @@
1
+ __version__ = "1.0.7"
@@ -35,14 +35,20 @@ SOFTWARE.
35
35
  """
36
36
  from __future__ import print_function
37
37
  import os
38
+ import io
38
39
  import json
39
40
  import requests
40
41
  import re
41
42
 
42
- # $Id: p2api.py 267837 2021-10-26 10:13:31Z jpritcha $
43
- P2_API_VERSION = '1.0.5'
44
- __version__=P2_API_VERSION
43
+ ## The hatchling way...
44
+ try:
45
+ from p2api.__about__ import __version__
46
+ except:
47
+ # Need this construct to run in VSCode
48
+ from __about__ import __version__
49
+ P2_API_VERSION = __version__
45
50
 
51
+ # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
46
52
  API_URL = {
47
53
  'production' : 'https://www.eso.org/cop/api/v1',
48
54
  'production_lasilla': 'https://www.eso.org/copls/api/v1',
@@ -0,0 +1,446 @@
1
+ #! /usr/bin/env python
2
+
3
+ # $Id: generate_finding_charts.py 267033 2021-09-16 11:08:57Z jpritcha $
4
+ from __future__ import print_function
5
+
6
+ _VERSION_ = "$Revision: 267033 $"
7
+ from ..p2api import __version__
8
+ _VERSION_ = __version__
9
+
10
+ import sys # interaction with Python interpreter
11
+ from optparse import OptionParser # for parsing command line arguments
12
+ from optparse import OptionGroup
13
+
14
+ import logging
15
+
16
+ import keyring
17
+ import datetime
18
+ import re
19
+
20
+ import p2api
21
+ import p2api.utils.p2api_utils as p2api_utils
22
+
23
+ from astropy.time import Time
24
+ import numpy as np
25
+ from astroplan import Observer
26
+ import astropy.units as u
27
+
28
+ ################################################################################
29
+ default_eso_period=p2api_utils.current_ESO_period()+1
30
+ min_ATI_length=1.*u.hr
31
+ ################################################################################
32
+ class get_options:
33
+ "get command line options"
34
+
35
+ def __init__(self, vers="0.0"):
36
+ # define options and arguments
37
+ parser = OptionParser(version="%prog "+vers, formatter=p2api_utils.IndentedHelpFormatterWithNL())
38
+
39
+ group = OptionGroup(parser, "Input OB(s)")
40
+ group.add_option(
41
+ "-i", "--ids",
42
+ dest="ids",
43
+ help="""\
44
+ List of run, container, folder, and/or OB IDs. \
45
+ Note that the ATCs will be applied to all OBs recursively when a \
46
+ run, container or folder is specified.
47
+ [required]\
48
+ """,
49
+ default=None
50
+ )
51
+ parser.add_option_group(group)
52
+
53
+ group = OptionGroup(parser, "Specify the Absolute Time Intervals explicity.")
54
+ group.add_option(
55
+ "--ATCs", dest="ATCs",
56
+ help="""\
57
+ Underscore separated list of comma separated Absolute Time Constraints \
58
+ e.g. yyyy-mm-ddThh:mm,yyyy-mm-ddThh:mm_yyyy-mm-ddThh:mm,yyyy-mm-ddThh:mm{_...}
59
+ [optional]\
60
+ """,
61
+ default=None
62
+ )
63
+ group.add_option("-f", "--file", dest="file", help="A file with one ATC per line [optional]", default=None)
64
+ parser.add_option_group(group)
65
+
66
+ group = OptionGroup(parser, "Specify the Absolute Time Intervals using an orbital ephemeris. This functionality is EXPERIMENTAL, please check carefully!!!")
67
+ group.add_option("-E", "--ephem", dest="ephem", metavar="T0,PERIOD", help="Comma separated list of orbital T0[JD] and period[days] [optional]", default=None)
68
+ group.add_option("--phase_intervals", dest="phase_intervals", help="Comma separated list of phase intervals e.g. 0.1-0.2,0.305-0.375 [optional]", default=None)
69
+ group.add_option("--phase_target", dest="phase_target", help="Comma separated list of target phase, start_before [mins], min_start_before [mins] e.g. 0.01,120,30 [optional]", default=None)
70
+ group.add_option("--periods", dest="periods", help="Comma separated list of periods, default={} [optional]".format(default_eso_period), default=str(default_eso_period))
71
+ parser.add_option_group(group)
72
+
73
+ group = OptionGroup(parser, "p2 server selection, options and password management")
74
+ group.add_option("--env", dest="env", help="Use the %s environment [optional], default = production" %("|".join(p2api.API_URL.keys())), default='production')
75
+ group.add_option("-u", "--user", dest="user", metavar="USER", help="the User Portal account of the user to use, e.g. ASMITHSONIAN [required for production (not required for demo)]", default=None)
76
+ group.add_option("-S", "--PWstore", dest="pwstore", metavar="PWSTORE", help="Store the password in the keyring, default=False", action="store_true", default=False)
77
+ group.add_option("-R", "--PWreenter", dest="pwreenter", metavar="PWREENTER", help="Reenter the password, thus overriding any PW in the keyring, default=False", action="store_true", default=False)
78
+ group.add_option("-Z", "--PWremove", dest="pwremove", metavar="PWREMOVE", help="Remove any password from the keyring, default=False", action="store_true", default=False)
79
+ parser.add_option_group(group)
80
+
81
+ group = OptionGroup(parser, "Verbosity...",
82
+ "All optional")
83
+ group.add_option("-n", "--dryRun", dest="dryrun", help="Do everything but make any changes to tickets [optional]", action="store_true", default=False)
84
+ group.add_option("-D", "--debug", dest="debug", help="Write out debugging info [optional]", action="store_true", default=False)
85
+ #group.add_option("-v", "--verbose", dest="vbose", help="Write out verbose info [optional]", action="store_true", default=False)
86
+ group.add_option("-q", "--quiet", dest="quiet", help="Write out minimal info [optional]", action="store_true", default=False)
87
+ parser.add_option_group(group)
88
+
89
+ # parse arguments
90
+ (options, args) = parser.parse_args()
91
+
92
+ if options.ids is None or (
93
+ (
94
+ options.ATCs is None
95
+ and
96
+ options.file is None
97
+ and
98
+ (
99
+ options.ephem is None
100
+ or
101
+ options.periods is None
102
+ or
103
+ options.phase_intervals is None
104
+ )
105
+ and
106
+ (
107
+ options.ephem is None
108
+ or
109
+ options.periods is None
110
+ or
111
+ options.phase_target is None
112
+ )
113
+ )
114
+ ) :
115
+ parser.print_help()
116
+ sys.exit(2)
117
+
118
+ self.ids = options.ids
119
+ self.periods = options.periods
120
+ self.ATCs = options.ATCs
121
+ self.file = options.file
122
+ self.ephem = options.ephem
123
+ self.phase_intervals = options.phase_intervals
124
+ self.phase_target = options.phase_target
125
+
126
+ self.user = options.user
127
+ self.env = options.env
128
+ self.pwstore = options.pwstore
129
+ self.pwreenter = options.pwreenter
130
+ self.pwremove = options.pwremove
131
+
132
+ self.debug = options.debug
133
+ self.dryrun = options.dryrun
134
+ self.quiet = options.quiet
135
+
136
+ logFMT = "%(asctime)s %(module)15s[%(process)5d] [%(levelname)s] %(message)s"
137
+ logLVL=logging.INFO
138
+ if options.debug:
139
+ logLVL=logging.DEBUG
140
+ if options.quiet:
141
+ logLVL=logging.WARN
142
+ logging.basicConfig(level=logLVL, format=logFMT)
143
+
144
+ options = get_options(_VERSION_)
145
+
146
+ # ------------------------------------------------------------------------------
147
+ def get_ob_ATCs(telescope=None, ob_info=None, run_info=None) :
148
+ ob_ATCs=[]
149
+ ATCs=None
150
+ check_ATCs=False
151
+ if options.ATCs is not None :
152
+ ATCs=[
153
+ [p2api_utils.decode_datetime(x.split(',')[0]),p2api_utils.decode_datetime(x.split(',')[1])]
154
+ for x in options.ATCs.splt('_')
155
+ ]
156
+
157
+ elif options.file is not None :
158
+ with open(options.file) as file:
159
+ lines = file.readlines()
160
+ lines = [line.rstrip().lstrip().split(',') for line in lines]
161
+ ATCs=[
162
+ [
163
+ p2api_utils.decode_datetime(x[0])+datetime.timedelta(seconds=30), # rounding to 1min
164
+ p2api_utils.decode_datetime(x[1])+datetime.timedelta(seconds=30), # rounding to 1min
165
+ ] for x in lines
166
+ ]
167
+
168
+ elif options.ephem is not None :
169
+ ob_exec_time=ob_info['executionTime']
170
+ T0,P=(float(x) for x in options.ephem.split(','))
171
+ periods=[
172
+ item for sublist in
173
+ [[y for y in range(int(x.split('-')[0]),int(x.split('-')[-1])+1)] for x in options.periods.split(',')]
174
+ for item in sublist
175
+ ]
176
+ periods.sort()
177
+ period_range=[periods[0],]
178
+ period_ranges=[]
179
+ _pp=periods[0]
180
+ sf_jds=[]
181
+ for _p in periods[1:] :
182
+ if _p > _pp+1 :
183
+ period_range+=[_pp,]
184
+ sf_jd=Time([p2api_utils.ESO_period_start_end_datetimes(ESO_P) for ESO_P in period_range]).jd
185
+ sf_jds+=[[sf_jd[0][0],sf_jd[1][1]],]
186
+ period_ranges+=[period_range,]
187
+ period_range=[_p,]
188
+ _pp=_p
189
+ period_range+=[_pp,]
190
+ sf_jd=Time([p2api_utils.ESO_period_start_end_datetimes(ESO_P) for ESO_P in period_range]).jd
191
+ sf_jds+=[[sf_jd[0][0],sf_jd[1][1]],]
192
+ period_ranges+=[period_range,]
193
+
194
+ post_ob_exec_time=0.
195
+ if options.phase_intervals is not None :
196
+ phase_intervals=[[float(y) for y in x.split('-')] for x in options.phase_intervals.split(',')]
197
+ min_start_before=0.
198
+ elif options.phase_target is not None :
199
+ phase_target,start_before,min_start_before=(float(x) for x in options.phase_target.split(','))
200
+ phase_intervals=[[phase_target-start_before/P/24./60.,phase_target],]
201
+ post_ob_exec_time=ob_exec_time
202
+ ob_exec_time=0.
203
+ else :
204
+ logging.error('No means to calculate phase intervals specified.')
205
+
206
+ checked_phase_intervals=[]
207
+ for p_int in phase_intervals :
208
+ if p_int[1] < p_int[0] :
209
+ p_int[0]=p_int[0]-1.
210
+ p_int_in_sec=(p_int[1]-p_int[0])*P*24.*60.*60.
211
+ if (
212
+ ob_exec_time < p_int_in_sec
213
+ and
214
+ p_int_in_sec > min_ATI_length.to_value(u.second)
215
+ ) :
216
+ checked_phase_intervals+=[p_int,]
217
+ else :
218
+ if ob_exec_time < p_int_in_sec :
219
+ logging.warning(
220
+ 'Phase interval {}--{} == {:3.1f}sec is too short to start and finish this OB with execution time of {}secs.'.format(
221
+ p_int[0],
222
+ p_int[1],
223
+ p_int_in_sec,
224
+ ob_exec_time,
225
+ )
226
+ )
227
+ if p_int_in_sec > min_ATI_length.to_value(u.second) :
228
+ logging.warning(
229
+ 'Phase interval {}--{} == {:3.1f}sec is shorter than the minimum ATI length time of {}secs.'.format(
230
+ p_int[0],
231
+ p_int[1],
232
+ p_int_in_sec,
233
+ min_ATI_length.to_value(u.second),
234
+ )
235
+ )
236
+
237
+ sf_ints=[]
238
+ for sf_jd in sf_jds :
239
+ orbits=(np.array(sf_jd)-T0)//P
240
+ for o in range(int(orbits[0])-1,int(orbits[1])+1) :
241
+ for p_int in checked_phase_intervals :
242
+ sf_int=[
243
+ T0+(o+p_int[0])*P,
244
+ T0+(o+p_int[1])*P-ob_exec_time/24./60./60.,
245
+ ]
246
+ if (
247
+ ( sf_int[0] > sf_jd[0] and sf_int[0] < sf_jd[1] )
248
+ or
249
+ ( sf_int[1] > sf_jd[0] and sf_int[1] < sf_jd[1] )
250
+ ) :
251
+ # Interval is within the time range of the specified period(s)...
252
+ original_sf_int=sf_int
253
+ int_spans_nighttime=True
254
+ if sf_int[1] - sf_int[0] < 1. :
255
+ sssr=[]
256
+ # Check that some part of the interval occurs during nighttime...
257
+ int_spans_nighttime=False
258
+ for which in ['previous','next',] :
259
+ sun_set_time=telescope.sun_set_time(Time(sf_int[0], format='jd'), which=which)
260
+ sun_rise_time=telescope.sun_rise_time(sun_set_time, which='next')
261
+ sssr+=[[sun_set_time,sun_rise_time],]
262
+ if (
263
+ (sf_int[0] > sun_set_time.jd and sf_int[0] < sun_rise_time.jd-ob_exec_time/24./60./60. )
264
+ or
265
+ (sf_int[1] > sun_set_time.jd and sf_int[1] < sun_rise_time.jd-ob_exec_time/24./60./60. )
266
+ or
267
+ (sf_int[0] < sun_set_time.jd and sf_int[1] > sun_rise_time.jd-ob_exec_time/24./60./60. )
268
+ ) :
269
+ sf_int=[
270
+ np.max([sf_int[0],sun_set_time.jd]),
271
+ np.min([sf_int[1],sun_rise_time.jd-ob_exec_time/24./60./60.,sun_rise_time.jd-post_ob_exec_time/24./60./60.]),
272
+ ]
273
+ if sf_int[1] > sf_int[0]+min_start_before/24./60. :
274
+ int_spans_nighttime=True
275
+ break
276
+ if (
277
+ int_spans_nighttime
278
+ and
279
+ ((sf_int[1]-sf_int[0])*24*60.*60. > ob_exec_time)
280
+ and
281
+ (sf_int[1]-sf_int[0])*24*60.*60. > min_ATI_length.to_value(u.second)
282
+ ) :
283
+ sf_ints+=[sf_int,]
284
+ else :
285
+ if not int_spans_nighttime :
286
+ logging.debug(
287
+ 'Interval does not span any part of the night, original interval = {}, SSSR of previous and next nights {}--{} and {}--{}'.format(
288
+ original_sf_int,
289
+ sssr[0][0].jd,
290
+ sssr[0][1].jd,
291
+ sssr[1][0].jd,
292
+ sssr[1][1].jd,
293
+ )
294
+ )
295
+ if ((sf_int[1]-sf_int[0])*24*60.*60. < ob_exec_time) :
296
+ logging.debug(
297
+ 'Checked Interval shorter [{:3.1f}sec] than OB execution time [{:3.1f}sec], original interval = {}, checked interval = {}'.format(
298
+ (sf_int[1]-sf_int[0])*24*60.*60.,
299
+ ob_exec_time,
300
+ original_sf_int,
301
+ sf_int,
302
+ )
303
+ )
304
+ if ((sf_int[1]-sf_int[0])*24*60.*60. < min_ATI_length.to_value(u.second)) :
305
+ logging.debug(
306
+ 'Checked Interval shorter [{:3.1f}sec] than minimum ATI length time [{:3.1f}sec], original interval = {}, checked interval = {}'.format(
307
+ (sf_int[1]-sf_int[0])*24*60.*60.,
308
+ min_ATI_length.to_value(u.second),
309
+ original_sf_int,
310
+ sf_int,
311
+ )
312
+ )
313
+ pass
314
+ ATCs=[Time(sf_int, format='jd') for sf_int in sf_ints]
315
+ check_ATCs=True
316
+ else :
317
+ raise "Don't know how to compute Absolute Time Constraints."
318
+
319
+ ob_ATCs=[]
320
+ for i,ATC in enumerate(ATCs) :
321
+ t=Time(ATC)
322
+ t.format = 'isot'
323
+ t.out_subfmt = 'date_hm'
324
+ ob_ATCs+=[{
325
+ 'from': t[0].isot,
326
+ 'to': t[1].isot,
327
+ },]
328
+ if check_ATCs :
329
+ phase_range=''
330
+ if options.ephem is not None :
331
+ phase_range=" [Phases: {:5.3f} -- {:5.3f}]".format(
332
+ (((t[0]-Time(T0,format='jd'))/P).to_value(u.day)%1),
333
+ (((t[1]-Time(T0,format='jd'))/P).to_value(u.day)%1),
334
+ )
335
+ print("{:3d} {} -- {} [duration={}]{}".format(
336
+ i+1,
337
+ ob_ATCs[-1]['from'],
338
+ ob_ATCs[-1]['to'],
339
+ ((t[1]-t[0]).to_datetime()),
340
+ phase_range,
341
+ ))
342
+ if check_ATCs :
343
+ '''
344
+ logging.warning("****************************************************************************************")
345
+ logging.warning("****************************************************************************************")
346
+ logging.warning("****************************************************************************************")
347
+ logging.warning("Intervals via Orbital Ephemeris functionality is EXPERIMENTAL")
348
+ logging.warning("Please check the above intervals carefully and only continue if you are sure they are what YOU require.")
349
+ logging.warning("YOU, and YOU ALONE are responsible for the accuracy of these inetervals.")
350
+ '''
351
+ print("****************************************************************************************")
352
+ print("****************************************************************************************")
353
+ print("****************************************************************************************")
354
+ print("Intervals via Orbital Ephemeris functionality is EXPERIMENTAL")
355
+ print("Please check the above intervals carefully and only continue if you are sure they are what YOU require.")
356
+ print("YOU, and YOU ALONE are responsible for the accuracy of these intervals.")
357
+ yorn = input("Continue [y/N] ")
358
+ if yorn.lower() not in ['y',] :
359
+ print("Please contact ESO User Support via a ticket at https://support.eso.org/tickets")
360
+ print("to report/discuss the errors/problems in the above calculations.")
361
+ print("Please include in the description the EXACT command line used to run the script.")
362
+ sys.exit(0)
363
+ #raise Exception("Aborted.")
364
+ return ob_ATCs
365
+ # ------------------------------------------------------------------------------
366
+ def oneOB( api, obId ) :
367
+ try :
368
+ OB, _ = api.getOB(obId)
369
+ except p2api.P2Error as e :
370
+ ## Looks like this ID is not an OB...
371
+ logging.error('OB %d could not be found...' %(obId))
372
+ return
373
+ except:
374
+ raise
375
+ ob_info = api.executionTime(obId)
376
+ run_info,_=api.getRun(OB['runId'])
377
+
378
+ telescope = Observer(location=p2api_utils.telescope_locations[run_info['telescope']])
379
+
380
+ if OB['itemType'] not in ['OB',] :
381
+ logging.warning('OB %d is not a science OB' %(obId))
382
+ return
383
+
384
+ if OB['obStatus'] not in ['P','-'] :
385
+ logging.warning('OB status is %s, OB not modifiable' %(OB['obstatus']))
386
+ return
387
+
388
+ runId, _ = api.getRun(OB['runId'])
389
+ ob_url=api.apiUrl+'/obsBlocks/'+str(obId)
390
+ oneOB_sdt=datetime.datetime.now()
391
+ ## For the moment only setting is supported,
392
+ ## ToDo: implement support for adding to...
393
+ logging.info("Setting Absolute Time Intervals for OBid = %d, OBname = '%s' @ %s..." % (OB['obId'],OB['name'],OB['instrument']))
394
+ absTCs, atcVersion = api.getAbsoluteTimeConstraints(obId)
395
+
396
+ ob_ATCs=get_ob_ATCs(
397
+ telescope=telescope,
398
+ ob_info=ob_info,
399
+ run_info=run_info,
400
+ )
401
+
402
+ if not options.dryrun :
403
+ api.saveAbsoluteTimeConstraints(
404
+ obId,
405
+ ob_ATCs,
406
+ atcVersion,
407
+ )
408
+ oneOB_edt=datetime.datetime.now()
409
+ logging.info(
410
+ "Added %d Absolute Time Intervals for OBid = %d, OBname = '%s' @ %s done in %s."
411
+ %(
412
+ len(ob_ATCs), obId, OB['name'], OB['instrument'], str(oneOB_edt-oneOB_sdt)
413
+ )
414
+ )
415
+ # ------------------------------------------------------------------------------
416
+
417
+ # ------------------------------------------------------------------------------
418
+ # ------------------------------------------------------------------------------
419
+ # ------------------------------------------------------------------------------
420
+
421
+ def main():
422
+
423
+ ################################################################################
424
+ # remove password from keyring...
425
+ if options.pwremove and options.user is not None :
426
+ keyring.delete_password('usd_p2api_interactive:www.eso.org', options.user)
427
+ sys.exit(0)
428
+
429
+ api=p2api_utils.get_api(options)
430
+ obIds=p2api_utils.r_get_all_OBIds( api, options.ids.split(',') )
431
+
432
+ if len(obIds) > 0 :
433
+ if len(obIds) > 1 :
434
+ sdt=datetime.datetime.now()
435
+ logging.warning('Adding Absolute Time Constraints to %d OBs...' %( len(obIds) ))
436
+ for obId in obIds :
437
+ oneOB(api, obId, )
438
+ if len(obIds) > 1 :
439
+ edt=datetime.datetime.now()
440
+ logging.info('Total elapsed time to process %d finding charts %s...' %(len(obIds),str(edt-sdt)))
441
+ else :
442
+ logging.error('No OBs found.')
443
+
444
+
445
+ if __name__ == '__main__' :
446
+ main()