Repository: incubator-ariatosca
Updated Branches:
  refs/heads/ARIA-44-Merge-parser-and-storage-models 23f6c030a -> 05b1cab24


First Phase: Artifact/Template, Operation/Template, Capability/Template, 
Node/Template


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

Branch: refs/heads/ARIA-44-Merge-parser-and-storage-models
Commit: 09f1f737cd051122604198ad7bf047793fbc5c91
Parents: 23f6c03
Author: mxmrlv <mxm...@gmail.com>
Authored: Thu Jan 19 14:54:31 2017 +0200
Committer: mxmrlv <mxm...@gmail.com>
Committed: Thu Jan 19 14:54:31 2017 +0200

----------------------------------------------------------------------
 aria/modeling/elements.py          |  29 ++-
 aria/modeling/instance_elements.py | 419 +++++++++++++++++++++++++++++++-
 aria/modeling/model_elements.py    | 363 ++++++++++++++++++++++++++-
 aria/storage/type.py               |   9 +-
 4 files changed, 799 insertions(+), 21 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/09f1f737/aria/modeling/elements.py
----------------------------------------------------------------------
diff --git a/aria/modeling/elements.py b/aria/modeling/elements.py
index 62bc7b8..465b0c0 100644
--- a/aria/modeling/elements.py
+++ b/aria/modeling/elements.py
@@ -13,10 +13,17 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from ...utils.collections import StrictDict, OrderedDict
-from ...utils.console import puts
+from sqlalchemy import (
+    Column,
+    Text
+)
+
+from ..utils.collections import OrderedDict
+from ..utils.console import puts
 from .utils import coerce_value
 
+from ..storage import structure, type
+
 
 class Function(object):
     """
@@ -71,17 +78,18 @@ class ModelElement(Element):
         raise NotImplementedError
 
 
-class Parameter(ModelElement):
+class Parameter(ModelElement, structure.ModelMixin):
     """
     Represents a typed value.
 
     This class is used by both service model and service instance elements.
     """
 
-    def __init__(self, type_name, value, description):
-        self.type_name = type_name
-        self.value = value
-        self.description = description
+    type_name = Column(Text)
+    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):
@@ -98,7 +106,7 @@ class Parameter(ModelElement):
             self.value = coerce_value(context, container, self.value, 
report_issues)
 
 
-class Metadata(ModelElement):
+class Metadata(ModelElement, structure.ModelMixin):
     """
     Custom values associated with the deployment template and its plans.
 
@@ -108,9 +116,8 @@ class Metadata(ModelElement):
 
     * :code:`values`: Dict of custom values
     """
-
-    def __init__(self):
-        self.values = StrictDict(key_class=basestring)
+    values = Column(type.StrictDict(key_cls=basestring))
+    # 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/09f1f737/aria/modeling/instance_elements.py
----------------------------------------------------------------------
diff --git a/aria/modeling/instance_elements.py 
b/aria/modeling/instance_elements.py
index f0becd4..82d2f7e 100644
--- a/aria/modeling/instance_elements.py
+++ b/aria/modeling/instance_elements.py
@@ -2,19 +2,21 @@
 from sqlalchemy import (
     Column,
     Text,
+    Integer
 )
 
-from . import elements
+from . import elements, utils
 from ..storage import structure, type
-from ..utils import collections
+from ..utils import collections, formatting, console
+from ..parser import validation
 
 
 class ServiceInstance(elements.Element, structure.ModelMixin):
 
     description = Column(Text)
     metadata = Column()
-    # TODO: is the solution is really to create a list of foreign key
-    # (which would replace the dict) and filter according to the name etc...?
+    # NOTE: is the solution is really to create a list of foreign key
+    # NOTE: (which would replace the dict) and filter according to the name 
etc...?
     nodes = Column(type.StrictDict(str, "Node"))
     groups = Column(type.StrictDict(str, "Group"))
     policies = Column(type.StrictDict(str, "Policy"))
@@ -73,4 +75,411 @@ class ServiceInstance(elements.Element, 
structure.ModelMixin):
                     if node is not None:
                         if self._is_node_a_target(context, node, target_node):
                             return True
-        return False
\ No newline at end of file
+        return False
+
+
+class Operation(elements.Element, structure.ModelMixin):
+    """
+    An operation in a :class:`Interface`.
+
+    Properties:
+
+    * :code:`name`: Name
+    * :code:`description`: Description
+    * :code:`implementation`: Implementation string (interpreted by the 
orchestrator)
+    * :code:`dependencies`: List of strings (interpreted by the orchestrator)
+    * :code:`executor`: Executor string (interpreted by the orchestrator)
+    * :code:`max_retries`: Maximum number of retries allowed in case of failure
+    * :code:`retry_interval`: Interval between retries
+    * :code:`inputs`: Dict of :class:`Parameter`
+    """
+
+    description = Column(Text)
+    # Check: what's this for?
+    implementation = Column()
+    dependencies = Column(type.StrictList(item_cls=basestring))
+    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))
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('name', self.name),
+            ('description', self.description),
+            ('implementation', self.implementation),
+            ('dependencies', self.dependencies),
+            ('executor', self.executor),
+            ('max_retries', self.max_retries),
+            ('retry_interval', self.retry_interval),
+            ('inputs', formatting.as_raw_dict(self.inputs))))
+
+    def validate(self, context):
+        utils.validate_dict_values(context, self.inputs)
+
+    def coerce_values(self, context, container, report_issues):
+        utils.coerce_dict_values(context, container, self.inputs, 
report_issues)
+
+    def dump(self, context):
+        console.puts(context.style.node(self.name))
+        if self.description:
+            console.puts(context.style.meta(self.description))
+        with context.style.indent:
+            if self.implementation is not None:
+                console.puts('Implementation: %s' % 
context.style.literal(self.implementation))
+            if self.dependencies:
+                console.puts('Dependencies: %s'
+                     % ', '.join((str(context.style.literal(v)) for v in 
self.dependencies)))
+            if self.executor is not None:
+                console.puts('Executor: %s' % 
context.style.literal(self.executor))
+            if self.max_retries is not None:
+                console.puts('Max retries: %s' % 
context.style.literal(self.max_retries))
+            if self.retry_interval is not None:
+                console.puts('Retry interval: %s' % 
context.style.literal(self.retry_interval))
+            utils.dump_parameters(context, self.inputs, 'Inputs')
+
+
+class Interface(elements.Element, structure.ModelMixin):
+    """
+    A typed set of :class:`Operation`.
+
+    Properties:
+
+    * :code:`name`: Name
+    * :code:`description`: Description
+    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+    * :code:`inputs`: Dict of :class:`Parameter`
+    * :code:`operations`: Dict of :class:`Operation`
+    """
+
+    description = Column(Text)
+    type_name = Column(Text)
+    inputs = Column(type.StrictDict(key_cls=str, value_cls=elements.Parameter))
+    operations = Column(type.StrictDict(key_cls=str, value_cls=Operation))
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('name', self.name),
+            ('description', self.description),
+            ('type_name', self.type_name),
+            ('inputs', formatting.as_raw_dict(self.inputs)),
+            ('operations', formatting.as_raw_list(self.operations))))
+
+    def validate(self, context):
+        if self.type_name:
+            if context.modeling.interface_types.get_descendant(self.type_name) 
is None:
+                context.validation.report('interface "%s" has an unknown type: 
%s'
+                                          % (self.name,
+                                             
formatting.safe_repr(self.type_name)),
+                                          level=validation.Issue.BETWEEN_TYPES)
+
+        utils.validate_dict_values(context, self.inputs)
+        utils.validate_dict_values(context, self.operations)
+
+    def coerce_values(self, context, container, report_issues):
+        utils.coerce_dict_values(context, container, self.inputs, 
report_issues)
+        utils.coerce_dict_values(context, container, self.operations, 
report_issues)
+
+    def dump(self, context):
+        console.puts(context.style.node(self.name))
+        if self.description:
+            console.puts(context.style.meta(self.description))
+        with context.style.indent:
+            console.puts('Interface type: %s' % 
context.style.type(self.type_name))
+            utils.dump_parameters(context, self.inputs, 'Inputs')
+            utils.dump_dict_values(context, self.operations, 'Operations')
+
+
+class Capability(elements.Element, structure.ModelMixin):
+    """
+    A capability of a :class:`Node`.
+
+    An instance of a :class:`CapabilityTemplate`.
+
+    Properties:
+
+    * :code:`name`: Name
+    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+    * :code:`min_occurrences`: Minimum number of requirement matches required
+    * :code:`max_occurrences`: Maximum number of requirement matches allowed
+    * :code:`properties`: Dict of :class:`Parameter`
+    """
+
+    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)
+
+    @property
+    def has_enough_relationships(self):
+        if self.min_occurrences is not None:
+            return self.occurrences >= self.min_occurrences
+        return True
+
+    def relate(self):
+        if self.max_occurrences is not None:
+            if self.occurrences == self.max_occurrences:
+                return False
+        self.occurrences += 1
+        return True
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('name', self.name),
+            ('type_name', self.type_name),
+            ('properties', formatting.as_raw_dict(self.properties))))
+
+    def validate(self, context):
+        if context.modeling.capability_types.get_descendant(self.type_name) is 
None:
+            context.validation.report('capability "%s" has an unknown type: %s'
+                                      % (self.name,
+                                         formatting.safe_repr(self.type_name)),
+                                      level=validation.Issue.BETWEEN_TYPES)
+
+        utils.validate_dict_values(context, self.properties)
+
+    def coerce_values(self, context, container, report_issues):
+        utils.coerce_dict_values(context, container, self.properties, 
report_issues)
+
+    def dump(self, context):
+        console.puts(context.style.node(self.name))
+        with context.style.indent:
+            console.puts('Type: %s' % context.style.type(self.type_name))
+            console.puts('Occurrences: %s (%s%s)'
+                 % (self.occurrences,
+                    self.min_occurrences or 0,
+                    (' to %d' % self.max_occurrences)
+                    if self.max_occurrences is not None
+                    else ' or more'))
+            utils.dump_parameters(context, self.properties)
+
+
+class Artifact(elements.Element, structure.ModelMixin):
+    """
+    A file associated with a :class:`Node`.
+
+    Properties:
+
+    * :code:`name`: Name
+    * :code:`description`: Description
+    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+    * :code:`source_path`: Source path (CSAR or repository)
+    * :code:`target_path`: Path at destination machine
+    * :code:`repository_url`: Repository URL
+    * :code:`repository_credential`: Dict of string
+    * :code:`properties`: Dict of :class:`Parameter`
+    """
+
+    description = Column(Text)
+    type_name = Column(Text)
+    # Check: what's that?
+    source_path = Column()
+    target_path = Column()
+    repository_url = Column(Text)
+    repository_credential = Column(type.StrictDict(basestring, basestring))
+    properties = Column(type.StrictDict(basestring, elements.Parameter))
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('name', self.name),
+            ('description', self.description),
+            ('type_name', self.type_name),
+            ('source_path', self.source_path),
+            ('target_path', self.target_path),
+            ('repository_url', self.repository_url),
+            ('repository_credential', 
formatting.as_agnostic(self.repository_credential)),
+            ('properties', formatting.as_raw_dict(self.properties))))
+
+    def validate(self, context):
+        if context.modeling.artifact_types.get_descendant(self.type_name) is 
None:
+            context.validation.report('artifact "%s" has an unknown type: %s'
+                                      % (self.name,
+                                         formatting.safe_repr(self.type_name)),
+                                      level=validation.Issue.BETWEEN_TYPES)
+        utils.validate_dict_values(context, self.properties)
+
+    def coerce_values(self, context, container, report_issues):
+        utils.coerce_dict_values(context, container, self.properties, 
report_issues)
+
+    def dump(self, context):
+        console.puts(context.style.node(self.name))
+        if self.description:
+            console.puts(context.style.meta(self.description))
+        with context.style.indent:
+            console.puts('Artifact type: %s' % 
context.style.type(self.type_name))
+            console.puts('Source path: %s' % 
context.style.literal(self.source_path))
+            if self.target_path is not None:
+                console.puts('Target path: %s' % 
context.style.literal(self.target_path))
+            if self.repository_url is not None:
+                console.puts('Repository URL: %s' % 
context.style.literal(self.repository_url))
+            if self.repository_credential:
+                console.puts('Repository credential: %s'
+                     % context.style.literal(self.repository_credential))
+            utils.dump_parameters(context, self.properties)
+
+
+
+class Node(elements.Element, structure.ModelMixin):
+    """
+    An instance of a :class:`NodeTemplate`.
+
+    Nodes may have zero or more :class:`Relationship` instances to other nodes.
+
+    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:`artifacts`: Dict of :class:`Artifact`
+    * :code:`capabilities`: Dict of :class:`CapabilityTemplate`
+    * :code:`relationship`: List of :class:`Relationship`
+    """
+
+    type_name = Column(Text)
+    template_name = Column(Text)
+    properties = Column(type.StrictDict(basestring, elements.Parameter))
+    interfaces = Column(type.StrictDict(basestring, Interface))
+    artifacts = Column(type.StrictDict(basestring, Artifact))
+    capabilities = Column(type.StrictDict(basestring, Capability))
+    relationships = Column(type.StrictList(Relationship))
+
+    def satisfy_requirements(self, context):
+        node_template = 
context.modeling.model.node_templates.get(self.template_name)
+        satisfied = True
+        for i in range(len(node_template.requirement_templates)):
+            requirement_template = node_template.requirement_templates[i]
+
+            # Find target template
+            target_node_template, target_node_capability = \
+                requirement_template.find_target(context, node_template)
+            if target_node_template is not None:
+                satisfied = self._satisfy_capability(context,
+                                                     target_node_capability,
+                                                     target_node_template,
+                                                     requirement_template,
+                                                     
requirement_template_index=i)
+            else:
+                context.validation.report('requirement "%s" of node "%s" has 
no target node '
+                                          'template' % 
(requirement_template.name,
+                                                        self.id),
+                                          
level=validation.Issue.BETWEEN_INSTANCES)
+                satisfied = False
+        return satisfied
+
+    def _satisfy_capability(self, context, target_node_capability, 
target_node_template,
+                            requirement_template, requirement_template_index):
+        # Find target nodes
+        target_nodes = 
context.modeling.instance.find_nodes(target_node_template.name)
+        if target_nodes:
+            target_node = None
+            target_capability = None
+
+            if target_node_capability is not None:
+                # Relate to the first target node that has capacity
+                for node in target_nodes:
+                    target_capability = 
node.capabilities.get(target_node_capability.name)
+                    if target_capability.relate():
+                        target_node = node
+                        break
+            else:
+                # Use first target node
+                target_node = target_nodes[0]
+
+            if target_node is not None:
+                if requirement_template.relationship_template is not None:
+                    relationship = \
+                        
requirement_template.relationship_template.instantiate(context, self)
+                else:
+                    relationship = Relationship()
+                relationship.name = requirement_template.name
+                relationship.source_requirement_index = 
requirement_template_index
+                relationship.target_node_id = target_node.id
+                if target_capability is not None:
+                    relationship.target_capability_name = 
target_capability.name
+                self.relationships.append(relationship)
+            else:
+                context.validation.report('requirement "%s" of node "%s" 
targets node '
+                                          'template "%s" but its instantiated 
nodes do not '
+                                          'have enough capacity'
+                                          % (requirement_template.name,
+                                             self.id,
+                                             target_node_template.name),
+                                          
level=validation.Issue.BETWEEN_INSTANCES)
+                return False
+        else:
+            context.validation.report('requirement "%s" of node "%s" targets 
node template '
+                                      '"%s" but it has no instantiated nodes'
+                                      % (requirement_template.name,
+                                         self.id,
+                                         target_node_template.name),
+                                      level=validation.Issue.BETWEEN_INSTANCES)
+            return False
+
+
+    def validate_capabilities(self, context):
+        satisfied = False
+        for capability in self.capabilities.itervalues():
+            if not capability.has_enough_relationships:
+                context.validation.report('capability "%s" of node "%s" 
requires at least %d '
+                                          'relationships but has %d'
+                                          % (capability.name,
+                                             self.id,
+                                             capability.min_occurrences,
+                                             capability.occurrences),
+                                          
level=validation.Issue.BETWEEN_INSTANCES)
+                satisfied = False
+        return satisfied
+
+    @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)),
+            ('artifacts', formatting.as_raw_list(self.artifacts)),
+            ('capabilities', formatting.as_raw_list(self.capabilities)),
+            ('relationships', formatting.as_raw_list(self.relationships))))
+
+    def validate(self, context):
+        if len(self.id) > context.modeling.id_max_length:
+            context.validation.report('"%s" has an ID longer than the limit of 
%d characters: %d'
+                                      % (self.id,
+                                         context.modeling.id_max_length,
+                                         len(self.id)),
+                                      level=validation.Issue.BETWEEN_INSTANCES)
+
+        # TODO: validate that node template is of type?
+
+        utils.validate_dict_values(context, self.properties)
+        utils.validate_dict_values(context, self.interfaces)
+        utils.validate_dict_values(context, self.artifacts)
+        utils.validate_dict_values(context, self.capabilities)
+        utils.validate_list_values(context, self.relationships)
+
+    def coerce_values(self, context, container, report_issues):
+        utils.coerce_dict_values(context, self, self.properties, report_issues)
+        utils.coerce_dict_values(context, self, self.interfaces, report_issues)
+        utils.coerce_dict_values(context, self, self.artifacts, report_issues)
+        utils.coerce_dict_values(context, self, self.capabilities, 
report_issues)
+        utils.coerce_list_values(context, self, self.relationships, 
report_issues)
+
+    def dump(self, context):
+        console.puts('Node: %s' % context.style.node(self.id))
+        with context.style.indent:
+            console.puts('Template: %s' % 
context.style.node(self.template_name))
+            console.puts('Type: %s' % context.style.type(self.type_name))
+            utils.dump_parameters(context, self.properties)
+            utils.dump_interfaces(context, self.interfaces)
+            utils.dump_dict_values(context, self.artifacts, 'Artifacts')
+            utils.dump_dict_values(context, self.capabilities, 'Capabilities')
+            utils.dump_list_values(context, self.relationships, 
'Relationships')
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/09f1f737/aria/modeling/model_elements.py
----------------------------------------------------------------------
diff --git a/aria/modeling/model_elements.py b/aria/modeling/model_elements.py
index ab68fbe..bf478bb 100644
--- a/aria/modeling/model_elements.py
+++ b/aria/modeling/model_elements.py
@@ -17,6 +17,7 @@ from copy import deepcopy
 from sqlalchemy import (
     Column,
     Text,
+    Integer,
     DateTime
 )
 
@@ -24,9 +25,10 @@ from . import elements, instance_elements, utils
 from ..storage import structure, type
 from ..utils import collections
 from ..utils import formatting, console
+from ..parser import validation
 
 
-class ServiceTemplate(elements.ModelElement, structure.ModelMixin):
+class ServiceModel(elements.ModelElement, structure.ModelMixin):
 
     __tablename__ = 'blueprints'
 
@@ -133,6 +135,364 @@ class ServiceTemplate(elements.ModelElement, 
structure.ModelMixin):
         utils.dump_dict_values(context, self.operation_templates, 'Operation 
templates')
 
 
+class InterfaceTemplate(elements.ModelElement, structure.ModelMixin):
+    description = Column(Text)
+    type_name = Column(Text)
+    inputs = Column(type.StrictDict(basestring, elements.Parameter))
+    operation_templates = Column(type.StrictDict(basestring, 
OperationTemplate))
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('name', self.name),
+            ('description', self.description),
+            ('type_name', self.type_name),
+            ('inputs', formatting.as_raw_dict(self.properties)),  # pylint: 
disable=no-member
+            # TODO fix self.properties reference
+            ('operation_templates', 
formatting.as_raw_list(self.operation_templates))))
+
+    def instantiate(self, context, container):
+        interface = instance_elements.Interface(self.name, self.type_name)
+        interface.description = deepcopy_with_locators(self.description)
+        utils.instantiate_dict(context, container, interface.inputs, 
self.inputs)
+        utils.instantiate_dict(context, container, interface.operations, 
self.operation_templates)
+        return interface
+
+    def validate(self, context):
+        if self.type_name:
+            if context.modeling.interface_types.get_descendant(self.type_name) 
is None:
+                context.validation.report('interface "%s" has an unknown type: 
%s'
+                                          % (self.name, 
formatting.safe_repr(self.type_name)),
+                                          level=validation.Issue.BETWEEN_TYPES)
+
+        utils.validate_dict_values(context, self.inputs)
+        utils.validate_dict_values(context, self.operation_templates)
+
+    def coerce_values(self, context, container, report_issues):
+        utils.coerce_dict_values(context, container, self.inputs, 
report_issues)
+        utils.coerce_dict_values(context, container, self.operation_templates, 
report_issues)
+
+    def dump(self, context):
+        console.puts(context.style.node(self.name))
+        if self.description:
+            console.puts(context.style.meta(self.description))
+        with context.style.indent:
+            console.puts('Interface type: %s' % 
context.style.type(self.type_name))
+            dump_parameters(context, self.inputs, 'Inputs')
+            utils.dump_dict_values(context, self.operation_templates, 
'Operation templates')
+
+
+class OperationTemplate(elements.ModelElement, structure.ModelMixin):
+
+    description = Column(Text)
+    implementation = Column()
+    dependencies = Column(type.StrictList(item_cls=basestring))
+    executor = Column()
+    max_retries = Column(Integer)
+    retry_interval = Column(Integer)
+    inputs = Column(type.StrictDict(key_cls=basestring, 
value_cls=elements.Parameter))
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('name', self.name),
+            ('description', self.description),
+            ('implementation', self.implementation),
+            ('dependencies', self.dependencies),
+            ('executor', self.executor),
+            ('max_retries', self.max_retries),
+            ('retry_interval', self.retry_interval),
+            ('inputs', formatting.as_raw_dict(self.inputs))))
+
+    def instantiate(self, context, container):
+        operation = instance_elements.Operation(self.name)
+        operation.description = deepcopy_with_locators(self.description)
+        operation.implementation = self.implementation
+        operation.dependencies = self.dependencies
+        operation.executor = self.executor
+        operation.max_retries = self.max_retries
+        operation.retry_interval = self.retry_interval
+        utils.instantiate_dict(context, container, operation.inputs, 
self.inputs)
+        return operation
+
+    def validate(self, context):
+        utils.validate_dict_values(context, self.inputs)
+
+    def coerce_values(self, context, container, report_issues):
+        utils.coerce_dict_values(context, container, self.inputs, 
report_issues)
+
+    def dump(self, context):
+        console.puts(context.style.node(self.name))
+        if self.description:
+            console.puts(context.style.meta(self.description))
+        with context.style.indent:
+            if self.implementation is not None:
+                console.puts('Implementation: %s' % 
context.style.literal(self.implementation))
+            if self.dependencies:
+                console.puts('Dependencies: %s' % ', '.join(
+                    (str(context.style.literal(v)) for v in 
self.dependencies)))
+            if self.executor is not None:
+                console.puts('Executor: %s' % 
context.style.literal(self.executor))
+            if self.max_retries is not None:
+                console.puts('Max retries: %s' % 
context.style.literal(self.max_retries))
+            if self.retry_interval is not None:
+                console.puts('Retry interval: %s' % 
context.style.literal(self.retry_interval))
+            dump_parameters(context, self.inputs, 'Inputs')
+
+
+class ArtifactTemplate(elements.ModelElement, structure.ModelMixin):
+    """
+    A file associated with a :class:`NodeTemplate`.
+
+    Properties:
+
+    * :code:`name`: Name
+    * :code:`description`: Description
+    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+    * :code:`source_path`: Source path (CSAR or repository)
+    * :code:`target_path`: Path at destination machine
+    * :code:`repository_url`: Repository URL
+    * :code:`repository_credential`: Dict of string
+    * :code:`properties`: Dict of :class:`Parameter`
+    """
+
+    description = Column(Text)
+    type_name = Column(Text)
+    source_path = Column(Text)
+    # Check: what the hell is target path?
+    target_path = Column()
+    repository_url = Column(Text)
+    repository_credential = Column(type.StrictDict(basestring, basestring))
+    properties = Column(type.StrictDict(basestring, elements.Parameter))
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('name', self.name),
+            ('description', self.description),
+            ('type_name', self.type_name),
+            ('source_path', self.source_path),
+            ('target_path', self.target_path),
+            ('repository_url', self.repository_url),
+            ('repository_credential', 
formatting.as_agnostic(self.repository_credential)),
+            ('properties', 
formatting.as_raw_dict(self.properties.iteritems()))))
+
+    def instantiate(self, context, container):
+        artifact = instance_elements.Artifact(self.name, self.type_name, 
self.source_path)
+        artifact.description = deepcopy_with_locators(self.description)
+        artifact.target_path = self.target_path
+        artifact.repository_url = self.repository_url
+        artifact.repository_credential = self.repository_credential
+        utils.instantiate_dict(context, container, artifact.properties, 
self.properties)
+        return artifact
+
+    def validate(self, context):
+        if context.modeling.artifact_types.get_descendant(self.type_name) is 
None:
+            context.validation.report('artifact "%s" has an unknown type: %s'
+                                      % (self.name, 
formatting.safe_repr(self.type_name)),
+                                      level=validation.Issue.BETWEEN_TYPES)
+
+        utils.validate_dict_values(context, self.properties)
+
+    def coerce_values(self, context, container, report_issues):
+        utils.coerce_dict_values(context, container, self.properties, 
report_issues)
+
+    def dump(self, context):
+        console.puts(context.style.node(self.name))
+        if self.description:
+            console.puts(context.style.meta(self.description))
+        with context.style.indent:
+            console.puts('Artifact type: %s' % 
context.style.type(self.type_name))
+            console.puts('Source path: %s' % 
context.style.literal(self.source_path))
+            if self.target_path is not None:
+                console.puts('Target path: %s' % 
context.style.literal(self.target_path))
+            if self.repository_url is not None:
+                console.puts('Repository URL: %s' % 
context.style.literal(self.repository_url))
+            if self.repository_credential:
+                console.puts('Repository credential: %s'
+                     % context.style.literal(self.repository_credential))
+            dump_parameters(context, self.properties)
+
+
+class CapabilityTemplate(elements.ModelElement, structure.ModelMixin):
+    """
+    A capability of a :class:`NodeTemplate`. Nodes expose zero or more 
capabilities that can be
+    matched with :class:`Requirement` instances of other nodes.
+
+    Properties:
+
+    * :code:`name`: Name
+    * :code:`description`: Description
+    * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+    * :code:`min_occurrences`: Minimum number of requirement matches required
+    * :code:`max_occurrences`: Maximum number of requirement matches allowed
+    * :code:`valid_source_node_type_names`: Must be represented in the 
:class:`ModelingContext`
+    * :code:`properties`: Dict of :class:`Parameter`
+    """
+
+    description = Column(Text)
+    type_name = Column(Text)
+    min_occurrences = Column(Integer, default=None)  # optional
+    max_occurrences = Column(Integer, default=None)  # optional
+    # CHECK: type?
+    valid_source_node_type_names = Column()
+    properties = Column(type.StrictDict(basestring, elements.Parameter))
+
+    def satisfies_requirement(self,
+                              context,
+                              source_node_template,
+                              requirement,
+                              target_node_template):
+        # Do we match the required capability type?
+        capability_types = context.modeling.capability_types
+        if not 
capability_types.is_descendant(requirement.target_capability_type_name,
+                                              self.type_name):
+            return False
+
+        # Are we in valid_source_node_type_names?
+        if self.valid_source_node_type_names:
+            for valid_source_node_type_name in 
self.valid_source_node_type_names:
+                if not 
context.modeling.node_types.is_descendant(valid_source_node_type_name,
+                                                                 
source_node_template.type_name):
+                    return False
+
+        # Apply requirement constraints
+        if requirement.target_node_template_constraints:
+            for node_type_constraint in 
requirement.target_node_template_constraints:
+                if not node_type_constraint(target_node_template, 
source_node_template):
+                    return False
+
+        return True
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('name', self.name),
+            ('description', self.description),
+            ('type_name', self.type_name),
+            ('min_occurrences', self.min_occurrences),
+            ('max_occurrences', self.max_occurrences),
+            ('valid_source_node_type_names', 
self.valid_source_node_type_names),
+            ('properties', formatting.as_raw_dict(self.properties))))
+
+    def instantiate(self, context, container):
+        capability = instance_elements.Capability(self.name, self.type_name)
+        capability.min_occurrences = self.min_occurrences
+        capability.max_occurrences = self.max_occurrences
+        utils.instantiate_dict(context, container, capability.properties, 
self.properties)
+        return capability
+
+    def validate(self, context):
+        if context.modeling.capability_types.get_descendant(self.type_name) is 
None:
+            context.validation.report(
+                'capability "%s" refers to an unknown type: %s'
+                % (self.name, formatting.safe_repr(self.type)),  # pylint: 
disable=no-member
+                # TODO fix self.type reference
+                level=validation.Issue.BETWEEN_TYPES)
+
+        utils.validate_dict_values(context, self.properties)
+
+    def coerce_values(self, context, container, report_issues):
+        utils.coerce_dict_values(context, self, self.properties, report_issues)
+
+    def dump(self, context):
+        console.puts(context.style.node(self.name))
+        if self.description:
+            console.puts(context.style.meta(self.description))
+        with context.style.indent:
+            console.puts('Type: %s' % context.style.type(self.type_name))
+            console.puts('Occurrences: %d%s'
+                 % (self.min_occurrences or 0, (' to %d' % 
self.max_occurrences)
+                    if self.max_occurrences is not None
+                    else ' or more'))
+            if self.valid_source_node_type_names:
+                console.puts('Valid source node types: %s'
+                     % ', '.join((str(context.style.type(v))
+                                  for v in self.valid_source_node_type_names)))
+            dump_parameters(context, self.properties)
+
+
+class NodeTemplate(elements.ModelElement, structure.ModelMixin):
+    description = Column(Text)
+    type_name = Column(Text)
+    default_instances = Column(Integer, default=1)
+    min_instances = Column(Integer, default=0)
+    man_instances = Column(Integer, default=None)
+    properties = Column(type.StrictDict(basestring, elements.Parameter))
+    interface_templates = Column(type.StrictDict(basestring, 
InterfaceTemplate))
+    artifact_templates = Column(type.StrictDict(basestring, ArtifactTemplate))
+    capability_templataes = Column(type.StrictDict(basestring, 
CapabilityTemplate))
+    target_node_template_constraints = 
Column(type.StrictList(elements.FunctionType))
+
+    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:
+                if not node_type_constraint(target_node_template, self):
+                    return False
+        return True
+
+    @property
+    def as_raw(self):
+        return collections.OrderedDict((
+            ('name', self.name),
+            ('description', self.description),
+            ('type_name', self.type_name),
+            ('default_instances', self.default_instances),
+            ('min_instances', self.min_instances),
+            ('max_instances', self.max_instances),
+            ('properties', formatting.as_raw_dict(self.properties)),
+            ('interface_templates', 
formatting.as_raw_list(self.interface_templates)),
+            ('artifact_templates', 
formatting.as_raw_list(self.artifact_templates)),
+            ('capability_templates', 
formatting.as_raw_list(self.capability_templates)),
+            ('requirement_templates', 
formatting.as_raw_list(self.requirement_templates))))
+
+    def instantiate(self, context, container):
+        node = instance_elements.Node(context, self.type_name, self.name)
+        utils.instantiate_dict(context, node, node.properties, self.properties)
+        utils.instantiate_dict(context, node, node.interfaces, 
self.interface_templates)
+        utils.instantiate_dict(context, node, node.artifacts, 
self.artifact_templates)
+        utils.instantiate_dict(context, node, node.capabilities, 
self.capability_templates)
+        return node
+
+    def validate(self, context):
+        if context.modeling.node_types.get_descendant(self.type_name) is None:
+            context.validation.report('node template "%s" has an unknown type: 
%s'
+                                      % (self.name,
+                                         formatting.safe_repr(self.type_name)),
+                                      level=validation.Issue.BETWEEN_TYPES)
+
+        utils.validate_dict_values(context, self.properties)
+        utils.validate_dict_values(context, self.interface_templates)
+        utils.validate_dict_values(context, self.artifact_templates)
+        utils.validate_dict_values(context, self.capability_templates)
+        utils.validate_list_values(context, self.requirement_templates)
+
+    def coerce_values(self, context, container, report_issues):
+        utils.coerce_dict_values(context, self, self.properties, report_issues)
+        utils.coerce_dict_values(context, self, self.interface_templates, 
report_issues)
+        utils.coerce_dict_values(context, self, self.artifact_templates, 
report_issues)
+        utils.coerce_dict_values(context, self, self.capability_templates, 
report_issues)
+        utils.coerce_list_values(context, self, self.requirement_templates, 
report_issues)
+
+    def dump(self, context):
+        console.puts('Node template: %s' % context.style.node(self.name))
+        if self.description:
+            console.puts(context.style.meta(self.description))
+        with context.style.indent:
+            console.puts('Type: %s' % context.style.type(self.type_name))
+            console.puts('Instances: %d (%d%s)'
+                 % (self.default_instances,
+                    self.min_instances,
+                    (' to %d' % self.max_instances
+                     if self.max_instances is not None
+                     else ' or more')))
+            dump_parameters(context, self.properties)
+            utils.dump_interfaces(context, self.interface_templates)
+            utils.dump_dict_values(context, self.artifact_templates, 'Artifact 
tempaltes')
+            utils.dump_dict_values(context, self.capability_templates, 
'Capability templates')
+            utils.dump_list_values(context, self.requirement_templates, 
'Requirement templates')
+
+
 def dump_parameters(context, parameters, name='Properties'):
     if not parameters:
         return
@@ -160,6 +520,7 @@ def deepcopy_with_locators(value):
     copy_locators(res, value)
     return res
 
+
 def copy_locators(target, source):
     """
     Copies over :code:`_locator` for all elements, recursively.

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/09f1f737/aria/storage/type.py
----------------------------------------------------------------------
diff --git a/aria/storage/type.py b/aria/storage/type.py
index 02b0ee3..728f8b1 100644
--- a/aria/storage/type.py
+++ b/aria/storage/type.py
@@ -14,6 +14,7 @@
 # limitations under the License.
 import json
 from collections import namedtuple
+from types import NoneType
 
 from sqlalchemy import (
     TypeDecorator,
@@ -106,14 +107,14 @@ class _StrictDictMixin(object):
 
     @classmethod
     def _assert_strict_key(cls, key):
-        if not isinstance(key, cls._key_cls):
+        if not isinstance(key, (cls._key_cls, NoneType)):
             raise exceptions.StorageError("Key type was set strictly to {0}, 
but was {1}".format(
                 cls._key_cls, type(key)
             ))
 
     @classmethod
     def _assert_strict_value(cls, value):
-        if not isinstance(value, cls._value_cls):
+        if not isinstance(value, (cls._value_cls, NoneType)):
             raise exceptions.StorageError("Value type was set strictly to {0}, 
but was {1}".format(
                 cls._value_cls, type(value)
             ))
@@ -172,7 +173,7 @@ class _StrictListMixin(object):
 
     @classmethod
     def _assert_item(cls, item):
-        if not isinstance(item, cls._item_cls):
+        if not isinstance(item, (cls._item_cls, NoneType)):
             raise exceptions.StorageError("Key type was set strictly to {0}, 
but was {1}".format(
                 cls._item_cls, type(item)
             ))
@@ -200,7 +201,7 @@ _strict_dict_id = namedtuple('strict_dict_id', 'key_cls, 
value_cls')
 _strict_map = {}
 
 
-def StrictDict(key_cls, value_cls):
+def StrictDict(key_cls=NoneType, value_cls=NoneType):
     strict_dict_map_key = _strict_dict_id(key_cls=key_cls, value_cls=value_cls)
     if strict_dict_map_key not in _strict_map:
         strict_dict_cls = type(


Reply via email to