diff options
author | Jordan Augé <jordan.auge+fdio@email.com> | 2017-02-24 14:58:01 +0100 |
---|---|---|
committer | Jordan Augé <jordan.auge+fdio@cisco.com> | 2017-02-24 18:36:29 +0000 |
commit | 85a341d645b57b7cd88a26ed2ea0a314704240ea (patch) | |
tree | bdda2b35003aae20103a796f86daced160b8a730 /netmodel/model/attribute.py | |
parent | 9b30fc10fb1cbebe651e5a107e8ca5b24de54675 (diff) |
Initial commit: vICN
Change-Id: I7ce66c4e84a6a1921c63442f858b49e083adc7a7
Signed-off-by: Jordan Augé <jordan.auge+fdio@cisco.com>
Diffstat (limited to 'netmodel/model/attribute.py')
-rw-r--r-- | netmodel/model/attribute.py | 262 |
1 files changed, 262 insertions, 0 deletions
diff --git a/netmodel/model/attribute.py b/netmodel/model/attribute.py new file mode 100644 index 00000000..b69ee1bf --- /dev/null +++ b/netmodel/model/attribute.py @@ -0,0 +1,262 @@ +#!/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. +# + +import abc +import copy +import logging +import operator +import types + +from netmodel.model.mapper import ObjectSpecification +from netmodel.model.type import is_type +from netmodel.util.meta import inheritors +from netmodel.util.misc import is_iterable +from vicn.core.sa_collections import InstrumentedList, _list_decorators + +log = logging.getLogger(__name__) +instance_dict = operator.attrgetter('__dict__') + +class NEVER_SET: None + +#------------------------------------------------------------------------------ +# Attribute Multiplicity +#------------------------------------------------------------------------------ + +class Multiplicity: + _1_1 = '1_1' + _1_N = '1_N' + _N_1 = 'N_1' + _N_N = 'N_N' + + + @staticmethod + def reverse(value): + reverse_map = { + Multiplicity._1_1: Multiplicity._1_1, + Multiplicity._1_N: Multiplicity._N_1, + Multiplicity._N_1: Multiplicity._1_N, + Multiplicity._N_N: Multiplicity._N_N, + } + return reverse_map[value] + + +# Default attribute properties values (default to None) +DEFAULT = { + 'multiplicity' : Multiplicity._1_1, + 'mandatory' : False, +} + +#------------------------------------------------------------------------------ +# Attribute +#------------------------------------------------------------------------------ + +class Attribute(abc.ABC, ObjectSpecification): + properties = [ + 'name', + 'type', + 'description', + 'default', + 'choices', + 'mandatory', + 'multiplicity', + 'ro', + 'auto', + 'func', + 'requirements', + 'reverse_name', + 'reverse_description', + 'reverse_auto' + ] + + def __init__(self, *args, **kwargs): + for key in Attribute.properties: + value = kwargs.pop(key, NEVER_SET) + setattr(self, key, value) + + if len(args) == 1: + self.type, = args + elif len(args) == 2: + self.name, self.type = args + assert is_type(self.type) + + self.is_aggregate = False + + self._reverse_attributes = list() + + #-------------------------------------------------------------------------- + # Display + #-------------------------------------------------------------------------- + + def __repr__(self): + return '<Attribute {}>'.format(self.name) + + __str__ = __repr__ + + #-------------------------------------------------------------------------- + # Descriptor protocol + # + # see. https://docs.python.org/3/howto/descriptor.html + #-------------------------------------------------------------------------- + + def __get__(self, instance, owner=None): + if instance is None: + return self + + value = instance_dict(instance).get(self.name, NEVER_SET) + + # Case : collection attribute + if self.is_collection: + if value is NEVER_SET: + if isinstance(self.default, types.FunctionType): + default = self.default(instance) + else: + default = self.default + value = InstrumentedList(default) + value._attribute = self + value._instance = instance + self.__set__(instance, value) + return value + return value + + # Case : scalar attribute + + if value in (None, NEVER_SET) and self.auto not in (None, NEVER_SET): + # Automatic instanciation + if not self.requirements in (None, NEVER_SET) and \ + self.requirements: + log.warning('Ignored requirement {}'.format(self.requirements)) + value = instance.auto_instanciate(self) + self.__set__(instance, value) + return value + + if value is NEVER_SET: + if isinstance(self.default, types.FunctionType): + value = self.default(instance) + else: + value = copy.deepcopy(self.default) + self.__set__(instance, value) + return value + + return value + + def __set__(self, instance, value): + if instance is None: + return + + if self.is_collection: + if not isinstance(value, InstrumentedList): + value = InstrumentedList(value) + value._attribute = self + value._instance = instance + + instance_dict(instance)[self.name] = value + if hasattr(instance, '_state'): + instance._state.attr_dirty.add(self.name) + instance._state.dirty = True + + def __delete__(self, instance): + raise NotImplementedError + + #-------------------------------------------------------------------------- + # Accessors + #-------------------------------------------------------------------------- + + def __getattribute__(self, name): + value = super().__getattribute__(name) + if value is NEVER_SET: + if name == 'default': + return list() if self.is_collection else None + return DEFAULT.get(name, None) + return value + + # Shortcuts + + def has_reverse_attribute(self): + return self.reverse_name and self.multiplicity + + @property + def is_collection(self): + return self.multiplicity in (Multiplicity._1_N, Multiplicity._N_N) + + def is_set(self, instance): + return self.name in instance_dict(instance) + + #-------------------------------------------------------------------------- + # Operations + #-------------------------------------------------------------------------- + + def merge(self, parent): + for prop in Attribute.properties: + # NOTE: we cannot use getattr otherwise we get the default value, + # and we never override + value = vars(self).get(prop, NEVER_SET) + if value is not NEVER_SET and not is_iterable(value): + continue + + parent_value = vars(parent).get(prop, NEVER_SET) + if parent_value is NEVER_SET: + continue + + if parent_value: + if is_iterable(value): + value.extend(parent_value) + else: + setattr(self, prop, parent_value) + + #-------------------------------------------------------------------------- + # Attribute values + #-------------------------------------------------------------------------- + + def _handle_getitem(self, instance, item): + return item + + def _handle_add(self, instance, item): + instance._state.dirty = True + instance._state.attr_dirty.add(self.name) + print('marking', self.name, 'as dirty') + return item + + def _handle_remove(self, instance, item): + instance._state.dirty = True + instance._state.attr_dirty.add(self.name) + print('marking', self.name, 'as dirty') + + def _handle_before_remove(self, instance): + pass + + #-------------------------------------------------------------------------- + # Attribute values + #-------------------------------------------------------------------------- + +class Relation(Attribute): + properties = Attribute.properties[:] + properties.extend([ + 'reverse_name', + 'reverse_description', + 'multiplicity', + ]) + +class SelfRelation(Relation): + def __init__(self, *args, **kwargs): + if args: + if not len(args) == 1: + raise ValueError('Bad initialized for SelfRelation') + name, = args + super().__init__(name, None, *args, **kwargs) + else: + super().__init__(None, *args, **kwargs) |