diff options
Diffstat (limited to 'netmodel/model/object.py')
-rw-r--r-- | netmodel/model/object.py | 231 |
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()] + |