# Copyright (c) 2014 Mirantis, Inc. # # 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 weakref from murano.dsl import constants from murano.dsl import dsl from murano.dsl import dsl_types from murano.dsl import exceptions from murano.dsl import helpers from murano.dsl import typespec from murano.dsl import yaql_integration class MuranoObject(dsl_types.MuranoObject): def __init__(self, murano_class, owner, object_store, object_id=None, name=None, known_classes=None, defaults=None, this=None): if known_classes is None: known_classes = {} self.__owner = owner.real_this if owner else None self.__object_id = object_id or helpers.generate_id() self.__type = murano_class self.__properties = {} self.__parents = {} self.__defaults = defaults or {} self.__this = this self.__name = name self.__extension = None self.__object_store = weakref.ref(object_store) self.__config = murano_class.package.get_class_config( murano_class.name) if not isinstance(self.__config, dict): self.__config = {} known_classes[murano_class.name] = self for parent_class in murano_class.parents(self.real_this.type): name = parent_class.name if name not in known_classes: obj = parent_class.new( owner, object_store, object_id=self.__object_id, known_classes=known_classes, defaults=defaults, this=self.real_this).object self.__parents[name] = known_classes[name] = obj else: self.__parents[name] = known_classes[name] self.__initialized = False @property def extension(self): return self.__extension @property def name(self): return self.real_this.__name @extension.setter def extension(self, value): self.__extension = value @property def object_store(self): return self.__object_store() def initialize(self, context, object_store, params): if self.__initialized: return for property_name in self.__type.properties: spec = self.__type.get_property(property_name) if spec.usage == typespec.PropertyUsages.Config: if property_name in self.__config: property_value = self.__config[property_name] else: property_value = dsl.NO_VALUE self.set_property(property_name, property_value) init = self.type.methods.get('.init') used_names = set() names = set(self.__type.properties) if init: names.update(init.arguments_scheme.iterkeys()) last_errors = len(names) init_args = {} while True: errors = 0 for property_name in names: if init and property_name in init.arguments_scheme: spec = init.arguments_scheme[property_name] is_init_arg = True else: spec = self.__type.get_property(property_name) is_init_arg = False if property_name in used_names: continue if spec.usage == typespec.PropertyUsages.Config: used_names.add(property_name) continue if spec.usage == typespec.PropertyUsages.Runtime: if not spec.has_default: used_names.add(property_name) continue property_value = dsl.NO_VALUE else: property_value = params.get(property_name, dsl.NO_VALUE) try: if is_init_arg: init_args[property_name] = property_value else: self.set_property(property_name, property_value) used_names.add(property_name) except exceptions.UninitializedPropertyAccessError: errors += 1 except exceptions.ContractViolationException: if spec.usage != typespec.PropertyUsages.Runtime: raise if not errors: break if errors >= last_errors: raise exceptions.CircularExpressionDependenciesError() last_errors = errors executor = helpers.get_executor(context) if not object_store.initializing and self.__extension is None: method = self.type.methods.get('__init__') if method: filtered_params = yaql_integration.filter_parameters( method.body, **params) self.__extension = method.invoke( executor, self, filtered_params[0], filtered_params[1], context) for parent in self.__parents.values(): parent.initialize(context, object_store, params) if not object_store.initializing and init: context[constants.CTX_ARGUMENT_OWNER] = self.real_this init.invoke(executor, self.real_this, (), init_args, context) self.__initialized = True @property def object_id(self): return self.__object_id @property def type(self): return self.__type @property def owner(self): return self.__owner @property def real_this(self): return self.__this or self def get_property(self, name, context=None): start_type, derived = self.__type, False caller_class = None if not context else helpers.get_type(context) if caller_class is not None and caller_class.is_compatible(self): start_type, derived = caller_class, True if name in start_type.properties: return self.cast(start_type)._get_property_value(name) else: declared_properties = start_type.find_single_property(name) if declared_properties: return self.cast(declared_properties).__properties[name] elif derived: return self.cast(caller_class)._get_property_value(name) else: raise exceptions.PropertyReadError(name, start_type) def _get_property_value(self, name): try: return self.__properties[name] except KeyError: raise exceptions.UninitializedPropertyAccessError( name, self.__type) def set_property(self, name, value, context=None): start_type, derived = self.__type, False caller_class = None if not context else helpers.get_type(context) if caller_class is not None and caller_class.is_compatible(self): start_type, derived = caller_class, True declared_properties = start_type.find_property(name) if context is None: context = self.object_store.executor.create_object_context(self) if len(declared_properties) > 0: declared_properties = self.type.find_property(name) values_to_assign = [] for mc in declared_properties: spec = mc.get_property(name) if (caller_class is not None and not helpers.are_property_modifications_allowed(context) and (spec.usage not in typespec.PropertyUsages.Writable or not derived)): raise exceptions.NoWriteAccessError(name) default = self.__config.get(name, spec.default) default = self.__defaults.get(name, default) default = helpers.evaluate(default, context) obj = self.cast(mc) values_to_assign.append((obj, spec.validate( value, self.real_this, self.real_this, default=default))) for obj, value in values_to_assign: obj.__properties[name] = value elif derived: obj = self.cast(caller_class) obj.__properties[name] = value else: raise exceptions.PropertyWriteError(name, start_type) def cast(self, cls): for p in helpers.traverse(self, lambda t: t.__parents.values()): if p.type is cls: return p raise TypeError('Cannot cast {0} to {1}'.format(self.type, cls)) def __repr__(self): return '<{0}/{1} {2} ({3})>'.format( self.type.name, self.type.version, self.object_id, id(self)) def to_dictionary(self, include_hidden=False): result = {} for parent in self.__parents.values(): result.update(parent.to_dictionary(include_hidden)) result.update({'?': { 'type': self.type.name, 'id': self.object_id, 'name': self.name, 'classVersion': str(self.type.version), 'package': self.type.package.name }}) if include_hidden: result.update(self.__properties) else: for property_name in self.type.properties: if property_name in self.__properties: spec = self.type.get_property(property_name) if spec.usage != typespec.PropertyUsages.Runtime: result[property_name] = self.__properties[ property_name] return result