configurationengine/source/cone/confml/model.py
changeset 3 e7e0ae78773e
parent 0 2e8eeb919028
child 5 d2c80f5cab53
--- a/configurationengine/source/cone/confml/model.py	Fri Mar 12 08:30:17 2010 +0200
+++ b/configurationengine/source/cone/confml/model.py	Tue Aug 10 14:29:28 2010 +0300
@@ -21,9 +21,15 @@
  All Confml element attributes become attributes of this instance.
 """
 import types
+import sys
+import re
 from cone.public import api, exceptions, container, utils
 
 class ConfmlElement(api.Base):
+    def __init__(self, ref="", **kwargs):
+        super(ConfmlElement,self).__init__(ref, **kwargs)
+        self.lineno = None
+
     def _get_mapper(self,modelname):
         """
         Return a instance of appropriate mapper for given model.
@@ -48,6 +54,18 @@
             pass
     """ The description as a property """
     desc = property(get_desc,set_desc,del_desc)
+    
+class ConfmlData(api.Data):
+    """
+    The data element can contain any data setting for a feature. The data element can be 
+    a value definition for any type of data. It basically just links some data to a feature. 
+    The default Data attribute is 'data', but it can be any string. For example current use case 
+    is 'rfs'.
+    """
+    def __init__(self, **kwargs):
+        super(ConfmlData,self).__init__(**kwargs)
+        self.lineno = None
+
 class ConfmlConfiguration(ConfmlElement, api.Configuration):
     """
     Confml configuration class. 
@@ -59,6 +77,14 @@
         if kwargs.get('desc'):
             self.desc = kwargs.get('desc')
 
+    def _view_class(self):
+        return ConfmlView
+
+    def _feature_class(self):
+        return ConfmlFeature
+
+    def _configuration_class(self):
+        return ConfmlConfiguration
 
     def get_desc(self): 
         """
@@ -84,7 +110,7 @@
 
     def get_meta(self): 
         """
-        @return: The description of the Configuration.
+        @return: The meta element of the Configuration.
         """
         try:
             meta = getattr(self,ConfmlMeta.refname)
@@ -104,88 +130,13 @@
     """ The meta element as a property """
     meta = property(get_meta,set_meta,del_meta)
 
-
-class ConfmlGroup(ConfmlElement, api.Group):
-    """
-    Confml view.
+class ConfmlSettingAttributes(ConfmlElement):
     """
-    def __init__(self, ref="", **kwargs):
-        super(ConfmlGroup,self).__init__(ref,**kwargs)
-        if kwargs.get('icon'):
-            self.icon = kwargs.get('icon')
-        if kwargs.get('desc'):
-            self.desc = kwargs.get('desc')
-
-    def get_icon(self): 
-        try:
-            icon = getattr(self,ConfmlIcon.refname)
-            return icon.href
-        except AttributeError:
-            return None
-    def set_icon(self,value): self._add(ConfmlIcon(value))
-    def del_icon(self): 
-        try:
-            self._remove(ConfmlIcon.refname)
-        except exceptions.NotFound:
-            pass
-    """ The icon as a property """
-    icon = property(get_icon,set_icon,del_icon)
-
-    def get_desc(self): 
-        try:
-            desc = getattr(self,ConfmlDescription.refname)
-            return desc.text
-        except AttributeError:
-            return None
-    def set_desc(self,value): self._add(ConfmlDescription(value))
-    def del_desc(self): 
-        try:
-            self._remove(ConfmlDescription.refname)
-        except exceptions.NotFound:
-            pass
-    """ The description as a property """
-    desc = property(get_desc,set_desc,del_desc)
-
-
-class ConfmlView(api.View):
+    Abstract base class for setting attributes. This is used as 
+    a base in actual ConfmlSetting and ConfmlFeatureLink.
     """
-    Confml view.
-    """
-    def __init__(self, ref="", **kwargs):
-        super(ConfmlView,self).__init__(ref,**kwargs)
-        if kwargs.get('desc'):
-            self.desc = kwargs.get('desc')
-
-
-    def get_desc(self): 
-        try:
-            desc = getattr(self,ConfmlDescription.refname)
-            return desc.text
-        except AttributeError:
-            return None
-    def set_desc(self,value): self._add(ConfmlDescription(value))
-    def del_desc(self): 
-        try:
-            self._remove(ConfmlDescription.refname)
-        except exceptions.NotFound:
-            pass
-    """ The description as a property """
-    desc = property(get_desc,set_desc,del_desc)
-
-class ConfmlFeature(ConfmlElement, api.Feature):
-    pass
-
-class ConfmlSetting(ConfmlElement, api.Feature):
-    """
-    Confml setting class. Attribute 'options' contains options of this setting.
-    """
-    supported_types = ['int',
-                       'string',
-                       'boolean',
-                       'selection']
     def __init__(self, ref,**kwargs):
-        super(ConfmlSetting,self).__init__(ref,**kwargs)
-        self.type = kwargs.get('type',None)
+        super(ConfmlSettingAttributes,self).__init__(ref,**kwargs)
         if kwargs.get('desc'):
             self.desc = kwargs.get('desc')
         if kwargs.get('minOccurs'):
@@ -196,10 +147,10 @@
             self.maxLength = kwargs.get('maxLength')
         if kwargs.get('minLength'):
             self.minLength = kwargs.get('minLength')
-        if kwargs.get('mapKey'):
-            self.mapKey = kwargs.get('mapKey')
-        if kwargs.get('mapValue'):
-            self.mapValue = kwargs.get('mapValue')
+        self.mapKey = kwargs.get('mapKey')
+        self.mapValue = kwargs.get('mapValue')
+        self.displayName = kwargs.get('displayName')
+
         
         self.readOnly = kwargs.get('readOnly',None)
         self.constraint = kwargs.get('constraint',None)
@@ -212,39 +163,6 @@
         """
         return api.ValueRe('.*')
 
-    def add_property(self, **kwargs):
-        """
-        @param name=str: property name 
-        @param value=str: property value
-        @param unit=str: property unit, e.g. kB
-        """
-        self._add(ConfmlProperty(**kwargs), container.APPEND)
-
-    def get_property(self, name):
-        """
-        @param name: The name of the property
-        """
-        for property in utils.get_list(self._get(ConfmlProperty.refname)):
-            if property.name == name:
-                return property
-        raise exceptions.NotFound("ConfmlProperty with name %s not found!" % name)
-
-    def remove_property(self, name):
-        """
-        remove a given option from this feature by name. 
-        @param name: 
-        """
-        for property in self._get(ConfmlProperty.refname):
-            if property.name == name:
-                return self._remove(property.get_fullref())
-        raise exceptions.NotFound("ConfmlProperty with name %s not found!" % name)
-
-    def list_properties(self):
-        """
-        Return a array of all Feature children references under this object.
-        """
-        return [obj.name for obj in utils.get_list(self._get(ConfmlProperty.refname))]
-
     def get_maxlength(self): 
         try:
             return getattr(self,ConfmlMaxLength.refname).value
@@ -259,7 +177,7 @@
             self._remove(ConfmlMaxLength.refname)
         except exceptions.NotFound:
             pass
-    """ The description as a property """
+    """ The maxLength as a property """
     maxLength = property(get_maxlength,set_maxlength,del_maxlength)
 
     def get_minlength(self): 
@@ -276,8 +194,25 @@
             self._remove(ConfmlMinLength.refname)
         except exceptions.NotFound:
             pass
-    """ The description as a property """
+    """ The minLength as a property """
     minLength = property(get_minlength,set_minlength,del_minlength)
+    
+    def get_length(self): 
+        try:
+            return getattr(self,ConfmlLength.refname).value
+        except AttributeError:
+            return None
+
+    def set_length(self,value): 
+        self._add(ConfmlLength(value))
+
+    def del_length(self): 
+        try:
+            self._remove(ConfmlLength.refname)
+        except exceptions.NotFound:
+            pass
+    """ The length as a property """
+    length = property(get_length,set_length,del_length)
 
     def get_minInclusive(self): 
         try:
@@ -390,22 +325,112 @@
 
     @property
     def properties(self):
-        dict = {}
-        for property in utils.get_list(self._get(ConfmlProperty.refname)):
-            dict[property.name] = property
-        return  dict
+        propdict = {}
+        for prop in self._objects(type=api.Property):
+            propdict[prop.name] = prop
+        return  propdict
 
-    def get_rfs(self,):
-        return super(ConfmlSetting,self).get_value('rfs')
+    def get_rfs(self):
+        return super(ConfmlSettingAttributes,self).get_value('rfs')
 
     def set_rfs(self, value):
-        super(ConfmlSetting,self).set_value('rfs',value)
+        super(ConfmlSettingAttributes,self).set_value('rfs',value)
 
     def del_rfs(self):
-        super(ConfmlSetting,self).del_value('rfs')
+        super(ConfmlSettingAttributes,self).del_value('rfs')
 
     rfs = property(get_rfs,set_rfs,del_rfs)
 
+
+class ConfmlGroup(ConfmlElement, api.Group):
+    """
+    Confml view.
+    """
+    def __init__(self, ref="", **kwargs):
+        super(ConfmlGroup,self).__init__(ref,**kwargs)
+        if kwargs.get('icon'):
+            self.icon = kwargs.get('icon')
+        if kwargs.get('desc'):
+            self.desc = kwargs.get('desc')
+
+    def _group_class(self):
+        return ConfmlGroup
+
+    def _featurelink_class(self):
+        return ConfmlFeatureLink
+
+    def get_icon(self): 
+        try:
+            icon = getattr(self,ConfmlIcon.refname)
+            return icon.href
+        except AttributeError:
+            return None
+    def set_icon(self,value): self._add(ConfmlIcon(value))
+    def del_icon(self): 
+        try:
+            self._remove(ConfmlIcon.refname)
+        except exceptions.NotFound:
+            pass
+    """ The icon as a property """
+    icon = property(get_icon,set_icon,del_icon)
+
+    def get_desc(self): 
+        try:
+            desc = getattr(self,ConfmlDescription.refname)
+            return desc.text
+        except AttributeError:
+            return None
+    def set_desc(self,value): self._add(ConfmlDescription(value))
+    def del_desc(self): 
+        try:
+            self._remove(ConfmlDescription.refname)
+        except exceptions.NotFound:
+            pass
+    """ The description as a property """
+    desc = property(get_desc,set_desc,del_desc)
+
+
+class ConfmlView(api.View, ConfmlGroup):
+    """
+    Confml view.
+    """
+    def __init__(self, ref="", **kwargs):
+        super(ConfmlView,self).__init__(ref,**kwargs)
+        if kwargs.get('desc'):
+            self.desc = kwargs.get('desc')
+    
+    def get_desc(self): 
+        try:
+            desc = getattr(self,ConfmlDescription.refname)
+            return desc.text
+        except AttributeError:
+            return None
+    def set_desc(self,value): self._add(ConfmlDescription(value))
+    def del_desc(self): 
+        try:
+            self._remove(ConfmlDescription.refname)
+        except exceptions.NotFound:
+            pass
+    """ The description as a property """
+    desc = property(get_desc,set_desc,del_desc)
+
+
+class ConfmlFeature(ConfmlElement, api.Feature):
+    def _feature_class(self):
+        return ConfmlSetting
+
+class ConfmlSetting(ConfmlSettingAttributes, api.Feature):
+    """
+    Confml setting class. Attribute 'options' contains options of this setting.
+    """
+    supported_types = ['int',
+                       'string',
+                       'boolean',
+                       'selection']
+    def __init__(self, ref,**kwargs):
+        super(ConfmlSetting,self).__init__(ref,**kwargs)
+        self.type = kwargs.get('type',None)
+
     def get_value_cast(self, value, attr=None):
         """
         A function to perform the value type casting in get operation  
@@ -425,13 +450,17 @@
         @param value: the value to cast 
         @param attr: the attribute which is fetched from model (normally in confml either None='data' or 'rfs')
         """
+        # Add a exception case for None value, because the data casting will always fail for it
+        if value == None:
+            return value
+        
         if not attr or attr == 'data':
             return self.set_data_cast(value)
         elif attr == 'rfs':
             return self.set_rfs_cast(value)
         else:
             return value
-
+        
     def get_data_cast(self, value):
         """
         A function to perform the data type casting in get operation  
@@ -600,48 +629,78 @@
     """
     Confml setting class for multiSelection type.
     """
-
+    
+    # Pattern for checking whether a data value should be interpreted
+    # in the old style (e.g. '"opt1" "opt2" "opt3"')
+    OLD_STYLE_DATA_PATTERN = re.compile(r'"[^"]*([^"]*" ")*[^"]*"')
+    
     def __init__(self, ref,**kwargs):
         kwargs['type'] = 'multiSelection'
         ConfmlSetting.__init__(self,ref,**kwargs)
         
-
+    def add_data(self, data):
+        """
+        Add a data value.
+        @param data: A Data object  
+        """
+        # If there are existing data objects added to the proxy, and they
+        # are not in the same DataContainer (ConfML data section), change the
+        # policy to replace
+        if self.dataproxy.datas.get(data.attr):
+            existing_data_obj = self.dataproxy.datas[data.attr][-1]
+            existing_obj_parent = existing_data_obj._find_parent_or_default(type=api.DataContainer)
+            new_obj_parent = data._find_parent_or_default(type=api.DataContainer)
+            
+            if existing_obj_parent is not new_obj_parent:
+                self.dataproxy.datas[data.attr] = []
+        
+        self.dataproxy._add_data(data)
+    
     def get_valueset(self):
         """
         Get the ValueSet object for this feature, that has the list of available values.
         """
         return api.Feature.get_valueset(self)
-
-    def get_data_cast(self, value):
-        """
-        A function to perform the value type casting in get operation  
-        """
-        try:
-            if not isinstance(value, types.ListType):
-                values = value.split('" "')
-                for i in range(len(values)):
-                    if values[i].startswith('"'):
-                        values[i] = values[i][1:] 
-                    if values[i].endswith('"'):
-                        values[i] = values[i][:-1]
-                return values
-            return value
-        except AttributeError:
-            return None
     
-    def set_data_cast(self, value):
-        """
-        A function to perform the value type casting in the set operation  
-        """
+    def convert_data_to_value(self, data_objects, cast=True, attr=None):
+        if len(data_objects) == 1:
+            d = data_objects[0]
+            
+            # Special handling for cases where the data is in the old format
+            # (pre-2.88 ConfML spec)
+            if d.value is not None:
+                if self.OLD_STYLE_DATA_PATTERN.match(d.value):
+                    return tuple([v.rstrip('"').lstrip('"') for v in d.value.split('" "')])
+            
+            # Single data object with empty="true" means that nothing is selected
+            if d.empty: return ()
         
-        if isinstance(value, list):
-            value = " ".join(['"%s"' % elem for elem in value])
-        return value
+        # Read each data value (or name-ID mapped value) into result
+        result = []
+        for data_obj in data_objects:
+            if data_obj.map:
+                value = self._resolve_name_id_mapped_value(data_obj.map, cast_value=cast)
+            else:
+                value = data_obj.value
+            result.append(value)
+        result = utils.distinct_array(result)
+        
+        # Handle None in the result (data element with no text data)
+        if None in result:
+            # If the empty string is a valid option, change the None to that,
+            # otherwise ignore
+            index = result.index(None)
+            if '' in self.get_valueset():   result[index] = ''
+            else:                           del result[index]
+        
+        return tuple(result)
     
-    def set_value(self, value):
-        if not isinstance(value, types.ListType):
-            raise ValueError("Only list types are allowed.")
-        self.value = value
+    def convert_value_to_data(self, value, attr=None):
+        if not isinstance(value, (types.ListType, types.TupleType, types.NoneType)):
+            raise ValueError("Only list, tuple and None types are allowed.")
+        
+        if value:   return [api.Data(fqr=self.fqr, value=v, attr=attr) for v in value]
+        else:       return [api.Data(fqr=self.fqr, empty=True, attr=attr)]
 
 class ConfmlDateSetting(ConfmlSetting):
     """
@@ -667,6 +726,32 @@
         kwargs['type'] = 'dateTime'
         ConfmlSetting.__init__(self,ref,**kwargs)
 
+class ConfmlHexBinarySetting(ConfmlSetting):
+    """
+    Confml setting class for hex-binary type.
+    """
+    def __init__(self, ref,**kwargs):
+        kwargs['type'] = 'hexBinary'
+        ConfmlSetting.__init__(self,ref,**kwargs)
+    
+    def get_valueset(self):
+        return api.ValueRe(r'^([0123456789ABCDEF]{2})*$')
+
+    def get_data_cast(self, value):
+        value = value or '' # Handle None
+        if value not in self.get_valueset():
+            raise ValueError("Cannot convert value %r of setting '%s' into binary data: Not a valid hex string", value)
+        
+        temp = []
+        for i in xrange(len(value) / 2):
+            start = i * 2
+            end   = start + 2 
+            temp.append(chr(int(value[start:end], 16)))
+        return ''.join(temp)
+    
+    def set_data_cast(self, value):
+        return ''.join(['%02X' % ord(c) for c in value])
+
 class ConfmlDurationSetting(ConfmlSetting):
     """
     Confml setting class for date type.
@@ -711,10 +796,12 @@
 
 class ConfmlLocalPath(ConfmlElement, api.Feature):
     """
-    Confml file class. Attribute setting.
+    Confml file class. Attribute setting. 
+    The localPath "name" is always the same as its ref 'localPath'
     """
     def __init__(self, ref='localPath', **kwargs):
         kwargs['type'] = 'string'
+        kwargs['name'] = ref
         ConfmlElement.__init__(self, **kwargs)
         api.Feature.__init__(self, ref, **kwargs)
         self.readOnly = kwargs.get('readOnly', None)
@@ -723,14 +810,45 @@
 class ConfmlTargetPath(ConfmlElement, api.Feature):
     """
     Confml file class. Attribute setting.
+    The targetPath "name" is always the same as its ref 'targetPath'
     """
     def __init__(self, ref='targetPath', **kwargs):
         kwargs['type'] = 'string'
+        kwargs['name'] = ref
         ConfmlElement.__init__(self, **kwargs)
         api.Feature.__init__(self, ref, **kwargs)
         self.readOnly = kwargs.get('readOnly', None)
 
 
+class ConfmlFeatureLink(ConfmlSettingAttributes, api.FeatureLink):
+    """
+    ConfmlFeatureLink object is the setting reference object inside confml 
+    group / view. It can populate the actual FeatureProxy objects under the
+    particular group / view object.
+    """
+
+    """ the override_attributes explicitly states which feature link attributes can be overridden """
+    override_attributes = ['name', 
+                           'desc', 
+                           'minLength',
+                           'maxLength',
+                           'minOccurs',
+                           'maxOccurs',
+                           'minInclusive',
+                           'maxInclusive',
+                           'minExclusive',
+                           'maxExclusive',
+                           'pattern',
+                           'totalDigits',
+                           'options',
+                           'properties',
+                           'readOnly'
+                           ]
+    def __init__(self, ref,**kwargs):
+        ConfmlSettingAttributes.__init__(self, ref,**kwargs)
+        api.FeatureLink.__init__(self, ref, **kwargs)
+        self.type = kwargs.get('type',None)
+
 class ConfmlMeta(api.Base):
     """
     Confml meta element
@@ -751,6 +869,9 @@
     def __setitem__(self, key, value):
         self.array[key] = value
 
+    def __len__(self):
+        return len(self.array)
+    
     def __str__(self):
         tempstr = "ConfmlMeta object\n"
         counter = 0
@@ -790,6 +911,16 @@
     def replace(self, index, tag, value, ns=None, dict=None):
         self.array[index] = ConfmlMetaProperty(tag, value, ns, attrs=dict)
 
+    def update(self, data):
+        """
+        Update this the ConfmlMeta object meta with the given data.
+        @param data: The input ConfmlMeta data to update for this object
+        """
+        if data:
+            for property in data.array:
+                self.set_property_by_tag(property.tag, property.value, property.ns, property.attrs)
+
+
     def clear(self, value):
         self.array = []
 
@@ -807,22 +938,38 @@
         return -1
 
     def find_by_attribute(self, name, value):
-        for item in self.array:
-            if item.attrs.has_key(name) and item.attrs[name] == value: 
+        for item in self.array:            
+            if item.attrs.has_key(name) and item.attrs[name] == value:
                 return self.array.index(item)
         return -1
 
-    def get_property_by_tag(self, tag):
+    def get_property_by_tag(self, tag, attrs={}):
         """
         Try to find the element by its tag in the meta elem array.
         @param tag: the tag that is searched
         @return: the ConfmlMetaProperty object if it is found. None if element with tag is not found.
-        """
+        """ 
         for item in self.array:
             if item.tag == tag:
-                return item
+                if not item.attrs or (item.attrs.get("name", None) == attrs.get("name", None)):
+                    return item
         return None
 
+    def set_property_by_tag(self, tag, value, ns=None, attributes=None):
+        """
+        Try to find the element by its tag and set it the meta elem array. 
+        This will either create a new element to the meta or replace first 
+        encountered elem in array. 
+        @param tag: the tag that is searched
+        @return: the ConfmlMetaProperty object if it is found. None if element with tag is not found.
+        """
+                
+        if self.get_property_by_tag(tag, attributes):
+            property = self.get_property_by_tag(tag, attributes) 
+            property.value = value
+            property.attrs = attributes or {}
+        else:
+            self.add(tag, value, ns, attributes)
 
 class ConfmlDescription(api.Base):
     """
@@ -844,21 +991,6 @@
         self.href = href
 
 
-class ConfmlProperty(api.Base):
-    """
-    Confml meta element
-    """
-    refname = "_property"
-    def __init__(self, **kwargs):
-        """
-        @param name=str: name string 
-        @param value=str: value for the property, string 
-        @param unit=str: unit of the property
-        """
-        super(ConfmlProperty,self).__init__(self.refname)
-        self.name = kwargs.get('name',None)
-        self.value = kwargs.get('value',None)
-        self.unit = kwargs.get('unit',None)
 
 
 class ConfmlMetaProperty(api.Base):
@@ -873,10 +1005,7 @@
         self.tag = tag
         self.value = value
         self.ns = ns
-        if kwargs.has_key("attrs") and kwargs["attrs"] != None:
-            self.attrs = dict(kwargs["attrs"])
-        else:
-            self.attrs = {}
+        self.attrs = dict(kwargs.get('attrs') or {})
 
     def __cmp__(self, other):
         try:
@@ -890,9 +1019,28 @@
     def __str__(self):
         return "Tag: %s Value: %s Namespace: %s Attributes: % s" % (self.tag, self.value, self.ns, repr(self.attrs))         
         
-            
 
-class ConfmlLength(api.Base):
+class ConfmlNumericValue(api.Base):
+    """
+    Confml base class for all float type properties.
+    Performs a simple value casting from string to int in value setting.
+    """
+    def __init__(self, ref="", **kwargs):
+        super(ConfmlNumericValue,self).__init__(ref, **kwargs)
+        self._value = None
+        
+    def get_value(self): return self._value
+    def del_value(self): self._value = None
+    def set_value(self, value): 
+        if utils.is_float(value):
+            self._value = float(value) 
+        else:
+            self._value = int(value)
+    """ The value as a property """
+    value = property(get_value,set_value,del_value)
+
+
+class ConfmlLength(ConfmlNumericValue):
     """
     Confml length element
     """
@@ -901,7 +1049,7 @@
         super(ConfmlLength,self).__init__(self.refname)
         self.value = value
 
-class ConfmlMaxLength(api.Base):
+class ConfmlMaxLength(ConfmlNumericValue):
     """
     Confml max element
     """
@@ -910,7 +1058,7 @@
         super(ConfmlMaxLength,self).__init__(self.refname)
         self.value = value
 
-class ConfmlMinLength(api.Base):
+class ConfmlMinLength(ConfmlNumericValue):
     """
     Confml min element
     """
@@ -919,7 +1067,7 @@
         super(ConfmlMinLength,self).__init__(self.refname)
         self.value = value
 
-class ConfmlMinInclusive(api.Base):
+class ConfmlMinInclusive(ConfmlNumericValue):
     """
     Confml minInclusive element
     """
@@ -928,7 +1076,7 @@
         super(ConfmlMinInclusive,self).__init__(self.refname)
         self.value = value
 
-class ConfmlMaxInclusive(api.Base):
+class ConfmlMaxInclusive(ConfmlNumericValue):
     """
     Confml minInclusive element
     """
@@ -937,7 +1085,7 @@
         super(ConfmlMaxInclusive,self).__init__(self.refname)
         self.value = value
 
-class ConfmlMinExclusive(api.Base):
+class ConfmlMinExclusive(ConfmlNumericValue):
     """
     Confml minExclusive element
     """
@@ -946,7 +1094,7 @@
         super(ConfmlMinExclusive,self).__init__(self.refname)
         self.value = value
 
-class ConfmlMaxExclusive(api.Base):
+class ConfmlMaxExclusive(ConfmlNumericValue):
     """
     Confml maxExclusive element
     """
@@ -964,7 +1112,7 @@
         super(ConfmlPattern,self).__init__(self.refname)
         self.value = value   
 
-class ConfmlTotalDigits(api.Base):
+class ConfmlTotalDigits(ConfmlNumericValue):
     """
     Confml totalDigits element
     """