HWRF  trunk@4391
config.py
1 """!parses UNIX conf files and makes the result readily available
2 
3 The hwrf.config module reads configuration information for the HWRF
4 system from one or more *.conf files, via the Python ConfigParser
5 module. This module also automatically fills in certain information,
6 such as fields calculated from the tcvitals or date. The result is
7 accessible via the HWRFConfig class, which provides many ways of
8 automatically accessing configuration options."""
9 
10 ##@var __all__
11 # decides what symbols are imported by "from hwrf.config import *"
12 __all__=['from_file','from-string','confwalker','HWRFConfig','fordriver','ENVIRONMENT']
13 
14 import ConfigParser,collections,re,string,os,logging,threading
15 import os.path,sys,StringIO
18 
19 from produtil.datastore import Datastore
20 from produtil.fileop import *
21 
22 import hwrf.revital
23 import datetime
24 
25 from hwrf.numerics import to_datetime
26 from hwrf.storminfo import find_tcvitals_for
27 from hwrf.exceptions import HWRFError
28 from string import Formatter
29 from ConfigParser import SafeConfigParser,NoOptionError,NoSectionError
30 
31 ########################################################################
32 
33 class Environment(object):
34  """!returns environment variables, allowing substitutions
35 
36  This class is used to read (but not write) environment variables
37  and provide default values if an environment variable is unset or
38  blank. It is only meant to be used in string formats, by passing
39  ENV=ENVIRONMENT. There is a global constant in this module,
40  ENVIRONMENT, which is an instance of this class. You should never
41  need to instantiate another one."""
42  def __contains__(self,s):
43  """!Determines if __getitem__ will return something (True) or
44  raise KeyError (False). Same as "s in os.environ" unless s
45  contains "|-", in which case, the result is True."""
46  return s.find('|-')>=0 or s in os.environ
47  def __getitem__(self,s):
48  """!Same as os.environ[s] unless s contains "|-".
49  ENVIRONMENT["VARNAME|-substitute"]
50  will return os.environ[VARNAME] if VARNAME is defined and
51  non-empty in os.environ. Otherwise, it will return
52  "substitute"."""
53  if not s: return ''
54  i=s.find('|-')
55  if i<0: return os.environ[s]
56  var=s[0:i]
57  sub=s[(i+2):]
58  val=os.environ.get(var,'')
59  if val!='': return val
60  return sub
61 
62 ## @var ENVIRONMENT
63 # an Environment object. You should never need to instantiate another one.
64 ENVIRONMENT=Environment()
65 
66 class ConfFormatter(Formatter):
67  """!Internal class that implements HWRFConfig.strinterp()
68 
69  This class is part of the implementation of HWRFConfig: it is
70  used to interpolate strings using a syntax similar to
71  string.format(), but it allows recursion in the config sections,
72  and it also is able to use the [config] and [dir] sections as
73  defaults for variables not found in the current section."""
74  def __init__(self):
75  """!Constructor for ConfFormatter"""
76  super(ConfFormatter,self).__init__()
77  def get_value(self,key,args,kwargs):
78  """!Return the value of variable, or a substitution.
79 
80  Never call this function. It is called automatically by
81  str.format. It provides the value of an variable,
82  or a string substitution.
83  @param key the string key being analyzed by str.format()
84  @param args the indexed arguments to str.format()
85  @param kwargs the keyword arguments to str.format()"""
86  kwargs['__depth']+=1
87  if kwargs['__depth']>=ConfigParser.MAX_INTERPOLATION_DEPTH:
88  raise ConfigParser.InterpolationDepthError(kwargs['__key'],
89  kwargs['__section'],key)
90  try:
91  if isinstance(key,int):
92  return args[key]
93  conf=kwargs.get('__conf',None)
94  if key in kwargs:
95  v=kwargs[key]
96  elif '__taskvars' in kwargs \
97  and kwargs['__taskvars'] \
98  and key in kwargs['__taskvars']:
99  v=kwargs['__taskvars'][key]
100  else:
101  isec=key.find('/')
102  if isec>=0:
103  section=key[0:isec]
104  nkey=key[(isec+1):]
105  if not section:
106  section=kwargs.get('__section',None)
107  if nkey:
108  key=nkey
109  else:
110  section=kwargs.get('__section',None)
111  conf=kwargs.get('__conf',None)
112  v=NOTFOUND
113  if section is not None and conf is not None:
114  if conf.has_option(section,key):
115  v=conf.get(section,key)
116  elif conf.has_option(section,'@inc'):
117  for osec in conf.get(section,'@inc').split(','):
118  if conf.has_option(osec,key):
119  v=conf.get(osec,key)
120  if v is NOTFOUND:
121  if conf.has_option('config',key):
122  v=conf.get('config',key)
123  elif conf.has_option('dir',key):
124  v=conf.get('dir',key)
125  if v is NOTFOUND:
126  raise KeyError(key)
127 
128  if isinstance(v,basestring):
129  if v.find('{')>=0 or v.find('%')>=0:
130  vnew=self.vformat(v,args,kwargs)
131  assert(vnew is not None)
132  return vnew
133  return v
134  finally:
135  kwargs['__depth']-=1
136 
137 ########################################################################
138 
139 ##@var FCST_KEYS
140 # the list of forecast time keys recognized by ConfTimeFormatter
141 FCST_KEYS={ 'fYMDHM':'%Y%m%d%H%M', 'fYMDH':'%Y%m%d%H', 'fYMD':'%Y%m%d',
142  'fyear':'%Y', 'fYYYY':'%Y', 'fYY':'%y', 'fCC':'%C', 'fcen':'%C',
143  'fmonth':'%m', 'fMM':'%m', 'fday':'%d', 'fDD':'%d', 'fhour':'%H',
144  'fcyc':'%H', 'fHH':'%H', 'fminute':'%M', 'fmin':'%M' }
145 """A list of keys recognized by ConfTimeFormatter if the key is
146 requested during string interpolation, and the key is not in the
147 relevant section. This list of keys represents the forecast time. It
148 is a dict mapping from the key name to the format sent to
149 datetime.datetime.strftime to generate the string value."""
150 
151 ##@var ANL_KEYS
152 # the list of analysis time keys recognized by ConfTimeFormatter
153 ANL_KEYS={ 'aYMDHM':'%Y%m%d%H%M', 'aYMDH':'%Y%m%d%H', 'aYMD':'%Y%m%d',
154  'ayear':'%Y', 'aYYYY':'%Y', 'aYY':'%y', 'aCC':'%C', 'acen':'%C',
155  'amonth':'%m', 'aMM':'%m', 'aday':'%d', 'aDD':'%d', 'ahour':'%H',
156  'acyc':'%H', 'aHH':'%H', 'aminute':'%M', 'amin':'%M' }
157 """A list of keys recognized by ConfTimeFormatter if the key is
158 requested during string interpolation, and the key is not in the
159 relevant section. This list of keys represents the analysis time. It
160 is a dict mapping from the key name to the format sent to
161 datetime.datetime.strftime to generate the string value."""
162 
163 ##@var M6_KEYS
164 # the list of analysis time ( -6h ) keys recognized by ConfTimeFormatter
165 ANL_M6_KEYS={ 'am6YMDHM':'%Y%m%d%H%M', 'am6YMDH':'%Y%m%d%H', 'am6YMD':'%Y%m%d',
166  'am6year':'%Y', 'am6YYYY':'%Y', 'am6YY':'%y', 'am6CC':'%C', 'am6cen':'%C',
167  'am6month':'%m', 'am6MM':'%m', 'am6day':'%d', 'am6DD':'%d', 'am6hour':'%H',
168  'am6cyc':'%H', 'am6HH':'%H', 'am6minute':'%M', 'am6min':'%M' }
169 """A list of keys recognized by ConfTimeFormatter if the key is
170 requested during string interpolation, and the key is not in the
171 relevant section. This list of keys represents the analysis time. It
172 is a dict mapping from the key name to the format sent to
173 datetime.datetime.strftime to generate the string value."""
174 
175 ##@var P6_KEYS
176 # the list of analysis time ( +6h ) keys recognized by ConfTimeFormatter
177 ANL_P6_KEYS={ 'ap6YMDHM':'%Y%m%d%H%M', 'ap6YMDH':'%Y%m%d%H', 'ap6YMD':'%Y%m%d',
178  'ap6year':'%Y', 'ap6YYYY':'%Y', 'ap6YY':'%y', 'ap6CC':'%C', 'ap6cen':'%C',
179  'ap6month':'%m', 'ap6MM':'%m', 'ap6day':'%d', 'ap6DD':'%d', 'ap6hour':'%H',
180  'ap6cyc':'%H', 'ap6HH':'%H', 'ap6minute':'%M', 'ap6min':'%M' }
181 """A list of keys recognized by ConfTimeFormatter if the key is
182 requested during string interpolation, and the key is not in the
183 relevant section. This list of keys represents the analysis time. It
184 is a dict mapping from the key name to the format sent to
185 datetime.datetime.strftime to generate the string value."""
186 
187 ##@var TIME_DIFF_KEYS
188 # the list of "forecast time minus analysis time" keys recognized by
189 # ConfTimeFormatter
190 TIME_DIFF_KEYS=set(['fahr','famin','fahrmin'])
191 """A list of keys recognized by ConfTimeFormatter if the key is
192 requested during string interpolation, and the key is not in the
193 relevant section. This list of keys represents the time difference
194 between the forecast and analysis time. Unlike FCST_KEYS and
195 ANL_KEYS, this is not a mapping: it is a set."""
196 
197 ##@var NOTFOUND
198 # a special constant that represents a key not being found
199 NOTFOUND=object()
200 
202  """!internal function that implements time formatting
203 
204  Like its superclass, ConfFormatter, this class is part of the
205  implementation of HWRFConfig, and is used to interpolate strings
206  in a way similar to string.format(). It works the same way as
207  ConfFormatter, but accepts additional keys generated based on the
208  forecast and analysis times:
209 
210  fYMDHM - 201409171200 = forecast time September 17, 2014 at 12:00 UTC
211  fYMDH - 2014091712
212  fYMD - 20140917
213  fyear - 2014
214  fYYYY - 2014
215  fYY - 14 (year % 100)
216  fCC - 20 (century)
217  fcen - 20
218  fmonth - 09
219  fMM - 09
220  fday - 17
221  fDD - 17
222  fhour - 12
223  fcyc - 12
224  fHH - 12
225  fminute - 00
226  fmin - 00
227 
228  Replace the initial "f" with "a" for analysis times. In addition,
229  the following are available for the time difference between
230  forecast and analysis time. Suppose the forecast is twenty-three
231  hours and nineteen minutes (23:19) after the analysis time:
232 
233  fahr - 23
234  famin - 1399 ( = 23*60+19)
235  fahrmin - 19 """
236  def __init__(self):
237  """!constructor for ConfTimeFormatter"""
238  super(ConfTimeFormatter,self).__init__()
239  def get_value(self,key,args,kwargs):
240  """!return the value of a variable, or a substitution
241 
242  Never call this function. It is called automatically by
243  str.format. It provides the value of an variable,
244  or a string substitution.
245  @param key the string key being analyzed by str.format()
246  @param args the indexed arguments to str.format()
247  @param kwargs the keyword arguments to str.format()"""
248  v=NOTFOUND
249  kwargs['__depth']+=1
250  if kwargs['__depth']>=ConfigParser.MAX_INTERPOLATION_DEPTH:
251  raise ConfigParser.InterpolationDepthError(
252  kwargs['__key'],kwargs['__section'],v)
253  try:
254  if isinstance(key,int):
255  return args[key]
256  if key in kwargs:
257  v=kwargs[key]
258  elif '__taskvars' in kwargs \
259  and kwargs['__taskvars'] \
260  and key in kwargs['__taskvars']:
261  v=kwargs['__taskvars'][key]
262  elif '__ftime' in kwargs and key in FCST_KEYS:
263  v=kwargs['__ftime'].strftime(FCST_KEYS[key])
264  elif '__atime' in kwargs and key in ANL_KEYS:
265  v=kwargs['__atime'].strftime(ANL_KEYS[key])
266  elif '__atime' in kwargs and key in ANL_M6_KEYS:
267  am6=kwargs['__atime']-datetime.timedelta(0,3600*6)
268  v=am6.strftime(ANL_M6_KEYS[key])
269  elif '__atime' in kwargs and key in ANL_P6_KEYS:
270  ap6=kwargs['__atime']+datetime.timedelta(0,3600*6)
271  v=ap6.strftime(ANL_P6_KEYS[key])
272  elif '__ftime' in kwargs and '__atime' in kwargs and \
273  key in TIME_DIFF_KEYS:
274  (ihours,iminutes)=hwrf.numerics.fcst_hr_min(
275  kwargs['__ftime'],kwargs['__atime'])
276  if key=='fahr':
277  v=int(ihours)
278  elif key=='famin':
279  v=int(ihours*60+iminutes)
280  elif key=='fahrmin':
281  v=int(iminutes)
282  else:
283  v=int(ihours*60+iminutes)
284  else:
285  isec=key.find('/')
286  if isec>=0:
287  section=key[0:isec]
288  nkey=key[(isec+1):]
289  if not section:
290  section=kwargs.get('__section',None)
291  if nkey:
292  key=nkey
293  else:
294  section=kwargs.get('__section',None)
295  conf=kwargs.get('__conf',None)
296  if section and conf:
297  if conf.has_option(section,key):
298  v=conf.get(section,key)
299  elif conf.has_option(section,'@inc'):
300  for osec in conf.get(section,'@inc').split(','):
301  if conf.has_option(osec,key):
302  v=conf.get(osec,key)
303  if v is NOTFOUND:
304  if conf.has_option('config',key):
305  v=conf.get('config',key)
306  elif conf.has_option('dir',key):
307  v=conf.get('dir',key)
308  if v is NOTFOUND:
309  raise KeyError('Cannot find key %s in section %s'
310  %(repr(key),repr(section)))
311 
312  if isinstance(v,basestring) and ( v.find('{')!=-1 or
313  v.find('%')!=-1 ):
314  try:
315  vnew=self.vformat(v,args,kwargs)
316  assert(vnew is not None)
317  return vnew
318  except KeyError as e:
319  # Seriously, does the exception's class name
320  # really need to be this long?
321  raise ConfigParser.InterpolationMissingOptionError(
322  kwargs['__key'],kwargs['__section'],v,str(e))
323  return v
324  finally:
325  kwargs['__depth']-=1
326 
327 ########################################################################
328 
329 def confwalker(conf,start,selector,acceptor,recursevar):
330  """!walks through a ConfigParser-like object performing some action
331 
332  Recurses through a ConfigParser-like object "conf" starting at
333  section "start", performing a specified action. The special
334  variable whose name is in recursevar specifies a list of
335  additional sections to recurse into. No section will be processed
336  more than once, and sections are processed in breadth-first order.
337  For each variable seen in each section (including recursevar),
338  this will call selector(sectionname, varname) to see if the
339  variable should be processed. If selector returns True, then
340  acceptor(section, varname, value) will be called.
341 
342  @param conf the ConfigParser-like object
343  @param start the starting section
344  @param selector a function selector(section,option) that decides
345  if an option needs processing (True) or not (False)
346  @param acceptor a function acceptor(section,option,value)
347  run on all options for which the selector returns True
348  @param recursevar an option in each section that lists more
349  sections the confwalker should touch. If the selector returns
350  True for the recursevar, then the recursevar will be sent to
351  the acceptor. However, it will be scanned for sections to
352  recurse into even if the selector rejects it."""
353  touched=set()
354  requested=[str(start)]
355  while len(requested)>0:
356  sec=requested.pop(0)
357  if sec in touched:
358  continue
359  touched.add(sec)
360  for (key,val) in conf.items(sec):
361  if selector(sec,key):
362  acceptor(sec,key,val)
363  if key==recursevar:
364  for sec2 in reversed(val.split(',')):
365  trim=sec2.strip()
366  if len(trim)>0 and not trim in touched:
367  requested.append(trim)
368 
369 ########################################################################
370 
371 def from_file(filename):
372  """!Reads the specified conf file into an HWRFConfig object.
373 
374  Creates a new HWRFConfig object and instructs it to read the specified file.
375  @param filename the path to the file that is to be read
376  @return a new HWRFConfig object"""
377  if not isinstance(filename,basestring):
378  raise TypeError('First input to hwrf.config.from_file must be a string.')
379  conf=HWRFConfig()
380  conf.read(filename)
381  return conf
382 
383 def from_string(confstr):
384  """!Reads the given string as if it was a conf file into an HWRFConfig object
385 
386  Creates a new HWRFConfig object and reads the string data into it
387  as if it was a config file
388  @param confstr the config data
389  @return a new HWRFConfig object"""
390  if not isinstance(confstr,basestring):
391  raise TypeError('First input to hwrf.config.from_string must be a string.')
392  conf=HWRFConfig()
393  conf.readstr(confstr)
394  return conf
395 
396 class HWRFConfig(object):
397  """!a class that contains configuration information
398 
399  This class keeps track of configuration information for all tasks
400  in a running HWRF model. It can be used in a read-only manner as
401  if it was a ConfigParser object. All HWRFTask objects require an
402  HWRFConfig object to keep track of registered task names via the
403  register_task_name method, the current forecast cycle (cycle
404  property) and the Datastore object (datastore property).
405 
406  This class should never be instantiated directly. Instead, you
407  should use the hwrf.config.from_string or hwrf.config.from_file to
408  read configuration information from an in-memory string or a file.
409 
410  Also note that this class should not be used to create a new
411  config file for the first HWRF job in a workflow. The
412  hwrf.launcher module does that for you."""
413 
414  def __init__(self,conf=None):
415  """!HWRFConfig constructor
416 
417  Creates a new HWRFConfig object.
418  @param conf the underlying ConfigParser.SafeConfigParser object
419  that stores the actual config data"""
420  self._logger=logging.getLogger('hwrf')
421  logger=self._logger
422  self._lock=threading.RLock()
425  self._datastore=None
426  self._tasknames=set()
427  self._conf=SafeConfigParser() if (conf is None) else conf
428  self._conf.optionxform=str
429 
430  self._conf.add_section('config')
431  self._conf.add_section('dir')
432 
433  def readstr(self,source):
434  """!read config data and add it to this object
435 
436  Given a string with conf data in it, parses the data.
437  @param source the data to parse
438  @return self"""
439  fp=StringIO.StringIO(str(source))
440  self._conf.readfp(fp)
441  fp.close()
442  return self
443 
444  def read(self,source):
445  """!reads and parses a config file
446 
447  Opens the specified config file and reads it, adding its
448  contents to the configuration. This is used to implement the
449  from_file module-scope function. You can use it again on an
450  HWRFConfig object to read additional files.
451  @param source the file to read
452  @return self"""
453  self._conf.read(source)
454  return self
455 
456  def readfp(self,source):
457  """!read config data from an open file
458 
459  Reads a config file from the specified file-like object.
460  This is used to implement the readstr.
461  @param source the opened file to read
462  @return self"""
463  self._conf.readfp(source)
464  return self
465 
466  def readstr(self,string):
467  """!reads config data from an in-memory string
468 
469  Reads the given string as a config file. This is used to
470  implement the from_string module-scope function. You can use
471  it again to read more config data into an existing HWRFConfig.
472  @param string the string to parse
473  @return self"""
474  sio=StringIO.StringIO(string)
475  self._conf.readfp(sio)
476  return self
477 
478  def set_options(self,section,**kwargs):
479  """!set values of several options in a section
480 
481  Sets the value of several options in one section. The
482  keywords arguments are the names of the options to set and the
483  keyword values are the option values.
484  @param section the section being modified
485  @param kwargs additional keyword arguments are the option names
486  and values"""
487  for k,v in kwargs.iteritems():
488  value=str(v)
489  self._conf.set(section,k,value)
490 
491  def read_precleaned_vitfile(self,vitfile):
492  """!reads tcvitals
493 
494  WARNING: This is presently unused and may be removed. It does
495  not belong in HWRFConfig.
496 
497  Reads tcvitals from the specified file if specified, or from
498  the current cycle's vitals storage area if not. Does not
499  parse, clean or otherwise modify the vitals: they are assumed
500  to contain output for only one storm, with no duplicate
501  cycles. Ideally, the file should have been created by the
502  hwrf.launcher module. Returns an hwrf.revital.Revital object."""
503  logger=self.log()
504  logger.info('read vitals from: '+vitfile)
505  revital=hwrf.revital.Revital(logger=logger)
506  with open(vitfile,'rt') as f:
507  revital.readfiles([vitfile])
508  return revital
509 
510  @property
511  def realtime(self):
512  """!is this a real-time simulation?
513 
514  Is this configuration for a real-time simulation? Defaults to
515  True if unknown. This is the same as doing
516  getbool('config','realtime',True)."""
517  return self.getbool('config','realtime',True)
518  def set(self,section,key,value):
519  """!set a config option
520 
521  Sets the specified config option (key) in the specified
522  section, to the specified value. All three are converted to
523  strings via str() before setting the value."""
524  self._conf.set(str(section),str(key),str(value))
525  def __enter__(self):
526  """!grab the thread lock
527 
528  Grabs this HWRFConfig's thread lock. This is only for future
529  compatibility and is never used."""
530  self._lock.acquire()
531  def __exit__(self,a,b,c):
532  """!release the thread lock
533 
534  Releases this HWRFConfig's thread lock. This is only for
535  future compatibility and is never used.
536  @param a,b,c unused"""
537  self._lock.release()
538  def register_hwrf_task(self,name):
539  """!add an hwrf.hwrftask.HWRFTask to the database
540 
541  Checks to ensure that there is no other task by this name, and
542  records the fact that there is now a task. This is used by
543  the hwrf.hwrftask.HWRFTask to ensure only one task is made by
544  any name."""
545  with self:
546  if name in self._tasknames:
548  '%s: attempted to use this task name twice'%(name,))
549  self._tasknames.add(name)
550  def log(self,sublog=None):
551  """!returns a logging.Logger object
552 
553  Returns a logging.Logger object. If the sublog argument is
554  provided, then the logger will be under that subdomain of the
555  "hwrf" logging domain. Otherwise, this HWRFConfig's logger
556  (usually the "hwrf" domain) is returned.
557  @param sublog the logging subdomain, or None
558  @return a logging.Logger object"""
559  if sublog is not None:
560  with self:
561  return logging.getLogger('hwrf.'+sublog)
562  return self._logger
563  def getdatastore(self):
564  """!returns the Datastore
565 
566  Returns the produtil.datastore.Datastore object for this
567  HWRFConfig."""
568  d=self._datastore
569  if d is not None:
570  return d
571  with self:
572  if self._datastore is None:
573  dsfile=self.getstr('config','datastore')
575  logger=self.log('datastore'))
576  return self._datastore
577 
578  ##@var datastore
579  # read-only property: the Datastore object for this HWRF simulation
580  datastore=property(getdatastore,None,None, \
581  """Returns the Datastore object for this HWRF simulation,
582  creating it if necessary. If the underlying datastore file
583  did not already exist, it will be opened in create=True mode.""")
584 
585  def getcycle(self):
586  """!get the analysis time
587 
588  Returns the analysis time of this HWRF workflow as a
589  datetime.datetime."""
590  if self._cycle is None:
591  self._cycle=to_datetime(self._conf.get('config','cycle'))
592  return self._cycle
593  def setcycle(self,cycle):
594  """!set the analysis time
595 
596  Sets the analysis time of this HWRF workflow. Also sets the
597  [config] section's "cycle" option. Accepts anything that
598  hwrf.numerics.to_datetime recognizes."""
599  cycle=to_datetime(cycle)
600  strcycle=cycle.strftime('%Y%m%d%H')
601  self._conf.set('config','cycle',strcycle)
602  self._cycle=cycle
603  self.set_time_vars()
604 
605  ##@var cycle
606  # the analysis cycle, a datetime.datetime object
607  cycle=property(getcycle,setcycle,None,
608  """The cycle this HWRF simulation should run, as a datetime.datetime.""")
609 
610  def set_time_vars(self):
611  """!internal function that sets time-related variables
612 
613  Sets many config options in the [config] section based on this
614  HWRF workflow's analysis time. This is called automatically
615  when the cycle property is assigned. You never need to call
616  this function directly.
617 
618  YMDHM - 201409171200 = forecast time September 17, 2014 at 12:00 UTC
619  YMDH - 2014091712
620  YMD - 20140917
621  year - 2014
622  YYYY - 2014
623  YY - 14 (year % 100)
624  CC - 20 (century)
625  cen - 20
626  month - 09
627  MM - 09
628  day - 17
629  DD - 17
630  hour - 12
631  cyc - 12
632  HH - 12
633  minute - 00
634  min - 00"""
635  with self:
636  for var,fmt in [ ('YMDHM','%Y%m%d%H%M'), ('YMDH','%Y%m%d%H'),
637  ('YMD','%Y%m%d'), ('year','%Y'), ('YYYY','%Y'),
638  ('YY','%y'), ('CC','%C'), ('cen','%C'),
639  ('month','%m'), ('MM','%m'), ('day','%d'),
640  ('DD','%d'), ('hour','%H'), ('cyc','%H'),
641  ('HH','%H'), ('minute','%M'), ('min','%M') ]:
642  self._conf.set('config',var,self._cycle.strftime(fmt))
643  def add_section(self,sec):
644  """!add a new config section
645 
646  Adds a section to this HWRFConfig. If the section did not
647  already exist, it will be initialized empty. Otherwise, this
648  function has no effect.
649  @param sec the new section's name"""
650  with self:
651  self._conf.add_section(sec)
652  return self
653  def has_section(self,sec):
654  """!does this section exist?
655 
656  Determines if a config section exists (even if it is empty)
657  @return True if this HWRFConfig has the given section and
658  False otherwise.
659  @param sec the section to check for"""
660  with self:
661  return self._conf.has_section(sec)
662  def has_option(self,sec,opt):
663  """! is this option set?
664 
665  Determines if an option is set in the specified section
666  @return True if this HWRFConfig has the given option in the
667  specified section, and False otherwise.
668  @param sec the section
669  @param opt the name of the option in that section"""
670  with self:
671  return self._conf.has_option(sec,opt)
672  def getdir(self,name,default=None,morevars=None,taskvars=None):
673  """! query the "dir" section
674 
675  Search the "dir" section.
676  @return the specified key (name) from the "dir" section.
677  Other options are passed to self.getstr.
678 
679  @param default the default value if the option is unset
680  @param morevars more variables for string substitution
681  @param taskvars even more variables for string substitution
682  @param name the option name to search for"""
683  with self:
684  return self.getstr('dir',name,default=default,
685  morevars=morevars,taskvars=taskvars)
686  def getloc(self,name,default=None,morevars=None,taskvars=None):
687  """!search the config, exe and dir sections in that order
688 
689  Find the location of a file in the named option. Searches
690  the [config], [exe] and [dir] sections in order for an option
691  by that name, returning the first one found.
692  @param default the default value if the option is unset
693  @param morevars more variables for string substitution
694  @param taskvars even more variables for string substitution
695  @param name the option name to search for
696  @returns the resulting value"""
697  with self:
698  if self.has_option('config',name):
699  return self.getstr('config',name,default=default,
700  morevars=morevars,taskvars=taskvars)
701  elif self.has_option('exe',name):
702  return self.getstr('exe',name,default=default,
703  morevars=morevars,taskvars=taskvars)
704  else:
705  return self.getstr('dir',name,default=default,
706  morevars=morevars,taskvars=taskvars)
707  def getexe(self,name,default=None,morevars=None,taskvars=None):
708  """! query the "exe" section
709 
710  Search the "exe" section.
711  @return the specified key (name) from the "exe" section.
712  Other options are passed to self.getstr.
713 
714  @param default the default value if the option is unset
715  @param morevars more variables for string substitution
716  @param taskvars even more variables for string substitution
717  @param name the option name to search for"""
718  with self:
719  return self.getstr('exe',name,default=default,morevars=morevars,
720  taskvars=taskvars)
721  def __getitem__(self,arg):
722  """!convenience function; replaces self.items and self.get
723 
724  This is a convenience function that provides access to the
725  self.items or self.get functions.
726 
727  * conf["section"] -- returns a dict containing the results of
728  self.items(arg)
729  * conf[a,b,c] -- returns self.get(a,b,c)
730  (b and c are optional)
731  @param arg the arguments: a list or string"""
732  with self:
733  if isinstance(arg,str):
734  return dict(self.items(arg))
735  elif ( isinstance(arg,list) or isinstance(arg,tuple) ):
736  if len(arg)==1: return dict(self.items(arg))
737  if len(arg)==2: return self.get(str(arg[0]),str(arg[1]))
738  if len(arg)==3: return self.get(str(arg[0]),str(arg[1]),
739  default=arg[2])
740  return NotImplemented
741  def makedirs(self,*args):
742  """!calls produtil.fileop.makedirs() on directories in the [dir] section
743 
744  This is a simple utility function that calls
745  produtil.fileop.makedirs() on some of the directories in the
746  [dir] section.
747  @param args the keys in the [dir] section for the directories
748  to make."""
749  with self:
750  dirs=[self.getstr('dir',arg) for arg in args]
751  for makeme in dirs:
753  def keys(self,sec):
754  """!get options in a section
755 
756  Returns a list containing the config options in the given
757  section.
758  @param sec the string name of the section"""
759  with self:
760  return [ opt for opt in self._conf.options(sec) ]
761  def items(self,sec,morevars=None,taskvars=None):
762  """!get the list of (option,value) tuples for a section
763 
764  Returns a section's options as a list of two-element
765  tuples. Each tuple contains a config option, and the value of
766  the config option after string interpolation. Note that the
767  special config section inclusion option "@inc" is also
768  returned.
769  @param sec the section
770  @param morevars variables for string substitution
771  @param taskvars yet more variables
772  @return a list of (option,value) tuples, where the value is
773  after string expansion"""
774  out=[]
775  with self:
776  for opt in self._conf.options(sec):
777  out.append((opt,self._interp(sec,opt,morevars,
778  taskvars=taskvars)))
779  return out
780  def write(self,fileobject):
781  """!write the contents of this HWRFConfig to a file
782 
783  Writes the contents of an HWRFConfig to the specified file,
784  without interpolating (expanding) any strings. The file will
785  be suitable for reading in to a new HWRFConfig object in a
786  later job. This is used by the hwrf.launcher module to create
787  the initial config file.
788  @param fileobject an opened file to write to"""
789  with self:
790  self._conf.write(fileobject)
791  def getraw(self,sec,opt,default=None):
792  """!return the raw value of an option
793 
794  Returns the raw value for the specified section and option,
795  without string interpolation. That is, any {...} will be
796  returned unmodified. Raises an exception if no value is set.
797  Will not search other sections, unlike other accessors.
798  @param sec the section
799  @param opt the option name
800  @param default the value to return if the option is unset.
801  If unspecified or None, NoOptionError is raised"""
802  try:
803  return self._conf.get(sec,opt,raw=True)
804  except NoOptionError:
805  if default is not None: return default
806  raise
807  def strinterp(self,sec,string,**kwargs):
808  """!perform string expansion
809 
810  Performs this HWRFConfig's string interpolation on the
811  specified string, as if it was a value from the specified
812  section.
813  @param sec the section name
814  @param string the string to expand
815  @param kwargs more variables for string substitution"""
816  assert(isinstance(sec,basestring))
817  assert(isinstance(string,basestring))
818  with self:
819  if 'vit' not in kwargs and 'syndat' in self.__dict__:
820  kwargs['vit']=self.syndat.__dict__
821  if 'oldvit' not in kwargs and 'oldsyndat' in self.__dict__:
822  kwargs['oldvit']=self.oldsyndat.__dict__
823  return self._formatter.format(string,__section=sec,
824  __key='__string__',__depth=0,__conf=self._conf,
825  ENV=ENVIRONMENT,**kwargs)
826  def timestrinterp(self,sec,string,ftime,atime=None,**kwargs):
827  """!performs string expansion, including time variables
828 
829  Performs this HWRFConfig's string interpolation on the
830  specified string, as self.strinterp would, but adds in
831  additional keys based on the given analysis and forecast
832  times. The keys are the same as the keys added to [config]
833  for the cycle, except with "a" prepended for the analysis
834  time, or "f" for the forecast time. There are three more keys
835  for the difference between the forecast an analysis time. The
836  famin is the forecast time in minutes, rounded down. The fahr
837  and fahrmin are the forecast hour, rounded down, and the
838  remainder in minutes, rounded down to the next nearest minute.
839 
840  If the analysis time is None or unspecified, then self.cycle
841  is used. The atime can be anything understood by
842  hwrf.numerics.to_datetime and the ftime can be anything
843  understood by hwrf.numerics.to_datetime_rel, given the atime
844  (or, absent atime, self.cycle) as the second argument.
845 
846  This is implemented as a wrapper around the
847  self._time_formatter object, which knows how to expand the a*
848  and f* variables without having to generate all of them.
849 
850  @param sec the section name
851  @param string the string to expand
852  @param ftime the forecast time or None
853  @param atime the analysis time or None
854  @param kwargs more variables for string expansion"""
855  if atime is not None:
856  atime=hwrf.numerics.to_datetime(atime)
857  else:
858  atime=self.cycle
859  ftime=hwrf.numerics.to_datetime_rel(ftime,atime)
860  if 'vit' not in kwargs and 'syndat' in self.__dict__:
861  kwargs['vit']=self.syndat.__dict__
862  if 'oldvit' not in kwargs and 'oldsyndat' in self.__dict__:
863  kwargs['oldvit']=self.oldsyndat.__dict__
864  with self:
865  return self._time_formatter.format(string,__section=sec,
866  __key='__string__',__depth=0,__conf=self._conf,ENV=ENVIRONMENT,
867  __atime=atime, __ftime=ftime,**kwargs)
868 
869  def _interp(self,sec,opt,morevars=None,taskvars=None):
870  """!implementation of data-getting routines
871 
872  This is the underlying implementation of the various self.get*
873  routines, and lies below the self._get. It reads a config
874  option opt from the config section sec.
875 
876  If the string contains a {...} expansion, the _interp will
877  perform string interpolation, expanding {...} strings
878  according to ConfigParser rules. If the section contains an
879  @inc, and any variables requested are not found, then the
880  sections listed in @inc are searched. Failing that, the
881  config and dir sections are searched.
882 
883  @param sec the section name
884  @param opt the option name
885  @param morevars a dict containing variables whose values will
886  override anything in this HWRFConfig when performing string
887  interpolation.
888  @param taskvars serves the same purpose as morevars, but
889  provides a second scope.
890  @return the result of the string expansion"""
891  vitdict={}
892  olddict={}
893  if 'syndat' in self.__dict__: vitdict=self.syndat.__dict__
894  if 'oldsyndat' in self.__dict__: olddict=self.oldsyndat.__dict__
895 
896  sections=( sec, 'config','dir', '@inc' )
897  gotted=False
898  for section in sections:
899  if section=='@inc':
900  try:
901  inc=self._conf.get(sec,'@inc')
902  except NoOptionError:
903  inc=''
904  if inc:
905  touched=set(( sec,'config','dir' ))
906  for incsection in inc.split(","):
907  trim=incsection.strip()
908  if len(trim)>0 and trim not in touched:
909  touched.add(trim)
910  try:
911  got=self._conf.get(trim,opt,raw=True)
912  gotted=True
913  break
914  except (KeyError,NoSectionError,NoOptionError) as e:
915  pass # var not in section; search elsewhere
916  if gotted: break
917  else:
918  try:
919  got=self._conf.get(section,opt,raw=True)
920  gotted=True
921  break
922  except (KeyError,NoSectionError,NoOptionError) as e:
923  pass # var not in section; search elsewhere
924 
925  if not gotted:
926  raise NoOptionError(opt,sec)
927 
928  if morevars is None:
929  return self._formatter.format(got,
930  __section=sec,__key=opt,__depth=0,__conf=self._conf, vit=vitdict,
931  ENV=ENVIRONMENT, oldvit=olddict,__taskvars=taskvars)
932  else:
933  return self._formatter.format(got,
934  __section=sec,__key=opt,__depth=0,__conf=self._conf, vit=vitdict,
935  ENV=ENVIRONMENT, oldvit=olddict,__taskvars=taskvars,
936  **morevars)
937 
938  def _get(self,sec,opt,typeobj,default,badtypeok,morevars=None,taskvars=None):
939  """! high-level implemention of get routines
940 
941  This is the implementation of all of the self.get* routines.
942  It obtains option opt from section sec via the self._interp,
943  providing the optional list of additional variables for string
944  interpolation in the morevars. It then converts to the given
945  type via typeobj (for example, typeobj=int for int
946  conversion). If default is not None, and the variable cannot
947  be found, then the default is returned.
948 
949  @param sec the section name
950  @param opt the option name in that section
951  @param default the default value to return if the variable
952  cannot be found, or None if no default is provided.
953  @param badtypeok if True and default is not None, and the type
954  conversion failed, then default is returned. Otherwise, the
955  TypeError resulting from the failed type conversion is passed
956  to the caller.
957  @param morevars a dict containing variables whose values will
958  override anything in this HWRFConfig when performing string
959  interpolation.
960  @param taskvars serves the same purpose as morevars, but
961  provides a second scope. """
962  try:
963  s=self._interp(sec,opt,morevars=morevars,taskvars=taskvars)
964  assert(s is not None)
965  return typeobj(s)
966  except NoOptionError:
967  if default is not None:
968  return default
969  raise
970  except TypeError:
971  if badtypeok:
972  if default is not None: return default
973  return None
974  raise
975  def getint(self,sec,opt,default=None,badtypeok=False,morevars=None,taskvars=None):
976  """!get an integer value
977 
978  Gets option opt from section sec and expands it; see "get" for
979  details. Attempts to convert it to an int.
980 
981  @param sec,opt the section and option
982  @param default if specified and not None, then the default is
983  returned if an option has no value or the section does not exist
984  @param badtypeok is True, and the conversion fails, and a
985  default is specified, the default will be returned.
986  @param morevars,taskvars dicts of more variables for string expansion"""
987  with self:
988  return self._get(sec,opt,int,default,badtypeok,morevars,taskvars=taskvars)
989 
990  def getfloat(self,sec,opt,default=None,badtypeok=False,morevars=None,taskvars=None):
991  """!get a float value
992 
993  Gets option opt from section sec and expands it; see "get" for
994  details. Attempts to convert it to a float
995 
996  @param sec,opt the section and option
997  @param default if specified and not None, then the default is
998  returned if an option has no value or the section does not exist
999  @param badtypeok is True, and the conversion fails, and a
1000  default is specified, the default will be returned.
1001  @param morevars,taskvars dicts of more variables for string expansion"""
1002  with self:
1003  return self._get(sec,opt,float,default,badtypeok,morevars,taskvars=taskvars)
1004 
1005  def getstr(self,sec,opt,default=None,badtypeok=False,morevars=None,taskvars=None):
1006  """!get a string value
1007 
1008  Gets option opt from section sec and expands it; see "get" for
1009  details. Attempts to convert it to a str
1010 
1011  @param sec,opt the section and option
1012  @param default if specified and not None, then the default is
1013  returned if an option has no value or the section does not exist
1014  @param badtypeok is True, and the conversion fails, and a
1015  default is specified, the default will be returned.
1016  @param morevars,taskvars dicts of more variables for string expansion"""
1017  with self:
1018  return self._get(sec,opt,str,default,badtypeok,morevars,taskvars=taskvars)
1019 
1020  def get(self,sec,opt,default=None,badtypeok=False,morevars=None,taskvars=None):
1021  """!get the value of an option from a section
1022 
1023  Gets option opt from section sec, expands it and converts
1024  to a string. If the option is not found and default is
1025  specified, returns default. If badtypeok, returns default if
1026  the option is found, but cannot be converted. The morevars is
1027  used during string expansion: if {abc} is in the value of the
1028  given option, and morevars contains a key abc, then {abc} will
1029  be expanded using that value. The morevars is a dict that
1030  allows the caller to override the list of variables for string
1031  extrapolation.
1032  @param sec,opt the section and option
1033  @param default if specified and not None, then the default is
1034  returned if an option has no value or the section does not exist
1035  @param badtypeok is True, and the conversion fails, and a
1036  default is specified, the default will be returned.
1037  @param morevars,taskvars dicts of more variables for string expansion"""
1038  with self:
1039  try:
1040  return self._interp(sec,opt,morevars,taskvars=taskvars)
1041  except NoOptionError:
1042  if default is not None:
1043  return default
1044  raise
1045 
1046  def options(self,sec):
1047  """!what options are in this section?
1048 
1049  Returns a list of options in the given section
1050  @param sec the section"""
1051  with self:
1052  return self._conf.options(sec)
1053 
1054  def getboolean(self,sec,opt,default=None,badtypeok=False,morevars=None,taskvars=None):
1055  """!alias for getbool: get a bool value
1056 
1057  This is an alias for getbool for code expecting a
1058  ConfigParser. Gets option opt from section sec and expands
1059  it; see "get" for details. Attempts to convert it to a bool
1060 
1061  @param sec,opt the section and option
1062  @param default if specified and not None, then the default is
1063  returned if an option has no value or the section does not exist
1064  @param badtypeok is True, and the conversion fails, and a
1065  default is specified, the default will be returned.
1066  @param morevars,taskvars dicts of more variables for string expansion"""
1067  return self.getbool(sec,opt,default=default,badtypeok=badtypeok,
1068  morevars=morevars,taskvars=taskvars)
1069 
1070  def getbool(self,sec,opt,default=None,badtypeok=False,morevars=None,taskvars=None):
1071  """!get a bool value
1072 
1073  Gets option opt from section sec and expands it; see "get" for
1074  details. Attempts to convert it to a bool
1075 
1076  @param sec,opt the section and option
1077  @param default if specified and not None, then the default is
1078  returned if an option has no value or the section does not exist
1079  @param badtypeok is True, and the conversion fails, and a
1080  default is specified, the default will be returned.
1081  @param morevars,taskvars dicts of more variables for string expansion"""
1082  try:
1083  with self:
1084  s=self._interp(sec,opt,morevars=morevars,taskvars=taskvars)
1085  except NoOptionError:
1086  if default is not None:
1087  return bool(default)
1088  raise
1089  if re.match('(?i)\A(?:T|\.true\.|true|yes|on|1)\Z',s): return True
1090  if re.match('(?i)\A(?:F|\.false\.|false|no|off|0)\Z',s): return False
1091  try:
1092  return int(s)==0
1093  except ValueError as e: pass
1094  if badtypeok and default is not None:
1095  return bool(default)
1096  raise ValueError('%s.%s: invalid value for HWRF conf file boolean: %s'
1097  %(sec,opt,repr(s)))
This module provides a set of utility functions to do filesystem operations.
Definition: fileop.py:1
def getexe
query the "exe" section
Definition: config.py:707
def fcst_hr_min(time, start)
Return forecast time in hours and minutes.
Definition: numerics.py:126
def _interp
implementation of data-getting routines
Definition: config.py:869
def to_datetime_rel(d, rel)
Converts objects to a datetime relative to another datetime.
Definition: numerics.py:319
def get_value(self, key, args, kwargs)
return the value of a variable, or a substitution
Definition: config.py:239
def getboolean
alias for getbool: get a bool value
Definition: config.py:1054
def _get
high-level implemention of get routines
Definition: config.py:938
def __getitem__(self, s)
Same as os.environ[s] unless s contains "|-".
Definition: config.py:47
def makedirs(self, args)
calls produtil.fileop.makedirs() on directories in the [dir] section
Definition: config.py:741
def read_precleaned_vitfile(self, vitfile)
reads tcvitals
Definition: config.py:491
def strinterp(self, sec, string, kwargs)
perform string expansion
Definition: config.py:807
def getfloat
get a float value
Definition: config.py:990
def __init__(self)
Constructor for ConfFormatter.
Definition: config.py:74
Defines the Revital class which manipulates tcvitals files.
Definition: revital.py:1
def register_hwrf_task(self, name)
add an hwrf.hwrftask.HWRFTask to the database
Definition: config.py:538
def from_string(confstr)
Reads the given string as if it was a conf file into an HWRFConfig object.
Definition: config.py:383
def set_time_vars(self)
internal function that sets time-related variables
Definition: config.py:610
Defines StormInfo and related functions for interacting with vitals ATCF data.
Definition: storminfo.py:1
def get_value(self, key, args, kwargs)
Return the value of variable, or a substitution.
Definition: config.py:77
Raised when more than one task is registered with the same name in an HWRFConfig object.
Definition: exceptions.py:22
def log
returns a logging.Logger object
Definition: config.py:550
def get
get the value of an option from a section
Definition: config.py:1020
def getloc
search the config, exe and dir sections in that order
Definition: config.py:686
def getstr
get a string value
Definition: config.py:1005
def readstr(self, source)
read config data and add it to this object
Definition: config.py:433
Stores products and tasks in an sqlite3 database file.
Definition: datastore.py:1
def to_datetime(d)
Converts the argument to a datetime.
Definition: numerics.py:346
def add_section(self, sec)
add a new config section
Definition: config.py:643
a class that contains configuration information
Definition: config.py:396
def makedirs
Make a directory tree, working around filesystem bugs.
Definition: fileop.py:224
Time manipulation and other numerical routines.
Definition: numerics.py:1
def readfp(self, source)
read config data from an open file
Definition: config.py:456
def getraw
return the raw value of an option
Definition: config.py:791
def __exit__(self, a, b, c)
release the thread lock
Definition: config.py:531
def set(self, section, key, value)
set a config option
Definition: config.py:518
returns environment variables, allowing substitutions
Definition: config.py:33
def setcycle(self, cycle)
set the analysis time
Definition: config.py:593
def getdatastore(self)
returns the Datastore
Definition: config.py:563
def __contains__(self, s)
Determines if getitem will return something (True) or raise KeyError (False).
Definition: config.py:42
def getint
get an integer value
Definition: config.py:975
def getcycle(self)
get the analysis time
Definition: config.py:585
Stores information about Datum objects in a database.
Definition: datastore.py:134
def from_file(filename)
Reads the specified conf file into an HWRFConfig object.
Definition: config.py:371
def confwalker(conf, start, selector, acceptor, recursevar)
walks through a ConfigParser-like object performing some action
Definition: config.py:329
internal function that implements time formatting
Definition: config.py:201
Internal class that implements HWRFConfig.strinterp()
Definition: config.py:66
def realtime(self)
is this a real-time simulation?
Definition: config.py:511
def __enter__(self)
grab the thread lock
Definition: config.py:525
def getbool
get a bool value
Definition: config.py:1070
def keys(self, sec)
get options in a section
Definition: config.py:753
def __init__
HWRFConfig constructor.
Definition: config.py:414
Exceptions raised by the hwrf package.
Definition: exceptions.py:1
def read(self, source)
reads and parses a config file
Definition: config.py:444
def write(self, fileobject)
write the contents of this HWRFConfig to a file
Definition: config.py:780
def __init__(self)
constructor for ConfTimeFormatter
Definition: config.py:236
def has_section(self, sec)
does this section exist?
Definition: config.py:653
def getdir
query the "dir" section
Definition: config.py:672
def has_option(self, sec, opt)
is this option set?
Definition: config.py:662
def items
get the list of (option,value) tuples for a section
Definition: config.py:761
def set_options(self, section, kwargs)
set values of several options in a section
Definition: config.py:478
def options(self, sec)
what options are in this section?
Definition: config.py:1046
cycle
the analysis cycle, a datetime.datetime object
Definition: config.py:607
def __getitem__(self, arg)
convenience function; replaces self.items and self.get
Definition: config.py:721
This class reads one or more tcvitals files and rewrites them as requested.
Definition: revital.py:38
def timestrinterp(self, sec, string, ftime, atime=None, kwargs)
performs string expansion, including time variables
Definition: config.py:826