property.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. #-*- coding: utf-8
  2. import odml.base as base
  3. import odml.format as format
  4. import odml.mapping as mapping
  5. import odml.value as odml_value
  6. import odml
  7. from odml.tools.doc_inherit import inherit_docstring, allow_inherit_docstring
  8. class Property(base._baseobj):
  9. pass
  10. @allow_inherit_docstring
  11. class BaseProperty(base.baseobject, mapping.mapableProperty, Property):
  12. """An odML Property"""
  13. _format = format.Property
  14. def __init__(self, name, value, definition=None, dependency=None, dependency_value=None, mapping=None):
  15. """
  16. Create a new Property with one single or multiple values. If something is passed as value that
  17. is not a Value object, the method will try to infer the values dtype from the type of the
  18. parameter.
  19. Example for a property with a single value
  20. >>> Property("property1", odml.Value(2)) #or
  21. >>> Property("property1", 2)
  22. Example for a property with multiple values
  23. >>> Property("property2", [odml.Value(data=1), odml.Value(data=2)]) #or
  24. >>> Property("property2", [1, 2])
  25. :param name: The mane of the property
  26. :param value: Either a Value or some type a Value can be created from or a list of values.
  27. :param definition: The definition of the property.
  28. :param dependency: Another property this property depends on.
  29. :param dependency_value: Dependency on a certain value.
  30. :param mapping: Mapping information of the property.
  31. """
  32. #TODO doc description for arguments
  33. #TODO validate arguments
  34. self._name = name
  35. self._section = None
  36. self._reset_values()
  37. self.definition = definition
  38. self.dependency = dependency
  39. self.dependency_value = dependency_value
  40. self._mapping = mapping
  41. if isinstance(value, list):
  42. for v in value:
  43. if not isinstance(v, odml_value.Value):
  44. v = odml.Value(v)
  45. self.append(v)
  46. elif value is not None:
  47. self.append(value)
  48. # getter and setter methods are omnitted for now, but they can easily
  49. # be introduced later using python-properties
  50. #odML "native" properties
  51. @property
  52. def name(self):
  53. return self._name
  54. @name.setter
  55. def name(self, new_value):
  56. self._name = new_value
  57. def __repr__(self):
  58. return "<Property %s>" % self._name
  59. # API (public)
  60. #
  61. # properties
  62. @property
  63. def parent(self):
  64. """the section containing this property"""
  65. return self._section
  66. @property
  67. def values(self):
  68. """returns the list of values for this property"""
  69. return self._values
  70. @values.setter
  71. def values(self, new_values):
  72. # TODO for consistency this actually needs to manually remove each existing value
  73. self._reset_values()
  74. for i in new_values:
  75. self.append(i)
  76. @property
  77. def value(self):
  78. """
  79. returns the value of this property (or list if multiple values are present)
  80. use :py:meth:`odml.property.BaseProperty.values` to always return the list
  81. """
  82. if len(self._values) == 1:
  83. return self._values[0]
  84. #create a copy of the list, so mutations in there won’t affect us:
  85. return self._values[:]
  86. @value.setter
  87. def value(self, new_value):
  88. self._reset_values()
  89. self.append(new_value)
  90. def append(self, value):
  91. """
  92. adds a value to the list of values
  93. If *value* is not an odml.Value instance, such an instance will be created
  94. given the addition function arguments (see :ref:`__init__` for their description).
  95. If *value* is not an odml.Value instance and *unit*, *dtype* or *uncertainty* are
  96. missing, the values will be copied from the last value in this properties
  97. value-list if *copy_attributes* is True. If there is no value present to be
  98. copied from, an IndexError will be raised.
  99. """
  100. if not isinstance(value, odml_value.Value):
  101. value = odml.Value(value)
  102. self._values.append(value)
  103. value._property = self
  104. def remove(self, value):
  105. """
  106. Remove a value from this property and unset its parent.
  107. Raises a TypeError if this would cause the property not to hold any value at all.
  108. This can be circumvented by using the *_values* property.
  109. """
  110. if len(self._values) == 1:
  111. raise TypeError("Cannot remove %s from %s. A property must always have at least one value." % (repr(value), repr(self)))
  112. self._values.remove(value)
  113. value._property = None
  114. @inherit_docstring
  115. def reorder(self, new_index):
  116. return self._reorder(self.parent.properties, new_index)
  117. def __len__(self):
  118. return len(self._values)
  119. def __iter__(self):
  120. return self._values.__iter__()
  121. def get_path(self):
  122. """return the absolute path to this object"""
  123. return self.parent.get_path() + ":" + self.name
  124. def clone(self, children=True):
  125. """
  126. clone this object recursively allowing to copy it independently
  127. to another document
  128. """
  129. obj = super(BaseProperty, self).clone(children)
  130. obj._section = None
  131. obj._reset_values()
  132. if children:
  133. for v in self._values:
  134. obj.append(v.clone())
  135. return obj
  136. def _reset_values(self):
  137. """
  138. reinitialize the list of values with an empty list
  139. """
  140. self._values = base.SafeList()
  141. def merge(self, property):
  142. """stub that doesn't do anything for this class"""
  143. pass
  144. def unmerge(self, property):
  145. """stub that doesn't do anything for this class"""
  146. pass
  147. def get_merged_equivalent(self):
  148. """
  149. return the merged object (i.e. if the section is linked to another one,
  150. return the corresponding property of the linked section) or None
  151. """
  152. if self._section._merged is None: return None
  153. return self._section._merged.contains(self)
  154. @inherit_docstring
  155. def get_terminology_equivalent(self):
  156. if self._section is None: return None
  157. sec = self._section.get_terminology_equivalent()
  158. if sec is None: return None
  159. try:
  160. return sec.properties[self.name]
  161. except KeyError:
  162. return None