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

Changeset 265

Show
Ignore:
Timestamp:
11/30/09 18:19:49
Author:
cbc
Message:

Robust Observation class and complete test coverage.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • sodar/branches/scintec-branch/sodar/scintec/maindata.py

    r264 r265  
    1414    Contain data from a Scintec sodar .mnd file. 
    1515     
     16    The data is contained as a list of Profile objects 
     17    along with some attributes which apply to all the Profile objects. 
     18     
    1619    Parse a known good .mnd file: 
    1720    >>> main_data = MainData(good_mnd) 
    1821     
    1922    Parse the format header: 
    20     >>> len(main_data._format_header
     23    >>> len(main_data._format_header_spec
    2124    4 
    22     >>> main_data._format_header[0] 
     25    >>> main_data._format_header_spec[0] 
    2326    'FORMAT-1' 
    2427     
     
    3841    >>> main_data[0].timestamp 
    3942    '2009-11-17 00:30:00 00:30:00' 
    40     >>> main_data[0].variables == ['z', 'speed', 'dir', 'W', 
    41     ...                            'sigW', 'bck', 'error'] 
    42     True 
     43    >>> main_data[0].variables 
     44    ['z', 'speed', 'dir', 'W', 'sigW', 'bck', 'error'] 
    4345    >>> main_data[0][0] == {'z':'10', 'speed':'99.99', 'dir':'999.9', 
    4446    ...                     'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', 
     
    5557    >>> main_data[-1].timestamp 
    5658    '2009-11-18 00:00:00 00:30:00' 
    57     >>> main_data[-1].variables == ['z', 'speed', 'dir', 'W', 
    58     ...                             'sigW', 'bck', 'error'] 
    59     True 
     59    >>> main_data[-1].variables 
     60    ['z', 'speed', 'dir', 'W', 'sigW', 'bck', 'error'] 
    6061    >>> main_data[-1][0] == {'z':'10', 'speed':'99.99', 'dir':'999.9', 
    6162    ...                     'W':'-0.32', 'sigW':'99.99', 'bck':'9.99E+37', 
     
    103104        self.file_path = '' 
    104105         
     106        # Optional args: smoke 'em if ya got 'em. 
    105107        try: 
    106108            self.file_name = str(args[0]) 
     
    109111            pass 
    110112         
     113        # Divide the data into blocks 
     114        # across boundaries separated by blank lines. 
    111115        self._blocks = [self._block.strip() 
    112                       for self._block in mnd.split('\n\n') 
    113                       if self._block.strip()] 
    114         self._format_header = [self._line.strip() 
    115                               for self._line in self._blocks[0].split('\n') 
    116                               if self._line.strip()] 
     116                        for self._block 
     117                        in mnd.split('\n\n') 
     118                        if self._block.strip()] 
     119         
     120        # The first block is the specification of the format header. 
     121        # Divide it into lines. 
     122        self._format_header_spec = [self._line.strip() 
     123                                    for self._line 
     124                                    in self._blocks[0].split('\n') 
     125                                    if self._line.strip()] 
     126         
     127        # The second block is the body of the format header. 
     128        # Divide it into lines. 
    117129        self._file_header_body = [self._line.strip() 
    118                                   for self._line in self._blocks[1].split('\n') 
     130                                  for self._line 
     131                                  in self._blocks[1].split('\n') 
    119132                                  if self._line.strip()] 
     133         
     134        # All the remaing blocks are individual profiles 
     135        # in chronological order. 
    120136        self.extend([Profile([self._line.strip() 
    121                               for self._line in self._block.split('\n') 
     137                              for self._line 
     138                              in self._block.split('\n') 
    122139                              if self._line.strip()]) 
    123                      for self._block in self._blocks[2:]]) 
     140                     for self._block 
     141                     in self._blocks[2:]]) 
     142         
     143        # No need to haul around bookkeeping storage used for initialization. 
    124144        del self._blocks, self._block, self._line 
    125145 
     
    127147    """ 
    128148    Contain data for single profile from a Scintec sodar .mnd file. 
     149     
     150    The data is contained as a list of Observation objects 
     151    along with some attributes which apply to all the Profile objects. 
    129152     
    130153    Parse a known good profile block: 
     
    132155    >>> profile.timestamp 
    133156    '2009-11-17 00:30:00 00:30:00' 
    134     >>> profile.variables == ['z', 'speed', 'dir', 'W', 
    135     ...                       'sigW', 'bck', 'error'] 
    136     True 
     157    >>> profile.variables 
     158    ['z', 'speed', 'dir', 'W', 'sigW', 'bck', 'error'] 
    137159    >>> profile[0] == {'z':'10', 'speed':'99.99', 'dir':'999.9', 
    138160    ...                'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', 
     
    160182        >>> profile.timestamp 
    161183        '2009-11-17 00:30:00 00:30:00' 
    162         >>> profile.variables == ['z', 'speed', 'dir', 'W', 
    163         ...                       'sigW', 'bck', 'error'] 
    164         True 
     184        >>> profile.variables 
     185        ['z', 'speed', 'dir', 'W', 'sigW', 'bck', 'error'] 
    165186        >>> profile[0] == {'z':'10', 'speed':'99.99', 'dir':'999.9', 
    166187        ...                'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', 
     
    174195         
    175196        super(Profile, self).__init__() 
    176          
     197 
     198        # The first line in the profile block is the timestamp.        
    177199        self.timestamp = profile_block[0] 
     200         
     201        # The second line in the profile block is 
     202        # a commented list of variable names 
    178203        self.variables = profile_block[1].split()[1:] 
     204         
     205        # All the remaining lines in the profile block are 
     206        # individual obervation columns in the same order 
     207        # as the variable names. 
    179208        self.extend([Observation(observation.split(),self.variables) 
    180209                     for observation in profile_block[2:]]) 
     
    183212    """ 
    184213    Contain data for single observation from a Scintec sodar .mnd files. 
     214     
     215    The data is contained as a dictionary of variable name/value pairs. 
     216     
     217    The variable values may also be accessed as attributes of the instance. 
    185218     
    186219    Parse a known good observation: 
     
    190223    ...                 'error':'0'} 
    191224    True 
     225    >>> observation.z 
     226    '10' 
     227    >>> observation.speed 
     228    '99.99' 
     229    >>> observation.dir 
     230    '999.9' 
     231    >>> observation.W 
     232    '-0.05' 
     233    >>> observation.sigW 
     234    '0.40' 
     235    >>> observation.bck 
     236    '5.46E+03' 
     237    >>> observation.error 
     238    '0' 
    192239    """ 
    193240     
     
    210257        ...                 'error':'0'} 
    211258        True 
    212         """ 
    213          
    214         super(Observation, self).__init__() 
    215          
    216         self.update(dict(zip(variables,data))) 
     259        >>> extra_variables = good_variables + ['extra'] 
     260        >>> observation = Observation(good_observation,extra_variables) 
     261        Traceback (most recent call last): 
     262            ... 
     263        AttributeError: Same number of attributes and values required in 
     264        'Observation' object 
     265        >>> extra_observation = good_observation + ['extra'] 
     266        >>> observation = Observation(extra_observation,good_variables) 
     267        Traceback (most recent call last): 
     268            ... 
     269        AttributeError: Same number of attributes and values required in 
     270        'Observation' object 
     271        """ 
     272         
     273        super(self.__class__, self).__init__() 
     274         
     275        if len(variables) == len(data): 
     276            # And update the dictionary with the variable names/values. 
     277            super(self.__class__, self).update(dict(zip(variables,data))) 
     278        else: 
     279            # Unless there are an unequal number of variable names/values. 
     280            raise AttributeError, ' '.join(['Same number of attributes', 
     281                                            'and values required in', 
     282                                            repr(self.__class__.__name__), 
     283                                            'object',]) 
    217284     
    218285    def __getattr__(self,name): 
    219286        """ 
    220         """ 
    221          
     287        Get a variable value by name. 
     288         
     289        observation_instance.variable_name -> variable_value 
     290         
     291        >>> observation = Observation(good_observation,good_variables) 
     292        >>> observation.z 
     293        '10' 
     294        >>> observation.speed 
     295        '99.99' 
     296        >>> observation.dir 
     297        '999.9' 
     298        >>> observation.W 
     299        '-0.05' 
     300        >>> observation.sigW 
     301        '0.40' 
     302        >>> observation.bck 
     303        '5.46E+03' 
     304        >>> observation.error 
     305        '0' 
     306        >>> observation.y 
     307        Traceback (most recent call last): 
     308            ... 
     309        AttributeError: 'Observation' object has no attribute 'y' 
     310        >>> observation.y = 'OK' 
     311        >>> observation.y 
     312        'OK' 
     313        >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9', 
     314        ...                 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', 
     315        ...                 'error':'0'} 
     316        True 
     317        """ 
     318         
     319        # Attempt attribute access by dictionary key. 
    222320        if name in self: 
    223321            return self[name] 
     
    225323            raise AttributeError, ' '.join([repr(self.__class__.__name__), 
    226324                                            'object has no attribute', 
    227                                             repr(name)]) 
     325                                            repr(name),]) 
     326     
     327    def __setattr__(self,name,value): 
     328        """ 
     329        Protect variable values from overwriting. 
     330         
     331        observation_instance.variable_name = variable_value 
     332         
     333        But retrieve any other bound attribute values. 
     334 
     335        >>> observation = Observation(good_observation,good_variables) 
     336        >>> observation.z = 'OK' 
     337        Traceback (most recent call last): 
     338            ... 
     339        AttributeError: Rebinding attribute 'z' disallowed in 
     340        'Observation' object 
     341        >>> observation.y = 'OK' 
     342        >>> observation.y 
     343        'OK' 
     344        >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9', 
     345        ...                 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', 
     346        ...                 'error':'0'} 
     347        True 
     348        """ 
     349         
     350        if name in self: 
     351            # Protect observation variables 
     352            raise AttributeError, ' '.join(['Rebinding attribute', 
     353                                            repr(name), 
     354                                            'disallowed in', 
     355                                            repr(self.__class__.__name__), 
     356                                            'object',]) 
     357        else: 
     358            # Otherwise, do it the normal way. 
     359            super(self.__class__, self).__setattr__(name,value) 
     360     
     361    def __delattr__(self,name): 
     362        """ 
     363        Protect variable values from deletion. 
     364         
     365        del observation_instance.variable_name 
     366         
     367        But delete any other bound attribute values. 
     368 
     369        >>> observation = Observation(good_observation,good_variables) 
     370        >>> del observation.z 
     371        Traceback (most recent call last): 
     372            ... 
     373        AttributeError: Unbinding attribute 'z' disallowed in 
     374        'Observation' object 
     375        >>> del observation.y 
     376        Traceback (most recent call last): 
     377            ... 
     378        AttributeError: 'Observation' object has no attribute 'y' 
     379        >>> observation.y = 'OK' 
     380        >>> del observation.y 
     381        >>> del observation.y 
     382        Traceback (most recent call last): 
     383            ... 
     384        AttributeError: y 
     385        >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9', 
     386        ...                 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', 
     387        ...                 'error':'0'} 
     388        True 
     389        """ 
     390         
     391        if name in self: 
     392            # Protect observation variables 
     393            raise AttributeError, ' '.join(['Unbinding attribute', 
     394                                            repr(name), 
     395                                            'disallowed in', 
     396                                            repr(self.__class__.__name__), 
     397                                            'object',]) 
     398        else: 
     399            # Otherwise, do it the normal way. 
     400            super(self.__class__, self).__delattr__(name) 
     401     
     402    def __setitem__(self,key,value): 
     403        """ 
     404        Protect items. 
     405         
     406        observation_instance[variable_name] = variable_value 
     407         
     408        >>> observation = Observation(good_observation,good_variables) 
     409        >>> observation['z'] = 'OK' 
     410        Traceback (most recent call last): 
     411            ... 
     412        TypeError: 'Observation' object's items are read-only 
     413        >>> observation['y'] = 'OK' 
     414        Traceback (most recent call last): 
     415            ... 
     416        TypeError: 'Observation' object's items are read-only 
     417        >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9', 
     418        ...                 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', 
     419        ...                 'error':'0'} 
     420        True 
     421        """ 
     422         
     423        raise TypeError, ' '.join([repr(self.__class__.__name__), 
     424                                   'object\'s items are read-only',]) 
     425     
     426    def __delitem__(self,key): 
     427        """ 
     428        Protect items. 
     429         
     430        observation_instance[variable_name] = variable_value 
     431         
     432        >>> observation = Observation(good_observation,good_variables) 
     433        >>> del observation['z'] 
     434        Traceback (most recent call last): 
     435            ... 
     436        TypeError: 'Observation' object's items are read-only 
     437        >>> del observation['y'] 
     438        Traceback (most recent call last): 
     439            ... 
     440        TypeError: 'Observation' object's items are read-only 
     441        >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9', 
     442        ...                 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', 
     443        ...                 'error':'0'} 
     444        True 
     445        """ 
     446         
     447        raise TypeError, ' '.join([repr(self.__class__.__name__), 
     448                                   'object\'s items are read-only',]) 
     449     
     450    def clear(self): 
     451        """ 
     452        Protect items. 
     453         
     454        >>> observation = Observation(good_observation,good_variables) 
     455        >>> observation.clear() 
     456        Traceback (most recent call last): 
     457            ... 
     458        TypeError: 'Observation' object's items are read-only 
     459        >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9', 
     460        ...                 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', 
     461        ...                 'error':'0'} 
     462        True 
     463        """ 
     464         
     465        raise TypeError, ' '.join([repr(self.__class__.__name__), 
     466                                   'object\'s items are read-only',]) 
     467     
     468    def pop(self,key,*args): 
     469        """ 
     470        Protect items. 
     471         
     472        >>> observation = Observation(good_observation,good_variables) 
     473        >>> observation.pop('z') 
     474        Traceback (most recent call last): 
     475            ... 
     476        TypeError: 'Observation' object's items are read-only 
     477        >>> observation.pop('y') 
     478        Traceback (most recent call last): 
     479            ... 
     480        TypeError: 'Observation' object's items are read-only 
     481        >>> observation.pop('y','OK') 
     482        Traceback (most recent call last): 
     483            ... 
     484        TypeError: 'Observation' object's items are read-only 
     485        >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9', 
     486        ...                 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', 
     487        ...                 'error':'0'} 
     488        True 
     489        """ 
     490         
     491        raise TypeError, ' '.join([repr(self.__class__.__name__), 
     492                                   'object\'s items are read-only',]) 
     493     
     494    def popitem(self): 
     495        """ 
     496        Protect items. 
     497         
     498        >>> observation = Observation(good_observation,good_variables) 
     499        >>> observation.popitem() 
     500        Traceback (most recent call last): 
     501            ... 
     502        TypeError: 'Observation' object's items are read-only 
     503        >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9', 
     504        ...                 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', 
     505        ...                 'error':'0'} 
     506        True 
     507        """ 
     508         
     509        raise TypeError, ' '.join([repr(self.__class__.__name__), 
     510                                   'object\'s items are read-only',]) 
     511     
     512    def setdefault(self,key,*args): 
     513        """ 
     514        Protect items. 
     515         
     516        >>> observation = Observation(good_observation,good_variables) 
     517        >>> observation.setdefault('z') 
     518        Traceback (most recent call last): 
     519            ... 
     520        TypeError: 'Observation' object's items are read-only 
     521        >>> observation.setdefault('y') 
     522        Traceback (most recent call last): 
     523            ... 
     524        TypeError: 'Observation' object's items are read-only 
     525        >>> observation.setdefault('y','OK') 
     526        Traceback (most recent call last): 
     527            ... 
     528        TypeError: 'Observation' object's items are read-only 
     529        >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9', 
     530        ...                 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', 
     531        ...                 'error':'0'} 
     532        True 
     533        """ 
     534         
     535        raise TypeError, ' '.join([repr(self.__class__.__name__), 
     536                                   'object\'s items are read-only',]) 
     537     
     538    def update(self,*args,**kwargs): 
     539        """ 
     540        Protect items. 
     541         
     542        >>> observation = Observation(good_observation,good_variables) 
     543        >>> observation.update({'UP':'up','DOWN':'down',}) 
     544        Traceback (most recent call last): 
     545            ... 
     546        TypeError: 'Observation' object's items are read-only 
     547        >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9', 
     548        ...                 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', 
     549        ...                 'error':'0'} 
     550        True 
     551        >>> observation.update((('UP','up'),('DOWN','down'),)) 
     552        Traceback (most recent call last): 
     553            ... 
     554        TypeError: 'Observation' object's items are read-only 
     555        >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9', 
     556        ...                 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', 
     557        ...                 'error':'0'} 
     558        True 
     559        >>> observation.update(UP='up',DOWN='down') 
     560        Traceback (most recent call last): 
     561            ... 
     562        TypeError: 'Observation' object's items are read-only 
     563        >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9', 
     564        ...                 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', 
     565        ...                 'error':'0'} 
     566        True 
     567        """ 
     568         
     569        raise TypeError, ' '.join([repr(self.__class__.__name__), 
     570                                   'object\'s items are read-only',]) 
    228571 
    229572def _test():