source: CCCC/trunk/ceda_cc/utils_c4.py @ 298

Subversion URL: http://proj.badc.rl.ac.uk/svn/exarch/CCCC/trunk/ceda_cc/utils_c4.py@298
Revision 298, 45.3 KB checked in by astephen, 7 years ago (diff)

added activity to json record for ESA-CCI

Line 
1import string, re, os, sys, traceback, ctypes
2
3def strmm3( mm ):
4  return string.join( map( lambda x: '%s="%s" [correct: "%s"]' % x, mm ), '; ' )
5
6from fcc_utils import mipTableScan
7from xceptions import *
8
9class reportSection(object):
10
11  def __init__(self,id,cls,parent=None, description=None):
12    self.id = id
13    self.cls = cls
14    self.parent = parent
15    self.description = description
16    self.records = []
17    self.subsections = []
18    self.closed = False
19    self.npass = 0
20    self.fail = 0
21    self.auditDone = True
22
23  def addSubSection( self, id, cls, description=None):
24    assert not self.closed, 'Attempt to add sub-section to closed report section'
25    self.subsections.append( reportSection(id, cls, parent=self, description=description )  )
26    self.auditDone = False
27    return self.subsections[-1]
28
29  def addRecord( self, id, cls, res, msg ):
30    assert not self.closed, 'Attempt to add record to closed report section'
31    self.records.append( (id, cls, res, msg) )
32    self.auditDone = False
33
34  def close(self):
35    self.closed = True
36
37  def reopen(self):
38    self.closed = False
39
40  def audit(self):
41    if self.auditDone:
42      return
43    self.closed = True
44    self.fail = 0
45    self.npass = 0
46    for ss in self.subsections:
47      ss.audit()
48      self.fail += ss.fail
49      self.npass += ss.npass
50
51    for r in self.records:
52      if r[2]:
53        self.npass += 1
54      else:
55        self.fail += 1
56
57class checkSeq(object):
58  def __init__(self):
59    pass
60
61  def check(self,x):
62    d = map( lambda i: x[i+1] - x[i], range(len(x)-1) )
63    self.delt = sum(d)/len(d)
64    self.dmx = max(d)
65    self.dmn = min(d)
66    return self.dmx - self.dmn < abs(self.delt)*1.e-4
67
68cs = checkSeq()
69
70class checkBase(object):
71
72  def  __init__(self,cls="CORDEX",reportPass=True,parent=None,monitor=None):
73    self.cls = cls
74    self.project = cls
75    self.abortMessageCount = parent.abortMessageCount
76    self.monitor = monitor
77    ## check done earlier
78    ## assert cls in ['CORDEX','SPECS'],'This version of the checker only supports CORDEX, SPECS'
79    self.re_isInt = re.compile( '[0-9]+' )
80    self.errorCount = 0
81    self.passCount = 0
82    self.missingValue = 1.e20
83    self.missingValue = ctypes.c_float(1.00000002004e+20).value
84    from file_utils import ncLib
85    if ncLib == 'netCDF4':
86      import numpy
87      self.missingValue = numpy.float32(self.missingValue)
88    self.parent = parent
89    self.reportPass=reportPass
90    self.pcfg = parent.pcfg
91################################
92    self.requiredGlobalAttributes = self.pcfg.requiredGlobalAttributes
93    self.controlledGlobalAttributes = self.pcfg.controlledGlobalAttributes
94    self.globalAttributesInFn = self.pcfg.globalAttributesInFn
95    self.requiredVarAttributes = self.pcfg.requiredVarAttributes
96    self.drsMappings = self.pcfg.drsMappings
97#######################################
98    self.checks = ()
99    self.messageCount = 0
100    self.init()
101    if not hasattr( self.parent, 'amapListDraft' ):
102      self.parent.amapListDraft = []
103
104  def isInt(self,x):
105    return self.re_isInt.match( x ) != None
106
107  def logMessage(self, msg, error=False ):
108    self.messageCount += 1
109    assert self.abortMessageCount < 0 or self.abortMessageCount > self.messageCount, 'Raising error [TESTX01], perhaps for testing'
110    if self.parent != None and self.parent.log != None:
111       if error:
112         self.parent.log.error( msg )
113       else:
114         self.parent.log.info( msg )
115    else:
116       print msg
117
118    doThis = True
119    if self.appendLogfile[0] != None and doThis:
120      if self.monitor != None:
121         nofh0 = self.monitor.get_open_fds()
122      xlog = self.c4i.getFileLog( self.appendLogfile[1], flf=self.appendLogfile[0] )
123      if error:
124         xlog.error( msg )
125      else:
126         xlog.info( msg )
127      self.c4i.closeFileLog()
128      if self.monitor != None:
129         nofh9 = self.monitor.get_open_fds()
130         if nofh9 > nofh0:
131           print 'Leaking file handles [1]: %s --- %s' % (nofh0, nofh9)
132
133  def log_exception( self, msg):
134    if self.parent != None and self.parent.log != None:
135        self.parent.log.error("Exception has occured" ,exc_info=1)
136    else:
137        traceback.print_exc(file=sys.stdout)
138
139  def log_error( self, msg ):
140    self.lastError = msg
141    self.errorCount += 1
142    self.logMessage( '%s.%s: FAILED:: %s' % (self.id,self.getCheckId(),msg), error=True )
143
144  def log_pass( self ):
145    self.passCount = True
146    if self.reportPass:
147      self.logMessage(  '%s.%s: OK' % (self.id,self.getCheckId()) )
148
149  def log_abort( self ):
150    self.completed = False
151    self.logMessage(   '%s.%s: ABORTED:: Errors too severe to complete further checks in this module' % (self.id,'xxx') )
152    raise abortChecks
153
154  def status(self):
155    return '%s.%s' % (self.id,self.getCheckId())
156
157  def getCheckId(self,full=True):
158    if type( self.checkId ) == type( 'x' ):
159      return self.checkId
160    else:
161      if full:
162        return '%s: [%s]' % self.checkId
163      else:
164        return self.checkId[0]
165
166  def test(self,res,msg,abort=False,part=False,appendLogfile=(None,None)):
167    self.appendLogfile = appendLogfile
168    if res:
169      if not part:
170         self.log_pass()
171    else:
172      self.log_error(msg)
173      if abort:
174        self.log_abort()
175    return res
176
177  def runChecks(self):
178
179    try:
180      for c in self.checks:
181        c()  # run check
182      self.completed = True
183    except abortChecks:
184      ## error logging done before raising this exception
185      return
186    except:
187      self.log_exception( 'Exception caught by runChecks' )
188      raise loggedException
189      ##traceback.print_exc(file=open("errlog.txt","a"))
190      ##logger.error("Exception has occured" ,exc_info=1)
191   
192class checkFileName(checkBase):
193
194  def init(self):
195    self.id = 'C4.001'
196    self.checkId = 'unset'
197    self.isFixed = False
198    self.step = 'Initialised'
199    self.checks = (self.do_check_fn,self.do_check_fnextra)
200    self.re_c1 = re.compile( '^[0-9]*$' )
201    self.fnDict = {}
202####
203
204  def check(self,fn):
205    self.errorCount = 0
206    assert type(fn) in [type('x'),type(u'x')], '1st argument to "check" method of checkGrids shound be a string variable name (not %s)' % type(fn)
207    self.fn = fn
208    self.fnsep = self.pcfg.fNameSep
209
210    self.runChecks()
211    self.parent.fnDict = self.fnDict
212###
213  def do_check_fn(self):
214    fn = self.fn
215    self.errorCount = 0
216    self.completed = False
217
218## check basic parsing of file name
219    self.checkId = ('001','parse_filename')
220    self.test( fn[-3:] == '.nc', 'File name ending ".nc" expected', abort=True, part=True )
221    bits = string.split( fn[:-3], self.fnsep )
222
223    self.fnParts = bits[:]
224    if self.pcfg.domainIndex != None:
225      self.domain = self.fnParts[self.pcfg.domainIndex]
226    else:
227      self.domain = None
228
229
230    if self.pcfg.projectV.id in ['ESA-CCI']:
231      self.test( 'ESACCI' in bits[:2], 'File name not a valid ESA-CCI file name: %s' % fn, abort=True )
232      if bits[0] == 'ESACCI':
233        self.esaFnId = 1
234      else:
235        self.esaFnId = 0
236        bb = string.split( bits[2], '_' )
237        self.test( bits[2][0] == 'L' and len(bb) == 2, 'Cannot parse ESA-CCI file name: %s' % fn, abort=True )
238        bits = bits[:2] + bb + bits[3:]
239        self.fnParts = bits[:]
240       
241      self.pcfg.setEsaCciFNType(self.esaFnId)
242    self.test( len(bits) in self.pcfg.fnPartsOkLen, 'File name not parsed in %s elements [%s]' % (str(self.pcfg.fnPartsOkLen),str(bits)), abort=True )
243
244    self.fnDict = {}
245    if self.pcfg.projectV.id in ['ESA-CCI']:
246      l0 = {0:6, 1:5}[self.esaFnId] 
247      for i in range(l0):
248        x = self.pcfg.globalAttributesInFn[i]
249        if x != None and x[0] == '*':
250          self.fnDict[x[1:]] = bits[i]
251      self.fnDict['version'] = bits[-1]
252      if self.esaFnId == 0:
253        if len(bits) == 9:
254          self.fnDict['additional'] = bits[-3]
255          self.fnDict['gdsv'] = bits[-2]
256        elif len(bits) == 8:
257          if bits[-2][0] == 'v':
258            self.fnDict['gdsv'] = bits[-2]
259          else:
260            self.fnDict['additional'] = bits[-2]
261      elif self.esaFnId == 1:
262        if len(bits) == 8:
263          self.fnDict['additional'] = bits[-3]
264       
265    if self.pcfg.groupIndex != None:
266      self.group = self.fnParts[self.pcfg.groupIndex]
267    else:
268      self.group = None
269
270    if self.pcfg.freqIndex != None:
271      self.freq = self.fnParts[self.pcfg.freqIndex]
272    elif self.group in ['fx','fixed']:
273      self.freq = 'fx'
274    else:
275      self.freq = None
276
277    ##if self.cls == 'CORDEX':
278      ##self.freq = self.fnParts[7]
279    ##elif self.cls == 'SPECS':
280      ##self.freq = self.fnParts[1]
281
282    self.var = self.fnParts[self.pcfg.varIndex]
283
284    if self.pcfg.fnvdict != None:
285      if self.pcfg.fnvdict.has_key( self.var ):
286        self.var = self.pcfg.fnvdict.get( self.var )['v']
287
288    self.isFixed = self.freq in ['fx','fixed']
289    self.parent.fileIsFixed = True
290    if self.isFixed:
291      self.test( len(self.fnParts) in self.pcfg.fnPartsOkFixedLen, 'Number of file name elements not acceptable for fixed data' )
292
293    self.checkId = ('002','parse_filename_timerange')
294    if not self.isFixed:
295
296## test time segment
297      if self.pcfg.trangeType == 'CMIP':
298        bits = string.split( self.fnParts[-1], '-' )
299        self.test( len(bits) == 2, 'File time segment [%s] will not parse into 2 elements' % (self.fnParts[-1] ), abort=True, part=True )
300
301        self.test(  len(bits[0]) == len(bits[1]), 'Start and end time specified in file name [%s] of unequal length' % (self.fnParts[-1] ), abort=True, part=True  )
302
303        for b in bits:
304          self.test( self.isInt(b), 'Time segment in filename [%s] contains non integer characters' % (self.fnParts[-1] ),  abort=True, part=True  )
305        self.log_pass()
306        self.fnTimeParts = bits[:]
307      elif self.pcfg.trangeType == 'ESA-CCI':
308        self.pcfg.checkTrangeLen = False
309        tt = self.fnParts[self.pcfg.trangeIndex] 
310        if self.test( len(tt) in [4,6,8,10,12,14] and self.re_c1.match(tt) != None, 'Length of indicative date/time not consistent with YYYY[MM[DD[HH[MM[SS]]]]] specification: %s' % self.fnParts[-1], part=True  ):
311          ll = [tt[:4],]
312          tt = tt[4:]
313          for j in range(5):
314            if len(tt) > 0:
315              ll.append( tt[:2] )
316              tt = tt[2:]
317            else:
318              ll.append( '00' )
319          indDateTime = map( int, ll )
320          self.test( indDateTime[1] in range(1,13), 'Invalid Month in indicative date time %s' % str(ll), part=True )
321          self.test( indDateTime[2] in range(1,32), 'Invalid Day in indicative date time %s' % str(ll), part=True )
322          self.test( indDateTime[3] in range(25), 'Invalid hour in indicative date time %s' % str(ll), part=True )
323          self.test( indDateTime[4] in range(60), 'Invalid minute in indicative date time %s' % str(ll), part=True )
324          self.test( indDateTime[5] in range(60), 'Invalid second in indicative date time %s' % str(ll), part=True )
325
326    self.checkId = '003'
327
328    self.checkId, ok = (('004','filename_timerange_length'),True)
329    if (not self.isFixed) and self.pcfg.checkTrangeLen:
330      ltr = { 'mon':6, 'sem':6, 'day':8, '3hr':[10,12], '6hr':10 }
331      ok &=self.test( self.freq in ltr.keys(), 'Frequency [%s] not recognised' % self.freq, part=True )
332      if ok:
333        if type( ltr[self.freq] ) == type(0):
334          msg = 'Length of time range parts [%s,%s] not equal to required length [%s] for frequency %s' % (self.fnTimeParts[0],self.fnTimeParts[1],ltr[self.freq],self.freq)
335          ok &= self.test( len(self.fnTimeParts[0]) == ltr[self.freq], msg, part=True )
336        elif type( ltr[self.freq] ) in [type([]),type( () )]:
337          msg = 'Length of time range parts [%s,%s] not in acceptable list [%s] for frequency %s' % (self.fnTimeParts[0],self.fnTimeParts[1],str(ltr[self.freq]),self.freq)
338          ok &= self.test( len(self.fnTimeParts[0]) in ltr[self.freq], msg, part=True )
339
340      if ok:
341        self.log_pass()
342
343  def do_check_fnextra(self):
344    self.checkId = ('004','file_name_extra' )
345    vocabs = self.pcfg.vocabs
346    m = []
347    for a in self.pcfg.controlledFnParts:
348      if self.fnDict.has_key(a):
349        try:
350          if not vocabs[a].check( str(self.fnDict[a]) ):
351            m.append( (a,self.fnDict[a],vocabs[a].note) )
352        except:
353          print 'failed trying to check file name component %s' % a
354          raise
355          ##raise baseException1( 'failed trying to check file name component %s' % a )
356
357    self.test( len(m)  == 0, 'File name components do not match constraints: %s' % str(m) )
358
359
360class checkGlobalAttributes(checkBase):
361
362  def init(self):
363    self.id = 'C4.002'
364    self.checkId = 'unset'
365    self.step = 'Initialised'
366    self.checks = (self.do_check_ga,)
367    self.fileId = None
368
369  def check(self,globalAts, varAts,varName,varGroup, vocabs, fnParts):
370    self.errorCount = 0
371    assert type(varName) in [type('x'),type(u'x')], '1st argument to "check" method of checkGrids shound be a string variable name (not %s)' % type(varName)
372    self.var = varName
373    self.globalAts = globalAts
374    self.varAts = varAts
375    self.varGroup = varGroup
376    self.vocabs = vocabs
377    self.fnParts = fnParts
378    self.runChecks()
379
380  def getId(self):
381    if self.fileId == None:
382      self.fileId = '%s.%s' % (self.globalAts['naming_authority'],self.globalAts['id'])
383
384  def getDrs( self ):
385    assert self.completed, 'method getDrs should not be called if checks have not been completed successfully'
386    ee = {}
387    drsDefaults = { 'convention_version':'n/a'}
388    if not self.globalAts.has_key('product'):
389        self.globalAts['product'] = 'output'
390    for k in self.drsMappings:
391      if self.drsMappings[k] == '@var':
392        ee[k] = self.var
393      elif self.drsMappings[k][0] == '=':
394        ee[k] = self.drsMappings[k][1:]
395      elif self.drsMappings[k] == '@ensemble':
396        ee[k] = "r%si%sp%s" % (self.globalAts["realization"],self.globalAts["initialization_method"],self.globalAts["physics_version"])
397      elif self.drsMappings[k] == '@forecast_reference_time':
398        x = self.globalAts.get("forecast_reference_time",'yyyy-mm-dd Thh:mm:ssZ' )
399        ee[k] = "%s%s%s" % (x[:4],x[5:7],x[8:10])
400      elif self.drsMappings[k] == '@mip_id':
401        ee[k] = string.split( self.globalAts["table_id"] )[1]
402      elif self.drsMappings[k] == '@ecv':
403        ee[k] = self.pcfg.ecvMappings[ self.parent.fnDict['project'] ]
404      elif self.drsMappings[k][0] == '$':
405        self.pcfg.getExtraAtts()
406        print self.pcfg.extraAtts
407        self.getId()
408        if string.find(self.drsMappings[k],':') != -1:
409          k2,dflt = string.split( self.drsMappings[k][1:],':')
410          ee[k] = self.pcfg.extraAtts[self.fileId].get( k2, dflt )
411        else:
412          ee[k] = self.pcfg.extraAtts[self.fileId][self.drsMappings[k][1:]]
413      elif self.drsMappings[k][0] == '*':
414        thisk = self.drsMappings[k][1:]
415        ee[k] = self.varAts[self.var][thisk]
416      elif self.drsMappings[k][0] == '#':
417        thisk = self.drsMappings[k][1:]
418        if drsDefaults.has_key( thisk ):
419          ee[k] = self.parent.fnDict.get(thisk, drsDefaults[thisk] )
420        else:
421          ee[k] = self.parent.fnDict[thisk]
422      else:
423        ee[k] = self.globalAts[ self.drsMappings[k] ]
424
425    for k in ['creation_date','tracking_id']:
426      if k in self.globalAts.keys():
427        ee[k] = self.globalAts[k]
428
429    return ee
430
431  def do_check_ga(self):
432    varName = self.var
433    globalAts = self.globalAts
434    varAts = self.varAts
435    varGroup = self.varGroup
436    vocabs = self.vocabs
437    fnParts = self.fnParts
438
439    self.completed = False
440    self.checkId = ('001','global_ncattribute_present')
441    m = []
442    for k in self.requiredGlobalAttributes:
443      if not globalAts.has_key(k):
444         m.append(k)
445         self.globalAts[k] = '__errorReported__'
446
447    if not self.test( len(m)  == 0, 'Required global attributes missing: %s' % str(m) ):
448      gaerr = True
449      for k in m:
450        self.parent.amapListDraft.append( '#@;%s=%s|%s=%s' % (k,'__absent__',k,'<insert attribute value and uncomment>') )
451
452    self.checkId = ('002','variable_in_group')
453
454    self.test( varAts.has_key( varName ), 'Expected variable [%s] not present' % varName, abort=True, part=True )
455    msg = 'Variable %s not in table %s' % (varName,varGroup)
456    self.test( vocabs['variable'].isInTable( varName, varGroup ), msg, abort=True, part=True )
457
458    if self.pcfg.checkVarType:
459      self.checkId = ('003','variable_type')
460
461      mipType = vocabs['variable'].getAttr( varName, varGroup, 'type' )
462      thisType = {'real':'float32', 'integer':'int32', 'float':'float32', 'double':'float64' }.get( mipType, mipType )
463      self.test( mipType == None or varAts[varName]['_type'] == thisType, 'Variable [%s/%s] not of type %s [%s]' % (varName,varGroup,str(thisType),varAts[varName]['_type']) )
464    else:
465      mipType = None
466
467    self.checkId = ('004','variable_ncattribute_present')
468    m = []
469    reqAts = self.requiredVarAttributes[:]
470    if (not self.parent.fileIsFixed) and self.pcfg.projectV.id in ['CORDEX']:
471      reqAts.append( 'cell_methods' )
472    for k in reqAts + vocabs['variable'].lists(varName, 'addRequiredAttributes'):
473      if not varAts[varName].has_key(k):
474         m.append(k)
475    if not self.test( len(m)  == 0, 'Required variable attributes missing: %s' % str(m) ):
476      vaerr = True
477      for k in m:
478        self.parent.amapListDraft.append( '#@var=%s;%s=%s|%s=%s' % (varName,k,'__absent__',k,'<insert attribute value and uncomment>') )
479        ## print self.parent.amapListDraft[-1]
480    ##vaerr = not self.test( len(m)  == 0, 'Required variable attributes missing: %s' % str(m) )
481
482    ##if vaerr or gaerr:
483      ##self.log_abort()
484
485## need to insert a check that variable is present
486    self.checkId = ('005','variable_ncattribute_mipvalues')
487    ok = True
488    hm = varAts[varName].get( 'missing_value', None ) != None
489    hf = varAts[varName].has_key( '_FillValue' )
490    if hm or hf:
491      if self.pcfg.varTables=='CMIP':
492        ok &= self.test( hm, 'missing_value must be present if _FillValue is [%s]' % varName )
493        ok &= self.test( hf, '_FillValue must be present if missing_value is [%s]' % varName )
494      else:
495        ok = True
496      if mipType == 'real':
497        if varAts[varName].has_key( 'missing_value' ):
498           msg = 'Variable [%s] has incorrect attribute missing_value=%s [correct: %s]' % (varName,varAts[varName]['missing_value'],self.missingValue)
499           ## print varAts[varName]['missing_value'], type(varAts[varName]['missing_value'])
500           ## print self.missingValue, type(self.missingValue)
501### need to use ctypes here when using ncq3 to read files -- appears OK for other libraries.
502           ok &= self.test( ctypes.c_float(varAts[varName]['missing_value']).value == ctypes.c_float(self.missingValue).value, msg, part=True )
503        if varAts[varName].has_key( '_FillValue' ):
504           msg = 'Variable [%s] has incorrect attribute _FillValue=%s [correct: %s]' % (varName,varAts[varName]['_FillValue'],self.missingValue)
505           ok &= self.test( varAts[varName]['_FillValue'] == self.missingValue, msg, part=True )
506
507    mm = []
508   
509    if self.pcfg.varTables=='CMIP':
510      contAts = ['long_name', 'standard_name', 'units']
511      if not self.parent.fileIsFixed:
512      ##if varGroup not in ['fx','fixed']:
513        contAts.append( 'cell_methods' )
514    else:
515      contAts = ['standard_name']
516    hcm = varAts[varName].has_key( "cell_methods" )
517    for k in contAts + vocabs['variable'].lists(varName,'addControlledAttributes'):
518      targ = varAts[varName].get( k, 'Attribute not present' )
519      val = vocabs['variable'].getAttr( varName, varGroup, k )
520
521      if k == "cell_methods":
522        if val != None:
523          parenthesies1 = []
524          targ0 = targ[:]
525          while string.find( targ, '(' ) != -1:
526            i0 = targ.index( '(' )
527            i1 = targ.index( ')' )
528            parenthesies1.append( targ[i0:i1+1] )
529            targ = targ[:i0-1] + targ[i1+1:]
530          parenthesies2 = []
531          val0 = val[:]
532          while string.find( val, '(' ) != -1:
533            i0 = val.index( '(' )
534            i1 = val.index( ')' )
535            parenthesies2.append( val[i0:i1+1] )
536            val = val[:i0-1] + val[i1+1:]
537          for p in parenthesies2:
538            if p not in parenthesies1:
539              mm.append( (k,parenthesies1,p) )
540          if string.find( targ, val):
541             mm.append( (k,targ,val) )
542      elif targ != 'Attribute not present' and targ != val:
543        mm.append( (k,targ,val) )
544
545    ok &= self.test( len(mm)  == 0, 'Variable [%s] has incorrect attributes: %s' % (varName, strmm3(mm)), part=True )
546    if len( mm  ) != 0:
547      if self.parent.amapListDraft == None:
548        self.parent.amapListDraft = []
549      for m in mm:
550          self.parent.amapListDraft.append( '@var=%s;%s=%s|%s=%s' % (varName,m[0],m[1],m[0],m[2]) )
551
552    if ok:
553       self.log_pass()
554
555    if (not self.parent.fileIsFixed) and hcm:
556    ## if (varGroup not in ['fx','fixed']) and hcm:
557      self.isInstantaneous = string.find( varAts[varName]['cell_methods'], 'time: point' ) != -1
558    else:
559      self.isInstantaneous = True
560
561    self.checkId = ('006','global_ncattribute_cv' )
562    m = []
563    for a in self.controlledGlobalAttributes:
564      if globalAts.has_key(a):
565        try:
566          if not vocabs[a].check( str(globalAts[a]) ):
567            m.append( (a,globalAts[a],vocabs[a].note) )
568        except:
569          print 'failed trying to check global attribute %s' % a
570          raise baseException( 'failed trying to check global attribute %s' % a )
571
572    if not self.test( len(m)  == 0, 'Global attributes do not match constraints: %s' % str(m) ):
573      for t in m:
574        self.parent.amapListDraft.append( '#@;%s=%s|%s=%s' % (t[0],str(t[1]),t[0],'<insert attribute value and uncomment>' + str(t[2]) ) )
575
576    self.checkId = ('007','filename_filemetadata_consistency')
577    m = []
578    for i in range(len(self.globalAttributesInFn)):
579       if self.globalAttributesInFn[i] != None and self.globalAttributesInFn[i][0] != '*':
580         targVal = fnParts[i]
581         if self.globalAttributesInFn[i][0] == "@":
582           if self.globalAttributesInFn[i][1:] == "mip_id":
583               bits = string.split( globalAts[ "table_id" ] ) 
584               if len( bits ) > 2 and bits[0] == "Table":
585                 thisVal = bits[1]
586               else:
587                 thisVal = globalAts[ "table_id" ]
588                 self.test( False, 'Global attribute table_id does not conform to CMOR pattern ["Table ......"]: %s' % thisVal, part=True)
589           elif self.globalAttributesInFn[i][1:] == "ensemble":
590               thisVal = "r%si%sp%s" % (globalAts["realization"],globalAts["initialization_method"],globalAts["physics_version"])
591## following mappings are depricated -- introduced for SPECS and withdrawn ---
592           elif self.globalAttributesInFn[i][1:] == "experiment_family":
593               thisVal = globalAts["experiment_id"][:-4]
594           elif self.globalAttributesInFn[i][1:] == "forecast_reference_time":
595               x = self.globalAts.get("forecast_reference_time",'yyyy-mm-dd Thh:mm:ssZ' )
596               thisVal = "S%s%s%s" % (x[:4],x[5:7],x[8:10])
597           elif self.globalAttributesInFn[i][1:] == "series":
598               thisVal = 'series%s' % globalAts["series"]
599           else:
600               assert False, "Not coded to deal with this configuration: globalAttributesInFn[%s]=%s" % (i,self.globalAttributesInFn[i])
601         
602         else:
603             thisVal = globalAts[self.globalAttributesInFn[i]]
604
605         if thisVal not in [targVal,'__errorReported__']:
606             m.append( (i,self.globalAttributesInFn[i]) )
607
608    self.test( len(m)  == 0,'File name segments do not match corresponding global attributes: %s' % str(m) )
609
610    self.completed = True
611       
612class checkStandardDims(checkBase):
613
614  def init(self):
615    self.id = 'C4.003'
616    self.checkId = 'unset'
617    self.step = 'Initialised'
618    self.checks = (self.do_check,)
619    self.plevRequired = self.pcfg.plevRequired
620    self.plevValues = self.pcfg.plevValues
621    self.heightRequired = self.pcfg.heightRequired
622    self.heightValues = self.pcfg.heightValues
623    self.heightRange = self.pcfg.heightRange
624
625  def check(self,varName,varGroup, da, va, isInsta,vocabs):
626    self.errorCount = 0
627    assert type(varName) in [type('x'),type(u'x')], '1st argument to "check" method of checkGrids shound be a string variable name (not %s)' % type(varName)
628    self.var = varName
629    self.varGroup = varGroup
630    self.da = da
631    self.va = va
632    self.isInsta = isInsta
633    self.vocabs = vocabs
634    self.runChecks()
635
636  def do_check(self):
637    varName = self.var
638    varGroup = self.varGroup
639    da = self.da
640    va = self.va
641    isInsta = self.isInsta
642
643    self.errorCount = 0
644    self.completed = False
645    self.checkId = ('001','time_attributes')
646    self.calendar = 'None'
647    if not self.parent.fileIsFixed:
648    ## if varGroup not in ['fx','fixed']:
649      ok = True
650      self.test( 'time' in da.keys(), 'Time dimension not found' , abort=True, part=True )
651      if self.pcfg.varTables=='CMIP':
652        if not isInsta:
653          ok &= self.test(  da['time'].get( 'bounds', 'xxx' ) == 'time_bnds', 'Required bounds attribute not present or not correct value', part=True )
654
655## is time zone designator needed?
656        tunits = da['time'].get( 'units', 'xxx' )
657        if self.project  == 'CORDEX':
658          ok &= self.test( tunits in ["days since 1949-12-01 00:00:00Z", "days since 1949-12-01 00:00:00", "days since 1949-12-01"],
659               'Time units [%s] attribute not set correctly to "days since 1949-12-01 00:00:00Z"' % tunits, part=True )
660        else:
661          ok &= self.test( tunits[:10] == "days since", 'time units [%s] attribute not set correctly to "days since ....."' % tunits, part=True )
662
663        ok &= self.test(  da['time'].has_key( 'calendar' ), 'Time: required attribute calendar missing', part=True )
664
665        ok &= self.test( da['time']['_type'] in ["float64","double"], 'Time: data type not float64 [%s]' % da['time']['_type'], part=True )
666       
667        if ok:
668          self.log_pass()
669        self.calendar = da['time'].get( 'calendar', 'None' )
670
671    self.checkId = ('002','pressure_levels')
672    if varName in self.plevRequired:
673      ok = True
674      self.test( 'plev' in va.keys(), 'plev coordinate not found %s' % str(va.keys()), abort=True, part=True )
675
676      ok &= self.test( int( va['plev']['_data'][0] ) == self.plevValues[varName],  \
677                  'plev value [%s] does not match required [%s]' % (va['plev']['_data'],self.plevValues[varName] ), part=True )
678     
679      plevAtDict = {'standard_name':"air_pressure", \
680                    'long_name':"pressure", \
681                    'units':"Pa", \
682                    'positive':"down", \
683                    'axis':"Z" }
684
685      if varName in ['clh','clm','cll']:
686        plevAtDict['bounds']= "plev_bnds"
687
688      for k in plevAtDict.keys():
689        ok &= self.test( va['plev'].get( k, None ) == plevAtDict[k], 
690                     'plev attribute %s absent or wrong value (should be %s)' % (k,plevAtDict[k]), part=True )
691
692      if varName in ['clh','clm','cll']:
693         self.test( "plev_bnds" in va.keys(), 'plev_bnds variable not found %s' % str(va.keys()), abort=True, part=True )
694         mm = []
695         for k in plevAtDict.keys():
696            if k != 'bounds' and k in va['plev_bnds'].keys():
697               if va['plev_bnds'][k] != va['plev'][k]:
698                 mm.append(k)
699         ok &= self.test( len(mm) == 0, 'Attributes of plev_bnds do not match those of plev: %s' % str(mm), part=True )
700
701         bndsVals = {'clh':[44000, 0], 'clm':[68000, 44000], 'cll':[100000, 68000] }
702         res = self.test( len( va['plev_bnds']['_data'] ) == 2, 'plev_bnds array is of wrong length', part=True )
703         ok &= res
704         if res:
705            kk = 0
706            for i in [0,1]:
707               if int(va['plev_bnds']['_data'][i]) != bndsVals[varName][i]:
708                  kk+=1
709            ok &= self.test( kk == 0, 'plev_bnds values not correct: should be %s' % str(bndsVals[varName]), part=True )
710
711      if ok:
712        self.log_pass()
713
714    self.checkId = ('003','height_levels')
715    hreq = varName in self.heightRequired
716    if self.parent.experimental:
717      print 'utils_c4: ', varName, self.vocabs['variable'].varcons[varGroup][varName].get( '_dimension',[])
718      hreq = "height2m" in self.vocabs['variable'].varcons[varGroup][varName].get( '_dimension',[])
719      if hreq:
720        print 'testing height, var=%s' % varName
721    if hreq:
722      heightAtDict = {'long_name':"height", 'standard_name':"height", 'units':"m", 'positive':"up", 'axis':"Z" }
723      ok = True
724      ok &= self.test( 'height' in va.keys(), 'height coordinate not found %s' % str(va.keys()), abort=True, part=True )
725      ##ok &= self.test( abs( va['height']['_data'] - self.heightValues[varName]) < 0.001, \
726                ##'height value [%s] does not match required [%s]' % (va['height']['_data'],self.heightValues[varName] ), part=True )
727
728      ok1 = self.test( len( va['height']['_data'] ) == 1, 'More height values (%s) than expected (1)' % (len( va['height']['_data'])), part=True )
729      if ok1:
730        r = self.heightRange[varName]
731        ok1 &= self.test( r[0] <= va['height']['_data'][0] <= r[1], \
732                'height value [%s] not in specified range [%s]' % (va['height']['_data'], (self.heightRange[varName] ) ), part=True )
733
734      ok &= ok1
735     
736      for k in heightAtDict.keys():
737        val =  va['height'].get( k, "none" )
738        if not self.test( val == heightAtDict[k], \
739                         'height attribute %s absent or wrong value (should be %s)' % (k,heightAtDict[k]), part=True ):
740          self.parent.amapListDraft.append( '@var=%s;%s=%s|%s=%s' % ('height',k,val,k,heightAtDict[k]) )
741          ok = False
742
743      if ok:
744        self.log_pass()
745
746    self.completed = True
747
748class checkGrids(checkBase):
749
750  def init(self):
751    self.id = 'C4.004'
752    self.checkId = 'unset'
753    self.step = 'Initialised'
754    self.checks = (self.do_check_rp,self.do_check_intd)
755
756  def check(self,varName, domain, da, va):
757    self.errorCount = 0
758    assert type(varName) in [type('x'),type(u'x')], '1st argument to "check" method of checkGrids shound be a string variable name (not %s)' % type(varName)
759    self.var = varName
760    self.domain = domain
761    self.da = da
762    self.va = va
763
764    self.runChecks()
765    ##for c in self.checks:
766      ##c()
767    ##self.do_check_rp()
768    ##self.do_check_intd()
769
770  def do_check_rp(self):
771    varName = self.var
772    domain = self.domain
773    da = self.da
774    va = self.va
775    if va[varName].get( 'grid_mapping', None ) == "rotated_pole":
776      self.checkId = ('001','grid_mapping')
777      atDict = { 'grid_mapping_name':'rotated_latitude_longitude' }
778      atDict['grid_north_pole_latitude'] = self.pcfg.rotatedPoleGrids[domain]['grid_np_lat']
779      if self.pcfg.rotatedPoleGrids[domain]['grid_np_lon'] != 'N/A':
780        atDict['grid_north_pole_longitude'] = self.pcfg.rotatedPoleGrids[domain]['grid_np_lon']
781
782      self.checkId = ('002','rotated_latlon_attributes')
783      self.test( 'rlat' in da.keys() and 'rlon' in da.keys(), 'rlat and rlon not found (required for grid_mapping = rotated_pole )', abort=True, part=True )
784
785      atDict = {'rlat':{'long_name':"rotated latitude", 'standard_name':"grid_latitude", 'units':"degrees", 'axis':"Y", '_type':'float64'},
786                'rlon':{'long_name':"rotated longitude", 'standard_name':"grid_longitude", 'units':"degrees", 'axis':"X", '_type':'float64'} }
787      mm = []
788      for k in ['rlat','rlon']:
789        for k2 in atDict[k].keys():
790          if atDict[k][k2] != da[k].get(k2, None ):
791            mm.append( (k,k2) )
792            record = '#@ax=%s;%s=%s|%s=%s <uncomment if correct>' % (k,k2,da[k].get(k2, '__missing__'),k2,atDict[k][k2]   )
793            self.parent.amapListDraft.append( record )
794      self.test( len(mm) == 0, 'Required attributes of grid coordinate arrays not correct: %s' % str(mm) )
795
796      self.checkId = ('003','rotated_latlon_domain')
797      ok = True
798      for k in ['rlat','rlon']:
799        res = len(da[k]['_data']) == self.pcfg.rotatedPoleGrids[domain][ {'rlat':'nlat','rlon':'nlon' }[k] ]
800        if not res:
801          self.test( res, 'Size of %s dimension does not match specification (%s,%s)' % (k,a,b), part=True  )
802          ok = False
803
804      a = ( da['rlat']['_data'][0], da['rlat']['_data'][-1], da['rlon']['_data'][0], da['rlon']['_data'][-1] )
805      b = map( lambda x: self.pcfg.rotatedPoleGrids[domain][x], ['s','n','w','e'] )
806      mm = []
807      for i in range(4):
808        if abs(a[i] - b[i]) > self.pcfg.gridSpecTol:
809          mm.append( (a[i],b[i]) )
810
811      ok &= self.test( len(mm) == 0, 'Domain boundaries for rotated pole grid do not match %s within tolerance (%s)' % (str(mm),self.pcfg.gridSpecTol), part=True )
812
813      for k in ['rlat','rlon']:
814        ok &= self.test( cs.check( da[k]['_data'] ), '%s values not evenly spaced -- min/max delta = %s, %s' % (k,cs.dmn,cs.dmx), part=True )
815
816      if ok:
817        self.log_pass()
818
819  def do_check_intd(self):
820    varName = self.var
821    domain = self.domain
822    da = self.da
823    va = self.va
824    if domain[-1] == 'i':
825      self.checkId = ('004','regular_grid_attributes')
826      self.test( 'lat' in da.keys() and 'lon' in da.keys(), 'lat and lon not found (required for interpolated data)', abort=True, part=True )
827
828      atDict = {'lat':{'long_name':"latitude", 'standard_name':"latitude", 'units':"degrees_north", '_type':'float64'},
829                'lon':{'long_name':"longitude", 'standard_name':"longitude", 'units':"degrees_east", '_type':'float64'} }
830      mm = []
831      for k in ['lat','lon']:
832        for k2 in atDict[k].keys():
833          if atDict[k][k2] != da[k].get(k2, None ):
834            mm.append( (k,k2) )
835            record = '#@ax=%s;%s=%s|%s=%s <uncomment if correct>' % (k,k2,da[k].get(k2, '__missing__'),k2,atDict[k][k2]   )
836            self.parent.amapListDraft.append( record )
837
838      self.test( len(mm) == 0,  'Required attributes of grid coordinate arrays not correct: %s' % str(mm), part=True )
839
840      ok = True
841      self.checkId = ('005','regular_grid_domain')
842      for k in ['lat','lon']:
843        res = len(da[k]['_data']) >= self.pcfg.interpolatedGrids[domain][ {'lat':'nlat','lon':'nlon' }[k] ]
844        if not res:
845          a,b =  len(da[k]['_data']), self.pcfg.interpolatedGrids[domain][ {'lat':'nlat','lon':'nlon' }[k] ]
846          self.test( res, 'Size of %s dimension does not match specification (%s,%s)' % (k,a,b), part=True )
847          ok = False
848
849      a = ( da['lat']['_data'][0], da['lat']['_data'][-1], da['lon']['_data'][0], da['lon']['_data'][-1] )
850      b = map( lambda x: self.pcfg.interpolatedGrids[domain][x], ['s','n','w','e'] )
851      rs = self.pcfg.interpolatedGrids[domain]['res']
852      c = [-rs,rs,-rs,rs]
853      mm = []
854      for i in range(4):
855        if a[i] != b[i]:
856          x = (a[i]-b[i])/c[i]
857          if x < 0 or abs( x - int(x) ) > 0.001:
858             skipThis = False
859             if self.project  == 'CORDEX':
860               if domain[:3] == 'ANT':
861                 if i == 2 and abs( a[i] - 0.25 ) < 0.001:
862                    skipThis = True
863                 elif i == 3 and abs( a[i] - 359.75 ) < 0.001:
864                    skipThis = True
865             if not skipThis:
866               mm.append( (a[i],b[i]) )
867
868      ok &= self.test( len(mm) == 0, 'Interpolated grid boundary error: File %s; Req. %s' % (str(a),str(b)), part=True )
869
870      for k in ['lat','lon']:
871        ok &= self.test( cs.check( da[k]['_data'] ), '%s values not evenly spaced -- min/max delta = %s, %s' % (k,cs.dmn,cs.dmx), part=True )
872      if ok:
873        self.log_pass()
874
875class mipVocab(object):
876
877  def __init__(self,pcfg,dummy=False):
878     self.pcfg = pcfg
879     if dummy:
880       self.dummyMipTable()
881     elif pcfg.varTables=='CMIP':
882       self.ingestMipTables()
883     elif pcfg.varTables=='FLAT':
884       self.flatTable()
885   
886  def ingestMipTables(self):
887     dir, tl, vgmap, fnpat = self.pcfg.mipVocabPars
888     ms = mipTableScan()
889     self.varInfo = {}
890     self.varcons = {}
891     for f in tl:
892        vg = vgmap.get( f, f )
893        self.varcons[vg] = {}
894        fn = fnpat % f
895        ll = open( '%s%s' % (dir,fn) ).readlines()
896        ee = ms.scan_table(ll,None,asDict=True)
897        for v in ee.keys():
898## set global default: type float
899          eeee = { 'type':self.pcfg.defaults.get( 'variableDataType', 'float' ) }
900          eeee['_dimension'] = ee[v][0]
901          ar = []
902          ac = []
903          for a in ee[v][1].keys():
904            eeee[a] = ee[v][1][a]
905          ##if 'positive' in eeee.keys():
906            ##ar.append( 'positive' )
907            ##ac.append( 'positive' )
908          self.varInfo[v] = {'ar':ar, 'ac':ac }
909          self.varcons[vg][v] = eeee
910           
911  def dummyMipTable(self):
912     self.varInfo = {}
913     self.varcons = {}
914     ee = { 'standard_name':'sn%s', 'long_name':'n%s', 'units':'1' }
915     dir, tl, vgmap, fnpat = self.pcfg.mipVocabPars
916     for f in tl:
917        vg = vgmap.get( f, f )
918        self.varcons[vg] = {}
919        for i in range(12):
920          v = 'v%s' % i
921          eeee = {}
922          eeee['standard_name'] = ee['standard_name'] % i
923          eeee['long_name'] = ee['long_name'] % i
924          eeee['cell_methods'] = 'time: point'
925          eeee['units'] = ee['units']
926          eeee['type'] = 'float'
927          ar = []
928          ac = []
929          self.varInfo[v] = {'ar':ar, 'ac':ac }
930          self.varcons[vg][v] = eeee
931
932  def flatTable(self):
933     self.varInfo = {}
934     self.varcons = {}
935     dir, tl, vg, fn = self.pcfg.mipVocabPars
936     ee = { 'standard_name':'sn%s', 'long_name':'n%s', 'units':'1' }
937     ll = open( '%s%s' % (dir,fn) ).readlines()
938     self.varcons[vg] = {}
939     for l in ll:
940       if l[0] != '#':
941          dt, v, sn = string.split( string.strip(l) )
942          self.pcfg.fnvdict[dt] = { 'v':v, 'sn':sn }
943          ar = []
944          ac = []
945          self.varInfo[v] = {'ar':ar, 'ac':ac }
946          self.varcons[vg][v] = {'standard_name':sn, 'type':'float' }
947
948  def lists( self, k, k2 ):
949     if k2 == 'addRequiredAttributes':
950       return self.varInfo[k]['ar']
951     elif k2 == 'addControlledAttributes':
952       return self.varInfo[k]['ac']
953     else:
954       raise baseException( 'mipVocab.lists called with bad list specifier %s' % k2 )
955
956  def isInTable( self, v, vg ):
957    assert vg in self.varcons.keys(), '%s not found in  self.varcons.keys() [%s]' % (vg,str(self.varcons.keys()) )
958    return (v in self.varcons[vg].keys())
959     
960  def getAttr( self, v, vg, a ):
961    assert vg in self.varcons.keys(), '%s not found in  self.varcons.keys()'
962    assert v in self.varcons[vg].keys(), '%s not found in self.varcons[%s].keys()' % (v,vg)
963     
964    return self.varcons[vg][v][a]
965     
966class patternControl(object):
967
968  def __init__(self,tag,pattern,list=None,cls=None,examples=None,badExamples=None,runTest=True):
969    if cls != None:
970      assert cls in ['ISO'], 'value of cls [%s] not recognised' % cls
971      if cls == 'ISO':
972        assert pattern in ['ISO8601 duration'], 'value of pattern [%s] for ISO constraint not recognised' % pattern
973        if pattern == 'ISO8601 duration':
974          thispat = '^(P([0-9]+Y){0,1}([0-9]+M){0,1}([0-9]+D){0,1}(T([0-9]+H){0,1}([0-9]+M){0,1}([0-9]+(.[0-9]+){0,1}S){0,1}){0,1})$'
975        self.re_pat = re.compile( thispat )
976        self.pattern = thispat
977        self.pattern_src = pattern
978    else:
979      try:
980        self.re_pat = re.compile( pattern )
981      except:
982        print "Failed to compile pattern >>%s<< (%s)" % (pattern, tag)
983      self.pattern = pattern
984   
985    self.examples = examples
986    self.badExamples = badExamples
987    self.list = list
988    self.cls = cls
989
990    if runTest:
991      if examples != None:
992        for e in examples:
993          assert self.check(e), 'Internal check failed: example %s does not fit pattern %s' % (e,self.pattern)
994
995  def check(self,val):
996    self.note = '-'
997    m = self.re_pat.match( val )
998    if self.list == None:
999      self.note = "simple test"
1000      return m != None
1001    else:
1002      if m == None:
1003        self.note = "no match %s::%s" % (val,self.pattern)
1004        return False
1005      if not m.groupdict().has_key("val"):
1006        self.note = "no 'val' in match"
1007        return False
1008      self.note = "val=%s" % m.groupdict()["val"]
1009      return m.groupdict()["val"] in self.list
1010   
1011class listControl(object):
1012  def __init__(self,tag,list,split=False,splitVal=None,enumeration=False):
1013    self.list = list
1014    self.tag = tag
1015    self.split = split
1016    self.splitVal = splitVal
1017    self.enumeration = enumeration
1018    self.etest = re.compile( '(.*)<([0-9]+(,[0-9]+)*)>' )
1019    self.essplit = re.compile(r'(?:[^\s,<]|<(?:\\.|[^>])*>)+')
1020
1021  def check(self,val):
1022    self.note = '-'
1023    if len(self.list) < 4:
1024      self.note = str( self.list )
1025    else:
1026      self.note = str( self.list[:4] )
1027    if self.split:
1028      if self.splitVal == None:
1029        vs = string.split( val )
1030      elif self.enumeration:
1031        vs = map( string.strip, self.essplit.findall( val ) )
1032      else:
1033        vs = map( string.strip, string.split( val, self.splitVal ) )
1034    else:
1035      vs = [val,]
1036    if self.enumeration:
1037      vs2 = []
1038      for v in vs:
1039        m = self.etest.findall( v )
1040        if m in [None,[]]:
1041          vs2.append( v )
1042        else:
1043          opts = string.split( m[0][1], ',' )
1044          for o in opts:
1045            vs2.append( '%s%s' % (m[0][0],o) )
1046      vs = vs2[:]
1047       
1048    return all( map( lambda x: x in self.list, vs ) )
1049
1050
1051class checkByVar(checkBase):
1052
1053  def init(self,fileNameSeparator='_'):
1054    self.id = 'C5.001'
1055    self.checkId = 'unset'
1056    self.step = 'Initialised'
1057    self.checks = (self.checkTrange,)
1058    self.fnsep = fileNameSeparator
1059
1060  def setLogDict( self,fLogDict ):
1061    self.fLogDict = fLogDict
1062
1063  def impt(self,flist):
1064    ee = {}
1065    elist = []
1066    for f in flist:
1067      fn = string.split(f, '/' )[-1]
1068      fnParts = string.split( fn[:-3], self.fnsep )
1069     
1070      try:
1071        if self.pcfg.freqIndex != None:
1072          freq = fnParts[self.pcfg.freqIndex]
1073        else:
1074          freq = None
1075
1076        ### isFixed = freq  in ['fx','fixed']
1077        group = fnParts[ self.pcfg.groupIndex ]
1078
1079        if self.parent.fileIsFixed:
1080       ## if isFixed:
1081          trange = None
1082        else:
1083          trange = string.split( fnParts[-1], '-' )
1084        var = fnParts[self.pcfg.varIndex]
1085        thisKey = string.join( fnParts[:-1], '.' )
1086        if group not in ee.keys():
1087          ee[group] = {}
1088        if thisKey not in ee[group].keys():
1089          ee[group][thisKey] = []
1090        ee[group][thisKey].append( (f,fn,group,trange) )
1091      except:
1092        print 'Cannot parse file name: %s' % (f) 
1093        elist.append(f)
1094## this ee entry is not used, except in bookkeeping check below.
1095## parsing of file name is repeated later, and a error log entry is created at that stage -- this could be improved.
1096## in order to improve, need to clarify flow of program: the list here is used to provide preliminary info before log files etc are set up.
1097        group = '__error__'
1098        thisKey = fn
1099        if group not in ee.keys():
1100          ee[group] = {}
1101        if thisKey not in ee[group].keys():
1102          ee[group][thisKey] = []
1103        ee[group][thisKey].append( (f,fn,group) )
1104
1105    nn = len(flist)
1106    n2 = 0
1107    for k in ee.keys():
1108      for k2 in ee[k].keys():
1109        n2 += len( ee[k][k2] )
1110
1111    assert nn==n2, 'some file lost!!!!!!'
1112    if len(elist) == 0:
1113      self.info =  '%s files, %s' % (nn,str(ee.keys()) )
1114    else:
1115      self.info =  '%s files, %s frequencies, severe errors in file names: %s' % (nn,len(ee.keys()),len(elist) )
1116      for e in elist:
1117        self.info += '\n%s' % e
1118    self.ee = ee
1119
1120  def check(self, recorder=None,calendar='None',norun=False):
1121    self.errorCount = 0
1122    self.recorder=recorder
1123    self.calendar=calendar
1124    if calendar == '360-day':
1125      self.enddec = 30
1126    else:
1127      self.enddec = 31
1128    mm = { 'enddec':self.enddec }
1129    self.pats = {'mon':('(?P<d>[0-9]{3})101','(?P<e>[0-9]{3})012'), \
1130            'sem':('(?P<d>[0-9]{3})(012|101)','(?P<e>[0-9]{3})(011|010)'), \
1131            'day':('(?P<d>[0-9]{3}[16])0101','(?P<e>[0-9]{3}[50])12%(enddec)s' % mm), \
1132            'subd':('(?P<d>[0-9]{4})0101(?P<h1>[0-9]{2})(?P<mm>[30]0){0,1}$', '(?P<e>[0-9]{4})12%(enddec)s(?P<h2>[0-9]{2})([30]0){0,1}$' % mm ), \
1133            'subd2':('(?P<d>[0-9]{4})0101(?P<h1>[0-9]{2})', '(?P<e>[0-9]{4})010100' ) }
1134
1135    if not norun:
1136      self.runChecks()
1137
1138  def checkTrange(self):
1139    keys = self.ee.keys()
1140    keys.sort()
1141    for k in keys:
1142      if k not in ['fx','fixed']:
1143        keys2 = self.ee[k].keys()
1144        keys2.sort()
1145        for k2 in keys2:
1146          self.checkThisTrange( self.ee[k][k2], k )
1147
1148  def checkThisTrange( self, tt, group):
1149
1150    if group in ['3hr','6hr']:
1151       kg = 'subd'
1152    else:
1153       kg = group
1154    ps = self.pats[kg]
1155    rere = (re.compile( ps[0] ), re.compile( ps[1] ) )
1156
1157    n = len(tt)
1158    self.checkId = ('001','filename_timerange_value')
1159    for j in range(n):
1160      if self.monitor != None:
1161         nofh0 = self.monitor.get_open_fds()
1162      t = tt[j]
1163      fn = t[1]
1164      isFirst = j == 0
1165      isLast = j == n-1
1166      lok = True
1167      for i in [0,1]:
1168        if not (i==0 and isFirst or i==1 and isLast):
1169          x = rere[i].match( t[3][i] )
1170          lok &= self.test( x != None, 'Cannot match time range %s: %s [%s/%s]' % (i,fn,j,n), part=True, appendLogfile=(self.fLogDict.get(fn,None),fn) )
1171        if not lok:
1172          if self.recorder != None:
1173            self.recorder.modify( t[1], 'ERROR: time range' )
1174      if self.monitor != None:
1175         nofh9 = self.monitor.get_open_fds()
1176         if nofh9 > nofh0:
1177           print 'Open file handles: %s --- %s [%s]' % (nofh0, nofh9, j )
1178
1179### http://stackoverflow.com/questions/2023608/check-what-files-are-open-in-python
1180class sysMonitor(object):
1181
1182  def __init__(self):
1183    self.fhCountMax = 0
1184
1185  def get_open_fds(self):
1186    '''
1187    return the number of open file descriptors for current process
1188    .. warning: will only work on UNIX-like os-es.
1189    '''
1190    import subprocess
1191    import os
1192
1193    pid = os.getpid()
1194    self.procs = subprocess.check_output( 
1195        [ "lsof", '-w', '-Ff', "-p", str( pid ) ] )
1196
1197    self.ps = filter( 
1198            lambda s: s and s[ 0 ] == 'f' and s[1: ].isdigit(),
1199            self.procs.split( '\n' ) )
1200    self.fhCountMax = max( self.fhCountMax, len(self.ps) )
1201    return len( self.ps )
Note: See TracBrowser for help on using the repository browser.