Skip to content

Commit

Permalink
Merge pull request #313 from mpsonntag/nixvalues
Browse files Browse the repository at this point in the history
nixpy style values and printing
  • Loading branch information
jgrewe authored Nov 23, 2018
2 parents 25330df + 3c0208c commit ac805c5
Show file tree
Hide file tree
Showing 18 changed files with 400 additions and 296 deletions.
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,26 @@ until the next release.

# Latest changes in master

...

# Version 1.4.2

## Print methods

`pprint` methods have been added to both `Section` and `Property`
to print whole Section trees with their child sections and properties.
The `__repr__` style of `Section` and `Property` has been changed to
be more similar to the [nixpy](https://github.com/G-Node/nixpy) `__repr__` style.
Printing a `Section` now also features the immediate `Property` child count
in addition to the immediate `Section` child count. See #309.

## Deprecation of 'Property.value' in favor of 'Property.values'

To make working with odML more similar to working with the
metadata part of [nixpy](https://github.com/G-Node/nixpy), the `Property.value`
attribute has been marked deprecated and the `Property.values`
attribute has been added. See #308.

## Uncertainty changes

Uncertainty is now limited to float only. See #294.
Expand Down
161 changes: 80 additions & 81 deletions doc/tutorial.rst

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions odml/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,8 +357,8 @@ def itervalues(self, max_depth=None, filter_func=lambda x: True):
:type filter_func: function
"""
for prop in [p for p in self.iterproperties(max_depth=max_depth)]:
if filter_func(prop.value):
yield prop.value
if filter_func(prop.values):
yield prop.values

def contains(self, obj):
"""
Expand Down
4 changes: 2 additions & 2 deletions odml/doc.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ def __init__(self, author=None, date=None, version=None, repository=None, oid=No
self._origin_file_name = None

def __repr__(self):
return "<Doc %s by %s (%d sections)>" % (self._version, self._author,
len(self._sections))
return "Document %s {author = %s, %d sections}" % \
(self._version, self._author, len(self._sections))

@property
def oid(self):
Expand Down
3 changes: 2 additions & 1 deletion odml/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ class Property(Format):
_map = {
'dependencyvalue': 'dependency_value',
'type': 'dtype',
'id': 'oid'
'id': 'oid',
'value': 'values'
}
_rdf_map = {
'id': _ns.hasId,
Expand Down
172 changes: 113 additions & 59 deletions odml/property.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ class BaseProperty(base.BaseObject):
"""An odML Property"""
_format = frmt.Property

def __init__(self, name=None, value=None, parent=None, unit=None,
def __init__(self, name=None, values=None, parent=None, unit=None,
uncertainty=None, reference=None, definition=None,
dependency=None, dependency_value=None, dtype=None,
value_origin=None, oid=None):
value_origin=None, oid=None, value=None):
"""
Create a new Property. If a value without an explicitly stated dtype
has been provided, the method will try to infer the value's dtype.
Expand All @@ -31,8 +31,8 @@ def __init__(self, name=None, value=None, parent=None, unit=None,
>>> p.dtype
>>> int
:param name: The name of the property.
:param value: Some data value, it can be a single value or
a list of homogeneous values.
:param values: Some data value, it can be a single value or
a list of homogeneous values.
:param unit: The unit of the stored data.
:param uncertainty: The uncertainty (e.g. the standard deviation)
associated with a measure value.
Expand All @@ -48,6 +48,8 @@ def __init__(self, name=None, value=None, parent=None, unit=None,
:param oid: object id, UUID string as specified in RFC 4122. If no id is provided,
an id will be generated and assigned. An id has to be unique
within an odML Document.
:param value: Legacy code to the 'values' attribute. If 'values' is provided,
any data provided via 'value' will be ignored.
"""
try:
if oid is not None:
Expand Down Expand Up @@ -78,28 +80,33 @@ def __init__(self, name=None, value=None, parent=None, unit=None,
else:
print("Warning: Unknown dtype '%s'." % dtype)

self._value = []
self.value = value
self._values = []
self.values = values
if not values and (value or isinstance(value, bool)):
self.values = value

self.parent = parent

def __len__(self):
return len(self._value)
return len(self._values)

def __getitem__(self, key):
return self._value[key]
return self._values[key]

def __setitem__(self, key, item):
if int(key) < 0 or int(key) > self.__len__():
raise IndexError("odml.Property.__setitem__: key %i invalid for "
"array of length %i" % (int(key), self.__len__()))
try:
val = dtypes.get(item, self.dtype)
self._value[int(key)] = val
self._values[int(key)] = val
except Exception:
raise ValueError("odml.Property.__setitem__: passed value cannot be "
"converted to data type \'%s\'!" % self._dtype)

def __repr__(self):
return "Property: {name = %s}" % self._name

@property
def oid(self):
"""
Expand Down Expand Up @@ -143,9 +150,6 @@ def name(self, new_name):

self._name = new_name

def __repr__(self):
return "<Property %s>" % self._name

@property
def dtype(self):
"""
Expand All @@ -166,10 +170,10 @@ def dtype(self, new_type):
raise AttributeError("'%s' is not a valid type." % new_type)
# we convert the value if possible
old_type = self._dtype
old_values = self._value
old_values = self._values
try:
self._dtype = new_type
self.value = old_values
self.values = old_values
except:
self._dtype = old_type # If conversion failed, restore old dtype
raise ValueError("cannot convert from '%s' to '%s'" %
Expand Down Expand Up @@ -209,42 +213,27 @@ def _validate_parent(new_parent):
@property
def value(self):
"""
Returns the value(s) stored in this property. Method always returns a list
that is a copy (!) of the stored value. Changing this list will NOT change
the property.
For manipulation of the stored values use the append, extend, and direct
access methods (using brackets).
For example:
>>> p = odml.Property("prop", value=[1, 2, 3])
>>> print(p.value)
[1, 2, 3]
>>> p.value.append(4)
>>> print(p.value)
[1, 2, 3]
Deprecated alias of 'values'. Will be removed with the next minor release.
"""
print("The attribute 'value' is deprecated. Please use 'values' instead.")
return self.values

Individual values can be accessed and manipulated like this:
>>> print(p[0])
[1]
>>> p[0] = 4
>>> print(p[0])
[4]
@value.setter
def value(self, new_value):
"""
Deprecated alias of 'values'. Will be removed with the next minor release.
The values can be iterated e.g. with a loop:
>>> for v in p.value:
>>> print(v)
4
2
3
:param new_value: a single value or list of values.
"""
return list(self._value)
print("The attribute 'value' is deprecated. Please use 'values' instead.")
self.values = new_value

def value_str(self, index=0):
"""
Used to access typed data of the value at a specific
index position as a string.
"""
return dtypes.set(self._value[index], self._dtype)
return dtypes.set(self._values[index], self._dtype)

def _validate_values(self, values):
"""
Expand Down Expand Up @@ -285,19 +274,52 @@ def _convert_value_input(self, new_value):
"unsupported data type for values: %s" % type(new_value))
return new_value

@value.setter
def value(self, new_value):
@property
def values(self):
"""
Set the value of the property discarding any previous information.
Returns the value(s) stored in this property. Method always returns a list
that is a copy (!) of the stored value. Changing this list will NOT change
the property.
For manipulation of the stored values use the append, extend, and direct
access methods (using brackets).
For example:
>>> p = odml.Property("prop", values=[1, 2, 3])
>>> print(p.values)
[1, 2, 3]
>>> p.values.append(4)
>>> print(p.values)
[1, 2, 3]
Individual values can be accessed and manipulated like this:
>>> print(p[0])
[1]
>>> p[0] = 4
>>> print(p[0])
[4]
The values can be iterated e.g. with a loop:
>>> for v in p.values:
>>> print(v)
4
2
3
"""
return list(self._values)

@values.setter
def values(self, new_value):
"""
Set the values of the property discarding any previous information.
Method will try to convert the passed value to the dtype of
the property and raise an ValueError if not possible.
the property and raise a ValueError if not possible.
:param new_value: a single value or list of values.
"""
# Make sure boolean value 'False' gets through as well...
if new_value is None or \
(isinstance(new_value, (list, tuple, str)) and len(new_value) == 0):
self._value = []
self._values = []
return

new_value = self._convert_value_input(new_value)
Expand All @@ -306,9 +328,9 @@ def value(self, new_value):
self._dtype = dtypes.infer_dtype(new_value[0])

if not self._validate_values(new_value):
raise ValueError("odml.Property.value: passed values are not of "
raise ValueError("odml.Property.values: passed values are not of "
"consistent type!")
self._value = [dtypes.get(v, self.dtype) for v in new_value]
self._values = [dtypes.get(v, self.dtype) for v in new_value]

@property
def value_origin(self):
Expand Down Expand Up @@ -394,8 +416,8 @@ def remove(self, value):
occurrence of the passed in value is removed from the properties
list of values.
"""
if value in self._value:
self._value.remove(value)
if value in self._values:
self._values.remove(value)

def get_path(self):
"""
Expand All @@ -417,7 +439,7 @@ def clone(self, keep_id=False):
"""
obj = super(BaseProperty, self).clone()
obj._parent = None
obj.value = self._value
obj.values = self._values
if not keep_id:
obj.new_id()

Expand All @@ -441,7 +463,7 @@ def merge_check(self, source, strict=True):

# Catch unmerge-able values at this point to avoid
# failing Section tree merges which cannot easily be rolled back.
new_value = self._convert_value_input(source.value)
new_value = self._convert_value_input(source.values)
if not self._validate_values(new_value):
raise ValueError("odml.Property.merge: passed value(s) cannot "
"be converted to data type '%s'!" % self._dtype)
Expand Down Expand Up @@ -512,7 +534,7 @@ def merge(self, other, strict=True):
if self.unit is None and other.unit is not None:
self.unit = other.unit

to_add = [v for v in other.value if v not in self._value]
to_add = [v for v in other.values if v not in self._values]
self.extend(to_add, strict=strict)

def unmerge(self, other):
Expand Down Expand Up @@ -557,11 +579,11 @@ def extend(self, obj, strict=True):
if obj.unit != self.unit:
raise ValueError("odml.Property.extend: src and dest units (%s, %s) "
"do not match!" % (obj.unit, self.unit))
self.extend(obj.value)
self.extend(obj.values)
return

if self.__len__() == 0:
self.value = obj
self.values = obj
return

new_value = self._convert_value_input(obj)
Expand All @@ -572,7 +594,7 @@ def extend(self, obj, strict=True):
if not self._validate_values(new_value):
raise ValueError("odml.Property.extend: passed value(s) cannot be converted "
"to data type \'%s\'!" % self._dtype)
self._value.extend([dtypes.get(v, self.dtype) for v in new_value])
self._values.extend([dtypes.get(v, self.dtype) for v in new_value])

def append(self, obj, strict=True):
"""
Expand All @@ -587,8 +609,8 @@ def append(self, obj, strict=True):
if obj in [None, "", [], {}]:
return

if not self.value:
self.value = obj
if not self.values:
self.values = obj
return

new_value = self._convert_value_input(obj)
Expand All @@ -603,4 +625,36 @@ def append(self, obj, strict=True):
raise ValueError("odml.Property.append: passed value(s) cannot be converted "
"to data type \'%s\'!" % self._dtype)

self._value.append(dtypes.get(new_value[0], self.dtype))
self._values.append(dtypes.get(new_value[0], self.dtype))

def pprint(self, indent=2, max_length=80, current_depth=-1):
"""
Pretty print method to visualize Properties and Section-Property trees.
:param indent: number of leading spaces for every child Property.
:param max_length: maximum number of characters printed in one line.
:param current_depth: number of hierarchical levels printed from the
starting Section.
"""
property_spaces = ""
prefix = ""
if current_depth >= 0:
property_spaces = " " * ((current_depth + 2) * indent)
prefix = "|-"

if self.unit is None:
value_string = str(self.values)
else:
value_string = "{}{}".format(self.values, self.unit)

p_len = len(property_spaces) + len(self.name) + len(value_string)
if p_len >= max_length - 4:
split_len = int((max_length - len(property_spaces)
+ len(self.name) - len(prefix))/2)
str1 = value_string[0: split_len]
str2 = value_string[-split_len:]
print(("{}{} {}: {} ... {}".format(property_spaces, prefix,
self.name, str1, str2)))
else:
print(("{}{} {}: {}".format(property_spaces, prefix, self.name,
value_string)))
Loading

0 comments on commit ac805c5

Please sign in to comment.