""" Module to handle Scintec sodar .mnd files. >>> from sodar.scintec import maindata """ __author__ = 'Chris Calloway' __email__ = 'cbc@chriscalloway.org' __copyright__ = 'Copyright 2009 UNC-CH Department of Marine Science' __license__ = 'GPL2' from datetime import datetime, timedelta class MainData(list): """ Contain data from a Scintec sodar .mnd file. The data is contained as a list of Profile objects along with some attributes which apply to all the Profile objects. Parse a known good .mnd file: >>> main_data = MainData(good_mnd) Parse the profile data: >>> len(main_data) 48 Parse the first profile: >>> len(main_data[0]) 39 >>> main_data[0].start datetime.datetime(2009, 11, 17, 0, 0) >>> main_data[0].stop datetime.datetime(2009, 11, 17, 0, 30) >>> main_data[0].variables ['z', 'speed', 'dir', 'W', 'sigW', 'bck', 'error'] >>> main_data[0][0] == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True >>> main_data[0][0].z '10' >>> main_data[0]['10'] == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True >>> main_data[0]['10'].speed '99.99' >>> main_data[0][-1] == {'z':'200', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.07', 'sigW':'99.99', 'bck':'9.99E+37', ... 'error':'0'} True >>> main_data[0][-1].dir '999.9' >>> main_data[0]['200'] == {'z':'200', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.07', 'sigW':'99.99', 'bck':'9.99E+37', ... 'error':'0'} True >>> main_data[0]['200'].W '-0.07' Parse the last profile: >>> len(main_data[-1]) 39 >>> main_data[-1].start datetime.datetime(2009, 11, 17, 23, 30) >>> main_data[-1].stop datetime.datetime(2009, 11, 18, 0, 0) >>> main_data[-1].variables ['z', 'speed', 'dir', 'W', 'sigW', 'bck', 'error'] >>> main_data[-1][0] == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.32', 'sigW':'99.99', 'bck':'9.99E+37', ... 'error':'0'} True >>> main_data[-1][0].z '10' >>> main_data[-1]['10'] == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.32', 'sigW':'99.99', 'bck':'9.99E+37', ... 'error':'0'} True >>> main_data[-1]['10'].speed '99.99' >>> main_data[-1][-1] == {'z':'200', 'speed':'15.05', 'dir':'71.8', ... 'W':'-0.19', 'sigW':'0.53', 'bck':'9.99E+37', ... 'error':'0'} True >>> main_data[-1][-1].dir '71.8' >>> main_data[-1]['200'] == {'z':'200', 'speed':'15.05', 'dir':'71.8', ... 'W':'-0.19', 'sigW':'0.53', 'bck':'9.99E+37', ... 'error':'0'} True >>> main_data[-1]['200'].W '-0.19' """ def __init__(self, mnd, *args): """ Parse main daily Scintec sodar .mnd file. MainData(mnd[,file_name[,file_path]]) -> Where: mnd is a str object containing the complete contents read from a Scintec .mnd daily sodar file including all line endings, file_name is an optional str object representing a file name for a file which contains the referenced .mnd daily sodar file, file_path is an optional str object representing the path to file_name. Parse a known good .mnd file: >>> main_data = MainData(good_mnd) >>> main_data = MainData(good_mnd,good_name) >>> main_data.file_name == good_name True >>> main_data = MainData(good_mnd,good_name,good_path) >>> main_data.file_name == good_name True >>> main_data.file_path == good_path True """ super(self.__class__, self).__init__() self.file_name = '' self.file_path = '' # Optional args: smoke 'em if ya got 'em. try: self.file_name = str(args[0]) self.file_path = str(args[1]) except IndexError: pass # Divide the data into blocks # across boundaries separated by blank lines. blocks = [block.strip() for block in mnd.split('\n\n') if block.strip()] # The first block is the specification of the format header. # Divide it into lines. format_header_spec = [line.strip() for line in blocks[0].split('\n') if line.strip()] # The second block is the body of the format header. # Divide it into lines. file_header_body = [line.strip() for line in blocks[1].split('\n') if line.strip()] # All the remaing blocks are individual profiles # in chronological order. self.extend([Profile([line.strip() for line in block.split('\n') if line.strip()]) for block in blocks[2:]]) def __call__(self,timestamp,start=False): """ Get a profile by timestamp maindata_instance(timestamp[,start]) -> Where: timestamp is a datetime.datetime object representing either the start or stop timestamp of the Profile object returned. start is a bool object. If start is False (default), then timestamp is matched to the stop timestamp of the returned Profile object. Otherwise, if start is True, then timestamp is matched to the start timestamp of the returned Profile object. Access profiles by timestamp: >>> main_data = MainData(good_mnd) >>> main_data(datetime(2009, 11, 17, 0, 30)).stop datetime.datetime(2009, 11, 17, 0, 30) >>> main_data(datetime(2009, 11, 17, 0, 0),True).start datetime.datetime(2009, 11, 17, 0, 0) >>> main_data(datetime(2009, 11, 18, 0, 0)).stop datetime.datetime(2009, 11, 18, 0, 0) >>> main_data(datetime(2009, 11, 17, 23, 0),True).start datetime.datetime(2009, 11, 17, 23, 0) >>> main_data(datetime(2009, 11, 16, 0, 30)) Traceback (most recent call last): ... IndexError: 'MainData' object index out of range >>> main_data('OK') Traceback (most recent call last): ... TypeError: 'MainData' object indices must be datetime.datetime instances """ for profile in self: if start: boundary = profile.start else: boundary = profile.stop if boundary == timestamp: return profile if isinstance(timestamp,datetime): raise IndexError, ' '.join([repr(self.__class__.__name__), 'object index out of range',]) else: raise TypeError, ' '.join([repr(self.__class__.__name__), 'object indices must be', 'datetime.datetime instances',]) class Profile(list): """ Contain data for single profile from a Scintec sodar .mnd file. The data is contained as a list of Observation objects along with some attributes which apply to all the Profile objects. Parse a known good profile block: >>> profile = Profile(good_profile) Access the timestamp attributes: >>> profile.start datetime.datetime(2009, 11, 17, 0, 0) >>> profile.stop datetime.datetime(2009, 11, 17, 0, 30) Access the variable list: >>> profile.variables ['z', 'speed', 'dir', 'W', 'sigW', 'bck', 'error'] Access observations by sequence number and variables by attribute name: >>> profile[0] == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True >>> profile[0].z '10' >>> profile[-1] == {'z':'200', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.07', 'sigW':'99.99', 'bck':'9.99E+37', ... 'error':'0'} True >>> profile[-1].speed '99.99' Access observations by elevation and variables by attribute name: >>> profile['10'] == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True >>> profile['10'].dir '999.9' >>> profile['200'] == {'z':'200', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.07', 'sigW':'99.99', 'bck':'9.99E+37', ... 'error':'0'} True >>> profile['200'].W '-0.07' """ def __init__(self, profile_block): """ Parse a profile block from a main daily Scintec sodar .mnd file. Profile(profile_data) -> Where: profile_block is a list of str objects containing all the lines from a single profile in a Scintec .mnd daily sodar file. Parse a known good profile block: >>> profile = Profile(good_profile) Access the timestamp attributes: >>> profile.start datetime.datetime(2009, 11, 17, 0, 0) >>> profile.stop datetime.datetime(2009, 11, 17, 0, 30) Access the variable list: >>> profile.variables ['z', 'speed', 'dir', 'W', 'sigW', 'bck', 'error'] Access observations by sequence number and variables by attribute name: >>> profile[0] == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True >>> profile[0].z '10' >>> profile[-1] == {'z':'200', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.07', 'sigW':'99.99', 'bck':'9.99E+37', ... 'error':'0'} True >>> profile[-1].speed '99.99' """ super(self.__class__, self).__init__() # The first line in the profile block is the timestamp. timestamp = profile_block[0] date, stop, duration = timestamp.split() date_year, date_month, date_day = date.split('-') stop_hour, stop_minute, stop_second = stop.split(':') duration_hour, duration_minute, duration_second = duration.split(':') interval = timedelta(0, # days int(duration_second), 0, # microseconds 0, # milliseconds int(duration_minute), int(duration_hour)) # omit optional weeks self.stop = datetime(int(date_year), int(date_month), int(date_day), int(stop_hour), int(stop_minute), # omit optional microseconds int(stop_second)) # and tzinfo self.start = self.stop - interval # The second line in the profile block is # a commented list of variable names self.variables = profile_block[1].split()[1:] # All the remaining lines in the profile block are # individual obervation columns in the same order # as the variable names. super(self.__class__, self).extend([Observation(observation.split(), self.variables) for observation in profile_block[2:]]) def __getitem__(self,key): """ Get a observation by elevation. profile_instance[elevation] -> Where: elevation is a str object object representing the elevation (value keyed by 'z') of the Observation object. Access observations by elevation and variables by attribute name: >>> profile = Profile(good_profile) >>> profile['10'] == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True >>> profile['10'].dir '999.9' >>> profile['200'] == {'z':'200', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.07', 'sigW':'99.99', 'bck':'9.99E+37', ... 'error':'0'} True >>> profile['200'].W '-0.07' >>> profile[100000] Traceback (most recent call last): ... IndexError: list index out of range >>> profile['100000'] Traceback (most recent call last): ... TypeError: list indices must be integers """ try: return super(self.__class__, self).__getitem__(key) except TypeError: for observation in self: if observation['z'] == key: return observation return super(self.__class__, self).__getitem__(key) def __setitem__(self,key,value): """ Protect items. profile_instance[object1] = object2 Raise an Exception if attempting to mutate a profile: >>> profile = Profile(good_profile) >>> profile[0] = 'OK' Traceback (most recent call last): ... TypeError: 'Profile' instance's items are read-only >>> profile['10'] = 'OK' Traceback (most recent call last): ... TypeError: 'Profile' instance's items are read-only >>> profile[100000] = 'OK' Traceback (most recent call last): ... TypeError: 'Profile' instance's items are read-only >>> profile[100000] Traceback (most recent call last): ... IndexError: list index out of range >>> profile['100000'] = 'OK' Traceback (most recent call last): ... TypeError: 'Profile' instance's items are read-only >>> profile['100000'] Traceback (most recent call last): ... TypeError: list indices must be integers Without affecting the observation: >>> profile[0] == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True >>> profile['10'] == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True """ raise TypeError, ' '.join([repr(self.__class__.__name__), 'instance\'s items are read-only',]) def __delitem__(self,key): """ Protect items. del profile_instance[object] Raise an Exception if attempting to mutate a profile: >>> profile = Profile(good_profile) >>> del profile[0] Traceback (most recent call last): ... TypeError: 'Profile' instance's items are read-only >>> del profile['10'] Traceback (most recent call last): ... TypeError: 'Profile' instance's items are read-only >>> del profile[100000] Traceback (most recent call last): ... TypeError: 'Profile' instance's items are read-only >>> del profile['100000'] Traceback (most recent call last): ... TypeError: 'Profile' instance's items are read-only Without affecting the observation: >>> profile[0] == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True >>> profile['10'] == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True """ raise TypeError, ' '.join([repr(self.__class__.__name__), 'instance\'s items are read-only',]) def __setslice__(self,i,j,seq): """ Protect items. profile_instance[i:j] = seq Raise an Exception if attempting to mutate a profile: >>> profile = Profile(good_profile) >>> profile[0:10] = 'OK' Traceback (most recent call last): ... TypeError: 'Profile' instance's items are read-only Without affecting the observations: >>> profile[0] == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True >>> profile['10'] == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True """ raise TypeError, ' '.join([repr(self.__class__.__name__), 'instance\'s items are read-only',]) def __delslice__(self,i,j): """ Protect items. del profile_instance[i:j] Raise an Exception if attempting to mutate a profile: >>> profile = Profile(good_profile) >>> del profile[0:10] Traceback (most recent call last): ... TypeError: 'Profile' instance's items are read-only Without affecting the observations: >>> profile[0] == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True >>> profile['10'] == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True """ raise TypeError, ' '.join([repr(self.__class__.__name__), 'instance\'s items are read-only',]) def append(self,item): """ Protect items. Raise an Exception if attempting to mutate a profile: >>> profile = Profile(good_profile) >>> profile.append('OK') Traceback (most recent call last): ... TypeError: 'Profile' instance's items are read-only Without affecting the observations: >>> profile[0] == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True """ raise TypeError, ' '.join([repr(self.__class__.__name__), 'instance\'s items are read-only',]) def extend(self,seq): """ Protect items. Raise an Exception if attempting to mutate a profile: >>> profile = Profile(good_profile) >>> profile.extend('OK') Traceback (most recent call last): ... TypeError: 'Profile' instance's items are read-only Without affecting the observations: >>> profile[0] == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True """ raise TypeError, ' '.join([repr(self.__class__.__name__), 'instance\'s items are read-only',]) def insert(self,i,item): """ Protect items. Raise an Exception if attempting to mutate a profile: >>> profile = Profile(good_profile) >>> profile.insert(0,'OK') Traceback (most recent call last): ... TypeError: 'Profile' instance's items are read-only Without affecting the observations: >>> profile[0] == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True """ raise TypeError, ' '.join([repr(self.__class__.__name__), 'instance\'s items are read-only',]) def pop(self,*args): """ Protect items. Raise an Exception if attempting to mutate a profile: >>> profile = Profile(good_profile) >>> profile.pop() Traceback (most recent call last): ... TypeError: 'Profile' instance's items are read-only >>> profile.pop(0) Traceback (most recent call last): ... TypeError: 'Profile' instance's items are read-only Without affecting the observations: >>> profile[0] == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True >>> profile[-1] == {'z':'200', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.07', 'sigW':'99.99', 'bck':'9.99E+37', ... 'error':'0'} True """ raise TypeError, ' '.join([repr(self.__class__.__name__), 'instance\'s items are read-only',]) def remove(self,i): """ Protect items. Raise an Exception if attempting to mutate a profile: >>> profile = Profile(good_profile) >>> observation = profile[0] >>> profile.remove(observation) Traceback (most recent call last): ... TypeError: 'Profile' instance's items are read-only Without affecting the observations: >>> profile[0] == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True """ raise TypeError, ' '.join([repr(self.__class__.__name__), 'instance\'s items are read-only',]) def reverse(self): """ Protect items. Raise an Exception if attempting to mutate a profile: >>> profile = Profile(good_profile) >>> profile.reverse() Traceback (most recent call last): ... TypeError: 'Profile' instance's items are read-only Without affecting the observations: >>> profile[0] == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True """ raise TypeError, ' '.join([repr(self.__class__.__name__), 'instance\'s items are read-only',]) def sort(self,**kwargs): """ Protect items. Raise an Exception if attempting to mutate a profile: >>> profile = Profile(good_profile) >>> profile.sort() Traceback (most recent call last): ... TypeError: 'Profile' instance's items are read-only >>> profile.sort(cmp=lambda x,y: cmp(x.lower(), y.lower())) Traceback (most recent call last): ... TypeError: 'Profile' instance's items are read-only >>> profile.sort(cmp=lambda x,y: cmp(x.lower(), y.lower()), ... key=str.lower) Traceback (most recent call last): ... TypeError: 'Profile' instance's items are read-only >>> profile.sort(cmp=lambda x,y: cmp(x.lower(), y.lower()), ... key=str.lower, ... reverse=True) Traceback (most recent call last): ... TypeError: 'Profile' instance's items are read-only Without affecting the observations: >>> profile[0] == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True """ raise TypeError, ' '.join([repr(self.__class__.__name__), 'instance\'s items are read-only',]) class Observation(dict): """ Contain data for single observation from a Scintec sodar .mnd files. The data is contained as a dictionary of variable name/value pairs. The variable values may also be accessed as attributes of the instance. Parse a known good observation: >>> observation = Observation(good_observation,good_variables) >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True Access observation variables as attributes: >>> observation.z '10' >>> observation.speed '99.99' >>> observation.dir '999.9' >>> observation.W '-0.05' >>> observation.sigW '0.40' >>> observation.bck '5.46E+03' >>> observation.error '0' """ def __init__(self,data,variables): """ Parse an observation from a main daily Scintec sodar .mnd file. Observation(data,variables) -> Where: data is a list of str object representing variable values, variables is a list of str objects representing variable labels. Parse a known good observation: >>> observation = Observation(good_observation,good_variables) >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True Raise an Exception if more variable names than values: >>> extra_variables = good_variables + ['extra'] >>> observation = Observation(good_observation,extra_variables) Traceback (most recent call last): ... AttributeError: Same number of attributes and values required in 'Observation' object Raise an Exception if more variable values than names: >>> extra_observation = good_observation + ['extra'] >>> observation = Observation(extra_observation,good_variables) Traceback (most recent call last): ... AttributeError: Same number of attributes and values required in 'Observation' object """ super(self.__class__, self).__init__() if len(variables) == len(data): # And update the dictionary with the variable names/values. super(self.__class__, self).update(dict(zip(variables,data))) else: # Unless there are an unequal number of variable names/values. raise AttributeError, ' '.join(['Same number of attributes', 'and values required in', repr(self.__class__.__name__), 'object',]) def __getattr__(self,name): """ Get a variable value by name. observation_instance.variable_name -> variable_value Access observation variables as attributes: >>> observation = Observation(good_observation,good_variables) >>> observation.z '10' >>> observation.speed '99.99' >>> observation.dir '999.9' >>> observation.W '-0.05' >>> observation.sigW '0.40' >>> observation.bck '5.46E+03' >>> observation.error '0' Raise an Exception for an undefined attribute: >>> observation.y Traceback (most recent call last): ... AttributeError: 'Observation' object has no attribute 'y' Allow the creation and access of attributes other than variables. >>> observation.y = 'OK' >>> observation.y 'OK' Without affecting the variables: >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True """ # Attempt attribute access by dictionary key. if name in self: return self[name] else: raise AttributeError, ' '.join([repr(self.__class__.__name__), 'object has no attribute', repr(name),]) def __setattr__(self,name,value): """ Protect variable values from overwriting. observation_instance.variable_name = variable_value But retrieve any other bound attribute values. Raise an Exception if attempting to rebind a variable: >>> observation = Observation(good_observation,good_variables) >>> observation.z = 'OK' Traceback (most recent call last): ... AttributeError: Rebinding attribute 'z' disallowed in 'Observation' object However, allow attributes other than variables to be bound: >>> observation.y = 'OK' >>> observation.y 'OK' Without affecting the variables: >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True """ if name in self: # Protect observation variables raise AttributeError, ' '.join(['Rebinding attribute', repr(name), 'disallowed in', repr(self.__class__.__name__), 'object',]) else: # Otherwise, do it the normal way. super(self.__class__, self).__setattr__(name,value) def __delattr__(self,name): """ Protect variable values from deletion. del observation_instance.variable_name But delete any other bound attribute values. Raise an Exception if attempting to unbind a variable: >>> observation = Observation(good_observation,good_variables) >>> del observation.z Traceback (most recent call last): ... AttributeError: Unbinding attribute 'z' disallowed in 'Observation' object Raise an Exception for attempting to unbind an undefined attribute: >>> del observation.y Traceback (most recent call last): ... AttributeError: 'Observation' object has no attribute 'y' However, allow attributes other than variables to be unbound: >>> observation.y = 'OK' >>> del observation.y >>> del observation.y Traceback (most recent call last): ... AttributeError: y Without affecting the variables: >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True """ if name in self: # Protect observation variables raise AttributeError, ' '.join(['Unbinding attribute', repr(name), 'disallowed in', repr(self.__class__.__name__), 'object',]) else: # Otherwise, do it the normal way. super(self.__class__, self).__delattr__(name) def __setitem__(self,key,value): """ Protect items. observation_instance[variable_name] = variable_value Raise an Exception if attempting to mutate an observation: >>> observation = Observation(good_observation,good_variables) >>> observation['z'] = 'OK' Traceback (most recent call last): ... TypeError: 'Observation' instance's items are read-only >>> observation['y'] = 'OK' Traceback (most recent call last): ... TypeError: 'Observation' instance's items are read-only >>> observation.y Traceback (most recent call last): ... AttributeError: 'Observation' object has no attribute 'y' >>> observation.y = 'OK' >>> observation['y'] = 'Not OK' Traceback (most recent call last): ... TypeError: 'Observation' instance's items are read-only >>> observation.y 'OK' Without affecting the variables: >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True """ raise TypeError, ' '.join([repr(self.__class__.__name__), 'instance\'s items are read-only',]) def __delitem__(self,key): """ Protect items. observation_instance[variable_name] = variable_value Raise an Exception if attempting to mutate an observation: >>> observation = Observation(good_observation,good_variables) >>> del observation['z'] Traceback (most recent call last): ... TypeError: 'Observation' instance's items are read-only >>> observation.y = 'OK' >>> del observation['y'] Traceback (most recent call last): ... TypeError: 'Observation' instance's items are read-only Without affecting the variables: >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True """ raise TypeError, ' '.join([repr(self.__class__.__name__), 'instance\'s items are read-only',]) def clear(self): """ Protect items. Raise an Exception if attempting to mutate an observation: >>> observation = Observation(good_observation,good_variables) >>> observation.clear() Traceback (most recent call last): ... TypeError: 'Observation' instance's items are read-only Without affecting the variables: >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True """ raise TypeError, ' '.join([repr(self.__class__.__name__), 'instance\'s items are read-only',]) def pop(self,key,*args): """ Protect items. Raise an Exception if attempting to mutate an observation: >>> observation = Observation(good_observation,good_variables) >>> observation.pop('z') Traceback (most recent call last): ... TypeError: 'Observation' instance's items are read-only >>> observation.pop('y') Traceback (most recent call last): ... TypeError: 'Observation' instance's items are read-only >>> observation.pop('y','OK') Traceback (most recent call last): ... TypeError: 'Observation' instance's items are read-only Without affecting the variables: >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True """ raise TypeError, ' '.join([repr(self.__class__.__name__), 'instance\'s items are read-only',]) def popitem(self): """ Protect items. Raise an Exception if attempting to mutate an observation: >>> observation = Observation(good_observation,good_variables) >>> observation.popitem() Traceback (most recent call last): ... TypeError: 'Observation' instance's items are read-only Without affecting the variables: >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True """ raise TypeError, ' '.join([repr(self.__class__.__name__), 'instance\'s items are read-only',]) def setdefault(self,key,*args): """ Protect items. Raise an Exception if attempting to mutate an observation: >>> observation = Observation(good_observation,good_variables) >>> observation.setdefault('z') Traceback (most recent call last): ... TypeError: 'Observation' instance's items are read-only >>> observation.setdefault('y') Traceback (most recent call last): ... TypeError: 'Observation' instance's items are read-only >>> observation.setdefault('y','OK') Traceback (most recent call last): ... TypeError: 'Observation' instance's items are read-only Without affecting the variables: >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True """ raise TypeError, ' '.join([repr(self.__class__.__name__), 'instance\'s items are read-only',]) def update(self,*args,**kwargs): """ Protect items. Raise an Exception if attempting to mutate an observation: >>> observation = Observation(good_observation,good_variables) >>> observation.update({'UP':'up','DOWN':'down',}) Traceback (most recent call last): ... TypeError: 'Observation' instance's items are read-only Without affecting the variables: >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True Raise an Exception if attempting to mutate an observation: >>> observation.update((('UP','up'),('DOWN','down'),)) Traceback (most recent call last): ... TypeError: 'Observation' instance's items are read-only Without affecting the variables: >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True Raise an Exception if attempting to mutate an observation: >>> observation.update(UP='up',DOWN='down') Traceback (most recent call last): ... TypeError: 'Observation' instance's items are read-only Without affecting the variables: >>> observation == {'z':'10', 'speed':'99.99', 'dir':'999.9', ... 'W':'-0.05', 'sigW':'0.40', 'bck':'5.46E+03', ... 'error':'0'} True """ raise TypeError, ' '.join([repr(self.__class__.__name__), 'instance\'s items are read-only',]) def _test(): """ Run module tests in script mode. >>> from sodar.scintec.maindata import _test """ import doctest from sodar.tests import suite mnd_path,mnd_file,mnd,profile,variables,observation = \ suite.setUpGoodMndData() doctest.testmod(extraglobs=dict(good_mnd=mnd, good_name=mnd_file, good_path=mnd_path, good_profile=profile, good_variables=variables, good_observation=observation), optionflags=doctest.NORMALIZE_WHITESPACE) if __name__ == "__main__": _test()