ariatosca-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From mxm...@apache.org
Subject [3/5] incubator-ariatosca git commit: Phase 1 added many-to-many relationships (Parameters etc...)
Date Sun, 22 Jan 2017 16:38:09 GMT
Phase 1 added many-to-many relationships (Parameters etc...)


Project: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/commit/67c873f0
Tree: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/tree/67c873f0
Diff: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/diff/67c873f0

Branch: refs/heads/ARIA-44-Merge-parser-and-storage-models
Commit: 67c873f0e8b4ce233e474869cab310caca2fa255
Parents: 5281bf5
Author: mxmrlv <mxmrlv@gmail.com>
Authored: Sun Jan 22 12:18:58 2017 +0200
Committer: mxmrlv <mxmrlv@gmail.com>
Committed: Sun Jan 22 12:20:37 2017 +0200

----------------------------------------------------------------------
 aria/modeling/elements.py          |   1 -
 aria/modeling/instance_elements.py | 235 +++++++++++++++++++-------------
 aria/modeling/model_elements.py    | 100 ++++++++------
 aria/modeling/utils.py             |  91 ++++++++++++-
 4 files changed, 290 insertions(+), 137 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/67c873f0/aria/modeling/elements.py
----------------------------------------------------------------------
diff --git a/aria/modeling/elements.py b/aria/modeling/elements.py
index 465b0c0..bb4359a 100644
--- a/aria/modeling/elements.py
+++ b/aria/modeling/elements.py
@@ -89,7 +89,6 @@ class Parameter(ModelElement, structure.ModelMixin):
     value = Column()
     description = Column(Text)
     # NOTE: there is no need to hold an owner. (prob no backtracking to the owner is needed)
-    # CHECK: what is the deal with the self instantiation
 
     @property
     def as_raw(self):

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/67c873f0/aria/modeling/instance_elements.py
----------------------------------------------------------------------
diff --git a/aria/modeling/instance_elements.py b/aria/modeling/instance_elements.py
index ccf1531..ab23c12 100644
--- a/aria/modeling/instance_elements.py
+++ b/aria/modeling/instance_elements.py
@@ -6,6 +6,7 @@ from sqlalchemy import (
     ForeignKey
 )
 from sqlalchemy import orm
+from sqlalchemy.ext.declarative import declared_attr
 
 from . import elements, utils
 from ..storage import structure, type
@@ -13,7 +14,7 @@ from ..utils import collections, formatting, console
 from ..parser import validation
 
 
-# region Tier 0 elements
+# region Element instances
 
 class ServiceInstance(elements.Element, structure.ModelMixin):
     __tablename__ = 'service_instance'
@@ -27,8 +28,13 @@ class ServiceInstance(elements.Element, structure.ModelMixin):
     substitution = Column()
     operations = orm.relationship('operation')
 
-    inputs = Column(type.StrictDict(str, "Parameter"))
-    outputs = Column(type.StrictDict(str, "Parameter"))
+    @declared_attr
+    def inputs(cls):
+        return utils.many_to_many_relationship(cls, elements.Parameter, table_prefix='inputs')
+
+    @declared_attr
+    def outputs(cls):
+        return utils.many_to_many_relationship(cls, elements.Parameter, table_prefix='outputs')
 
     def satisfy_requirements(self, context):
         satisfied = True
@@ -111,7 +117,10 @@ class Operation(elements.Element, structure.ModelMixin):
     executor = Column()
     max_retries = Column(Integer, default=None)
     retry_interval = Column(Integer, default=None)
-    inputs = Column(type.StrictDict(key_cls=basestring, value_cls=elements.Parameter))
+
+    @declared_attr
+    def inputs(cls):
+        return utils.many_to_many_relationship(cls, elements.Parameter, table_prefix='inputs')
 
     @property
     def as_raw(self):
@@ -171,9 +180,12 @@ class Interface(elements.Element, structure.ModelMixin):
 
     description = Column(Text)
     type_name = Column(Text)
-    inputs = Column(type.StrictDict(key_cls=str, value_cls=elements.Parameter))
     operations = orm.relationship('operation')
 
+    @declared_attr
+    def inputs(cls):
+        return utils.many_to_many_relationship(cls, elements.Parameter, table_prefix='inputs')
+
     @property
     def as_raw(self):
         return collections.OrderedDict((
@@ -228,12 +240,15 @@ class Capability(elements.Element, structure.ModelMixin):
 
     # endregion
     type_name = Column(Text)
-    properties = Column(type.StrictDict(basestring, elements.Parameter))
 
     min_occurrences = Column(Integer, default=None) # optional
     max_occurrences = Column(Integer, default=None) # optional
     occurrences = Column(Integer, default=0)
 
+    @declared_attr
+    def properties(cls):
+        return utils.many_to_many_relationship(cls, elements.Parameter, table_prefix='properties')
+
     @property
     def has_enough_relationships(self):
         if self.min_occurrences is not None:
@@ -307,7 +322,10 @@ class Artifact(elements.Element, structure.ModelMixin):
     target_path = Column()
     repository_url = Column(Text)
     repository_credential = Column(type.StrictDict(basestring, basestring))
-    properties = Column(type.StrictDict(basestring, elements.Parameter))
+
+    @declared_attr
+    def properties(cls):
+        return utils.many_to_many_relationship(cls, elements.Parameter, table_prefix='properties')
 
     @property
     def as_raw(self):
@@ -367,10 +385,13 @@ class Policy(elements.Element, structure.ModelMixin):
 
     # endregion
     type_name = Column(Text)
-    properties = Column(type.StrictDict(basestring, elements.Parameter))
     target_node_ids = Column(type.StrictList(basestring))
     target_group_ids = Column(type.StrictList(basestring))
 
+    @declared_attr
+    def properties(cls):
+        return utils.many_to_many_relationship(cls, elements.Parameter, table_prefix='properties')
+
     @property
     def as_raw(self):
         return collections.OrderedDict((
@@ -429,9 +450,12 @@ class GroupPolicy(elements.Element, structure.ModelMixin):
 
     description = Column(Text)
     type_name = Column(Text)
-    properties = Column(type.StrictDict(basestring, elements.Parameter))
     triggers = orm.relationship('group_policy_trigger')
 
+    @declared_attr
+    def properties(cls):
+        return utils.many_to_many_relationship(cls, elements.Parameter, table_prefix='properties')
+
     @property
     def as_raw(self):
         return collections.OrderedDict((
@@ -485,7 +509,10 @@ class GroupPolicyTrigger(elements.Element, structure.ModelMixin):
     description = Column(Text)
     # Check: what's that?
     implementation = Column()
-    properties = Column(type.StrictDict(basestring, elements.Parameter))
+
+    @declared_attr
+    def properties(cls):
+        return utils.many_to_many_relationship(cls, elements.Parameter, table_prefix='properties')
 
     @property
     def as_raw(self):
@@ -550,8 +577,14 @@ class Substitution(elements.Element, structure.ModelMixin):
     """
 
     node_type_name = Column(Text)
-    capabilities = Column(type.StrictDict(basestring, Mapping))
-    requirements = Column(type.StrictDict(basestring, Mapping))
+
+    @declared_attr
+    def capabilities(cls):
+        return utils.many_to_many_relationship(cls, elements.Parameter, table_prefix='capabilities')
+
+    @declared_attr
+    def requirements(cls):
+        return utils.many_to_many_relationship(cls, elements.Parameter, table_prefix='requirements')
 
     @property
     def as_raw(self):
@@ -585,8 +618,7 @@ class Substitution(elements.Element, structure.ModelMixin):
 
 # endregion
 
-
-# region Tier 1 elements
+# region Node instances
 
 class Node(elements.Element, structure.ModelMixin):
     """
@@ -614,12 +646,15 @@ class Node(elements.Element, structure.ModelMixin):
 
     type_name = Column(Text)
     template_name = Column(Text)
-    properties = Column(type.StrictDict(basestring, elements.Parameter))
     interfaces = orm.relationship('interface')
     artifacts = orm.relationship('artifact')
     capabilities = orm.relationship('capability')
     relationships = orm.relationship('relationship')
 
+    @declared_attr
+    def properties(cls):
+        return utils.many_to_many_relationship(cls, elements.Parameter, table_prefix='properties')
+
     def satisfy_requirements(self, context):
         node_template = context.modeling.model.node_templates.get(self.template_name)
         satisfied = True
@@ -753,6 +788,86 @@ class Node(elements.Element, structure.ModelMixin):
             utils.dump_list_values(context, self.relationships, 'Relationships')
 
 
+class Group(elements.Element, structure.ModelMixin):
+    """
+    An instance of a :class:`GroupTemplate`.
+
+    Properties:
+
+    * :code:`id`: Unique ID (prefixed with the template name)
+    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+    * :code:`template_name`: Must be represented in the :class:`ServiceModel`
+    * :code:`properties`: Dict of :class:`Parameter`
+    * :code:`interfaces`: Dict of :class:`Interface`
+    * :code:`policies`: Dict of :class:`GroupPolicy`
+    * :code:`member_node_ids`: Must be represented in the :class:`ServiceInstance`
+    * :code:`member_group_ids`: Must be represented in the :class:`ServiceInstance`
+    """
+    __tablename__ = 'group'
+    # region foreign_keys
+    service_instance_fk = Column(Integer, ForeignKey('service_instance.id'))
+
+    # endregion
+
+    type_name = Column(Text)
+    template_name = Column(Text)
+    interfaces = orm.relationship('interface')
+    policies = orm.relationship('group_policy')
+    member_node_ids = Column(type.StrictList(basestring))
+    member_group_ids = Column(type.StrictList(basestring))
+
+    @declared_attr
+    def properties(cls):
+        return utils.many_to_many_relationship(cls, elements.Parameter,
+                                               table_prefix='properties')
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('id', self.id),
+            ('type_name', self.type_name),
+            ('template_name', self.template_name),
+            ('properties', formatting.as_raw_dict(self.properties)),
+            ('interfaces', formatting.as_raw_list(self.interfaces)),
+            ('policies', formatting.as_raw_list(self.policies)),
+            ('member_node_ids', self.member_node_ids),
+            ('member_group_ids', self.member_group_ids)))
+
+    def validate(self, context):
+        if context.modeling.group_types.get_descendant(self.type_name) is None:
+            context.validation.report('group "%s" has an unknown type: %s'
+                                      % (self.name,  # pylint: disable=no-member
+                                         # TODO fix self.name reference
+                                         formatting.safe_repr(self.type_name)),
+                                      level=validation.Issue.BETWEEN_TYPES)
+
+        utils.validate_dict_values(context, self.properties)
+        utils.validate_dict_values(context, self.interfaces)
+        utils.validate_dict_values(context, self.policies)
+
+    def coerce_values(self, context, container, report_issues):
+        utils.coerce_dict_values(context, container, self.properties, report_issues)
+        utils.coerce_dict_values(context, container, self.interfaces, report_issues)
+        utils.coerce_dict_values(context, container, self.policies, report_issues)
+
+    def dump(self, context):
+        console.puts('Group: %s' % context.style.node(self.id))
+        with context.style.indent:
+            console.puts('Type: %s' % context.style.type(self.type_name))
+            console.puts('Template: %s' % context.style.type(self.template_name))
+            utils.dump_parameters(context, self.properties)
+            utils.dump_interfaces(context, self.interfaces)
+            utils.dump_dict_values(context, self.policies, 'Policies')
+            if self.member_node_ids:
+                console.puts('Member nodes:')
+                with context.style.indent:
+                    for node_id in self.member_node_ids:
+                        console.puts(context.style.node(node_id))
+
+# endregion
+
+# region Relationship instances
+
 class Relationship(elements.Element, structure.ModelMixin):
     """
     Connects :class:`Node` to another node.
@@ -782,9 +897,20 @@ class Relationship(elements.Element, structure.ModelMixin):
     target_capability_name = Column(Text)
     type_name = Column(Text)
     template_name = Column(Text)
-    properties = Column(type.StrictDict(basestring, elements.Parameter))
-    source_interfaces = Column(type.StrictDict(basestring, Interface))
-    target_interfaces = Column(type.StrictDict(basestring, Interface))
+
+    @declared_attr
+    def properties(cls):
+        return utils.many_to_many_relationship(cls, elements.Parameter, table_prefix='properties')
+
+    @declared_attr
+    def source_interfaces(cls):
+        return utils.many_to_many_relationship(
+            cls, elements.Parameter, table_prefix='source_interfaces')
+
+    @declared_attr
+    def target_interfaces(cls):
+        return utils.many_to_many_relationship(
+            cls, elements.Parameter, table_prefix='target_interfaces')
 
     @property
     def as_raw(self):
@@ -836,78 +962,5 @@ class Relationship(elements.Element, structure.ModelMixin):
             utils.dump_interfaces(context, self.source_interfaces, 'Source interfaces')
             utils.dump_interfaces(context, self.target_interfaces, 'Target interfaces')
 
-
-class Group(elements.Element, structure.ModelMixin):
-    """
-    An instance of a :class:`GroupTemplate`.
-
-    Properties:
-
-    * :code:`id`: Unique ID (prefixed with the template name)
-    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
-    * :code:`template_name`: Must be represented in the :class:`ServiceModel`
-    * :code:`properties`: Dict of :class:`Parameter`
-    * :code:`interfaces`: Dict of :class:`Interface`
-    * :code:`policies`: Dict of :class:`GroupPolicy`
-    * :code:`member_node_ids`: Must be represented in the :class:`ServiceInstance`
-    * :code:`member_group_ids`: Must be represented in the :class:`ServiceInstance`
-    """
-    __tablename__ = 'group'
-    # region foreign_keys
-    service_instance_fk = Column(Integer, ForeignKey('service_instance.id'))
-
-    # endregion
-
-    type_name = Column(Text)
-    template_name = Column(Text)
-    properties = Column(type.StrictDict(basestring, elements.Parameter))
-    interfaces = orm.relationship('interface')
-    policies = orm.relationship('group_policy')
-    member_node_ids = Column(type.StrictList(basestring))
-    member_group_ids = Column(type.StrictList(basestring))
-
-    @property
-    def as_raw(self):
-        return collections.OrderedDict((
-            ('id', self.id),
-            ('type_name', self.type_name),
-            ('template_name', self.template_name),
-            ('properties', formatting.as_raw_dict(self.properties)),
-            ('interfaces', formatting.as_raw_list(self.interfaces)),
-            ('policies', formatting.as_raw_list(self.policies)),
-            ('member_node_ids', self.member_node_ids),
-            ('member_group_ids', self.member_group_ids)))
-
-    def validate(self, context):
-        if context.modeling.group_types.get_descendant(self.type_name) is None:
-            context.validation.report('group "%s" has an unknown type: %s'
-                                      % (self.name,  # pylint: disable=no-member
-                                         # TODO fix self.name reference
-                                         formatting.safe_repr(self.type_name)),
-                                      level=validation.Issue.BETWEEN_TYPES)
-
-        utils.validate_dict_values(context, self.properties)
-        utils.validate_dict_values(context, self.interfaces)
-        utils.validate_dict_values(context, self.policies)
-
-    def coerce_values(self, context, container, report_issues):
-        utils.coerce_dict_values(context, container, self.properties, report_issues)
-        utils.coerce_dict_values(context, container, self.interfaces, report_issues)
-        utils.coerce_dict_values(context, container, self.policies, report_issues)
-
-    def dump(self, context):
-        console.puts('Group: %s' % context.style.node(self.id))
-        with context.style.indent:
-            console.puts('Type: %s' % context.style.type(self.type_name))
-            console.puts('Template: %s' % context.style.type(self.template_name))
-            utils.dump_parameters(context, self.properties)
-            utils.dump_interfaces(context, self.interfaces)
-            utils.dump_dict_values(context, self.policies, 'Policies')
-            if self.member_node_ids:
-                console.puts('Member nodes:')
-                with context.style.indent:
-                    for node_id in self.member_node_ids:
-                        console.puts(context.style.node(node_id))
-
 # endregion
 

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/67c873f0/aria/modeling/model_elements.py
----------------------------------------------------------------------
diff --git a/aria/modeling/model_elements.py b/aria/modeling/model_elements.py
index 28cc553..234c59c 100644
--- a/aria/modeling/model_elements.py
+++ b/aria/modeling/model_elements.py
@@ -22,16 +22,14 @@ from sqlalchemy import (
     ForeignKey
 )
 from sqlalchemy import orm
+from sqlalchemy.ext.declarative import declared_attr
 
 from . import elements, instance_elements, utils
 from ..storage import structure, type
 from ..utils import collections, formatting, console
 from ..parser import validation
 
-# region Elements
-
-# TODO: fix the Parameters tables (maybe many to many/dynamic table creation).
-
+# region Element templates
 
 class ServiceTemplate(elements.ModelElement, structure.ModelMixin):
 
@@ -42,20 +40,16 @@ class ServiceTemplate(elements.ModelElement, structure.ModelMixin):
     node_templates = orm.relationship('note_template')
     group_templates = orm.relationship('group_template')
     policy_templates = orm.relationship('policy_template')
+    operation_templates = orm.relationship('operation_template')
     substitution_template = Column()
 
-    inputs = Column(type.StrictDict(str, elements.Parameter))
-    # inputs = orm.relationship(elements.Parameter)
-    outputs = Column(type.StrictDict(str, elements.Parameter))
-    # outputs = orm.relationship(elements.Parameter)
-
-    # operation_templates = Column(type.StrictDict(str, OperationTemplate))
-    operation_tepmlates = orm.relationship('operation_template')
+    @declared_attr
+    def inputs(cls):
+        return utils.many_to_many_relationship(cls, elements.Parameter, table_prefix='inputs')
 
-    # created_at = Column(DateTime, nullable=False, index=True)
-    # main_file_name = Column(Text, nullable=False)
-    # plan = Column(type.Dict, nullable=False)
-    # updated_at = Column(DateTime)
+    @declared_attr
+    def outputs(cls):
+        return utils.many_to_many_relationship(cls, elements.Parameter, table_prefix='outputs')
 
     @property
     def as_raw(self):
@@ -155,11 +149,11 @@ class InterfaceTemplate(elements.ModelElement, structure.ModelMixin):
 
     description = Column(Text)
     type_name = Column(Text)
-
-    inputs = Column(type.StrictDict(basestring, elements.Parameter))
-
     operation_templates = orm.relationship('operation_template')
-    # operation_templates = Column(type.StrictDict(basestring, OperationTemplate))
+
+    @declared_attr
+    def inputs(cls):
+        return utils.many_to_many_relationship(cls, elements.Parameter, table_prefix='inputs')
 
     @property
     def as_raw(self):
@@ -216,7 +210,10 @@ class OperationTemplate(elements.ModelElement, structure.ModelMixin):
     executor = Column()
     max_retries = Column(Integer)
     retry_interval = Column(Integer)
-    inputs = Column(type.StrictDict(key_cls=basestring, value_cls=elements.Parameter))
+
+    @declared_attr
+    def inputs(cls):
+        return utils.many_to_many_relationship(cls, elements.Parameter, table_prefix='inputs')
 
     @property
     def as_raw(self):
@@ -294,7 +291,10 @@ class ArtifactTemplate(elements.ModelElement, structure.ModelMixin):
     target_path = Column()
     repository_url = Column(Text)
     repository_credential = Column(type.StrictDict(basestring, basestring))
-    properties = Column(type.StrictDict(basestring, elements.Parameter))
+
+    @declared_attr
+    def properties(cls):
+        return utils.many_to_many_relationship(cls, elements.Parameter, table_prefix='properties')
 
     @property
     def as_raw(self):
@@ -367,10 +367,14 @@ class PolicyTemplate(elements.ModelElement, structure.ModelMixin):
 
     description = Column(Text)
     type_name = Column(Text)
-    properties = Column(type.StrictDict(basestring, elements.Parameter))
     target_node_template_names = Column(type.StrictList(basestring))
     target_group_template_names = Column(type.StrictList(basestring))
 
+    @declared_attr
+    def properties(cls):
+        return utils.many_to_many_relationship(cls, elements.Parameter, table_prefix='properties')
+
+
     @property
     def as_raw(self):
         return collections.OrderedDict((
@@ -435,14 +439,15 @@ class GroupPolicyTemplate(elements.ModelElement, structure.ModelMixin):
     # region foreign keys
     group_template_fk = Column(Integer, ForeignKey('group_template.id'))
 
+    # endregion
 
     description = Column(Text)
     type_name = Column(Text)
-    properties = Column(type.StrictDict(basestring, elements.Parameter))
-
-
     triggers = orm.relationship('group_policy_trigger_template')
-    # triggers = Column(type.StrictDict(basestring, GroupPolicyTriggerTemplate))
+
+    @declared_attr
+    def properties(cls):
+        return utils.many_to_many_relationship(cls, elements.Parameter, table_prefix='properties')
 
     @property
     def as_raw(self):
@@ -503,7 +508,10 @@ class GroupPolicyTriggerTemplate(elements.ModelElement, structure.ModelMixin):
     description = Column(Text)
     # Check: ???
     implementation = Column()
-    properties = Column(type.StrictDict(basestring, elements.Parameter))
+
+    @declared_attr
+    def properties(cls):
+        return utils.many_to_many_relationship(cls, elements.Parameter, table_prefix='properties')
 
     @property
     def as_raw(self):
@@ -595,9 +603,15 @@ class SubstitutionTemplate(elements.ModelElement, structure.ModelMixin):
     __tablename__ = 'substituion_template'
     node_type_name = Column(Text)
 
-    # TODO: same as parameters, many to many relationship
-    capability_templates = Column(type.StrictDict(basestring, MappingTemplate))
-    requirement_templates = Column(type.StrictDict(basestring, MappingTemplate))
+    @declared_attr
+    def capability_templates(cls):
+        return utils.many_to_many_relationship(
+            cls, MappingTemplate, table_prefix='capability_templates')
+
+    @declared_attr
+    def requirement_templates(cls):
+        return utils.many_to_many_relationship(
+            cls, MappingTemplate, table_prefix='requirement_templates')
 
     @property
     def as_raw(self):
@@ -639,7 +653,7 @@ class SubstitutionTemplate(elements.ModelElement, structure.ModelMixin):
 
 # endregion
 
-# region Nodes
+# region Node templates
 
 class NodeTemplate(elements.ModelElement, structure.ModelMixin):
     __tablename__ = 'node_template'
@@ -655,15 +669,16 @@ class NodeTemplate(elements.ModelElement, structure.ModelMixin):
     min_instances = Column(Integer, default=0)
     man_instances = Column(Integer, default=None)
 
-    properties = Column(type.StrictDict(basestring, elements.Parameter))
-
     interface_templates = orm.relationship('interface_template')
     artifact_templates = orm.relationship('artifact_template')
     capability_templates = orm.relationship('capability_template')
     requirement_templates = orm.relationship('requirement_template')
-
     target_node_template_constraints = Column(type.StrictList(FunctionType))
 
+    @declared_attr
+    def properties(cls):
+        return utils.many_to_many_relationship(cls, elements.Parameter, table_prefix='properties')
+
     def is_target_node_valid(self, target_node_template):
         if self.target_node_template_constraints:
             for node_type_constraint in self.target_node_template_constraints:
@@ -757,16 +772,18 @@ class GroupTemplate(elements.ModelElement, structure.ModelMixin):
 
     # endregion
 
-    description = Column(Text)
-    type_name = Column(Text)
-    properties = Column(type.StrictDict(basestring, elements.Parameter))
-
     interface_templates = orm.relationship('interface_template')
     policy_templates = orm.relationship('policy_template')
 
+    description = Column(Text)
+    type_name = Column(Text)
     member_node_template_names = Column(type.StrictList(basestring))
     member_group_template_names = Column(type.StrictList(basestring))
 
+    @declared_attr
+    def properties(cls):
+        return utils.many_to_many_relationship(cls, elements.Parameter, table_prefix='properties')
+
     @property
     def as_raw(self):
         return collections.OrderedDict((
@@ -824,7 +841,7 @@ class GroupTemplate(elements.ModelElement, structure.ModelMixin):
 
 # endregion
 
-# region Relationship
+# region Relationship templates
 
 class RequirementTemplate(elements.ModelElement, structure.ModelMixin):
     """
@@ -1006,7 +1023,10 @@ class CapabilityTemplate(elements.ModelElement, structure.ModelMixin):
     max_occurrences = Column(Integer, default=None)  # optional
     # CHECK: type?
     valid_source_node_type_names = Column()
-    properties = Column(type.StrictDict(basestring, elements.Parameter))
+
+    @declared_attr
+    def properties(cls):
+        return utils.many_to_many_relationship(cls, elements.Parameter, table_prefix='properties')
 
     def satisfies_requirement(self,
                               context,

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/67c873f0/aria/modeling/utils.py
----------------------------------------------------------------------
diff --git a/aria/modeling/utils.py b/aria/modeling/utils.py
index 906106e..0b4444e 100644
--- a/aria/modeling/utils.py
+++ b/aria/modeling/utils.py
@@ -15,13 +15,14 @@
 
 from random import randrange
 
+from sqlalchemy import orm, Table, Column, Integer, ForeignKey
 from shortuuid import ShortUUID
 
-from ...utils.collections import OrderedDict
-from ...utils.console import puts
-from ..exceptions import InvalidValueError
-from ..presentation import Value
-from .exceptions import CannotEvaluateFunctionException
+from ..utils.collections import OrderedDict
+from ..utils.console import puts
+from ..parser.exceptions import InvalidValueError
+from ..parser.presentation import Value
+from ..parser.modeling.exceptions import CannotEvaluateFunctionException
 
 # UUID = ShortUUID() # default alphabet is base57, which is alphanumeric without visually
ambiguous
 # characters; ID length is 22
@@ -144,3 +145,83 @@ def dump_interfaces(context, interfaces, name='Interfaces'):
     with context.style.indent:
         for interface in interfaces.itervalues():
             interface.dump(context)
+
+
+def many_to_many_relationship(current_cls, other_cls, table_prefix):
+    """Return a many-to-many SQL relationship object
+
+    Notes:
+    1. The backreference name is the current table's table name
+    2. This method creates a new helper table in the DB
+
+    :param current_cls: The class of the table we're connecting from
+    :param other_cls: The class of the table we're connecting to
+    :param table_prefix: Custom prefix for the helper table name and the
+    backreference name
+    """
+    current_table_name = current_cls.__tablename__
+    current_column_name = '{0}_id'.format(current_table_name[:-1])
+    current_foreign_key = '{0}.{1}'.format(
+        current_table_name,
+        current_cls.unique_id()
+    )
+
+    other_table_name = other_cls.__tablename__
+    other_column_name = '{0}_id'.format(other_table_name[:-1])
+    other_foreign_key = '{0}.{1}'.format(
+        other_table_name,
+        other_cls.unique_id()
+    )
+
+    helper_table_name = '{0}_{1}'.format(
+        current_table_name,
+        other_table_name
+    )
+
+    backref_name = current_table_name
+    if table_prefix:
+        helper_table_name = '{0}_{1}'.format(table_prefix, helper_table_name)
+        backref_name = '{0}_{1}'.format(table_prefix, backref_name)
+
+    secondary_table = get_secondary_table(
+        helper_table_name,
+        current_column_name,
+        other_column_name,
+        current_foreign_key,
+        other_foreign_key
+    )
+    return orm.relationship(
+        other_cls,
+        secondary=secondary_table,
+        backref=orm.backref(backref_name)
+    )
+
+
+def get_secondary_table(helper_table_name,
+                        first_column_name,
+                        second_column_name,
+                        first_foreign_key,
+                        second_foreign_key):
+    """Create a helper table for a many-to-many relationship
+
+    :param helper_table_name: The name of the table
+    :param first_column_name: The name of the first column in the table
+    :param second_column_name: The name of the second column in the table
+    :param first_foreign_key: The string representing the first foreign key,
+    for example `blueprint.storage_id`, or `tenants.id`
+    :param second_foreign_key: The string representing the second foreign key
+    :return: A Table object
+    """
+    return Table(
+        helper_table_name,
+        Column(
+            first_column_name,
+            Integer,
+            ForeignKey(first_foreign_key)
+        ),
+        Column(
+            second_column_name,
+            Integer,
+            ForeignKey(second_foreign_key)
+        )
+    )


Mime
View raw message