1 | import string, os, re, stat, sys |
---|
2 | |
---|
3 | ncdumpCmd = 'ncdump' |
---|
4 | ncdumpCmd = '/usr/local/5/bin/ncdump' |
---|
5 | ## |
---|
6 | |
---|
7 | from xceptions import * |
---|
8 | |
---|
9 | class mipTableScan(object): |
---|
10 | |
---|
11 | def __init__(self, vats = ['standard_name','long_name','units','cell_methods'] ): |
---|
12 | self.vats = vats |
---|
13 | self.re_cmor_mip2 = re.compile( 'dimensions:(?P<dims>.*?):::' ) |
---|
14 | self.re_vats = { } |
---|
15 | for v in vats: |
---|
16 | self.re_vats[v] = re.compile( '%s:(?P<dims>.*?):::' % v ) |
---|
17 | ## |
---|
18 | def scan_table(self,ll,log,asDict=False,appendTo=None,lax=False,tag=None): |
---|
19 | |
---|
20 | lll0 = map( string.strip, ll ) |
---|
21 | lll = [] |
---|
22 | for l in lll0: |
---|
23 | if len(l) != 0: |
---|
24 | if l[0] != '!': |
---|
25 | lll.append(string.split(l,'!')[0]) |
---|
26 | sll = [] |
---|
27 | sll.append( ['header',[]] ) |
---|
28 | for l in lll: |
---|
29 | k = string.split( l, ':' )[0] |
---|
30 | if k in ['variable_entry','axis_entry']: |
---|
31 | sll.append( [k,[]] ) |
---|
32 | sll[-1][1].append(l) |
---|
33 | |
---|
34 | eee = [] |
---|
35 | for s in sll: |
---|
36 | if s[0] == 'variable_entry': |
---|
37 | bits = string.split(s[1][0],':') |
---|
38 | assert len(bits) == 2, 'Can not unpack: %s' % str(s[1]) |
---|
39 | k,var = map( string.strip, string.split(s[1][0],':') ) |
---|
40 | aa = {'standard_name':None, 'long_name':None,'units':None,'cell_methods':None } |
---|
41 | ds = 'scalar' |
---|
42 | for l in s[1][1:]: |
---|
43 | bits = string.split(l,':') |
---|
44 | k = string.strip(bits[0]) |
---|
45 | v = string.strip( string.join( bits[1:], ':' ) ) |
---|
46 | if k == 'dimensions': |
---|
47 | ds = string.split(v) |
---|
48 | else: |
---|
49 | aa[k] = v |
---|
50 | eee.append( (var,ds,aa,tag) ) |
---|
51 | |
---|
52 | |
---|
53 | checkOldMethod = False |
---|
54 | if checkOldMethod: |
---|
55 | ssss = string.join( lll, ':::' ) |
---|
56 | vitems = string.split( ssss, ':::variable_entry:' )[1:] |
---|
57 | |
---|
58 | ee = [] |
---|
59 | for i in vitems: |
---|
60 | b1 = string.split( i, ':::')[0] |
---|
61 | var = string.strip( b1 ) |
---|
62 | aa = {} |
---|
63 | for v in self.vats: |
---|
64 | mm = self.re_vats[v].findall(i) |
---|
65 | if len(mm) == 1: |
---|
66 | aa[v] = string.strip(mm[0]) |
---|
67 | else: |
---|
68 | aa[v] = 'None' |
---|
69 | |
---|
70 | mm = self.re_cmor_mip2.findall( i ) |
---|
71 | if len(mm) == 1: |
---|
72 | ds = string.split( string.strip(mm[0]) ) |
---|
73 | elif len(mm) == 0: |
---|
74 | ds = 'scalar' |
---|
75 | else: |
---|
76 | if log != None: |
---|
77 | log.warn( 'Mistake?? in scan_table %s' % str(mm) ) |
---|
78 | ds = mm |
---|
79 | raise baseException( 'Mistake?? in scan_table %s' % str(mm) ) |
---|
80 | ee.append( (var,ds,aa,tag) ) |
---|
81 | |
---|
82 | for k in range(len(ee) ): |
---|
83 | if ee[k][0:2] == eee[k][0:2] and ee[k][2]['standard_name'] == eee[k][2]['standard_name'] and ee[k][2]['long_name'] == eee[k][2]['long_name']: |
---|
84 | print 'OK:::', ee[k] |
---|
85 | else: |
---|
86 | print 'DIFF: ',ee[k],eee[k] |
---|
87 | |
---|
88 | if not asDict: |
---|
89 | return tuple( eee ) |
---|
90 | else: |
---|
91 | ff = {} |
---|
92 | for l in eee: |
---|
93 | ff[l[0]] = ( l[1], l[2], l[3] ) |
---|
94 | if appendTo != None: |
---|
95 | for k in ff.keys(): |
---|
96 | assert ff[k][1].has_key( 'standard_name' ), 'No standard name in %s:: %s' % (k,str(ff[k][1].keys())) |
---|
97 | if appendTo.has_key(k): |
---|
98 | if lax and ff[k][1]['standard_name'] != appendTo[k][1]['standard_name']: |
---|
99 | print 'ERROR[X1]: Inconsistent entry definitions %s:: %s [%s] --- %s [%s]' % (k,ff[k][1],ff[k][2], appendTo[k][1], appendTo[k][2]) |
---|
100 | if not lax: |
---|
101 | assert ff[k][1] == appendTo[k][1], 'Inconsistent entry definitions %s:: %s [%s] --- %s [%s]' % (k,ff[k][1],ff[k][2], appendTo[k][1], appendTo[k][2]) |
---|
102 | else: |
---|
103 | appendTo[k] = ff[k] |
---|
104 | return appendTo |
---|
105 | else: |
---|
106 | return ff |
---|
107 | ## |
---|
108 | ## this class carries a logging method, and is used to carry information about datasets being parsed. |
---|
109 | ## |
---|
110 | class qcHandler(object): |
---|
111 | |
---|
112 | def __init__( self, qcc, log, baseDir, logPasses=True ): |
---|
113 | self.datasets = {} |
---|
114 | self.groups = {} |
---|
115 | self.baseDir = baseDir |
---|
116 | self.logPasses = logPasses |
---|
117 | self.log = log |
---|
118 | self.nofail = True |
---|
119 | self.hasTimeRange = False |
---|
120 | for k in qcc.datasets.keys(): |
---|
121 | self.datasets[k] = {} |
---|
122 | for g in qcc.groups: |
---|
123 | self.groups[g[0]] = { 'pat':g[1]} |
---|
124 | self.msg = {} |
---|
125 | self.msgk = {} |
---|
126 | self.msg['CQC.101.001.001'] = 'File size above 10 bytes' |
---|
127 | self.msg['CQC.101.001.002'] = 'File name matches DRS syntax' |
---|
128 | self.msg['CQC.101.001.003'] = 'File name time component matches DRS syntax' |
---|
129 | self.msg['CQC.101.001.004'] = 'File name component not in vocabulary' |
---|
130 | self.msg['CQC.101.001.005'] = 'File name component does not match regex' |
---|
131 | self.msg['CQC.101.001.006'] = 'File name component does not match regex list' |
---|
132 | self.msg['CQC.101.001.007'] = 'File name component does not match regex list with constraints' |
---|
133 | self.msg['CQC.102.002.001'] = 'File name time components in ADS have same length' |
---|
134 | self.msg['CQC.102.002.002'] = 'File name time components in ADS do not overlap' |
---|
135 | self.msg['CQC.102.002.003'] = 'File name time components in ADS have no gaps' |
---|
136 | self.msg['CQC.102.002.004'] = 'File name time components in ADS have correct gap for monthly data' |
---|
137 | self.msg['CQC.102.002.005'] = 'File name time components present for multi-file dataset' |
---|
138 | self.msg['CQC.102.002.006'] = 'Consistency checks' |
---|
139 | self.msg['CQC.102.002.007'] = 'Required variables' |
---|
140 | self.msg['CQC.102.002.008'] = 'Required data variables' |
---|
141 | self.msg['CQC.102.002.009'] = 'File is a recognised NetCDF format' |
---|
142 | self.msg['CQC.102.002.010'] = 'Variable attributes match tables' |
---|
143 | self.msg['CQC.200.003.001'] = 'NetCDF files occur at one directory level' |
---|
144 | self.msg['CQC.103.003.002'] = 'Conformant version directory' |
---|
145 | self.msg['CQC.103.003.003'] = 'Latest link points to most recent version' |
---|
146 | self.msg['CQC.200.003.004'] = 'ads occurs in a single directory' |
---|
147 | self.msg['CQC.104.004.001'] = 'Consistent global attributes across experiment' |
---|
148 | self.msg['CQC.105.004.002'] = 'Valid calendar attribute' |
---|
149 | self.msg['CQC.101.004.003'] = 'Regular time step in file' |
---|
150 | self.msg['CQC.102.004.004'] = 'Regular time step between files' |
---|
151 | self.msg['CQC.102.004.005'] = 'Exceptions to regular time period' |
---|
152 | self.msg['CQC.102.004.005'] = 'Consistent global attributes across ADS' |
---|
153 | self.msg['CQC.105.004.006'] = 'Consistent global attributes across ensemble' |
---|
154 | self.msg['CQC.101.004.007'] = 'Required global attributes' |
---|
155 | self.msg['CQC.103.900.001'] = 'Identifiedmost recent version' |
---|
156 | self.msg['CQC.103.003.005'] = 'Version directories identified in directory containing "latest"' |
---|
157 | ## error keys: when these occur, further processing of that file is blocked. |
---|
158 | self.errorKeys = ['CQC.101.001.001', 'CQC.101.001.002'] |
---|
159 | ## keys in this list will not be recorded as failed tests. |
---|
160 | self.ignoreKeys = [] |
---|
161 | for k in self.msg.keys(): |
---|
162 | self.msgk[k] = 0 |
---|
163 | |
---|
164 | def _log( self, key, item, msg, ok=False ): |
---|
165 | if ok: |
---|
166 | if self.logPasses: |
---|
167 | thisMsg = '%s OK: %s: %s: %s' % (key,item,self.msg[key], msg) |
---|
168 | self.log.info( thisMsg ) |
---|
169 | return |
---|
170 | |
---|
171 | if key not in self.ignoreKeys: |
---|
172 | self.nofail = False |
---|
173 | item = string.replace( item, self.baseDir, '' ) |
---|
174 | if key in self.errorKeys: |
---|
175 | self.log.error( '%s [ERROR] FAIL !(%s): %s: %s' % (key,self.msg[key], item,msg)) |
---|
176 | self.noerror = False |
---|
177 | else: |
---|
178 | thisMsg = '%s FAIL !(%s): %s: %s' % (key,self.msg[key],item, msg) |
---|
179 | self.log.info( thisMsg ) |
---|
180 | |
---|
181 | self.msgk[key] += 1 |
---|
182 | |
---|
183 | class dirParser(object): |
---|
184 | |
---|
185 | def __init__(self, qcc, linksOnly=True): |
---|
186 | self.nclevs = [] |
---|
187 | self.qcc = qcc |
---|
188 | self.dirNames = {} |
---|
189 | self.count_nc = 0 |
---|
190 | self.linksOnly=linksOnly |
---|
191 | |
---|
192 | def parse( self, handler,dir, files ): |
---|
193 | handler.log.info( 'Directory: %s [%s]' % (dir, len(files)) ) |
---|
194 | bits = string.split(dir,'/') |
---|
195 | thisLev = len(bits) |
---|
196 | files.sort() |
---|
197 | skipf = [] |
---|
198 | |
---|
199 | for f in files: |
---|
200 | if os.path.isdir( '%s/%s' % (dir,f) ) and f in self.qcc.omitDirectories: |
---|
201 | skipf.append(f) |
---|
202 | for f in skipf: |
---|
203 | handler.log.info( 'skipping %s' % f ) |
---|
204 | files.pop( files.index(f) ) |
---|
205 | |
---|
206 | # record diretory names at each level |
---|
207 | if thisLev not in self.dirNames.keys(): |
---|
208 | self.dirNames[thisLev] = [] |
---|
209 | if bits[-1] not in self.dirNames[thisLev]: |
---|
210 | self.dirNames[thisLev].append( bits[-1] ) |
---|
211 | |
---|
212 | ncFiles = [] |
---|
213 | for f in files: |
---|
214 | if f[-3:] == ".nc" and (not self.linksOnly or os.path.islink('%s/%s'%(dir,f))): |
---|
215 | ncFiles.append(f) |
---|
216 | |
---|
217 | # record which directory levels contain netcdf files |
---|
218 | if len(ncFiles) and thisLev not in self.nclevs: |
---|
219 | self.nclevs.append( thisLev ) |
---|
220 | |
---|
221 | tbits = [] |
---|
222 | ncFiles.sort() |
---|
223 | self.count_nc += len( ncFiles ) |
---|
224 | dbits = string.split( string.strip(dir,'/'), '/' ) |
---|
225 | for f in ncFiles: |
---|
226 | fpath = '%s/%s' % (dir,f) |
---|
227 | handler.noerror = True |
---|
228 | handler.nofail = True |
---|
229 | |
---|
230 | if not os.path.islink( fpath ): |
---|
231 | fsb = os.stat( fpath )[stat.ST_SIZE] |
---|
232 | else: |
---|
233 | fsb = os.stat( fpath )[stat.ST_SIZE] |
---|
234 | if fsb < 10: |
---|
235 | handler._log( 'CQC.101.001.001', fpath, '' ) |
---|
236 | |
---|
237 | fbits = string.split( string.split(f,'.')[0], self.qcc.fileNameSegments.sep ) |
---|
238 | if not len( fbits ) in self.qcc.fileNameSegments.nn: |
---|
239 | handler._log( 'CQC.101.001.002', fpath, str(fbits) ) |
---|
240 | ###### |
---|
241 | ###### |
---|
242 | else: |
---|
243 | qfns = self.qcc.fileNameSegments |
---|
244 | ns = {} |
---|
245 | for k in range(len(fbits)): |
---|
246 | ns['fn_%s' % qfns.segments[k][1]] = fbits[k] |
---|
247 | if qfns.segments[k][0] == 'vocabulary': |
---|
248 | assert qfns.segments[k][1] in self.qcc.vocab.keys(), '%s not a valid vocabulary name' % qfns.segments[k][1] |
---|
249 | if not fbits[k] in self.qcc.vocab[qfns.segments[k][1]]: |
---|
250 | handler._log( 'CQC.101.001.004', fpath, 'Not in vocab %s' % qfns.segments[k][1] ) |
---|
251 | elif qfns.segments[k][0] == 'abstractVocab': |
---|
252 | assert qfns.segments[k][1] in self.qcc.vocab.keys(), '%s not a valid abstract vocabulary name' % qfns.segments[k][1] |
---|
253 | this = self.qcc.vocab[qfns.segments[k][1]] |
---|
254 | assert this[0] == 'regex', 'Unexpected start of abstractVocab, %s' % str( this ) |
---|
255 | match = False |
---|
256 | for s,t,tt in this[1]: |
---|
257 | if s.match( fbits[k] ): |
---|
258 | match = True |
---|
259 | ## print 'Match [%s] found for %s {%s}' % (t,fbits[k],tt) |
---|
260 | for k in y.groupdict().keys(): |
---|
261 | ns['fnre_%s' % k] = y.groupdict()[k] |
---|
262 | if tt != None: |
---|
263 | ##print 'trying further test' |
---|
264 | tt1 = string.replace(tt,'$','_arg_') |
---|
265 | y = s.match( fbits[k] ) |
---|
266 | for k in y.groupdict().keys(): |
---|
267 | eval( '_arg_%s = int( %s )' % (k,y.groupdict()[k] ) ) |
---|
268 | eval( 'res = tt1' ) |
---|
269 | ##print res |
---|
270 | else: |
---|
271 | pass |
---|
272 | ## print 'no match [%s] for %s ' % (t,fbits[k]) |
---|
273 | |
---|
274 | if not match: |
---|
275 | handler._log( 'CQC.101.001.006', fpath, 'Failed abstractVocab regex tests %s' % fbits[k] ) |
---|
276 | elif qfns.segments[k][0] == 'condAbstractVocab': |
---|
277 | assert qfns.segments[k][1] in self.qcc.vocab.keys(), '%s not a valid abstract vocabulary name' % qfns.segments[k][1] |
---|
278 | this = self.qcc.vocab[qfns.segments[k][1]] |
---|
279 | assert this[0] == 'regex', 'Unexpected start of abstractVocab, %s' % str( this ) |
---|
280 | match = False |
---|
281 | olc = 0 |
---|
282 | for sss in this[1]: |
---|
283 | ol = False |
---|
284 | if sss[0] == '*': |
---|
285 | ol = True |
---|
286 | else: |
---|
287 | for b in string.split(sss[0],','): |
---|
288 | if b in fbits: |
---|
289 | ol = True |
---|
290 | if ol: |
---|
291 | nunc = 0 |
---|
292 | olc += 1 |
---|
293 | for s,t,tt in sss[1]: |
---|
294 | |
---|
295 | if not match: |
---|
296 | y = s.match( fbits[k] ) |
---|
297 | if y: |
---|
298 | ## print 'Match [%s] found for %s {%s}' % (t,fbits[k],tt) |
---|
299 | nunc += 1 |
---|
300 | for key in y.groupdict().keys(): |
---|
301 | ns['fnre_%s' % key] = y.groupdict()[key] |
---|
302 | ##print '--- Match [%s] found for %s {%s}' % (t,fbits[k],tt) |
---|
303 | if tt != None: |
---|
304 | ## create string with test condition.` |
---|
305 | tt1 = string.replace(tt,'$','_arg_') |
---|
306 | y = s.match( fbits[k] ) |
---|
307 | for key in y.groupdict().keys(): |
---|
308 | locals()['_arg_%s' % key ] = int( y.groupdict()[key] ) |
---|
309 | ##print '_arg_%s' % key , locals()['_arg_%s' % key ] |
---|
310 | res = eval( tt1 ) |
---|
311 | ## print '#####', res,tt1 |
---|
312 | if res: |
---|
313 | match = True |
---|
314 | else: |
---|
315 | match = True |
---|
316 | else: |
---|
317 | ##print 'no match [%s] for %s ' % (t,fbits[k]) |
---|
318 | pass |
---|
319 | ##else: |
---|
320 | ##print 'No overlap for %s, %s' % (sss[0],str(fbits)) |
---|
321 | if olc == 0: |
---|
322 | ##print 'No matches fround for %s' % str(fbits) |
---|
323 | pass |
---|
324 | |
---|
325 | if not match: |
---|
326 | handler._log( 'CQC.101.001.007', fpath, 'Failed constrained regex tests %s (%s unconditional matches)' % (fbits[k], nunc) ) |
---|
327 | elif qfns.segments[k][0] == 'regex-match': |
---|
328 | res = qfns.segments[k][2].match( fbits[k] ) |
---|
329 | if res == None: |
---|
330 | handler._log( 'CQC.101.001.005', fpath, 'Failed regex-match test: %s [%s]' % (fbits[k],qfns.segments[k][1] ) ) |
---|
331 | elif qfns.segments[k][0] == 'vocabulary*': |
---|
332 | pass |
---|
333 | else: |
---|
334 | print 'segment test id %s not recognised' % qfns.segments[k][0] |
---|
335 | raise baseException( 'segment test id %s not recognised' % qfns.segments[k][0] ) |
---|
336 | ################################## |
---|
337 | versionned = False |
---|
338 | if not versionned: |
---|
339 | for k in self.qcc.datasets.keys(): |
---|
340 | if self.qcc.datasets[k].datasetIdArg == 'fileNameBits': |
---|
341 | dsId = self.qcc.datasets[k].getDatasetId( fbits ) |
---|
342 | elif self.qcc.datasets[k].datasetIdArg == 'filePathBits': |
---|
343 | try: |
---|
344 | dsId = self.qcc.datasets[k].getDatasetId( fbits, dbits ) |
---|
345 | except: |
---|
346 | print 'Failed to get dsID:',fbits,dbits |
---|
347 | raise baseException( 'Failed to get dsID: %s,%s' % (fbits,dbits) ) |
---|
348 | else: |
---|
349 | assert False, 'datasetIdMethod %s not supported yet' % self.qcc.datasets[k].datasetIdMethod |
---|
350 | |
---|
351 | if os.path.islink( fpath ): |
---|
352 | dsId += '_lnk' |
---|
353 | if not handler.datasets[k].has_key( dsId ): |
---|
354 | handler.datasets[k][dsId] = [] |
---|
355 | handler.datasets[k][dsId].append( (dir,f, handler.nofail, ns) ) |
---|
356 | |
---|
357 | class dataSetParser(object): |
---|
358 | |
---|
359 | def __init__(self,qcc, log, handler): |
---|
360 | self.qcc = qcc |
---|
361 | self.log = log |
---|
362 | self.h = handler |
---|
363 | self.re_istr = re.compile( '^[0-9]*$' ) |
---|
364 | |
---|
365 | def parse(self,dsclass, dsid, files, inFileChecks=False, forceInFileChecks=True): |
---|
366 | self.h.nofail = True |
---|
367 | ## allowEndPeriodEqual should only be permitted for time averaged fields, but in this version it is set true for all fields. |
---|
368 | allowEndPeriodEqual = True |
---|
369 | try: |
---|
370 | fns = map( lambda x: x[1], self.qcc.fileNameSegments.segments ) |
---|
371 | except: |
---|
372 | print self.qcc.fileNameSegments.segments |
---|
373 | raise baseException( str( self.qcc.fileNameSegments.segments ) ) |
---|
374 | dsok = True |
---|
375 | for dir,f, fok, ns in files: |
---|
376 | dsok &= fok |
---|
377 | |
---|
378 | self.h.nofail = dsok |
---|
379 | ## |
---|
380 | ## this test should have a switch -- only to be applied to one category of file group |
---|
381 | ## need dsclass constraints |
---|
382 | ## |
---|
383 | ## constraint: setOnce: |
---|
384 | ## |
---|
385 | if dsok: |
---|
386 | if self.qcc.hasTimeRange: |
---|
387 | allOk = True |
---|
388 | tbl = [] |
---|
389 | for dir,f, fok, ns in files: |
---|
390 | thisOk = True |
---|
391 | fbits = string.split( string.split(f,'.')[0], self.qcc.fileNameSegments.sep ) |
---|
392 | thisOk, tb = self.qcc.timeRange.get( fbits ) |
---|
393 | |
---|
394 | allOk &= thisOk |
---|
395 | tbl.append( tb ) |
---|
396 | |
---|
397 | if allOk: |
---|
398 | kkl = [] |
---|
399 | for tb in tbl: |
---|
400 | kk = 0 |
---|
401 | for i in range(2): |
---|
402 | if tb[i] != None: |
---|
403 | ## tb[i] = int(tb[i]) |
---|
404 | kk+=1 |
---|
405 | kkl.append(kk) |
---|
406 | |
---|
407 | thisOk = True |
---|
408 | cc = '' |
---|
409 | for k in range( len(tbl)-1 ): |
---|
410 | if kkl[k] != kkl[0]: |
---|
411 | thisOk = False |
---|
412 | cc += str(files[k]) |
---|
413 | self.h._log( 'CQC.102.002.001', cc, '', ok=thisOk ) |
---|
414 | |
---|
415 | self.h._log( 'CQC.102.002.005', '%s@%s' % (dsid,dsclass), '', ok=not(thisOk and kkl[0] == 0 and len(files) > 1) ) |
---|
416 | |
---|
417 | if thisOk and kkl[0] == 2: |
---|
418 | cc = '' |
---|
419 | for k in range( len(tbl) -1 ): |
---|
420 | if tbl[k+1][0] < tbl[k][1] or (tbl[k+1][0] == tbl[k][1] and not allowEndPeriodEqual): |
---|
421 | thisOk = False |
---|
422 | cc += '%s, %s [%s,%s];' % (str(files[k]), str(files[k+1]),tbl[k][1],tbl[k+1][0]) |
---|
423 | self.h._log( 'CQC.102.002.002', cc, '', ok=thisOk ) |
---|
424 | |
---|
425 | ### |
---|
426 | ### run group constraints |
---|
427 | ### |
---|
428 | if self.qcc.groupConstraints.has_key( dsclass ): |
---|
429 | for ct in self.qcc.groupConstraints[dsclass]: |
---|
430 | ct.__reset__() |
---|
431 | for dir,f, fok, ns in files: |
---|
432 | if fok: |
---|
433 | ### |
---|
434 | rv,res = ct.check( ns ) |
---|
435 | if rv != 'PASS': |
---|
436 | self.h._log( ct.code, f, ct.msg, False ) |
---|
437 | |
---|
438 | ## |
---|
439 | ## should only do the in-file checks once |
---|
440 | ## intention is to be able to have multiple definitions of groups with different tests |
---|
441 | ## |
---|
442 | files2 = [] |
---|
443 | if (self.h.nofail and inFileChecks) or forceInFileChecks: |
---|
444 | ##print 'starting in-file checks' |
---|
445 | import ncd_parse |
---|
446 | for dir,f, fok, ns in files: |
---|
447 | if fok or forceInFileChecks: |
---|
448 | tmpDumpFile = '/tmp/qc_ncdump_tmp.txt' |
---|
449 | if os.path.isfile( tmpDumpFile ): |
---|
450 | os.unlink( tmpDumpFile ) |
---|
451 | targf = '%s/%s' % (dir,f) |
---|
452 | fsb = os.stat( targf )[stat.ST_SIZE] |
---|
453 | assert fsb > 10, 'Small file slipped through: %s, %s' % (targ,fok) |
---|
454 | cmd = '%s -k %s/%s 2>&1 > %s' % (ncdumpCmd,dir,f,tmpDumpFile) |
---|
455 | res = os.popen( cmd ).readlines() |
---|
456 | ii = open( tmpDumpFile ).readlines() |
---|
457 | if len(ii) == 0: |
---|
458 | this_ok = False |
---|
459 | else: |
---|
460 | this_ok = 'Unknown' not in ii[0] |
---|
461 | self.h._log( 'CQC.102.002.009', '%s/%s' % (dir,f), '', ok=this_ok ) |
---|
462 | files2.append( (dir,f, this_ok, ns) ) |
---|
463 | if this_ok: |
---|
464 | cmd = '%s -h %s/%s > %s' % (ncdumpCmd,dir,f,tmpDumpFile) |
---|
465 | ii = os.popen( cmd ).readlines() |
---|
466 | fsb = os.stat( tmpDumpFile )[stat.ST_SIZE] |
---|
467 | assert fsb > 100, 'ncdump output too small, %s/%s' % (dir,f) |
---|
468 | |
---|
469 | rd = ncd_parse.read_ncdump( tmpDumpFile ) |
---|
470 | rd.parse() |
---|
471 | |
---|
472 | ## |
---|
473 | ## messy hack -- copying globals attributes into a new dictionary |
---|
474 | ## |
---|
475 | for k in rd.gats.keys(): |
---|
476 | ns['g_%s' % k] = rd.gats[k] |
---|
477 | ## rd.vars[k] is a tuple: (dims,atts), where atts is a dictionary of attributes. |
---|
478 | for k in rd.vars.keys(): |
---|
479 | ns['v_%s' % k] = rd.vars[k] |
---|
480 | for k in rd.dims.keys(): |
---|
481 | ns['d_%s' % k] = rd.dims[k] |
---|
482 | |
---|
483 | if self.qcc.attributeTests: |
---|
484 | for a in self.qcc.requiredGlobalAttributes: |
---|
485 | self.h._log( 'CQC.101.004.007', '%s/%s' % (dir,f), 'Attribute: %s' % a, ok=a in rd.gats.keys() ) |
---|
486 | |
---|
487 | if self.qcc.variableTests: |
---|
488 | for dir,f, fok, ns in files2: |
---|
489 | if fok: |
---|
490 | for rv in self.qcc.requiredVariables: |
---|
491 | if rv[0][0] != '$': |
---|
492 | self.h._log( 'CQC.102.002.007', f, 'Required variable %s'% (rv[0]), 'v_%s' % rv[0] in ns.keys()) |
---|
493 | |
---|
494 | if self.qcc.groups: |
---|
495 | for dir,f, fok, ns in files2: |
---|
496 | if fok: |
---|
497 | for g in self.qcc.groups: |
---|
498 | gid = g[1] % ns |
---|
499 | if not self.qcc.groupDict[g[0]].has_key( gid ): |
---|
500 | self.qcc.groupDict[g[0]][ gid ] = [] |
---|
501 | self.qcc.groupDict[g[0]][ gid ].append( ( dir,f,fok) ) |
---|
502 | ## print '%s:: %s' % (g[0],gid) |
---|
503 | |
---|
504 | |
---|
505 | if self.qcc.constraintTests: |
---|
506 | for dir,f, fok, ns in files2: |
---|
507 | if fok: |
---|
508 | for ct in self.qcc.constraints: |
---|
509 | ### |
---|
510 | rv,res = ct.check( ns ) |
---|
511 | if rv != 'PASS': |
---|
512 | self.h._log( ct.code, f, ct.msg, False ) |
---|
513 | |
---|
514 | if self.qcc.variableTests: |
---|
515 | for dir,f, fok, ns in files2: |
---|
516 | if fok: |
---|
517 | for v in self.qcc.dataVariables: |
---|
518 | var = ns[v[1]] |
---|
519 | if v[0] == 'ns': |
---|
520 | isPresent = 'v_%s' % var in ns.keys() |
---|
521 | if v[3]: |
---|
522 | self.h._log( 'CQC.102.002.008', f, '%s [%s::%s]'% (var,v[1],v[2]), isPresent ) |
---|
523 | |
---|
524 | |
---|
525 | class dataset(object): |
---|
526 | def __init__(self,name): |
---|
527 | self.name = name |
---|
528 | |
---|
529 | class qcConfigParse(object): |
---|
530 | |
---|
531 | def __init__( self, file, log=None ): |
---|
532 | assert os.path.isfile( file ), '%s not found' % file |
---|
533 | self.firstFile = True |
---|
534 | self.fh = open( file ) |
---|
535 | self.file = file |
---|
536 | self.sections = {} |
---|
537 | self.omitDirectories = ['.svn'] |
---|
538 | self.log = log |
---|
539 | self.mipTables = None |
---|
540 | self.mipMapping = None |
---|
541 | self.hasTimeRange = False |
---|
542 | |
---|
543 | def close(self): |
---|
544 | self.fh.close() |
---|
545 | self.file = None |
---|
546 | |
---|
547 | def open(self,file): |
---|
548 | assert os.path.isfile( file ), '%s not found' % file |
---|
549 | self.fh = open( file ) |
---|
550 | self.file = file |
---|
551 | |
---|
552 | def parse_l0(self): |
---|
553 | f = False |
---|
554 | sname = None |
---|
555 | for l in self.fh.readlines(): |
---|
556 | if f: |
---|
557 | if l[0:4] == 'END ' and string.index( l,sname) == 4: |
---|
558 | f = False |
---|
559 | self._parse_l0_section.close() |
---|
560 | else: |
---|
561 | self._parse_l0_section.add( l ) |
---|
562 | elif l[0:6] == 'START ': |
---|
563 | sname = string.strip( string.split(l)[1] ) |
---|
564 | self._parse_l0_section = section_parser_l0( self, sname ) |
---|
565 | f = True |
---|
566 | |
---|
567 | def parse_l1(self): |
---|
568 | |
---|
569 | if self.firstFile: |
---|
570 | requiredSections = ['FILENAME', 'VOCABULARIES','PATH'] |
---|
571 | else: |
---|
572 | requiredSections = [] |
---|
573 | self.firstFile = False |
---|
574 | |
---|
575 | for s in requiredSections: |
---|
576 | assert s in self.sections.keys(), 'Required section %s not found in %s [parsing %s]' % (s, self.section.keys(),self.file) |
---|
577 | self._parse_l1 = section_parser_l1( self ) |
---|
578 | self._parse_l1.parse( 'GENERAL' ) |
---|
579 | self._parse_l1.parse( 'VOCABULARIES' ) |
---|
580 | if self.mipTables != None: |
---|
581 | assert self.mipMapping != None, '"mipMapping" must be set if MIP tables are used' |
---|
582 | ee = {} |
---|
583 | for m in self.mipTables: |
---|
584 | ee[m] = self.vocab[m] |
---|
585 | self.mipConstraint = Constraint__VarAtts( ee, self.mipMapping,self.vocab['mipVarAtts'] ) |
---|
586 | |
---|
587 | self._parse_l1.parse( 'FILENAME' ) |
---|
588 | self._parse_l1.parse( 'PATH' ) |
---|
589 | self._parse_l1.parse( 'ATTRIBUTES' ) |
---|
590 | self._parse_l1.parse( 'VARIABLES' ) |
---|
591 | self._parse_l1.parse( 'CONSTRAINTS' ) |
---|
592 | self._parse_l1.parse( 'GROUPS' ) |
---|
593 | |
---|
594 | regv = re.compile( 'version=([0-9.]+)' ) |
---|
595 | refs = re.compile( 'separator=(.+)' ) |
---|
596 | revsc = re.compile( 'validSectionCount,(.+)' ) |
---|
597 | |
---|
598 | class section_parser_l1(object): |
---|
599 | |
---|
600 | def __init__(self,parent): |
---|
601 | self.parent = parent |
---|
602 | self.currentSection = None |
---|
603 | self.gc = {} |
---|
604 | self.parent.constraintTests = False |
---|
605 | |
---|
606 | def _getVersion( self ): |
---|
607 | assert self.currentSection != None, '_getVersion called with no section set' |
---|
608 | x = regv.findall( self.currentSection[0] ) |
---|
609 | assert len(x) == 1, 'valid version not identified at start of section: %s\n%s' % (self.currentSectionName,self.currentSection[0]) |
---|
610 | self.version = x[0] |
---|
611 | |
---|
612 | def parse( self, sname ): |
---|
613 | if self.parent.sections.has_key( sname ): |
---|
614 | |
---|
615 | self.currentSectionName = sname |
---|
616 | self.currentSection = self.parent.sections[sname] |
---|
617 | self._getVersion() |
---|
618 | else: |
---|
619 | self.currentSection = None |
---|
620 | |
---|
621 | ## print 'Parsing %s' % sname |
---|
622 | if sname == 'VOCABULARIES': |
---|
623 | self.parent.vocab = {} |
---|
624 | self.parse_vocabularies() |
---|
625 | elif sname == 'FILENAME': |
---|
626 | self.parse_filename() |
---|
627 | elif sname == 'PATH': |
---|
628 | self.parse_path() |
---|
629 | elif sname == 'ATTRIBUTES': |
---|
630 | self.parse_attributes() |
---|
631 | elif sname == 'VARIABLES': |
---|
632 | self.parse_variables() |
---|
633 | elif sname == 'CONSTRAINTS': |
---|
634 | self.parse_constraints() |
---|
635 | elif sname == 'GENERAL': |
---|
636 | self.parse_general() |
---|
637 | elif sname == 'GROUPS': |
---|
638 | self.parse_groups() |
---|
639 | |
---|
640 | def __get_match( self, regex, line, id ): |
---|
641 | x = regex.findall( line ) |
---|
642 | assert len(x) == 1, 'No match found, id=%s, line=%s' % 'id,line' |
---|
643 | return x[0] |
---|
644 | |
---|
645 | def parse_vocabularies(self): |
---|
646 | ## print len( self.currentSection ) |
---|
647 | base = '' |
---|
648 | for l in self.currentSection[1:]: |
---|
649 | bits = map( string.strip, string.split( l, ', ' ) ) |
---|
650 | id = bits[0] |
---|
651 | if id[0:2] == '__': |
---|
652 | assert id[2:] in ['base','mipMapping'], 'vocabulary record not recognised: %s' % l |
---|
653 | if id[2:] == 'base': |
---|
654 | assert os.path.isdir( bits[1] ), '!!!parse_vocabularies: directory %s not found' % bits[1] |
---|
655 | base = bits[1] |
---|
656 | if base[-1] != '/': |
---|
657 | base += '/' |
---|
658 | elif id[2:] == 'mipMapping': |
---|
659 | self.parent.mipMapping = bits[1] |
---|
660 | else: |
---|
661 | isAbstract = False |
---|
662 | if id[0] == '*': |
---|
663 | id = id[1:] |
---|
664 | isAbstract = True |
---|
665 | |
---|
666 | sl = string.split( bits[1], '|' ) |
---|
667 | fromFile = 'file' in sl |
---|
668 | isRegex = 'regex' in sl |
---|
669 | withSub = 'sub' in sl |
---|
670 | isCond = 'cond' in sl |
---|
671 | |
---|
672 | if not fromFile: |
---|
673 | vlist = string.split( bits[2] ) |
---|
674 | else: |
---|
675 | fn = '%s%s' % (base,bits[2]) |
---|
676 | assert os.path.isfile( fn), 'File %s (specified as vocabulary %s) not found' % (fn,bits[0] ) |
---|
677 | ii = open( fn ).readlines() |
---|
678 | bb = string.split( bits[1], '|' ) |
---|
679 | if '1stonline' in sl: |
---|
680 | vlist = [] |
---|
681 | for i in ii: |
---|
682 | if i[0] != '#' and len( string.strip(i) ) > 0: |
---|
683 | vlist.append( string.split( string.strip(i) )[0] ) |
---|
684 | elif '1perline' in sl: |
---|
685 | vlist = map( string.strip, ii ) |
---|
686 | elif 'mip' in sl: |
---|
687 | vlist = self.mipsc.scan_table( ii, self.parent.log ) |
---|
688 | if self.parent.mipTables == None: |
---|
689 | self.parent.mipTables = [] |
---|
690 | self.parent.mipTables.append( id ) |
---|
691 | else: |
---|
692 | assert False, 'file syntax option (%s) not recognised' % bits[1] |
---|
693 | |
---|
694 | if isRegex: |
---|
695 | cr = [] |
---|
696 | if withSub: |
---|
697 | if isCond: |
---|
698 | for ccc in vlist: |
---|
699 | i0 = ccc.index(':') |
---|
700 | cc = ccc[:i0] |
---|
701 | cr0 = [] |
---|
702 | for v in string.split( ccc[i0+1:] ): |
---|
703 | v = string.strip(v) |
---|
704 | if v[0] == '{': |
---|
705 | i1 = v.index('}') |
---|
706 | tt = v[1:i1] |
---|
707 | v = v[i1+1:] |
---|
708 | else: |
---|
709 | tt = None |
---|
710 | v = string.strip( v, "'" ) |
---|
711 | try: |
---|
712 | cr0.append( (re.compile( v % self.gc ),v % self.gc,tt) ) |
---|
713 | except: |
---|
714 | print 'Error trying to compile: ', v % self.gc |
---|
715 | print 'Pattern: ',v |
---|
716 | raise baseException( 'Error trying to compile: %s [%s]' % ( v % self.gc, v) ) |
---|
717 | ## print 'compiled ' + v % self.gc, tt |
---|
718 | cr.append( (cc,cr0) ) |
---|
719 | else: |
---|
720 | for v in vlist: |
---|
721 | v = string.strip( v, "'" ) |
---|
722 | cr.append( (re.compile( v % self.gc ),v % self.gc) ) |
---|
723 | else: |
---|
724 | for v in vlist: |
---|
725 | v = string.strip( v, "'" ) |
---|
726 | cr.append( (re.compile( v ),v) ) |
---|
727 | self.parent.vocab[id] = ('regex', cr ) |
---|
728 | else: |
---|
729 | self.parent.vocab[id] = vlist[:] |
---|
730 | if id == 'mipVarAtts': |
---|
731 | self.mipsc = mipTableScan( vlist ) |
---|
732 | |
---|
733 | def parse_filename(self): |
---|
734 | sep = self.__get_match( refs, self.currentSection[1], 'File separator' ) |
---|
735 | nn = map( int, string.split( self.__get_match( revsc, self.currentSection[2], 'File separator' ),',') ) |
---|
736 | self.parent.fileNameSegments = fileNameSegments( self.parent, sep, nn ) |
---|
737 | for l in self.currentSection[3:]: |
---|
738 | self.parent.fileNameSegments.add(l) |
---|
739 | self.parent.fileNameSegments.finish() |
---|
740 | |
---|
741 | def parse_attributes(self): |
---|
742 | if self.currentSection == None: |
---|
743 | self.parent.attributeTests = False |
---|
744 | return |
---|
745 | self.parent.requiredGlobalAttributes = [] |
---|
746 | self.parent.attributeTests = True |
---|
747 | for l in self.currentSection[1:]: |
---|
748 | bits = map( string.strip, string.split(l,',')) |
---|
749 | if bits[0] == 'global': |
---|
750 | if bits[2] == 'required': |
---|
751 | self.parent.requiredGlobalAttributes.append( bits[1] ) |
---|
752 | |
---|
753 | def parse_general(self): |
---|
754 | if self.currentSection == None: |
---|
755 | return |
---|
756 | self.parent.requiredGlobalAttributes = [] |
---|
757 | self.parent.dataVariables = [] |
---|
758 | for l in self.currentSection[1:]: |
---|
759 | if l[0] == '$': |
---|
760 | bits = map( string.strip, string.split(l[1:],'=')) |
---|
761 | self.gc[bits[0]] = bits[1] |
---|
762 | else: |
---|
763 | bits = map( string.strip, string.split(l,',')) |
---|
764 | if bits[0] == 'DataVariable': |
---|
765 | if bits[1] == 'byName': |
---|
766 | isRequired = bits[3] == 'required' |
---|
767 | key, msg = ref_to_key( bits[2] ) |
---|
768 | self.parent.dataVariables.append( ('ns',key,msg,isRequired) ) |
---|
769 | |
---|
770 | def parse_groups(self): |
---|
771 | self.parent.groups = [] |
---|
772 | self.parent.groupDict = {} |
---|
773 | if self.currentSection == None: |
---|
774 | return |
---|
775 | for l in self.currentSection[1:]: |
---|
776 | bits = map( string.strip, string.split(l,',')) |
---|
777 | if bits[1] not in self.parent.groupDict.keys(): |
---|
778 | self.parent.groupDict[bits[1]] = {} |
---|
779 | if bits[0] == 'group': |
---|
780 | cc = [] |
---|
781 | for r in string.split( bits[2], '.' ): |
---|
782 | cc.append( '%' + ('(%s)s' % ref_to_key( r )[0] ) ) |
---|
783 | self.parent.groups.append( (bits[1], string.join( cc, '.' ) ) ) |
---|
784 | |
---|
785 | def parse_constraints(self): |
---|
786 | if self.currentSection == None: |
---|
787 | self.parent.constraintTests = False |
---|
788 | return |
---|
789 | self.parent.constraintTests = True |
---|
790 | self.parent.constraints = [] |
---|
791 | self.parent.groupConstraints = {} |
---|
792 | for l in self.currentSection[1:]: |
---|
793 | bits = map( string.strip, string.split(l,',')) |
---|
794 | bb = string.split( bits[0], ':' ) |
---|
795 | if len(bb) == 2: |
---|
796 | gid = bb[0] |
---|
797 | cid = bb[1] |
---|
798 | if gid not in self.parent.groupConstraints.keys(): |
---|
799 | self.parent.groupConstraints[gid] = [] |
---|
800 | else: |
---|
801 | gid = None |
---|
802 | cid = bits[0] |
---|
803 | assert cid in ['identity','onlyOnce','constant','special'], 'constraint id %s not recognised' % cid |
---|
804 | |
---|
805 | if cid == 'identity': |
---|
806 | cstr = Constraint__IdentityChecker( bits[1], bits[2] ) |
---|
807 | elif cid == 'onlyOnce': |
---|
808 | ## print 'Set Constraint only once, %s ' % bits[1] |
---|
809 | cstr = Constraint__OnlyOnce( bits[1] ) |
---|
810 | elif cid == 'constant': |
---|
811 | ## print 'Set Constraint only once, %s ' % bits[1] |
---|
812 | cstr = Constraint__Constant( bits[1] ) |
---|
813 | elif cid == 'special': |
---|
814 | assert bits[1] in ['mip','CordexInterpolatedGrid'], 'Special constraint [%s] not recognised' % bits[1] |
---|
815 | if bits[1] == 'mip': |
---|
816 | cstr = self.parent.mipConstraint |
---|
817 | elif bits[1] == 'CordexInterpolatedGrid': |
---|
818 | cstr = Constraint__CordexInterpolatedGrid() |
---|
819 | |
---|
820 | if gid == None: |
---|
821 | self.parent.constraints.append( cstr ) |
---|
822 | else: |
---|
823 | self.parent.groupConstraints[gid].append( cstr ) |
---|
824 | |
---|
825 | def parse_variables(self): |
---|
826 | if self.currentSection == None: |
---|
827 | self.parent.variableTests = False |
---|
828 | return |
---|
829 | self.parent.variableTests = True |
---|
830 | self.parent.requiredVariables = [] |
---|
831 | for l in self.currentSection[1:]: |
---|
832 | bits = map( string.strip, string.split(l,',')) |
---|
833 | isDimension = bits[0] == 'dimension' |
---|
834 | if bits[2] == 'required': |
---|
835 | if bits[1][0] != '$': |
---|
836 | self.parent.requiredVariables.append( (bits[1],isDimension) ) |
---|
837 | else: |
---|
838 | key,info = ref_to_key( bits[1][1:] ) |
---|
839 | if key == 'VALUE': |
---|
840 | self.parent.requiredVariables.append( (info,isDimension) ) |
---|
841 | else: |
---|
842 | self.parent.requiredVariables.append( ('$%s' % key, isDimension) ) |
---|
843 | |
---|
844 | |
---|
845 | |
---|
846 | def parse_path(self): |
---|
847 | if self.currentSection == None: |
---|
848 | self.pathTests = False |
---|
849 | return |
---|
850 | self.pathTests = True |
---|
851 | self.datasetIdMethod = None |
---|
852 | self.datasetVersionMode = [None,] |
---|
853 | self.parent.datasets = {} |
---|
854 | datasetHierarchy = None |
---|
855 | for l in self.currentSection[1:]: |
---|
856 | bits = map( string.strip, string.split(l,',')) |
---|
857 | if bits[0] == 'datasetVersion': |
---|
858 | vdsName = bits[1] |
---|
859 | if bits[2] == 'pathElement': |
---|
860 | self.datasetVersionMode = ['pathElement',] |
---|
861 | self.versionPathElement = int( bits[3] ) |
---|
862 | if bits[4] == 'regex': |
---|
863 | self.datasetVersionMode.append( 'regex' ) |
---|
864 | self.datasetVersionRe = re.compile( string.strip( bits[5], '"' ) ) |
---|
865 | else: |
---|
866 | self.datasetVersionMode.append( None ) |
---|
867 | elif bits[0] == 'datasetId': |
---|
868 | thisDs = dataset(bits[1]) |
---|
869 | thisDs.datasetIdMethod = bits[2] |
---|
870 | if bits[2] == 'prints': |
---|
871 | thisDs.getDatasetId = lambda x: bits[3] % x |
---|
872 | thisDs.datasetIdTuple = tuple( bits[4:] ) |
---|
873 | elif bits[2] == 'joinFileNameSegSlice': |
---|
874 | thisSlice = slice( int(bits[4]), int(bits[5]) ) |
---|
875 | thisDs.getDatasetId = dsid1( thisSlice, bits[3] ).get |
---|
876 | thisDs.datasetIdArg = 'fileNameBits' |
---|
877 | elif bits[2] == 'cmip5': |
---|
878 | thisSlice = slice( int(bits[4]), int(bits[5]) ) |
---|
879 | thisDs.getDatasetId = cmip5_dsid( thisSlice, bits[3] ).get |
---|
880 | thisDs.datasetIdArg = 'filePathBits' |
---|
881 | self.parent.datasets[bits[1]] = thisDs |
---|
882 | elif bits[0] == 'datasetHierarchy': |
---|
883 | datasetHierarchy = bits[1:] |
---|
884 | elif bits[0] == 'omitDirectories': |
---|
885 | self.parent.omitDirectories = string.split( string.strip( bits[1] ) ) |
---|
886 | |
---|
887 | if self.datasetVersionMode[0] != None: |
---|
888 | assert vdsName in self.parent.datasets.keys(), 'Invalid dataset specified for version: %s [%s]' % (vdsName, str( self.parent.datasets.keys() ) ) |
---|
889 | self.versionnedDataset = self.parent.datasets[ vdsName ] |
---|
890 | |
---|
891 | if datasetHierarchy == None: |
---|
892 | self.datasetHierarchy = False |
---|
893 | else: |
---|
894 | self.datasetHierarchy = True |
---|
895 | bb = string.split( string.strip( datasetHierarchy[0]), '/' ) |
---|
896 | for b in bb: |
---|
897 | assert b in self.parent.datasets.keys(), 'Invalid dataset hierarchy, %s not among defined datasets' % b |
---|
898 | for k in self.parent.datasets.keys(): |
---|
899 | self.parent.datasets[k].inHierarchy = k in bb |
---|
900 | |
---|
901 | for k in range( len(bb) ): |
---|
902 | if k == 0: |
---|
903 | self.parent.datasets[bb[k]].parent = None |
---|
904 | else: |
---|
905 | self.parent.datasets[bb[k]].parent = self.parent.datasets[bb[k-1]] |
---|
906 | if k == len(bb)-1: |
---|
907 | self.parent.datasets[bb[k]].child = None |
---|
908 | else: |
---|
909 | self.parent.datasets[bb[k]].child = self.parent.datasets[bb[k+1]] |
---|
910 | |
---|
911 | class dsid1(object): |
---|
912 | |
---|
913 | def __init__(self,slice,sep): |
---|
914 | self.slice = slice |
---|
915 | self.sep = sep |
---|
916 | |
---|
917 | def get(self,x): |
---|
918 | return string.join( x[self.slice], self.sep ) |
---|
919 | |
---|
920 | class cmip5_dsid(object): |
---|
921 | |
---|
922 | def __init__(self,slice,sep): |
---|
923 | self.slice = slice |
---|
924 | self.sep = sep |
---|
925 | |
---|
926 | def get(self,x,y): |
---|
927 | return '%s_%s.%s' % (string.join( x[self.slice], self.sep ) , y[-2], y[-1] ) |
---|
928 | |
---|
929 | |
---|
930 | class get_trange(object): |
---|
931 | |
---|
932 | def __init__(self,pat,kseg): |
---|
933 | self.kseg = kseg |
---|
934 | self.re_istr = re.compile( '^[0-9]*$' ) |
---|
935 | if type( pat ) == type( 'x' ): |
---|
936 | self.pat = pat |
---|
937 | self.re = re.compile( pat ) |
---|
938 | else: |
---|
939 | self.re = pat |
---|
940 | |
---|
941 | def _test( self, s): |
---|
942 | return self.re.match( s ) != None |
---|
943 | |
---|
944 | def _get( self, s, handler=None ): |
---|
945 | x = self.re.match( s ) |
---|
946 | tb = [None,None] |
---|
947 | if x == None: |
---|
948 | return False, tuple(tb) |
---|
949 | |
---|
950 | thisOk = True |
---|
951 | tb[0] = x.groupdict().get( 'start', None ) |
---|
952 | tb[1] = x.groupdict().get( 'end', None ) |
---|
953 | if x.groupdict().has_key( 'isClim' ): |
---|
954 | tb.append( x.groupdict()['isClim'] ) |
---|
955 | for i in range(2): |
---|
956 | b = tb[i] |
---|
957 | if b != None: |
---|
958 | if self.re_istr.match( b ) == None: |
---|
959 | if handler != None: |
---|
960 | handler._log( 'CQC.101.001.003', dir + f, 'part of string not an integer' ) |
---|
961 | thisOk = False |
---|
962 | else: |
---|
963 | tb[i] = int(tb[i]) |
---|
964 | |
---|
965 | return thisOk, tb |
---|
966 | |
---|
967 | |
---|
968 | def test(self, l ): |
---|
969 | if len(l) < self.kseg + 1: |
---|
970 | return True |
---|
971 | return self._test( l[self.kseg] ) |
---|
972 | |
---|
973 | def get(self,l): |
---|
974 | if len(l) < self.kseg + 1: |
---|
975 | return True, (None,None) |
---|
976 | return self._get( l[self.kseg] ) |
---|
977 | |
---|
978 | class fileNameSegments(object): |
---|
979 | def __init__(self, parent, sep, nn ): |
---|
980 | self.sep = sep |
---|
981 | self.nn = nn |
---|
982 | self.nn.sort() |
---|
983 | self.__segments = {} |
---|
984 | self.parent = parent |
---|
985 | |
---|
986 | def add( self, line ): |
---|
987 | bits = map( string.strip, string.split( line, ', ' ) ) |
---|
988 | k = int(bits[0]) |
---|
989 | if bits[1] == 'vocabulary': |
---|
990 | assert bits[2] in self.parent.vocab.keys(), 'Vocabulary specified in file name section not defined in vocab sections, %s' % bits[2] |
---|
991 | |
---|
992 | self.__segments[k] = ('vocabulary',bits[2]) |
---|
993 | elif bits[1][0:5] == 'regex' or bits[2] == 'TimeRange': |
---|
994 | try: |
---|
995 | regex = re.compile( string.strip( bits[3], "'") ) |
---|
996 | except: |
---|
997 | print 'Failed to compile (in re): %s' % bits[3] |
---|
998 | raise baseException( 'Failed to compile (in re): %s' % bits[3] ) |
---|
999 | self.__segments[k] = (bits[1],bits[2], regex) |
---|
1000 | else: |
---|
1001 | self.__segments[k] = tuple( bits[1:] ) |
---|
1002 | |
---|
1003 | if bits[2] == 'TimeRange': |
---|
1004 | self.parent.hasTimeRange = True |
---|
1005 | self.parent.timeRange = get_trange(regex,k) |
---|
1006 | |
---|
1007 | def finish(self): |
---|
1008 | sl = [] |
---|
1009 | for k in range(self.nn[-1]): |
---|
1010 | sl.append( self.__segments.get( k, None ) ) |
---|
1011 | self.segments = tuple( sl ) |
---|
1012 | |
---|
1013 | class Constraint__CordexInterpolatedGrid(object): |
---|
1014 | |
---|
1015 | def __init__(self): |
---|
1016 | self.code = 'CQC.999.999.999' |
---|
1017 | self.name = 'CordexInterpolatedGrid' |
---|
1018 | self.mode = 'd' |
---|
1019 | |
---|
1020 | def __reset__(self): |
---|
1021 | pass |
---|
1022 | |
---|
1023 | def check(self,fns): |
---|
1024 | region = fns.get( 'g_CORDEX_domain', 'unset' ) |
---|
1025 | assert region != 'unset', 'CORDEX domain not found in %s' % str(fns.keys()) |
---|
1026 | if region[-3:] != '44i': |
---|
1027 | self.msg = 'Interpolated grid constraint not applicable to region %s' % region |
---|
1028 | return ('PASS',self.msg) |
---|
1029 | print 'WARNING -- check not implemented' |
---|
1030 | self.msg = 'WARNING -- check not implemented' |
---|
1031 | return ('PASS',self.msg) |
---|
1032 | |
---|
1033 | class Constraint__IdentityChecker(object): |
---|
1034 | |
---|
1035 | def __init__(self, ref1, ref2 ): |
---|
1036 | self.code = 'CQC.102.002.006' |
---|
1037 | self.name = 'IdentityChecker' |
---|
1038 | self.mode = 'd' |
---|
1039 | self.Ref1 = self.__parse_ref(ref1) |
---|
1040 | self.Ref2 = self.__parse_ref(ref2) |
---|
1041 | if self.Ref1 == 'VALUE': |
---|
1042 | self.Ref1 = self.Ref2 |
---|
1043 | self.Ref2 = 'VALUE' |
---|
1044 | if self.Ref2 == 'VALUE': |
---|
1045 | self.mode = 's' |
---|
1046 | |
---|
1047 | if self.mode == 's': |
---|
1048 | self.PassMsg = '%s (%s) equals %s' % (self.Ref1[1], ref1, self.value) |
---|
1049 | self.FailMsg = '%s (%s) not equal %s' % (self.Ref1[1], ref1, self.value) |
---|
1050 | else: |
---|
1051 | self.PassMsg = '%s (%s) not equal %s (%s)' % (self.Ref1[1],ref1, self.Ref2[1],ref2) |
---|
1052 | self.FailMsg = '%s (%s) not equal %s (%s)' % (self.Ref1[1],ref1, self.Ref2[1],ref2) |
---|
1053 | |
---|
1054 | def __parse_ref(self,ref): |
---|
1055 | bits = string.split(ref,'/') |
---|
1056 | assert bits[0] in ['VALUE','PATH','FILENAME','ATTRIBUTES','CONFIG','ARGS'], 'Bad line in CONSTRAINT section of config file' |
---|
1057 | if bits[0] == 'ATTRIBUTES': |
---|
1058 | if bits[1] == 'Global': |
---|
1059 | return ('g_%s' % bits[2],'Global attribute %s' % bits[2] ) |
---|
1060 | elif bits[0] == 'FILENAME': |
---|
1061 | return ('fn_%s' % bits[1],'File name component %s' % bits[1] ) |
---|
1062 | elif bits[0] == 'VALUE': |
---|
1063 | self.value = bits[1] |
---|
1064 | return 'VALUE' |
---|
1065 | |
---|
1066 | def __reset__(self): |
---|
1067 | pass |
---|
1068 | |
---|
1069 | def check(self,fns): |
---|
1070 | if self.mode == 's': |
---|
1071 | if fns.has_key( self.Ref1[0] ): |
---|
1072 | if fns[self.Ref1[0]] == self.value: |
---|
1073 | self.msg = self.PassMsg |
---|
1074 | return ('PASS',self.msg) |
---|
1075 | else: |
---|
1076 | self.msg = self.FailMsg + ' [%s]' % fns[self.Ref1[0]] |
---|
1077 | return ('FAIL',self.msg) |
---|
1078 | else: |
---|
1079 | return ('DEFER', 'No entry in fns for %s' % self.Ref1[0]) |
---|
1080 | else: |
---|
1081 | if fns.has_key( self.Ref1[0] ) and fns.has_key( self.Ref2[0] ): |
---|
1082 | if fns[self.Ref1[0]] == fns[self.Ref2[0]]: |
---|
1083 | self.msg = self.PassMsg |
---|
1084 | return ('PASS',self.msg) |
---|
1085 | else: |
---|
1086 | self.msg = self.FailMsg + ' [%s,%s]' % (fns[self.Ref1[0]] , fns[self.Ref2[0]]) |
---|
1087 | return ('FAIL',self.msg) |
---|
1088 | else: |
---|
1089 | return ('DEFER', 'No entry in fns for %s,%s' % (self.Ref1[0],self.Ref2[0])) |
---|
1090 | |
---|
1091 | def parse_ref(ref): |
---|
1092 | bits = string.split(ref,'/') |
---|
1093 | assert bits[0] in ['VALUE','PATH','FILENAME','FILENAMEregex','ATTRIBUTES','CONFIG','ARGS'], 'Bad line in CONSTRAINT section of config file' |
---|
1094 | if bits[0] == 'ATTRIBUTES': |
---|
1095 | if bits[1] == 'Global': |
---|
1096 | return ('g_%s' % bits[2],'Global attribute %s' % bits[2] ) |
---|
1097 | elif bits[0] == 'FILENAME': |
---|
1098 | return ('fn_%s' % bits[1],'File name component %s' % bits[1] ) |
---|
1099 | elif bits[0] == 'FILENAMEregex': |
---|
1100 | return ('fnre_%s' % bits[1],'File name component %s' % bits[1] ) |
---|
1101 | elif bits[0] == 'VALUE': |
---|
1102 | return ('VALUE', bits[1]) |
---|
1103 | |
---|
1104 | class Constraint__OnlyOnce(object): |
---|
1105 | |
---|
1106 | def __init__(self, ref1): |
---|
1107 | self.code = 'CQC.102.004.005' |
---|
1108 | self.name = 'OnlyOnce' |
---|
1109 | self.nn = 0 |
---|
1110 | self.Ref1 = parse_ref(ref1) |
---|
1111 | self.msg = '%s occurs only once' % self.Ref1[1] |
---|
1112 | |
---|
1113 | def __reset__(self): |
---|
1114 | self.nn = 0 |
---|
1115 | |
---|
1116 | def check(self,fns): |
---|
1117 | if fns.has_key( self.Ref1[0] ): |
---|
1118 | self.nn+=1 |
---|
1119 | if self.nn <= 1: |
---|
1120 | return ('PASS' ,'Occurence rate OK') |
---|
1121 | else: |
---|
1122 | return ('FAIL', '%s occurs too often' % self.Ref1[0] ) |
---|
1123 | else: |
---|
1124 | keys = fns.keys() |
---|
1125 | keys.sort() |
---|
1126 | return ('PASS',None) |
---|
1127 | |
---|
1128 | #### Mip table variable attribute check |
---|
1129 | |
---|
1130 | class Constraint__VarDimsCordexHardWired(object): |
---|
1131 | def __init__(self, attribVocabs, kpat, keys, logger=None): |
---|
1132 | self.code = 'CQC.102.002.010' |
---|
1133 | self.name = 'VarAtts' |
---|
1134 | self.tables = {} |
---|
1135 | self.keys = keys |
---|
1136 | self.kpat = kpat |
---|
1137 | self.logger = logger |
---|
1138 | self.plev_vars = ['clh','clm','cll','ua850','va850'] |
---|
1139 | |
---|
1140 | class Constraint__VarAtts(object): |
---|
1141 | |
---|
1142 | def __init__(self, attribVocabs, kpat, keys, logger=None): |
---|
1143 | self.code = 'CQC.102.002.010' |
---|
1144 | self.name = 'VarAtts' |
---|
1145 | self.tables = {} |
---|
1146 | self.keys = keys |
---|
1147 | self.kpat = kpat |
---|
1148 | self.logger = logger |
---|
1149 | for t in attribVocabs.keys(): |
---|
1150 | self.tables[t] = {} |
---|
1151 | for i in attribVocabs[t]: |
---|
1152 | self.tables[t][i[0]] = (i[1],i[2]) |
---|
1153 | self.log( 'i', 'Initialising Constraint__VarAtts, table %s' % t ) |
---|
1154 | |
---|
1155 | def log( self, lev, msg ): |
---|
1156 | if self.logger != None: |
---|
1157 | if lev == 'i': |
---|
1158 | self.logger.info( msg ) |
---|
1159 | elif lev == 'w': |
---|
1160 | self.logger.warn( msg ) |
---|
1161 | |
---|
1162 | def check(self, fns): |
---|
1163 | self.msg = 'starting' |
---|
1164 | mip = self.kpat % fns |
---|
1165 | var = fns['fn_variable'] |
---|
1166 | if not self.tables.has_key( mip ): |
---|
1167 | self.msg = 'VarAtts: no table found -- kpat = %s' % self.kpat |
---|
1168 | return ('FAIL', self.msg ) |
---|
1169 | res = self.__check( mip, var, fns ) |
---|
1170 | return res |
---|
1171 | |
---|
1172 | def __check( self, mip, var, fns ): |
---|
1173 | ms = '' |
---|
1174 | self.log( 'i', 'CHECKING %s' % var ) |
---|
1175 | nf = 0 |
---|
1176 | if not self.tables[mip].has_key(var): |
---|
1177 | self.msg = 'Variable %s not present in table %s' % (var,mip) |
---|
1178 | ##print ('FAIL',self.msg) |
---|
1179 | return ('FAIL', self.msg) |
---|
1180 | assert fns.has_key( 'v_%s' % var ), 'Internal error: attempt to check variable %s which is not in fns' % var |
---|
1181 | mip_dims, mip_ats = self.tables[mip][var] |
---|
1182 | var_dims = fns['v_%s' % var ][0] |
---|
1183 | for k in self.keys: |
---|
1184 | if mip_ats[k] != fns['v_%s' % var ][1][k]: |
---|
1185 | self.log( 'w', 'Variable attribute mismatch: %s -- %s' % (mip_ats[k], str(fns['v_%s' % var ]) ) ) |
---|
1186 | nf += 1 |
---|
1187 | ms += '%s; ' % k |
---|
1188 | else: |
---|
1189 | self.log( 'i', 'Attribute OK: %s' % (self.tables[mip][var][1][k]) ) |
---|
1190 | |
---|
1191 | # unclear how to proceed here -- want to check, e.g., time dimension. -- possibly easiest to have a CORDEX_SPECIAL flag for some ad hoc code.. |
---|
1192 | # ideally get axis information from "axis_entry" in mip tables -- need to improve the scanner for this. |
---|
1193 | ##print 'DIMS: %s -- %s -- %s' % (var, str(mip_dims), str(var_dims)) |
---|
1194 | if nf > 0: |
---|
1195 | if nf == 1: |
---|
1196 | self.msg = 'Failed 1 attribute test: %s' % ms |
---|
1197 | else: |
---|
1198 | self.msg = 'Failed %s attribute tests: %s' % (nf,ms) |
---|
1199 | ##print ('FAIL',self.msg) |
---|
1200 | return ('FAIL',self.msg) |
---|
1201 | else: |
---|
1202 | ##print ('PASS','%s attributes checked' % len(self.keys) ) |
---|
1203 | return ('PASS','%s attributes checked' % len(self.keys) ) |
---|
1204 | |
---|
1205 | #### check whether a NS element is constant |
---|
1206 | class Constraint__Constant(object): |
---|
1207 | |
---|
1208 | def __init__(self, ref1, required=False): |
---|
1209 | self.code = 'CQC.102.002.006' |
---|
1210 | self.name = 'Constant' |
---|
1211 | self.nn = 0 |
---|
1212 | self.Ref1 = parse_ref(ref1) |
---|
1213 | self.msg = '%s occurs only once' % self.Ref1[1] |
---|
1214 | self.value = None |
---|
1215 | self.required = required |
---|
1216 | |
---|
1217 | def __reset__(self): |
---|
1218 | self.nn = 0 |
---|
1219 | self.value = None |
---|
1220 | |
---|
1221 | def check(self,fns): |
---|
1222 | if fns.has_key( self.Ref1[0] ): |
---|
1223 | if self.value == None: |
---|
1224 | self.value = fns[self.Ref1[0]] |
---|
1225 | return ('DEFER', 'first element') |
---|
1226 | else: |
---|
1227 | if self.value == fns[self.Ref1[0]]: |
---|
1228 | return ('PASS','%s checked' % self.Ref1[0] ) |
---|
1229 | else: |
---|
1230 | return ('FAIL', '%s not constant across file group' % self.Ref1[0] ) |
---|
1231 | else: |
---|
1232 | if self.required: |
---|
1233 | return ('FAIL', 'missing NS element %s' % self.Ref1[0] ) |
---|
1234 | else: |
---|
1235 | return ('PASS',None) |
---|
1236 | |
---|
1237 | def ref_to_key(ref): |
---|
1238 | bits = string.split(ref,'/') |
---|
1239 | assert bits[0] in ['VALUE','PATH','FILENAME','ATTRIBUTES','CONFIG','ARGS'], 'Bad line in CONSTRAINT section of config file' |
---|
1240 | if bits[0] == 'ATTRIBUTES': |
---|
1241 | if bits[1] == 'Global': |
---|
1242 | return ('g_%s' % bits[2],'Global attribute %s' % bits[2] ) |
---|
1243 | elif bits[0] == 'FILENAME': |
---|
1244 | return ('fn_%s' % bits[1],'File name component %s' % bits[1] ) |
---|
1245 | elif bits[0] == 'VALUE': |
---|
1246 | return ('VALUE',bits[1]) |
---|
1247 | |
---|
1248 | class section_parser_l0(object): |
---|
1249 | |
---|
1250 | def __init__(self,parent,sectionName): |
---|
1251 | self.sname = sectionName |
---|
1252 | self.parent = parent |
---|
1253 | self.lines = [] |
---|
1254 | |
---|
1255 | def add( self, l ): |
---|
1256 | self.lines.append( string.strip( l ) ) |
---|
1257 | |
---|
1258 | def close(self): |
---|
1259 | assert type(self.parent.sections) == type( {} ), 'parent.sections has wrong type (%s), should be a dictionary' % ( str( type( self.parent.sections ) ) ) |
---|
1260 | |
---|
1261 | self.parent.sections[self.sname] = self.lines[:] |
---|
1262 | self.lines = [] |
---|