You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@ariatosca.apache.org by mx...@apache.org on 2017/01/22 16:38:09 UTC
[3/5] incubator-ariatosca git commit: Phase 1 added many-to-many
relationships (Parameters etc...)
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 <mx...@gmail.com>
Authored: Sun Jan 22 12:18:58 2017 +0200
Committer: mxmrlv <mx...@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)
+ )
+ )