@@ -82,6 +82,10 @@ class BaseProperty(base.BaseObject):
8282 :param oid: object id, UUID string as specified in RFC 4122. If no id is provided,
8383 an id will be generated and assigned. An id has to be unique
8484 within an odML Document.
85+ :param val_cardinality: Value cardinality defines how many values are allowed for this Property.
86+ By default unlimited values can be set.
87+ A required number of values can be set by assigning a tuple of the
88+ format "(min, max)".
8589 :param value: Legacy code to the 'values' attribute. If 'values' is provided,
8690 any data provided via 'value' will be ignored.
8791 """
@@ -91,7 +95,7 @@ class BaseProperty(base.BaseObject):
9195 def __init__ (self , name = None , values = None , parent = None , unit = None ,
9296 uncertainty = None , reference = None , definition = None ,
9397 dependency = None , dependency_value = None , dtype = None ,
94- value_origin = None , oid = None , value = None ):
98+ value_origin = None , oid = None , val_cardinality = None , value = None ):
9599
96100 try :
97101 if oid is not None :
@@ -115,6 +119,7 @@ def __init__(self, name=None, values=None, parent=None, unit=None,
115119 self ._definition = definition
116120 self ._dependency = dependency
117121 self ._dependency_value = dependency_value
122+ self ._val_cardinality = None
118123
119124 self ._dtype = None
120125 if dtypes .valid_type (dtype ):
@@ -129,6 +134,10 @@ def __init__(self, name=None, values=None, parent=None, unit=None,
129134
130135 self .parent = parent
131136
137+ # Cardinality should always be set after values have been added
138+ # since it is always tested against values when it is set.
139+ self .val_cardinality = val_cardinality
140+
132141 for err in validation .Validation (self ).errors :
133142 if err .is_error :
134143 msg = "\n \t - %s %s: %s" % (err .obj , err .rank , err .msg )
@@ -401,6 +410,11 @@ def values(self, new_value):
401410 raise ValueError (msg )
402411 self ._values = [dtypes .get (v , self .dtype ) for v in new_value ]
403412
413+ # Validate and inform user if the current values cardinality is violated
414+ valid = validation .Validation (self )
415+ for err in valid .errors :
416+ print ("%s: %s" % (err .rank .capitalize (), err .msg ))
417+
404418 @property
405419 def value_origin (self ):
406420 """
@@ -507,6 +521,88 @@ def dependency_value(self, new_value):
507521 new_value = None
508522 self ._dependency_value = new_value
509523
524+ @property
525+ def val_cardinality (self ):
526+ """
527+ The value cardinality of a Property. It defines how many values
528+ are minimally required and how many values should be maximally
529+ stored. Use 'values_set_cardinality' to set.
530+ """
531+ return self ._val_cardinality
532+
533+ @val_cardinality .setter
534+ def val_cardinality (self , new_value ):
535+ """
536+ Sets the values cardinality of a Property.
537+
538+ The following cardinality cases are supported:
539+ (n, n) - default, no restriction
540+ (d, n) - minimally d entries, no maximum
541+ (n, d) - maximally d entries, no minimum
542+ (d, d) - minimally d entries, maximally d entries
543+
544+ Only positive integers are supported. 'None' is used to denote
545+ no restrictions on a maximum or minimum.
546+
547+ :param new_value: Can be either 'None', a positive integer, which will set
548+ the maximum or an integer 2-tuple of the format '(min, max)'.
549+ """
550+ invalid_input = False
551+ exc_msg = "Can only assign positive single int or int-tuples of the format '(min, max)'"
552+
553+ # Empty values reset the cardinality to None.
554+ if not new_value or new_value == (None , None ):
555+ self ._val_cardinality = None
556+
557+ # Providing a single integer sets the maximum value in a tuple.
558+ elif isinstance (new_value , int ) and new_value > 0 :
559+ self ._val_cardinality = (None , new_value )
560+
561+ # Only integer 2-tuples of the format '(min, max)' are supported to set the cardinality
562+ elif isinstance (new_value , tuple ) and len (new_value ) == 2 :
563+ v_min = new_value [0 ]
564+ v_max = new_value [1 ]
565+
566+ min_int = isinstance (v_min , int ) and v_min >= 0
567+ max_int = isinstance (v_max , int ) and v_max >= 0
568+
569+ if max_int and min_int and v_max > v_min :
570+ self ._val_cardinality = (v_min , v_max )
571+
572+ elif max_int and not v_min :
573+ self ._val_cardinality = (None , v_max )
574+
575+ elif min_int and not v_max :
576+ self ._val_cardinality = (v_min , None )
577+
578+ else :
579+ invalid_input = True
580+
581+ # Use helpful exception message in the following case:
582+ if max_int and min_int and v_max < v_min :
583+ exc_msg = "Minimum larger than maximum (min=%s, max=%s)" % (v_min , v_max )
584+ else :
585+ invalid_input = True
586+
587+ if not invalid_input :
588+ # Validate and inform user if the current values cardinality is violated
589+ valid = validation .Validation (self )
590+ for err in valid .errors :
591+ print ("%s: %s" % (err .rank .capitalize (), err .msg ))
592+ else :
593+ raise ValueError (exc_msg )
594+
595+ def set_values_cardinality (self , min_val = None , max_val = None ):
596+ """
597+ Sets the values cardinality of a Property.
598+
599+ :param min_val: Required minimal number of values elements. None denotes
600+ no restrictions on values elements minimum. Default is None.
601+ :param max_val: Allowed maximal number of values elements. None denotes
602+ no restrictions on values elements maximum. Default is None.
603+ """
604+ self .val_cardinality = (min_val , max_val )
605+
510606 def remove (self , value ):
511607 """
512608 Remove a value from this property. Only the first encountered
0 commit comments