summaryrefslogtreecommitdiffstats
path: root/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/mixins.py
diff options
context:
space:
mode:
Diffstat (limited to 'azure/aria/aria-extension-cloudify/src/aria/aria/modeling/mixins.py')
-rw-r--r--azure/aria/aria-extension-cloudify/src/aria/aria/modeling/mixins.py333
1 files changed, 333 insertions, 0 deletions
diff --git a/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/mixins.py b/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/mixins.py
new file mode 100644
index 0000000..d58c25a
--- /dev/null
+++ b/azure/aria/aria-extension-cloudify/src/aria/aria/modeling/mixins.py
@@ -0,0 +1,333 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+"""
+ARIA modeling mix-ins module
+"""
+
+from sqlalchemy.ext import associationproxy
+from sqlalchemy import (
+ Column,
+ Integer,
+ Text,
+ PickleType
+)
+
+from ..utils import collections, caching
+from ..utils.type import canonical_type_name, full_type_name
+from . import utils, functions
+
+
+class ModelMixin(object):
+
+ @utils.classproperty
+ def __modelname__(cls): # pylint: disable=no-self-argument
+ return getattr(cls, '__mapiname__', cls.__tablename__)
+
+ @classmethod
+ def id_column_name(cls):
+ raise NotImplementedError
+
+ @classmethod
+ def name_column_name(cls):
+ raise NotImplementedError
+
+ def to_dict(self, fields=None, suppress_error=False):
+ """
+ Create a dict representation of the model.
+
+ :param suppress_error: if set to ``True``, sets ``None`` to attributes that it's unable to
+ retrieve (e.g., if a relationship wasn't established yet, and so it's impossible to access
+ a property through it)
+ """
+
+ res = dict()
+ fields = fields or self.fields()
+ for field in fields:
+ try:
+ field_value = getattr(self, field)
+ except AttributeError:
+ if suppress_error:
+ field_value = None
+ else:
+ raise
+ if isinstance(field_value, list):
+ field_value = list(field_value)
+ elif isinstance(field_value, dict):
+ field_value = dict(field_value)
+ elif isinstance(field_value, ModelMixin):
+ field_value = field_value.to_dict()
+ res[field] = field_value
+
+ return res
+
+ @classmethod
+ def fields(cls):
+ """
+ List of field names for this table.
+
+ Mostly for backwards compatibility in the code (that uses ``fields``).
+ """
+
+ fields = set(cls._iter_association_proxies())
+ fields.update(cls.__table__.columns.keys())
+ return fields - set(getattr(cls, '__private_fields__', ()))
+
+ @classmethod
+ def _iter_association_proxies(cls):
+ for col, value in vars(cls).items():
+ if isinstance(value, associationproxy.AssociationProxy):
+ yield col
+
+ def __repr__(self):
+ return '<{cls} id=`{id}`>'.format(
+ cls=self.__class__.__name__,
+ id=getattr(self, self.name_column_name()))
+
+
+class ModelIDMixin(object):
+ id = Column(Integer, primary_key=True, autoincrement=True, doc="""
+ Unique ID.
+
+ :type: :obj:`int`
+ """)
+
+ name = Column(Text, index=True, doc="""
+ Model name.
+
+ :type: :obj:`basestring`
+ """)
+
+ @classmethod
+ def id_column_name(cls):
+ return 'id'
+
+ @classmethod
+ def name_column_name(cls):
+ return 'name'
+
+
+class InstanceModelMixin(ModelMixin):
+ """
+ Mix-in for service instance models.
+
+ All models support validation, diagnostic dumping, and representation as raw data (which can be
+ translated into JSON or YAML) via :meth:`as_raw`.
+ """
+
+ @property
+ def as_raw(self):
+ raise NotImplementedError
+
+ def coerce_values(self, report_issues):
+ pass
+
+
+class TemplateModelMixin(InstanceModelMixin): # pylint: disable=abstract-method
+ """
+ Mix-in for service template models.
+
+ All model models can be instantiated into service instance models.
+ """
+
+
+class ParameterMixin(TemplateModelMixin, caching.HasCachedMethods): #pylint: disable=abstract-method
+ """
+ Mix-in for typed values. The value can contain nested intrinsic functions.
+
+ This model can be used as the ``container_holder`` argument for
+ :func:`~aria.modeling.functions.evaluate`.
+ """
+
+ type_name = Column(Text, doc="""
+ Type name.
+
+ :type: :obj:`basestring`
+ """)
+
+ description = Column(Text, doc="""
+ Human-readable description.
+
+ :type: :obj:`basestring`
+ """)
+
+ _value = Column(PickleType)
+
+ @property
+ def value(self):
+ value = self._value
+ if value is not None:
+ evaluation = functions.evaluate(value, self)
+ if evaluation is not None:
+ value = evaluation.value
+ return value
+
+ @value.setter
+ def value(self, value):
+ self._value = value
+
+ @property
+ @caching.cachedmethod
+ def owner(self):
+ """
+ The sole owner of this parameter, which is another model that relates to it.
+
+ *All* parameters should have an owner model.
+
+ :raises ~exceptions.ValueError: if failed to find an owner, which signifies an abnormal,
+ orphaned parameter
+ """
+
+ # Find first non-null relationship
+ for the_relationship in self.__mapper__.relationships:
+ v = getattr(self, the_relationship.key)
+ if v:
+ return v
+
+ raise ValueError('orphaned {class_name}: does not have an owner: {name}'.format(
+ class_name=type(self).__name__, name=self.name))
+
+ @property
+ @caching.cachedmethod
+ def container(self): # pylint: disable=too-many-return-statements,too-many-branches
+ """
+ The logical container for this parameter, which would be another model: service, node,
+ group, or policy (or their templates).
+
+ The logical container is equivalent to the ``SELF`` keyword used by intrinsic functions in
+ TOSCA.
+
+ *All* parameters should have a container model.
+
+ :raises ~exceptions.ValueError: if failed to find a container model, which signifies an
+ abnormal, orphaned parameter
+ """
+
+ from . import models
+
+ container = self.owner
+
+ # Extract interface from operation
+ if isinstance(container, models.Operation):
+ container = container.interface
+ elif isinstance(container, models.OperationTemplate):
+ container = container.interface_template
+
+ # Extract from other models
+ if isinstance(container, models.Interface):
+ container = container.node or container.group or container.relationship
+ elif isinstance(container, models.InterfaceTemplate):
+ container = container.node_template or container.group_template \
+ or container.relationship_template
+ elif isinstance(container, models.Capability) or isinstance(container, models.Artifact):
+ container = container.node
+ elif isinstance(container, models.CapabilityTemplate) \
+ or isinstance(container, models.ArtifactTemplate):
+ container = container.node_template
+ elif isinstance(container, models.Task):
+ container = container.actor
+
+ # Extract node from relationship
+ if isinstance(container, models.Relationship):
+ container = container.source_node
+ elif isinstance(container, models.RelationshipTemplate):
+ container = container.requirement_template.node_template
+
+ if container is not None:
+ return container
+
+ raise ValueError('orphaned parameter: does not have a container: {0}'.format(self.name))
+
+ @property
+ @caching.cachedmethod
+ def service(self):
+ """
+ The :class:`~aria.modeling.models.Service` model containing this parameter, or ``None`` if
+ not contained in a service.
+
+ :raises ~exceptions.ValueError: if failed to find a container model, which signifies an
+ abnormal, orphaned parameter
+ """
+
+ from . import models
+ container = self.container
+ if isinstance(container, models.Service):
+ return container
+ elif hasattr(container, 'service'):
+ return container.service
+ return None
+
+ @property
+ @caching.cachedmethod
+ def service_template(self):
+ """
+ The :class:`~aria.modeling.models.ServiceTemplate` model containing this parameter, or
+ ``None`` if not contained in a service template.
+
+ :raises ~exceptions.ValueError: if failed to find a container model, which signifies an
+ abnormal, orphaned parameter
+ """
+
+ from . import models
+ container = self.container
+ if isinstance(container, models.ServiceTemplate):
+ return container
+ elif hasattr(container, 'service_template'):
+ return container.service_template
+ return None
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('type_name', self.type_name),
+ ('value', self.value),
+ ('description', self.description)))
+
+ @property
+ def unwrapped(self):
+ return self.name, self.value
+
+ @classmethod
+ def wrap(cls, name, value, description=None):
+ """
+ Wraps an arbitrary value as a parameter. The type will be guessed via introspection.
+
+ For primitive types, we will prefer their TOSCA aliases. See the `TOSCA Simple Profile v1.0
+ cos01 specification <http://docs.oasis-open.org/tosca/TOSCA-Simple-Profile-YAML/v1.0/cos01
+ /TOSCA-Simple-Profile-YAML-v1.0-cos01.html#_Toc373867862>`__
+
+ :param name: parameter name
+ :type name: basestring
+ :param value: parameter value
+ :param description: human-readable description (optional)
+ :type description: basestring
+ """
+
+ type_name = canonical_type_name(value)
+ if type_name is None:
+ type_name = full_type_name(value)
+ return cls(name=name, # pylint: disable=unexpected-keyword-arg
+ type_name=type_name,
+ value=value,
+ description=description)
+
+ def as_other_parameter_model(self, other_model_cls):
+ name, value = self.unwrapped
+ return other_model_cls.wrap(name, value)
+
+ def as_argument(self):
+ from . import models
+ return self.as_other_parameter_model(models.Argument)