aboutsummaryrefslogtreecommitdiffstats
path: root/netmodel/model/object.py
diff options
context:
space:
mode:
Diffstat (limited to 'netmodel/model/object.py')
-rw-r--r--netmodel/model/object.py231
1 files changed, 231 insertions, 0 deletions
diff --git a/netmodel/model/object.py b/netmodel/model/object.py
new file mode 100644
index 00000000..32d3a833
--- /dev/null
+++ b/netmodel/model/object.py
@@ -0,0 +1,231 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2017 Cisco and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from abc import ABCMeta
+
+from netmodel.model.attribute import Attribute
+from netmodel.model.type import BaseType
+from netmodel.model.mapper import ObjectSpecification
+
+# Warning and error messages
+
+E_UNK_RES_NAME = 'Unknown resource name for attribute {} in {} ({}) : {}'
+
+class ObjectMetaclass(ABCMeta):
+ """
+ Object metaclass allowing non-uniform attribute declaration.
+ """
+
+ def __init__(cls, class_name, parents, attrs):
+ """
+ Args:
+ cls: The class type we're registering.
+ class_name: A String containing the class_name.
+ parents: The parent class types of 'cls'.
+ attrs: The attribute (members) of 'cls'.
+ """
+ super().__init__(class_name, parents, attrs)
+ cls._sanitize()
+
+class Object(BaseType, metaclass = ObjectMetaclass):
+
+ def __init__(self, **kwargs):
+ """
+ Object constructor.
+
+ Args:
+ kwargs: named arguments consisting in object attributes to be
+ initialized at construction.
+ """
+ mandatory = { a.name for a in self.iter_attributes() if a.mandatory }
+
+ for key, value in kwargs.items():
+ attribute = self.get_attribute(key)
+ if issubclass(attribute.type, Object):
+ if attribute.is_collection:
+ new_value = list()
+ for x in value:
+ if isinstance(x, str):
+ resource = self._state.manager.by_name(x)
+ elif isinstance(x, UUID):
+ resource = self._state.manager.by_uuid(x)
+ else:
+ resource = x
+ if not resource:
+ raise LurchException(E_UNK_RES_NAME.format(key,
+ self.name, self.__class__.__name__, x))
+ new_value.append(resource._state.uuid)
+ value = new_value
+ else:
+ if isinstance(value, str):
+ resource = self._state.manager.by_name(value)
+ elif isinstance(value, UUID):
+ resource = self._state.manager.by_uuid(value)
+ else:
+ resource = value
+ if not resource:
+ raise LurchException(E_UNK_RES_NAME.format(key,
+ self.name, self.__class__.__name__, value))
+ value = resource._state.uuid
+ setattr(self, key, value)
+ mandatory -= { key }
+
+ # Check that all mandatory atttributes have been set
+ # Mandatory resource attributes will be marked as pending since they
+ # might be discovered
+ # Eventually, their absence will be discovered at runtime
+ if mandatory:
+ raise Exception('Mandatory attributes not set: %r' % (mandatory,))
+
+ # Assign backreferences (we need attribute to be initialized, so it has
+ # to be done at the end of __init__
+ for other_instance, attribute in self.iter_backrefs():
+ if attribute.is_collection:
+ collection = getattr(other_instance, attribute.name)
+ collection.append(self)
+ else:
+ setattr(other_instance, attribute.name, self)
+
+ #--------------------------------------------------------------------------
+ # Object model
+ #--------------------------------------------------------------------------
+
+ @classmethod
+ def get_attribute(cls, key):
+ return getattr(cls, key)
+
+ @classmethod
+ def _sanitize(cls):
+ """Sanitize the object model to accomodate for multiple declaration
+ styles
+
+ In particular, this method:
+ - set names to all attributes
+ """
+ cls._reverse_attributes = dict()
+ cur_reverse_attributes = dict()
+ for name, obj in vars(cls).items():
+ if not isinstance(obj, ObjectSpecification):
+ continue
+ if isinstance(obj, Attribute):
+ obj.name = name
+
+ # Remember whether a reverse_name is defined before loading
+ # inherited properties from parent
+ has_reverse = bool(obj.reverse_name)
+
+ # Handle overloaded attributes
+ # By recursion, it is sufficient to look into the parent
+ for base in cls.__bases__:
+ if hasattr(base, name):
+ parent_attribute = getattr(base, name)
+ obj.merge(parent_attribute)
+ assert obj.type
+
+ # Handle reverse attribute
+ #
+ # NOTE: we need to do this after merging to be sure we get all
+ # properties inherited from parent (eg. multiplicity)
+ if has_reverse:
+ a = {
+ 'name' : obj.reverse_name,
+ 'description' : obj.reverse_description,
+ 'multiplicity' : Multiplicity.reverse(obj.multiplicity),
+ 'auto' : obj.reverse_auto,
+ }
+ reverse_attribute = Attribute(cls, **a)
+ reverse_attribute.is_aggregate = True
+
+ cur_reverse_attributes[obj.type] = reverse_attribute
+
+ if not obj in cls._reverse_attributes:
+ cls._reverse_attributes[obj] = list()
+ cls._reverse_attributes[obj].append(reverse_attribute)
+
+ for cls, a in cur_reverse_attributes.items():
+ setattr(cls, a.name, a)
+
+ @classmethod
+ def iter_attributes(cls, aggregates = False):
+ for name in dir(cls):
+ attribute = getattr(cls, name)
+ if not isinstance(attribute, Attribute):
+ continue
+ if attribute.is_aggregate and not aggregates:
+ continue
+
+ yield attribute
+
+ def get_attributes(self, aggregates = False):
+ return list(self.iter_attributes(aggregates = aggregates))
+
+ def get_attribute_names(self, aggregates = False):
+ return set(a.name for a in self.iter_attributes(aggregates = \
+ aggregates))
+
+ def get_attribute_dict(self, field_names = None, aggregates = False,
+ uuid = True):
+ assert not field_names or field_names.is_star()
+ attributes = self.get_attributes(aggregates = aggregates)
+
+ ret = dict()
+ for a in attributes:
+ if not a.is_set(self):
+ continue
+ value = getattr(self, a.name)
+ if a.is_collection:
+ ret[a.name] = list()
+ for x in value:
+ if uuid and isinstance(x, Object):
+ x = x._state.uuid._uuid
+ ret[a.name].append(x)
+ else:
+ if uuid and isinstance(value, Object):
+ value = value._state.uuid._uuid
+ ret[a.name] = value
+ return ret
+
+ def get_tuple(self):
+ return (self.__class__, self._get_attribute_dict())
+
+ def format(self, fmt):
+ return fmt.format(**self.get_attribute_dict(uuid = False))
+
+ def iter_backrefs(self):
+ for base in self.__class__.mro():
+ if not hasattr(base, '_reverse_attributes'):
+ continue
+ for attr, rattrs in base._reverse_attributes.items():
+ instances = getattr(self, attr.name)
+ if not attr.is_collection:
+ instances = [instances]
+ for instance in instances:
+ # - instance = node
+ if instance in (None, NEVER_SET):
+ continue
+ for rattr in rattrs:
+ yield instance, rattr
+
+ #--------------------------------------------------------------------------
+ # Accessors
+ #--------------------------------------------------------------------------
+
+ @classmethod
+ def has_attribute(cls, name):
+ return name in [a.name for a in cls.attributes()]
+