source: CCCC/trunk/c4.py @ 101

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

created a main() class in c4.py to facilitate testing, added test

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