ariatosca-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From r..@apache.org
Subject [08/11] incubator-ariatosca git commit: ARIA-18 Migrate DSL parser and TOSCA extension code
Date Tue, 15 Nov 2016 15:54:03 GMT
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/6a4dc43f/aria/parser/presentation/fields.py
----------------------------------------------------------------------
diff --git a/aria/parser/presentation/fields.py b/aria/parser/presentation/fields.py
new file mode 100644
index 0000000..4669fee
--- /dev/null
+++ b/aria/parser/presentation/fields.py
@@ -0,0 +1,754 @@
+# 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.
+
+import threading
+from functools import wraps
+from types import MethodType
+from collections import OrderedDict
+
+from ..exceptions import InvalidValueError, AriaException
+from ..utils import (FrozenList, FrozenDict, print_exception, deepcopy_with_locators, merge,
+                     cachedmethod, puts, as_raw, full_type_name, safe_repr)
+from .null import NULL
+from .utils import validate_primitive
+
+#
+# Class decorators
+#
+
+# pylint: disable=unused-argument
+
+def has_fields(cls):
+    """
+    Class decorator for validated field support.
+
+    1. Adds a :code:`FIELDS` class property that is a dict of all the fields.
+       Will inherit and merge :code:`FIELDS` properties from base classes if
+       they have them.
+
+    2. Generates automatic :code:`@property` implementations for the fields
+       with the help of a set of special function decorators.
+
+    The class also works with the Python dict protocol, so that
+    fields can be accessed via dict semantics. The functionality is
+    identical to that of using attribute access.
+
+    The class will also gain two utility methods, :code:`_iter_field_names`
+    and :code:`_iter_fields`.
+    """
+
+    # Make sure we have FIELDS
+    if 'FIELDS' not in cls.__dict__:
+        setattr(cls, 'FIELDS', OrderedDict())
+
+    # Inherit FIELDS from base classes
+    for base in cls.__bases__:
+        if hasattr(base, 'FIELDS'):
+            cls.FIELDS.update(base.FIELDS)
+
+    # We could do this:
+    #
+    #  for name, field in cls.__dict__.iteritems():
+    #
+    # But dir() is better because it has a deterministic order (alphabetical)
+
+    for name in dir(cls):
+        field = getattr(cls, name)
+
+        if isinstance(field, Field):
+            # Accumulate
+            cls.FIELDS[name] = field
+
+            field.name = name
+            field.container_cls = cls
+
+            # This function is here just to create an enclosed scope for "field"
+            def closure(field):
+
+                # By convention, we have the getter wrap the original function.
+                # (It is, for example, where the Python help() function will look for
+                # docstrings when encountering a property.)
+                @cachedmethod
+                @wraps(field.func)
+                def getter(self):
+                    return field.get(self, None)
+
+                def setter(self, value):
+                    field.set(self, None, value)
+
+                # Convert to Python property
+                return property(fget=getter, fset=setter)
+
+            setattr(cls, name, closure(field))
+
+    # Bind methods
+    setattr(cls, '_iter_field_names', MethodType(has_fields_iter_field_names, None, cls))
+    setattr(cls, '_iter_fields', MethodType(has_fields_iter_fields, None, cls))
+
+    # Behave like a dict
+    setattr(cls, '__len__', MethodType(has_fields_len, None, cls))
+    setattr(cls, '__getitem__', MethodType(has_fields_getitem, None, cls))
+    setattr(cls, '__setitem__', MethodType(has_fields_setitem, None, cls))
+    setattr(cls, '__delitem__', MethodType(has_fields_delitem, None, cls))
+    setattr(cls, '__iter__', MethodType(has_fields_iter, None, cls))
+    setattr(cls, '__contains__', MethodType(has_fields_contains, None, cls))
+
+    return cls
+
+
+def short_form_field(name):
+    """
+    Class decorator for specifying the short form field.
+
+    The class must be decorated with :func:`has_fields`.
+    """
+
+    def decorator(cls):
+        if hasattr(cls, name) and hasattr(cls, 'FIELDS') and (name in cls.FIELDS):
+            setattr(cls, 'SHORT_FORM_FIELD', name)
+            return cls
+        else:
+            raise AttributeError('@short_form_field must be used with '
+                                 'a Field name in @has_fields class')
+    return decorator
+
+
+def allow_unknown_fields(cls):
+    """
+    Class decorator specifying that the class allows unknown fields.
+
+    The class must be decorated with :func:`has_fields`.
+    """
+
+    if hasattr(cls, 'FIELDS'):
+        setattr(cls, 'ALLOW_UNKNOWN_FIELDS', True)
+        return cls
+    else:
+        raise AttributeError('@allow_unknown_fields must be used with a @has_fields class')
+
+#
+# Method decorators
+#
+
+
+def primitive_field(cls=None, default=None, allowed=None, required=False):
+    """
+    Method decorator for primitive fields.
+
+    The function must be a method in a class decorated with :func:`has_fields`.
+    """
+
+    def decorator(func):
+        return Field(field_variant='primitive', func=func, cls=cls, default=default,
+                     allowed=allowed, required=required)
+    return decorator
+
+
+def primitive_list_field(cls=None, default=None, allowed=None, required=False):
+    """
+    Method decorator for list of primitive fields.
+
+    The function must be a method in a class decorated with :func:`has_fields`.
+    """
+
+    def decorator(func):
+        return Field(field_variant='primitive_list', func=func, cls=cls, default=default,
+                     allowed=allowed, required=required)
+    return decorator
+
+
+def primitive_dict_field(cls=None, default=None, allowed=None, required=False):
+    """
+    Method decorator for dict of primitive fields.
+
+    The function must be a method in a class decorated with :func:`has_fields`.
+    """
+    def decorator(func):
+        return Field(field_variant='primitive_dict', func=func, cls=cls, default=default,
+                     allowed=allowed, required=required)
+    return decorator
+
+
+def primitive_dict_unknown_fields(cls=None, default=None, allowed=None, required=False):
+    """
+    Method decorator for dict of primitive fields, for all the fields that are
+    not already decorated.
+
+    The function must be a method in a class decorated with :func:`has_fields`.
+    """
+
+    def decorator(func):
+        return Field(field_variant='primitive_dict_unknown_fields', func=func, cls=cls,
+                     default=default, allowed=allowed, required=required)
+    return decorator
+
+
+def object_field(cls, default=None, allowed=None, required=False):
+    """
+    Method decorator for object fields.
+
+    The function must be a method in a class decorated with :func:`has_fields`.
+    """
+    def decorator(func):
+        return Field(field_variant='object', func=func, cls=cls, default=default, allowed=allowed,
+                     required=required)
+    return decorator
+
+
+def object_list_field(cls, default=None, allowed=None, required=False):
+    """
+    Method decorator for list of object fields.
+
+    The function must be a method in a class decorated with :func:`has_fields`.
+    """
+
+    def decorator(func):
+        return Field(field_variant='object_list', func=func, cls=cls, default=default,
+                     allowed=allowed, required=required)
+    return decorator
+
+
+def object_dict_field(cls, default=None, allowed=None, required=False):
+    """
+    Method decorator for dict of object fields.
+
+    The function must be a method in a class decorated with :func:`has_fields`.
+    """
+
+    def decorator(func):
+        return Field(field_variant='object_dict', func=func, cls=cls, default=default,
+                     allowed=allowed, required=required)
+    return decorator
+
+
+def object_sequenced_list_field(cls, default=None, allowed=None, required=False):
+    """
+    Method decorator for sequenced list of object fields.
+
+    The function must be a method in a class decorated with :func:`has_fields`.
+    """
+
+    def decorator(func):
+        return Field(field_variant='sequenced_object_list', func=func, cls=cls, default=default,
+                     allowed=allowed, required=required)
+    return decorator
+
+
+def object_dict_unknown_fields(cls, default=None, allowed=None, required=False):
+    """
+    Method decorator for dict of object fields, for all the fields that are not already decorated.
+
+    The function must be a method in a class decorated with :func:`has_fields`.
+    """
+    def decorator(func):
+        return Field(field_variant='object_dict_unknown_fields', func=func, cls=cls,
+                     default=default, allowed=allowed, required=required)
+    return decorator
+
+
+def field_getter(getter_func):
+    """
+    Method decorator for overriding the getter function of a field.
+
+    The signature of the getter function must be: :code:`f(field, presentation, context)`.
+    The default getter can be accessed as :code:`field.default_get(presentation, context)`.
+
+    The function must already be decorated with a field decorator.
+    """
+
+    def decorator(field):
+        if isinstance(field, Field):
+            field.get = MethodType(getter_func, field, Field)
+            return field
+        else:
+            raise AttributeError('@field_getter must be used with a Field')
+    return decorator
+
+
+def field_setter(setter_func):
+    """
+    Method decorator for overriding the setter function of a field.
+
+    The signature of the setter function must be: :code:`f(field, presentation, context, value)`.
+    The default setter can be accessed as :code:`field.default_set(presentation, context, value)`.
+
+    The function must already be decorated with a field decorator.
+    """
+
+    def decorator(field):
+        if isinstance(field, Field):
+            field.set = MethodType(setter_func, field, Field)
+            return field
+        else:
+            raise AttributeError('@field_setter must be used with a Field')
+    return decorator
+
+
+def field_validator(validator_fn):
+    """
+    Method decorator for overriding the validator function of a field.
+
+    The signature of the validator function must be: :code:f(field, presentation, context)`.
+    The default validator can be accessed as :code:`field.default_validate(presentation, context)`.
+
+    The function must already be decorated with a field decorator.
+    """
+
+    def decorator(field):
+        if isinstance(field, Field):
+            field.validate = MethodType(validator_fn, field, Field)
+            return field
+        else:
+            raise AttributeError('@field_validator must be used with a Field')
+    return decorator
+
+#
+# Utils
+#
+
+
+def has_fields_iter_field_names(self):
+    for name in self.__class__.FIELDS:
+        yield name
+
+
+def has_fields_iter_fields(self):
+    return self.FIELDS.iteritems()
+
+
+def has_fields_len(self):
+    return len(self.__class__.FIELDS)
+
+
+def has_fields_getitem(self, key):
+    if not isinstance(key, basestring):
+        raise TypeError('key must be a string')
+    if key not in self.__class__.FIELDS:
+        raise KeyError('no \'%s\' property' % key)
+    return getattr(self, key)
+
+
+def has_fields_setitem(self, key, value):
+    if not isinstance(key, basestring):
+        raise TypeError('key must be a string')
+    if key not in self.__class__.FIELDS:
+        raise KeyError('no \'%s\' property' % key)
+    return setattr(self, key, value)
+
+
+def has_fields_delitem(self, key):
+    if not isinstance(key, basestring):
+        raise TypeError('key must be a string')
+    if key not in self.__class__.FIELDS:
+        raise KeyError('no \'%s\' property' % key)
+    return setattr(self, key, None)
+
+
+def has_fields_iter(self):
+    return self.__class__.FIELDS.iterkeys()
+
+
+def has_fields_contains(self, key):
+    if not isinstance(key, basestring):
+        raise TypeError('key must be a string')
+    return key in self.__class__.FIELDS
+
+
+class Field(object):
+    """
+    Field handler used by :code:`@has_fields` decorator.
+    """
+
+    def __init__(self, field_variant, func, cls=None, default=None, allowed=None, required=False):
+        if cls == str:
+            # Use "unicode" instead of "str"
+            cls = unicode
+
+        self.container_cls = None
+        self.name = None
+        self.field_variant = field_variant
+        self.func = func
+        self.cls = cls
+        self.default = default
+        self.allowed = allowed
+        self.required = required
+
+    @property
+    def full_name(self):
+        return 'field "%s" in "%s"' % (self.name, full_type_name(self.container_cls))
+
+    @property
+    def full_cls_name(self):
+        name = full_type_name(self.cls)
+        if name == 'unicode':
+            # For simplicity, display "unicode" as "str"
+            name = 'str'
+        return name
+
+    def get(self, presentation, context):
+        return self.default_get(presentation, context)
+
+    def set(self, presentation, context, value):
+        return self.default_set(presentation, context, value)
+
+    def validate(self, presentation, context):
+        self.default_validate(presentation, context)
+
+    def get_locator(self, raw):
+        if hasattr(raw, '_locator'):
+            locator = raw._locator
+            if locator is not None:
+                return locator.get_child(self.name)
+        return None
+
+    def dump(self, presentation, context):
+        value = getattr(presentation, self.name)
+        if value is None:
+            return
+
+        dumper = getattr(self, '_dump_%s' % self.field_variant)
+        dumper(context, value)
+
+    def default_get(self, presentation, context):
+        # Handle raw
+
+        default_raw = (presentation._get_default_raw()
+                       if hasattr(presentation, '_get_default_raw')
+                       else None)
+
+        if default_raw is None:
+            raw = presentation._raw
+        else:
+            # Handle default raw value
+            raw = deepcopy_with_locators(default_raw)
+            merge(raw, presentation._raw)
+
+        # Handle unknown fields
+
+        if self.field_variant == 'primitive_dict_unknown_fields':
+            return self._get_primitive_dict_unknown_fields(presentation, raw, context)
+        elif self.field_variant == 'object_dict_unknown_fields':
+            return self._get_object_dict_unknown_fields(presentation, raw, context)
+
+        is_short_form_field = (self.container_cls.SHORT_FORM_FIELD == self.name
+                               if hasattr(self.container_cls, 'SHORT_FORM_FIELD')
+                               else False)
+        is_dict = isinstance(raw, dict)
+
+        # Find value
+
+        value = self._find_value(is_short_form_field, is_dict, raw)
+
+        # Handle required
+
+        if value is None:
+            if self.required:
+                raise InvalidValueError('required %s does not have a value' % self.full_name,
+                                        locator=self.get_locator(raw))
+            else:
+                return None
+
+        # Handle allowed values
+
+        if self.allowed is not None:
+            if value not in self.allowed:
+                raise InvalidValueError('%s is not %s'
+                                        % (self.full_name, ' or '.join([safe_repr(v)
+                                                                        for v in self.allowed])),
+                                        locator=self.get_locator(raw))
+
+        # Handle get according to variant
+
+        getter = getattr(self, '_get_%s' % self.field_variant, None)
+
+        if getter is None:
+            locator = self.get_locator(raw)
+            location = (' @%s' % locator) if locator is not None else ''
+            raise AttributeError('%s has unsupported field variant: "%s"%s'
+                                 % (self.full_name, self.field_variant, location))
+
+        return getter(presentation, raw, value, context)
+
+    def _find_value(self, is_short_form_field, is_dict, raw):
+        value = None
+        if is_short_form_field and not is_dict:
+            # Handle short form
+            value = raw
+        elif is_dict:
+            if self.name in raw:
+                value = raw[self.name]
+                if value is None:
+                    # An explicit null
+                    value = NULL
+            else:
+                value = self.default
+        return value
+
+    def default_set(self, presentation, context, value):
+        raw = presentation._raw
+        old = self.get(presentation, context)
+        raw[self.name] = value
+        try:
+            self.validate(presentation, context)
+        except Exception as e:
+            raw[self.name] = old
+            raise e
+        return old
+
+    def default_validate(self, presentation, context):
+        value = None
+
+        try:
+            value = self.get(presentation, context)
+        except AriaException as e:
+            if e.issue:
+                context.validation.report(issue=e.issue)
+        except Exception as e:
+            context.validation.report(exception=e)
+            print_exception(e)
+
+        self.validate_value(value, context)
+
+    def validate_value(self, value, context):
+        if isinstance(value, list):
+            if self.field_variant == 'object_list':
+                for element in value:
+                    if hasattr(element, '_validate'):
+                        element._validate(context)
+            elif self.field_variant == 'sequenced_object_list':
+                for _, element in value:
+                    if hasattr(element, '_validate'):
+                        element._validate(context)
+        elif isinstance(value, dict):
+            if self.field_variant in ('object_dict', 'object_dict_unknown_fields'):
+                for inner_value in value.itervalues():
+                    if hasattr(inner_value, '_validate'):
+                        inner_value._validate(context)
+
+        if hasattr(value, '_validate'):
+            value._validate(context)
+
+    @staticmethod
+    def _get_context():
+        thread_locals = threading.local()
+        return getattr(thread_locals, 'aria_consumption_context', None)
+
+    def _coerce_primitive(self, value, context):
+        if context is None:
+            context = Field._get_context()
+        allow_primitive_coercion = (context.validation.allow_primitive_coersion
+                                    if context is not None
+                                    else True)
+        return validate_primitive(value, self.cls, allow_primitive_coercion)
+
+    # primitive
+
+    def _get_primitive(self, presentation, raw, value, context):
+        if (self.cls is not None and not isinstance(value, self.cls)
+                and value is not None and value is not NULL):
+            try:
+                return self._coerce_primitive(value, context)
+            except ValueError as e:
+                raise InvalidValueError('%s is not a valid "%s": %s' %
+                                        (self.full_name, self.full_cls_name, safe_repr(value)),
+                                        locator=self.get_locator(raw), cause=e)
+        return value
+
+    def _dump_primitive(self, context, value):
+        if hasattr(value, 'as_raw'):
+            value = as_raw(value)
+        puts('%s: %s' % (self.name, context.style.literal(value)))
+
+    # primitive list
+
+    def _get_primitive_list(self, presentation, raw, value, context):
+        if not isinstance(value, list):
+            raise InvalidValueError('%s is not a list: %s' % (self.full_name, safe_repr(value)),
+                                    locator=self.get_locator(raw))
+        primitive_list = value
+        if self.cls is not None:
+            if context is None:
+                context = Field._get_context()
+            primitive_list = []
+            for i, _ in enumerate(value):
+                primitive = value[i]
+                try:
+                    primitive = self._coerce_primitive(primitive, context)
+                except ValueError as e:
+                    raise InvalidValueError('%s is not a list of "%s": element %d is %s'
+                                            % (self.full_name,
+                                               self.full_cls_name,
+                                               i,
+                                               safe_repr(primitive)),
+                                            locator=self.get_locator(raw), cause=e)
+                if primitive in primitive_list:
+                    raise InvalidValueError('%s has a duplicate "%s": %s'
+                                            % (self.full_name,
+                                               self.full_cls_name,
+                                               safe_repr(primitive)),
+                                            locator=self.get_locator(raw))
+                primitive_list.append(primitive)
+        return FrozenList(primitive_list)
+
+    def _dump_primitive_list(self, context, value):
+        puts('%s:' % self.name)
+        with context.style.indent:
+            for primitive in value:
+                if hasattr(primitive, 'as_raw'):
+                    primitive = as_raw(primitive)
+                puts(context.style.literal(primitive))
+
+    # primitive dict
+
+    def _get_primitive_dict(self, presentation, raw, value, context):
+        if not isinstance(value, dict):
+            raise InvalidValueError('%s is not a dict: %s' % (self.full_name, safe_repr(value)),
+                                    locator=self.get_locator(raw))
+        primitive_dict = value
+        if self.cls is not None:
+            if context is None:
+                context = Field._get_context()
+            primitive_dict = OrderedDict()
+            for k, v in value.iteritems():
+                try:
+                    primitive_dict[k] = self._coerce_primitive(v, context)
+                except ValueError as e:
+                    raise InvalidValueError('%s is not a dict of "%s" values: entry "%d" is %s'
+                                            % (self.full_name, self.full_cls_name, k, safe_repr(v)),
+                                            locator=self.get_locator(raw),
+                                            cause=e)
+        return FrozenDict(primitive_dict)
+
+    def _dump_primitive_dict(self, context, value):
+        puts('%s:' % self.name)
+        with context.style.indent:
+            for v in value.itervalues():
+                if hasattr(v, 'as_raw'):
+                    v = as_raw(v)
+                puts(context.style.literal(v))
+
+    # object
+
+    def _get_object(self, presentation, raw, value, context):
+        try:
+            return self.cls(name=self.name, raw=value, container=presentation)
+        except TypeError as e:
+            raise InvalidValueError('%s cannot not be initialized to an instance of "%s": %s'
+                                    % (self.full_name, self.full_cls_name, safe_repr(value)),
+                                    cause=e,
+                                    locator=self.get_locator(raw))
+
+    def _dump_object(self, context, value):
+        puts('%s:' % self.name)
+        with context.style.indent:
+            if hasattr(value, '_dump'):
+                value._dump(context)
+
+    # object list
+
+    def _get_object_list(self, presentation, raw, value, context):
+        if not isinstance(value, list):
+            raise InvalidValueError('%s is not a list: %s'
+                                    % (self.full_name, safe_repr(value)),
+                                    locator=self.get_locator(raw))
+        return FrozenList((self.cls(name=self.name, raw=v, container=presentation) for v in value))
+
+    def _dump_object_list(self, context, value):
+        puts('%s:' % self.name)
+        with context.style.indent:
+            for v in value:
+                if hasattr(v, '_dump'):
+                    v._dump(context)
+
+    # object dict
+
+    def _get_object_dict(self, presentation, raw, value, context):
+        if not isinstance(value, dict):
+            raise InvalidValueError('%s is not a dict: %s' % (self.full_name, safe_repr(value)),
+                                    locator=self.get_locator(raw))
+        return FrozenDict(((k, self.cls(name=k, raw=v, container=presentation))
+                           for k, v in value.iteritems()))
+
+    def _dump_object_dict(self, context, value):
+        puts('%s:' % self.name)
+        with context.style.indent:
+            for v in value.itervalues():
+                if hasattr(v, '_dump'):
+                    v._dump(context)
+
+    # sequenced object list
+
+    def _get_sequenced_object_list(self, presentation, raw, value, context):
+        if not isinstance(value, list):
+            raise InvalidValueError('%s is not a sequenced list (a list of dicts, '
+                                    'each with exactly one key): %s'
+                                    % (self.full_name, safe_repr(value)),
+                                    locator=self.get_locator(raw))
+        sequence = []
+        for v in value:
+            if not isinstance(v, dict):
+                raise InvalidValueError('%s list elements are not all dicts with '
+                                        'exactly one key: %s' % (self.full_name, safe_repr(value)),
+                                        locator=self.get_locator(raw))
+            if len(v) != 1:
+                raise InvalidValueError('%s list elements do not all have exactly one key: %s'
+                                        % (self.full_name, safe_repr(value)),
+                                        locator=self.get_locator(raw))
+            key, value = v.items()[0]
+            sequence.append((key, self.cls(name=key, raw=value, container=presentation)))
+        return FrozenList(sequence)
+
+    def _dump_sequenced_object_list(self, context, value):
+        puts('%s:' % self.name)
+        for _, v in value:
+            if hasattr(v, '_dump'):
+                v._dump(context)
+
+    # primitive dict for unknown fields
+
+    def _get_primitive_dict_unknown_fields(self, presentation, raw, context):
+        if isinstance(raw, dict):
+            primitive_dict = raw
+            if self.cls is not None:
+                if context is None:
+                    context = Field._get_context()
+                primitive_dict = OrderedDict()
+                for k, v in raw.iteritems():
+                    if k not in presentation.FIELDS:
+                        try:
+                            primitive_dict[k] = self._coerce_primitive(v, context)
+                        except ValueError as e:
+                            raise InvalidValueError('%s is not a dict of "%s" values:'
+                                                    ' entry "%d" is %s'
+                                                    % (self.full_name, self.full_cls_name,
+                                                       k, safe_repr(v)),
+                                                    locator=self.get_locator(raw),
+                                                    cause=e)
+            return FrozenDict(primitive_dict)
+        return None
+
+    def _dump_primitive_dict_unknown_fields(self, context, value):
+        self._dump_primitive_dict(context, value)
+
+    # object dict for unknown fields
+
+    def _get_object_dict_unknown_fields(self, presentation, raw, context):
+        if isinstance(raw, dict):
+            return FrozenDict(((k, self.cls(name=k, raw=v, container=presentation))
+                               for k, v in raw.iteritems() if k not in presentation.FIELDS))
+        return None
+
+    def _dump_object_dict_unknown_fields(self, context, value):
+        self._dump_object_dict(context, value)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/6a4dc43f/aria/parser/presentation/null.py
----------------------------------------------------------------------
diff --git a/aria/parser/presentation/null.py b/aria/parser/presentation/null.py
new file mode 100644
index 0000000..a69134e
--- /dev/null
+++ b/aria/parser/presentation/null.py
@@ -0,0 +1,67 @@
+# 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 ..utils import deepcopy_with_locators
+
+
+class Null(object):
+    """
+    Represents an explicit null value provided by the user, which is different from
+    not supplying a value at all.
+
+    It is a singleton.
+    """
+
+    @property
+    def as_raw(self):
+        return None
+
+NULL = Null()
+
+
+def none_to_null(value):
+    """
+    Convert :code:`None` to :code:`NULL`, recursively.
+    """
+
+    if value is None:
+        return NULL
+    if isinstance(value, list):
+        value = deepcopy_with_locators(value)
+        for i, _ in enumerate(value):
+            value[i] = none_to_null(value[i])
+    elif isinstance(value, dict):
+        value = deepcopy_with_locators(value)
+        for k, v in value.iteritems():
+            value[k] = none_to_null(v)
+    return value
+
+
+def null_to_none(value):
+    """
+    Convert :code:`NULL` to :code:`None`, recursively.
+    """
+
+    if value is NULL:
+        return None
+    if isinstance(value, list):
+        value = deepcopy_with_locators(value)
+        for i, _ in enumerate(value):
+            value[i] = none_to_null(value[i])
+    elif isinstance(value, dict):
+        value = deepcopy_with_locators(value)
+        for k, v in value.iteritems():
+            value[k] = none_to_null(v)
+    return value

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/6a4dc43f/aria/parser/presentation/presentation.py
----------------------------------------------------------------------
diff --git a/aria/parser/presentation/presentation.py b/aria/parser/presentation/presentation.py
new file mode 100644
index 0000000..15cb5ed
--- /dev/null
+++ b/aria/parser/presentation/presentation.py
@@ -0,0 +1,235 @@
+# 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 ..validation import Issue
+from ..utils import HasCachedMethods, full_type_name, deepcopy_with_locators, puts, safe_repr
+from .null import none_to_null
+from .utils import (get_locator, validate_no_short_form, validate_no_unknown_fields,
+                    validate_known_fields, validate_primitive)
+
+
+class Value(object):
+    """
+    Encapsulates a typed value assignment.
+    """
+
+    def __init__(self, type_name, value, description):
+        self.type = deepcopy_with_locators(type_name)
+        self.value = deepcopy_with_locators(value)
+        self.description = deepcopy_with_locators(description)
+
+
+class PresentationBase(HasCachedMethods):
+    """
+    Base class for ARIA presentation classes.
+    """
+
+    def __init__(self, name=None, raw=None, container=None):
+        self._name = name
+        self._raw = raw
+        self._container = container
+        super(PresentationBase, self).__init__()
+
+    @property
+    def as_raw(self):
+        return self._raw
+
+    def _validate(self, context):
+        """
+        Validates the presentation while reporting errors in the validation context but
+        *not* raising exceptions.
+
+        The base class does not thing, but subclasses may override this for specialized
+        validation.
+        """
+
+    @property
+    def _fullname(self):
+        """
+        Always returns a usable full name of the presentation, whether it itself is named,
+        or recursing to its container, and finally defaulting to the class name.
+        """
+
+        if self._name is not None:
+            return self._name
+        elif self._container is not None:
+            return self._container._fullname
+        return full_type_name(self)
+
+    @property
+    def _locator(self):
+        """
+        Attempts to return the most relevant locator, whether we have one, or recursing
+        to our container.
+
+        :rtype: :class:`aria.reading.Locator`
+        """
+
+        return get_locator(self._raw, self._container)
+
+    def _get(self, *names):
+        """
+        Gets attributes recursively.
+        """
+
+        obj = self
+        if (obj is not None) and names:
+            for name in names:
+                obj = getattr(obj, name, None)
+                if obj is None:
+                    break
+        return obj
+
+    def _get_from_dict(self, *names):
+        """
+        Gets attributes recursively, except for the last name which is used
+        to get a value from the last dict.
+        """
+
+        if names:
+            obj = self._get(*names[:-1])
+            if isinstance(obj, dict):
+                return obj.get(names[-1])  # pylint: disable=no-member
+        return None
+
+    def _get_child_locator(self, *names):
+        """
+        Attempts to return the locator of one our children. Will default to our locator
+        if not found.
+
+        :rtype: :class:`aria.reading.Locator`
+        """
+
+        if hasattr(self._raw, '_locator'):
+            locator = self._raw._locator
+            if locator is not None:
+                return locator.get_child(*names)
+        return self._locator
+
+    def _dump(self, context):
+        """
+        Emits a colorized representation.
+
+        The base class will emit a sensible default representation of the fields,
+        (by calling :code:`_dump_content`), but subclasses may override this for specialized
+        dumping.
+        """
+
+        if self._name:
+            puts(context.style.node(self._name))
+            with context.style.indent:
+                self._dump_content(context)
+        else:
+            self._dump_content(context)
+
+    def _dump_content(self, context, field_names=None):
+        """
+        Emits a colorized representation of the contents.
+
+        The base class will call :code:`_dump_field` on all the fields, but subclasses may
+        override this for specialized dumping.
+        """
+
+        if field_names:
+            for field_name in field_names:
+                self._dump_field(context, field_name)
+        elif hasattr(self, '_iter_field_names'):
+            for field_name in self._iter_field_names():  # pylint: disable=no-member
+                self._dump_field(context, field_name)
+        else:
+            puts(context.style.literal(self._raw))
+
+    def _dump_field(self, context, field_name):
+        """
+        Emits a colorized representation of the field.
+
+        According to the field type, this may trigger nested recursion. The nested
+        types will delegate to their :code:`_dump` methods.
+        """
+
+        field = self.FIELDS[field_name]  # pylint: disable=no-member
+        field.dump(self, context)
+
+    def _clone(self, container=None):
+        """
+        Creates a clone of this presentation, optionally allowing for a new container.
+        """
+
+        raw = deepcopy_with_locators(self._raw)
+        if container is None:
+            container = self._container
+        return self.__class__(name=self._name, raw=raw, container=container)
+
+
+class Presentation(PresentationBase):
+    """
+    Base class for ARIA presentations. A presentation is a Pythonic wrapper around
+    agnostic raw data, adding the ability to read and modify the data with proper
+    validation.
+
+    ARIA presentation classes will often be decorated with @has_fields, as that
+    mechanism automates a lot of field-specific validation. However, that is not a
+    requirement.
+
+    Make sure that your utility property and method names begin with a "_", because
+    those names without a "_" prefix are normally reserved for fields.
+    """
+
+    def _validate(self, context):
+        validate_no_short_form(context, self)
+        validate_no_unknown_fields(context, self)
+        validate_known_fields(context, self)
+
+
+class AsIsPresentation(PresentationBase):
+    """
+    Base class for trivial ARIA presentations that provide the raw value as is.
+    """
+
+    def __init__(self, name=None, raw=None, container=None, cls=None):
+        super(AsIsPresentation, self).__init__(name, raw, container)
+        self.cls = cls
+
+    @property
+    def value(self):
+        return none_to_null(self._raw)
+
+    @value.setter
+    def value(self, value):
+        self._raw = value
+
+    @property
+    def _full_cls_name(self):
+        name = full_type_name(self.cls) if self.cls is not None else None
+        if name == 'unicode':
+            # For simplicity, display "unicode" as "str"
+            name = 'str'
+        return name
+
+    def _validate(self, context):
+        try:
+            validate_primitive(self._raw, self.cls, context.validation.allow_primitive_coersion)
+        except ValueError as e:
+            context.validation.report('"%s" is not a valid "%s": %s'
+                                      % (self._fullname, self._full_cls_name, safe_repr(self._raw)),
+                                      locator=self._locator,
+                                      level=Issue.FIELD,
+                                      exception=e)
+
+    def _dump(self, context):
+        if hasattr(self._raw, '_dump'):
+            self._raw._dump(context)
+        else:
+            super(AsIsPresentation, self)._dump(context)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/6a4dc43f/aria/parser/presentation/presenter.py
----------------------------------------------------------------------
diff --git a/aria/parser/presentation/presenter.py b/aria/parser/presentation/presenter.py
new file mode 100644
index 0000000..4cade3a
--- /dev/null
+++ b/aria/parser/presentation/presenter.py
@@ -0,0 +1,69 @@
+# 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 ..validation import Issue
+from ..utils import merge, safe_repr
+from .presentation import Presentation
+
+
+class Presenter(Presentation):
+    """
+    Base class for ARIA presenters.
+
+    Presenters provide a robust API over agnostic raw data.
+    """
+
+    DSL_VERSIONS = None
+    ALLOWED_IMPORTED_DSL_VERSIONS = None
+
+    @classmethod
+    def can_present(cls, raw):
+        dsl = raw.get('tosca_definitions_version')
+        assert cls.DSL_VERSIONS
+        return dsl in cls.DSL_VERSIONS
+
+    def _validate_import(self, context, presentation):
+        tosca_definitions_version = presentation.service_template.tosca_definitions_version
+        assert self.ALLOWED_IMPORTED_DSL_VERSIONS
+        if tosca_definitions_version is not None \
+                and tosca_definitions_version not in self.__class__.ALLOWED_IMPORTED_DSL_VERSIONS:
+            context.validation.report(
+                'import "tosca_definitions_version" is not one of %s: %s'
+                % (' or '.join([safe_repr(v)
+                                for v in self.__class__.ALLOWED_IMPORTED_DSL_VERSIONS]),
+                   presentation.service_template.tosca_definitions_version),
+                locator=presentation._get_child_locator('inputs'),
+                level=Issue.BETWEEN_TYPES)
+            return False
+        return True
+
+    def _merge_import(self, presentation):
+        merge(self._raw, presentation._raw)
+        if hasattr(self._raw, '_locator') and hasattr(presentation._raw, '_locator'):
+            self._raw._locator.merge(presentation._raw._locator)
+
+    def _link_locators(self):
+        if hasattr(self._raw, '_locator'):
+            locator = self._raw._locator
+            delattr(self._raw, '_locator')
+            locator.link(self._raw)
+
+    @staticmethod
+    def _get_import_locations(context):
+        raise NotImplementedError
+
+    @staticmethod
+    def _get_deployment_template(context):
+        raise NotImplementedError

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/6a4dc43f/aria/parser/presentation/source.py
----------------------------------------------------------------------
diff --git a/aria/parser/presentation/source.py b/aria/parser/presentation/source.py
new file mode 100644
index 0000000..6f195d0
--- /dev/null
+++ b/aria/parser/presentation/source.py
@@ -0,0 +1,47 @@
+# 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 .exceptions import PresenterNotFoundError
+
+PRESENTER_CLASSES = []
+
+
+class PresenterSource(object):
+    """
+    Base class for ARIA presenter sources.
+
+    Presenter sources provide appropriate :class:`Presenter` classes for agnostic raw data.
+    """
+
+    def get_presenter(self, raw):  # pylint: disable=unused-argument,no-self-use
+        raise PresenterNotFoundError('presenter not found')
+
+
+class DefaultPresenterSource(PresenterSource):
+    """
+    The default ARIA presenter source supports TOSCA Simple Profile.
+    """
+
+    def __init__(self, classes=None):
+        if classes is None:
+            classes = PRESENTER_CLASSES
+        self.classes = classes
+
+    def get_presenter(self, raw):
+        for cls in self.classes:
+            if cls.can_present(raw):
+                return cls
+
+        return super(DefaultPresenterSource, self).get_presenter(raw)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/6a4dc43f/aria/parser/presentation/utils.py
----------------------------------------------------------------------
diff --git a/aria/parser/presentation/utils.py b/aria/parser/presentation/utils.py
new file mode 100644
index 0000000..2e4d873
--- /dev/null
+++ b/aria/parser/presentation/utils.py
@@ -0,0 +1,186 @@
+# 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 types import FunctionType
+
+from ..validation import Issue
+from ..utils import full_type_name, safe_repr
+from .null import NULL
+
+
+def get_locator(*values):
+    """
+    Gets the first available locator.
+
+    :rtype: :class:`aria.reading.Locator`
+    """
+
+    for v in values:
+        if hasattr(v, '_locator'):
+            locator = v._locator
+            if locator is not None:
+                return locator
+    return None
+
+
+def parse_types_dict_names(types_dict_names):
+    """
+    If the first element in the array is a function, extracts it out.
+    """
+
+    convert = None
+    if isinstance(types_dict_names[0], FunctionType):
+        convert = types_dict_names[0]
+        types_dict_names = types_dict_names[1:]
+    return types_dict_names, convert
+
+
+def validate_primitive(value, cls, coerce=False):
+    """
+    Checks if the value is of the primitive type, optionally attempting to coerce it
+    if it is not.
+
+    Raises a :code:`ValueError` if it isn't or if coercion failed.
+    """
+
+    if (cls is not None) and (value is not None) and (value is not NULL):
+        if (cls is unicode) or (cls is str): # These two types are interchangeable
+            valid = isinstance(value, basestring)
+        elif cls is int:
+            # In Python, a bool is an int
+            valid = isinstance(value, int) and not isinstance(value, bool)
+        else:
+            valid = isinstance(value, cls)
+        if not valid:
+            if coerce:
+                value = cls(value)
+            else:
+                raise ValueError('not a "%s": %s' % (full_type_name(cls), safe_repr(value)))
+    return value
+
+
+def validate_no_short_form(context, presentation):
+    """
+    Makes sure that we can use short form definitions only if we allowed it.
+    """
+
+    if not hasattr(presentation, 'SHORT_FORM_FIELD') and not isinstance(presentation._raw, dict):
+        context.validation.report('short form not allowed for field "%s"' % presentation._fullname,
+                                  locator=presentation._locator,
+                                  level=Issue.BETWEEN_FIELDS)
+
+
+def validate_no_unknown_fields(context, presentation):
+    """
+    Make sure that we can use unknown fields only if we allowed it.
+    """
+
+    if not getattr(presentation, 'ALLOW_UNKNOWN_FIELDS', False) \
+            and not context.validation.allow_unknown_fields \
+            and isinstance(presentation._raw, dict) \
+            and hasattr(presentation, 'FIELDS'):
+        for k in presentation._raw:
+            if k not in presentation.FIELDS:
+                context.validation.report('field "%s" is not supported in "%s"'
+                                          % (k, presentation._fullname),
+                                          locator=presentation._get_child_locator(k),
+                                          level=Issue.BETWEEN_FIELDS)
+
+
+def validate_known_fields(context, presentation):
+    """
+    Validates all known fields.
+    """
+
+    if hasattr(presentation, '_iter_fields'):
+        for _, field in presentation._iter_fields():
+            field.validate(presentation, context)
+
+
+def get_parent_presentation(context, presentation, *types_dict_names):
+    """
+    Returns the parent presentation according to the :code:`derived_from` field, or None if invalid.
+
+    Checks that we do not derive from ourselves and that we do not cause a circular hierarchy.
+
+    The arguments from the third onwards are used to locate a nested field under
+    :code:`service_template` under the root presenter. The first of these can optionally
+    be a function, in which case it will be called to convert type names. This can be used
+    to support shorthand type names, aliases, etc.
+    """
+
+    type_name = presentation.derived_from
+
+    if type_name is None:
+        return None
+
+    types_dict_names, convert = parse_types_dict_names(types_dict_names)
+    types_dict = context.presentation.get('service_template', *types_dict_names) or {}
+
+    if convert:
+        type_name = convert(context, type_name, types_dict)
+
+    # Make sure not derived from self
+    if type_name == presentation._name:
+        return None
+    # Make sure derived from type exists
+    elif type_name not in types_dict:
+        return None
+    else:
+        # Make sure derivation hierarchy is not circular
+        hierarchy = [presentation._name]
+        presentation_copy = presentation
+        while presentation_copy.derived_from is not None:
+            derived_from = presentation_copy.derived_from
+            if convert:
+                derived_from = convert(context, derived_from, types_dict)
+
+            if derived_from == presentation_copy._name or derived_from not in types_dict:
+                return None
+            presentation_copy = types_dict[derived_from]
+            if presentation_copy._name in hierarchy:
+                return None
+            hierarchy.append(presentation_copy._name)
+
+    return types_dict[type_name]
+
+
+def report_issue_for_unknown_type(context, presentation, type_name, field_name, value=None):
+    if value is None:
+        value = getattr(presentation, field_name)
+    context.validation.report('"%s" refers to an unknown %s in "%s": %s'
+                              % (field_name, type_name, presentation._fullname, safe_repr(value)),
+                              locator=presentation._get_child_locator(field_name),
+                              level=Issue.BETWEEN_TYPES)
+
+
+def report_issue_for_parent_is_self(context, presentation, field_name):
+    context.validation.report('parent type of "%s" is self' % presentation._fullname,
+                              locator=presentation._get_child_locator(field_name),
+                              level=Issue.BETWEEN_TYPES)
+
+
+def report_issue_for_unknown_parent_type(context, presentation, field_name):
+    context.validation.report('unknown parent type "%s" in "%s"'
+                              % (getattr(presentation, field_name), presentation._fullname),
+                              locator=presentation._get_child_locator(field_name),
+                              level=Issue.BETWEEN_TYPES)
+
+
+def report_issue_for_circular_type_hierarchy(context, presentation, field_name):
+    context.validation.report('"%s" of "%s" creates a circular type hierarchy'
+                              % (getattr(presentation, field_name), presentation._fullname),
+                              locator=presentation._get_child_locator(field_name),
+                              level=Issue.BETWEEN_TYPES)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/6a4dc43f/aria/parser/reading/__init__.py
----------------------------------------------------------------------
diff --git a/aria/parser/reading/__init__.py b/aria/parser/reading/__init__.py
new file mode 100644
index 0000000..32aa5b5
--- /dev/null
+++ b/aria/parser/reading/__init__.py
@@ -0,0 +1,39 @@
+# Licensed 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 .raw import RawReader
+from .reader import Reader
+from .yaml import YamlReader
+from .locator import Locator
+from .json import JsonReader
+from .jinja import JinjaReader
+from .context import ReadingContext
+from .source import ReaderSource, DefaultReaderSource
+from .exceptions import (ReaderException,
+                         ReaderNotFoundError,
+                         ReaderSyntaxError,
+                         AlreadyReadException)
+
+__all__ = (
+    'ReaderException',
+    'ReaderNotFoundError',
+    'ReaderSyntaxError',
+    'AlreadyReadException',
+    'Reader',
+    'ReaderSource',
+    'DefaultReaderSource',
+    'ReadingContext',
+    'RawReader',
+    'Locator',
+    'YamlReader',
+    'JsonReader',
+    'JinjaReader')

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/6a4dc43f/aria/parser/reading/context.py
----------------------------------------------------------------------
diff --git a/aria/parser/reading/context.py b/aria/parser/reading/context.py
new file mode 100644
index 0000000..81135dc
--- /dev/null
+++ b/aria/parser/reading/context.py
@@ -0,0 +1,29 @@
+# Licensed 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 .source import DefaultReaderSource
+from ..utils import LockedList
+
+
+class ReadingContext(object):
+    """
+    Properties:
+
+    * :code:`reader_source`: For finding reader instances
+    * :code:`reader`: Overrides :code:`reader_source` with a specific class
+    """
+
+    def __init__(self):
+        self.reader_source = DefaultReaderSource()
+        self.reader = None
+
+        self._locations = LockedList() # for keeping track of locations already read

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/6a4dc43f/aria/parser/reading/exceptions.py
----------------------------------------------------------------------
diff --git a/aria/parser/reading/exceptions.py b/aria/parser/reading/exceptions.py
new file mode 100644
index 0000000..bc2d2d5
--- /dev/null
+++ b/aria/parser/reading/exceptions.py
@@ -0,0 +1,44 @@
+# Licensed 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 .. import AriaException
+from ..validation import Issue
+
+
+class ReaderException(AriaException):
+    """
+    ARIA reader exception.
+    """
+
+
+class ReaderNotFoundError(ReaderException):
+    """
+    ARIA reader error: reader not found for source.
+    """
+
+
+class ReaderSyntaxError(ReaderException):
+    """
+    ARIA read format error.
+    """
+
+    def __init__(self, message, cause=None, cause_tb=None, location=None, line=None,
+                 column=None, locator=None, snippet=None, level=Issue.SYNTAX):
+        super(ReaderSyntaxError, self).__init__(message, cause, cause_tb)
+        self.issue = Issue(message, location=location, line=line, column=column,
+                           locator=locator, snippet=snippet, level=level)
+
+
+class AlreadyReadException(ReaderException):
+    """
+    ARIA reader exception: already read.
+    """

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/6a4dc43f/aria/parser/reading/jinja.py
----------------------------------------------------------------------
diff --git a/aria/parser/reading/jinja.py b/aria/parser/reading/jinja.py
new file mode 100644
index 0000000..e15e54e
--- /dev/null
+++ b/aria/parser/reading/jinja.py
@@ -0,0 +1,55 @@
+# Licensed 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.
+
+import os
+
+from jinja2 import Template
+
+from .. import VERSION
+from ..loading import LiteralLocation, LiteralLoader
+from .reader import Reader
+from .exceptions import ReaderSyntaxError
+
+
+# TODO: we could put a lot of other useful stuff here.
+CONTEXT = {
+    'ARIA_VERSION': VERSION,
+    'ENV': os.environ}
+
+
+class JinjaReader(Reader):
+    """
+    ARIA Jinja reader.
+
+    Forwards the rendered result to a new reader in the reader source.
+    """
+
+    def read(self):
+        data = self.load()
+        try:
+            data = str(data)
+            template = Template(data)
+            literal = template.render(CONTEXT)
+            # TODO: might be useful to write the literal result to a file for debugging
+            location = self.location
+            if isinstance(location, basestring) and location.endswith('.jinja'):
+                # Use reader based on the location with the ".jinja" prefix stripped off
+                location = location[:-6]
+                next_reader = self.context.reading.reader_source.get_reader(
+                    self.context, LiteralLocation(literal, name=location), LiteralLoader(literal))
+            else:
+                # Use reader for literal loader
+                next_reader = self.context.reading.reader_source.get_reader(
+                    self.context, LiteralLocation(literal), LiteralLoader(literal))
+            return next_reader.read()
+        except Exception as e:
+            raise ReaderSyntaxError('Jinja: %s' % e, cause=e)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/6a4dc43f/aria/parser/reading/json.py
----------------------------------------------------------------------
diff --git a/aria/parser/reading/json.py b/aria/parser/reading/json.py
new file mode 100644
index 0000000..4747651
--- /dev/null
+++ b/aria/parser/reading/json.py
@@ -0,0 +1,33 @@
+# Licensed 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 __future__ import absolute_import  # so we can import standard 'json'
+
+import json
+from collections import OrderedDict
+
+from .reader import Reader
+from .exceptions import ReaderSyntaxError
+
+
+class JsonReader(Reader):
+    """
+    ARIA JSON reader.
+    """
+
+    def read(self):
+        data = self.load()
+        try:
+            data = unicode(data)
+            return json.loads(data, object_pairs_hook=OrderedDict)
+        except Exception as e:
+            raise ReaderSyntaxError('JSON: %s' % e, cause=e)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/6a4dc43f/aria/parser/reading/locator.py
----------------------------------------------------------------------
diff --git a/aria/parser/reading/locator.py b/aria/parser/reading/locator.py
new file mode 100644
index 0000000..90b9e73
--- /dev/null
+++ b/aria/parser/reading/locator.py
@@ -0,0 +1,119 @@
+# Licensed 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 ..utils import puts, Colored, indent
+
+# We are inheriting the primitive types in order to add the ability to set
+# an attribute (_locator) on them.
+
+
+class LocatableString(unicode):
+    pass
+
+
+class LocatableInt(int):
+    pass
+
+
+class LocatableFloat(float):
+    pass
+
+
+def wrap(value):
+    if isinstance(value, basestring):
+        return True, LocatableString(value)
+    elif isinstance(value, int) and \
+            not isinstance(value, bool):  # Note: bool counts as int in Python!
+        return True, LocatableInt(value)
+    elif isinstance(value, float):
+        return True, LocatableFloat(value)
+    return False, value
+
+
+class Locator(object):
+    """
+    Stores location information (line and column numbers) for agnostic raw data.
+    """
+    def __init__(self, location, line, column, children=None):
+        self.location = location
+        self.line = line
+        self.column = column
+        self.children = children
+
+    def get_child(self, *names):
+        if (not names) or (not isinstance(self.children, dict)):
+            return self
+        name = names[0]
+        if name not in self.children:
+            return self
+        child = self.children[name]
+        return child.get_child(names[1:])
+
+    def link(self, raw, path=None):
+        if hasattr(raw, '_locator'):
+            # This can happen when we use anchors
+            return
+
+        try:
+            setattr(raw, '_locator', self)
+        except AttributeError:
+            return
+
+        if isinstance(raw, list):
+            for i, raw_element in enumerate(raw):
+                wrapped, raw_element = wrap(raw_element)
+                if wrapped:
+                    raw[i] = raw_element
+                child_path = '%s.%d' % (path, i) if path else str(i)
+                try:
+                    self.children[i].link(raw_element, child_path)
+                except KeyError:
+                    raise ValueError('location map does not match agnostic raw data: %s' %
+                                     child_path)
+        elif isinstance(raw, dict):
+            for k, raw_element in raw.iteritems():
+                wrapped, raw_element = wrap(raw_element)
+                if wrapped:
+                    raw[k] = raw_element
+                child_path = '%s.%s' % (path, k) if path else k
+                try:
+                    self.children[k].link(raw_element, child_path)
+                except KeyError:
+                    raise ValueError('location map does not match agnostic raw data: %s' %
+                                     child_path)
+
+    def merge(self, locator):
+        if isinstance(self.children, dict) and isinstance(locator.children, dict):
+            for k, loc in locator.children.iteritems():
+                if k in self.children:
+                    self.children[k].merge(loc)
+                else:
+                    self.children[k] = loc
+
+    def dump(self, key=None):
+        if key:
+            puts('%s "%s":%d:%d' %
+                 (Colored.red(key), Colored.blue(self.location), self.line, self.column))
+        else:
+            puts('"%s":%d:%d' % (Colored.blue(self.location), self.line, self.column))
+        if isinstance(self.children, list):
+            with indent(2):
+                for loc in self.children:
+                    loc.dump()
+        elif isinstance(self.children, dict):
+            with indent(2):
+                for k, loc in self.children.iteritems():
+                    loc.dump(k)
+
+    def __str__(self):
+        # Should be in same format as Issue.locator_as_str
+        return '"%s":%d:%d' % (self.location, self.line, self.column)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/6a4dc43f/aria/parser/reading/raw.py
----------------------------------------------------------------------
diff --git a/aria/parser/reading/raw.py b/aria/parser/reading/raw.py
new file mode 100644
index 0000000..ed980ac
--- /dev/null
+++ b/aria/parser/reading/raw.py
@@ -0,0 +1,24 @@
+# Licensed 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 .reader import Reader
+
+
+class RawReader(Reader):
+    """
+    ARIA raw reader.
+
+    Expects to receive agnostic raw data from the loader, and so does nothing to it.
+    """
+
+    def read(self):
+        return self.load()

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/6a4dc43f/aria/parser/reading/reader.py
----------------------------------------------------------------------
diff --git a/aria/parser/reading/reader.py b/aria/parser/reading/reader.py
new file mode 100644
index 0000000..3a50739
--- /dev/null
+++ b/aria/parser/reading/reader.py
@@ -0,0 +1,44 @@
+# Licensed 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 ..utils import OpenClose
+from .exceptions import ReaderException, AlreadyReadException
+
+
+class Reader(object):
+    """
+    Base class for ARIA readers.
+
+    Readers provide agnostic raw data by consuming :class:`aria.loading.Loader` instances.
+    """
+
+    def __init__(self, context, location, loader):
+        self.context = context
+        self.location = location
+        self.loader = loader
+
+    def load(self):
+        with OpenClose(self.loader) as loader:
+            if self.context is not None:
+                with self.context._locations:
+                    for location in self.context._locations:
+                        if location.is_equivalent(loader.location):
+                            raise AlreadyReadException('already read: %s' % loader.location)
+                    self.context._locations.append(loader.location)
+
+            data = loader.load()
+            if data is None:
+                raise ReaderException('loader did not provide data: %s' % loader)
+            return data
+
+    def read(self):
+        raise NotImplementedError

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/6a4dc43f/aria/parser/reading/source.py
----------------------------------------------------------------------
diff --git a/aria/parser/reading/source.py b/aria/parser/reading/source.py
new file mode 100644
index 0000000..6fff2f6
--- /dev/null
+++ b/aria/parser/reading/source.py
@@ -0,0 +1,59 @@
+# Licensed 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 ..loading import LiteralLocation, UriLocation
+from .yaml import YamlReader
+from .json import JsonReader
+from .jinja import JinjaReader
+from .exceptions import ReaderNotFoundError
+
+
+EXTENSIONS = {
+    '.yaml': YamlReader,
+    '.json': JsonReader,
+    '.jinja': JinjaReader}
+
+
+class ReaderSource(object):
+    """
+    Base class for ARIA reader sources.
+
+    Reader sources provide appropriate :class:`Reader` instances for locations.
+    """
+
+    @staticmethod
+    def get_reader(context, location, loader):  # pylint: disable=unused-argument
+        raise ReaderNotFoundError('location: %s' % location)
+
+
+class DefaultReaderSource(ReaderSource):
+    """
+    The default ARIA reader source will generate a :class:`YamlReader` for
+    locations that end in ".yaml", a :class:`JsonReader` for locations that
+    end in ".json",  and a :class:`JinjaReader` for locations that end in
+    ".jinja".
+    """
+
+    def __init__(self, literal_reader_class=YamlReader):
+        super(DefaultReaderSource, self).__init__()
+        self.literal_reader_class = literal_reader_class
+
+    def get_reader(self, context, location, loader):
+        if isinstance(location, LiteralLocation):
+            return self.literal_reader_class(context, location, loader)
+
+        elif isinstance(location, UriLocation):
+            for extension, reader_class in EXTENSIONS.iteritems():
+                if location.uri.endswith(extension):
+                    return reader_class(context, location, loader)
+
+        return super(DefaultReaderSource, self).get_reader(context, location, loader)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/6a4dc43f/aria/parser/reading/yaml.py
----------------------------------------------------------------------
diff --git a/aria/parser/reading/yaml.py b/aria/parser/reading/yaml.py
new file mode 100644
index 0000000..55bcbd6
--- /dev/null
+++ b/aria/parser/reading/yaml.py
@@ -0,0 +1,114 @@
+# Licensed 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
+
+from ruamel import yaml # @UnresolvedImport
+
+from .reader import Reader
+from .locator import Locator
+from .exceptions import ReaderSyntaxError
+from .locator import LocatableString, LocatableInt, LocatableFloat
+
+# Add our types to ruamel.yaml
+yaml.representer.RoundTripRepresenter.add_representer(
+    LocatableString, yaml.representer.RoundTripRepresenter.represent_unicode)
+yaml.representer.RoundTripRepresenter.add_representer(
+    LocatableInt, yaml.representer.RoundTripRepresenter.represent_int)
+yaml.representer.RoundTripRepresenter.add_representer(
+    LocatableFloat, yaml.representer.RoundTripRepresenter.represent_float)
+
+MERGE_TAG = u'tag:yaml.org,2002:merge'
+MAP_TAG = u'tag:yaml.org,2002:map'
+
+
+class YamlLocator(Locator):
+    """
+    Map for agnostic raw data read from YAML.
+    """
+
+    def add_children(self, node):
+        if isinstance(node, yaml.SequenceNode):
+            self.children = []
+            for child_node in node.value:
+                self.add_child(child_node)
+        elif isinstance(node, yaml.MappingNode):
+            self.children = {}
+            for k, child_node in node.value:
+                self.add_child(child_node, k)
+
+    def add_child(self, node, key=None):
+        locator = YamlLocator(self.location, node.start_mark.line + 1, node.start_mark.column + 1)
+        if key is not None:
+            # Dict
+            if key.tag == MERGE_TAG:
+                for merge_key, merge_node in node.value:
+                    self.add_child(merge_node, merge_key)
+            else:
+                self.children[key.value] = locator
+        else:
+            # List
+            self.children.append(locator)
+        locator.add_children(node)
+
+
+def construct_yaml_map(self, node):
+    data = OrderedDict()
+    yield data
+    value = self.construct_mapping(node)
+    data.update(value)
+
+
+yaml.constructor.SafeConstructor.add_constructor(MAP_TAG, construct_yaml_map)
+
+
+class YamlReader(Reader):
+    """
+    ARIA YAML reader.
+    """
+
+    def read(self):
+        data = self.load()
+        try:
+            data = unicode(data)
+            # see issue here:
+            # https://bitbucket.org/ruamel/yaml/issues/61/roundtriploader-causes-exceptions-with
+            #yaml_loader = yaml.RoundTripLoader(data)
+            yaml_loader = yaml.SafeLoader(data)
+            try:
+                node = yaml_loader.get_single_node()
+                locator = YamlLocator(self.loader.location, 0, 0)
+                if node is not None:
+                    locator.add_children(node)
+                    raw = yaml_loader.construct_document(node)
+                else:
+                    raw = OrderedDict()
+                #locator.dump()
+                setattr(raw, '_locator', locator)
+                return raw
+            finally:
+                yaml_loader.dispose()
+        except yaml.parser.MarkedYAMLError as e:
+            context = e.context or 'while parsing'
+            problem = e.problem
+            line = e.problem_mark.line
+            column = e.problem_mark.column
+            snippet = e.problem_mark.get_snippet()
+            raise ReaderSyntaxError('YAML %s: %s %s' %
+                                    (e.__class__.__name__, problem, context),
+                                    location=self.loader.location,
+                                    line=line,
+                                    column=column,
+                                    snippet=snippet,
+                                    cause=e)
+        except Exception as e:
+            raise ReaderSyntaxError('YAML: %s' % e, cause=e)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/6a4dc43f/aria/parser/specification.py
----------------------------------------------------------------------
diff --git a/aria/parser/specification.py b/aria/parser/specification.py
new file mode 100644
index 0000000..7a269f5
--- /dev/null
+++ b/aria/parser/specification.py
@@ -0,0 +1,79 @@
+# 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
+
+from .utils import full_type_name
+
+DSL_SPECIFICATION = {}
+DSL_SPECIFICATION_PACKAGES = []
+
+URL = {
+    'tosca-simple-profile-1.0': 'http://docs.oasis-open.org/tosca/TOSCA-Simple-Profile-YAML/v1.0/'
+                                'csprd02/TOSCA-Simple-Profile-YAML-v1.0-csprd02.html',
+    'tosca-simple-nfv-1.0': 'http://docs.oasis-open.org/tosca/tosca-nfv/v1.0/tosca-nfv-v1.0.html'}
+
+
+def dsl_specification(section, spec):
+    """
+    Decorator for TOSCA specification.
+
+    Used for documentation and standards compliance.
+    """
+
+    def decorator(obj):
+        specification = DSL_SPECIFICATION.get(spec)
+        if specification is None:
+            specification = {}
+            DSL_SPECIFICATION[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))
+
+        url = URL.get(spec)
+        if url:
+            doc = obj.__doc__
+            if doc is not None:
+                url_start = doc.find(url)
+                if url_start != -1:
+                    url_end = doc.find('>', url_start + len(url))
+                    if url_end != -1:
+                        url = doc[url_start:url_end]
+
+        specification[section] = OrderedDict((
+            ('code', full_type_name(obj)),
+            ('url', url)))
+        try:
+            setattr(obj, DSL_SPECIFICATION, {section: section, spec: spec})
+        except BaseException:
+            pass
+        return obj
+    return decorator
+
+
+def iter_spec(spec):
+    sections = DSL_SPECIFICATION[spec]
+    keys = sections.keys()
+    def key(value):
+        try:
+            parts = value.split('-', 1)
+            first = (int(v) for v in parts[0].split('.'))
+            second = parts[1] if len(parts) > 1 else None
+            return (first, second)
+        except ValueError:
+            return value
+    keys.sort(key=key)
+    for key in keys:
+        yield key, sections[key]

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/6a4dc43f/aria/parser/tools/__init__.py
----------------------------------------------------------------------
diff --git a/aria/parser/tools/__init__.py b/aria/parser/tools/__init__.py
new file mode 100644
index 0000000..ae1e83e
--- /dev/null
+++ b/aria/parser/tools/__init__.py
@@ -0,0 +1,14 @@
+# 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.

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/6a4dc43f/aria/parser/tools/cli.py
----------------------------------------------------------------------
diff --git a/aria/parser/tools/cli.py b/aria/parser/tools/cli.py
new file mode 100644
index 0000000..a274e49
--- /dev/null
+++ b/aria/parser/tools/cli.py
@@ -0,0 +1,69 @@
+# 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 aria.parser import install_aria_extensions
+from aria.parser.utils import (print_exception, import_fullname)
+from aria.parser.tools.utils import (CommonArgumentParser, create_context_from_namespace)
+from aria.parser.consumption import (ConsumerChain, Read, Validate, Model, Types, Inputs, Instance)
+
+class ArgumentParser(CommonArgumentParser):
+    def __init__(self):
+        super(ArgumentParser, self).__init__(description='CLI', prog='aria')
+        self.add_argument('uri', help='URI or file path to profile')
+        self.add_argument('consumer',
+                          nargs='?',
+                          default='instance',
+                          help='consumer class name (full class path or short name)')
+
+def main():
+    try:
+
+        args, unknown_args = ArgumentParser().parse_known_args()
+
+        install_aria_extensions()
+
+        context = create_context_from_namespace(args)
+        context.args = unknown_args
+
+        consumer = ConsumerChain(context, (Read, Validate))
+
+        consumer_class_name = args.consumer
+        dumper = None
+        if consumer_class_name == 'presentation':
+            dumper = consumer.consumers[0]
+        elif consumer_class_name == 'model':
+            consumer.append(Model)
+        elif consumer_class_name == 'types':
+            consumer.append(Model, Types)
+        elif consumer_class_name == 'instance':
+            consumer.append(Model, Inputs, Instance)
+        else:
+            consumer.append(Model, Inputs, Instance)
+            consumer.append(import_fullname(consumer_class_name))
+
+        if dumper is None:
+            # Default to last consumer
+            dumper = consumer.consumers[-1]
+
+        consumer.consume()
+
+        if not context.validation.dump_issues():
+            dumper.dump()
+
+    except Exception as e:
+        print_exception(e)
+
+if __name__ == '__main__':
+    main()


Mime
View raw message