NCCOOS Trac Projects: Top | Web | Platforms | Processing | Viz | Sprints | Sandbox | (Wind)

root/sodar/branches/scintec-branch/sodar/scintec/maindata.py

Revision 267 (checked in by cbc, 14 years ago)

Just a few more tests.

Line 
1 """
2 Module to handle Scintec sodar .mnd files.
3
4 >>> from sodar.scintec import maindata
5 """
6
7 __author__ = 'Chris Calloway'
8 __email__ = 'cbc@chriscalloway.org'
9 __copyright__ = 'Copyright 2009 UNC-CH Department of Marine Science'
10 __license__ = 'GPL2'
11
12 from datetime import datetime, timedelta
13
14 class MainData(list):
15     """
16     Contain data from a Scintec sodar .mnd file.
17    
18     The data is contained as a list of Profile objects
19     along with some attributes which apply to all the Profile objects.
20    
21     Parse a known good .mnd file:
22     >>> main_data = MainData(good_mnd)
23    
24     Parse the profile data:
25     >>> len(main_data)
26     48
27    
28     Parse the first profile:
29     >>> len(main_data[0])
30     39
31     >>> main_data[0].start
32     datetime.datetime(2009, 11, 17, 0, 0)
33     >>> main_data[0].stop
34     datetime.datetime(2009, 11, 17, 0, 30)
35     >>> main_data[0].variables
36     ['z', 'speed', 'dir', 'W', 'sigW', 'bck', 'error']
37     >>> main_data[0][0] == {'z':'10', 'speed':'99.99', 'dir':'999.9',
38     ...                     'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
39     ...                     'error':'0'}
40     True
41     >>> main_data[0][0].z
42     '10'
43     >>> main_data[0]['10'] == {'z':'10', 'speed':'99.99', 'dir':'999.9',
44     ...                     'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
45     ...                     'error':'0'}
46     True
47     >>> main_data[0]['10'].speed
48     '99.99'
49     >>> main_data[0][-1] == {'z':'200', 'speed':'99.99', 'dir':'999.9',
50     ...                      'W':'-0.07', 'sigW':'99.99', 'bck':'9.99E+37',
51     ...                      'error':'0'}
52     True
53     >>> main_data[0][-1].dir
54     '999.9'
55     >>> main_data[0]['200'] == {'z':'200', 'speed':'99.99', 'dir':'999.9',
56     ...                         'W':'-0.07', 'sigW':'99.99', 'bck':'9.99E+37',
57     ...                         'error':'0'}
58     True
59     >>> main_data[0]['200'].W
60     '-0.07'
61    
62     Parse the last profile:
63     >>> len(main_data[-1])
64     39
65     >>> main_data[-1].start
66     datetime.datetime(2009, 11, 17, 23, 30)
67     >>> main_data[-1].stop
68     datetime.datetime(2009, 11, 18, 0, 0)
69     >>> main_data[-1].variables
70     ['z', 'speed', 'dir', 'W', 'sigW', 'bck', 'error']
71     >>> main_data[-1][0] == {'z':'10', 'speed':'99.99', 'dir':'999.9',
72     ...                     'W':'-0.32', 'sigW':'99.99', 'bck':'9.99E+37',
73     ...                     'error':'0'}
74     True
75     >>> main_data[-1][0].z
76     '10'
77     >>> main_data[-1]['10'] == {'z':'10', 'speed':'99.99', 'dir':'999.9',
78     ...                         'W':'-0.32', 'sigW':'99.99', 'bck':'9.99E+37',
79     ...                         'error':'0'}
80     True
81     >>> main_data[-1]['10'].speed
82     '99.99'
83     >>> main_data[-1][-1] == {'z':'200', 'speed':'15.05', 'dir':'71.8',
84     ...                      'W':'-0.19', 'sigW':'0.53', 'bck':'9.99E+37',
85     ...                      'error':'0'}
86     True
87     >>> main_data[-1][-1].dir
88     '71.8'
89     >>> main_data[-1]['200'] == {'z':'200', 'speed':'15.05', 'dir':'71.8',
90     ...                          'W':'-0.19', 'sigW':'0.53', 'bck':'9.99E+37',
91     ...                          'error':'0'}
92     True
93     >>> main_data[-1]['200'].W
94     '-0.19'
95     """
96    
97     def __init__(self, mnd, *args):
98         """
99         Parse main daily Scintec sodar .mnd file.
100        
101         MainData(mnd[,file_name[,file_path]]) -> <MainData object>
102        
103         Where:
104          
105             mnd is a str object containing the complete contents read from a
106             Scintec .mnd daily sodar file including all line endings,
107            
108             file_name is an optional str object representing a file name for
109             a file which contains the referenced .mnd daily sodar file,
110            
111             file_path is an optional str object representing the path to
112             file_name.
113        
114         Parse a known good .mnd file:
115        
116         >>> main_data = MainData(good_mnd)
117         >>> main_data = MainData(good_mnd,good_name)
118         >>> main_data.file_name == good_name
119         True
120         >>> main_data = MainData(good_mnd,good_name,good_path)
121         >>> main_data.file_name == good_name
122         True
123         >>> main_data.file_path == good_path
124         True
125         """
126        
127         super(self.__class__, self).__init__()
128        
129         self.file_name = ''
130         self.file_path = ''
131        
132         # Optional args: smoke 'em if ya got 'em.
133         try:
134             self.file_name = str(args[0])
135             self.file_path = str(args[1])
136         except IndexError:
137             pass
138        
139         # Divide the data into blocks
140         # across boundaries separated by blank lines.
141         blocks = [block.strip()
142                   for block
143                   in mnd.split('\n\n')
144                   if block.strip()]
145        
146         # The first block is the specification of the format header.
147         # Divide it into lines.
148         format_header_spec = [line.strip()
149                               for line
150                               in blocks[0].split('\n')
151                               if line.strip()]
152        
153         # The second block is the body of the format header.
154         # Divide it into lines.
155         file_header_body = [line.strip()
156                             for line
157                             in blocks[1].split('\n')
158                             if line.strip()]
159        
160         # All the remaing blocks are individual profiles
161         # in chronological order.
162         self.extend([Profile([line.strip()
163                               for line
164                               in block.split('\n')
165                               if line.strip()])
166                      for block
167                      in blocks[2:]])
168        
169
170 class Profile(list):
171     """
172     Contain data for single profile from a Scintec sodar .mnd file.
173    
174     The data is contained as a list of Observation objects
175     along with some attributes which apply to all the Profile objects.
176    
177     Parse a known good profile block:
178     >>> profile = Profile(good_profile)
179    
180     Access the timestamp attributes:
181     >>> profile.start
182     datetime.datetime(2009, 11, 17, 0, 0)
183     >>> profile.stop
184     datetime.datetime(2009, 11, 17, 0, 30)
185    
186     Access the variable list:
187     >>> profile.variables
188     ['z', 'speed', 'dir', 'W', 'sigW', 'bck', 'error']
189    
190     Access profiles by sequence number and variables by attribute name:
191     >>> profile[0] == {'z':'10', 'speed':'99.99', 'dir':'999.9',
192     ...                'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
193     ...                'error':'0'}
194     True
195     >>> profile[0].z
196     '10'
197     >>> profile[-1] == {'z':'200', 'speed':'99.99', 'dir':'999.9',
198     ...                 'W':'-0.07', 'sigW':'99.99', 'bck':'9.99E+37',
199     ...                 'error':'0'}
200     True
201     >>> profile[-1].speed
202     '99.99'
203    
204     Access profiles by elevation and variables by attribute name:
205     >>> profile['10'] == {'z':'10', 'speed':'99.99', 'dir':'999.9',
206     ...                   'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
207     ...                   'error':'0'}
208     True
209     >>> profile['10'].dir
210     '999.9'
211     >>> profile['200'] == {'z':'200', 'speed':'99.99', 'dir':'999.9',
212     ...                    'W':'-0.07', 'sigW':'99.99', 'bck':'9.99E+37',
213     ...                    'error':'0'}
214     True
215     >>> profile['200'].W
216     '-0.07'
217     """
218    
219     def __init__(self, profile_block):
220         """
221         Parse a profile block from a main daily Scintec sodar .mnd file.
222        
223         Profile(profile_data) -> <Profile object>
224        
225         Where:
226        
227             profile_block is a list of str objects containing all the lines
228             from a single profile in a Scintec .mnd daily sodar file.
229        
230         Parse a known good profile block:
231         >>> profile = Profile(good_profile)
232        
233         Access the timestamp attributes:
234         >>> profile.start
235         datetime.datetime(2009, 11, 17, 0, 0)
236         >>> profile.stop
237         datetime.datetime(2009, 11, 17, 0, 30)
238        
239         Access the variable list:
240         >>> profile.variables
241         ['z', 'speed', 'dir', 'W', 'sigW', 'bck', 'error']
242        
243         Access profiles by sequence number and variables by attribute name:
244         >>> profile[0] == {'z':'10', 'speed':'99.99', 'dir':'999.9',
245         ...                'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
246         ...                'error':'0'}
247         True
248         >>> profile[0].z
249         '10'
250         >>> profile[-1] == {'z':'200', 'speed':'99.99', 'dir':'999.9',
251         ...                 'W':'-0.07', 'sigW':'99.99', 'bck':'9.99E+37',
252         ...                 'error':'0'}
253         True
254         >>> profile[-1].speed
255         '99.99'
256         """
257        
258         super(self.__class__, self).__init__()
259
260         # The first line in the profile block is the timestamp.       
261         timestamp = profile_block[0]
262         date, stop, duration = timestamp.split()
263         date_year, date_month, date_day = date.split('-')
264         stop_hour, stop_minute, stop_second = stop.split(':')
265         duration_hour, duration_minute, duration_second = duration.split(':')
266         interval = timedelta(0,                    # days
267                              int(duration_second),
268                              0,                    # microseconds
269                              0,                    # milliseconds
270                              int(duration_minute),
271                              int(duration_hour))   # omit optional weeks
272         self.stop = datetime(int(date_year),
273                              int(date_month),
274                              int(date_day),
275                              int(stop_hour),
276                              int(stop_minute),     # omit optional microseconds
277                              int(stop_second))     # and tzinfo
278         self.start = self.stop - interval
279        
280         # The second line in the profile block is
281         # a commented list of variable names
282         self.variables = profile_block[1].split()[1:]
283        
284         # All the remaining lines in the profile block are
285         # individual obervation columns in the same order
286         # as the variable names.
287         super(self.__class__, self).extend([Observation(observation.split(),
288                                                         self.variables)
289                                             for observation
290                                             in profile_block[2:]])
291    
292     def __getitem__(self,key):
293         """
294         Get a profile by elevation.
295        
296         profile_instance[elevation] -> <Observation object>
297        
298         Where:
299        
300             elevation is a str object object representing the elevation
301             (value keyed by 'z') of the Observation object.
302        
303         Access profiles by elevation and variables by attribute name:
304         >>> profile = Profile(good_profile)
305         >>> profile['10'] == {'z':'10', 'speed':'99.99', 'dir':'999.9',
306         ...                   'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
307         ...                   'error':'0'}
308         True
309         >>> profile['10'].dir
310         '999.9'
311         >>> profile['200'] == {'z':'200', 'speed':'99.99', 'dir':'999.9',
312         ...                    'W':'-0.07', 'sigW':'99.99', 'bck':'9.99E+37',
313         ...                    'error':'0'}
314         True
315         >>> profile['200'].W
316         '-0.07'
317         """
318        
319         try:
320             return super(self.__class__, self).__getitem__(key)
321         except TypeError:
322             for observation in self:
323                 if observation['z'] == key:
324                     return observation
325             return super(self.__class__, self).__getitem__(key)
326    
327     def __setitem__(self,key,value):
328         """
329         Protect items.
330        
331         profile_instance[object1] = object2
332        
333         Raise an Exception if attempting to mutate a profile:
334         >>> profile = Profile(good_profile)
335         >>> profile[0] = 'OK'
336         Traceback (most recent call last):
337             ...
338         TypeError: 'Profile' instance's items are read-only
339         >>> profile['10'] = 'OK'
340         Traceback (most recent call last):
341             ...
342         TypeError: 'Profile' instance's items are read-only
343         >>> profile[100000] = 'OK'
344         Traceback (most recent call last):
345             ...
346         TypeError: 'Profile' instance's items are read-only
347         >>> profile[100000]
348         Traceback (most recent call last):
349             ...
350         IndexError: list index out of range
351         >>> profile['100000'] = 'OK'
352         Traceback (most recent call last):
353             ...
354         TypeError: 'Profile' instance's items are read-only
355         >>> profile['100000']
356         Traceback (most recent call last):
357             ...
358         TypeError: list indices must be integers
359        
360         Without affecting the observation:
361         >>> profile[0] == {'z':'10', 'speed':'99.99', 'dir':'999.9',
362         ...                'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
363         ...                'error':'0'}
364         True
365         >>> profile['10'] == {'z':'10', 'speed':'99.99', 'dir':'999.9',
366         ...                   'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
367         ...                   'error':'0'}
368         True
369         """
370        
371         raise TypeError, ' '.join([repr(self.__class__.__name__),
372                                    'instance\'s items are read-only',])
373    
374     def __delitem__(self,key):
375         """
376         Protect items.
377        
378         del profile_instance[object]
379        
380         Raise an Exception if attempting to mutate a profile:
381         >>> profile = Profile(good_profile)
382         >>> del profile[0]
383         Traceback (most recent call last):
384             ...
385         TypeError: 'Profile' instance's items are read-only
386         >>> del profile['10']
387         Traceback (most recent call last):
388             ...
389         TypeError: 'Profile' instance's items are read-only
390         >>> del profile[100000]
391         Traceback (most recent call last):
392             ...
393         TypeError: 'Profile' instance's items are read-only
394         >>> del profile['100000']
395         Traceback (most recent call last):
396             ...
397         TypeError: 'Profile' instance's items are read-only
398        
399         Without affecting the observation:
400         >>> profile[0] == {'z':'10', 'speed':'99.99', 'dir':'999.9',
401         ...                'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
402         ...                'error':'0'}
403         True
404         >>> profile['10'] == {'z':'10', 'speed':'99.99', 'dir':'999.9',
405         ...                   'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
406         ...                   'error':'0'}
407         True
408         """
409        
410         raise TypeError, ' '.join([repr(self.__class__.__name__),
411                                    'instance\'s items are read-only',])
412    
413     def __setslice__(self,i,j,seq):
414         """
415         Protect items.
416        
417         profile_instance[i:j] = seq
418        
419         Raise an Exception if attempting to mutate a profile:
420         >>> profile = Profile(good_profile)
421         >>> profile[0:10] = 'OK'
422         Traceback (most recent call last):
423             ...
424         TypeError: 'Profile' instance's items are read-only
425        
426         Without affecting the observations:
427         >>> profile[0] == {'z':'10', 'speed':'99.99', 'dir':'999.9',
428         ...                'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
429         ...                'error':'0'}
430         True
431         >>> profile['10'] == {'z':'10', 'speed':'99.99', 'dir':'999.9',
432         ...                   'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
433         ...                   'error':'0'}
434         True
435         """
436        
437         raise TypeError, ' '.join([repr(self.__class__.__name__),
438                                    'instance\'s items are read-only',])
439    
440     def __delslice__(self,i,j):
441         """
442         Protect items.
443        
444         del profile_instance[i:j]
445        
446         Raise an Exception if attempting to mutate a profile:
447         >>> profile = Profile(good_profile)
448         >>> del profile[0:10]
449         Traceback (most recent call last):
450             ...
451         TypeError: 'Profile' instance's items are read-only
452        
453         Without affecting the observations:
454         >>> profile[0] == {'z':'10', 'speed':'99.99', 'dir':'999.9',
455         ...                'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
456         ...                'error':'0'}
457         True
458         >>> profile['10'] == {'z':'10', 'speed':'99.99', 'dir':'999.9',
459         ...                   'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
460         ...                   'error':'0'}
461         True
462         """
463        
464         raise TypeError, ' '.join([repr(self.__class__.__name__),
465                                    'instance\'s items are read-only',])
466    
467     def append(self,item):
468         """
469         Protect items.
470        
471         Raise an Exception if attempting to mutate a profile:
472         >>> profile = Profile(good_profile)
473         >>> profile.append('OK')
474         Traceback (most recent call last):
475             ...
476         TypeError: 'Profile' instance's items are read-only
477        
478         Without affecting the observations:
479         >>> profile[0] == {'z':'10', 'speed':'99.99', 'dir':'999.9',
480         ...                'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
481         ...                'error':'0'}
482         True
483         """
484        
485         raise TypeError, ' '.join([repr(self.__class__.__name__),
486                                    'instance\'s items are read-only',])
487    
488     def extend(self,seq):
489         """
490         Protect items.
491        
492         Raise an Exception if attempting to mutate a profile:
493         >>> profile = Profile(good_profile)
494         >>> profile.extend('OK')
495         Traceback (most recent call last):
496             ...
497         TypeError: 'Profile' instance's items are read-only
498        
499         Without affecting the observations:
500         >>> profile[0] == {'z':'10', 'speed':'99.99', 'dir':'999.9',
501         ...                'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
502         ...                'error':'0'}
503         True
504         """
505        
506         raise TypeError, ' '.join([repr(self.__class__.__name__),
507                                    'instance\'s items are read-only',])
508    
509     def insert(self,i,item):
510         """
511         Protect items.
512        
513         Raise an Exception if attempting to mutate a profile:
514         >>> profile = Profile(good_profile)
515         >>> profile.insert(0,'OK')
516         Traceback (most recent call last):
517             ...
518         TypeError: 'Profile' instance's items are read-only
519        
520         Without affecting the observations:
521         >>> profile[0] == {'z':'10', 'speed':'99.99', 'dir':'999.9',
522         ...                'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
523         ...                'error':'0'}
524         True
525         """
526        
527         raise TypeError, ' '.join([repr(self.__class__.__name__),
528                                    'instance\'s items are read-only',])
529    
530     def pop(self,*args):
531         """
532         Protect items.
533        
534         Raise an Exception if attempting to mutate a profile:
535         >>> profile = Profile(good_profile)
536         >>> profile.pop()
537         Traceback (most recent call last):
538             ...
539         TypeError: 'Profile' instance's items are read-only
540         >>> profile.pop(0)
541         Traceback (most recent call last):
542             ...
543         TypeError: 'Profile' instance's items are read-only
544        
545         Without affecting the observations:
546         >>> profile[0] == {'z':'10', 'speed':'99.99', 'dir':'999.9',
547         ...                'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
548         ...                'error':'0'}
549         True
550         >>> profile[-1] == {'z':'200', 'speed':'99.99', 'dir':'999.9',
551         ...                 'W':'-0.07', 'sigW':'99.99', 'bck':'9.99E+37',
552         ...                 'error':'0'}
553         True
554         """
555        
556         raise TypeError, ' '.join([repr(self.__class__.__name__),
557                                    'instance\'s items are read-only',])
558    
559     def remove(self,i):
560         """
561         Protect items.
562        
563         Raise an Exception if attempting to mutate a profile:
564         >>> profile = Profile(good_profile)
565         >>> observation = profile[0]
566         >>> profile.remove(observation)
567         Traceback (most recent call last):
568             ...
569         TypeError: 'Profile' instance's items are read-only
570        
571         Without affecting the observations:
572         >>> profile[0] == {'z':'10', 'speed':'99.99', 'dir':'999.9',
573         ...                'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
574         ...                'error':'0'}
575         True
576         """
577        
578         raise TypeError, ' '.join([repr(self.__class__.__name__),
579                                    'instance\'s items are read-only',])
580    
581     def reverse(self):
582         """
583         Protect items.
584        
585         Raise an Exception if attempting to mutate a profile:
586         >>> profile = Profile(good_profile)
587         >>> profile.reverse()
588         Traceback (most recent call last):
589             ...
590         TypeError: 'Profile' instance's items are read-only
591        
592         Without affecting the observations:
593         >>> profile[0] == {'z':'10', 'speed':'99.99', 'dir':'999.9',
594         ...                'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
595         ...                'error':'0'}
596         True
597         """
598        
599         raise TypeError, ' '.join([repr(self.__class__.__name__),
600                                    'instance\'s items are read-only',])
601    
602     def sort(self,**kwargs):
603         """
604         Protect items.
605        
606         Raise an Exception if attempting to mutate a profile:
607         >>> profile = Profile(good_profile)
608         >>> profile.sort()
609         Traceback (most recent call last):
610             ...
611         TypeError: 'Profile' instance's items are read-only
612         >>> profile.sort(cmp=lambda x,y: cmp(x.lower(), y.lower()))
613         Traceback (most recent call last):
614             ...
615         TypeError: 'Profile' instance's items are read-only
616         >>> profile.sort(cmp=lambda x,y: cmp(x.lower(), y.lower()),
617         ...              key=str.lower)
618         Traceback (most recent call last):
619             ...
620         TypeError: 'Profile' instance's items are read-only
621         >>> profile.sort(cmp=lambda x,y: cmp(x.lower(), y.lower()),
622         ...              key=str.lower,
623         ...              reverse=True)
624         Traceback (most recent call last):
625             ...
626         TypeError: 'Profile' instance's items are read-only
627        
628        
629         Without affecting the observations:
630         >>> profile[0] == {'z':'10', 'speed':'99.99', 'dir':'999.9',
631         ...                'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
632         ...                'error':'0'}
633         True
634         """
635        
636         raise TypeError, ' '.join([repr(self.__class__.__name__),
637                                    'instance\'s items are read-only',])
638
639 class Observation(dict):
640     """
641     Contain data for single observation from a Scintec sodar .mnd files.
642    
643     The data is contained as a dictionary of variable name/value pairs.
644    
645     The variable values may also be accessed as attributes of the instance.
646    
647     Parse a known good observation:
648     >>> observation = Observation(good_observation,good_variables)
649     >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9',
650     ...                 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
651     ...                 'error':'0'}
652     True
653    
654     Access observation variables as attributes:
655     >>> observation.z
656     '10'
657     >>> observation.speed
658     '99.99'
659     >>> observation.dir
660     '999.9'
661     >>> observation.W
662     '-0.05'
663     >>> observation.sigW
664     '0.40'
665     >>> observation.bck
666     '5.46E+03'
667     >>> observation.error
668     '0'
669     """
670    
671     def __init__(self,data,variables):
672         """
673         Parse an observation from a main daily Scintec sodar .mnd file.
674        
675         Observation(data,variables) -> <Observation object>
676        
677         Where:
678          
679             data is a list of str object representing variable values,
680            
681             variables is a list of str objects representing variable labels.
682        
683         Parse a known good observation:
684         >>> observation = Observation(good_observation,good_variables)
685         >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9',
686         ...                 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
687         ...                 'error':'0'}
688         True
689        
690         Raise an Exception if more variable names than values:
691         >>> extra_variables = good_variables + ['extra']
692         >>> observation = Observation(good_observation,extra_variables)
693         Traceback (most recent call last):
694             ...
695         AttributeError: Same number of attributes and values required in
696         'Observation' object
697        
698         Raise an Exception if more variable values than names:
699         >>> extra_observation = good_observation + ['extra']
700         >>> observation = Observation(extra_observation,good_variables)
701         Traceback (most recent call last):
702             ...
703         AttributeError: Same number of attributes and values required in
704         'Observation' object
705         """
706        
707         super(self.__class__, self).__init__()
708        
709         if len(variables) == len(data):
710             # And update the dictionary with the variable names/values.
711             super(self.__class__, self).update(dict(zip(variables,data)))
712         else:
713             # Unless there are an unequal number of variable names/values.
714             raise AttributeError, ' '.join(['Same number of attributes',
715                                             'and values required in',
716                                             repr(self.__class__.__name__),
717                                             'object',])
718    
719     def __getattr__(self,name):
720         """
721         Get a variable value by name.
722        
723         observation_instance.variable_name -> variable_value
724        
725         Access observation variables as attributes:
726         >>> observation = Observation(good_observation,good_variables)
727         >>> observation.z
728         '10'
729         >>> observation.speed
730         '99.99'
731         >>> observation.dir
732         '999.9'
733         >>> observation.W
734         '-0.05'
735         >>> observation.sigW
736         '0.40'
737         >>> observation.bck
738         '5.46E+03'
739         >>> observation.error
740         '0'
741        
742         Raise an Exception for an undefined attribute:
743         >>> observation.y
744         Traceback (most recent call last):
745             ...
746         AttributeError: 'Observation' object has no attribute 'y'
747        
748         Allow the creation and access of attributes other than variables.
749         >>> observation.y = 'OK'
750         >>> observation.y
751         'OK'
752        
753         Without affecting the variables:
754         >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9',
755         ...                 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
756         ...                 'error':'0'}
757         True
758         """
759        
760         # Attempt attribute access by dictionary key.
761         if name in self:
762             return self[name]
763         else:
764             raise AttributeError, ' '.join([repr(self.__class__.__name__),
765                                             'object has no attribute',
766                                             repr(name),])
767    
768     def __setattr__(self,name,value):
769         """
770         Protect variable values from overwriting.
771        
772         observation_instance.variable_name = variable_value
773        
774         But retrieve any other bound attribute values.
775        
776         Raise an Exception if attempting to rebind a variable:
777         >>> observation = Observation(good_observation,good_variables)
778         >>> observation.z = 'OK'
779         Traceback (most recent call last):
780             ...
781         AttributeError: Rebinding attribute 'z' disallowed in
782         'Observation' object
783        
784         However, allow attributes other than variables to be bound:
785         >>> observation.y = 'OK'
786         >>> observation.y
787         'OK'
788        
789         Without affecting the variables:
790         >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9',
791         ...                 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
792         ...                 'error':'0'}
793         True
794         """
795        
796         if name in self:
797             # Protect observation variables
798             raise AttributeError, ' '.join(['Rebinding attribute',
799                                             repr(name),
800                                             'disallowed in',
801                                             repr(self.__class__.__name__),
802                                             'object',])
803         else:
804             # Otherwise, do it the normal way.
805             super(self.__class__, self).__setattr__(name,value)
806    
807     def __delattr__(self,name):
808         """
809         Protect variable values from deletion.
810        
811         del observation_instance.variable_name
812        
813         But delete any other bound attribute values.
814        
815         Raise an Exception if attempting to unbind a variable:
816         >>> observation = Observation(good_observation,good_variables)
817         >>> del observation.z
818         Traceback (most recent call last):
819             ...
820         AttributeError: Unbinding attribute 'z' disallowed in
821         'Observation' object
822        
823         Raise an Exception for attempting to unbind an undefined attribute:
824         >>> del observation.y
825         Traceback (most recent call last):
826             ...
827         AttributeError: 'Observation' object has no attribute 'y'
828        
829         However, allow attributes other than variables to be unbound:
830         >>> observation.y = 'OK'
831         >>> del observation.y
832         >>> del observation.y
833         Traceback (most recent call last):
834             ...
835         AttributeError: y
836        
837         Without affecting the variables:
838         >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9',
839         ...                 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
840         ...                 'error':'0'}
841         True
842         """
843        
844         if name in self:
845             # Protect observation variables
846             raise AttributeError, ' '.join(['Unbinding attribute',
847                                             repr(name),
848                                             'disallowed in',
849                                             repr(self.__class__.__name__),
850                                             'object',])
851         else:
852             # Otherwise, do it the normal way.
853             super(self.__class__, self).__delattr__(name)
854    
855     def __setitem__(self,key,value):
856         """
857         Protect items.
858        
859         observation_instance[variable_name] = variable_value
860        
861         Raise an Exception if attempting to mutate an observation:
862         >>> observation = Observation(good_observation,good_variables)
863         >>> observation['z'] = 'OK'
864         Traceback (most recent call last):
865             ...
866         TypeError: 'Observation' instance's items are read-only
867         >>> observation['y'] = 'OK'
868         Traceback (most recent call last):
869             ...
870         TypeError: 'Observation' instance's items are read-only
871         >>> observation.y
872         Traceback (most recent call last):
873             ...
874         AttributeError: 'Observation' object has no attribute 'y'
875         >>> observation.y = 'OK'
876         >>> observation['y'] = 'Not OK'
877         Traceback (most recent call last):
878             ...
879         TypeError: 'Observation' instance's items are read-only
880         >>> observation.y
881         'OK'
882        
883         Without affecting the variables:
884         >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9',
885         ...                 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
886         ...                 'error':'0'}
887         True
888         """
889        
890         raise TypeError, ' '.join([repr(self.__class__.__name__),
891                                    'instance\'s items are read-only',])
892    
893     def __delitem__(self,key):
894         """
895         Protect items.
896        
897         observation_instance[variable_name] = variable_value
898        
899         Raise an Exception if attempting to mutate an observation:
900         >>> observation = Observation(good_observation,good_variables)
901         >>> del observation['z']
902         Traceback (most recent call last):
903             ...
904         TypeError: 'Observation' instance's items are read-only
905         >>> observation.y = 'OK'
906         >>> del observation['y']
907         Traceback (most recent call last):
908             ...
909         TypeError: 'Observation' instance's items are read-only
910        
911         Without affecting the variables:
912         >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9',
913         ...                 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
914         ...                 'error':'0'}
915         True
916         """
917        
918         raise TypeError, ' '.join([repr(self.__class__.__name__),
919                                    'instance\'s items are read-only',])
920    
921     def clear(self):
922         """
923         Protect items.
924        
925         Raise an Exception if attempting to mutate an observation:
926         >>> observation = Observation(good_observation,good_variables)
927         >>> observation.clear()
928         Traceback (most recent call last):
929             ...
930         TypeError: 'Observation' instance's items are read-only
931        
932         Without affecting the variables:
933         >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9',
934         ...                 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
935         ...                 'error':'0'}
936         True
937         """
938        
939         raise TypeError, ' '.join([repr(self.__class__.__name__),
940                                    'instance\'s items are read-only',])
941    
942     def pop(self,key,*args):
943         """
944         Protect items.
945        
946         Raise an Exception if attempting to mutate an observation:
947         >>> observation = Observation(good_observation,good_variables)
948         >>> observation.pop('z')
949         Traceback (most recent call last):
950             ...
951         TypeError: 'Observation' instance's items are read-only
952         >>> observation.pop('y')
953         Traceback (most recent call last):
954             ...
955         TypeError: 'Observation' instance's items are read-only
956         >>> observation.pop('y','OK')
957         Traceback (most recent call last):
958             ...
959         TypeError: 'Observation' instance's items are read-only
960        
961         Without affecting the variables:
962         >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9',
963         ...                 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
964         ...                 'error':'0'}
965         True
966         """
967        
968         raise TypeError, ' '.join([repr(self.__class__.__name__),
969                                    'instance\'s items are read-only',])
970    
971     def popitem(self):
972         """
973         Protect items.
974        
975         Raise an Exception if attempting to mutate an observation:
976         >>> observation = Observation(good_observation,good_variables)
977         >>> observation.popitem()
978         Traceback (most recent call last):
979             ...
980         TypeError: 'Observation' instance's items are read-only
981        
982         Without affecting the variables:
983         >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9',
984         ...                 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
985         ...                 'error':'0'}
986         True
987         """
988        
989         raise TypeError, ' '.join([repr(self.__class__.__name__),
990                                    'instance\'s items are read-only',])
991    
992     def setdefault(self,key,*args):
993         """
994         Protect items.
995        
996         Raise an Exception if attempting to mutate an observation:
997         >>> observation = Observation(good_observation,good_variables)
998         >>> observation.setdefault('z')
999         Traceback (most recent call last):
1000             ...
1001         TypeError: 'Observation' instance's items are read-only
1002         >>> observation.setdefault('y')
1003         Traceback (most recent call last):
1004             ...
1005         TypeError: 'Observation' instance's items are read-only
1006         >>> observation.setdefault('y','OK')
1007         Traceback (most recent call last):
1008             ...
1009         TypeError: 'Observation' instance's items are read-only
1010        
1011         Without affecting the variables:
1012         >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9',
1013         ...                 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
1014         ...                 'error':'0'}
1015         True
1016         """
1017        
1018         raise TypeError, ' '.join([repr(self.__class__.__name__),
1019                                    'instance\'s items are read-only',])
1020    
1021     def update(self,*args,**kwargs):
1022         """
1023         Protect items.
1024        
1025         Raise an Exception if attempting to mutate an observation:
1026         >>> observation = Observation(good_observation,good_variables)
1027         >>> observation.update({'UP':'up','DOWN':'down',})
1028         Traceback (most recent call last):
1029             ...
1030         TypeError: 'Observation' instance's items are read-only
1031        
1032         Without affecting the variables:
1033         >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9',
1034         ...                 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
1035         ...                 'error':'0'}
1036         True
1037        
1038         Raise an Exception if attempting to mutate an observation:
1039         >>> observation.update((('UP','up'),('DOWN','down'),))
1040         Traceback (most recent call last):
1041             ...
1042         TypeError: 'Observation' instance's items are read-only
1043
1044         Without affecting the variables:
1045         >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9',
1046         ...                 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
1047         ...                 'error':'0'}
1048         True
1049        
1050         Raise an Exception if attempting to mutate an observation:
1051         >>> observation.update(UP='up',DOWN='down')
1052         Traceback (most recent call last):
1053             ...
1054         TypeError: 'Observation' instance's items are read-only
1055        
1056         Without affecting the variables:
1057         >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9',
1058         ...                 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03',
1059         ...                 'error':'0'}
1060         True
1061         """
1062        
1063         raise TypeError, ' '.join([repr(self.__class__.__name__),
1064                                    'instance\'s items are read-only',])
1065
1066 def _test():
1067     """
1068     Run module tests in script mode.
1069    
1070     >>> from sodar.scintec.maindata import _test
1071     """
1072    
1073     import doctest
1074     from sodar.tests import suite
1075     mnd_path,mnd_file,mnd,profile,variables,observation = \
1076         suite.setUpGoodMndData()
1077     doctest.testmod(extraglobs=dict(good_mnd=mnd,
1078                                     good_name=mnd_file,
1079                                     good_path=mnd_path,
1080                                     good_profile=profile,
1081                                     good_variables=variables,
1082                                     good_observation=observation),
1083                     optionflags=doctest.NORMALIZE_WHITESPACE)
1084
1085 if __name__ == "__main__":
1086     _test()
Note: See TracBrowser for help on using the browser.