123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333 |
- #-*- coding: utf8
- import odml.dtypes as dtypes
- import odml.base as base
- import odml.format as format
- import string
- from odml.tools.doc_inherit import inherit_docstring, allow_inherit_docstring
- class Value(base._baseobj):
- pass
- @allow_inherit_docstring
- class BaseValue(base.baseobject, Value):
- """
- An odML value
- data
- mandatory (unless value is set). It's the content itself.
- (see the data and value attributes of this object)
- uncertainty (optional)
- an estimation of the value's uncertainty.
- unit (optional)
- the value's unit
- dtype (optional)
- the data type of the value
- reference (optional)
- an external reference number (e.g. entry in a database)
- filename (optional)
- the default file name which should be used when saving the object
- encoder (encoder)
- binary content must be encoded into ascii to be included in odML files.
- Currently supported is only base64
- checksum (optional)
- if binary content is directly included or if the URL of an external file is given,
- a checksum entry can be used to validate the file's identity, integrity.
- Use this element to indicate the algorithm and the checksum in the format algorithm$checksum
- e.g. crc32$b84892a2 or md5$d41d8cd98f00b204e9800998ecf8427e
- definition (optional)
- additional comments on the value of the property
- value
- mandatory (unless data is set). It's the string representation of the value.
- TODO: comment
- """
- _format = format.Value
- def __init__(self, data=None, uncertainty=None, unit=None, dtype=None, definition=None, reference=None,
- filename=None, encoder=None, checksum=None, comment=None, value=None):
- if data is None and value is None:
- raise TypeError("either data or value has to be set")
- if data is not None and value is not None:
- raise TypeError("only one of data or value can be set")
- self._property = None
- self._unit = unit
- self._uncertainty = uncertainty
- self._dtype = dtype
- self._definition = definition
- self._reference = reference
- self._filename = filename
- self._comment = comment
- self._encoder = encoder
- if value is not None:
- # assign value directly (through property would raise a change-event)
- self._value = dtypes.get(value, self._dtype, self._encoder)
- elif data is not None:
- if dtype is None:
- self._dtype = dtypes.infer_dtype(data)
- self._value = data
- self._checksum_type = None
- if checksum is not None:
- self.checksum = checksum
- def __repr__(self):
- if self._dtype:
- return "<%s %s>" % (str(self._dtype), self.get_display())
- return "<%s>" % str(self._value)
- @property
- def parent(self):
- """the property containing this value"""
- return self._property
- @property
- def data(self):
- """
- used to access the raw data of the value
- (i.e. a datetime-object if dtype is "datetime")
- see also the value attribute
- """
- return self._value
- @data.setter
- def data(self, new_value):
- self._value = new_value
- @property
- def value(self):
- """
- used to access typed data of the value as a string.
- Use data to access the raw type, i.e.:
- >>> v = Value(1, type="float")
- >>> v.data
- 1.0
- >>> v.data = 1.5
- >>> v.value
- "1.5"
- >>> v.value = 2
- >>> v.data
- 2.0
- """
- return dtypes.set(self._value, self._dtype, self._encoder)
- @value.setter
- def value(self, new_string):
- self._value = dtypes.get(new_string, self._dtype, self._encoder)
- @property
- def dtype(self):
- """
- the data type of the value
- If the data type is changed, it is tried, to convert the value to the new type.
- If this doesn't work, the change is refused.
- This behaviour can be overridden by directly accessing the *_dtype* attribute
- and adjusting the *data* attribute manually.
- """
- return self._dtype
- @dtype.setter
- def dtype(self, new_type):
- # check if this is a valid type
- if not dtypes.valid_type(new_type):
- raise AttributeError("'%s' is not a valid type." % new_type)
- # we convert the value if possible
- old_type = self._dtype
- old_value = dtypes.set(self._value, self._dtype, self._encoder)
- try:
- new_value = dtypes.get(old_value, new_type, self._encoder)
- except:
- # cannot convert, try the other way around
- try:
- old_value = dtypes.set(self._value, new_type, self._encoder)
- new_value = dtypes.get(old_value, new_type, self._encoder)
- except:
- #doesn't work either, therefore refuse
- raise ValueError("cannot convert '%s' from '%s' to '%s'" % (self.value, old_type, new_type))
- self._value = new_value
- self._dtype = new_type
- @property
- def encoder(self):
- """
- the encoding of binary data
- changing the encoding also converts the data
- """
- if self._dtype == "binary":
- return self._encoder
- return None
- @encoder.setter
- def encoder(self, encoding):
- if not self._dtype == "binary":
- raise AttributeError("attribute 'encoding' can only be set for binary types, not for '%s'" % self._dtype)
- if not encoding:
- encoding = None
- if not dtypes.valid_encoding(encoding):
- raise AttributeError("'%s' is not a valid encoding" % encoding)
- # no need to cast anything here, because the encoding
- # effects only the display, not the representation
- self._encoder = encoding
- @property
- def uncertainty(self):
- return self._uncertainty
- @uncertainty.setter
- def uncertainty(self, new_value):
- self._uncertainty = new_value
- @property
- def unit(self):
- return self._unit
- @unit.setter
- def unit(self, new_value):
- self._unit = new_value
- @property
- def reference(self):
- return self._reference
- @reference.setter
- def reference(self, new_value):
- self._reference = new_value
- @property
- def definition(self):
- return self._definition
- @definition.setter
- def definition(self, new_value):
- self._definition = new_value
- @property
- def comment(self):
- return self._comment
- @comment.setter
- def comment(self, new_value):
- self._comment = new_value
- @property
- def filename(self):
- return self._filename
- @filename.setter
- def filename(self, new_value):
- self._filename = new_value
- @property
- def checksum(self):
- if not self._dtype == "binary":
- return None
- cs_type = self._checksum_type
- if cs_type is None:
- cs_type = "crc32"
- return "%s$%s" % (cs_type, self.calculate_checksum(cs_type))
- @checksum.setter
- def checksum(self, value):
- if not self._dtype == "binary":
- raise AttributeError("attribute 'checksum' can only be set for binary types, not for '%s'" % self._dtype)
- data = value.split("$", 1)
- if not dtypes.valid_checksum_type(data[0]):
- raise AttributeError("unsupported checksum type '%s'" % data[0])
- self._checksum_type = data[0]
- def calculate_checksum(self, cs_type):
- """
- returns the checksum for the data of this Value-object
- *cs_type* is the checksum mechanism (e.g. 'crc32' or 'md5')
- """
- return dtypes.calculate_checksum(self._value, cs_type)
- def can_display(self, text=None, max_length=-1):
- """
- return whether the content of this can be safely displayed in the gui
- """
- if text is None:
- text = self._value
- if text is None:
- return True
- if max_length != -1 and len(text) > max_length:
- return False
- if self._dtype == "binary":
- unprintable = filter(lambda x: x not in string.printable, text)
- if self._encoder is None and len(unprintable) > 0:
- return False
- if "\n" in text or "\t" in text:
- return False
- return True
- def get_display(self, max_length=-1):
- """
- return a textual representation that can be used for display
- typically takes the first line (max *max_length* chars) and adds '…'
- """
- text = self.value
- if self.can_display(text, max_length):
- return text
- text = text.split("\n")[0]
- if max_length != -1:
- text = text[:max_length]
- if self.can_display(text, max_length):
- return text + u'…'
- return "(%d bytes)" % len(self._value)
- # def can_edit(self, max_length=-1)
- # if not self.can_display(self): return False
- #
- # return True
- #
- @inherit_docstring
- def reorder(self, new_index):
- return self._reorder(self.parent.values, new_index)
- def clone(self):
- obj = super(BaseValue, self).clone()
- obj._property = None
- return obj
- @inherit_docstring
- def get_terminology_equivalent(self):
- prop = self._property.get_terminology_equivalent()
- if prop is None:
- return None
- for val in prop:
- if val == self: # TODO: shouldn't we take the index or st.?
- return val
- return None
|