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

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

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

More robust Profile class.

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