Skip to content
This repository has been archived by the owner. It is now read-only.
Browse files
ARIA-180 Separate Parameter models and change relationships
First, the Parameter model was separated in 6 individual models:
Input, Output, Property, Attribute, Configuration, Argument.

The first four models are TOSCA-related, and we needed to separate them
as according to the TOSCA spec, all four of these represent different
things, and have different fields.

The other two models (Configuration, Argument) are
orchestration-related, as and such are not supposed to represent the
same object as the TOSCA-related objects.

Second, up until now, the relationships of the then-Parameter models
were many-to-many to any other object related to them. This was a problem,
as logically the relationships were one-to-many (from the other models
to the Parameter models).
In this commit, we changed them to one-to-many. In addition, separating
the Parameter models allowed us to more accurately represent the relationship of each individual Parameter-model with its related models.
  • Loading branch information
aviaefrat committed Jun 4, 2017
1 parent 9174f94 commit b6d3c43ba42ccc3fa7287aff132c83563abea035
Showing 25 changed files with 867 additions and 511 deletions.
@@ -23,10 +23,14 @@
from sqlalchemy import (

from . import utils
from ..parser.consumption import ConsumptionContext
from ..utils import console, collections, caching, formatting
from ..utils.type import canonical_type_name, full_type_name
from . import utils, functions

class ModelMixin(object):
@@ -140,3 +144,211 @@ class TemplateModelMixin(InstanceModelMixin):

def instantiate(self, container):
raise NotImplementedError

class ParameterMixin(TemplateModelMixin, caching.HasCachedMethods): #pylint: disable=abstract-method
Represents a typed value. The value can contain nested intrinsic functions.
This model can be used as the ``container_holder`` argument for :func:`functions.evaluate`.
:ivar name: Name
:vartype name: basestring
:ivar type_name: Type name
:vartype type_name: basestring
:ivar value: Value
:ivar description: Description
:vartype description: basestring

__tablename__ = 'parameter'

name = Column(Text)
type_name = Column(Text)
description = Column(Text)
_value = Column(PickleType)

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

def value(self, value):
self._value = value

def owner(self):
The sole owner of this parameter, which is another model that relates to it.
*All* parameters should have an owner model. In case this property method fails to find
it, it will raise a ValueError, which should signify 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(

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
*All* parameters should have a container model. In case this property method fails to find
it, it will raise a ValueError, which should signify 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 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 =

# 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(

def service(self):
The :class:`Service` containing this parameter, or None if not contained in a service.

from . import models
container = self.container
if isinstance(container, models.Service):
return container
elif hasattr(container, 'service'):
return container.service
return None

def service_template(self):
The :class:`ServiceTemplate` containing this parameter, or None if not contained in a
service template.

from . import models
container = self.container
if isinstance(container, models.ServiceTemplate):
return container
elif hasattr(container, 'service_template'):
return container.service_template
return None

def as_raw(self):
return collections.OrderedDict((
('type_name', self.type_name),
('value', self.value),
('description', self.description)))

def instantiate(self, container):
return self.__class__(, # pylint: disable=unexpected-keyword-arg

def coerce_values(self, report_issues):
value = self._value
if value is not None:
evaluation = functions.evaluate(value, self, report_issues)
if (evaluation is not None) and
# A final evaluation can safely replace the existing value
self._value = evaluation.value

def dump(self):
context = ConsumptionContext.get_thread_local()
if self.type_name is not None:
console.puts('{0}: {1} ({2})'.format(,,
console.puts('{0}: {1}'.format(,
if self.description:

def unwrapped(self):
return, self.value

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 <
:param name: Parameter name
:type name: basestring
:param value: Parameter value
:param description: 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

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)
@@ -72,15 +72,20 @@

# Common service models

# Orchestration models

@@ -206,7 +211,24 @@ class ServiceModification(aria_declarative_base, service_changes.ServiceModifica

# region common service models

class Parameter(aria_declarative_base, service_common.ParameterBase):

class Input(aria_declarative_base, service_common.InputBase):

class Configuration(aria_declarative_base, service_common.ConfigurationBase):

class Output(aria_declarative_base, service_common.OutputBase):

class Property(aria_declarative_base, service_common.PropertyBase):

class Attribute(aria_declarative_base, service_common.AttributeBase):

@@ -237,6 +259,11 @@ class Task(aria_declarative_base, orchestration.TaskBase):
class Log(aria_declarative_base, orchestration.LogBase):

class Argument(aria_declarative_base, orchestration.ArgumentBase):

# endregion

@@ -276,13 +303,18 @@ class Log(aria_declarative_base, orchestration.LogBase):

# Common service models

# Orchestration models
@@ -39,7 +39,7 @@
from sqlalchemy.ext.declarative import declared_attr

from ..orchestrator.exceptions import (TaskAbortException, TaskRetryException)
from .mixins import ModelMixin
from .mixins import ModelMixin, ParameterMixin
from . import (
types as modeling_types
@@ -115,7 +115,7 @@ def tasks(cls):

def inputs(cls):
return relationship.many_to_many(cls, 'parameter', prefix='inputs', dict_key='name')
return relationship.one_to_many(cls, 'input', dict_key='name')

# region foreign keys

@@ -233,7 +233,7 @@ class TaskBase(ModelMixin):
:ivar function: Python path to an ``@operation`` function
:vartype function: basestring
:ivar arguments: Arguments that can be used by this task
:vartype arguments: {basestring: :class:`Parameter`}
:vartype arguments: {basestring: :class:`Argument`}
:ivar max_attempts: Maximum number of retries allowed in case of failure
:vartype max_attempts: int
:ivar retry_interval: Interval between retries (in seconds)
@@ -301,7 +301,7 @@ def execution(cls):

def arguments(cls):
return relationship.many_to_many(cls, 'parameter', prefix='arguments', dict_key='name')
return relationship.one_to_many(cls, 'argument', dict_key='name')

function = Column(String)
max_attempts = Column(Integer, default=1)
@@ -433,3 +433,32 @@ def __str__(self):
def __repr__(self):
name = ( if self.task else self.execution).name
return '{name}: {self.msg}'.format(name=name, self=self)

class ArgumentBase(ParameterMixin):

__tablename__ = 'argument'

# region foreign keys

def task_fk(cls):
return relationship.foreign_key('task', nullable=True)

def operation_fk(cls):
return relationship.foreign_key('operation', nullable=True)

# endregion

# region many_to_one relationships

def task(cls):
return relationship.many_to_one(cls, 'task')

def operation(cls):
return relationship.many_to_one(cls, 'operation')

# endregion

0 comments on commit b6d3c43

Please sign in to comment.