ariatosca-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From r..@apache.org
Subject [11/11] incubator-ariatosca git commit: added parser, tosca extension, updated setup.py and requirements
Date Tue, 15 Nov 2016 00:12:08 GMT
added parser, tosca extension, updated setup.py and requirements


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

Branch: refs/heads/ARIA-18-migrate-tosca-parser
Commit: a31f3d86bb87b1aec9096516ca103568fbf19cd7
Parents: 2493ebb
Author: Ran Ziv <ran@gigaspaces.com>
Authored: Tue Nov 15 02:11:47 2016 +0200
Committer: Ran Ziv <ran@gigaspaces.com>
Committed: Tue Nov 15 02:11:47 2016 +0200

----------------------------------------------------------------------
 aria/parser/__init__.py                         |   61 +
 aria/parser/consumption/__init__.py             |   37 +
 aria/parser/consumption/consumer.py             |   86 ++
 aria/parser/consumption/context.py              |   99 ++
 aria/parser/consumption/exceptions.py           |   23 +
 aria/parser/consumption/inputs.py               |   53 +
 aria/parser/consumption/modeling.py             |  160 +++
 aria/parser/consumption/presentation.py         |  134 ++
 aria/parser/consumption/style.py                |   49 +
 aria/parser/consumption/validation.py           |   30 +
 aria/parser/exceptions.py                       |   47 +
 aria/parser/loading/__init__.py                 |   46 +
 aria/parser/loading/context.py                  |   31 +
 aria/parser/loading/exceptions.py               |   35 +
 aria/parser/loading/file.py                     |   64 +
 aria/parser/loading/literal.py                  |   31 +
 aria/parser/loading/loader.py                   |   34 +
 aria/parser/loading/location.py                 |   82 ++
 aria/parser/loading/request.py                  |   83 ++
 aria/parser/loading/source.py                   |   44 +
 aria/parser/loading/uri.py                      |   95 ++
 aria/parser/modeling/__init__.py                |   68 +
 aria/parser/modeling/context.py                 |  145 +++
 aria/parser/modeling/elements.py                |  129 ++
 aria/parser/modeling/exceptions.py              |   22 +
 aria/parser/modeling/instance_elements.py       | 1042 +++++++++++++++
 aria/parser/modeling/model_elements.py          | 1223 ++++++++++++++++++
 aria/parser/modeling/types.py                   |  134 ++
 aria/parser/modeling/utils.py                   |  146 +++
 aria/parser/presentation/__init__.py            |   79 ++
 aria/parser/presentation/context.py             |   57 +
 aria/parser/presentation/exceptions.py          |   29 +
 aria/parser/presentation/field_validators.py    |  165 +++
 aria/parser/presentation/fields.py              |  754 +++++++++++
 aria/parser/presentation/null.py                |   67 +
 aria/parser/presentation/presentation.py        |  235 ++++
 aria/parser/presentation/presenter.py           |   69 +
 aria/parser/presentation/source.py              |   47 +
 aria/parser/presentation/utils.py               |  186 +++
 aria/parser/reading/__init__.py                 |   39 +
 aria/parser/reading/context.py                  |   29 +
 aria/parser/reading/exceptions.py               |   44 +
 aria/parser/reading/jinja.py                    |   55 +
 aria/parser/reading/json.py                     |   33 +
 aria/parser/reading/locator.py                  |  119 ++
 aria/parser/reading/raw.py                      |   24 +
 aria/parser/reading/reader.py                   |   44 +
 aria/parser/reading/source.py                   |   59 +
 aria/parser/reading/yaml.py                     |  114 ++
 aria/parser/specification.py                    |   79 ++
 aria/parser/tools/__init__.py                   |   14 +
 aria/parser/tools/cli.py                        |   69 +
 aria/parser/tools/rest.py                       |  262 ++++
 aria/parser/tools/spec.py                       |   60 +
 aria/parser/tools/utils.py                      |   73 ++
 aria/parser/tools/web/index.html                |    8 +
 aria/parser/utils/__init__.py                   |   81 ++
 aria/parser/utils/argparse.py                   |  113 ++
 aria/parser/utils/caching.py                    |  132 ++
 aria/parser/utils/collections.py                |  283 ++++
 aria/parser/utils/console.py                    |   60 +
 aria/parser/utils/daemon.py                     |   70 +
 aria/parser/utils/exceptions.py                 |   64 +
 aria/parser/utils/formatting.py                 |  205 +++
 aria/parser/utils/imports.py                    |   51 +
 aria/parser/utils/openclose.py                  |   32 +
 aria/parser/utils/rest_client.py                |   59 +
 aria/parser/utils/rest_server.py                |  253 ++++
 aria/parser/utils/threading.py                  |  252 ++++
 aria/parser/utils/uris.py                       |   28 +
 aria/parser/validation/__init__.py              |   21 +
 aria/parser/validation/context.py               |   79 ++
 aria/parser/validation/issue.py                 |  125 ++
 extensions/aria_extension_tosca/__init__.py     |   46 +
 .../profiles/tosca-simple-1.0/artifacts.yaml    |  121 ++
 .../profiles/tosca-simple-1.0/capabilities.yaml |  319 +++++
 .../profiles/tosca-simple-1.0/data.yaml         |  268 ++++
 .../profiles/tosca-simple-1.0/groups.yaml       |   28 +
 .../profiles/tosca-simple-1.0/interfaces.yaml   |   86 ++
 .../profiles/tosca-simple-1.0/nodes.yaml        |  523 ++++++++
 .../profiles/tosca-simple-1.0/policies.yaml     |   71 +
 .../tosca-simple-1.0/relationships.yaml         |  158 +++
 .../tosca-simple-1.0/tosca-simple-1.0.yaml      |   24 +
 .../tosca-simple-nfv-1.0/capabilities.yaml      |  108 ++
 .../profiles/tosca-simple-nfv-1.0/data.yaml     |   91 ++
 .../profiles/tosca-simple-nfv-1.0/groups.yaml   |   56 +
 .../profiles/tosca-simple-nfv-1.0/nodes.yaml    |  183 +++
 .../tosca-simple-nfv-1.0/relationships.yaml     |   64 +
 .../tosca-simple-nfv-1.0.yaml                   |   21 +
 .../simple_nfv_v1_0/__init__.py                 |   19 +
 .../simple_nfv_v1_0/presenter.py                |   44 +
 .../simple_v1_0/__init__.py                     |  100 ++
 .../simple_v1_0/assignments.py                  |  414 ++++++
 .../simple_v1_0/data_types.py                   |  536 ++++++++
 .../simple_v1_0/definitions.py                  |  503 +++++++
 .../aria_extension_tosca/simple_v1_0/filters.py |  101 ++
 .../simple_v1_0/functions.py                    |  527 ++++++++
 .../aria_extension_tosca/simple_v1_0/misc.py    |  409 ++++++
 .../simple_v1_0/modeling/__init__.py            |  497 +++++++
 .../simple_v1_0/modeling/artifacts.py           |   41 +
 .../simple_v1_0/modeling/capabilities.py        |  177 +++
 .../simple_v1_0/modeling/copy.py                |   32 +
 .../simple_v1_0/modeling/data_types.py          |  497 +++++++
 .../simple_v1_0/modeling/interfaces.py          |  504 ++++++++
 .../simple_v1_0/modeling/policies.py            |   79 ++
 .../simple_v1_0/modeling/properties.py          |  201 +++
 .../simple_v1_0/modeling/requirements.py        |  358 +++++
 .../modeling/substitution_mappings.py           |  126 ++
 .../simple_v1_0/presentation/__init__.py        |   14 +
 .../simple_v1_0/presentation/extensible.py      |   32 +
 .../simple_v1_0/presentation/field_getters.py   |   36 +
 .../presentation/field_validators.py            |  567 ++++++++
 .../simple_v1_0/presentation/types.py           |   57 +
 .../simple_v1_0/presenter.py                    |   78 ++
 .../simple_v1_0/templates.py                    |  725 +++++++++++
 .../aria_extension_tosca/simple_v1_0/types.py   |  859 ++++++++++++
 requirements.txt                                |    6 +
 setup.py                                        |   30 +-
 118 files changed, 18687 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a31f3d86/aria/parser/__init__.py
----------------------------------------------------------------------
diff --git a/aria/parser/__init__.py b/aria/parser/__init__.py
new file mode 100644
index 0000000..d479082
--- /dev/null
+++ b/aria/parser/__init__.py
@@ -0,0 +1,61 @@
+# 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 sys
+import pkgutil
+
+from .exceptions import AriaException, InvalidValueError
+from .specification import (DSL_SPECIFICATION, DSL_SPECIFICATION_PACKAGES, dsl_specification,
+                            iter_spec)
+
+VERSION = '0.1'
+
+
+def install_aria_extensions():
+    """
+    Iterates all Python packages with names beginning with :code:`aria_extension_` and calls
+    their :code:`install_aria_extension` function if they have it.
+    """
+
+    for loader, module_name, _ in pkgutil.iter_modules():
+        if module_name.startswith('aria_extension_'):
+            module = loader.find_module(module_name).load_module(module_name)
+
+            if hasattr(module, 'install_aria_extension'):
+                module.install_aria_extension()
+
+            # Loading the module has contaminated sys.modules, so we'll clean it up
+            del sys.modules[module_name]
+
+MODULES = (
+    'consumption',
+    'loading',
+    'modeling',
+    'presentation',
+    'reading',
+    'tools',
+    'utils',
+    'validation')
+
+__all__ = (
+    'MODULES',
+    'VERSION',
+    'install_aria_extensions',
+    'AriaException',
+    'InvalidValueError',
+    'DSL_SPECIFICATION',
+    'DSL_SPECIFICATION_PACKAGES',
+    'dsl_specification',
+    'iter_spec')

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a31f3d86/aria/parser/consumption/__init__.py
----------------------------------------------------------------------
diff --git a/aria/parser/consumption/__init__.py b/aria/parser/consumption/__init__.py
new file mode 100644
index 0000000..7b7590e
--- /dev/null
+++ b/aria/parser/consumption/__init__.py
@@ -0,0 +1,37 @@
+# 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 ConsumerException
+from .context import ConsumptionContext
+from .style import Style
+from .consumer import Consumer, ConsumerChain
+from .presentation import Read
+from .validation import Validate
+from .modeling import Model, Types, Instance
+from .inputs import Inputs
+
+__all__ = (
+    'ConsumerException',
+    'ConsumptionContext',
+    'Style',
+    'Consumer',
+    'ConsumerChain',
+    'Read',
+    'Validate',
+    'Model',
+    'Types',
+    'Instance',
+    'Inputs')

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a31f3d86/aria/parser/consumption/consumer.py
----------------------------------------------------------------------
diff --git a/aria/parser/consumption/consumer.py b/aria/parser/consumption/consumer.py
new file mode 100644
index 0000000..847d015
--- /dev/null
+++ b/aria/parser/consumption/consumer.py
@@ -0,0 +1,86 @@
+# 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 .. import AriaException
+from ..validation import Issue
+from ..utils import print_exception
+
+
+class Consumer(object):
+    """
+    Base class for ARIA consumers.
+
+    Consumers provide useful functionality by consuming presentations.
+    """
+
+    def __init__(self, context):
+        self.context = context
+
+    def consume(self):
+        pass
+
+    def dump(self):
+        pass
+
+    def _handle_exception(self, e):
+        if hasattr(e, 'issue') and isinstance(e.issue, Issue):
+            self.context.validation.report(issue=e.issue)
+        else:
+            self.context.validation.report(exception=e)
+        if not isinstance(e, AriaException):
+            print_exception(e)
+
+
+class ConsumerChain(Consumer):
+    """
+    ARIA consumer chain.
+
+    Calls consumers in order, handling exception by calling `_handle_exception` on them,
+    and stops the chain if there are any validation issues.
+    """
+
+    def __init__(self, context, consumer_classes=None, handle_exceptions=True):
+        super(ConsumerChain, self).__init__(context)
+        self.handle_exceptions = handle_exceptions
+        self.consumers = []
+        if consumer_classes:
+            for consumer_class in consumer_classes:
+                self.append(consumer_class)
+
+    def append(self, *consumer_classes):
+        for consumer_class in consumer_classes:
+            self.consumers.append(consumer_class(self.context))
+
+    def consume(self):
+        for consumer in self.consumers:
+            try:
+                consumer.consume()
+            except BaseException as e:
+                if self.handle_exceptions:
+                    handle_exception(consumer, e)
+                else:
+                    raise e
+            if self.context.validation.has_issues:
+                break
+
+
+def handle_exception(consumer, e):
+    if isinstance(e, AriaException) and e.issue:
+        consumer.context.validation.report(issue=e.issue)
+    else:
+        consumer.context.validation.report(exception=e)
+    if not isinstance(e, AriaException):
+        print_exception(e)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a31f3d86/aria/parser/consumption/context.py
----------------------------------------------------------------------
diff --git a/aria/parser/consumption/context.py b/aria/parser/consumption/context.py
new file mode 100644
index 0000000..8fb9bb6
--- /dev/null
+++ b/aria/parser/consumption/context.py
@@ -0,0 +1,99 @@
+# 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 sys
+import threading
+
+from ..validation import ValidationContext
+from ..loading import LoadingContext
+from ..reading import ReadingContext
+from ..presentation import PresentationContext
+from ..modeling import ModelingContext
+from .style import Style
+
+
+class ConsumptionContext(object):
+    """
+    Properties:
+
+    * :code:`args`: The runtime arguments (usually provided on the command line)
+    * :code:`out`: Message output stream (defaults to stdout)
+    * :code:`style`: Message output style
+    * :code:`validation`: :class:`aria.validation.ValidationContext`
+    * :code:`loading`: :class:`aria.loading.LoadingContext`
+    * :code:`reading`: :class:`aria.reading.ReadingContext`
+    * :code:`presentation`: :class:`aria.presentation.PresentationContext`
+    * :code:`modeling`: :class:`aria.service.ModelingContext`
+    """
+
+    @staticmethod
+    def get_thread_local():
+        """
+        Gets the context attached to the current thread if there is one.
+        """
+
+        thread_locals = threading.local()
+        return getattr(thread_locals, 'aria_consumption_context', None)
+
+    def __init__(self, set_thread_local=True):
+        self.args = []
+        self.out = sys.stdout
+        self.style = Style()
+        self.validation = ValidationContext()
+        self.loading = LoadingContext()
+        self.reading = ReadingContext()
+        self.presentation = PresentationContext()
+        self.modeling = ModelingContext()
+
+        if set_thread_local:
+            self.set_thread_local()
+
+    def set_thread_local(self):
+        """
+        Attaches this context to the current thread.
+        """
+
+        thread_locals = threading.local()
+        thread_locals.aria_consumption_context = self
+
+    def write(self, string):
+        """
+        Writes to our :code:`out`, making sure to encode UTF-8 if required.
+        """
+
+        try:
+            self.out.write(string)
+        except UnicodeEncodeError:
+            self.out.write(string.encode('utf8'))
+
+    def has_arg_switch(self, name):
+        name = '--%s' % name
+        return name in self.args
+
+    def get_arg_value(self, name, default=None):
+        name = '--%s=' % name
+        for arg in self.args:
+            if arg.startswith(name):
+                return arg[len(name):]
+        return default
+
+    def get_arg_value_int(self, name, default=None):
+        value = self.get_arg_value(name)
+        if value is not None:
+            try:
+                return int(value)
+            except (TypeError, ValueError):
+                pass
+        return default

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a31f3d86/aria/parser/consumption/exceptions.py
----------------------------------------------------------------------
diff --git a/aria/parser/consumption/exceptions.py b/aria/parser/consumption/exceptions.py
new file mode 100644
index 0000000..564e71f
--- /dev/null
+++ b/aria/parser/consumption/exceptions.py
@@ -0,0 +1,23 @@
+# 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 .. import AriaException
+
+
+class ConsumerException(AriaException):
+    """
+    ARIA consumer exception.
+    """

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a31f3d86/aria/parser/consumption/inputs.py
----------------------------------------------------------------------
diff --git a/aria/parser/consumption/inputs.py b/aria/parser/consumption/inputs.py
new file mode 100644
index 0000000..3711001
--- /dev/null
+++ b/aria/parser/consumption/inputs.py
@@ -0,0 +1,53 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from ..loading import UriLocation, LiteralLocation
+from ..reading import JsonReader
+from ..utils import safe_repr
+from .consumer import Consumer
+
+
+class Inputs(Consumer):
+    """
+    Fills in the inputs if provided as arguments.
+    """
+
+    def consume(self):
+        inputs = self.context.get_arg_value('inputs')
+        if inputs is None:
+            return
+
+        if inputs.endswith('.json') or inputs.endswith('.yaml'):
+            location = UriLocation(inputs)
+        else:
+            location = LiteralLocation(inputs)
+
+        loader = self.context.loading.loader_source.get_loader(self.context.loading, location, None)
+
+        if isinstance(location, LiteralLocation):
+            reader = JsonReader(self.context.reading, location, loader)
+        else:
+            reader = self.context.reading.reader_source.get_reader(self.context.reading,
+                                                                   location, loader)
+
+        inputs = reader.read()
+
+        if not isinstance(inputs, dict):
+            self.context.validation.report(
+                'Inputs consumer: inputs are not a dict: %s' % safe_repr(inputs))
+            return
+
+        for name, value in inputs.iteritems():
+            self.context.modeling.set_input(name, value)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a31f3d86/aria/parser/consumption/modeling.py
----------------------------------------------------------------------
diff --git a/aria/parser/consumption/modeling.py b/aria/parser/consumption/modeling.py
new file mode 100644
index 0000000..8f8ee53
--- /dev/null
+++ b/aria/parser/consumption/modeling.py
@@ -0,0 +1,160 @@
+# 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 json_dumps, yaml_dumps
+from .consumer import Consumer, ConsumerChain
+
+
+class Derive(Consumer):
+    """
+    Derives the service model.
+    """
+
+    def consume(self):
+        if self.context.presentation.presenter is None:
+            self.context.validation.report('Derive consumer: missing presenter')
+            return
+
+        if not hasattr(self.context.presentation.presenter, '_get_service_model'):
+            self.context.validation.report('Derive consumer: presenter does not support '
+                                           '"_get_service_model"')
+            return
+
+        self.context.modeling.model = \
+            self.context.presentation.presenter._get_service_model(self.context)
+
+
+class CoerceModelValues(Consumer):
+    """
+    Coerces values in the service model.
+    """
+
+    def consume(self):
+        self.context.modeling.model.coerce_values(self.context, None, True)
+
+
+class ValidateModel(Consumer):
+    """
+    Validates the service model.
+    """
+
+    def consume(self):
+        self.context.modeling.model.validate(self.context)
+
+class Model(ConsumerChain):
+    """
+    Generates the service model by deriving it from the presentation.
+    """
+
+    def __init__(self, context):
+        super(Model, self).__init__(context, (Derive, CoerceModelValues, ValidateModel))
+
+    def dump(self):
+        if self.context.has_arg_switch('yaml'):
+            indent = self.context.get_arg_value_int('indent', 2)
+            raw = self.context.modeling.model_as_raw
+            self.context.write(yaml_dumps(raw, indent=indent))
+        elif self.context.has_arg_switch('json'):
+            indent = self.context.get_arg_value_int('indent', 2)
+            raw = self.context.modeling.model_as_raw
+            self.context.write(json_dumps(raw, indent=indent))
+        else:
+            self.context.modeling.model.dump(self.context)
+
+class Types(Consumer):
+    """
+    Used to just dump the types.
+    """
+
+    def dump(self):
+        if self.context.has_arg_switch('yaml'):
+            indent = self.context.get_arg_value_int('indent', 2)
+            raw = self.context.modeling.types_as_raw
+            self.context.write(yaml_dumps(raw, indent=indent))
+        elif self.context.has_arg_switch('json'):
+            indent = self.context.get_arg_value_int('indent', 2)
+            raw = self.context.modeling.types_as_raw
+            self.context.write(json_dumps(raw, indent=indent))
+        else:
+            self.context.modeling.dump_types(self.context)
+
+class Instantiate(Consumer):
+    """
+    Instantiates the service model.
+    """
+
+    def consume(self):
+        if self.context.modeling.model is None:
+            self.context.validation.report('Instantiate consumer: missing service model')
+            return
+
+        self.context.modeling.model.instantiate(self.context, None)
+
+class CoerceInstanceValues(Consumer):
+    """
+    Coerces values in the service instance.
+    """
+
+    def consume(self):
+        self.context.modeling.instance.coerce_values(self.context, None, True)
+
+class ValidateInstance(Consumer):
+    """
+    Validates the service instance.
+    """
+
+    def consume(self):
+        self.context.modeling.instance.validate(self.context)
+
+class SatisfyRequirements(Consumer):
+    """
+    Satisfies node requirements in the service instance.
+    """
+
+    def consume(self):
+        self.context.modeling.instance.satisfy_requirements(self.context)
+
+class ValidateCapabilities(Consumer):
+    """
+    Validates capabilities in the service instance.
+    """
+
+    def consume(self):
+        self.context.modeling.instance.validate_capabilities(self.context)
+
+class Instance(ConsumerChain):
+    """
+    Generates the service instance by instantiating the service model.
+    """
+
+    def __init__(self, context):
+        super(Instance, self).__init__(context, (Instantiate, CoerceInstanceValues,
+                                                 ValidateInstance, CoerceInstanceValues,
+                                                 SatisfyRequirements, CoerceInstanceValues,
+                                                 ValidateCapabilities, CoerceInstanceValues))
+
+    def dump(self):
+        if self.context.has_arg_switch('graph'):
+            self.context.modeling.instance.dump_graph(self.context)
+        elif self.context.has_arg_switch('yaml'):
+            indent = self.context.get_arg_value_int('indent', 2)
+            raw = self.context.modeling.instance_as_raw
+            self.context.write(yaml_dumps(raw, indent=indent))
+        elif self.context.has_arg_switch('json'):
+            indent = self.context.get_arg_value_int('indent', 2)
+            raw = self.context.modeling.instance_as_raw
+            self.context.write(json_dumps(raw, indent=indent))
+        else:
+            self.context.modeling.instance.dump(self.context)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a31f3d86/aria/parser/consumption/presentation.py
----------------------------------------------------------------------
diff --git a/aria/parser/consumption/presentation.py b/aria/parser/consumption/presentation.py
new file mode 100644
index 0000000..9723b9f
--- /dev/null
+++ b/aria/parser/consumption/presentation.py
@@ -0,0 +1,134 @@
+# 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 FixedThreadPoolExecutor, json_dumps, yaml_dumps
+from ..loading import UriLocation
+from ..reading import AlreadyReadException
+from ..presentation import PresenterNotFoundError
+from .consumer import Consumer
+
+
+class Read(Consumer):
+    """
+    Reads the presentation, handling imports recursively.
+
+    It works by consuming a data source via appropriate :class:`aria.loader.Loader`,
+    :class:`aria.reader.Reader`, and :class:`aria.presenter.Presenter` instances.
+
+    It supports agnostic raw data composition for presenters that have
+    :code:`_get_import_locations` and :code:`_merge_import`.
+
+    To improve performance, loaders are called asynchronously on separate threads.
+
+    Note that parsing may internally trigger more than one loading/reading/presentation
+    cycle, for example if the agnostic raw data has dependencies that must also be parsed.
+    """
+
+    def consume(self):
+        if self.context.presentation.location is None:
+            self.context.validation.report('Presentation consumer: missing location')
+            return
+
+        presenter = None
+        imported_presentations = None
+
+        executor = FixedThreadPoolExecutor(size=self.context.presentation.threads,
+                                           timeout=self.context.presentation.timeout)
+        executor.print_exceptions = self.context.presentation.print_exceptions
+        try:
+            presenter = self._present(self.context.presentation.location, None, None, executor)
+            executor.drain()
+
+            # Handle exceptions
+            for e in executor.exceptions:
+                self._handle_exception(e)
+
+            imported_presentations = executor.returns
+        finally:
+            executor.close()
+
+        # Merge imports
+        if (imported_presentations is not None) and hasattr(presenter, '_merge_import'):
+            for imported_presentation in imported_presentations:
+                okay = True
+                if hasattr(presenter, '_validate_import'):
+                    okay = presenter._validate_import(self.context, imported_presentation)
+                if okay:
+                    presenter._merge_import(imported_presentation)
+
+        self.context.presentation.presenter = presenter
+
+    def dump(self):
+        if self.context.has_arg_switch('yaml'):
+            indent = self.context.get_arg_value_int('indent', 2)
+            raw = self.context.presentation.presenter._raw
+            self.context.write(yaml_dumps(raw, indent=indent))
+        elif self.context.has_arg_switch('json'):
+            indent = self.context.get_arg_value_int('indent', 2)
+            raw = self.context.presentation.presenter._raw
+            self.context.write(json_dumps(raw, indent=indent))
+        else:
+            self.context.presentation.presenter._dump(self.context)
+
+    def _handle_exception(self, e):
+        if isinstance(e, AlreadyReadException):
+            return
+        super(Read, self)._handle_exception(e)
+
+    def _present(self, location, origin_location, presenter_class, executor):
+        # Link the context to this thread
+        self.context.set_thread_local()
+
+        raw = self._read(location, origin_location)
+
+        if self.context.presentation.presenter_class is not None:
+            # The presenter class we specified in the context overrides everything
+            presenter_class = self.context.presentation.presenter_class
+        else:
+            try:
+                presenter_class = self.context.presentation.presenter_source.get_presenter(raw)
+            except PresenterNotFoundError:
+                # We'll use the presenter class we were given (from the presenter that imported us)
+                pass
+            if presenter_class is None:
+                raise PresenterNotFoundError('presenter not found')
+
+        presentation = presenter_class(raw=raw)
+
+        if presentation is not None and hasattr(presentation, '_link_locators'):
+            presentation._link_locators()
+
+        # Submit imports to executor
+        if hasattr(presentation, '_get_import_locations'):
+            import_locations = presentation._get_import_locations(self.context)
+            if import_locations:
+                for import_location in import_locations:
+                    # The imports inherit the parent presenter class and use the current location as
+                    # their origin location
+                    import_location = UriLocation(import_location)
+                    executor.submit(self._present, import_location, location, presenter_class,
+                                    executor)
+
+        return presentation
+
+    def _read(self, location, origin_location):
+        if self.context.reading.reader is not None:
+            return self.context.reading.reader.read()
+        loader = self.context.loading.loader_source.get_loader(self.context.loading, location,
+                                                               origin_location)
+        reader = self.context.reading.reader_source.get_reader(self.context.reading, location,
+                                                               loader)
+        return reader.read()

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a31f3d86/aria/parser/consumption/style.py
----------------------------------------------------------------------
diff --git a/aria/parser/consumption/style.py b/aria/parser/consumption/style.py
new file mode 100644
index 0000000..8222f3e
--- /dev/null
+++ b/aria/parser/consumption/style.py
@@ -0,0 +1,49 @@
+# 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 safe_repr, Colored, indent
+
+
+class Style(object):
+    def __init__(self, indentation=2):
+        self.indentation = indentation
+
+    @property
+    def indent(self):
+        return indent(self.indentation)
+
+    @staticmethod
+    def section(value):
+        return Colored.cyan(value, bold=True)
+
+    @staticmethod
+    def type(value):
+        return Colored.blue(value, bold=True)
+
+    @staticmethod
+    def node(value):
+        return Colored.red(value, bold=True)
+
+    @staticmethod
+    def property(value):
+        return Colored.magenta(value, bold=True)
+
+    @staticmethod
+    def literal(value):
+        return Colored.yellow(safe_repr(value), bold=True)
+
+    @staticmethod
+    def meta(value):
+        return Colored.green(value)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a31f3d86/aria/parser/consumption/validation.py
----------------------------------------------------------------------
diff --git a/aria/parser/consumption/validation.py b/aria/parser/consumption/validation.py
new file mode 100644
index 0000000..a7bc3b8
--- /dev/null
+++ b/aria/parser/consumption/validation.py
@@ -0,0 +1,30 @@
+# 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 .consumer import Consumer
+
+
+class Validate(Consumer):
+    """
+    Validates the presentation.
+    """
+
+    def consume(self):
+        if self.context.presentation.presenter is None:
+            self.context.validation.report('Validation consumer: missing presenter')
+            return
+
+        self.context.presentation.presenter._validate(self.context)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a31f3d86/aria/parser/exceptions.py
----------------------------------------------------------------------
diff --git a/aria/parser/exceptions.py b/aria/parser/exceptions.py
new file mode 100644
index 0000000..ab1b4fd
--- /dev/null
+++ b/aria/parser/exceptions.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.
+
+import sys
+
+from .validation import Issue
+
+
+class AriaException(Exception):
+    """
+    Base class for ARIA exceptions.
+    """
+
+    def __init__(self, message=None, cause=None, cause_traceback=None):
+        super(AriaException, self).__init__(message)
+        self.cause = cause
+        self.issue = None
+        if cause_traceback is None:
+            _, e, traceback = sys.exc_info()
+            if cause == e:
+                # Make sure it's our traceback
+                cause_traceback = traceback
+        self.cause_tb = cause_traceback
+
+
+class InvalidValueError(AriaException):
+    """
+    ARIA error: value is invalid.
+    """
+
+    def __init__(self, message, cause=None, cause_tb=None, location=None, line=None, column=None,
+                 locator=None, snippet=None, level=Issue.FIELD):
+        super(InvalidValueError, self).__init__(message, cause, cause_tb)
+        self.issue = Issue(message, location=location, line=line, column=column, locator=locator,
+                           snippet=snippet, level=level, exception=cause)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a31f3d86/aria/parser/loading/__init__.py
----------------------------------------------------------------------
diff --git a/aria/parser/loading/__init__.py b/aria/parser/loading/__init__.py
new file mode 100644
index 0000000..f331e39
--- /dev/null
+++ b/aria/parser/loading/__init__.py
@@ -0,0 +1,46 @@
+# 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 LoaderException, LoaderNotFoundError, DocumentNotFoundException
+from .context import LoadingContext
+from .loader import Loader
+from .source import LoaderSource, DefaultLoaderSource
+from .location import Location, UriLocation, LiteralLocation
+from .literal import LiteralLoader
+from .uri import URI_LOADER_PREFIXES, UriTextLoader
+from .request import SESSION, SESSION_CACHE_PATH, RequestLoader, RequestTextLoader
+from .file import FileTextLoader
+
+
+__all__ = (
+    'LoaderException',
+    'LoaderNotFoundError',
+    'DocumentNotFoundException',
+    'LoadingContext',
+    'Loader',
+    'LoaderSource',
+    'DefaultLoaderSource',
+    'Location',
+    'UriLocation',
+    'LiteralLocation',
+    'LiteralLoader',
+    'URI_LOADER_PREFIXES',
+    'UriTextLoader',
+    'SESSION',
+    'SESSION_CACHE_PATH',
+    'RequestLoader',
+    'RequestTextLoader',
+    'FileTextLoader')

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a31f3d86/aria/parser/loading/context.py
----------------------------------------------------------------------
diff --git a/aria/parser/loading/context.py b/aria/parser/loading/context.py
new file mode 100644
index 0000000..2c52086
--- /dev/null
+++ b/aria/parser/loading/context.py
@@ -0,0 +1,31 @@
+# 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 StrictList
+from .source import DefaultLoaderSource
+
+
+class LoadingContext(object):
+    """
+    Properties:
+
+    * :code:`loader_source`: For finding loader instances
+    * :code:`prefixes`: List of additional prefixes for :class:`UriTextLoader`
+    """
+
+    def __init__(self):
+        self.loader_source = DefaultLoaderSource()
+        self.prefixes = StrictList(value_class=basestring)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a31f3d86/aria/parser/loading/exceptions.py
----------------------------------------------------------------------
diff --git a/aria/parser/loading/exceptions.py b/aria/parser/loading/exceptions.py
new file mode 100644
index 0000000..204ad28
--- /dev/null
+++ b/aria/parser/loading/exceptions.py
@@ -0,0 +1,35 @@
+# 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 .. import AriaException
+
+
+class LoaderException(AriaException):
+    """
+    ARIA loader exception.
+    """
+
+
+class LoaderNotFoundError(LoaderException):
+    """
+    ARIA loader error: loader not found for source.
+    """
+
+
+class DocumentNotFoundException(LoaderException):
+    """
+    ARIA loader exception: document not found.
+    """

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a31f3d86/aria/parser/loading/file.py
----------------------------------------------------------------------
diff --git a/aria/parser/loading/file.py b/aria/parser/loading/file.py
new file mode 100644
index 0000000..a02bd69
--- /dev/null
+++ b/aria/parser/loading/file.py
@@ -0,0 +1,64 @@
+# 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 codecs
+
+from .loader import Loader
+from .exceptions import LoaderException, DocumentNotFoundException
+
+
+class FileTextLoader(Loader):
+    """
+    ARIA file text loader.
+
+    Extracts a text document from a file. The default encoding is UTF-8, but other supported
+    encoding can be specified instead.
+    """
+
+    def __init__(self, context, path, encoding='utf-8'):
+        self.context = context
+        self.path = path
+        self.encoding = encoding
+        self._file = None
+
+    def open(self):
+        try:
+            self._file = codecs.open(self.path, mode='r', encoding=self.encoding, buffering=1)
+        except IOError as e:
+            if e.errno == 2:
+                raise DocumentNotFoundException('file not found: "%s"' % self.path, cause=e)
+            else:
+                raise LoaderException('file I/O error: "%s"' % self.path, cause=e)
+        except Exception as e:
+            raise LoaderException('file error: "%s"' % self.path, cause=e)
+
+    def close(self):
+        if self._file is not None:
+            try:
+                self._file.close()
+            except IOError as e:
+                raise LoaderException('file I/O error: "%s"' % self.path, cause=e)
+            except Exception as e:
+                raise LoaderException('file error: "%s"' % self.path, cause=e)
+
+    def load(self):
+        if self._file is not None:
+            try:
+                return self._file.read()
+            except IOError as e:
+                raise LoaderException('file I/O error: "%s"' % self.path, cause=e)
+            except Exception as e:
+                raise LoaderException('file error %s' % self.path, cause=e)
+        return None

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a31f3d86/aria/parser/loading/literal.py
----------------------------------------------------------------------
diff --git a/aria/parser/loading/literal.py b/aria/parser/loading/literal.py
new file mode 100644
index 0000000..1b99fd8
--- /dev/null
+++ b/aria/parser/loading/literal.py
@@ -0,0 +1,31 @@
+# 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 .loader import Loader
+
+
+class LiteralLoader(Loader):
+    """
+    ARIA literal loader.
+
+    See :class:`aria.loading.LiteralLocation`.
+    """
+
+    def __init__(self, location):
+        self.location = location
+
+    def load(self):
+        return self.location.content

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a31f3d86/aria/parser/loading/loader.py
----------------------------------------------------------------------
diff --git a/aria/parser/loading/loader.py b/aria/parser/loading/loader.py
new file mode 100644
index 0000000..e1abfbf
--- /dev/null
+++ b/aria/parser/loading/loader.py
@@ -0,0 +1,34 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+class Loader(object):
+    """
+    Base class for ARIA loaders.
+
+    Loaders extract a document by consuming a document source.
+
+    Though the extracted document is often textual (a string or string-like
+    data), loaders may provide any format.
+    """
+
+    def open(self):
+        pass
+
+    def close(self):
+        pass
+
+    def load(self):
+        raise NotImplementedError

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a31f3d86/aria/parser/loading/location.py
----------------------------------------------------------------------
diff --git a/aria/parser/loading/location.py b/aria/parser/loading/location.py
new file mode 100644
index 0000000..0a3d428
--- /dev/null
+++ b/aria/parser/loading/location.py
@@ -0,0 +1,82 @@
+# 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 os
+
+from ..utils import as_file
+
+
+class Location(object):
+    """
+    Base class for ARIA locations.
+
+    Locations are used by :class:`aria.loading.LoaderSource` to delegate to
+    an appropriate :class:`aria.loading.Loader`.
+    """
+
+    def is_equivalent(self, location):
+        raise NotImplementedError
+
+    @property
+    def prefix(self):
+        return None
+
+
+class UriLocation(Location):
+    """
+    A URI location can be absolute or relative, and can include a scheme or not.
+
+    If no scheme is included, it should be treated as a filesystem path.
+
+    See :class:`aria.loading.UriTextLoader`.
+    """
+
+    def __init__(self, uri):
+        self.uri = uri
+
+    def is_equivalent(self, location):
+        return isinstance(location, UriLocation) and (location.uri == self.uri)
+
+    @property
+    def prefix(self):
+        prefix = os.path.dirname(self.uri)
+        if prefix and (as_file(prefix) is None):
+            # Yes, it's weird, but dirname handles URIs,
+            # too: http://stackoverflow.com/a/35616478/849021
+            # We just need to massage it with a trailing slash
+            prefix += '/'
+        return prefix
+
+    def __str__(self):
+        return self.uri
+
+
+class LiteralLocation(Location):
+    """
+    A location that embeds content.
+
+    See :class:`aria.loading.LiteralLoader`.
+    """
+
+    def __init__(self, content, name='literal'):
+        self.content = content
+        self.name = name
+
+    def is_equivalent(self, location):
+        return isinstance(location, LiteralLocation) and (location.content == self.content)
+
+    def __str__(self):
+        return '<%s>' % self.name

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a31f3d86/aria/parser/loading/request.py
----------------------------------------------------------------------
diff --git a/aria/parser/loading/request.py b/aria/parser/loading/request.py
new file mode 100644
index 0000000..6ebabfc
--- /dev/null
+++ b/aria/parser/loading/request.py
@@ -0,0 +1,83 @@
+# 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 requests import Session
+from requests.exceptions import ConnectionError
+from cachecontrol import CacheControl
+from cachecontrol.caches import FileCache
+
+from .exceptions import LoaderException, DocumentNotFoundException
+from .loader import Loader
+
+SESSION = None
+SESSION_CACHE_PATH = '/tmp'
+
+
+class RequestLoader(Loader):
+    """
+    Base class for ARIA request-based loaders.
+
+    Extracts a document from a URI by performing a request.
+
+    Note that the "file:" schema is not supported: :class:`FileTextLoader` should
+    be used instead.
+    """
+
+    def __init__(self, context, uri, headers=None):
+        if headers is None:
+            headers = {}
+        self.context = context
+        self.uri = uri
+        self.headers = headers
+        self._response = None
+
+    def load(self):
+        pass
+
+    def open(self):
+        global SESSION
+        if SESSION is None:
+            SESSION = CacheControl(Session(), cache=FileCache(SESSION_CACHE_PATH))
+
+        try:
+            self._response = SESSION.get(self.uri, headers=self.headers)
+        except ConnectionError as e:
+            raise LoaderException('request connection error: "%s"' % self.uri, cause=e)
+        except Exception as e:
+            raise LoaderException('request error: "%s"' % self.uri, cause=e)
+
+        status = self._response.status_code
+        if status == 404:
+            self._response = None
+            raise DocumentNotFoundException('document not found: "%s"' % self.uri)
+        elif status != 200:
+            self._response = None
+            raise LoaderException('request error %d: "%s"' % (status, self.uri))
+
+
+class RequestTextLoader(RequestLoader):
+    """
+    ARIA request-based text loader.
+    """
+
+    def load(self):
+        if self._response is not None:
+            try:
+                if self._response.encoding is None:
+                    self._response.encoding = 'utf8'
+                return self._response.text
+            except Exception as e:
+                raise LoaderException('request error: %s' % self.uri, cause=e)
+        return None

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a31f3d86/aria/parser/loading/source.py
----------------------------------------------------------------------
diff --git a/aria/parser/loading/source.py b/aria/parser/loading/source.py
new file mode 100644
index 0000000..7acf813
--- /dev/null
+++ b/aria/parser/loading/source.py
@@ -0,0 +1,44 @@
+# 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 .location import LiteralLocation, UriLocation
+from .literal import LiteralLoader
+from .uri import UriTextLoader
+
+
+class LoaderSource(object):
+    """
+    Base class for ARIA loader sources.
+
+    Loader sources provide appropriate :class:`Loader` instances for locations.
+    """
+
+    def get_loader(self, context, location, origin_location):
+        raise NotImplementedError
+
+
+class DefaultLoaderSource(LoaderSource):
+    """
+    The default ARIA loader source will generate a :class:`UriTextLoader` for
+    :class:`UriLocation' and a :class:`LiteralLoader` for a :class:`LiteralLocation`.
+    """
+
+    def get_loader(self, context, location, origin_location):
+        if isinstance(location, UriLocation):
+            return UriTextLoader(context, location, origin_location)
+        elif isinstance(location, LiteralLocation):
+            return LiteralLoader(location)
+
+        return super(DefaultLoaderSource, self).get_loader(context, location, origin_location)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a31f3d86/aria/parser/loading/uri.py
----------------------------------------------------------------------
diff --git a/aria/parser/loading/uri.py b/aria/parser/loading/uri.py
new file mode 100644
index 0000000..5e6ff39
--- /dev/null
+++ b/aria/parser/loading/uri.py
@@ -0,0 +1,95 @@
+# 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 os
+from urlparse import urljoin
+
+from ..utils import StrictList, as_file
+from .loader import Loader
+from .file import FileTextLoader
+from .request import RequestTextLoader
+from .exceptions import DocumentNotFoundException
+
+URI_LOADER_PREFIXES = StrictList(value_class=basestring)
+
+class UriTextLoader(Loader):
+    """
+    Base class for ARIA URI loaders.
+
+    See :class:`aria.loading.UriLocation`.
+
+    Supports a list of search prefixes that are tried in order if the URI cannot be found.
+    They will be:
+
+    * If :code:`origin_location` is provided its prefix will come first.
+    * Then the prefixes in the :class:`LoadingContext` will be added.
+    * Finally, the global prefixes specified in :code:`URI_LOADER_PREFIXES` will be added.
+    """
+
+    def __init__(self, context, location, origin_location=None):
+        self.context = context
+        self.location = location
+        self._prefixes = StrictList(value_class=basestring)
+        self._loader = None
+
+        def add_prefix(prefix):
+            if prefix and (prefix not in self._prefixes):
+                self._prefixes.append(prefix)
+
+        def add_prefixes(prefixes):
+            for prefix in prefixes:
+                add_prefix(prefix)
+
+        if origin_location is not None:
+            add_prefix(origin_location.prefix)
+
+        add_prefixes(context.prefixes)
+        add_prefixes(URI_LOADER_PREFIXES)
+
+    def open(self):
+        try:
+            self._open(self.location.uri)
+            return
+        except DocumentNotFoundException:
+            # Try prefixes in order
+            for prefix in self._prefixes:
+                if as_file(prefix) is not None:
+                    uri = os.path.join(prefix, self.location.uri)
+                else:
+                    uri = urljoin(prefix, self.location.uri)
+                try:
+                    self._open(uri)
+                    return
+                except DocumentNotFoundException:
+                    pass
+        raise DocumentNotFoundException('document not found at URI: "%s"' % self.location)
+
+    def close(self):
+        if self._loader is not None:
+            self._loader.close()
+
+    def load(self):
+        return self._loader.load() if self._loader is not None else None
+
+    def _open(self, uri):
+        the_file = as_file(uri)
+        if the_file is not None:
+            uri = the_file
+            loader = FileTextLoader(self.context, uri)
+        else:
+            loader = RequestTextLoader(self.context, uri)
+        loader.open() # might raise an exception
+        self._loader = loader
+        self.location.uri = uri

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a31f3d86/aria/parser/modeling/__init__.py
----------------------------------------------------------------------
diff --git a/aria/parser/modeling/__init__.py b/aria/parser/modeling/__init__.py
new file mode 100644
index 0000000..bc4373c
--- /dev/null
+++ b/aria/parser/modeling/__init__.py
@@ -0,0 +1,68 @@
+# 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 CannotEvaluateFunctionException
+from .context import IdType, ModelingContext
+from .elements import Element, ModelElement, Function, Parameter, Metadata
+from .instance_elements import (ServiceInstance, Node, Capability, Relationship, Artifact, Group,
+                                Policy, GroupPolicy, GroupPolicyTrigger, Mapping, Substitution,
+                                Interface, Operation)
+from .model_elements import (ServiceModel, NodeTemplate, RequirementTemplate, CapabilityTemplate,
+                             RelationshipTemplate, ArtifactTemplate, GroupTemplate, PolicyTemplate,
+                             GroupPolicyTemplate, GroupPolicyTriggerTemplate, MappingTemplate,
+                             SubstitutionTemplate, InterfaceTemplate, OperationTemplate)
+from .types import TypeHierarchy, Type, RelationshipType, PolicyType, PolicyTriggerType
+
+__all__ = (
+    'CannotEvaluateFunctionException',
+    'IdType',
+    'ModelingContext',
+    'Element',
+    'ModelElement',
+    'Function',
+    'Parameter',
+    'Metadata',
+    'ServiceInstance',
+    'Node',
+    'Capability',
+    'Relationship',
+    'Artifact',
+    'Group',
+    'Policy',
+    'GroupPolicy',
+    'GroupPolicyTrigger',
+    'Mapping',
+    'Substitution',
+    'Interface',
+    'Operation',
+    'ServiceModel',
+    'NodeTemplate',
+    'RequirementTemplate',
+    'CapabilityTemplate',
+    'RelationshipTemplate',
+    'ArtifactTemplate',
+    'GroupTemplate',
+    'PolicyTemplate',
+    'GroupPolicyTemplate',
+    'GroupPolicyTriggerTemplate',
+    'MappingTemplate',
+    'SubstitutionTemplate',
+    'InterfaceTemplate',
+    'OperationTemplate',
+    'TypeHierarchy',
+    'Type',
+    'RelationshipType',
+    'PolicyType',
+    'PolicyTriggerType')

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a31f3d86/aria/parser/modeling/context.py
----------------------------------------------------------------------
diff --git a/aria/parser/modeling/context.py b/aria/parser/modeling/context.py
new file mode 100644
index 0000000..817ec15
--- /dev/null
+++ b/aria/parser/modeling/context.py
@@ -0,0 +1,145 @@
+# 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
+import itertools
+
+from ..utils import StrictDict, prune, puts, as_raw
+from .types import TypeHierarchy
+from .utils import generate_id_string
+
+
+class IdType(object):
+    LOCAL_SERIAL = 0
+    """
+    Locally unique serial ID: a running integer.
+    """
+
+    LOCAL_RANDOM = 1
+    """
+    Locally unique ID: 6 random safe characters.
+    """
+
+    UNIVERSAL_RANDOM = 2
+    """
+    Universally unique ID (UUID): 25 random safe characters.
+    """
+
+
+class ModelingContext(object):
+    """
+    Properties:
+
+    * :code:`model`: The generated service model
+    * :code:`instance`: The generated service instance
+    * :code:`id_type`: Type of IDs to use for instances
+    * :code:`id_max_length`: Maximum allowed instance ID length
+    * :code:`inputs`: Dict of inputs values
+    * :code:`node_types`: The generated hierarchy of node types
+    * :code:`group_types`: The generated hierarchy of group types
+    * :code:`capability_types`: The generated hierarchy of capability types
+    * :code:`relationship_types`: The generated hierarchy of relationship types
+    * :code:`policy_types`: The generated hierarchy of policy types
+    * :code:`policy_trigger_types`: The generated hierarchy of policy trigger types
+    * :code:`artifact_types`: The generated hierarchy of artifact types
+    * :code:`interface_types`: The generated hierarchy of interface types
+    """
+
+    def __init__(self):
+        self.model = None
+        self.instance = None
+        #self.id_type = IdType.LOCAL_SERIAL
+        #self.id_type = IdType.LOCAL_RANDOM
+        self.id_type = IdType.UNIVERSAL_RANDOM
+        self.id_max_length = 63 # See: http://www.faqs.org/rfcs/rfc1035.html
+        self.inputs = StrictDict(key_class=basestring)
+        self.node_types = TypeHierarchy()
+        self.group_types = TypeHierarchy()
+        self.capability_types = TypeHierarchy()
+        self.relationship_types = TypeHierarchy()
+        self.policy_types = TypeHierarchy()
+        self.policy_trigger_types = TypeHierarchy()
+        self.artifact_types = TypeHierarchy()
+        self.interface_types = TypeHierarchy()
+
+        self._serial_id_counter = itertools.count(1)
+        self._locally_unique_ids = set()
+
+    def generate_id(self):
+        if self.id_type == IdType.LOCAL_SERIAL:
+            return self._serial_id_counter.next()
+
+        elif self.id_type == IdType.LOCAL_RANDOM:
+            the_id = generate_id_string(6)
+            while the_id in self._locally_unique_ids:
+                the_id = generate_id_string(6)
+            self._locally_unique_ids.add(the_id)
+            return the_id
+
+        return generate_id_string()
+
+    def set_input(self, name, value):
+        self.inputs[name] = value
+        # TODO: coerce to validate type
+
+    @property
+    def types_as_raw(self):
+        return OrderedDict((
+            ('node_types', as_raw(self.node_types)),
+            ('group_types', as_raw(self.group_types)),
+            ('capability_types', as_raw(self.capability_types)),
+            ('relationship_types', as_raw(self.relationship_types)),
+            ('policy_types', as_raw(self.policy_types)),
+            ('policy_trigger_types', as_raw(self.policy_trigger_types)),
+            ('artifact_types', as_raw(self.artifact_types)),
+            ('interface_types', as_raw(self.interface_types))))
+
+    @property
+    def model_as_raw(self):
+        raw = self.model.as_raw
+        prune(raw)
+        return raw
+
+    @property
+    def instance_as_raw(self):
+        raw = self.instance.as_raw
+        prune(raw)
+        return raw
+
+    def dump_types(self, context):
+        if self.node_types.children:
+            puts('Node types:')
+            self.node_types.dump(context)
+        if self.group_types.children:
+            puts('Group types:')
+            self.group_types.dump(context)
+        if self.capability_types.children:
+            puts('Capability types:')
+            self.capability_types.dump(context)
+        if self.relationship_types.children:
+            puts('Relationship types:')
+            self.relationship_types.dump(context)
+        if self.policy_types.children:
+            puts('Policy types:')
+            self.policy_types.dump(context)
+        if self.policy_trigger_types.children:
+            puts('Policy trigger types:')
+            self.policy_trigger_types.dump(context)
+        if self.artifact_types.children:
+            puts('Artifact types:')
+            self.artifact_types.dump(context)
+        if self.interface_types.children:
+            puts('Interface types:')
+            self.interface_types.dump(context)

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a31f3d86/aria/parser/modeling/elements.py
----------------------------------------------------------------------
diff --git a/aria/parser/modeling/elements.py b/aria/parser/modeling/elements.py
new file mode 100644
index 0000000..8974fd6
--- /dev/null
+++ b/aria/parser/modeling/elements.py
@@ -0,0 +1,129 @@
+# 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 StrictDict, puts
+from .utils import coerce_value
+
+
+class Function(object):
+    """
+    An intrinsic function.
+
+    Serves as a placeholder for a value that should eventually be derived
+    by calling the function.
+    """
+
+    @property
+    def as_raw(self):
+        raise NotImplementedError
+
+    def _evaluate(self, context, container):
+        raise NotImplementedError
+
+    def __deepcopy__(self, memo):
+        # Circumvent cloning in order to maintain our state
+        return self
+
+
+class Element(object):
+    """
+    Base class for :class:`ServiceInstance` elements.
+
+    All elements support validation, diagnostic dumping, and representation as
+    raw data (which can be translated into JSON or YAML) via :code:`as_raw`.
+    """
+
+    @property
+    def as_raw(self):
+        raise NotImplementedError
+
+    def validate(self, context):
+        pass
+
+    def coerce_values(self, context, container, report_issues):
+        pass
+
+    def dump(self, context):
+        pass
+
+
+class ModelElement(Element):
+    """
+    Base class for :class:`ServiceModel` elements.
+
+    All model elements can be instantiated into :class:`ServiceInstance` elements.
+    """
+
+    def instantiate(self, context, container):
+        raise NotImplementedError
+
+
+class Parameter(ModelElement):
+    """
+    Represents a typed value.
+
+    This class is used by both service model and service instance elements.
+    """
+
+    def __init__(self, type_name, value, description):
+        self.type_name = type_name
+        self.value = value
+        self.description = description
+
+    @property
+    def as_raw(self):
+        return OrderedDict((
+            ('type_name', self.type_name),
+            ('value', self.value),
+            ('description', self.description)))
+
+    def instantiate(self, context, container):
+        return Parameter(self.type_name, self.value, self.description)
+
+    def coerce_values(self, context, container, report_issues):
+        if self.value is not None:
+            self.value = coerce_value(context, container, self.value, report_issues)
+
+
+class Metadata(ModelElement):
+    """
+    Custom values associated with the deployment template and its plans.
+
+    This class is used by both service model and service instance elements.
+
+    Properties:
+
+    * :code:`values`: Dict of custom values
+    """
+
+    def __init__(self):
+        self.values = StrictDict(key_class=basestring)
+
+    @property
+    def as_raw(self):
+        return self.values
+
+    def instantiate(self, context, container):
+        metadata = Metadata()
+        metadata.values.update(self.values)
+        return metadata
+
+    def dump(self, context):
+        puts('Metadata:')
+        with context.style.indent:
+            for name, value in self.values.iteritems():
+                puts('%s: %s' % (name, context.style.meta(value)))

http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a31f3d86/aria/parser/modeling/exceptions.py
----------------------------------------------------------------------
diff --git a/aria/parser/modeling/exceptions.py b/aria/parser/modeling/exceptions.py
new file mode 100644
index 0000000..66fb7d2
--- /dev/null
+++ b/aria/parser/modeling/exceptions.py
@@ -0,0 +1,22 @@
+# 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 .. import AriaException
+
+
+class CannotEvaluateFunctionException(AriaException):
+    """
+    ARIA modeling exception: cannot evaluate the function at this time.
+    """


Mime
View raw message