"""
Summary:
Contains the ADataObject class and all of its subclasses.
These classes can be used to hold used by the :class:'<RowDataCollection>'.
They provide a range of methods for easily acessing, amending and retrieving
values to print to file. Classes include support for:
# Numeric data.
# String data.
# Symbol data.
# Constant data (a tuple of legal values).
Author:
Duncan Runnacles
Created:
01 Apr 2016
Copyright:
Duncan Runnacles 2016
TODO:
Updates:
"""
from __future__ import unicode_literals
from abc import ABCMeta, abstractmethod
import logging
logger = logging.getLogger(__name__)
"""logging references with a __name__ set to this module."""
[docs]class ADataRowObject(object):
"""Abstract class for all data objects used in an AUnit class.
Note:
It is unlikley that you want to call a class of this type directly.
The RowDataFactory will perform checks required when constructing one
of these objects. It should be used instead.
"""
__metaclass__ = ABCMeta
# def __init__(self, row_pos, datatype, format_str, default):
def __init__(self, datatype, format_str, **kwargs):
"""Constructor
Args:
datatype (int): The type of collection it will hold (see the enum
list in the isis.datunits.ROW_DATA_TYPES.
format_str (str): Represents the format that should be used to
print out the values kept in this data collection. This
should be in the form '{:<10}' = 10 spaces formatted left.
With the required spaces need to correctly format the value
in the file.
**kwargs:
default: The default value that the collection should use -
Can be None if defaults are not allowed or '~' if the default
should remove the formatting and apply an empty string.
update_callback: a callback function that should be run everytime
a value is updated (added or set). For example to check
that chainage is increasing.
When called it will provide the following arguments:
(self, value, index).
"""
self.data_type = datatype
self.format_str = format_str
self.default = kwargs.get('default', None)
self.update_callback = kwargs.get('update_callback', None)
self.has_changed = False
self.data_collection = []
self._min = 0
self._max = len(self.data_collection)
self._current = 0
@property
def record_length(self):
return len(self.data_collection)
def __len__(self):
return len(self.data_collection)
def __iter__(self):
"""Return an iterator for the data_collection list"""
return iter(self.data_collection)
def __next__(self):
"""Iterate to the next item in data_collection list"""
if self._current > self._max or self._current < self._min:
raise StopIteration
else:
self._current += 1
return self.data_collection[self._current]
def __getitem__(self, key):
"""Gets a value from data_collection using index notation.
Returns:
contents of the data_collection element at index.
"""
return self.data_collection[key]
def __setitem__(self, index, value):
"""Sets a value using index notation
Calls the setValue() function to do the hard work.
Args:
index (int): index to update.
value: the value to add to the data_collection.
"""
self.setValue(value, index)
[docs] def getValue(self, index):
"""Getter for retrieving the value at the supplied index.
Args:
index (int): The index of the required value.
Returns:
The value at the supplied index.
"""
return self.data_collection[index]
[docs] def getPrintableValue(self, index):
"""Getter for the formatted printable value at the supplied index.
If the default value given == '~' then all formatting should be removed
from the value if it is empty. Otherwise we call the subclass
formatPrintString() method.
Args:
index (int): The index of the required value.
Returns:
String containing the .DAT file print formatted value as defined
by the format_print_string variables set when the object was
initialised.
Raises:
IndexError: If the given index doesn't exist.
"""
try:
out_value = self.data_collection[index]
if self.default == '~' and out_value == '':
out_value = ''
elif out_value == '':
out_value = self.format_str.format('')
return out_value
elif self.format_str == None:
return str(out_value)
else:
out_value = self.formatPrintString(out_value)
except IndexError:
logger.error('DataObject addValue() index out of bounds')
raise
return out_value
[docs] def addValue(self, value=None, index=None):
"""Adds a value to the data_collection.
This values will either be appended or put at a specific index if
one is provided.
Arguments:
value: Optional - The value to be added. If no value is
provided the default value will be used.
index (int): Optional - The index at which to add the value. If no
value is given it will be appended to the end.
Raises:
IndexError: If index does not exist.
"""
if value == None: value = self.default
if self.update_callback is not None:
self.update_callback(self, value, index)
length = len(self.data_collection)
if index == None or index == length:
self.data_collection.append(value)
elif index > length:
raise IndexError
else:
try:
self.data_collection.insert(index, value)
except IndexError:
logger.error('DataObject addValue() index out of bounds')
raise IndexError ('DataObject addValue() index out of bounds')
self.has_changed = True
# self.record_length += 1
self._max = len(self.data_collection)
[docs] def setValue(self, value, index):
"""Changes the value at the given index
Args:
value: The new value to set.
index (int): The index of the value to be changed
Raises:
IndexError: If index does not exist.
"""
if self.update_callback is not None:
self.update_callback(self, value, index)
length = len(self.data_collection)
if index == None or index == length:
self.data_collection.append(value)
elif index > length:
raise IndexError
else:
try:
self.data_collection[index] = value
except IndexError:
logger.error('DataObject setValue() index out of bounds')
raise IndexError ('DataObject setValue() index out of bounds')
self.has_changed = True
[docs] def deleteValue(self, index):
"""Delete value at supplied position in unit.
Args:
index (int): index of value to be removed.
Raises:
IndexError: If index does not exist.
"""
try:
del self.data_collection[index]
except IndexError:
logger.error('DataObject deleteValue() index out of bounds')
raise IndexError ('DataObject deleteValue() index out of bounds')
self.has_changed = True
# self.record_length -= 1
self._max = len(self.data_collection)
[docs] def getDataCollection(self):
return self.data_collection
[docs] def checkDefault(self, value):
if self.default == '~' and value == self.default:
return True
else:
return False
@abstractmethod
[docs] def setChangeStatus(self, status):
if status == False:
self.has_changed = False
elif status == True:
self.has_changed = True
[docs]class IntData(ADataRowObject):
"""Concrete implememtation of the ADataRowObject for integer values.
See Also:
ADataRowObject
"""
# def __init__(self, row_pos, datatype, format_str='{}', default=None):
def __init__(self, datatype, format_str='{}', **kwargs):
"""Constructor.
Args:
datatype (int): The type of collection it will hold (see the enum
list in the isis.datunits.ROW_DATA_TYPES.
format_str={} (str): Represents the format that should be used to
print out the values kept in this data collection. This
should be in the form '{:<10}' = 10 spaces formatted left.
With the required spaces need to correctly format the value
in the file.
**kwargs:
default=None: The default value that the collection should use -
Can be None if defaults are not allowed or '~' if the default
should remove the formatting and apply an empty string.
"""
ADataRowObject.__init__(self, datatype, format_str, **kwargs)
[docs] def addValue(self, value=None, index = None):
"""Adds a value to the collection.
See Also:
ADataRowObject: addValue()
"""
if not value == None:
try:
value = int(value)
except ValueError:
logger.error('Attempted to add invalid value to IntDataObject')
raise ValueError ('Attempted to add invalid value to IntDataObject')
ADataRowObject.addValue(self, value, index)
[docs] def setValue(self, value, index):
"""Changes the value at the given index
See Also:
ADataRowObject: setValue()
"""
try:
value = int(value)
except ValueError:
logger.error('Attempted to add invalid value to IntDataObject')
raise ValueError ('Attempted to add invalid value to IntDataObject')
ADataRowObject.setValue(self, value, index)
[docs]class FloatData(ADataRowObject):
"""Overrides the value return methods from ADataObject to return a
float value instead of a string.
"""
# def __init__(self, row_pos, datatype, format_str='{}', default=None, no_of_dps=0):
def __init__(self, datatype, format_str='{}', **kwargs): #default=None, no_of_dps=0):
"""Constructor.
Args:
datatype (int): The type of collection it will hold (see the enum
list in the isis.datunits.ROW_DATA_TYPES.
format_str={} (str): Represents the format that should be used to
print out the values kept in this data collection. This
should be in the form '{:<10}' = 10 spaces formatted left.
With the required spaces need to correctly format the value
in the file.
**kwargs:
default=None: The default value that the collection should use -
Can be None if defaults are not allowed or '~' if the default
should remove the formatting and apply an empty string.
no_of_dps: int of the number of decimal places that this value
should be represented with when printed to file.
"""
self.no_of_dps = kwargs.get('no_of_dps', 0)
ADataRowObject.__init__(self, datatype, format_str, **kwargs)
[docs] def addValue(self, value=None, index = None):
"""Adds a value to the collection.
See Also:
ADataRowObject: addValue()
"""
if not value == None:
try:
value = float(value)
except ValueError:
logger.error('Attempted to add invalid value to FloatDataObject')
raise ValueError ('Attempted to add invalid value to FloatDataObject')
ADataRowObject.addValue(self, value, index)
[docs] def setValue(self, value, index):
"""Changes the value at the given index
See Also:
ADataRowObject: setValue()
"""
try:
value = float(value)
except ValueError:
logger.error('Attempted to add invalid value to FloatDataObject')
raise ValueError ('Attempted to add invalid value to FloatDataObject')
ADataRowObject.setValue(self, value, index)
[docs]class StringData(ADataRowObject):
"""Overrides the value return methods from ADataObject to return a
str value.
"""
def __init__(self, datatype, format_str='{}', **kwargs):
"""Constructor.
Args:
datatype (int): The type of collection it will hold (see the enum
list in the isis.datunits.ROW_DATA_TYPES.
format_str={} (str): Represents the format that should be used to
print out the values kept in this data collection. This
should be in the form '{:<10}' = 10 spaces formatted left.
With the required spaces need to correctly format the value
in the file.
**kwargs:
default=None: The default value that the collection should use -
Can be None if defaults are not allowed or '~' if the default
should remove the formatting and apply an empty string.
"""
ADataRowObject.__init__(self, datatype, format_str, **kwargs)
[docs] def addValue(self, value=None, index = None):
"""Adds a value to the collection.
See Also:
ADataRowObject: addValue()
"""
if not value == None:
try:
value = str(value)
except ValueError:
logger.error('Attempted to add invalid value to StringDataObject')
raise ValueError ('Attempted to add invalid value to StringDataObject')
# Strip any whitespace off
value = value.strip()
# Call the superclass part of the method to add it.
ADataRowObject.addValue(self, value, index)
[docs] def setValue(self, value, index):
"""Changes the value at the given index
See Also:
ADataRowObject: setValue()
"""
try:
value = str(value)
except ValueError:
logger.error('Attempted to add invalid value to StringDataObject')
raise ValueError('Attempted to add invalid value to StringDataObject')
# Get rid of any whitespace
value = value.strip()
# Call the superclass to set the value
ADataRowObject.setValue(self, value, index)
[docs]class ConstantData(ADataRowObject):
"""Overrides the value return methods from ADataObject to return a
str value from a list of predefined constants.
"""
def __init__(self, datatype, legal_values, format_str='{}', **kwargs):
"""Constructor.
Args:
datatype (int): The type of collection it will hold (see the enum
list in the isis.datunits.ROW_DATA_TYPES.
legal_values(tuple): contains the possible values that this
collection can have.
format_str={} (str): Represents the format that should be used to
print out the values kept in this data collection. This
should be in the form '{:<10}' = 10 spaces formatted left.
With the required spaces need to correctly format the value
in the file.
**kwargs:
default=None: The default value that the collection should use -
Can be None if defaults are not allowed or '~' if the default
should remove the formatting and apply an empty string.
Raises:
AttributeError: if legal_values in not a valid tuple.
"""
if not isinstance(legal_values, tuple):
raise AttributeError ('legal_values is not a tuple')
self.legal_values = legal_values
ADataRowObject.__init__(self, datatype, format_str, **kwargs)
[docs] def addValue(self, value=None, index = None):
"""Adds a value to the collection.
See Also:
ADataRowObject: addValue()
"""
if value is None:
if self.default is not None:
value = self.default
else:
raise ValueError('value cannot be None when no default is set.')
elif value is not None:
if not value in self.legal_values:
if self.default is not None:
value = self.default
else:
raise ValueError('value %s is not included in %s' % (str(value), str(self.legal_values)))
# Call the superclass part of the method to add it.
ADataRowObject.addValue(self, value, index)
[docs] def setValue(self, value, index):
"""Changes the value at the given index
See Also:
ADataRowObject: setValue()
"""
if not value in self.legal_values:
raise ValueError('value %S does is not included in %s' % (str(value), str(self.legal_values)))
# Call the superclass to set the value
ADataRowObject.setValue(self, value, index)
[docs]class SymbolData(ADataRowObject):
"""Overrides the value return methods from ADataObject to return a
float value instead of a string.
"""
def __init__(self, datatype, symbol, format_str='{}', **kwargs):
"""Constructor.
Args:
datatype (int): The type of collection it will hold (see the enum
list in the isis.datunits.ROW_DATA_TYPES.
symbol(str): The symbol that should should be used to represent the
presence of this value in the file.
legal_values(tuple): contains the possible values that this
collection can have.
format_str={} (str): Represents the format that should be used to
print out the values kept in this data collection. This
should be in the form '{:<10}' = 10 spaces formatted left.
With the required spaces need to correctly format the value
in the file.
**kwargs:
default=None: The default value that the collection should use -
Can be None if defaults are not allowed or '~' if the default
should remove the formatting and apply an empty string.
Raises:
AttributeError: if legal_values in not a valid tuple.
"""
self.symbol = symbol
self.bool_type = bool # Used to test if a value is of type bool or not
ADataRowObject.__init__(self, datatype, format_str, **kwargs)
[docs] def addValue(self, value=None, index = None):
"""Adds a value to the collection.
See Also:
ADataRowObject: addValue()
"""
if not value == None:
if value == self.symbol:
value = True
elif value == '':
value = False
# Make sure that we are adding a bool type
if not isinstance(value, self.bool_type):
logger.error('Attempted to add invalid value to SymbolDataObject')
raise ValueError ('Attempted to add invalid value to SymbolDataObject')
# Call superclass to add the value
ADataRowObject.addValue(self, value, index)
[docs] def setValue(self, value, index):
"""Changes the value at the given index
See Also:
ADataRowObject: setValue()
"""
if value == self.symbol:
value = True
elif value == '':
value = False
# Make sure that we are adding a bool type
if not isinstance(value, self.bool_type):
logger.error('Attempted to add invalid value to SymbolDataObject')
raise ValueError ('Attempted to add invalid value to SymbolDataObject')
# Call the superclass to set the value
ADataRowObject.setValue(self, value, index)