value.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. #-*- coding: utf8
  2. import odml.dtypes as dtypes
  3. import odml.base as base
  4. import odml.format as format
  5. import string
  6. from odml.tools.doc_inherit import inherit_docstring, allow_inherit_docstring
  7. class Value(base._baseobj):
  8. pass
  9. @allow_inherit_docstring
  10. class BaseValue(base.baseobject, Value):
  11. """
  12. An odML value
  13. data
  14. mandatory (unless value is set). It's the content itself.
  15. (see the data and value attributes of this object)
  16. uncertainty (optional)
  17. an estimation of the value's uncertainty.
  18. unit (optional)
  19. the value's unit
  20. dtype (optional)
  21. the data type of the value
  22. reference (optional)
  23. an external reference number (e.g. entry in a database)
  24. filename (optional)
  25. the default file name which should be used when saving the object
  26. encoder (encoder)
  27. binary content must be encoded into ascii to be included in odML files.
  28. Currently supported is only base64
  29. checksum (optional)
  30. if binary content is directly included or if the URL of an external file is given,
  31. a checksum entry can be used to validate the file's identity, integrity.
  32. Use this element to indicate the algorithm and the checksum in the format algorithm$checksum
  33. e.g. crc32$b84892a2 or md5$d41d8cd98f00b204e9800998ecf8427e
  34. definition (optional)
  35. additional comments on the value of the property
  36. value
  37. mandatory (unless data is set). It's the string representation of the value.
  38. TODO: comment
  39. """
  40. _format = format.Value
  41. def __init__(self, data=None, uncertainty=None, unit=None, dtype=None, definition=None, reference=None,
  42. filename=None, encoder=None, checksum=None, comment=None, value=None):
  43. if data is None and value is None:
  44. raise TypeError("either data or value has to be set")
  45. if data is not None and value is not None:
  46. raise TypeError("only one of data or value can be set")
  47. self._property = None
  48. self._unit = unit
  49. self._uncertainty = uncertainty
  50. self._dtype = dtype
  51. self._definition = definition
  52. self._reference = reference
  53. self._filename = filename
  54. self._comment = comment
  55. self._encoder = encoder
  56. if value is not None:
  57. # assign value directly (through property would raise a change-event)
  58. self._value = dtypes.get(value, self._dtype, self._encoder)
  59. elif data is not None:
  60. if dtype is None:
  61. self._dtype = dtypes.infer_dtype(data)
  62. self._value = data
  63. self._checksum_type = None
  64. if checksum is not None:
  65. self.checksum = checksum
  66. def __repr__(self):
  67. if self._dtype:
  68. return "<%s %s>" % (str(self._dtype), self.get_display())
  69. return "<%s>" % str(self._value)
  70. @property
  71. def parent(self):
  72. """the property containing this value"""
  73. return self._property
  74. @property
  75. def data(self):
  76. """
  77. used to access the raw data of the value
  78. (i.e. a datetime-object if dtype is "datetime")
  79. see also the value attribute
  80. """
  81. return self._value
  82. @data.setter
  83. def data(self, new_value):
  84. self._value = new_value
  85. @property
  86. def value(self):
  87. """
  88. used to access typed data of the value as a string.
  89. Use data to access the raw type, i.e.:
  90. >>> v = Value(1, type="float")
  91. >>> v.data
  92. 1.0
  93. >>> v.data = 1.5
  94. >>> v.value
  95. "1.5"
  96. >>> v.value = 2
  97. >>> v.data
  98. 2.0
  99. """
  100. return dtypes.set(self._value, self._dtype, self._encoder)
  101. @value.setter
  102. def value(self, new_string):
  103. self._value = dtypes.get(new_string, self._dtype, self._encoder)
  104. @property
  105. def dtype(self):
  106. """
  107. the data type of the value
  108. If the data type is changed, it is tried, to convert the value to the new type.
  109. If this doesn't work, the change is refused.
  110. This behaviour can be overridden by directly accessing the *_dtype* attribute
  111. and adjusting the *data* attribute manually.
  112. """
  113. return self._dtype
  114. @dtype.setter
  115. def dtype(self, new_type):
  116. # check if this is a valid type
  117. if not dtypes.valid_type(new_type):
  118. raise AttributeError("'%s' is not a valid type." % new_type)
  119. # we convert the value if possible
  120. old_type = self._dtype
  121. old_value = dtypes.set(self._value, self._dtype, self._encoder)
  122. try:
  123. new_value = dtypes.get(old_value, new_type, self._encoder)
  124. except:
  125. # cannot convert, try the other way around
  126. try:
  127. old_value = dtypes.set(self._value, new_type, self._encoder)
  128. new_value = dtypes.get(old_value, new_type, self._encoder)
  129. except:
  130. #doesn't work either, therefore refuse
  131. raise ValueError("cannot convert '%s' from '%s' to '%s'" % (self.value, old_type, new_type))
  132. self._value = new_value
  133. self._dtype = new_type
  134. @property
  135. def encoder(self):
  136. """
  137. the encoding of binary data
  138. changing the encoding also converts the data
  139. """
  140. if self._dtype == "binary":
  141. return self._encoder
  142. return None
  143. @encoder.setter
  144. def encoder(self, encoding):
  145. if not self._dtype == "binary":
  146. raise AttributeError("attribute 'encoding' can only be set for binary types, not for '%s'" % self._dtype)
  147. if not encoding:
  148. encoding = None
  149. if not dtypes.valid_encoding(encoding):
  150. raise AttributeError("'%s' is not a valid encoding" % encoding)
  151. # no need to cast anything here, because the encoding
  152. # effects only the display, not the representation
  153. self._encoder = encoding
  154. @property
  155. def uncertainty(self):
  156. return self._uncertainty
  157. @uncertainty.setter
  158. def uncertainty(self, new_value):
  159. self._uncertainty = new_value
  160. @property
  161. def unit(self):
  162. return self._unit
  163. @unit.setter
  164. def unit(self, new_value):
  165. self._unit = new_value
  166. @property
  167. def reference(self):
  168. return self._reference
  169. @reference.setter
  170. def reference(self, new_value):
  171. self._reference = new_value
  172. @property
  173. def definition(self):
  174. return self._definition
  175. @definition.setter
  176. def definition(self, new_value):
  177. self._definition = new_value
  178. @property
  179. def comment(self):
  180. return self._comment
  181. @comment.setter
  182. def comment(self, new_value):
  183. self._comment = new_value
  184. @property
  185. def filename(self):
  186. return self._filename
  187. @filename.setter
  188. def filename(self, new_value):
  189. self._filename = new_value
  190. @property
  191. def checksum(self):
  192. if not self._dtype == "binary":
  193. return None
  194. cs_type = self._checksum_type
  195. if cs_type is None:
  196. cs_type = "crc32"
  197. return "%s$%s" % (cs_type, self.calculate_checksum(cs_type))
  198. @checksum.setter
  199. def checksum(self, value):
  200. if not self._dtype == "binary":
  201. raise AttributeError("attribute 'checksum' can only be set for binary types, not for '%s'" % self._dtype)
  202. data = value.split("$", 1)
  203. if not dtypes.valid_checksum_type(data[0]):
  204. raise AttributeError("unsupported checksum type '%s'" % data[0])
  205. self._checksum_type = data[0]
  206. def calculate_checksum(self, cs_type):
  207. """
  208. returns the checksum for the data of this Value-object
  209. *cs_type* is the checksum mechanism (e.g. 'crc32' or 'md5')
  210. """
  211. return dtypes.calculate_checksum(self._value, cs_type)
  212. def can_display(self, text=None, max_length=-1):
  213. """
  214. return whether the content of this can be safely displayed in the gui
  215. """
  216. if text is None:
  217. text = self._value
  218. if text is None:
  219. return True
  220. if max_length != -1 and len(text) > max_length:
  221. return False
  222. if self._dtype == "binary":
  223. unprintable = filter(lambda x: x not in string.printable, text)
  224. if self._encoder is None and len(unprintable) > 0:
  225. return False
  226. if "\n" in text or "\t" in text:
  227. return False
  228. return True
  229. def get_display(self, max_length=-1):
  230. """
  231. return a textual representation that can be used for display
  232. typically takes the first line (max *max_length* chars) and adds '…'
  233. """
  234. text = self.value
  235. if self.can_display(text, max_length):
  236. return text
  237. text = text.split("\n")[0]
  238. if max_length != -1:
  239. text = text[:max_length]
  240. if self.can_display(text, max_length):
  241. return text + u'…'
  242. return "(%d bytes)" % len(self._value)
  243. # def can_edit(self, max_length=-1)
  244. # if not self.can_display(self): return False
  245. #
  246. # return True
  247. #
  248. @inherit_docstring
  249. def reorder(self, new_index):
  250. return self._reorder(self.parent.values, new_index)
  251. def clone(self):
  252. obj = super(BaseValue, self).clone()
  253. obj._property = None
  254. return obj
  255. @inherit_docstring
  256. def get_terminology_equivalent(self):
  257. prop = self._property.get_terminology_equivalent()
  258. if prop is None:
  259. return None
  260. for val in prop:
  261. if val == self: # TODO: shouldn't we take the index or st.?
  262. return val
  263. return None