You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@ariatosca.apache.org by ra...@apache.org on 2017/05/22 12:17:43 UTC

[16/19] incubator-ariatosca git commit: ARIA-148 Enhance CLI show commands

ARIA-148 Enhance CLI show commands

* Allow "--full" flag to provide a complete dump
* Allow "--json" and "--yaml" flags for dump in those formats
* Support for node graph and type hierarchies
* Some fixes for YAML dump for our custom types
* Also closes ARIA-186: "aria services show" command


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

Branch: refs/heads/ARIA-208-Missing-back-refrences-for-models
Commit: fdd57c47acfe63dc25c6f20bcb3785225b3f774d
Parents: 60ea3eb
Author: Tal Liron <ta...@gmail.com>
Authored: Thu Apr 20 17:54:47 2017 -0500
Committer: Tal Liron <ta...@gmail.com>
Committed: Thu May 11 12:42:40 2017 -0500

----------------------------------------------------------------------
 aria/cli/commands/service_templates.py |  72 ++++++----
 aria/cli/commands/services.py          |  51 ++++++-
 aria/cli/core/aria.py                  | 203 +++++++++++++++++++---------
 aria/cli/helptexts.py                  |  12 +-
 aria/cli/table.py                      |   3 +-
 aria/modeling/service_instance.py      |   4 +-
 aria/modeling/service_template.py      |   2 +-
 aria/modeling/types.py                 |   5 +
 aria/utils/collections.py              |   2 +-
 9 files changed, 260 insertions(+), 94 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/fdd57c47/aria/cli/commands/service_templates.py
----------------------------------------------------------------------
diff --git a/aria/cli/commands/service_templates.py b/aria/cli/commands/service_templates.py
index e459871..0a24907 100644
--- a/aria/cli/commands/service_templates.py
+++ b/aria/cli/commands/service_templates.py
@@ -23,11 +23,13 @@ from .. import utils
 from ..core import aria
 from ...core import Core
 from ...storage import exceptions as storage_exceptions
+from ...parser import consumption
+from ...utils import (formatting, collections, console)
 
 
 DESCRIPTION_FIELD_LENGTH_LIMIT = 20
 SERVICE_TEMPLATE_COLUMNS = \
-    ['id', 'name', 'description', 'main_file_name', 'created_at', 'updated_at']
+    ('id', 'name', 'description', 'main_file_name', 'created_at', 'updated_at')
 
 
 @aria.group(name='service-templates')
@@ -43,32 +45,52 @@ def service_templates():
 @aria.argument('service-template-name')
 @aria.options.verbose()
 @aria.pass_model_storage
+@aria.options.service_template_mode_full
+@aria.options.mode_types
+@aria.options.format_json
+@aria.options.format_yaml
 @aria.pass_logger
-def show(service_template_name, model_storage, logger):
-    """Show information for a specific service templates
+def show(service_template_name, model_storage, mode_full, mode_types, format_json, format_yaml,
+         logger):
+    """Show information for a specific service template
 
     `SERVICE_TEMPLATE_NAME` is the name of the service template to show information on.
     """
-    logger.info('Showing service template {0}...'.format(service_template_name))
     service_template = model_storage.service_template.get_by_name(service_template_name)
-    service_template_dict = service_template.to_dict()
-    service_template_dict['#services'] = len(service_template.services)
 
-    column_formatters = \
-        dict(description=table.trim_formatter_generator(DESCRIPTION_FIELD_LENGTH_LIMIT))
-    columns = SERVICE_TEMPLATE_COLUMNS + ['#services']
-    table.print_data(columns, service_template_dict, 'Service-template:',
-                     column_formatters=column_formatters, col_max_width=50)
-
-    if service_template_dict['description'] is not None:
-        logger.info('Description:')
-        logger.info('{0}{1}'.format(service_template_dict['description'].encode('UTF-8') or '',
-                                    os.linesep))
-
-    if service_template.services:
-        logger.info('Existing services:')
-        for service in service_template.services:
-            logger.info('\t{0}'.format(service.name))
+    if format_json or format_yaml:
+        mode_full = True
+
+    if mode_full:
+        consumption.ConsumptionContext()
+        if format_json:
+            console.puts(formatting.json_dumps(collections.prune(service_template.as_raw)))
+        elif format_yaml:
+            console.puts(formatting.yaml_dumps(collections.prune(service_template.as_raw)))
+        else:
+            service_template.dump()
+    elif mode_types:
+        consumption.ConsumptionContext()
+        service_template.dump_types()
+    else:
+        logger.info('Showing service template {0}...'.format(service_template_name))
+        service_template_dict = service_template.to_dict()
+        service_template_dict['#services'] = len(service_template.services)
+        columns = SERVICE_TEMPLATE_COLUMNS + ('#services',)
+        column_formatters = \
+            dict(description=table.trim_formatter_generator(DESCRIPTION_FIELD_LENGTH_LIMIT))
+        table.print_data(columns, service_template_dict, 'Service-template:',
+                         column_formatters=column_formatters, col_max_width=50)
+
+        if service_template_dict['description'] is not None:
+            logger.info('Description:')
+            logger.info('{0}{1}'.format(service_template_dict['description'].encode('UTF-8') or '',
+                                        os.linesep))
+
+        if service_template.services:
+            logger.info('Existing services:')
+            for service in service_template.services:
+                logger.info('\t{0}'.format(service.name))
 
 
 @service_templates.command(name='list',
@@ -135,6 +157,7 @@ def store(service_template_path, service_template_name, service_template_filenam
 @aria.pass_logger
 def delete(service_template_name, model_storage, resource_storage, plugin_manager, logger):
     """Delete a service template
+
     `SERVICE_TEMPLATE_NAME` is the name of the service template to delete.
     """
     logger.info('Deleting service template {0}...'.format(service_template_name))
@@ -172,7 +195,7 @@ def validate(service_template, service_template_filename,
              model_storage, resource_storage, plugin_manager, logger):
     """Validate a service template
 
-    `SERVICE_TEMPLATE` is the path or url of the service template or archive to validate.
+    `SERVICE_TEMPLATE` is the path or URL of the service template or archive to validate.
     """
     logger.info('Validating service template: {0}'.format(service_template))
     service_template_path = service_template_utils.get(service_template, service_template_filename)
@@ -188,11 +211,10 @@ def validate(service_template, service_template_filename,
 @aria.options.verbose()
 @aria.pass_logger
 def create_archive(service_template_path, destination, logger):
-    """Create a csar archive on the local file system
+    """Create a CSAR archive
 
     `service_template_path` is the path of the service template to create the archive from
-
-    `destination` is the path of the output CSAR archive file
+    `destination` is the path of the output CSAR archive
     """
     logger.info('Creating a CSAR archive')
     if not destination.endswith(csar.CSAR_FILE_EXTENSION):

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/fdd57c47/aria/cli/commands/services.py
----------------------------------------------------------------------
diff --git a/aria/cli/commands/services.py b/aria/cli/commands/services.py
index 50b530a..24de7c5 100644
--- a/aria/cli/commands/services.py
+++ b/aria/cli/commands/services.py
@@ -25,9 +25,12 @@ from ..core import aria
 from ...core import Core
 from ...modeling import exceptions as modeling_exceptions
 from ...storage import exceptions as storage_exceptions
+from ...parser import consumption
+from ...utils import (formatting, collections, console)
 
 
-SERVICE_COLUMNS = ['id', 'name', 'service_template_name', 'created_at', 'updated_at']
+DESCRIPTION_FIELD_LENGTH_LIMIT = 20
+SERVICE_COLUMNS = ('id', 'name', 'description', 'service_template_name', 'created_at', 'updated_at')
 
 
 @aria.group(name='services')
@@ -38,6 +41,52 @@ def services():
     pass
 
 
+@services.command(name='show',
+                  short_help='Display service information')
+@aria.argument('service-name')
+@aria.options.verbose()
+@aria.options.service_mode_full
+@aria.options.mode_graph
+@aria.options.format_json
+@aria.options.format_yaml
+@aria.pass_model_storage
+@aria.pass_logger
+def show(service_name, model_storage, mode_full, mode_graph, format_json, format_yaml, logger):
+    """Show information for a specific service template
+
+    `SERVICE_NAME` is the name of the service to display information on.
+    """
+    service = model_storage.service.get_by_name(service_name)
+
+    if format_json or format_yaml:
+        mode_full = True
+
+    if mode_full:
+        consumption.ConsumptionContext()
+        if format_json:
+            console.puts(formatting.json_dumps(collections.prune(service.as_raw)))
+        elif format_yaml:
+            console.puts(formatting.yaml_dumps(collections.prune(service.as_raw)))
+        else:
+            service.dump()
+    elif mode_graph:
+        consumption.ConsumptionContext()
+        service.dump_graph()
+    else:
+        logger.info('Showing service {0}...'.format(service_name))
+        service_dict = service.to_dict()
+        columns = SERVICE_COLUMNS
+        column_formatters = \
+            dict(description=table.trim_formatter_generator(DESCRIPTION_FIELD_LENGTH_LIMIT))
+        table.print_data(columns, service_dict, 'Service:',
+                         column_formatters=column_formatters, col_max_width=50)
+
+        if service_dict['description'] is not None:
+            logger.info('Description:')
+            logger.info('{0}{1}'.format(service_dict['description'].encode('UTF-8') or '',
+                                        os.linesep))
+
+
 @services.command(name='list', short_help='List services')
 @aria.options.service_template_name()
 @aria.options.sort_by()

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/fdd57c47/aria/cli/core/aria.py
----------------------------------------------------------------------
diff --git a/aria/cli/core/aria.py b/aria/cli/core/aria.py
index ed6afa1..56fe2f7 100644
--- a/aria/cli/core/aria.py
+++ b/aria/cli/core/aria.py
@@ -19,6 +19,7 @@ import sys
 import difflib
 import StringIO
 import traceback
+import inspect
 from functools import wraps
 
 import click
@@ -40,40 +41,57 @@ CLICK_CONTEXT_SETTINGS = dict(
 
 
 class MutuallyExclusiveOption(click.Option):
-    """Makes options mutually exclusive. The option must pass a `cls` argument
-    with this class name and a `mutually_exclusive` argument with a list of
-    argument names it is mutually exclusive with.
-
-    NOTE: All mutually exclusive options must use this. It's not enough to
-    use it in just one of the options.
-    """
-
     def __init__(self, *args, **kwargs):
-        self.mutually_exclusive = set(kwargs.pop('mutually_exclusive', []))
-        self.mutuality_error_message = \
-            kwargs.pop('mutuality_error_message',
-                       helptexts.DEFAULT_MUTUALITY_MESSAGE)
-        self.mutuality_string = ', '.join(self.mutually_exclusive)
+        self.mutually_exclusive = set(kwargs.pop('mutually_exclusive', tuple()))
+        self.mutuality_description = kwargs.pop('mutuality_description',
+                                                ', '.join(self.mutually_exclusive))
+        self.mutuality_error = kwargs.pop('mutuality_error',
+                                          helptexts.DEFAULT_MUTUALITY_ERROR_MESSAGE)
         if self.mutually_exclusive:
             help = kwargs.get('help', '')
-            kwargs['help'] = (
-                '{0}. This argument is mutually exclusive with '
-                'arguments: [{1}] ({2})'.format(
-                    help,
-                    self.mutuality_string,
-                    self.mutuality_error_message))
+            kwargs['help'] = '{0}. {1}'.format(help, self._message)
         super(MutuallyExclusiveOption, self).__init__(*args, **kwargs)
 
     def handle_parse_result(self, ctx, opts, args):
-        if self.mutually_exclusive.intersection(opts) and self.name in opts:
-            raise click.UsageError(
-                'Illegal usage: `{0}` is mutually exclusive with '
-                'arguments: [{1}] ({2}).'.format(
-                    self.name,
-                    self.mutuality_string,
-                    self.mutuality_error_message))
-        return super(MutuallyExclusiveOption, self).handle_parse_result(
-            ctx, opts, args)
+        if (self.name in opts) and self.mutually_exclusive.intersection(opts):
+            raise click.UsageError('Illegal usage: {0}'.format(self._message))
+        return super(MutuallyExclusiveOption, self).handle_parse_result(ctx, opts, args)
+
+    @property
+    def _message(self):
+        return '{0} be used together with {1} ({2}).'.format(
+            '{0} cannot'.format(', '.join(self.opts)) if hasattr(self, 'opts') else 'Cannot',
+            self.mutuality_description,
+            self.mutuality_error)
+
+
+def mutually_exclusive_option(*param_decls, **attrs):
+    """
+    Decorator for mutually exclusive options.
+
+    This decorator works similarly to `click.option`, but supports an extra ``mutually_exclusive``
+    argument, which is a list of argument names with which the option is mutually exclusive.
+
+    You can optionally also supply ``mutuality_description`` and ``mutuality_error`` to override the
+    default messages.
+
+    NOTE: All mutually exclusive options must use this. It's not enough to use it in just one of the
+    options.
+    """
+
+    # NOTE: This code is copied and slightly modified from click.decorators.option and
+    # click.decorators._param_memo. Unfortunately, using click's ``cls`` parameter support does not
+    # work as is with extra decorator arguments.
+
+    def decorator(func):
+        if 'help' in attrs:
+            attrs['help'] = inspect.cleandoc(attrs['help'])
+        param = MutuallyExclusiveOption(param_decls, **attrs)
+        if not hasattr(func, '__click_params__'):
+            func.__click_params__ = []
+        func.__click_params__.append(param)
+        return func
+    return decorator
 
 
 def _format_version_data(version,
@@ -105,13 +123,12 @@ def show_version(ctx, param, value):
 
 
 def inputs_callback(ctx, param, value):
-    """Allow to pass any inputs we provide to a command as
-    processed inputs instead of having to call `inputs_to_dict`
-    inside the command.
+    """
+    Allow to pass any inputs we provide to a command as processed inputs instead of having to call
+    ``inputs_to_dict`` inside the command.
 
-    `@aria.options.inputs` already calls this callback so that
-    every time you use the option it returns the inputs as a
-    dictionary.
+    ``@aria.options.inputs`` already calls this callback so that every time you use the option it
+    returns the inputs as a dictionary.
     """
     if not value:
         return {}
@@ -127,7 +144,6 @@ def set_verbosity_level(ctx, param, value):
 
 
 def set_cli_except_hook():
-
     def recommend(possible_solutions):
         logger.info('Possible solutions:')
         for solution in possible_solutions:
@@ -155,7 +171,8 @@ def set_cli_except_hook():
 
 
 def pass_logger(func):
-    """Simply passes the logger to a command.
+    """
+    Simply passes the logger to a command.
     """
     # Wraps here makes sure the original docstring propagates to click
     @wraps(func)
@@ -166,7 +183,8 @@ def pass_logger(func):
 
 
 def pass_plugin_manager(func):
-    """Simply passes the plugin manager to a command.
+    """
+    Simply passes the plugin manager to a command.
     """
     # Wraps here makes sure the original docstring propagates to click
     @wraps(func)
@@ -177,7 +195,8 @@ def pass_plugin_manager(func):
 
 
 def pass_model_storage(func):
-    """Simply passes the model storage to a command.
+    """
+    Simply passes the model storage to a command.
     """
     # Wraps here makes sure the original docstring propagates to click
     @wraps(func)
@@ -188,7 +207,8 @@ def pass_model_storage(func):
 
 
 def pass_resource_storage(func):
-    """Simply passes the resource storage to a command.
+    """
+    Simply passes the resource storage to a command.
     """
     # Wraps here makes sure the original docstring propagates to click
     @wraps(func)
@@ -199,11 +219,11 @@ def pass_resource_storage(func):
 
 
 def pass_context(func):
-    """Make click context ARIA specific
+    """
+    Make click context ARIA specific.
 
-    This exists purely for aesthetic reasons, otherwise
-    Some decorators are called `@click.something` instead of
-    `@aria.something`
+    This exists purely for aesthetic reasons, otherwise some decorators are called
+    ``@click.something`` instead of ``@aria.something``.
     """
     return click.pass_context(func)
 
@@ -227,7 +247,8 @@ class AliasedGroup(click.Group):
         ctx.fail('Too many matches: {0}'.format(', '.join(sorted(matches))))
 
     def resolve_command(self, ctx, args):
-        """Override clicks ``resolve_command`` method
+        """
+        Override clicks ``resolve_command`` method
         and appends *Did you mean ...* suggestions
         to the raised exception message.
         """
@@ -249,9 +270,9 @@ class AliasedGroup(click.Group):
 
 
 def group(name):
-    """Allow to create a group with a default click context
-    and a cls for click's `didyoueamn` without having to repeat
-    it for every group.
+    """
+    Allow to create a group with a default click context and a cls for click's ``didyoueamn``
+    without having to repeat it for every group.
     """
     return click.group(
         name=name,
@@ -260,34 +281,34 @@ def group(name):
 
 
 def command(*args, **kwargs):
-    """Make Click commands ARIA specific
+    """
+    Make Click commands ARIA specific.
 
-    This exists purely for aesthetical reasons, otherwise
-    Some decorators are called `@click.something` instead of
-    `@aria.something`
+    This exists purely for aesthetic reasons, otherwise some decorators are called
+    ``@click.something`` instead of ``@aria.something``.
     """
     return click.command(*args, **kwargs)
 
 
 def argument(*args, **kwargs):
-    """Make Click arguments ARIA specific
+    """
+    Make Click arguments ARIA specific.
 
-    This exists purely for aesthetic reasons, otherwise
-    Some decorators are called `@click.something` instead of
-    `@aria.something`
+    This exists purely for aesthetic reasons, otherwise some decorators are called
+    ``@click.something`` instead of ``@aria.something``
     """
     return click.argument(*args, **kwargs)
 
 
 class Options(object):
     def __init__(self):
-        """The options api is nicer when you use each option by calling
-        `@aria.options.some_option` instead of `@aria.some_option`.
+        """
+        The options API is nicer when you use each option by calling ``@aria.options.some_option``
+        instead of ``@aria.some_option``.
 
-        Note that some options are attributes and some are static methods.
-        The reason for that is that we want to be explicit regarding how
-        a developer sees an option. It it can receive arguments, it's a
-        method - if not, it's an attribute.
+        Note that some options are attributes and some are static methods. The reason for that is
+        that we want to be explicit regarding how a developer sees an option. If it can receive
+        arguments, it's a method - if not, it's an attribute.
         """
         self.version = click.option(
             '--version',
@@ -325,6 +346,66 @@ class Options(object):
             default=defaults.SERVICE_TEMPLATE_FILENAME,
             help=helptexts.SERVICE_TEMPLATE_FILENAME)
 
+        self.service_template_mode_full = mutually_exclusive_option(
+            '-f',
+            '--full',
+            'mode_full',
+            mutually_exclusive=('mode_types',),
+            is_flag=True,
+            help=helptexts.SHOW_FULL,
+            mutuality_description='-t, --types',
+            mutuality_error=helptexts.MODE_MUTUALITY_ERROR_MESSAGE)
+
+        self.service_mode_full = mutually_exclusive_option(
+            '-f',
+            '--full',
+            'mode_full',
+            mutually_exclusive=('mode_graph',),
+            is_flag=True,
+            help=helptexts.SHOW_FULL,
+            mutuality_description='-g, --graph',
+            mutuality_error=helptexts.MODE_MUTUALITY_ERROR_MESSAGE)
+
+        self.mode_types = mutually_exclusive_option(
+            '-t',
+            '--types',
+            'mode_types',
+            mutually_exclusive=('mode_full',),
+            is_flag=True,
+            help=helptexts.SHOW_TYPES,
+            mutuality_description='-f, --full',
+            mutuality_error=helptexts.MODE_MUTUALITY_ERROR_MESSAGE)
+
+        self.mode_graph = mutually_exclusive_option(
+            '-g',
+            '--graph',
+            'mode_graph',
+            mutually_exclusive=('mode_full',),
+            is_flag=True,
+            help=helptexts.SHOW_GRAPH,
+            mutuality_description='-f, --full',
+            mutuality_error=helptexts.MODE_MUTUALITY_ERROR_MESSAGE)
+
+        self.format_json = mutually_exclusive_option(
+            '-j',
+            '--json',
+            'format_json',
+            mutually_exclusive=('format_yaml',),
+            is_flag=True,
+            help=helptexts.SHOW_JSON,
+            mutuality_description='-y, --yaml',
+            mutuality_error=helptexts.FORMAT_MUTUALITY_ERROR_MESSAGE)
+
+        self.format_yaml = mutually_exclusive_option(
+            '-y',
+            '--yaml',
+            'format_yaml',
+            mutually_exclusive=('format_json',),
+            is_flag=True,
+            help=helptexts.SHOW_YAML,
+            mutuality_description='-j, --json',
+            mutuality_error=helptexts.FORMAT_MUTUALITY_ERROR_MESSAGE)
+
     @staticmethod
     def verbose(expose_value=False):
         return click.option(

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/fdd57c47/aria/cli/helptexts.py
----------------------------------------------------------------------
diff --git a/aria/cli/helptexts.py b/aria/cli/helptexts.py
index 8641822..74934db 100644
--- a/aria/cli/helptexts.py
+++ b/aria/cli/helptexts.py
@@ -14,7 +14,7 @@
 # limitations under the License.
 
 
-DEFAULT_MUTUALITY_MESSAGE = 'Cannot be used simultaneously'
+DEFAULT_MUTUALITY_ERROR_MESSAGE = 'mutually exclusive'
 VERBOSE = \
     "Show verbose output. You can supply this up to three times (i.e. -vvv)"
 
@@ -29,7 +29,7 @@ EXECUTION_ID = "The unique identifier for the execution"
 SERVICE_TEMPLATE_PATH = "The path to the application's service template file"
 SERVICE_TEMPLATE_FILENAME = (
     "The name of the archive's main service template file. "
-    "This is only relevant if uploading a (non-csar) archive")
+    "This is only relevant if uploading a (non-CSAR) archive")
 INPUTS_PARAMS_USAGE = (
     '(Can be provided as wildcard based paths '
     '(*.yaml, /my_inputs/, etc..) to YAML files, a JSON string or as '
@@ -48,3 +48,11 @@ SORT_BY = "Key for sorting the list"
 DESCENDING = "Sort list in descending order [default: False]"
 JSON_OUTPUT = "Output logs in a consumable JSON format"
 MARK_PATTERN = "Mark a regex pattern in the logs"
+
+SHOW_FULL = "Show full information"
+SHOW_JSON = "Show in JSON format (implies --full)"
+SHOW_YAML = "Show in YAML format (implies --full)"
+SHOW_TYPES = "Show only the type hierarchies"
+SHOW_GRAPH = "Show only the node graph"
+MODE_MUTUALITY_ERROR_MESSAGE = 'only one mode is possible'
+FORMAT_MUTUALITY_ERROR_MESSAGE = 'only one format is possible'

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/fdd57c47/aria/cli/table.py
----------------------------------------------------------------------
diff --git a/aria/cli/table.py b/aria/cli/table.py
index 408f81e..d984c87 100644
--- a/aria/cli/table.py
+++ b/aria/cli/table.py
@@ -85,9 +85,10 @@ def _generate(cols, data, column_formatters=None, defaults=None):
 
             return val
         else:
-            return defaults[column]
+            return defaults.get(column)
 
     column_formatters = column_formatters or dict()
+    defaults = defaults or dict()
     pretty_table = PrettyTable(list(cols))
 
     for datum in data:

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/fdd57c47/aria/modeling/service_instance.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_instance.py b/aria/modeling/service_instance.py
index 1efe1e1..41a388d 100644
--- a/aria/modeling/service_instance.py
+++ b/aria/modeling/service_instance.py
@@ -1019,7 +1019,7 @@ class SubstitutionBase(InstanceModelMixin):
     @property
     def as_raw(self):
         return collections.OrderedDict((
-            ('node_type_name', self.node_type_name),
+            ('node_type_name', self.node_type.name),
             ('mappings', formatting.as_raw_dict(self.mappings))))
 
     def validate(self):
@@ -1127,7 +1127,7 @@ class SubstitutionMappingBase(InstanceModelMixin):
     @property
     def as_raw(self):
         return collections.OrderedDict((
-            ('name', self.name)))
+            ('name', self.name),))
 
     def coerce_values(self, report_issues):
         pass

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/fdd57c47/aria/modeling/service_template.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_template.py b/aria/modeling/service_template.py
index 7a192a7..7eb35bd 100644
--- a/aria/modeling/service_template.py
+++ b/aria/modeling/service_template.py
@@ -1063,7 +1063,7 @@ class SubstitutionTemplateMappingBase(TemplateModelMixin):
     @property
     def as_raw(self):
         return collections.OrderedDict((
-            ('name', self.name)))
+            ('name', self.name),))
 
     def coerce_values(self, report_issues):
         pass

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/fdd57c47/aria/modeling/types.py
----------------------------------------------------------------------
diff --git a/aria/modeling/types.py b/aria/modeling/types.py
index 06f171c..7460f47 100644
--- a/aria/modeling/types.py
+++ b/aria/modeling/types.py
@@ -22,6 +22,7 @@ from sqlalchemy import (
     event
 )
 from sqlalchemy.ext import mutable
+from ruamel import yaml
 
 from . import exceptions
 
@@ -206,6 +207,8 @@ class _StrictDict(object):
                 (_StrictDictMixin, _MutableDict),
                 {'_key_cls': key_cls, '_value_cls': value_cls}
             )
+            yaml.representer.RoundTripRepresenter.add_representer(
+                listener_cls, yaml.representer.RoundTripRepresenter.represent_list)
             self._strict_map[strict_dict_map_key] = _StrictValue(type_cls=strict_dict_cls,
                                                                  listener_cls=listener_cls)
 
@@ -242,6 +245,8 @@ class _StrictList(object):
                 (_StrictListMixin, _MutableList),
                 {'_item_cls': item_cls}
             )
+            yaml.representer.RoundTripRepresenter.add_representer(
+                listener_cls, yaml.representer.RoundTripRepresenter.represent_list)
             self._strict_map[item_cls] = _StrictValue(type_cls=strict_list_cls,
                                                       listener_cls=listener_cls)
 

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/fdd57c47/aria/utils/collections.py
----------------------------------------------------------------------
diff --git a/aria/utils/collections.py b/aria/utils/collections.py
index 03feabd..1e732aa 100644
--- a/aria/utils/collections.py
+++ b/aria/utils/collections.py
@@ -249,7 +249,7 @@ def prune(value, is_removable_function=is_removable):
             else:
                 prune(v, is_removable_function)
     elif isinstance(value, dict):
-        for k, v in value.iteritems():
+        for k, v in value.items():
             if is_removable_function(value, k, v):
                 del value[k]
             else: