ARIA-18 Migrate DSL parser and TOSCA extension code Additional changes: - Added several rules to pylint configuration.
Project: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/commit/8ee1470e Tree: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/tree/8ee1470e Diff: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/diff/8ee1470e Branch: refs/heads/master Commit: 8ee1470e314cfd3cac5cc4e17c80ea6ab67bad8a Parents: 0fdf594 Author: Tal Liron <tal.li...@gmail.com> Authored: Wed Nov 9 16:34:52 2016 +0200 Committer: Ran Ziv <r...@gigaspaces.com> Committed: Tue Nov 15 18:04:19 2016 +0200 ---------------------------------------------------------------------- aria/.pylintrc | 8 +- aria/exceptions.py | 2 +- 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 | 250 ++++ 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 +- tests/.pylintrc | 8 +- tox.ini | 4 +- 122 files changed, 18695 insertions(+), 14 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/8ee1470e/aria/.pylintrc ---------------------------------------------------------------------- diff --git a/aria/.pylintrc b/aria/.pylintrc index bf90513..13a84fc 100644 --- a/aria/.pylintrc +++ b/aria/.pylintrc @@ -77,7 +77,7 @@ confidence= # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" -disable=import-star-module-level,old-octal-literal,oct-method,print-statement,unpacking-in-except,parameter-unpacking,backtick,old-raise-syntax,old-ne-operator,long-suffix,dict-view-method,dict-iter-method,metaclass-assignment,next-method-called,raising-string,indexing-exception,raw_input-builtin,long-builtin,file-builtin,execfile-builtin,coerce-builtin,cmp-builtin,buffer-builtin,basestring-builtin,apply-builtin,filter-builtin-not-iterating,using-cmp-argument,useless-suppression,range-builtin-not-iterating,suppressed-message,no-absolute-import,old-division,cmp-method,reload-builtin,zip-builtin-not-iterating,intern-builtin,unichr-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,input-builtin,round-builtin,hex-method,nonzero-method,map-builtin-not-iterating,redefined-builtin,logging-format-interpolation,import-error,protected-access +disable=import-star-module-level,old-octal-literal,oct-method,print-statement,unpacking-in-except,parameter-unpacking,backtick,old-raise-syntax,old-ne-operator,long-suffix,dict-view-method,dict-iter-method,metaclass-assignment,next-method-called,raising-string,indexing-exception,raw_input-builtin,long-builtin,file-builtin,execfile-builtin,coerce-builtin,cmp-builtin,buffer-builtin,basestring-builtin,apply-builtin,filter-builtin-not-iterating,using-cmp-argument,useless-suppression,range-builtin-not-iterating,suppressed-message,no-absolute-import,old-division,cmp-method,reload-builtin,zip-builtin-not-iterating,intern-builtin,unichr-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,input-builtin,round-builtin,hex-method,nonzero-method,map-builtin-not-iterating,redefined-builtin,logging-format-interpolation,import-error,redefined-variable-type,broad-except,protected-access,global-statement [REPORTS] @@ -162,7 +162,7 @@ logging-modules=logging [BASIC] # Good variable names which should always be accepted, separated by a comma -good-names=i,j,k,ex,e,_,id +good-names=i,j,k,v,f,ex,e,_,id # Bad variable names which should always be refused, separated by a comma bad-names=foo,bar,baz,toto,tutu,tata @@ -315,7 +315,7 @@ single-line-if-stmt=no no-space-check=trailing-comma,dict-separator # Maximum number of lines in a module -max-module-lines=1000 +max-module-lines=1500 # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 # tab). @@ -357,7 +357,7 @@ ignored-argument-names=_.* max-locals=20 # Maximum number of return / yield for function / method body -max-returns=6 +max-returns=10 # Maximum number of branch for function / method body max-branches=12 http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/8ee1470e/aria/exceptions.py ---------------------------------------------------------------------- diff --git a/aria/exceptions.py b/aria/exceptions.py index 8b7ede7..d68b5a2 100644 --- a/aria/exceptions.py +++ b/aria/exceptions.py @@ -19,7 +19,7 @@ Every sub-package in Aria has a module with its exceptions. aria.exceptions module conveniently collects all these exceptions for easier imports. """ -from .workflows.exceptions import * # pylint: disable=W0401, W0614 +from .workflows.exceptions import * # pylint: disable=wildcard-import,unused-wildcard-import class AriaError(Exception): http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/8ee1470e/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/8ee1470e/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/8ee1470e/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/8ee1470e/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/8ee1470e/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/8ee1470e/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/8ee1470e/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/8ee1470e/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/8ee1470e/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/8ee1470e/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/8ee1470e/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/8ee1470e/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/8ee1470e/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/8ee1470e/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/8ee1470e/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/8ee1470e/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/8ee1470e/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/8ee1470e/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/8ee1470e/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/8ee1470e/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/8ee1470e/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/8ee1470e/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/8ee1470e/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/8ee1470e/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/8ee1470e/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. + """