source: CCCC/trunk/c4.py @ 103

Subversion URL: http://proj.badc.rl.ac.uk/svn/exarch/CCCC/trunk/c4.py@103
Revision 103, 17.0 KB checked in by mjuckes, 7 years ago (diff)

added specific file handle tracking test to unit test suite 2

Line 
1
2# Standard library imports
3import os, string, time, logging, sys, glob
4
5# Third party imports
6
7try:
8  import cdms2
9  withCdms = True
10except:
11  print 'Failed to import cdms2: will not be able to read NetCDF'
12  withCdms = False
13
14# Local imports
15import utils_c4 as utils
16import config_c4 as config
17
18reload( utils )
19
20
21#driving_model_ensemble_member = <CMIP5Ensemble_member>
22#rcm_version_id = <RCMVersionID>                     
23
24class fileMetadata:
25
26  def __init__(self,dummy=False):
27     self.dummy = dummy
28
29  def loadNc(self,fpath):
30    self.fn = string.split( fpath, '/' )[-1]
31    self.fparts = string.split( self.fn[:-3], '_' )
32    self.ga = {}
33    self.va = {}
34    self.da = {}
35    if self.dummy:
36      self.makeDummyFileImage()
37      return
38    self.nc = cdms2.open( fpath )
39    for k in self.nc.attributes.keys():
40      self.ga[k] = self.nc.attributes[k]
41    for v in self.nc.variables.keys():
42      self.va[v] = {}
43      for k in self.nc.variables[v].attributes.keys():
44        self.va[v][k] = self.nc.variables[v].attributes[k]
45      self.va[v]['_type'] = str( self.nc.variables[v].dtype )
46      if v in ['plev','plev_bnds','height']:
47        self.va[v]['_data'] = self.nc.variables[v].getValue().tolist()
48
49    for v in self.nc.axes.keys():
50      self.da[v] = {}
51      for k in self.nc.axes[v].attributes.keys():
52        self.da[v][k] = self.nc.axes[v].attributes[k]
53      self.da[v]['_type'] = str( self.nc.axes[v].getValue().dtype )
54      self.da[v]['_data'] = self.nc.axes[v].getValue().tolist()
55     
56    self.nc.close()
57
58  def makeDummyFileImage(self):
59    for k in range(10):
60      self.ga['ga%s' % k] =  str(k)
61    for v in [self.fparts[0],]:
62      self.va[v] = {}
63      self.va[v]['standard_name'] = 's%s' % v
64      self.va[v]['long_name'] = v
65      self.va[v]['cell_methods'] = 'time: point'
66      self.va[v]['units'] = '1'
67      self.va[v]['_type'] = 'float32'
68
69    for v in ['lat','lon','time']:
70      self.da[v] = {}
71      self.da[v]['_type'] = 'float64'
72      self.da[v]['_data'] = range(5)
73    dlist = ['lat','lon','time']
74    svals = lambda p,q: map( lambda y,z: self.da[y].__setitem__(p, z), dlist, q )
75    svals( 'standard_name', ['latitude', 'longitude','time'] )
76    svals( 'long_name', ['latitude', 'longitude','time'] )
77    svals( 'units', ['degrees_north', 'degrees_east','days since 19590101'] )
78
79  def applyMap( self, mapList, globalAttributesInFn, log=None ):
80    for m in mapList:
81      if m[0] == 'am001':
82        if m[1][0][0] == "@var":
83          if m[1][0][1] in self.va.keys():
84            this = self.va[m[1][0][1]]
85            apThis = True
86            for c in m[1][1:]:
87              if c[0] not in this.keys():
88                apThis = False
89              elif c[1] != this[c[0]]:
90                apThis = False
91            if m[2][0] != '':
92              targ = m[2][0]
93            else:
94              targ = m[1][-1][0]
95            if apThis:
96              if log != None:
97                log.info( 'Setting %s to %s' % (targ,m[2][1]) )
98              ##print 'Setting %s:%s to %s' % (m[1][0][1],targ,m[2][1])
99              self.va[m[1][0][1]][targ] = m[2][1]
100
101        elif m[1][0][0] == "@ax":
102          ##print 'checking dimension ',m[1][0][1], self.da.keys()
103          if m[1][0][1] in self.da.keys():
104            ##print 'checking dimension [2]',m[1][0][1]
105            this = self.da[m[1][0][1]]
106            apThis = True
107            for c in m[1][1:]:
108              if c[0] not in this.keys():
109                apThis = False
110              elif c[1] != this[c[0]]:
111                apThis = False
112            if m[2][0] != '':
113              targ = m[2][0]
114            else:
115              targ = m[1][-1][0]
116            if apThis:
117              if log != None:
118                log.info( 'Setting %s to %s' % (targ,m[2][1]) )
119              ##print 'Setting %s:%s to %s' % (m[1][0][1],targ,m[2][1])
120              self.da[m[1][0][1]][targ] = m[2][1]
121        elif m[1][0][0][0] != "@":
122            this = self.ga
123            apThis = True
124            for c in m[1]:
125              if c[0] not in this.keys():
126                apThis = False
127              elif c[1] != this[c[0]]:
128                apThis = False
129            if m[2][0] != '':
130              targ = m[2][0]
131            else:
132              targ = m[1][-1][0]
133            if apThis:
134              if log != None:
135                log.info( 'Setting %s to %s' % (targ,m[2][1]) )
136              print 'Setting %s:%s to %s' % (m[1][0][1],targ,m[2][1])
137              self.ga[targ] = m[2][1]
138              if targ in globalAttributesInFn:
139                self.fparts[ globalAttributesInFn.index(targ) ] = m[2][1]
140                self.fn = string.join( self.fparts, '_' ) + '.nc'
141        else:
142          print 'Token %s not recognised' % m[1][0][0]
143
144
145class dummy:
146   pass
147
148class recorder:
149
150  def __init__(self,fileName,type='map',dummy=False):
151    self.dummy = dummy
152    self.file = fileName
153    self.type = type
154    self.pathTmpl = '%(project)s/%(product)s/%(domain)s/%(institute)s/%(driving_model)s/%(experiment)s/%(ensemble)s/%(model)s/%(model_version)s/%(frequency)s/%(variable)s/files/%%(version)s/'
155    self.records = {}
156
157  def open(self):
158    if self.type == 'map':
159      self.fh = open( self.file, 'a' )
160    else:
161      self.sh = shelve.open( self.file )
162
163  def close(self):
164    if self.type == 'map':
165      self.fh.close()
166    else:
167      self.sh.close()
168
169  def add(self,fpath,drs,safe=True):
170    assert self.type == 'map','Can only do map files at present'
171    assert type(drs) == type( {} ), '2nd user argument to method add should be a dictionary [%s]' % type(drs)
172    tpath = self.pathTmpl % drs
173    if not self.dummy:
174      assert os.path.isfile( fpath ), 'File %s not found' % fpath
175      fdate = time.ctime(os.path.getmtime(fpath))
176      sz = os.stat(fpath).st_size
177    else:
178      fdate = "na"
179      sz = 0
180    record = '%s | OK | %s | modTime = %s | target = %s ' % (fpath,sz,fdate,tpath)
181    for k in ['creation_date','tracking_id']:
182      if k in drs.keys():
183        record += ' | %s = %s' % (k,drs[k])
184
185    fn = string.split( fpath, '/' )[-1]
186    self.records[fn] = record
187 
188  def modify(self,fn,msg):
189    assert fn in self.records.keys(),'Attempt to modify non-existent record %s, %s' % [fn,str(self.records.keys()[0:10])]
190    if string.find( self.records[fn], '| OK |') == -1:
191      ##print 'File %s already flagged with errors' % fn
192      return
193    s = string.replace( self.records[fn], '| OK |', '| %s |' % msg )
194    ##print '--> ',s
195    self.records[fn] = s
196
197  def dumpAll(self,safe=True):
198    keys = self.records.keys()
199    keys.sort()
200    for k in keys:
201      self.dump( self.records[k], safe=safe )
202
203  def dump( self, record, safe=True ):
204    if safe:
205      self.open()
206    self.fh.write( record + '\n' )
207    if safe:
208      self.close()
209
210  def addErr(self,fpath,reason,safe=True):
211    record = '%s | %s' % (fpath, reason)
212    fn = string.split( fpath, '/' )[-1]
213    self.records[fn] = record
214
215class checker:
216  def __init__(self, pcfg, cls,reader,abortMessageCount=-1):
217    self.info = dummy()
218    self.info.pcfg = pcfg
219    self.info.abortMessageCount = abortMessageCount
220    self.calendar = 'None'
221    self.ncReader = reader
222    self.cfn = utils.checkFileName( parent=self.info,cls=cls)
223    self.cga = utils.checkGlobalAttributes( parent=self.info,cls=cls)
224    self.cgd = utils.checkStandardDims( parent=self.info,cls=cls)
225    self.cgg = utils.checkGrids( parent=self.info,cls=cls)
226    self.cls = cls
227
228    # Define vocabs based on project
229    ##self.vocabs = getVocabs(pcgf)
230    self.vocabs = pcfg.vocabs
231
232  def checkFile(self,fpath,log=None,attributeMappings=[]):
233    self.calendar = 'None'
234    self.info.log = log
235
236    fn = string.split( fpath, '/' )[-1]
237
238    if attributeMappings != []:
239      self.ncReader.loadNc( fpath )
240      self.ncReader.applyMap( attributeMappings, self.cfn.globalAttributesInFn, log=log )
241      ncRed = True
242      thisFn = self.ncReader.fn
243    else:
244      ncRed = False
245      thisFn = fn
246
247    self.cfn.check( thisFn )
248    if not self.cfn.completed:
249      self.completed = False
250      return
251    if not self.info.pcfg.project[:2] == '__':
252      if not os.path.isfile( fpath ):
253        print 'File %s not found [2]' % fpath
254        self.completed = False
255        return
256
257    if not ncRed:
258      self.ncReader.loadNc( fpath )
259    self.ga = self.ncReader.ga
260    self.va = self.ncReader.va
261    self.da = self.ncReader.da
262
263    self.cga.check( self.ga, self.va, self.cfn.var, self.cfn.freq, self.vocabs, self.cfn.fnParts )
264    if not self.cga.completed:
265      self.completed = False
266      return
267
268    ##self.cgd.plevRequired = config.plevRequired
269    ##self.cgd.plevValues = config.plevValues
270    ##self.cgd.heightRequired = config.heightRequired
271    ##self.cgd.heightValues = config.heightValues
272    ##self.cgd.heightRange = config.heightRange
273    self.cgd.check( self.cfn.var, self.cfn.freq, self.da, self.va, self.cga.isInstantaneous )
274    self.calendar = self.cgd.calendar
275    if not self.cgd.completed:
276      self.completed = False
277      return
278
279    if self.info.pcfg.doCheckGrids:
280      ##self.cgg.rotatedPoleGrids = config.rotatedPoleGrids
281      ##self.cgg.interpolatedGrids = config.interpolatedGrids
282      self.cgg.check( self.cfn.var, self.cfn.domain, self.da, self.va )
283   
284      if not self.cgg.completed:
285        self.completed = False
286        return
287    self.completed = True
288    self.drs = self.cga.getDrs()
289    self.errorCount = self.cfn.errorCount + self.cga.errorCount + self.cgd.errorCount + self.cgg.errorCount
290
291class c4_init:
292
293  def __init__(self,args=None):
294    self.logByFile = True
295    if args==None:
296       args = sys.argv[1:]
297    nn = 0
298
299    self.attributeMappingFile = None
300    self.recordFile = 'Rec.txt'
301    self.logDir = 'logs_02'
302   
303    # Set default project to "CORDEX"
304    self.project = "CORDEX"
305
306    while len(args) > 0:
307      next = args.pop(0)
308      if next == '-f':
309        flist = [args.pop(0),]
310        self.logByFile = False
311      elif next == '-d':
312        fdir = args.pop(0)
313        flist = glob.glob( '%s/*.nc' % fdir  )
314      elif next == '-D':
315        flist  = []
316        fdir = args.pop(0)
317        for root, dirs, files in os.walk( fdir ):
318          for f in files:
319            fpath = '%s/%s' % (root,f)
320            if os.path.isfile( fpath ) and f[-3:] == '.nc':
321              flist.append( fpath )
322      elif next == '-R':
323        self.recordFile = args.pop(0)
324      elif next == '--ld':
325        self.logDir = args.pop(0)
326      elif next == '--aMap':
327        self.attributeMappingFile = args.pop(0)
328        assert os.path.isfile( self.attributeMappingFile ), 'The token "--aMap" should be followed by the path or name of a file'
329      elif next == "-p":
330        self.project = args.pop(0)
331      else:
332       print 'Unused argument: %s' % next
333       nn+=1
334    assert nn==0, 'Aborting because of unused arguments'
335
336    if self.project[:2] == '__':
337       flist = []
338       ss = 'abcdefgijk'
339       ss = 'abcdefgijklmnopqrstuvwxyz'
340       ss = 'abc'
341       for i in range(10):
342         v = 'v%s' % i
343         for a in ss:
344           for b in ss:
345             flist.append( '%s_day_%s_%s_1900-1909.nc' % (v,a,b) )
346    flist.sort()
347    fnl = []
348    nd = 0
349    for f in flist:
350      fn = string.split(f, '/')[-1]
351      if fn in fnl:
352        print 'ERROR: file name duplicated %s' % fn
353        nd += 0
354      else:
355        fnl.append(fn)
356    assert nd == 0, 'Duplicate file names encountered'
357    self.flist = flist
358    self.fnl = fnl
359    if not os.path.isdir(   self.logDir ):
360       os.mkdir(   self.logDir )
361
362    tstring1 = '%4.4i%2.2i%2.2i_%2.2i%2.2i%2.2i' % time.gmtime()[0:6]
363    batchLogFile = '%s/qcBatchLog_%s.txt' % (  self.logDir,tstring1)
364## default appending to myapp.log; mode='w' forces a new file (deleting old contents).
365    self.logger = logging.getLogger('c4logger')
366    self.hdlr = logging.FileHandler(batchLogFile,mode='w')
367    formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
368    self.hdlr.setFormatter(formatter)
369    self.logger.setLevel(logging.INFO)
370    self.logger.addHandler(self.hdlr)
371
372    self.attributeMappings = []
373    if self.attributeMappingFile != None:
374      for l in open( self.attributeMappingFile ).readlines():
375        if l[0] != '#':
376          bb = string.split( string.strip(l), '|' ) 
377          assert len(bb) ==2, "Error in experimental module attributeMapping -- configuration line not scanned [%s]" % str(l)
378          bits = string.split( bb[0], ';' )
379          cl = []
380          for b in bits:
381            cl.append( string.split(b, '=' ) )
382          self.attributeMappings.append( ('am001',cl, string.split(bb[1],'=') ) )
383
384  def getFileLog( self, fn, flf=None ):
385    if flf == None:
386      tstring2 = '%4.4i%2.2i%2.2i' % time.gmtime()[0:3]
387      self.fileLogFile = '%s/%s__qclog_%s.txt' % (self.logDir,fn[:-3],tstring2)
388      m = 'w'
389    else:
390      m = 'a'
391      self.fileLogFile = flf
392
393    self.fLogger = logging.getLogger('fileLog_%s_%s' % (fn,m))
394    if m == 'a':
395      self.fHdlr = logging.FileHandler(self.fileLogFile)
396    else:
397      self.fHdlr = logging.FileHandler(self.fileLogFile,mode=m)
398    fileFormatter = logging.Formatter('%(message)s')
399    self.fHdlr.setFormatter(fileFormatter)
400    self.fLogger.addHandler(self.fHdlr)
401    self.fLogger.setLevel(logging.INFO)
402    return self.fLogger
403
404  def closeFileLog(self):
405    self.fLogger.removeHandler(self.fHdlr)
406    self.fHdlr.close()
407
408
409class main:
410
411  def __init__(self,args=None,holdExceptions=False,abortMessageCount=-1,printInfo=False,monitorFileHandles = False):
412    logDict = {}
413    ecount = 0
414    c4i = c4_init(args=args)
415    isDummy  = c4i.project[:2] == '__'
416    if (not withCdms) and isDummy:
417       print withCdms, c4i.project
418       print 'Cannot proceed with non-dummy project without cdms'
419       raise
420    pcfg = config.projectConfig( c4i.project )
421    ncReader = fileMetadata(dummy=isDummy)
422    cc = checker(pcfg, c4i.project, ncReader,abortMessageCount=abortMessageCount)
423    rec = recorder( c4i.recordFile, dummy=isDummy )
424    if monitorFileHandles:
425      self.monitor = utils.sysMonitor()
426    else:
427      self.monitor = None
428
429    cal = None
430    c4i.logger.info( 'Starting batch -- number of file: %s' % (len(c4i.flist)) )
431 
432    cbv = utils.checkByVar( parent=cc.info,cls=c4i.project,monitor=self.monitor)
433    cbv.impt( c4i.flist )
434    if printInfo:
435      print cbv.info
436
437    for f in c4i.flist:
438      if monitorFileHandles:
439        nofhStart = self.monitor.get_open_fds()
440      fn = string.split(f,'/')[-1]
441      c4i.logger.info( 'Starting: %s' % fn )
442      try:
443  ### need to have a unique name, otherwise get mixing of logs despite close statement below.
444        if c4i.logByFile:
445          fLogger = c4i.getFileLog( fn )
446          logDict[fn] = c4i.fileLogFile
447          c4i.logger.info( 'Log file: %s' % c4i.fileLogFile )
448        else:
449          fLogger = c4i.logger
450 
451        fLogger.info( 'Starting file %s' % fn )
452## default appending to myapp.log; mode='w' forces a new file (deleting old contents).
453        cc.checkFile( f, log=fLogger,attributeMappings=c4i.attributeMappings )
454
455        if cc.completed:
456          if cal not in (None,'None'):
457            if cal != cc.calendar:
458              c4i.logger.info( 'Error: change in calendar attribute %s --> %s' % (cal, cc.calendar) )
459              fLogger.info( 'Error: change in calendar attribute %s --> %s' % (cal, cc.calendar) )
460              cc.errorCount += 1
461          cal = cc.calendar
462
463        if c4i.logByFile:
464          if cc.completed:
465            fLogger.info( 'Done -- error count %s' % cc.errorCount )
466          else:
467            fLogger.info( 'Done -- checks not completed' )
468          c4i.closeFileLog( )
469
470        if cc.completed:
471          c4i.logger.info( 'Done -- error count %s' % cc.errorCount ) 
472          ecount += cc.errorCount
473          if cc.errorCount == 0:
474            rec.add( f, cc.drs )
475          else:
476            rec.addErr( f, 'ERRORS FOUND | errorCount = %s' % cc.errorCount )
477        else:
478          ecount += 20
479          c4i.logger.info( 'Done -- testing aborted because of severity of errors' )
480          rec.addErr( f, 'ERRORS FOUND AND CHECKS ABORTED' )
481      except:
482        c4i.logger.error("Exception has occured" ,exc_info=1)
483        rec.addErr( f, 'ERROR: Exception' )
484        if not holdExceptions:
485          raise
486      if monitorFileHandles:
487        nofhEnd = self.monitor.get_open_fds()
488        if nofhEnd > nofhStart:
489           print 'Open file handles: %s --- %s' % (nofhStart, nofhEnd)
490 
491    cc.info.log = c4i.logger
492   
493    if c4i.project != 'SPECS':
494       cbv.c4i = c4i
495       cbv.setLogDict( logDict )
496       cbv.check( recorder=rec, calendar=cc.calendar)
497       try:
498         ecount += cbv.errorCount
499       except:
500         ecount = None
501    rec.dumpAll()
502    if printInfo:
503      print 'Error count %s' % ecount
504    c4i.hdlr.close()
505if __name__ == '__main__':
506  main(printInfo=True)
507
508
509##else:
510  ##f1 = '/data/u10/cordex/AFR-44/SMHI/ECMWF-ERAINT/evaluation/SMHI-RCA4/v1/day/clh/clh_AFR-44_ECMWF-ERAINT_evaluation_r1i1p1_SMHI-RCA4_v1_day_19810101-19851231.nc'
511  ##f2 = '/data/u10/cordex/AFR-44/SMHI/ECMWF-ERAINT/evaluation/SMHI-RCA4/v1/sem/tas/tas_AFR-44_ECMWF-ERAINT_evaluation_r1i1p1_SMHI-RCA4_v1_sem_200012-201011.nc'
512  ##f3 = '/data/u10/cordex/AFR-44i/SMHI/ECMWF-ERAINT/evaluation/SMHI-RCA4/v1/mon/tas/tas_AFR-44i_ECMWF-ERAINT_evaluation_r1i1p1_SMHI-RCA4_v1_mon_199101-200012.nc'
513  ##cc.checkFile( f3 )
Note: See TracBrowser for help on using the repository browser.