ARIA-139 Support attributes * Fully implement attribute support in parser * New intrinsic function evaluation mechanism * Implemented more intrinsic functions, including get_attribute * Fix to one-on-one relationship back population * Fixes to TOSCA use case examples * Indirectly related: re-enabled node_filter mechanism and reworked filter constraints in order to make them serializable * utils/type is much more robust now and consolidates all conversions and names * Moved dsl_specification to new utils/specification (because utils/type uses it)
Project: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/commit/60ea3ebb Tree: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/tree/60ea3ebb Diff: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/diff/60ea3ebb Branch: refs/heads/ARIA-208-Missing-back-refrences-for-models Commit: 60ea3ebb21e762d36115db26563a93dd3cb72003 Parents: 2ee06b8 Author: Tal Liron <tal.li...@gmail.com> Authored: Wed Apr 19 20:07:33 2017 -0500 Committer: Tal Liron <tal.li...@gmail.com> Committed: Thu May 11 11:31:02 2017 -0500 ---------------------------------------------------------------------- aria/core.py | 6 +- aria/modeling/contraints.py | 28 + aria/modeling/functions.py | 104 ++- aria/modeling/mixins.py | 2 +- aria/modeling/relationship.py | 19 +- aria/modeling/service_common.py | 164 ++++- aria/modeling/service_instance.py | 78 ++- aria/modeling/service_template.py | 126 ++-- aria/modeling/utils.py | 50 +- .../execution_plugin/instantiation.py | 2 +- aria/parser/__init__.py | 4 +- aria/parser/consumption/__init__.py | 11 +- aria/parser/consumption/modeling.py | 4 +- aria/parser/presentation/fields.py | 3 +- aria/parser/presentation/presentation.py | 3 +- aria/parser/presentation/utils.py | 3 +- aria/parser/specification.py | 39 +- aria/parser/validation/issue.py | 2 +- aria/utils/caching.py | 13 +- aria/utils/formatting.py | 16 +- aria/utils/specification.py | 53 ++ aria/utils/type.py | 157 ++++- .../block-storage-1/block-storage-1.yaml | 2 +- .../block-storage-2/block-storage-2.yaml | 2 +- .../block-storage-3/block-storage-3.yaml | 2 +- .../block-storage-4/block-storage-4.yaml | 2 +- .../block-storage-5/block-storage-5.yaml | 6 +- .../block-storage-6/block-storage-6.yaml | 4 +- .../use-cases/multi-tier-1/multi-tier-1.yaml | 14 +- .../simple_v1_0/__init__.py | 12 +- .../simple_v1_0/assignments.py | 16 +- .../simple_v1_0/data_types.py | 20 +- .../simple_v1_0/definitions.py | 18 +- .../aria_extension_tosca/simple_v1_0/filters.py | 6 +- .../simple_v1_0/functions.py | 536 --------------- .../aria_extension_tosca/simple_v1_0/misc.py | 20 +- .../simple_v1_0/modeling/__init__.py | 185 ++--- .../simple_v1_0/modeling/constraints.py | 144 ++++ .../simple_v1_0/modeling/data_types.py | 23 +- .../simple_v1_0/modeling/functions.py | 677 +++++++++++++++++++ .../simple_v1_0/modeling/properties.py | 17 +- .../presentation/field_validators.py | 13 +- .../simple_v1_0/presenter.py | 4 +- .../simple_v1_0/templates.py | 47 +- .../aria_extension_tosca/simple_v1_0/types.py | 18 +- .../node-cellar/node-cellar.yaml | 6 +- 46 files changed, 1644 insertions(+), 1037 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/core.py ---------------------------------------------------------------------- diff --git a/aria/core.py b/aria/core.py index af1984a..cc943ef 100644 --- a/aria/core.py +++ b/aria/core.py @@ -77,10 +77,14 @@ class Core(object): consumption.ConsumerChain( context, ( + consumption.CoerceServiceInstanceValues, + consumption.ValidateServiceInstance, consumption.SatisfyRequirements, + consumption.CoerceServiceInstanceValues, consumption.ValidateCapabilities, consumption.FindHosts, - consumption.ConfigureOperations + consumption.ConfigureOperations, + consumption.CoerceServiceInstanceValues )).consume() if context.validation.dump_issues(): raise exceptions.InstantiationError('Failed to instantiate service template') http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/modeling/contraints.py ---------------------------------------------------------------------- diff --git a/aria/modeling/contraints.py b/aria/modeling/contraints.py new file mode 100644 index 0000000..107b010 --- /dev/null +++ b/aria/modeling/contraints.py @@ -0,0 +1,28 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +class NodeTemplateConstraint(object): + """ + Used to constrain requirements for node templates. + + Must be serializable. + """ + + def matches(self, source_node_template, target_node_template): + """ + Returns true is the target matches the constraint for the source. + """ + raise NotImplementedError http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/modeling/functions.py ---------------------------------------------------------------------- diff --git a/aria/modeling/functions.py b/aria/modeling/functions.py index 02f4454..06fd19f 100644 --- a/aria/modeling/functions.py +++ b/aria/modeling/functions.py @@ -13,20 +13,118 @@ # See the License for the specific language governing permissions and # limitations under the License. +from ..parser.consumption import ConsumptionContext +from ..parser.exceptions import InvalidValueError +from ..utils.collections import OrderedDict +from . import exceptions + + class Function(object): """ - An intrinsic function. + Base class for intrinsic functions. Serves as a placeholder for a value that should eventually + be derived by "evaluating" (calling) the function. - Serves as a placeholder for a value that should eventually be derived by calling the function. + Note that this base class is provided as a convenience and you do not have to inherit it: any + object with an ``__evaluate__`` method would be treated similarly. """ @property def as_raw(self): raise NotImplementedError - def _evaluate(self, context, container): + def __evaluate__(self, container_holder): + """ + Evaluates the function if possible. If impossible, raises + :class:`CannotEvaluateFunctionException` (do not just return None). + + :rtype: Evaluation (or any object with ``value`` and ``final`` properties) + """ + raise NotImplementedError def __deepcopy__(self, memo): # Circumvent cloning in order to maintain our state return self + + +class Evaluation(object): + """ + An evaluated :class:`Function` return value. + """ + + def __init__(self, value, final=False): + self.value = value + self.final = final + + +def evaluate(value, container_holder, report_issues=False): # pylint: disable=too-many-branches + """ + Recursively attempts to call ``__evaluate__``. If an evaluation occurred will return an + :class:`Evaluation`, otherwise it will be None. If any evaluation is non-final, then the entire + evaluation will also be non-final. + + The ``container_holder`` argument should have three properties: ``container`` should return + the model that contains the value, ``service`` should return the containing + :class:`aria.modeling.models.Service` model or None, and ``service_template`` should return the + containing :class:`aria.modeling.models.ServiceTemplate` model or None. + """ + + evaluated = False + final = True + + if hasattr(value, '__evaluate__'): + try: + evaluation = value.__evaluate__(container_holder) + + # Verify evaluation structure + if (evaluation is None) \ + or (not hasattr(evaluation, 'value')) \ + or (not hasattr(evaluation, 'final')): + raise InvalidValueError('bad __evaluate__ implementation') + + evaluated = True + value = evaluation.value + final = evaluation.final + + # The evaluated value might itself be evaluable + evaluation = evaluate(value, container_holder, report_issues) + if evaluation is not None: + value = evaluation.value + if not evaluation.final: + final = False + except exceptions.CannotEvaluateFunctionException: + pass + except InvalidValueError as e: + if report_issues: + context = ConsumptionContext.get_thread_local() + context.validation.report(e.issue) + + elif isinstance(value, list): + evaluated_list = [] + for v in value: + evaluation = evaluate(v, container_holder, report_issues) + if evaluation is not None: + evaluated_list.append(evaluation.value) + evaluated = True + if not evaluation.final: + final = False + else: + evaluated_list.append(v) + if evaluated: + value = evaluated_list + + elif isinstance(value, dict): + evaluated_dict = OrderedDict() + for k, v in value.iteritems(): + evaluation = evaluate(v, container_holder, report_issues) + if evaluation is not None: + evaluated_dict[k] = evaluation.value + evaluated = True + if not evaluation.final: + final = False + else: + evaluated_dict[k] = v + if evaluated: + value = evaluated_dict + + return Evaluation(value, final) if evaluated else None http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/modeling/mixins.py ---------------------------------------------------------------------- diff --git a/aria/modeling/mixins.py b/aria/modeling/mixins.py index e6db5a3..38c812d 100644 --- a/aria/modeling/mixins.py +++ b/aria/modeling/mixins.py @@ -124,7 +124,7 @@ class InstanceModelMixin(ModelMixin): def validate(self): pass - def coerce_values(self, container, report_issues): + def coerce_values(self, report_issues): pass def dump(self): http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/modeling/relationship.py ---------------------------------------------------------------------- diff --git a/aria/modeling/relationship.py b/aria/modeling/relationship.py index 291d08c..40be5b2 100644 --- a/aria/modeling/relationship.py +++ b/aria/modeling/relationship.py @@ -146,13 +146,18 @@ def one_to_one(model_class, false to disable :type back_populates: basestring|bool """ - if back_populates is None: - back_populates = model_class.__tablename__ + backref_kwargs = None + if back_populates is not NO_BACK_POP: + if back_populates is None: + back_populates = model_class.__tablename__ + backref_kwargs = {'name': back_populates, 'uselist': False} + back_populates = None return _relationship(model_class, other_table, fk=fk, back_populates=back_populates, + backref_kwargs=backref_kwargs, other_fk=other_fk) @@ -190,6 +195,7 @@ def one_to_many(model_class, rel_kwargs.setdefault('cascade', 'all') if back_populates is None: back_populates = model_class.__tablename__ + return _relationship( model_class, child_table, @@ -330,10 +336,11 @@ def _relationship(model_class, if backref_kwargs: assert back_populates is None - return relationship(lambda: _get_class_for_table(model_class, other_table_name), - backref=backref(**backref_kwargs), - **relationship_kwargs - ) + return relationship( + lambda: _get_class_for_table(model_class, other_table_name), + backref=backref(**backref_kwargs), + **relationship_kwargs + ) else: if back_populates is not NO_BACK_POP: relationship_kwargs['back_populates'] = back_populates http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/modeling/service_common.py ---------------------------------------------------------------------- diff --git a/aria/modeling/service_common.py b/aria/modeling/service_common.py index 1188f34..e9c96a4 100644 --- a/aria/modeling/service_common.py +++ b/aria/modeling/service_common.py @@ -23,19 +23,20 @@ from sqlalchemy import ( from sqlalchemy.ext.declarative import declared_attr from ..parser.consumption import ConsumptionContext -from ..utils import collections, formatting, console -from .mixins import InstanceModelMixin, TemplateModelMixin +from ..utils import (collections, formatting, console, caching) +from ..utils.type import (canonical_type_name, full_type_name) +from .mixins import (InstanceModelMixin, TemplateModelMixin) from . import ( relationship, - utils + functions ) -class ParameterBase(TemplateModelMixin): +class ParameterBase(TemplateModelMixin, caching.HasCachedMethods): """ - Represents a typed value. + Represents a typed value. The value can contain nested intrinsic functions. - This model is used by both service template and service instance elements. + This model can be used as the ``container_holder`` argument for :func:`functions.evaluate`. :ivar name: Name :vartype name: basestring @@ -50,8 +51,120 @@ class ParameterBase(TemplateModelMixin): name = Column(Text) type_name = Column(Text) - value = Column(PickleType) description = Column(Text) + _value = Column(PickleType) + + @property + def value(self): + value = self._value + if value is not None: + evaluation = functions.evaluate(value, self) + if evaluation is not None: + value = evaluation.value + return value + + @value.setter + def value(self, value): + self._value = value + + @property + @caching.cachedmethod + def owner(self): + """ + The sole owner of this parameter, which is another model that relates to it. + + *All* parameters should have an owner model. In case this property method fails to find + it, it will raise a ValueError, which should signify an abnormal, orphaned parameter. + """ + + # Find first non-null relationship + for the_relationship in self.__mapper__.relationships: + v = getattr(self, the_relationship.key) + if v: + return v[0] # because we are many-to-many, the back reference will be a list + + raise ValueError('orphaned parameter: does not have an owner: {0}'.format(self.name)) + + + @property + @caching.cachedmethod + def container(self): # pylint: disable=too-many-return-statements,too-many-branches + """ + The logical container for this parameter, which would be another model: service, node, + group, or policy (or their templates). + + The logical container is equivalent to the ``SELF`` keyword used by intrinsic functions in + TOSCA. + + *All* parameters should have a container model. In case this property method fails to find + it, it will raise a ValueError, which should signify an abnormal, orphaned parameter. + """ + + from . import models + + container = self.owner + + # Extract interface from operation + if isinstance(container, models.Operation): + container = container.interface + elif isinstance(container, models.OperationTemplate): + container = container.interface_template + + # Extract from other models + if isinstance(container, models.Interface): + container = container.node or container.group or container.relationship + elif isinstance(container, models.InterfaceTemplate): + container = container.node_template or container.group_template \ + or container.relationship_template + elif isinstance(container, models.Capability) or isinstance(container, models.Artifact): + container = container.node + elif isinstance(container, models.CapabilityTemplate) \ + or isinstance(container, models.ArtifactTemplate): + container = container.node_template + elif isinstance(container, models.Task): + container = container.actor + + # Extract node from relationship + if isinstance(container, models.Relationship): + container = container.source_node + elif isinstance(container, models.RelationshipTemplate): + container = container.requirement_template.node_template + + if container is not None: + return container + + raise ValueError('orphaned parameter: does not have a container: {0}'.format(self.name)) + + @property + @caching.cachedmethod + def service(self): + """ + The :class:`Service` containing this parameter, or None if not contained in a service. + """ + + from . import models + container = self.container + if isinstance(container, models.Service): + return container + elif hasattr(container, 'service'): + return container.service + return None + + @property + @caching.cachedmethod + def service_template(self): + """ + The :class:`ServiceTemplate` containing this parameter, or None if not contained in a + service template. + """ + + from . import models + container = self.container + if isinstance(container, models.ServiceTemplate): + return container + elif hasattr(container, 'service_template'): + return container.service_template + return None @property def as_raw(self): @@ -63,27 +176,30 @@ class ParameterBase(TemplateModelMixin): def instantiate(self, container): from . import models - return models.Parameter(name=self.name, + return models.Parameter(name=self.name, # pylint: disable=unexpected-keyword-arg type_name=self.type_name, - value=self.value, + _value=self._value, description=self.description) - def coerce_values(self, container, report_issues): - if self.value is not None: - self.value = utils.coerce_value(container, self.value, - report_issues) + def coerce_values(self, report_issues): + value = self._value + if value is not None: + evaluation = functions.evaluate(value, self, report_issues) + if (evaluation is not None) and evaluation.final: + # A final evaluation can safely replace the existing value + self._value = evaluation.value def dump(self): context = ConsumptionContext.get_thread_local() if self.type_name is not None: console.puts('{0}: {1} ({2})'.format( context.style.property(self.name), - context.style.literal(self.value), + context.style.literal(formatting.as_raw(self.value)), context.style.type(self.type_name))) else: console.puts('{0}: {1}'.format( context.style.property(self.name), - context.style.literal(self.value))) + context.style.literal(formatting.as_raw(self.value)))) if self.description: console.puts(context.style.meta(self.description)) @@ -101,11 +217,15 @@ class ParameterBase(TemplateModelMixin): :param description: Description (optional) :type description: basestring """ - return cls(name=name, - type_name=formatting.full_type_name(value) - if value is not None else None, - value=value, - description=description) + + from . import models + type_name = canonical_type_name(value) + if type_name is None: + type_name = full_type_name(value) + return models.Parameter(name=name, # pylint: disable=unexpected-keyword-arg + type_name=type_name, + value=value, + description=description) class TypeBase(InstanceModelMixin): @@ -188,7 +308,7 @@ class TypeBase(InstanceModelMixin): self._append_raw_children(types) return types - def coerce_values(self, container, report_issues): + def coerce_values(self, report_issues): pass def dump(self): @@ -237,7 +357,7 @@ class MetadataBase(TemplateModelMixin): ('name', self.name), ('value', self.value))) - def coerce_values(self, container, report_issues): + def coerce_values(self, report_issues): pass def instantiate(self, container): http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/modeling/service_instance.py ---------------------------------------------------------------------- diff --git a/aria/modeling/service_instance.py b/aria/modeling/service_instance.py index ad8e7ed..1efe1e1 100644 --- a/aria/modeling/service_instance.py +++ b/aria/modeling/service_instance.py @@ -251,16 +251,16 @@ class ServiceBase(InstanceModelMixin): utils.validate_dict_values(self.outputs) utils.validate_dict_values(self.workflows) - def coerce_values(self, container, report_issues): - utils.coerce_dict_values(container, self.meta_data, report_issues) - utils.coerce_dict_values(container, self.nodes, report_issues) - utils.coerce_dict_values(container, self.groups, report_issues) - utils.coerce_dict_values(container, self.policies, report_issues) + def coerce_values(self, report_issues): + utils.coerce_dict_values(self.meta_data, report_issues) + utils.coerce_dict_values(self.nodes, report_issues) + utils.coerce_dict_values(self.groups, report_issues) + utils.coerce_dict_values(self.policies, report_issues) if self.substitution is not None: - self.substitution.coerce_values(container, report_issues) - utils.coerce_dict_values(container, self.inputs, report_issues) - utils.coerce_dict_values(container, self.outputs, report_issues) - utils.coerce_dict_values(container, self.workflows, report_issues) + self.substitution.coerce_values(report_issues) + utils.coerce_dict_values(self.inputs, report_issues) + utils.coerce_dict_values(self.outputs, report_issues) + utils.coerce_dict_values(self.workflows, report_issues) def dump(self): context = ConsumptionContext.get_thread_local() @@ -513,6 +513,10 @@ class NodeBase(InstanceModelMixin): def properties(cls): return relationship.many_to_many(cls, 'parameter', prefix='properties', dict_key='name') + @declared_attr + def attributes(cls): + return relationship.many_to_many(cls, 'parameter', prefix='attributes', dict_key='name') + # endregion description = Column(Text) @@ -646,6 +650,7 @@ class NodeBase(InstanceModelMixin): ('name', self.name), ('type_name', self.type.name), ('properties', formatting.as_raw_dict(self.properties)), + ('attributes', 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)), @@ -664,17 +669,19 @@ class NodeBase(InstanceModelMixin): # TODO: validate that node template is of type? utils.validate_dict_values(self.properties) + utils.validate_dict_values(self.attributes) utils.validate_dict_values(self.interfaces) utils.validate_dict_values(self.artifacts) utils.validate_dict_values(self.capabilities) utils.validate_list_values(self.outbound_relationships) - def coerce_values(self, container, report_issues): - utils.coerce_dict_values(self, self.properties, report_issues) - utils.coerce_dict_values(self, self.interfaces, report_issues) - utils.coerce_dict_values(self, self.artifacts, report_issues) - utils.coerce_dict_values(self, self.capabilities, report_issues) - utils.coerce_list_values(self, self.outbound_relationships, report_issues) + def coerce_values(self, report_issues): + utils.coerce_dict_values(self.properties, report_issues) + utils.coerce_dict_values(self.attributes, report_issues) + utils.coerce_dict_values(self.interfaces, report_issues) + utils.coerce_dict_values(self.artifacts, report_issues) + utils.coerce_dict_values(self.capabilities, report_issues) + utils.coerce_list_values(self.outbound_relationships, report_issues) def dump(self): context = ConsumptionContext.get_thread_local() @@ -683,6 +690,7 @@ class NodeBase(InstanceModelMixin): console.puts('Type: {0}'.format(context.style.type(self.type.name))) console.puts('Template: {0}'.format(context.style.node(self.node_template.name))) utils.dump_dict_values(self.properties, 'Properties') + utils.dump_dict_values(self.attributes, 'Attributes') utils.dump_interfaces(self.interfaces) utils.dump_dict_values(self.artifacts, 'Artifacts') utils.dump_dict_values(self.capabilities, 'Capabilities') @@ -797,9 +805,9 @@ class GroupBase(InstanceModelMixin): utils.validate_dict_values(self.properties) utils.validate_dict_values(self.interfaces) - def coerce_values(self, container, report_issues): - utils.coerce_dict_values(container, self.properties, report_issues) - utils.coerce_dict_values(container, self.interfaces, report_issues) + def coerce_values(self, report_issues): + utils.coerce_dict_values(self.properties, report_issues) + utils.coerce_dict_values(self.interfaces, report_issues) def dump(self): context = ConsumptionContext.get_thread_local() @@ -916,8 +924,8 @@ class PolicyBase(InstanceModelMixin): def validate(self): utils.validate_dict_values(self.properties) - def coerce_values(self, container, report_issues): - utils.coerce_dict_values(container, self.properties, report_issues) + def coerce_values(self, report_issues): + utils.coerce_dict_values(self.properties, report_issues) def dump(self): context = ConsumptionContext.get_thread_local() @@ -1017,8 +1025,8 @@ class SubstitutionBase(InstanceModelMixin): def validate(self): utils.validate_dict_values(self.mappings) - def coerce_values(self, container, report_issues): - utils.coerce_dict_values(container, self.mappings, report_issues) + def coerce_values(self, report_issues): + utils.coerce_dict_values(self.mappings, report_issues) def dump(self): context = ConsumptionContext.get_thread_local() @@ -1121,7 +1129,7 @@ class SubstitutionMappingBase(InstanceModelMixin): return collections.OrderedDict(( ('name', self.name))) - def coerce_values(self, container, report_issues): + def coerce_values(self, report_issues): pass def validate(self): @@ -1311,9 +1319,9 @@ class RelationshipBase(InstanceModelMixin): utils.validate_dict_values(self.properties) utils.validate_dict_values(self.interfaces) - def coerce_values(self, container, report_issues): - utils.coerce_dict_values(container, self.properties, report_issues) - utils.coerce_dict_values(container, self.interfaces, report_issues) + def coerce_values(self, report_issues): + utils.coerce_dict_values(self.properties, report_issues) + utils.coerce_dict_values(self.interfaces, report_issues) def dump(self): context = ConsumptionContext.get_thread_local() @@ -1451,8 +1459,8 @@ class CapabilityBase(InstanceModelMixin): def validate(self): utils.validate_dict_values(self.properties) - def coerce_values(self, container, report_issues): - utils.coerce_dict_values(container, self.properties, report_issues) + def coerce_values(self, report_issues): + utils.coerce_dict_values(self.properties, report_issues) def dump(self): context = ConsumptionContext.get_thread_local() @@ -1598,9 +1606,9 @@ class InterfaceBase(InstanceModelMixin): utils.validate_dict_values(self.inputs) utils.validate_dict_values(self.operations) - def coerce_values(self, container, report_issues): - utils.coerce_dict_values(container, self.inputs, report_issues) - utils.coerce_dict_values(container, self.operations, report_issues) + def coerce_values(self, report_issues): + utils.coerce_dict_values(self.inputs, report_issues) + utils.coerce_dict_values(self.operations, report_issues) def dump(self): context = ConsumptionContext.get_thread_local() @@ -1765,8 +1773,8 @@ class OperationBase(InstanceModelMixin): # TODO must be associated with interface or service utils.validate_dict_values(self.inputs) - def coerce_values(self, container, report_issues): - utils.coerce_dict_values(container, self.inputs, report_issues) + def coerce_values(self, report_issues): + utils.coerce_dict_values(self.inputs, report_issues) def dump(self): context = ConsumptionContext.get_thread_local() @@ -1905,8 +1913,8 @@ class ArtifactBase(InstanceModelMixin): def validate(self): utils.validate_dict_values(self.properties) - def coerce_values(self, container, report_issues): - utils.coerce_dict_values(container, self.properties, report_issues) + def coerce_values(self, report_issues): + utils.coerce_dict_values(self.properties, report_issues) def dump(self): context = ConsumptionContext.get_thread_local() http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/modeling/service_template.py ---------------------------------------------------------------------- diff --git a/aria/modeling/service_template.py b/aria/modeling/service_template.py index e3320fa..7a192a7 100644 --- a/aria/modeling/service_template.py +++ b/aria/modeling/service_template.py @@ -17,7 +17,6 @@ from __future__ import absolute_import # so we can import standard 'types' -from types import FunctionType from datetime import datetime from sqlalchemy import ( @@ -25,7 +24,8 @@ from sqlalchemy import ( Text, Integer, Boolean, - DateTime + DateTime, + PickleType ) from sqlalchemy.ext.declarative import declared_attr from sqlalchemy.ext.associationproxy import association_proxy @@ -347,16 +347,16 @@ class ServiceTemplateBase(TemplateModelMixin): if self.artifact_types is not None: self.artifact_types.validate() - def coerce_values(self, container, report_issues): - utils.coerce_dict_values(container, self.meta_data, report_issues) - utils.coerce_dict_values(container, self.node_templates, report_issues) - utils.coerce_dict_values(container, self.group_templates, report_issues) - utils.coerce_dict_values(container, self.policy_templates, report_issues) + def coerce_values(self, report_issues): + utils.coerce_dict_values(self.meta_data, report_issues) + utils.coerce_dict_values(self.node_templates, report_issues) + utils.coerce_dict_values(self.group_templates, report_issues) + utils.coerce_dict_values(self.policy_templates, report_issues) if self.substitution_template is not None: - self.substitution_template.coerce_values(container, report_issues) - utils.coerce_dict_values(container, self.inputs, report_issues) - utils.coerce_dict_values(container, self.outputs, report_issues) - utils.coerce_dict_values(container, self.workflow_templates, report_issues) + self.substitution_template.coerce_values(report_issues) + utils.coerce_dict_values(self.inputs, report_issues) + utils.coerce_dict_values(self.outputs, report_issues) + utils.coerce_dict_values(self.workflow_templates, report_issues) def dump(self): context = ConsumptionContext.get_thread_local() @@ -427,7 +427,7 @@ class NodeTemplateBase(TemplateModelMixin): :ivar requirement_templates: Potential relationships with other nodes :vartype requirement_templates: [:class:`RequirementTemplate`] :ivar target_node_template_constraints: Constraints for filtering relationship targets - :vartype target_node_template_constraints: [:class:`FunctionType`] + :vartype target_node_template_constraints: [:class:`NodeTemplateConstraint`] :ivar service_template: Containing service template :vartype service_template: :class:`ServiceTemplate` :ivar group_templates: We are a member of these groups @@ -504,6 +504,10 @@ class NodeTemplateBase(TemplateModelMixin): return relationship.many_to_many(cls, 'parameter', prefix='properties', dict_key='name') @declared_attr + def attributes(cls): + return relationship.many_to_many(cls, 'parameter', prefix='attributes', dict_key='name') + + @declared_attr def interface_templates(cls): return relationship.one_to_many(cls, 'interface_template', dict_key='name') @@ -525,12 +529,12 @@ class NodeTemplateBase(TemplateModelMixin): default_instances = Column(Integer, default=1) min_instances = Column(Integer, default=0) max_instances = Column(Integer, default=None) - target_node_template_constraints = Column(modeling_types.StrictList(FunctionType)) + target_node_template_constraints = Column(PickleType) - def is_target_node_valid(self, target_node_template): + def is_target_node_template_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): + for node_template_constraint in self.target_node_template_constraints: + if not node_template_constraint.matches(self, target_node_template): return False return True @@ -544,6 +548,7 @@ class NodeTemplateBase(TemplateModelMixin): ('min_instances', self.min_instances), ('max_instances', self.max_instances), ('properties', formatting.as_raw_dict(self.properties)), + ('attributes', 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)), @@ -564,24 +569,34 @@ class NodeTemplateBase(TemplateModelMixin): runtime_properties={}, node_template=self) utils.instantiate_dict(node, node.properties, self.properties) + utils.instantiate_dict(node, node.attributes, self.attributes) utils.instantiate_dict(node, node.interfaces, self.interface_templates) utils.instantiate_dict(node, node.artifacts, self.artifact_templates) utils.instantiate_dict(node, node.capabilities, self.capability_templates) + + # Default attributes + if 'tosca_name' in node.attributes: + node.attributes['tosca_name'].value = self.name + if 'tosca_id' in node.attributes: + node.attributes['tosca_id'].value = name + return node def validate(self): utils.validate_dict_values(self.properties) + utils.validate_dict_values(self.attributes) utils.validate_dict_values(self.interface_templates) utils.validate_dict_values(self.artifact_templates) utils.validate_dict_values(self.capability_templates) utils.validate_list_values(self.requirement_templates) - def coerce_values(self, container, report_issues): - utils.coerce_dict_values(self, self.properties, report_issues) - utils.coerce_dict_values(self, self.interface_templates, report_issues) - utils.coerce_dict_values(self, self.artifact_templates, report_issues) - utils.coerce_dict_values(self, self.capability_templates, report_issues) - utils.coerce_list_values(self, self.requirement_templates, report_issues) + def coerce_values(self, report_issues): + utils.coerce_dict_values(self.properties, report_issues) + utils.coerce_dict_values(self.attributes, report_issues) + utils.coerce_dict_values(self.interface_templates, report_issues) + utils.coerce_dict_values(self.artifact_templates, report_issues) + utils.coerce_dict_values(self.capability_templates, report_issues) + utils.coerce_list_values(self.requirement_templates, report_issues) def dump(self): context = ConsumptionContext.get_thread_local() @@ -597,6 +612,7 @@ class NodeTemplateBase(TemplateModelMixin): if self.max_instances is not None else ' or more')) utils.dump_dict_values(self.properties, 'Properties') + utils.dump_dict_values(self.attributes, 'Attributes') utils.dump_interfaces(self.interface_templates) utils.dump_dict_values(self.artifact_templates, 'Artifact templates') utils.dump_dict_values(self.capability_templates, 'Capability templates') @@ -720,9 +736,9 @@ class GroupTemplateBase(TemplateModelMixin): utils.validate_dict_values(self.properties) utils.validate_dict_values(self.interface_templates) - def coerce_values(self, container, report_issues): - utils.coerce_dict_values(self, self.properties, report_issues) - utils.coerce_dict_values(self, self.interface_templates, report_issues) + def coerce_values(self, report_issues): + utils.coerce_dict_values(self.properties, report_issues) + utils.coerce_dict_values(self.interface_templates, report_issues) def dump(self): context = ConsumptionContext.get_thread_local() @@ -851,8 +867,8 @@ class PolicyTemplateBase(TemplateModelMixin): def validate(self): utils.validate_dict_values(self.properties) - def coerce_values(self, container, report_issues): - utils.coerce_dict_values(self, self.properties, report_issues) + def coerce_values(self, report_issues): + utils.coerce_dict_values(self.properties, report_issues) def dump(self): context = ConsumptionContext.get_thread_local() @@ -945,8 +961,8 @@ class SubstitutionTemplateBase(TemplateModelMixin): def validate(self): utils.validate_dict_values(self.mappings) - def coerce_values(self, container, report_issues): - utils.coerce_dict_values(self, self.mappings, report_issues) + def coerce_values(self, report_issues): + utils.coerce_dict_values(self.mappings, report_issues) def dump(self): context = ConsumptionContext.get_thread_local() @@ -1049,7 +1065,7 @@ class SubstitutionTemplateMappingBase(TemplateModelMixin): return collections.OrderedDict(( ('name', self.name))) - def coerce_values(self, container, report_issues): + def coerce_values(self, report_issues): pass def instantiate(self, container): @@ -1113,7 +1129,7 @@ class RequirementTemplateBase(TemplateModelMixin): :ivar target_capability_name: Name of capability in target node (optional) :vartype target_capability_name: basestring :ivar target_node_template_constraints: Constraints for filtering relationship targets - :vartype target_node_template_constraints: [:class:`FunctionType`] + :vartype target_node_template_constraints: [:class:`NodeTemplateConstraint`] :ivar relationship_template: Template for relationships (optional) :vartype relationship_template: :class:`RelationshipTemplate` :ivar node_template: Containing node template @@ -1183,9 +1199,7 @@ class RequirementTemplateBase(TemplateModelMixin): @declared_attr def relationship_template(cls): - return relationship.one_to_one(cls, - 'relationship_template', - back_populates=relationship.NO_BACK_POP) + return relationship.one_to_one(cls, 'relationship_template') # endregion @@ -1215,18 +1229,18 @@ class RequirementTemplateBase(TemplateModelMixin): # endregion target_capability_name = Column(Text) - target_node_template_constraints = Column(modeling_types.StrictList(FunctionType)) + target_node_template_constraints = Column(PickleType) def find_target(self, source_node_template): context = ConsumptionContext.get_thread_local() # We might already have a specific node template, so we'll just verify it if self.target_node_template is not None: - if not source_node_template.is_target_node_valid(self.target_node_template): + if not source_node_template.is_target_node_template_valid(self.target_node_template): context.validation.report('requirement "{0}" of node template "{1}" is for node ' 'template "{2}" but it does not match constraints'.format( self.name, - self.target_node_template_name, + self.target_node_template.name, source_node_template.name), level=validation.Issue.BETWEEN_TYPES) if (self.target_capability_type is not None) \ @@ -1247,7 +1261,7 @@ class RequirementTemplateBase(TemplateModelMixin): if self.target_node_type.get_descendant(target_node_template.type.name) is None: continue - if not source_node_template.is_target_node_valid(target_node_template): + if not source_node_template.is_target_node_template_valid(target_node_template): continue target_node_capability = self.find_target_capability(source_node_template, @@ -1284,9 +1298,9 @@ class RequirementTemplateBase(TemplateModelMixin): if self.relationship_template: self.relationship_template.validate() - def coerce_values(self, container, report_issues): + def coerce_values(self, report_issues): if self.relationship_template is not None: - self.relationship_template.coerce_values(container, report_issues) + self.relationship_template.coerce_values(report_issues) def dump(self): context = ConsumptionContext.get_thread_local() @@ -1417,9 +1431,9 @@ class RelationshipTemplateBase(TemplateModelMixin): utils.validate_dict_values(self.properties) utils.validate_dict_values(self.interface_templates) - def coerce_values(self, container, report_issues): - utils.coerce_dict_values(self, self.properties, report_issues) - utils.coerce_dict_values(self, self.interface_templates, report_issues) + def coerce_values(self, report_issues): + utils.coerce_dict_values(self.properties, report_issues) + utils.coerce_dict_values(self.interface_templates, report_issues) def dump(self): context = ConsumptionContext.get_thread_local() @@ -1543,8 +1557,8 @@ class CapabilityTemplateBase(TemplateModelMixin): # 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): + for node_template_constraint in requirement.target_node_template_constraints: + if not node_template_constraint.matches(source_node_template, target_node_template): return False return True @@ -1574,8 +1588,8 @@ class CapabilityTemplateBase(TemplateModelMixin): def validate(self): utils.validate_dict_values(self.properties) - def coerce_values(self, container, report_issues): - utils.coerce_dict_values(self, self.properties, report_issues) + def coerce_values(self, report_issues): + utils.coerce_dict_values(self.properties, report_issues) def dump(self): context = ConsumptionContext.get_thread_local() @@ -1728,9 +1742,9 @@ class InterfaceTemplateBase(TemplateModelMixin): utils.validate_dict_values(self.inputs) utils.validate_dict_values(self.operation_templates) - def coerce_values(self, container, report_issues): - utils.coerce_dict_values(container, self.inputs, report_issues) - utils.coerce_dict_values(container, self.operation_templates, report_issues) + def coerce_values(self, report_issues): + utils.coerce_dict_values(self.inputs, report_issues) + utils.coerce_dict_values(self.operation_templates, report_issues) def dump(self): context = ConsumptionContext.get_thread_local() @@ -1882,7 +1896,7 @@ class OperationTemplateBase(TemplateModelMixin): plugin = None implementation = None else: - # using the execution plugin + # Using the execution plugin plugin = None implementation = self.implementation @@ -1903,8 +1917,8 @@ class OperationTemplateBase(TemplateModelMixin): def validate(self): utils.validate_dict_values(self.inputs) - def coerce_values(self, container, report_issues): - utils.coerce_dict_values(container, self.inputs, report_issues) + def coerce_values(self, report_issues): + utils.coerce_dict_values(self.inputs, report_issues) def dump(self): context = ConsumptionContext.get_thread_local() @@ -2051,8 +2065,8 @@ class ArtifactTemplateBase(TemplateModelMixin): def validate(self): utils.validate_dict_values(self.properties) - def coerce_values(self, container, report_issues): - utils.coerce_dict_values(container, self.properties, report_issues) + def coerce_values(self, report_issues): + utils.coerce_dict_values(self.properties, report_issues) def dump(self): context = ConsumptionContext.get_thread_local() @@ -2128,7 +2142,7 @@ class PluginSpecificationBase(TemplateModelMixin): ('version', self.version), ('enabled', self.enabled))) - def coerce_values(self, container, report_issues): + def coerce_values(self, report_issues): pass def resolve(self, model_storage): http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/modeling/utils.py ---------------------------------------------------------------------- diff --git a/aria/modeling/utils.py b/aria/modeling/utils.py index 91d7b9c..0404fe4 100644 --- a/aria/modeling/utils.py +++ b/aria/modeling/utils.py @@ -19,9 +19,6 @@ from StringIO import StringIO from . import exceptions from ..parser.consumption import ConsumptionContext -from ..parser.exceptions import InvalidValueError -from ..parser.presentation import Value -from ..utils.collections import OrderedDict from ..utils.console import puts from ..utils.type import validate_value_type @@ -39,6 +36,21 @@ class ModelJSONEncoder(JSONEncoder): return JSONEncoder.default(self, o) +class NodeTemplateContainerHolder(object): + """ + Wrapper that allows using a :class:`aria.modeling.models.NodeTemplate` model directly as the + ``container_holder`` argument for :func:`aria.modeling.functions.evaluate`. + """ + + def __init__(self, node_template): + self.container = node_template + self.service = None + + @property + def service_template(self): + return self.container.service_template + + def create_inputs(inputs, template_inputs): """ :param inputs: key-value dict @@ -50,7 +62,7 @@ def create_inputs(inputs, template_inputs): from . import models input_models = [] for input_name, input_val in merged_inputs.iteritems(): - parameter = models.Parameter( + parameter = models.Parameter( # pylint: disable=unexpected-keyword-arg name=input_name, type_name=template_inputs[input_name].type_name, description=template_inputs[input_name].description, @@ -109,39 +121,17 @@ def _merge_and_validate_inputs(inputs, template_inputs): return merged_inputs -def coerce_value(container, value, report_issues=False): - if isinstance(value, Value): - value = value.value - - if isinstance(value, list): - return [coerce_value(container, v, report_issues) for v in value] - elif isinstance(value, dict): - return OrderedDict((k, coerce_value(container, v, report_issues)) - for k, v in value.iteritems()) - elif hasattr(value, '_evaluate'): - context = ConsumptionContext.get_thread_local() - try: - value = value._evaluate(context, container) - value = coerce_value(container, value, report_issues) - except exceptions.CannotEvaluateFunctionException: - pass - except InvalidValueError as e: - if report_issues: - context.validation.report(e.issue) - return value - - -def coerce_dict_values(container, the_dict, report_issues=False): +def coerce_dict_values(the_dict, report_issues=False): if not the_dict: return - coerce_list_values(container, the_dict.itervalues(), report_issues) + coerce_list_values(the_dict.itervalues(), report_issues) -def coerce_list_values(container, the_list, report_issues=False): +def coerce_list_values(the_list, report_issues=False): if not the_list: return for value in the_list: - value.coerce_values(container, report_issues) + value.coerce_values(report_issues) def validate_dict_values(the_dict): http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/orchestrator/execution_plugin/instantiation.py ---------------------------------------------------------------------- diff --git a/aria/orchestrator/execution_plugin/instantiation.py b/aria/orchestrator/execution_plugin/instantiation.py index ce114f0..c09434e 100644 --- a/aria/orchestrator/execution_plugin/instantiation.py +++ b/aria/orchestrator/execution_plugin/instantiation.py @@ -15,7 +15,7 @@ # TODO: this module will eventually be moved to a new "aria.instantiation" package -from ...utils.formatting import full_type_name +from ...utils.type import full_type_name from ...utils.collections import OrderedDict from ...parser import validation from ...parser.consumption import ConsumptionContext http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/parser/__init__.py ---------------------------------------------------------------------- diff --git a/aria/parser/__init__.py b/aria/parser/__init__.py index 9ab8785..64df88a 100644 --- a/aria/parser/__init__.py +++ b/aria/parser/__init__.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from .specification import dsl_specification, iter_specifications +from .specification import implements_specification, iter_specifications MODULES = ( @@ -26,5 +26,5 @@ MODULES = ( __all__ = ( 'MODULES', - 'dsl_specification', + 'implements_specification', 'iter_specifications') http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/parser/consumption/__init__.py ---------------------------------------------------------------------- diff --git a/aria/parser/consumption/__init__.py b/aria/parser/consumption/__init__.py index 8f6d2b6..76e73be 100644 --- a/aria/parser/consumption/__init__.py +++ b/aria/parser/consumption/__init__.py @@ -28,9 +28,11 @@ from .modeling import ( Types, ServiceInstance, FindHosts, + ValidateServiceInstance, ConfigureOperations, SatisfyRequirements, - ValidateCapabilities + ValidateCapabilities, + CoerceServiceInstanceValues ) from .inputs import Inputs @@ -45,7 +47,10 @@ __all__ = ( 'ServiceTemplate', 'Types', 'ServiceInstance', - 'Inputs', + 'FindHosts', + 'ValidateServiceInstance', + 'ConfigureOperations', 'SatisfyRequirements', - 'ValidateCapabilities' + 'ValidateCapabilities', + 'CoerceServiceInstanceValues' ) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/parser/consumption/modeling.py ---------------------------------------------------------------------- diff --git a/aria/parser/consumption/modeling.py b/aria/parser/consumption/modeling.py index 771fd7f..44027b9 100644 --- a/aria/parser/consumption/modeling.py +++ b/aria/parser/consumption/modeling.py @@ -42,7 +42,7 @@ class CoerceServiceTemplateValues(Consumer): """ def consume(self): - self.context.modeling.template.coerce_values(None, True) + self.context.modeling.template.coerce_values(True) class ValidateServiceTemplate(Consumer): @@ -116,7 +116,7 @@ class CoerceServiceInstanceValues(Consumer): """ def consume(self): - self.context.modeling.instance.coerce_values(None, True) + self.context.modeling.instance.coerce_values(True) class ValidateServiceInstance(Consumer): http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/parser/presentation/fields.py ---------------------------------------------------------------------- diff --git a/aria/parser/presentation/fields.py b/aria/parser/presentation/fields.py index da3cb05..7f85723 100644 --- a/aria/parser/presentation/fields.py +++ b/aria/parser/presentation/fields.py @@ -21,7 +21,8 @@ from ...exceptions import AriaException from ...utils.collections import FrozenDict, FrozenList, deepcopy_with_locators, merge, OrderedDict from ...utils.caching import cachedmethod from ...utils.console import puts -from ...utils.formatting import as_raw, safe_repr, full_type_name +from ...utils.formatting import as_raw, safe_repr +from ...utils.type import full_type_name from ...utils.exceptions import print_exception from ..exceptions import InvalidValueError http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/parser/presentation/presentation.py ---------------------------------------------------------------------- diff --git a/aria/parser/presentation/presentation.py b/aria/parser/presentation/presentation.py index 0f098b5..644d880 100644 --- a/aria/parser/presentation/presentation.py +++ b/aria/parser/presentation/presentation.py @@ -15,7 +15,8 @@ from ...utils.caching import HasCachedMethods from ...utils.collections import deepcopy_with_locators -from ...utils.formatting import full_type_name, safe_repr +from ...utils.formatting import safe_repr +from ...utils.type import full_type_name from ...utils.console import puts from ..validation import Issue from .null import none_to_null http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/parser/presentation/utils.py ---------------------------------------------------------------------- diff --git a/aria/parser/presentation/utils.py b/aria/parser/presentation/utils.py index 74adbd1..fbe971b 100644 --- a/aria/parser/presentation/utils.py +++ b/aria/parser/presentation/utils.py @@ -15,7 +15,8 @@ from types import FunctionType -from ...utils.formatting import full_type_name, safe_repr +from ...utils.formatting import safe_repr +from ...utils.type import full_type_name from ..validation import Issue from .null import NULL http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/parser/specification.py ---------------------------------------------------------------------- diff --git a/aria/parser/specification.py b/aria/parser/specification.py index 1df11ce..714bed1 100644 --- a/aria/parser/specification.py +++ b/aria/parser/specification.py @@ -17,40 +17,7 @@ import re from ..extension import parser from ..utils.collections import OrderedDict -from ..utils.formatting import full_type_name - -_DSL_SPECIFICATIONS = {} - - -def dsl_specification(section, spec): - """ - Decorator for TOSCA specification. - - Used for documentation and standards compliance. - """ - def decorator(obj): - specification = _DSL_SPECIFICATIONS.get(spec) - - if specification is None: - specification = {} - _DSL_SPECIFICATIONS[spec] = specification - - if section in specification: - raise Exception('you cannot specify the same @dsl_specification twice, consider' - ' adding \'-1\', \'-2\', etc.: %s, %s' % (spec, section)) - - specification[section] = OrderedDict(( - ('code', full_type_name(obj)), - ('doc', obj.__doc__))) - - try: - setattr(obj, '_dsl_specifications', {section: section, spec: spec}) - except BaseException: - pass - - return obj - - return decorator +from ..utils.specification import (DSL_SPECIFICATIONS, implements_specification) # pylint: disable=unused-import def iter_specifications(): @@ -63,12 +30,10 @@ def iter_specifications(): details['code'] = sections[k]['code'] yield k, _fix_details(sections[k], spec) - for spec, sections in _DSL_SPECIFICATIONS.iteritems(): + for spec, sections in DSL_SPECIFICATIONS.iteritems(): yield spec, iter_sections(spec, sections) -# Utils - def _section_key(value): try: parts = value.split('-', 1) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/parser/validation/issue.py ---------------------------------------------------------------------- diff --git a/aria/parser/validation/issue.py b/aria/parser/validation/issue.py index f001efc..db8065d 100644 --- a/aria/parser/validation/issue.py +++ b/aria/parser/validation/issue.py @@ -16,7 +16,7 @@ from __future__ import absolute_import # so we can import standard 'collections' from ...utils.collections import OrderedDict -from ...utils.formatting import full_type_name +from ...utils.type import full_type_name class Issue(object): http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/utils/caching.py ---------------------------------------------------------------------- diff --git a/aria/utils/caching.py b/aria/utils/caching.py index 613ade6..c9e475a 100644 --- a/aria/utils/caching.py +++ b/aria/utils/caching.py @@ -65,22 +65,24 @@ class cachedmethod(object): # pylint: disable=invalid-name return self.func(*args, **kwargs) instance = args[0] - cache = instance.get_method_cache() + if not hasattr(instance, '_method_cache'): + instance._method_cache = {} + method_cache = instance._method_cache key = (self.func, args[1:], frozenset(kwargs.items())) try: with self.lock: - return_value = cache[key] + return_value = method_cache[key] self.hits += 1 except KeyError: return_value = self.func(*args, **kwargs) with self.lock: - cache[key] = return_value + method_cache[key] = return_value self.misses += 1 # Another thread may override our cache entry here, so we need to read # it again to make sure all threads use the same return value - return_value = cache.get(key, return_value) + return_value = method_cache.get(key, return_value) return return_value @@ -93,9 +95,6 @@ class HasCachedMethods(object): def __init__(self, method_cache=None): self._method_cache = method_cache or {} - def get_method_cache(self): - return self._method_cache - @property def _method_cache_info(self): """ http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/utils/formatting.py ---------------------------------------------------------------------- diff --git a/aria/utils/formatting.py b/aria/utils/formatting.py index b5e141d..f96a4ce 100644 --- a/aria/utils/formatting.py +++ b/aria/utils/formatting.py @@ -71,18 +71,6 @@ class YamlAsRawDumper(yaml.dumper.RoundTripDumper): # pylint: disable=too-many- return super(YamlAsRawDumper, self).represent_data(data) -def full_type_name(value): - """ - The full class name of a type or object. - """ - - if not isinstance(value, type): - value = value.__class__ - module = str(value.__module__) - name = str(value.__name__) - return name if module == '__builtin__' else '%s.%s' % (module, name) - - def decode_list(data): decoded_list = [] for item in data: @@ -163,8 +151,8 @@ def as_raw(value): value = value() elif isinstance(value, list): value = list(value) - for i, _ in enumerate(value): - value[i] = as_raw(value[i]) + for i, v in enumerate(value): + value[i] = as_raw(v) elif isinstance(value, dict): value = dict(value) for k, v in value.iteritems(): http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/utils/specification.py ---------------------------------------------------------------------- diff --git a/aria/utils/specification.py b/aria/utils/specification.py new file mode 100644 index 0000000..e74c103 --- /dev/null +++ b/aria/utils/specification.py @@ -0,0 +1,53 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .collections import OrderedDict + + +DSL_SPECIFICATIONS = {} + + +def implements_specification(section, spec): + """ + Decorator for specification implementations. + + Used for documentation and standards compliance. + """ + + from .type import full_type_name + + def decorator(obj): + specification = DSL_SPECIFICATIONS.get(spec) + + if specification is None: + specification = {} + DSL_SPECIFICATIONS[spec] = specification + + if section in specification: + raise Exception('you cannot specify the same @implements_specification twice, consider' + ' adding \'-1\', \'-2\', etc.: {0}, {1}'.format(spec, section)) + + specification[section] = OrderedDict(( + ('code', full_type_name(obj)), + ('doc', obj.__doc__))) + + try: + setattr(obj, '_dsl_specifications', {section: section, spec: spec}) + except BaseException: + pass + + return obj + + return decorator http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/aria/utils/type.py ---------------------------------------------------------------------- diff --git a/aria/utils/type.py b/aria/utils/type.py index dad5427..f08159a 100644 --- a/aria/utils/type.py +++ b/aria/utils/type.py @@ -13,49 +13,142 @@ # See the License for the specific language governing permissions and # limitations under the License. +import datetime + +from .specification import implements_specification + + +BASE_TYPES_TO_CANONICAL_NAMES = { + # TOSCA aliases: + None.__class__: 'null', + basestring: 'string', + int: 'integer', + float: 'float', + bool: 'boolean', + list: 'list', + tuple: 'list', + dict: 'map', + datetime.datetime: 'timestamp' +} + +NAMES_TO_CANONICAL_TYPES = { + # Python: + 'none': None.__class__, + 'basestring': unicode, + 'str': unicode, + 'unicode': unicode, + 'int': int, + 'float': float, # also a TOSCA alias + 'bool': bool, + 'list': list, # also a TOSCA alias + 'tuple': list, + 'dict': dict, + 'datetime': datetime.datetime, + + # YAML 1.2: + 'tag:yaml.org,2002:null': None.__class__, + 'tag:yaml.org,2002:str': unicode, + 'tag:yaml.org,2002:integer': int, + 'tag:yaml.org,2002:float': float, + 'tag:yaml.org,2002:bool': bool, + + # TOSCA aliases: + 'null': None.__class__, + 'string': unicode, + 'integer': int, + 'boolean': bool, + + # TOSCA custom types: + 'map': dict, + 'timestamp': datetime.datetime +} + + +def full_type_name(value): + """ + The full class name of a type or instance. + """ + + if not isinstance(value, type): + value = value.__class__ + module = str(value.__module__) + name = str(value.__name__) + return name if module == '__builtin__' else '{0}.{1}'.format(module, name) + + +@implements_specification('3.2.1-1', 'tosca-simple-1.0') +def canonical_type_name(value): + """ + Returns the canonical TOSCA type name of a primitive value, or None if unknown. + + For a list of TOSCA type names, see the `TOSCA Simple Profile v1.0 + cos01 specification <http://docs.oasis-open.org/tosca/TOSCA-Simple-Profile-YAML/v1.0/cos01 + /TOSCA-Simple-Profile-YAML-v1.0-cos01.html#_Toc373867862>`__ + """ + + for the_type, name in BASE_TYPES_TO_CANONICAL_NAMES.iteritems(): + if isinstance(value, the_type): + return name + return None + + +@implements_specification('3.2.1-2', 'tosca-simple-1.0') +def canonical_type(type_name): + """ + Return the canonical type for any Python, YAML, or TOSCA type name or alias, or None if + unsupported. + + :param type_name: Type name (case insensitive) + """ + + return NAMES_TO_CANONICAL_TYPES.get(type_name.lower()) + def validate_value_type(value, type_name): """ - Validate a value is of a specific type. + Validate that a value is of a specific type. Supports Python, YAML, and TOSCA type names and + aliases. + A ValueError will be raised on type mismatch. - Supports both python and yaml type names. - """ - - #TODO add timestamp type? - name_to_type = { - 'list': list, - 'dict': dict, - 'tuple': tuple, - 'str': str, - 'unicode': str, - 'string': str, - 'int': int, - 'integer': int, - 'bool': bool, - 'boolean': bool, - 'float': float - } - - type_ = name_to_type.get(type_name.lower()) - if type_ is None: - raise RuntimeError('No supported type_name was provided') - - if not isinstance(value, type_): + + :param type_name: Type name (case insensitive) + """ + + the_type = canonical_type(type_name) + if the_type is None: + raise RuntimeError('Unsupported type name: {0}'.format(type_name)) + + # The following Python types do not inherit from the canonical type, but are considered valid + if (the_type is unicode) and isinstance(value, str): + return + if (the_type is list) and isinstance(value, tuple): + return + + if not isinstance(value, the_type): raise ValueError('Value {0} is not of type {1}'.format(value, type_name)) -def convert_value_to_type(str_value, type_name): +def convert_value_to_type(str_value, python_type_name): + """ + Converts a value to a specific Python primitive type. + + A ValueError will be raised for unsupported types or conversion failure. + + :param python_type_name: Python primitive type name (case insensitive) + """ + + python_type_name = python_type_name.lower() try: - if type_name.lower() in ['str', 'unicode']: + if python_type_name in ('str', 'unicode'): return str_value.decode('utf-8') - elif type_name.lower() == 'int': + elif python_type_name == 'int': return int(str_value) - elif type_name.lower() == 'bool': + elif python_type_name == 'bool': return bool(str_value) - elif type_name.lower() == 'float': + elif python_type_name == 'float': return float(str_value) else: - raise ValueError('No supported type_name was provided') + raise ValueError('Unsupported Python type name: {0}'.format(python_type_name)) except ValueError: - raise ValueError('Trying to convert {0} to {1} failed'.format(str_value, - type_name)) + raise ValueError('Failed to to convert {0} to {1}'.format(str_value, + python_type_name)) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/examples/tosca-simple-1.0/use-cases/block-storage-1/block-storage-1.yaml ---------------------------------------------------------------------- diff --git a/examples/tosca-simple-1.0/use-cases/block-storage-1/block-storage-1.yaml b/examples/tosca-simple-1.0/use-cases/block-storage-1/block-storage-1.yaml index ff6dc92..b912fb2 100644 --- a/examples/tosca-simple-1.0/use-cases/block-storage-1/block-storage-1.yaml +++ b/examples/tosca-simple-1.0/use-cases/block-storage-1/block-storage-1.yaml @@ -65,4 +65,4 @@ topology_template: value: { get_attribute: [ my_server, private_address ] } volume_id: description: The volume id of the block storage instance. - value: { get_attribute: [ my_storage, volume_id ] } + value: { get_property: [ my_storage, volume_id ] } # ARIA NOTE: wrong in spec http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/examples/tosca-simple-1.0/use-cases/block-storage-2/block-storage-2.yaml ---------------------------------------------------------------------- diff --git a/examples/tosca-simple-1.0/use-cases/block-storage-2/block-storage-2.yaml b/examples/tosca-simple-1.0/use-cases/block-storage-2/block-storage-2.yaml index 09c30a7..ac475cf 100644 --- a/examples/tosca-simple-1.0/use-cases/block-storage-2/block-storage-2.yaml +++ b/examples/tosca-simple-1.0/use-cases/block-storage-2/block-storage-2.yaml @@ -72,4 +72,4 @@ topology_template: volume_id: description: The volume id of the block storage instance. - value: { get_attribute: [ my_storage, volume_id ] } + value: { get_property: [ my_storage, volume_id ] } # ARIA NOTE: wrong in spec http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/examples/tosca-simple-1.0/use-cases/block-storage-3/block-storage-3.yaml ---------------------------------------------------------------------- diff --git a/examples/tosca-simple-1.0/use-cases/block-storage-3/block-storage-3.yaml b/examples/tosca-simple-1.0/use-cases/block-storage-3/block-storage-3.yaml index 3018fe9..c3f183e 100644 --- a/examples/tosca-simple-1.0/use-cases/block-storage-3/block-storage-3.yaml +++ b/examples/tosca-simple-1.0/use-cases/block-storage-3/block-storage-3.yaml @@ -65,4 +65,4 @@ topology_template: value: { get_attribute: [ my_server, private_address ] } volume_id: description: The volume id of the block storage instance. - value: { get_attribute: [ my_storage, volume_id ] } + value: { get_property: [ my_storage, volume_id ] } # ARIA NOTE: wrong in spec http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/examples/tosca-simple-1.0/use-cases/block-storage-4/block-storage-4.yaml ---------------------------------------------------------------------- diff --git a/examples/tosca-simple-1.0/use-cases/block-storage-4/block-storage-4.yaml b/examples/tosca-simple-1.0/use-cases/block-storage-4/block-storage-4.yaml index 0693ddd..e2bdb9f 100644 --- a/examples/tosca-simple-1.0/use-cases/block-storage-4/block-storage-4.yaml +++ b/examples/tosca-simple-1.0/use-cases/block-storage-4/block-storage-4.yaml @@ -93,4 +93,4 @@ topology_template: value: { get_attribute: [ my_web_app_tier_2, private_address ] } volume_id: description: The volume id of the block storage instance. - value: { get_attribute: [ my_storage, volume_id ] } + value: { get_property: [ my_storage, volume_id ] } # ARIA NOTE: wrong in spec http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/examples/tosca-simple-1.0/use-cases/block-storage-5/block-storage-5.yaml ---------------------------------------------------------------------- diff --git a/examples/tosca-simple-1.0/use-cases/block-storage-5/block-storage-5.yaml b/examples/tosca-simple-1.0/use-cases/block-storage-5/block-storage-5.yaml index 5f5cf71..a0c2229 100644 --- a/examples/tosca-simple-1.0/use-cases/block-storage-5/block-storage-5.yaml +++ b/examples/tosca-simple-1.0/use-cases/block-storage-5/block-storage-5.yaml @@ -100,10 +100,10 @@ topology_template: outputs: private_ip_1: description: The private IP address of the application's first tier. - value: { get_attribute: [my_web_app_tier_1, private_address] } + value: { get_attribute: [ my_web_app_tier_1, private_address ] } private_ip_2: description: The private IP address of the application's second tier. - value: { get_attribute: [my_web_app_tier_2, private_address] } + value: { get_attribute: [ my_web_app_tier_2, private_address ] } volume_id: description: The volume id of the block storage instance. - value: { get_attribute: [my_storage, volume_id] } + value: { get_property: [ my_storage, volume_id ] } # ARIA NOTE: wrong in spec http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/examples/tosca-simple-1.0/use-cases/block-storage-6/block-storage-6.yaml ---------------------------------------------------------------------- diff --git a/examples/tosca-simple-1.0/use-cases/block-storage-6/block-storage-6.yaml b/examples/tosca-simple-1.0/use-cases/block-storage-6/block-storage-6.yaml index 808245b..534884a 100644 --- a/examples/tosca-simple-1.0/use-cases/block-storage-6/block-storage-6.yaml +++ b/examples/tosca-simple-1.0/use-cases/block-storage-6/block-storage-6.yaml @@ -96,7 +96,7 @@ topology_template: value: { get_attribute: [ my_server2, private_address ] } volume_id_1: description: The volume id of the first block storage instance. - value: { get_attribute: [my_storage, volume_id] } + value: { get_property: [ my_storage, volume_id ] } # ARIA NOTE: wrong in spec volume_id_2: description: The volume id of the second block storage instance. - value: { get_attribute: [ my_storage2, volume_id ] } + value: { get_property: [ my_storage2, volume_id ] } # ARIA NOTE: wrong in spec http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/examples/tosca-simple-1.0/use-cases/multi-tier-1/multi-tier-1.yaml ---------------------------------------------------------------------- diff --git a/examples/tosca-simple-1.0/use-cases/multi-tier-1/multi-tier-1.yaml b/examples/tosca-simple-1.0/use-cases/multi-tier-1/multi-tier-1.yaml index 3485e49..50401ec 100644 --- a/examples/tosca-simple-1.0/use-cases/multi-tier-1/multi-tier-1.yaml +++ b/examples/tosca-simple-1.0/use-cases/multi-tier-1/multi-tier-1.yaml @@ -59,7 +59,7 @@ topology_template: implementation: scripts/nodejs/configure.sh inputs: github_url: { get_property: [ SELF, github_url ] } - mongodb_ip: { get_attribute: [mongo_server, private_address] } + mongodb_ip: { get_attribute: [ mongo_server, private_address ] } start: scripts/nodejs/start.sh nodejs: @@ -90,7 +90,7 @@ topology_template: configure: implementation: scripts/mongodb/config.sh inputs: - mongodb_ip: { get_attribute: [mongo_server, ip_address] } + mongodb_ip: { get_attribute: [ mongo_server, private_address ] } # ARIA NOTE: wrong in spec start: scripts/mongodb/start.sh elasticsearch: @@ -115,7 +115,7 @@ topology_template: pre_configure_source: implementation: python/logstash/configure_elasticsearch.py inputs: - elasticsearch_ip: { get_attribute: [elasticsearch_server, ip_address] } + elasticsearch_ip: { get_attribute: [ elasticsearch_server, private_address ] } # ARIA NOTE: wrong in spec interfaces: Standard: # ARIA NOTE: wrong in spec create: scripts/lostash/create.sh @@ -133,8 +133,8 @@ topology_template: configure: implementation: scripts/kibana/config.sh inputs: - elasticsearch_ip: { get_attribute: [ elasticsearch_server, ip_address ] } - kibana_ip: { get_attribute: [ kibana_server, ip_address ] } + elasticsearch_ip: { get_attribute: [ elasticsearch_server, private_address ] } # ARIA NOTE: wrong in spec + kibana_ip: { get_attribute: [ kibana_server, private_address ] } # ARIA NOTE: wrong in spec start: scripts/kibana/start.sh app_collectd: @@ -155,7 +155,7 @@ topology_template: configure: implementation: python/collectd/config.py inputs: - logstash_ip: { get_attribute: [ logstash_server, ip_address ] } + logstash_ip: { get_attribute: [ logstash_server, private_address ] } # ARIA NOTE: wrong in spec start: scripts/collectd/start.sh app_rsyslog: @@ -176,7 +176,7 @@ topology_template: configure: implementation: scripts/rsyslog/config.sh inputs: - logstash_ip: { get_attribute: [ logstash_server, ip_address ] } + logstash_ip: { get_attribute: [ logstash_server, private_address ] } # ARIA NOTE: wrong in spec start: scripts/rsyslog/start.sh app_server: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/extensions/aria_extension_tosca/simple_v1_0/__init__.py ---------------------------------------------------------------------- diff --git a/extensions/aria_extension_tosca/simple_v1_0/__init__.py b/extensions/aria_extension_tosca/simple_v1_0/__init__.py index 29df362..7dcc60a 100644 --- a/extensions/aria_extension_tosca/simple_v1_0/__init__.py +++ b/extensions/aria_extension_tosca/simple_v1_0/__init__.py @@ -30,8 +30,6 @@ from .types import (ArtifactType, DataType, CapabilityType, InterfaceType, Relat NodeType, GroupType, PolicyType) from .data_types import (Timestamp, Version, Range, List, Map, ScalarSize, ScalarTime, ScalarFrequency) -from .functions import (Concat, Token, GetInput, GetProperty, GetAttribute, GetOperationOutput, - GetNodesOfType, GetArtifact) MODULES = ( 'modeling', @@ -89,12 +87,4 @@ __all__ = ( 'Map', 'ScalarSize', 'ScalarTime', - 'ScalarFrequency', - 'Concat', - 'Token', - 'GetInput', - 'GetProperty', - 'GetAttribute', - 'GetOperationOutput', - 'GetNodesOfType', - 'GetArtifact') + 'ScalarFrequency') http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/extensions/aria_extension_tosca/simple_v1_0/assignments.py ---------------------------------------------------------------------- diff --git a/extensions/aria_extension_tosca/simple_v1_0/assignments.py b/extensions/aria_extension_tosca/simple_v1_0/assignments.py index 9a2179a..d929ce0 100644 --- a/extensions/aria_extension_tosca/simple_v1_0/assignments.py +++ b/extensions/aria_extension_tosca/simple_v1_0/assignments.py @@ -15,7 +15,7 @@ from aria.utils.collections import FrozenDict from aria.utils.caching import cachedmethod -from aria.parser import dsl_specification +from aria.parser import implements_specification from aria.parser.presentation import (AsIsPresentation, has_fields, allow_unknown_fields, short_form_field, primitive_field, object_field, object_dict_field, object_dict_unknown_fields, @@ -32,7 +32,7 @@ from .presentation.field_validators import (node_template_or_type_validator, from .presentation.types import (convert_shorthand_to_full_type_name, get_type_by_full_or_shorthand_name) -@dsl_specification('3.5.9', 'tosca-simple-1.0') +@implements_specification('3.5.9', 'tosca-simple-1.0') class PropertyAssignment(AsIsPresentation): """ This section defines the grammar for assigning values to named properties within TOSCA Node and @@ -45,7 +45,7 @@ class PropertyAssignment(AsIsPresentation): @short_form_field('implementation') @has_fields -@dsl_specification('3.5.13-2', 'tosca-simple-1.0') +@implements_specification('3.5.13-2', 'tosca-simple-1.0') class OperationAssignment(ExtensiblePresentation): """ An operation definition defines a named function or procedure that can be bound to an @@ -105,7 +105,7 @@ class OperationAssignment(ExtensiblePresentation): @allow_unknown_fields @has_fields -@dsl_specification('3.5.14-2', 'tosca-simple-1.0') +@implements_specification('3.5.14-2', 'tosca-simple-1.0') class InterfaceAssignment(ExtensiblePresentation): """ An interface definition defines a named interface that can be associated with a Node or @@ -200,7 +200,7 @@ class RelationshipAssignment(ExtensiblePresentation): @short_form_field('node') @has_fields -@dsl_specification('3.7.2', 'tosca-simple-1.0') +@implements_specification('3.7.2', 'tosca-simple-1.0') class RequirementAssignment(ExtensiblePresentation): """ A Requirement assignment allows template authors to provide either concrete names of TOSCA @@ -297,7 +297,7 @@ class RequirementAssignment(ExtensiblePresentation): return None, None -@dsl_specification('3.5.11', 'tosca-simple-1.0') +@implements_specification('3.5.11', 'tosca-simple-1.0') class AttributeAssignment(AsIsPresentation): """ This section defines the grammar for assigning values to named attributes within TOSCA Node and @@ -309,7 +309,7 @@ class AttributeAssignment(AsIsPresentation): """ @has_fields -@dsl_specification('3.7.1', 'tosca-simple-1.0') +@implements_specification('3.7.1', 'tosca-simple-1.0') class CapabilityAssignment(ExtensiblePresentation): """ A capability assignment allows node template authors to assign values to properties and @@ -351,7 +351,7 @@ class CapabilityAssignment(ExtensiblePresentation): if capability_definition is not None else None @has_fields -@dsl_specification('3.5.6', 'tosca-simple-1.0') +@implements_specification('3.5.6', 'tosca-simple-1.0') class ArtifactAssignment(ExtensiblePresentation): """ An artifact definition defines a named, typed file that can be associated with Node Type or Node http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/extensions/aria_extension_tosca/simple_v1_0/data_types.py ---------------------------------------------------------------------- diff --git a/extensions/aria_extension_tosca/simple_v1_0/data_types.py b/extensions/aria_extension_tosca/simple_v1_0/data_types.py index a06834c..c385f78 100644 --- a/extensions/aria_extension_tosca/simple_v1_0/data_types.py +++ b/extensions/aria_extension_tosca/simple_v1_0/data_types.py @@ -20,7 +20,7 @@ try: except ImportError: from total_ordering import total_ordering -from aria.parser import dsl_specification +from aria.parser import implements_specification from aria.utils.collections import StrictDict, OrderedDict from aria.utils.formatting import safe_repr @@ -51,7 +51,7 @@ class Timezone(tzinfo): UTC = Timezone() @total_ordering -@dsl_specification('timestamp', 'yaml-1.1') +@implements_specification('timestamp', 'yaml-1.1') class Timestamp(object): ''' TOSCA timestamps follow the YAML specification, which in turn is a variant of ISO8601. @@ -146,7 +146,7 @@ class Timestamp(object): return '{0:g}'.format(the_datetime.microsecond / 1000000.0).lstrip('0') @total_ordering -@dsl_specification('3.2.2', 'tosca-simple-1.0') +@implements_specification('3.2.2', 'tosca-simple-1.0') class Version(object): """ TOSCA supports the concept of "reuse" of type definitions, as well as template definitions which @@ -229,7 +229,7 @@ class Version(object): return True return False -@dsl_specification('3.2.3', 'tosca-simple-1.0') +@implements_specification('3.2.3', 'tosca-simple-1.0') class Range(object): """ The range type can be used to define numeric ranges with a lower and upper boundary. For @@ -276,7 +276,7 @@ class Range(object): def as_raw(self): return list(self.value) -@dsl_specification('3.2.4', 'tosca-simple-1.0') +@implements_specification('3.2.4', 'tosca-simple-1.0') class List(list): """ The list type allows for specifying multiple values for a parameter of property. For example, if @@ -309,7 +309,7 @@ class List(list): def as_raw(self): return list(self) -@dsl_specification('3.2.5', 'tosca-simple-1.0') +@implements_specification('3.2.5', 'tosca-simple-1.0') class Map(StrictDict): """ The map type allows for specifying multiple values for a parameter of property as a map. In @@ -349,7 +349,7 @@ class Map(StrictDict): return OrderedDict(self) @total_ordering -@dsl_specification('3.2.6', 'tosca-simple-1.0') +@implements_specification('3.2.6', 'tosca-simple-1.0') class Scalar(object): """ The scalar-unit type can be used to define scalar values along with a unit from the list of @@ -416,7 +416,7 @@ class Scalar(object): value = self.TYPE(scalar) # pylint: disable=no-member return self.value < value -@dsl_specification('3.2.6.4', 'tosca-simple-1.0') +@implements_specification('3.2.6.4', 'tosca-simple-1.0') class ScalarSize(Scalar): """ Integer scalar for counting bytes. @@ -444,7 +444,7 @@ class ScalarSize(Scalar): TYPE = int UNIT = 'bytes' -@dsl_specification('3.2.6.5', 'tosca-simple-1.0') +@implements_specification('3.2.6.5', 'tosca-simple-1.0') class ScalarTime(Scalar): """ Floating point scalar for counting seconds. @@ -469,7 +469,7 @@ class ScalarTime(Scalar): TYPE = float UNIT = 'seconds' -@dsl_specification('3.2.6.6', 'tosca-simple-1.0') +@implements_specification('3.2.6.6', 'tosca-simple-1.0') class ScalarFrequency(Scalar): """ Floating point scalar for counting cycles per second (Hz).