Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 16 additions & 45 deletions odml/property.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from . import validation
from . import format as frmt
from .tools.doc_inherit import inherit_docstring, allow_inherit_docstring
from .util import format_cardinality


def odml_tuple_import(t_count, new_value):
Expand Down Expand Up @@ -413,9 +414,7 @@ def values(self, new_value):
self._values = [dtypes.get(v, self.dtype) for v in new_value]

# Validate and inform user if the current values cardinality is violated
valid = validation.Validation(self)
for err in valid.errors:
print("%s: %s" % (err.rank.capitalize(), err.msg))
self._values_cardinality_validation()

@property
def value_origin(self):
Expand Down Expand Up @@ -528,7 +527,7 @@ def val_cardinality(self):
"""
The value cardinality of a Property. It defines how many values
are minimally required and how many values should be maximally
stored. Use 'values_set_cardinality' to set.
stored. Use the 'set_values_cardinality' method to set.
"""
return self._val_cardinality

Expand All @@ -549,50 +548,22 @@ def val_cardinality(self, new_value):
:param new_value: Can be either 'None', a positive integer, which will set
the maximum or an integer 2-tuple of the format '(min, max)'.
"""
invalid_input = False
exc_msg = "Can only assign positive single int or int-tuples of the format '(min, max)'"

# Empty values reset the cardinality to None.
if not new_value or new_value == (None, None):
self._val_cardinality = None

# Providing a single integer sets the maximum value in a tuple.
elif isinstance(new_value, int) and new_value > 0:
self._val_cardinality = (None, new_value)

# Only integer 2-tuples of the format '(min, max)' are supported to set the cardinality
elif isinstance(new_value, tuple) and len(new_value) == 2:
v_min = new_value[0]
v_max = new_value[1]

min_int = isinstance(v_min, int) and v_min >= 0
max_int = isinstance(v_max, int) and v_max >= 0

if max_int and min_int and v_max > v_min:
self._val_cardinality = (v_min, v_max)

elif max_int and not v_min:
self._val_cardinality = (None, v_max)

elif min_int and not v_max:
self._val_cardinality = (v_min, None)
self._val_cardinality = format_cardinality(new_value)

else:
invalid_input = True
# Validate and inform user if the current values cardinality is violated
self._values_cardinality_validation()

# Use helpful exception message in the following case:
if max_int and min_int and v_max < v_min:
exc_msg = "Minimum larger than maximum (min=%s, max=%s)" % (v_min, v_max)
else:
invalid_input = True
def _values_cardinality_validation(self):
"""
Runs a validation to check whether the values cardinality
is respected and prints a warning message otherwise.
"""
valid = validation.Validation(self)

if not invalid_input:
# Validate and inform user if the current values cardinality is violated
valid = validation.Validation(self)
for err in valid.errors:
print("%s: %s" % (err.rank.capitalize(), err.msg))
else:
raise ValueError(exc_msg)
# Make sure to display only warnings of the current property
res = [curr for curr in valid.errors if self.id == curr.obj.id]
for err in res:
print("%s: %s" % (err.rank.capitalize(), err.msg))

def set_values_cardinality(self, min_val=None, max_val=None):
"""
Expand Down
63 changes: 63 additions & 0 deletions odml/util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# -*- coding: utf-8
"""
Module containing general utility functions.
"""


def format_cardinality(in_val):
"""
Checks an input value and formats it towards a custom tuple format
used in odml Section, Property and Values cardinality.

The following cases are supported:
(n, n) - default, no restriction
(d, n) - minimally d entries, no maximum
(n, d) - maximally d entries, no minimum
(d, d) - minimally d entries, maximally d entries

Only positive integers are supported. 'None' is used to denote
no restrictions on a maximum or minimum.

:param in_val: Can either be 'None', a positive integer, which will set
the maximum or an integer 2-tuple of the format '(min, max)'.

:returns: None or the value as tuple. A ValueError is raised, if the
provided value was not in an acceptable format.
"""
exc_msg = "Can only assign positive single int or int-tuples of the format '(min, max)'"

# Empty values reset the cardinality to None.
if not in_val:
return None

# Catch tuple edge cases (0, 0); (None, None); (0, None); (None, 0)
if isinstance(in_val, (tuple, list)) and len(in_val) == 2 and not in_val[0] and not in_val[1]:
return None

# Providing a single integer sets the maximum value in a tuple.
if isinstance(in_val, int) and in_val > 0:
return None, in_val

# Integer 2-tuples of the format '(min, max)' are supported to set the cardinality.
# Also support lists with a length of 2 without advertising it.
if isinstance(in_val, (tuple, list)) and len(in_val) == 2:
v_min = in_val[0]
v_max = in_val[1]

min_int = isinstance(v_min, int) and v_min >= 0
max_int = isinstance(v_max, int) and v_max >= 0

if max_int and min_int and v_max >= v_min:
return v_min, v_max

if max_int and not v_min:
return None, v_max

if min_int and not v_max:
return v_min, None

# Use helpful exception message in the following case:
if max_int and min_int and v_max < v_min:
exc_msg = "Minimum larger than maximum (min=%s, max=%s)" % (v_min, v_max)

raise ValueError(exc_msg)
79 changes: 79 additions & 0 deletions test/test_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"""
This file tests odml util functions.
"""

import unittest

from odml.util import format_cardinality


class TestUtil(unittest.TestCase):

def test_format_cardinality(self):
# Test empty set
self.assertIsNone(format_cardinality(None))
self.assertIsNone(format_cardinality([]))
self.assertIsNone(format_cardinality({}))
self.assertIsNone(format_cardinality(""))
self.assertIsNone(format_cardinality(()))

# Test empty tuple edge cases
self.assertIsNone(format_cardinality((None, None)))
self.assertIsNone(format_cardinality((0, 0)))
self.assertIsNone(format_cardinality((None, 0)))
self.assertIsNone(format_cardinality((0, None)))

# Test single int max set
self.assertEqual(format_cardinality(10), (None, 10))

# Test tuple set
set_val = (2, None)
self.assertEqual(format_cardinality(set_val), set_val)
set_val = (None, 2)
self.assertEqual(format_cardinality(set_val), set_val)
set_val = (2, 3)
self.assertEqual(format_cardinality(set_val), set_val)

# Test list simple list set
set_val = [2, None]
self.assertEqual(format_cardinality(set_val), tuple(set_val))
set_val = [None, 2]
self.assertEqual(format_cardinality(set_val), tuple(set_val))
set_val = [2, 3]
self.assertEqual(format_cardinality(set_val), tuple(set_val))

# Test exact value tuple set
set_val = (5, 5)
self.assertEqual(format_cardinality(set_val), set_val)

# Test set failures
with self.assertRaises(ValueError):
format_cardinality("a")

with self.assertRaises(ValueError):
format_cardinality([1])

with self.assertRaises(ValueError):
format_cardinality([1, 2, 3])

with self.assertRaises(ValueError):
format_cardinality({1: 2, 3: 4})

with self.assertRaises(ValueError):
format_cardinality(-1)

with self.assertRaises(ValueError):
format_cardinality((1, "b"))

with self.assertRaises(ValueError):
format_cardinality((1, 2, 3))

with self.assertRaises(ValueError):
format_cardinality((-1, 1))

with self.assertRaises(ValueError):
format_cardinality((1, -5))

with self.assertRaises(ValueError) as exc:
format_cardinality((5, 1))
self.assertIn("Minimum larger than maximum ", str(exc))
7 changes: 7 additions & 0 deletions test/test_validation_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ def tearDown(self):
sys.stdout = self.stdout_orig
self.capture.close()

def _clear_output(self):
self.capture.seek(0)
self.capture.truncate()

def _get_captured_output(self):
out = [txt.strip() for txt in self.capture.getvalue().split('\n') if txt]

Expand All @@ -42,6 +46,9 @@ def test_property_values_cardinality(self):
doc = odml.Document()
sec = odml.Section(name="sec", type="sec_type", parent=doc)

# Making sure only the required warnings are tested
self._clear_output()

# -- Test cardinality validation warnings on Property init
# Test warning when setting invalid minimum
_ = odml.Property(name="prop_card_min", values=[1], val_cardinality=(2, None), parent=sec)
Expand Down