[SEBA-497]

Change to using alpine-grpc-base
Fix issue with nested exceptions causing an error in structlog
Reformat and python3 fixes, v3.5 mock support
Record execution times in the loader

Change-Id: I6d7923818d57012fca32ce44668820de422206d6
diff --git a/.dockerignore b/.dockerignore
index 5465185..8a56dfc 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -4,3 +4,6 @@
 test
 Dockerfile
 Makefile
+venv-tosca
+.coverage
+.tox
diff --git a/.gitignore b/.gitignore
index 24478e5..b1f3efc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,11 +5,12 @@
 src/tosca/tmp.yaml
 src/grpc_client/KEYS.py
 
-# Test output
-nose2-junit.xml
+# Test
 .coverage
-cover
+.tox
 coverage.xml
+nose2-results.xml
+venv-tosca
 
 # hack
 xos
diff --git a/Dockerfile b/Dockerfile
index fb1fb82..aebd6d5 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,50 +1,50 @@
-# docker build -t xosproject/xos-tosca:candidate .
+# Copyright 2018-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
 
 # xosproject/xos-tosca
 
-FROM xosproject/xos-client:3.0.0
+FROM xosproject/alpine-grpc-base:0.9.0
 
 # Set environment variables
-ENV CODE_SOURCE .
 ENV CODE_DEST /opt/xos-tosca
 WORKDIR ${CODE_DEST}
 
 # Add XOS-TOSCA code
-COPY ${CODE_SOURCE}/ ${CODE_DEST}/
+COPY . ${CODE_DEST}/
 
-# Install dependencies
-RUN pip install klein==16.12.0
-
-EXPOSE 9102
+# Install python packages with pip
+COPY requirements.txt /tmp/requirements.txt
+RUN pip install -r /tmp/requirements.txt \
+ && pip freeze > /var/xos/pip_freeze_xos-tosca_`date -u +%Y%m%dT%H%M%S`
 
 # Label image
-ARG org_label_schema_schema_version=1.0
-ARG org_label_schema_name=gui-extension-sample
 ARG org_label_schema_version=unknown
 ARG org_label_schema_vcs_url=unknown
 ARG org_label_schema_vcs_ref=unknown
 ARG org_label_schema_build_date=unknown
 ARG org_opencord_vcs_commit_date=unknown
-ARG org_opencord_component_chameleon_version=unknown
-ARG org_opencord_component_chameleon_vcs_url=unknown
-ARG org_opencord_component_chameleon_vcs_ref=unknown
-ARG org_opencord_component_xos_version=unknown
-ARG org_opencord_component_xos_vcs_url=unknown
-ARG org_opencord_component_xos_vcs_ref=unknown
 
-LABEL org.label-schema.schema-version=$org_label_schema_schema_version \
-      org.label-schema.name=$org_label_schema_name \
+
+LABEL org.label-schema.schema-version=1.0 \
+      org.label-schema.name=xos-tosca \
       org.label-schema.version=$org_label_schema_version \
       org.label-schema.vcs-url=$org_label_schema_vcs_url \
       org.label-schema.vcs-ref=$org_label_schema_vcs_ref \
       org.label-schema.build-date=$org_label_schema_build_date \
-      org.opencord.vcs-commit-date=$org_opencord_vcs_commit_date \
-      org.opencord.component.chameleon.version=$org_opencord_component_chameleon_version \
-      org.opencord.component.chameleon.vcs-url=$org_opencord_component_chameleon_vcs_url \
-      org.opencord.component.chameleon.vcs-ref=$org_opencord_component_chameleon_vcs_ref \
-      org.opencord.component.xos.version=$org_opencord_component_xos_version \
-      org.opencord.component.xos.vcs-url=$org_opencord_component_xos_vcs_url \
-      org.opencord.component.xos.vcs-ref=$org_opencord_component_xos_vcs_ref
+      org.opencord.vcs-commit-date=$org_opencord_vcs_commit_date
+
+EXPOSE 9102
 
 ENTRYPOINT [ "/usr/bin/python", "src/main.py" ]
 
diff --git a/Makefile b/Makefile
index b91ee28..988ca06 100644
--- a/Makefile
+++ b/Makefile
@@ -1,32 +1,127 @@
-help:
-	@echo "tests: Run unit tests (need the xos dev virtual-env activated)"
-	@echo "tosca: Generate tosca definition from core.xproto"
-	@echo "build: Build the docker image for xos-tosca"
-	@echo "start: Run an xos-tosca container"
-	@echo "clean: Remove the xos-tosca container (if any), and the image (if any)"
-	@echo "test-create: Send a sample tosca recipe"
-	@echo "test-delete: Delete a sample tosca recipe"
+# Copyright 2019-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
 
-tests: tosca
-	nose2 --verbose --coverage-report xml --coverage-report term --junit-xml
+# Configure shell
+SHELL = bash -e -o pipefail
 
-build:
-	docker build -t xosproject/xos-tosca .
-	docker tag xosproject/xos-tosca:latest xosproject/xos-tosca:candidate
+# Variables
+VERSION                  ?= $(shell cat ./VERSION)
+SERVICE_NAME             ?= $(notdir $(abspath .))
+LOADER_NAME              ?= tosca-loader
 
-start: build
-	docker run -p 9200:9200 --name xos-tosca -d xosproject/xos-tosca
+## Docker related
+DOCKER_REGISTRY          ?=
+DOCKER_REPOSITORY        ?=
+DOCKER_BUILD_ARGS        ?=
+DOCKER_TAG               ?= ${VERSION}
+DOCKER_IMAGENAME         := ${DOCKER_REGISTRY}${DOCKER_REPOSITORY}${SERVICE_NAME}:${DOCKER_TAG}
+LOADER_IMAGENAME         := ${DOCKER_REGISTRY}${DOCKER_REPOSITORY}${LOADER_NAME}:${DOCKER_TAG}
 
-clean:
-	docker rm -f xos-tosca || true
-	docker rmi -f xosproject/xos-tosca || true
+## Docker labels. Only set ref and commit date if committed
+DOCKER_LABEL_VCS_URL     ?= $(shell git remote get-url $(shell git remote))
+DOCKER_LABEL_VCS_REF     ?= $(shell git diff-index --quiet HEAD -- && git rev-parse HEAD || echo "unknown")
+DOCKER_LABEL_COMMIT_DATE ?= $(shell git diff-index --quiet HEAD -- && git show -s --format=%cd --date=iso-strict HEAD || echo "unknown" )
+DOCKER_LABEL_BUILD_DATE  ?= $(shell date -u "+%Y-%m-%dT%H:%M:%SZ")
+
+## xosgenx related - paths are relative to this directory
+XOS_DIR                  ?= "../xos"
+
+all: test
+
+docker-build: generate-xproto
+	docker build $(DOCKER_BUILD_ARGS) \
+    -t ${DOCKER_IMAGENAME} \
+    --build-arg org_label_schema_version="${VERSION}" \
+    --build-arg org_label_schema_vcs_url="${DOCKER_LABEL_VCS_URL}" \
+    --build-arg org_label_schema_vcs_ref="${DOCKER_LABEL_VCS_REF}" \
+    --build-arg org_label_schema_build_date="${DOCKER_LABEL_BUILD_DATE}" \
+    --build-arg org_opencord_vcs_commit_date="${DOCKER_LABEL_COMMIT_DATE}" \
+    -f Dockerfile .
+	docker build $(DOCKER_BUILD_ARGS) \
+    -t ${LOADER_IMAGENAME} \
+    --build-arg org_label_schema_version="${VERSION}" \
+    --build-arg org_label_schema_vcs_url="${DOCKER_LABEL_VCS_URL}" \
+    --build-arg org_label_schema_vcs_ref="${DOCKER_LABEL_VCS_REF}" \
+    --build-arg org_label_schema_build_date="${DOCKER_LABEL_BUILD_DATE}" \
+    --build-arg org_opencord_vcs_commit_date="${DOCKER_LABEL_COMMIT_DATE}" \
+    -f loader/Dockerfile.tosca-loader loader
+
+docker-push:
+	docker push ${DOCKER_IMAGENAME}
+	docker push ${LOADER_IMAGENAME}
+
+# Test starting the image, loading TOSCA, deleting TOSCA, and cleaning up after
+# Not sure if this has been functional recently
+test-docker: docker-start test-create test-delete docker-clean
+
+docker-start:
+	docker run -p 9102:9102 --name xos-tosca -d ${DOCKER_IMAGENAME}
 
 test-create:
-	curl -H "xos-username: xosadmin@opencord.org" -H "xos-password: rk1UYDHZXbu6KVCMkhmV" -X POST --data-binary @test/tosca/test.yaml 127.0.0.1:9102/run
+	curl -H "xos-username: xosadmin@opencord.org" \
+       -H "xos-password: rk1UYDHZXbu6KVCMkhmV" \
+       -X POST \
+       --data-binary @test/tosca/test.yaml \
+       127.0.0.1:9102/run
 
 test-delete:
-	curl -H "xos-username: xosadmin@opencord.org" -H "xos-password: rk1UYDHZXbu6KVCMkhmV" -X POST --data-binary @test/tosca/test.yaml 127.0.0.1:9102/delete
+	curl -H "xos-username: xosadmin@opencord.org" \
+       -H "xos-password: rk1UYDHZXbu6KVCMkhmV" \
+       -X POST \
+       --data-binary @test/tosca/test.yaml \
+       127.0.0.1:9102/delete
 
-tosca:
-	xosgenx --target=src/tosca/xtarget/tosca.xtarget --output=src/tosca/custom_types --write-to-file=target ../xos/xos/core/models/core.xproto
-	xosgenx --target=src/tosca/xtarget/tosca_keys.xtarget --output=src/grpc_client/ --write-to-file=single --dest-file=KEYS.py ../xos/xos/core/models/core.xproto
+docker-clean:
+	docker rm -f xos-tosca || true
+	docker rmi -f ${DOCKER_IMAGENAME} || true
+
+test: test-unit
+
+test-unit: generate-xproto
+	tox
+
+venv-tosca:
+	virtualenv $@;\
+    source ./$@/bin/activate ; set -u ;\
+    pip install -r requirements.txt
+
+generate-xproto: venv-tosca
+	source ./venv-tosca/bin/activate ; set -u ;\
+	xosgenx \
+    --target=src/tosca/xtarget/tosca.xtarget \
+    --output=src/tosca/custom_types \
+    --write-to-file=target \
+    ${XOS_DIR}/xos/core/models/core.xproto ;\
+	xosgenx \
+    --target=src/tosca/xtarget/tosca_keys.xtarget \
+    --output=src/grpc_client/ \
+    --write-to-file=single \
+    --dest-file=KEYS.py \
+    ${XOS_DIR}/xos/core/models/core.xproto
+
+clean:
+	find . -name '*.pyc' | xargs rm -f
+	rm -rf \
+    .tox \
+    .coverage \
+    venv-tosca \
+    coverage \
+    coverage.xml \
+    nose2-results.xml \
+    src/grpc_client/KEYS.py \
+    src/grpc_client/__pycache__ \
+    src/tosca/__pycache__ \
+    src/tosca/custom_types/* \
+    test/__pycache__ \
+    test/out/*
diff --git a/VERSION b/VERSION
index 26aaba0..f0bb29e 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.2.0
+1.3.0
diff --git a/book.json b/book.json
deleted file mode 100644
index abbf32f..0000000
--- a/book.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-  "title": "XOS-TOSCA User Documentation",
-  "root": "./docs"
-}
diff --git a/docs/GLOSSARY.md b/docs/GLOSSARY.md
deleted file mode 100644
index 3bef333..0000000
--- a/docs/GLOSSARY.md
+++ /dev/null
@@ -1,2 +0,0 @@
-# XOS-TOSCA Glossary
-
diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md
deleted file mode 100644
index 263112f..0000000
--- a/docs/SUMMARY.md
+++ /dev/null
@@ -1,4 +0,0 @@
-# Summary
-
-* [Introduction](README.md)
-
diff --git a/loader/Dockerfile.tosca-loader b/loader/Dockerfile.tosca-loader
index fd55517..cb58351 100644
--- a/loader/Dockerfile.tosca-loader
+++ b/loader/Dockerfile.tosca-loader
@@ -14,23 +14,21 @@
 
 # xosproject/tosca-loader
 
-FROM alpine:3.7
+FROM alpine:3.9.2
 
-RUN apk add --no-cache httpie=0.9.9-r0
+RUN apk add --no-cache httpie
 
 COPY tosca-loader.sh /usr/local/bin/tosca-loader.sh
 
 # Label image
-ARG org_label_schema_schema_version=1.0
-ARG org_label_schema_name=tosca-loader
 ARG org_label_schema_version=unknown
 ARG org_label_schema_vcs_url=unknown
 ARG org_label_schema_vcs_ref=unknown
 ARG org_label_schema_build_date=unknown
 ARG org_opencord_vcs_commit_date=unknown
 
-LABEL org.label-schema.schema-version=$org_label_schema_schema_version \
-      org.label-schema.name=$org_label_schema_name \
+LABEL org.label-schema.schema-version=1.0 \
+      org.label-schema.name=tosca-loader \
       org.label-schema.version=$org_label_schema_version \
       org.label-schema.vcs-url=$org_label_schema_vcs_url \
       org.label-schema.vcs-ref=$org_label_schema_vcs_ref \
diff --git a/loader/tosca-loader.sh b/loader/tosca-loader.sh
index 9f27a8b..dfd8e82 100755
--- a/loader/tosca-loader.sh
+++ b/loader/tosca-loader.sh
@@ -21,11 +21,12 @@
 
 for recipe in /opt/tosca/*
 do
-  echo "Loading: $recipe"
+  echo "Loading: $recipe, started at $(date -u '+%Y%m%d%H%M%SZ')"
   http --check-status --ignore-stdin \
        POST "http://xos-tosca:$XOS_TOSCA_SERVICE_PORT/run" \
        "xos-username:$XOS_USER" \
        "xos-password:$XOS_PASSWD" \
        "@$recipe" || exit 1
   echo ''
+  echo "Finished loading at $(date -u '+%Y%m%d%H%M%SZ')"
 done
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..4b44898
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,4 @@
+tosca-parser~=1.4.0
+xosgenx~=3.0.5
+xosapi~=3.0.5
+klein~=17.10.0
diff --git a/src/__init__.py b/src/__init__.py
new file mode 100644
index 0000000..19d1424
--- /dev/null
+++ b/src/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2019-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
diff --git a/src/grpc_client/KEYS.reference.py b/src/grpc_client/KEYS.reference.py
index fd8c776..6453117 100644
--- a/src/grpc_client/KEYS.reference.py
+++ b/src/grpc_client/KEYS.reference.py
@@ -19,57 +19,57 @@
 #########################################################################
 
 TOSCA_KEYS = {
-    'XOSBase': [],
-    'User': ['email'],
-    'Privilege': [],
-    'AddressPool': ['name'],
-    'Controller': ['name'],
-    'ControllerImages': [],
-    'ControllerNetwork': [],
-    'ControllerRole': [],
-    'ControllerSite': [],
-    'ControllerPrivilege': [],
-    'ControllerSitePrivilege': [],
-    'ControllerSlice': [],
-    'ControllerSlicePrivilege': [],
-    'ControllerUser': [],
-    'Deployment': ['name'],
-    'DeploymentPrivilege': [],
-    'DeploymentRole': [],
-    'Diag': ['name'],
-    'Flavor': ['name'],
-    'Image': ['name'],
-    'ImageDeployments': [],
-    'Instance': ['name'],
-    'Network': ['name'],
-    'NetworkParameter': [],
-    'NetworkParameterType': ['name'],
-    'NetworkSlice': ['network', 'slice'],
-    'NetworkTemplate': ['name'],
-    'Node': ['name'],
-    'NodeLabel': ['name'],
-    'Port': [],
-    'Role': [],
-    'Service': ['name'],
-    'ServiceAttribute': ['name'],
-    'ServiceDependency': ['provider_service'],
-    'ServiceMonitoringAgentInfo': ['name'],
-    'ServicePrivilege': [],
-    'ServiceRole': [],
-    'Site': ['name'],
-    'SiteDeployment': ['site', 'deployment'],
-    'SitePrivilege': ['site', 'role'],
-    'SiteRole': [],
-    'Slice': ['name'],
-    'SlicePrivilege': [],
-    'SliceRole': [],
-    'Tag': ['name'],
-    'InterfaceType': ['name'],
-    'ServiceInterface': ['service', 'interface_type'],
-    'ServiceInstance': ['name'],
-    'ServiceInstanceLink': ['provider_service_instance'],
-    'ServiceInstanceAttribute': ['name'],
-    'TenantWithContainer': ['name'],
-    'XOS': ['name'],
-    'XOSGuiExtension': ['name'],
-}
\ No newline at end of file
+    "XOSBase": [],
+    "User": ["email"],
+    "Privilege": [],
+    "AddressPool": ["name"],
+    "Controller": ["name"],
+    "ControllerImages": [],
+    "ControllerNetwork": [],
+    "ControllerRole": [],
+    "ControllerSite": [],
+    "ControllerPrivilege": [],
+    "ControllerSitePrivilege": [],
+    "ControllerSlice": [],
+    "ControllerSlicePrivilege": [],
+    "ControllerUser": [],
+    "Deployment": ["name"],
+    "DeploymentPrivilege": [],
+    "DeploymentRole": [],
+    "Diag": ["name"],
+    "Flavor": ["name"],
+    "Image": ["name"],
+    "ImageDeployments": [],
+    "Instance": ["name"],
+    "Network": ["name"],
+    "NetworkParameter": [],
+    "NetworkParameterType": ["name"],
+    "NetworkSlice": ["network", "slice"],
+    "NetworkTemplate": ["name"],
+    "Node": ["name"],
+    "NodeLabel": ["name"],
+    "Port": [],
+    "Role": [],
+    "Service": ["name"],
+    "ServiceAttribute": ["name"],
+    "ServiceDependency": ["provider_service"],
+    "ServiceMonitoringAgentInfo": ["name"],
+    "ServicePrivilege": [],
+    "ServiceRole": [],
+    "Site": ["name"],
+    "SiteDeployment": ["site", "deployment"],
+    "SitePrivilege": ["site", "role"],
+    "SiteRole": [],
+    "Slice": ["name"],
+    "SlicePrivilege": [],
+    "SliceRole": [],
+    "Tag": ["name"],
+    "InterfaceType": ["name"],
+    "ServiceInterface": ["service", "interface_type"],
+    "ServiceInstance": ["name"],
+    "ServiceInstanceLink": ["provider_service_instance"],
+    "ServiceInstanceAttribute": ["name"],
+    "TenantWithContainer": ["name"],
+    "XOS": ["name"],
+    "XOSGuiExtension": ["name"],
+}
diff --git a/src/grpc_client/__init__.py b/src/grpc_client/__init__.py
index d4e8062..c7879fb 100644
--- a/src/grpc_client/__init__.py
+++ b/src/grpc_client/__init__.py
@@ -1,4 +1,3 @@
-
 # Copyright 2017-present Open Networking Foundation
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,4 +12,10 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from __future__ import absolute_import
 
+from .main import GRPC_Client
+
+__all__ = [
+    'GRPC_Client',
+]
diff --git a/src/grpc_client/main.py b/src/grpc_client/main.py
index 619b43b..314081f 100644
--- a/src/grpc_client/main.py
+++ b/src/grpc_client/main.py
@@ -1,4 +1,3 @@
-
 # Copyright 2017-present Open Networking Foundation
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,23 +13,27 @@
 # limitations under the License.
 
 
-import functools
-from xosapi.xos_grpc_client import SecureClient, InsecureClient
-from twisted.internet import defer
-from resources import RESOURCES
-from xosconfig import Config
-from twisted.internet import reactor
+from __future__ import absolute_import
 
-from xosconfig import Config
+import functools
+
+from xosapi.xos_grpc_client import InsecureClient, SecureClient
+
 from multistructlog import create_logger
-log = create_logger(Config().get('logging'))
+from twisted.internet import defer, reactor
+from xosconfig import Config
+
+from .resources import RESOURCES
+
+log = create_logger(Config().get("logging"))
+
 
 class GRPC_Client:
     def __init__(self):
         self.client = None
 
-        insecure = Config.get('gprc_endpoint')
-        secure = Config.get('gprc_endpoint')
+        insecure = Config.get("gprc_endpoint")
+        secure = Config.get("gprc_endpoint")
 
         self.grpc_secure_endpoint = secure + ":50051"
         self.grpc_insecure_endpoint = insecure + ":50055"
@@ -55,26 +58,36 @@
         self.client = InsecureClient(endpoint=self.grpc_insecure_endpoint)
         self.client.restart_on_disconnect = True
 
-        self.client.set_reconnect_callback(functools.partial(deferred.callback, self.client))
+        self.client.set_reconnect_callback(
+            functools.partial(deferred.callback, self.client)
+        )
         self.client.start()
 
         return deferred
 
     def create_secure_client(self, username, password, arg):
         """
-        This method will check if this combination of username/password already has stored orm classes in RESOURCES, otherwise create them
+        This method will check if this combination of username/password already
+        has stored orm classes in RESOURCES, otherwise create them
         """
         deferred = defer.Deferred()
         key = "%s~%s" % (username, password)
         if key in RESOURCES:
             reactor.callLater(0, deferred.callback, arg)
         else:
-            local_cert = Config.get('local_cert')
-            client = SecureClient(endpoint=self.grpc_secure_endpoint, username=username, password=password, cacert=local_cert)
+            local_cert = Config.get("local_cert")
+            client = SecureClient(
+                endpoint=self.grpc_secure_endpoint,
+                username=username,
+                password=password,
+                cacert=local_cert,
+            )
             client.restart_on_disconnect = True
             # SecureClient is preceeded by an insecure client, so treat all secure clients as previously connected
             # See CORD-3152
             client.was_connected = True
-            client.set_reconnect_callback(functools.partial(self.setup_resources, client, key, deferred, arg))
+            client.set_reconnect_callback(
+                functools.partial(self.setup_resources, client, key, deferred, arg)
+            )
             client.start()
         return deferred
diff --git a/src/grpc_client/models_accessor.py b/src/grpc_client/models_accessor.py
index 2c96c48..445918a 100644
--- a/src/grpc_client/models_accessor.py
+++ b/src/grpc_client/models_accessor.py
@@ -1,4 +1,3 @@
-
 # Copyright 2017-present Open Networking Foundation
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,11 +12,22 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from xosconfig import Config
-from multistructlog import create_logger
-log = create_logger(Config().get('logging'))
+from __future__ import absolute_import
 
-from resources import RESOURCES
+from multistructlog import create_logger
+from xosconfig import Config
+
+from .resources import RESOURCES
+
+log = create_logger(Config().get("logging"))
+
+
+class GRPCModelsException(Exception):
+    """
+    Differentiates between exceptions created by GRPCModelsAccessor and other exceptions
+    """
+    pass
+
 
 class GRPCModelsAccessor:
     """
@@ -31,24 +41,31 @@
         """
 
         # NOTE: we need to import this later as it's generated by main.py
-        from KEYS import TOSCA_KEYS
+        from .KEYS import TOSCA_KEYS
 
         # get the key for this model
         try:
             filter_keys = TOSCA_KEYS[class_name]
-        except KeyError, e:
-            raise Exception("[XOS-TOSCA] Model %s doesn't have a tosca_key specified" % (class_name))
+        except KeyError:
+            raise GRPCModelsException(
+                "[XOS-TOSCA] Model %s doesn't have a tosca_key specified" % (class_name)
+            )
 
         if len(filter_keys) == 0:
-            raise Exception("[XOS-TOSCA] Model %s doesn't have a tosca_key specified" % (class_name))
+            raise GRPCModelsException(
+                "[XOS-TOSCA] Model %s doesn't have a tosca_key specified" % (class_name)
+            )
 
         filter = {}
         for k in filter_keys:
             if isinstance(k, str):
                 try:
                     filter[k] = data[k]
-                except KeyError, e:
-                    raise Exception("[XOS-TOSCA] Model %s doesn't have a property for the specified tosca_key (%s)" % (class_name, e))
+                except KeyError as e:
+                    raise GRPCModelsException(
+                        "[XOS-TOSCA] Model %s doesn't have a property for the specified tosca_key (%s)"
+                        % (class_name, e)
+                    )
             elif isinstance(k, list):
                 # one of they keys in this list has to be set
                 one_of_key = None
@@ -57,15 +74,23 @@
                         one_of_key = i
                         one_of_key_val = data[i]
                 if not one_of_key:
-                    raise Exception("[XOS-TOSCA] Model %s doesn't have a property for the specified tosca_key_one_of (%s)" % (class_name, k))
+                    raise GRPCModelsException(
+                        "[XOS-TOSCA] Model %s doesn't have a property for the specified tosca_key_one_of (%s)"
+                        % (class_name, k)
+                    )
                 else:
                     filter[one_of_key] = one_of_key_val
 
         key = "%s~%s" % (username, password)
-        if not key in RESOURCES:
-            raise Exception("[XOS-TOSCA] User '%s' does not have ready resources" % username)
+        if key not in RESOURCES:
+            raise GRPCModelsException(
+                "[XOS-TOSCA] User '%s' does not have ready resources" % username
+            )
         if class_name not in RESOURCES[key]:
-            raise Exception('[XOS-TOSCA] The model you are trying to create (class: %s, properties, %s) is not know by xos-core' % (class_name, str(filter)))
+            raise GRPCModelsException(
+                "[XOS-TOSCA] The model you are trying to create (class: %s, properties, %s) is not know by xos-core"
+                % (class_name, str(filter))
+            )
 
         cls = RESOURCES[key][class_name]
 
@@ -73,15 +98,27 @@
 
         if len(models) == 1:
             model = models[0]
-            log.info("[XOS-Tosca] Model of class %s and properties %s already exist, retrieving instance..." % (class_name, str(filter)), model=model)
+            log.info(
+                "[XOS-TOSCA] Model of class %s and properties %s already exist, retrieving instance..."
+                % (class_name, str(filter)),
+                model=model,
+            )
         elif len(models) == 0:
 
-            if 'must-exist' in data and data['must-exist']:
-                raise Exception("[XOS-TOSCA] Model of class %s and properties %s has property 'must-exist' but cannot be found" % (class_name, str(filter)))
+            if "must-exist" in data and data["must-exist"]:
+                raise GRPCModelsException(
+                    "[XOS-TOSCA] Model of class %s and properties %s has property 'must-exist' but cannot be found"
+                    % (class_name, str(filter))
+                )
 
             model = cls.objects.new()
-            log.info("[XOS-Tosca] Model (%s) is new, creating new instance..." % str(filter))
+            log.info(
+                "[XOS-TOSCA] Model (%s) is new, creating new instance..." % str(filter)
+            )
         else:
-            raise Exception("[XOS-Tosca] Model of class %s and properties %s has multiple instances, I can't handle it" % (class_name, str(filter)))
+            raise GRPCModelsException(
+                "[XOS-TOSCA] Model of class %s and properties %s has multiple instances, I can't handle it"
+                % (class_name, str(filter))
+            )
 
-        return model
\ No newline at end of file
+        return model
diff --git a/src/grpc_client/resources.py b/src/grpc_client/resources.py
index cfe3e2e..88ce137 100644
--- a/src/grpc_client/resources.py
+++ b/src/grpc_client/resources.py
@@ -1,4 +1,3 @@
-
 # Copyright 2017-present Open Networking Foundation
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,4 +14,4 @@
 
 
 # NOTE will add all the resources in this dictionary
-RESOURCES = {}
\ No newline at end of file
+RESOURCES = {}
diff --git a/src/main.py b/src/main.py
index 6b0359d..94955b0 100644
--- a/src/main.py
+++ b/src/main.py
@@ -1,4 +1,3 @@
-
 # Copyright 2017-present Open Networking Foundation
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,26 +12,28 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from __future__ import absolute_import
 
 import os
+
+from twisted.internet import defer
 from xosconfig import Config
 from multistructlog import create_logger
 
 current_dir = os.path.dirname(os.path.realpath(__file__))
-config_file = os.path.join(current_dir, 'xos-tosca.config.yaml')
-config_schema = os.path.join(current_dir, 'xos-tosca-config-schema.yaml')
+config_file = os.path.join(current_dir, "xos-tosca.config.yaml")
+config_schema = os.path.join(current_dir, "xos-tosca-config-schema.yaml")
 
 Config.init(config_file, config_schema)
-log = create_logger(Config().get('logging'))
+log = create_logger(Config().get("logging"))
 
-from grpc_client.main import GRPC_Client
-from tosca.generator import TOSCA_Generator
-from web_server.main import TOSCA_WebServer
-from twisted.internet import defer
+# config needs to be init before these imports, thus E402
+from grpc_client import GRPC_Client  # noqa: E402
+from tosca import TOSCA_Generator  # noqa: E402
+from web_server import TOSCA_WebServer  # noqa: E402
 
 
 class Main:
-
     def __init__(self):
         self.grpc_client = None
 
@@ -47,9 +48,11 @@
     def start(self):
         log.info("[XOS-TOSCA] Starting")
 
-        # Remove generated TOSCA and KEYS that may have been downloaded by a previous session. This is done here, rather
-        # than in the generator, to cover the case where the TOSCA engine is restarted and a web request is received
-        # and processed before generate_tosca() has completed. 
+        # Remove generated TOSCA and KEYS that may have been downloaded by a
+        # previous session. This is done here, rather than in the generator, to
+        # cover the case where the TOSCA engine is restarted and a web request
+        # is received and processed before generate_tosca() has completed.
+
         TOSCA_Generator().clean()
         TOSCA_Generator().clean_keys()
 
@@ -60,5 +63,5 @@
         TOSCA_WebServer()
 
 
-if __name__ == '__main__':
-    Main().start()
\ No newline at end of file
+if __name__ == "__main__":
+    Main().start()
diff --git a/src/tosca/__init__.py b/src/tosca/__init__.py
index d4e8062..cbdb48b 100644
--- a/src/tosca/__init__.py
+++ b/src/tosca/__init__.py
@@ -1,4 +1,3 @@
-
 # Copyright 2017-present Open Networking Foundation
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,4 +12,10 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from __future__ import absolute_import
 
+from .generator import TOSCA_Generator
+
+__all__ = [
+    'TOSCA_Generator',
+]
diff --git a/src/tosca/default.py b/src/tosca/default.py
index 4de0975..02d7de4 100644
--- a/src/tosca/default.py
+++ b/src/tosca/default.py
@@ -1,4 +1,3 @@
-
 # Copyright 2017-present Open Networking Foundation
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,8 +13,12 @@
 # limitations under the License.
 
 
+from __future__ import absolute_import
+
 import os
 
 TOSCA_DEFS_DIR = os.path.dirname(os.path.realpath(__file__)) + "/custom_types"
 TOSCA_RECIPES_DIR = os.path.dirname(os.path.realpath(__file__)) + "/"
-TOSCA_KEYS_DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/../grpc_client")
\ No newline at end of file
+TOSCA_KEYS_DIR = os.path.abspath(
+    os.path.dirname(os.path.realpath(__file__)) + "/../grpc_client"
+)
diff --git a/src/tosca/generator.py b/src/tosca/generator.py
index 1c2dccb..fbaf4e5 100644
--- a/src/tosca/generator.py
+++ b/src/tosca/generator.py
@@ -1,4 +1,3 @@
-
 # Copyright 2017-present Open Networking Foundation
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,24 +12,30 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from xosconfig import Config
-from multistructlog import create_logger
-log = create_logger(Config().get('logging'))
+from __future__ import absolute_import
 
 import os
-from default import TOSCA_DEFS_DIR, TOSCA_KEYS_DIR
-from xosgenx.generator import XOSProcessor, XOSProcessorArgs
+
 from xosapi.xos_grpc_client import Empty
+from xosgenx.generator import XOSProcessor, XOSProcessorArgs
+
+from multistructlog import create_logger
+from xosconfig import Config
+
+from .default import TOSCA_DEFS_DIR, TOSCA_KEYS_DIR
+
+log = create_logger(Config().get("logging"))
+
 
 current_dir = os.path.dirname(os.path.realpath(__file__))
 
-class TOSCA_Generator:
 
+class TOSCA_Generator:
     def clean(self, dir=TOSCA_DEFS_DIR):
         filesToRemove = [f for f in os.listdir(dir)]
         for f in filesToRemove:
-            if not f.startswith('.'):
-                os.remove(dir + '/' + f)
+            if not f.startswith("."):
+                os.remove(dir + "/" + f)
 
     def clean_keys(self, dir=TOSCA_KEYS_DIR):
         keys_fn = os.path.join(dir, "KEYS.py")
@@ -42,23 +47,27 @@
 
         try:
             xproto = client.utility.GetXproto(Empty())
-            args = XOSProcessorArgs(output = TOSCA_DEFS_DIR,
-                                    inputs = str(xproto.xproto),
-                                    target = os.path.join(current_dir, 'xtarget/tosca.xtarget'),
-                                    write_to_file = 'target')
+            args = XOSProcessorArgs(
+                output=TOSCA_DEFS_DIR,
+                inputs=str(xproto.xproto),
+                target=os.path.join(current_dir, "xtarget/tosca.xtarget"),
+                write_to_file="target",
+            )
             XOSProcessor.process(args)
             log.info("[XOS-TOSCA] Recipes generated in %s" % args.output)
-        except Exception as e:
+        except Exception:
             log.exception("[XOS-TOSCA] Failed to generate TOSCA")
 
         try:
             xproto = client.utility.GetXproto(Empty())
-            args = XOSProcessorArgs(output = TOSCA_KEYS_DIR,
-                                    inputs = str(xproto.xproto),
-                                    target = os.path.join(current_dir, 'xtarget/tosca_keys.xtarget'),
-                                    write_to_file = 'single',
-                                    dest_file = 'KEYS.py')
+            args = XOSProcessorArgs(
+                output=TOSCA_KEYS_DIR,
+                inputs=str(xproto.xproto),
+                target=os.path.join(current_dir, "xtarget/tosca_keys.xtarget"),
+                write_to_file="single",
+                dest_file="KEYS.py",
+            )
             XOSProcessor.process(args)
             log.info("[XOS-TOSCA] TOSCA Keys generated in %s" % args.output)
-        except Exception as e:
+        except Exception:
             log.exception("[XOS-TOSCA] Failed to generate TOSCA Keys")
diff --git a/src/tosca/parser.py b/src/tosca/parser.py
index 3f44c50..6643ac8 100644
--- a/src/tosca/parser.py
+++ b/src/tosca/parser.py
@@ -1,4 +1,3 @@
-
 # Copyright 2017-present Open Networking Foundation
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,23 +12,24 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from __future__ import absolute_import
 
+import json
 import os
 from tempfile import NamedTemporaryFile
-from xosconfig import Config
-from multistructlog import create_logger
-log = create_logger(Config().get('logging'))
 
-from toscaparser.tosca_template import ToscaTemplate, ValidationError
-from default import TOSCA_RECIPES_DIR
-from grpc_client.resources import RESOURCES
-from grpc_client.models_accessor import GRPCModelsAccessor
 from grpc._channel import _Rendezvous
-import json
-import traceback
+from grpc_client.models_accessor import GRPCModelsAccessor, GRPCModelsException
+from multistructlog import create_logger
+from toscaparser.tosca_template import ToscaTemplate, ValidationError
+from xosconfig import Config
+
+from .default import TOSCA_RECIPES_DIR
+
+log = create_logger(Config().get("logging"))
+
 
 class TOSCA_Parser:
-
     def compute_dependencies(self, template, models_by_name):
         """
         NOTE this method is augmenting self.template, isn't there a more explicit way to achieve it?
@@ -38,18 +38,18 @@
             nodetemplate.dependencies = []
             nodetemplate.dependencies_names = []
             for reqs in nodetemplate.requirements:
-                for (k,v) in reqs.items():
+                for (k, v) in reqs.items():
                     name = v["node"]
-                    if (name in models_by_name):
+                    if name in models_by_name:
                         nodetemplate.dependencies.append(models_by_name[name])
                         nodetemplate.dependencies_names.append(name)
 
                     # go another level deep, as our requirements can have requirements...
                     # NOTE do we still need to go deep?
-                    for sd_req in v.get("requirements",[]):
+                    for sd_req in v.get("requirements", []):
                         for (sd_req_k, sd_req_v) in sd_req.items():
                             name = sd_req_v["node"]
-                            if (name in models_by_name):
+                            if name in models_by_name:
                                 nodetemplate.dependencies.append(models_by_name[name])
                                 nodetemplate.dependencies_names.append(name)
 
@@ -63,8 +63,8 @@
             values = values | set(v.dependencies_names)
 
         all_nodes = list(keys | values)
-        steps = all_nodes
 
+        steps = all_nodes
 
         # Final order
         order = []
@@ -72,26 +72,26 @@
         # DFS stack, not using recursion
         stack = []
 
-        # Unmarked set
-        unmarked = all_nodes
+        # Unmarked set, alpha sorted to provide determinism not present in dicts
+        unmarked = sorted(all_nodes)
 
         # visiting = [] - skip, don't expect 1000s of nodes, |E|/|V| is small
 
         while unmarked:
             stack.insert(0, unmarked[0])  # push first unmarked
 
-            while (stack):
+            while stack:
                 n = stack[0]
                 add = True
                 try:
                     for m in g[n].dependencies_names:
-                        if (m in unmarked):
+                        if m in unmarked:
                             add = False
                             stack.insert(0, m)
                 except KeyError:
                     pass
-                if (add):
-                    if (n in steps and n not in order):
+                if add:
+                    if n in steps and n not in order:
                         order.append(n)
                     item = stack.pop(0)
                     try:
@@ -99,33 +99,39 @@
                     except ValueError:
                         pass
 
-        noorder = list(set(steps) - set(order))
+        # remove ordred items from steps, then alpha sort unordered items (determinism)
+        noorder = sorted(list(set(steps) - set(order)))
         return order + noorder
 
     @staticmethod
     def populate_model(model, data):
-        for k,v in data.iteritems():
+        for key in sorted(data):
             # NOTE must-exists is a TOSCA implementation choice, remove it before saving the model
-            if k != "must-exist":
+            if key != "must-exist":
                 try:
-                    setattr(model, k, v)
-                except TypeError, e:
-                    raise Exception('Failed to set %s on field %s for class %s, Exception was: "%s"' % (v, k, model.model_name, e))
+                    setattr(model, key, data[key])
+                except TypeError as e:
+                    raise Exception(
+                        'Failed to set %s on field %s for class %s, Exception was: "%s"'
+                        % (data[key], key, model.model_name, e)
+                    )
         return model
 
     @staticmethod
     def _translate_exception(msg):
         readable = []
         for line in msg.splitlines():
-            if line.strip().startswith('MissingRequiredFieldError') or \
-                    line.strip().startswith('UnknownFieldError') or \
-                    line.strip().startswith('ImportError') or \
-                    line.strip().startswith('InvalidTypeError') or \
-                    line.strip().startswith('TypeMismatchError'):
+            if (
+                line.strip().startswith("MissingRequiredFieldError")
+                or line.strip().startswith("UnknownFieldError")
+                or line.strip().startswith("ImportError")
+                or line.strip().startswith("InvalidTypeError")
+                or line.strip().startswith("TypeMismatchError")
+            ):
                 readable.append(line)
 
         if len(readable) > 0:
-            return '\n'.join(readable) + '\n'
+            return "\n".join(readable) + "\n"
         else:
             return msg
 
@@ -147,24 +153,24 @@
     @staticmethod
     def populate_dependencies(model, requirements, saved_models):
         for dep in requirements:
-            class_name = dep.keys()[0]
-            related_model = saved_models[dep[class_name]['node']]
+            class_name = list(dep.keys())[0]
+            related_model = saved_models[dep[class_name]["node"]]
             setattr(model, "%s_id" % class_name, related_model.id)
         return model
 
     @staticmethod
     def add_dependencies(data, requirements, saved_models):
         for dep in requirements:
-            class_name = dep.keys()[0]
-            related_model = saved_models[dep[class_name]['node']]
+            class_name = list(dep.keys())[0]
+            related_model = saved_models[dep[class_name]["node"]]
             data["%s_id" % class_name] = related_model.id
         return data
 
     def __init__(self, recipe, username, password, **kwargs):
 
         self.delete = False
-        if 'delete' in kwargs:
-            self.delete = True
+        if "delete" in kwargs:
+            self.delete = kwargs["delete"]
 
         # store username/password combination to read resources
         self.username = username
@@ -187,7 +193,9 @@
 
         try:
             # [] save the recipe to a tmp file
-            with NamedTemporaryFile(delete=False, suffix=".yaml", dir=TOSCA_RECIPES_DIR) as recipe_file:
+            with NamedTemporaryFile(
+                mode="w", delete=False, suffix=".yaml", dir=TOSCA_RECIPES_DIR
+            ) as recipe_file:
                 try:
                     recipe_file.write(self.recipe)
                     recipe_file.close()
@@ -203,49 +211,65 @@
             # [] compute requirements
             self.compute_dependencies(self.template, self.templates_by_model_name)
             # [] topsort requirements
-            self.ordered_models_name = self.topsort_dependencies(self.templates_by_model_name)
+            self.ordered_models_name = self.topsort_dependencies(
+                self.templates_by_model_name
+            )
             # [] topsort templates
-            self.ordered_models_template = self.get_ordered_models_template(self.ordered_models_name, self.templates_by_model_name)
+            self.ordered_models_template = self.get_ordered_models_template(
+                self.ordered_models_name, self.templates_by_model_name
+            )
 
             for recipe in self.ordered_models_template:
                 try:
                     # get properties from tosca
-                    if not 'properties' in recipe.templates[recipe.name]:
+                    if "properties" not in recipe.templates[recipe.name]:
                         data = {}
                     else:
-                        data = recipe.templates[recipe.name]['properties']
-                        if data == None:
+                        data = recipe.templates[recipe.name]["properties"]
+                        if data is None:
                             data = {}
                     # [] get model by class name
                     class_name = recipe.type.replace("tosca.nodes.", "")
 
                     # augemnt data with relations
-                    data = self.add_dependencies(data, recipe.requirements, self.saved_model_by_name)
+                    data = self.add_dependencies(
+                        data, recipe.requirements, self.saved_model_by_name
+                    )
 
-                    model = GRPCModelsAccessor.get_model_from_classname(class_name, data, self.username, self.password)
+                    model = GRPCModelsAccessor.get_model_from_classname(
+                        class_name, data, self.username, self.password
+                    )
                     # [] populate model with data
                     model = self.populate_model(model, data)
                     # [] check if the model has requirements
                     # [] if it has populate them
-                    model = self.populate_dependencies(model, recipe.requirements, self.saved_model_by_name)
+                    model = self.populate_dependencies(
+                        model, recipe.requirements, self.saved_model_by_name
+                    )
                     # [] save, update or delete
 
                     reference_only = False
-                    if 'must-exist' in data:
+                    if "must-exist" in data:
                         reference_only = True
 
                     if self.delete and not model.is_new and not reference_only:
-                        log.info("[XOS-Tosca] Deleting model %s[%s]" % (class_name, model.id))
+                        log.info(
+                            "[XOS-Tosca] Deleting model %s[%s]" % (class_name, model.id)
+                        )
                         model.delete()
                     elif not self.delete:
-                        log.info("[XOS-Tosca] Saving model %s[%s]" % (class_name, model.id))
+                        log.info(
+                            "[XOS-Tosca] Saving model %s[%s]" % (class_name, model.id)
+                        )
                         model.save()
 
-
                     self.saved_model_by_name[recipe.name] = model
-                except Exception, e:
-                    log.exception("[XOS-TOSCA] Failed to save model: %s [%s]" % (class_name, recipe.name))
-                    raise e
+
+                except GRPCModelsException as e:
+                    raise Exception(
+                        "[XOS-TOSCA] Failed to save or delete model %s [%s]: %s"
+                        % (class_name, recipe.name, str(e))
+                    )
 
         except ValidationError as e:
             if e.message:
@@ -254,17 +278,15 @@
                 exception_msg = TOSCA_Parser._translate_exception(str(e))
             raise Exception(exception_msg)
 
-        except _Rendezvous, e:
+        except _Rendezvous as e:
             try:
                 details = json.loads(e._state.details)
                 exception_msg = details["error"]
                 if "specific_error" in details:
-                    exception_msg = "%s: %s" % (exception_msg, details["specific_error"])
+                    exception_msg = "%s: %s" % (
+                        exception_msg,
+                        details["specific_error"],
+                    )
             except Exception:
                 exception_msg = e._state.details
             raise Exception(exception_msg)
-        except Exception, e:
-            log.exception(e)
-            raise Exception(e)
-
-
diff --git a/src/tosca/xtarget/tosca_keys.xtarget b/src/tosca/xtarget/tosca_keys.xtarget
index 9569061..6b73cb3 100644
--- a/src/tosca/xtarget/tosca_keys.xtarget
+++ b/src/tosca/xtarget/tosca_keys.xtarget
@@ -2,4 +2,5 @@
 {%- for m in proto.messages %}
     '{{ m.name }}': {{ xproto_fields_to_tosca_keys(m.fields + xproto_base_fields(m, proto.message_table), m) }},
 {%- endfor %}
-}
\ No newline at end of file
+}
+
diff --git a/src/web_server/__init__.py b/src/web_server/__init__.py
index d4e8062..ae374a7 100644
--- a/src/web_server/__init__.py
+++ b/src/web_server/__init__.py
@@ -1,4 +1,3 @@
-
 # Copyright 2017-present Open Networking Foundation
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,4 +12,10 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from __future__ import absolute_import
 
+from .main import TOSCA_WebServer
+
+__all__ = [
+    'TOSCA_WebServer',
+]
diff --git a/src/web_server/main.py b/src/web_server/main.py
index 271b1b2..34a83f5 100644
--- a/src/web_server/main.py
+++ b/src/web_server/main.py
@@ -1,4 +1,3 @@
-
 # Copyright 2017-present Open Networking Foundation
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,29 +12,34 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from xosconfig import Config
-from multistructlog import create_logger
-log = create_logger(Config().get('logging'))
+from __future__ import absolute_import, print_function
 
-from grpc_client.main import GRPC_Client
-from klein import Klein
-import os
-from tosca.parser import TOSCA_Parser
-from tosca.default import TOSCA_DEFS_DIR
 import json
+import os
 
-BANNER = """
-   _  ______  _____    __________  _____ _________ 
+from grpc_client import GRPC_Client
+from klein import Klein
+from multistructlog import create_logger
+from tosca.default import TOSCA_DEFS_DIR
+from tosca.parser import TOSCA_Parser
+from xosconfig import Config
+
+log = create_logger(Config().get("logging"))
+
+
+BANNER = r"""
+   _  ______  _____    __________  _____ _________
   | |/ / __ \/ ___/   /_  __/ __ \/ ___// ____/   |
   |   / / / /\__ \     / / / / / /\__ \/ /   / /| |
  /   / /_/ /___/ /    / / / /_/ /___/ / /___/ ___ |
 /_/|_\____//____/    /_/  \____//____/\____/_/  |_|
 """
 
+
 class TOSCA_WebServer:
 
     current_dir = os.path.dirname(os.path.realpath(__file__))
-    template_dir = os.path.join(current_dir, 'templates/')
+    template_dir = os.path.join(current_dir, "templates/")
 
     app = Klein()
 
@@ -59,48 +63,43 @@
             log.info("[XOS-TOSCA] Fatal Error: \n\n", failure=failure)
             return "Internal server error, please report this along with the failed recipe."
 
-    @app.route('/', methods=['GET'])
+    @app.route("/", methods=["GET"])
     def index(self, request):
         request.responseHeaders.addRawHeader(b"content-type", b"application/json")
-        tosca_defs = [f for f in os.listdir(TOSCA_DEFS_DIR) if not f.startswith('.')]
+        tosca_defs = [f for f in os.listdir(TOSCA_DEFS_DIR) if not f.startswith(".")]
 
         response = {}
         for d in tosca_defs:
-            name = d.replace('.yaml', '')
+            name = d.replace(".yaml", "")
             response[name] = "/custom_type/%s" % name
         return json.dumps(response)
 
     @app.route("/custom_type/<name>")
     def custom_type(self, request, name):
         request.responseHeaders.addRawHeader(b"content-type", b"text/plain")
-        custom_type = open(TOSCA_DEFS_DIR + '/' + name + '.yaml').read()
+        custom_type = open(TOSCA_DEFS_DIR + "/" + name + ".yaml").read()
         return custom_type
 
-    @app.route('/run', methods=['POST'])
+    @app.route("/run", methods=["POST"])
     def run(self, request):
-        recipe = request.content.read()
-        headers = request.getAllHeaders()
-        username = headers['xos-username']
-        password = headers['xos-password']
+        return self._handle_post(request, delete=False)
 
-        parser = TOSCA_Parser(recipe, username, password)
-        d = GRPC_Client().create_secure_client(username, password, parser)
-        tosca_execution = d.addCallback(self.execute_tosca)
-        tosca_execution.addErrback(self.errorCallback, request)
-        return d
-
-    @app.route('/delete', methods=['POST'])
+    @app.route("/delete", methods=["POST"])
     def delete(self, request):
-        recipe = request.content.read()
-        headers = request.getAllHeaders()
-        username = headers['xos-username']
-        password = headers['xos-password']
+        return self._handle_post(request, delete=True)
 
-        parser = TOSCA_Parser(recipe, username, password, delete=True)
+    def _handle_post(self, request, delete=False):
+
+        headers = request.getAllHeaders()
+        username = headers["xos-username"]
+        password = headers["xos-password"]
+        recipe = request.content.read()
+
+        parser = TOSCA_Parser(recipe, username, password, delete=delete)
         d = GRPC_Client().create_secure_client(username, password, parser)
         tosca_execution = d.addCallback(self.execute_tosca)
         tosca_execution.addErrback(self.errorCallback, request)
         return d
 
     def __init__(self):
-        self.app.run('0.0.0.0', '9102')
\ No newline at end of file
+        self.app.run("0.0.0.0", "9102")
diff --git a/src/xos-tosca-config-schema.yaml b/src/xos-tosca-config-schema.yaml
index db68a43..281bf77 100644
--- a/src/xos-tosca-config-schema.yaml
+++ b/src/xos-tosca-config-schema.yaml
@@ -1,4 +1,4 @@
-
+---
 # Copyright 2017-present Open Networking Foundation
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,16 +13,15 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-
 map:
   name:
     type: str
-    required: True
+    required: true
   gprc_endpoint:
     type: str
-    required: True
+    required: true
   local_cert:
     type: str
-    required: True
+    required: true
   logging:
     type: any
diff --git a/src/xos-tosca.config.yaml b/src/xos-tosca.config.yaml
new file mode 100644
index 0000000..4231b3b
--- /dev/null
+++ b/src/xos-tosca.config.yaml
@@ -0,0 +1,14 @@
+---
+name: "xos-tosca"
+gprc_endpoint: "xos-core:30001"
+local_cert: ""
+logging:
+  version: 1
+  handlers:
+    console:
+      class: logging.StreamHandler
+  loggers:
+    'multistructlog':
+      handlers:
+        - console
+      level: ERROR
diff --git a/test/__init__.py b/test/__init__.py
new file mode 100644
index 0000000..19d1424
--- /dev/null
+++ b/test/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2019-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
diff --git a/test/helpers.py b/test/helpers.py
index 2a07499..91f70fa 100644
--- a/test/helpers.py
+++ b/test/helpers.py
@@ -14,8 +14,9 @@
 
 import os
 from xosconfig import Config
+
 current_dir = os.path.dirname(os.path.realpath(__file__))
-config_file = os.path.join(current_dir, 'test_config.yaml')
-config_schema = os.path.join(current_dir, '../src/xos-tosca-config-schema.yaml')
+config_file = os.path.join(current_dir, "test_config.yaml")
+config_schema = os.path.join(current_dir, "../src/xos-tosca-config-schema.yaml")
 Config.clear()
-Config.init(config_file, config_schema)
\ No newline at end of file
+Config.init(config_file, config_schema)
diff --git a/test/test_grpc_models_accessor.py b/test/test_grpc_models_accessor.py
index a385f86..0ac10b1 100644
--- a/test/test_grpc_models_accessor.py
+++ b/test/test_grpc_models_accessor.py
@@ -1,4 +1,3 @@
-
 # Copyright 2017-present Open Networking Foundation
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,60 +12,74 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from helpers import *
+from __future__ import absolute_import
+from . import helpers  # noqa: F401
 import unittest
-from mock import patch, MagicMock
+
+try:  # python 3
+    from unittest.mock import patch, MagicMock
+except ImportError:  # python 2
+    from mock import patch, MagicMock
+
 from grpc_client.models_accessor import GRPCModelsAccessor
 from grpc_client.resources import RESOURCES
 from grpc_client.KEYS import TOSCA_KEYS
 
+
 class FakeObj:
     new = None
     filter = None
 
+
 class FakeResource:
     objects = FakeObj
 
+
 class FakeModel:
     pass
 
+
 class FakeExistingModel:
     pass
 
+
 mock_resources = {
-    'username~pass': {
-        'test-model': FakeResource,
-        'single-key': FakeResource,
-        'double-key': FakeResource,
-        'one-of-key': FakeResource
+    "username~pass": {
+        "test-model": FakeResource,
+        "single-key": FakeResource,
+        "double-key": FakeResource,
+        "one-of-key": FakeResource,
     }
 }
 
 mock_keys = {
-    'i-do-not-exists': ['name'],
-    'test-model': ['name'],
-    'empty-key': [],
-    'single-key': ['fake_key'],
-    'double-key': ['key_1', 'key_2'],
-    'one-of-key': ['key_1', ['key_2', 'key_3']],
+    "i-do-not-exists": ["name"],
+    "test-model": ["name"],
+    "empty-key": [],
+    "single-key": ["fake_key"],
+    "double-key": ["key_1", "key_2"],
+    "one-of-key": ["key_1", ["key_2", "key_3"]],
 }
 
-USERNAME = 'username'
-PASSWORD = 'pass'
+USERNAME = "username"
+PASSWORD = "pass"
+
 
 class GRPCModelsAccessor_Create_or_update_Test(unittest.TestCase):
-
     @patch.dict(TOSCA_KEYS, mock_keys, clear=True)
     def test_unkown_user(self):
         """
         [GRPCModelsAccessor] get_model_from_classname: If a user does not have orm classes, raise
         """
-        data = {
-            "name": "test"
-        }
+        data = {"name": "test"}
         with self.assertRaises(Exception) as e:
-            GRPCModelsAccessor.get_model_from_classname('i-do-not-exists', data, USERNAME, PASSWORD)
-        self.assertEqual(e.exception.message, "[XOS-TOSCA] User 'username' does not have ready resources")
+            GRPCModelsAccessor.get_model_from_classname(
+                "i-do-not-exists", data, USERNAME, PASSWORD
+            )
+        self.assertEqual(
+            str(e.exception),
+            "[XOS-TOSCA] User 'username' does not have ready resources",
+        )
 
     @patch.dict(RESOURCES, mock_resources, clear=True)
     @patch.dict(TOSCA_KEYS, mock_keys, clear=True)
@@ -74,47 +87,60 @@
         """
         [GRPCModelsAccessor] get_model_from_classname: If a model is not know by the grpc api, raise
         """
-        data = {
-            "name": "test"
-        }
+        data = {"name": "test"}
         with self.assertRaises(Exception) as e:
-            GRPCModelsAccessor.get_model_from_classname('i-do-not-exists', data, USERNAME, PASSWORD)
-        self.assertEqual(e.exception.message, "[XOS-TOSCA] The model you are trying to create (class: i-do-not-exists, properties, {'name': 'test'}) is not know by xos-core")
+            GRPCModelsAccessor.get_model_from_classname(
+                "i-do-not-exists", data, USERNAME, PASSWORD
+            )
+        self.assertEqual(
+            str(e.exception),
+            "[XOS-TOSCA] The model you are trying to create "
+            "(class: i-do-not-exists, properties, {'name': 'test'}) is not know by xos-core",
+        )
 
     def test_unkown_tosca_key(self):
         """
         [GRPCModelsAccessor] get_model_from_classname: If a model does not have a tosca_key, raise
         """
-        data = {
-            "name": "test"
-        }
+        data = {"name": "test"}
         with self.assertRaises(Exception) as e:
-            GRPCModelsAccessor.get_model_from_classname('no-key', data, USERNAME, PASSWORD)
-        self.assertEqual(e.exception.message, "[XOS-TOSCA] Model no-key doesn't have a tosca_key specified")
+            GRPCModelsAccessor.get_model_from_classname(
+                "no-key", data, USERNAME, PASSWORD
+            )
+        self.assertEqual(
+            str(e.exception),
+            "[XOS-TOSCA] Model no-key doesn't have a tosca_key specified",
+        )
 
     @patch.dict(TOSCA_KEYS, mock_keys, clear=True)
     def test_empty_tosca_key(self):
         """
         [GRPCModelsAccessor] get_model_from_classname: If a model does not have a tosca_key, raise
         """
-        data = {
-            "name": "test"
-        }
+        data = {"name": "test"}
         with self.assertRaises(Exception) as e:
-            GRPCModelsAccessor.get_model_from_classname('empty-key', data, USERNAME, PASSWORD)
-        self.assertEqual(e.exception.message, "[XOS-TOSCA] Model empty-key doesn't have a tosca_key specified")
+            GRPCModelsAccessor.get_model_from_classname(
+                "empty-key", data, USERNAME, PASSWORD
+            )
+        self.assertEqual(
+            str(e.exception),
+            "[XOS-TOSCA] Model empty-key doesn't have a tosca_key specified",
+        )
 
     @patch.dict(TOSCA_KEYS, mock_keys, clear=True)
     def test_tosca_key_are_defined(self):
         """
         [GRPCModelsAccessor] get_model_from_classname: a model should have a property for it's tosca_key
         """
-        data = {
-            "name": "test",
-        }
+        data = {"name": "test"}
         with self.assertRaises(Exception) as e:
-            GRPCModelsAccessor.get_model_from_classname('single-key', data, USERNAME, PASSWORD)
-        self.assertEqual(e.exception.message, "[XOS-TOSCA] Model single-key doesn't have a property for the specified tosca_key ('fake_key')")
+            GRPCModelsAccessor.get_model_from_classname(
+                "single-key", data, USERNAME, PASSWORD
+            )
+        self.assertEqual(
+            str(e.exception),
+            "[XOS-TOSCA] Model single-key doesn't have a property for the specified tosca_key ('fake_key')",
+        )
 
     @patch.object(FakeResource.objects, "filter")
     @patch.object(FakeResource.objects, "new", MagicMock(return_value=FakeModel))
@@ -123,13 +149,11 @@
         """
         [GRPCModelsAccessor] get_model_from_classname: should use a composite key to lookup a model
         """
-        data = {
-            "name": "test",
-            "key_1": "key1",
-            "key_2": "key2"
-        }
+        data = {"name": "test", "key_1": "key1", "key_2": "key2"}
         with patch.dict(RESOURCES, mock_resources, clear=True):
-            model = GRPCModelsAccessor.get_model_from_classname('double-key', data, USERNAME, PASSWORD)
+            model = GRPCModelsAccessor.get_model_from_classname(
+                "double-key", data, USERNAME, PASSWORD
+            )
             mock_filter.assert_called_with(key_1="key1", key_2="key2")
             self.assertEqual(model, FakeModel)
 
@@ -141,23 +165,19 @@
         [GRPCModelsAccessor] get_model_from_classname: should use a composite with one_of key to lookup a model
         """
         # NOTE it should be valid for items with either one of the keys
-        data2 = {
-            "name": "test",
-            "key_1": "key1",
-            "key_2": "key2"
-        }
+        data2 = {"name": "test", "key_1": "key1", "key_2": "key2"}
         with patch.dict(RESOURCES, mock_resources, clear=True):
-            model = GRPCModelsAccessor.get_model_from_classname('one-of-key', data2, USERNAME, PASSWORD)
+            model = GRPCModelsAccessor.get_model_from_classname(
+                "one-of-key", data2, USERNAME, PASSWORD
+            )
             mock_filter.assert_called_with(key_1="key1", key_2="key2")
             self.assertEqual(model, FakeModel)
 
-        data3 = {
-            "name": "test",
-            "key_1": "key1",
-            "key_3": "key3"
-        }
+        data3 = {"name": "test", "key_1": "key1", "key_3": "key3"}
         with patch.dict(RESOURCES, mock_resources, clear=True):
-            model = GRPCModelsAccessor.get_model_from_classname('one-of-key', data3, USERNAME, PASSWORD)
+            model = GRPCModelsAccessor.get_model_from_classname(
+                "one-of-key", data3, USERNAME, PASSWORD
+            )
             mock_filter.assert_called_with(key_1="key1", key_3="key3")
             self.assertEqual(model, FakeModel)
 
@@ -165,13 +185,16 @@
     @patch.object(FakeResource.objects, "new", MagicMock(return_value=FakeModel))
     @patch.dict(TOSCA_KEYS, mock_keys, clear=True)
     def test_one_of_key_error(self, mock_filter):
-        data = {
-            "name": "test",
-            "key_1": "key1"
-        }
+        data = {"name": "test", "key_1": "key1"}
         with self.assertRaises(Exception) as e:
-            GRPCModelsAccessor.get_model_from_classname('one-of-key', data, USERNAME, PASSWORD)
-        self.assertEqual(e.exception.message, "[XOS-TOSCA] Model one-of-key doesn't have a property for the specified tosca_key_one_of (['key_2', 'key_3'])")
+            GRPCModelsAccessor.get_model_from_classname(
+                "one-of-key", data, USERNAME, PASSWORD
+            )
+        self.assertEqual(
+            str(e.exception),
+            "[XOS-TOSCA] Model one-of-key doesn't have a property "
+            "for the specified tosca_key_one_of (['key_2', 'key_3'])",
+        )
 
     @patch.object(FakeResource.objects, "filter")
     @patch.object(FakeResource.objects, "new", MagicMock(return_value=FakeModel))
@@ -180,42 +203,47 @@
         """
         [GRPCModelsAccessor] get_model_from_classname: should create a new model
         """
-        data = {
-            "name": "test",
-            "fake_key": "key"
-        }
+        data = {"name": "test", "fake_key": "key"}
         with patch.dict(RESOURCES, mock_resources, clear=True):
-            model = GRPCModelsAccessor.get_model_from_classname('single-key', data, USERNAME, PASSWORD)
+            model = GRPCModelsAccessor.get_model_from_classname(
+                "single-key", data, USERNAME, PASSWORD
+            )
             mock_filter.assert_called_with(fake_key="key")
             self.assertEqual(model, FakeModel)
 
-    @patch.object(FakeResource.objects, "filter", MagicMock(return_value=[FakeExistingModel]))
+    @patch.object(
+        FakeResource.objects, "filter", MagicMock(return_value=[FakeExistingModel])
+    )
     @patch.dict(TOSCA_KEYS, mock_keys, clear=True)
     def test_existing_model(self):
         """
         [GRPCModelsAccessor] get_model_from_classname: should update an existing model
         """
-        data = {
-            "name": "test",
-            "fake_key": "key"
-        }
+        data = {"name": "test", "fake_key": "key"}
         with patch.dict(RESOURCES, mock_resources, clear=True):
-            model = GRPCModelsAccessor.get_model_from_classname('single-key', data, USERNAME, PASSWORD)
+            model = GRPCModelsAccessor.get_model_from_classname(
+                "single-key", data, USERNAME, PASSWORD
+            )
             self.assertEqual(model, FakeExistingModel)
 
-    @patch.object(FakeResource.objects, "filter", MagicMock(return_value=['a', 'b']))
+    @patch.object(FakeResource.objects, "filter", MagicMock(return_value=["a", "b"]))
     @patch.dict(TOSCA_KEYS, mock_keys, clear=True)
     def test_multiple_models(self):
         """
         [GRPCModelsAccessor] get_model_from_classname: should raise an exception if multiple instances are found
         """
-        data = {
-            "name": "test"
-        }
+        data = {"name": "test"}
         with patch.dict(RESOURCES, mock_resources, clear=True):
             with self.assertRaises(Exception) as e:
-                GRPCModelsAccessor.get_model_from_classname('test-model', data, USERNAME, PASSWORD)
-            self.assertEqual(e.exception.message, "[XOS-Tosca] Model of class test-model and properties {'name': 'test'} has multiple instances, I can't handle it")
+                GRPCModelsAccessor.get_model_from_classname(
+                    "test-model", data, USERNAME, PASSWORD
+                )
+            self.assertEqual(
+                str(e.exception),
+                "[XOS-TOSCA] Model of class test-model and properties "
+                "{'name': 'test'} has multiple instances, I can't handle it",
+            )
 
-if __name__ == '__main__':
-    unittest.main()
\ No newline at end of file
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/test/test_tosca_generator.py b/test/test_tosca_generator.py
index f76dce5..871ca18 100644
--- a/test/test_tosca_generator.py
+++ b/test/test_tosca_generator.py
@@ -1,4 +1,3 @@
-
 # Copyright 2017-present Open Networking Foundation
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,38 +12,55 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from helpers import *
+from __future__ import absolute_import
+from __future__ import print_function
+from . import helpers  # noqa: F401
 import unittest
 import os
 from xosgenx.generator import XOSProcessor, XOSProcessorArgs
 
 current_dir = os.path.dirname(os.path.realpath(__file__))
-OUTPUT_DIR = os.path.join(current_dir, 'out');
-print OUTPUT_DIR
+OUTPUT_DIR = os.path.join(current_dir, "out")
+print(OUTPUT_DIR)
+
 
 class TOSCA_Generator_Test(unittest.TestCase):
-
     def test_generate_basic_tosca(self):
         """
         [TOSCA_xtarget] Should generate a basic TOSCA recipe
         """
-        xproto = \
-            """
+        xproto = """
             option app_label = "core";
 
             message XOSGuiExtension (XOSBase) {
                  option verbose_name="XOS GUI Extension";
                  option description="This model holds the instruction to load an extension in the GUI";
-                 required string name = 1 [max_length = 200, content_type = "stripped", blank = False, help_text = "Name of the GUI Extensions", null = False, db_index = False];
-                 required string files = 2 [max_length = 1024, content_type = "stripped", blank = False, help_text = "List of comma separated file composing the view", null = False, db_index = False];
+                 required string name = 1 [
+                    max_length = 200,
+                    content_type = "stripped",
+                    blank = False,
+                    help_text = "Name of the GUI Extensions",
+                    null = False,
+                    db_index = False
+                    ];
+                 required string files = 2 [
+                    max_length = 1024,
+                    content_type = "stripped",
+                    blank = False,
+                    help_text = "List of comma separated file composing the view",
+                    null = False,
+                    db_index = False
+                    ];
             }
             """
-        args = XOSProcessorArgs(inputs = xproto,
-                                target = os.path.join(current_dir, '../src/tosca/xtarget/tosca.xtarget'),
-                                output = OUTPUT_DIR,
-                                write_to_file = "single",
-                                dest_file = "basic.yaml",
-                                quiet = False)
+        args = XOSProcessorArgs(
+            inputs=xproto,
+            target=os.path.join(current_dir, "../src/tosca/xtarget/tosca.xtarget"),
+            output=OUTPUT_DIR,
+            write_to_file="single",
+            dest_file="basic.yaml",
+            quiet=False,
+        )
         output = XOSProcessor.process(args)
         self.assertIn("name:", output)
         self.assertIn("files:", output)
@@ -53,28 +69,42 @@
         """
         [TOSCA_xtarget] Should generate a TOSCA recipe for a models that inherits from another model
         """
-        xproto = \
-            """
+        xproto = """
             option app_label = "core";
 
             message Service (XosBase) {
                  option verbose_name="Basic Service";
-                 required string name = 1 [max_length = 200, content_type = "stripped", blank = False, null = False, db_index = False];
+                 required string name = 1 [
+                    max_length = 200,
+                    content_type = "stripped",
+                    blank = False,
+                    null = False,
+                    db_index = False
+                    ];
             }
-            
+
             message MyService (Service) {
                  option verbose_name="Extending service";
-                 required string prop = 1 [max_length = 200, content_type = "stripped", blank = False, null = False, db_index = False];
+                 required string prop = 1 [
+                    max_length = 200,
+                    content_type = "stripped",
+                    blank = False,
+                    null = False,
+                    db_index = False
+                    ];
             }
             """
-        args = XOSProcessorArgs(inputs = xproto,
-                                target = os.path.join(current_dir, '../src/tosca/xtarget/tosca.xtarget'),
-                                output = OUTPUT_DIR,
-                                write_to_file = 'target',
-                                quiet = False)
+        args = XOSProcessorArgs(
+            inputs=xproto,
+            target=os.path.join(current_dir, "../src/tosca/xtarget/tosca.xtarget"),
+            output=OUTPUT_DIR,
+            write_to_file="target",
+            quiet=False,
+        )
         output = XOSProcessor.process(args)
         self.assertEqual(output.count("name:"), 4)
         self.assertIn("prop:", output)
 
-if __name__ == '__main__':
-  unittest.main()
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/test/test_tosca_parser.py b/test/test_tosca_parser.py
index 9641380..051b1d0 100644
--- a/test/test_tosca_parser.py
+++ b/test/test_tosca_parser.py
@@ -1,4 +1,3 @@
-
 # Copyright 2017-present Open Networking Foundation
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,119 +12,114 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-
-from helpers import *
+from __future__ import absolute_import
+from . import helpers  # noqa: F401
 import unittest
-import os
 from tosca.parser import TOSCA_Parser
 
-class TOSCA_Parser_Test(unittest.TestCase):
 
+class TOSCA_Parser_Test(unittest.TestCase):
     def test_get_tosca_models_by_name(self):
         """
         [TOSCA_Parser] get_tosca_models_by_name: should extract models from the TOSCA recipe and store them in a dict
         """
+
         class FakeNode:
             def __init__(self, name):
                 self.name = name
 
         class FakeTemplate:
-            nodetemplates = [
-                FakeNode('model1'),
-                FakeNode('model2')
-            ]
-
+            nodetemplates = [FakeNode("model1"), FakeNode("model2")]
 
         res = TOSCA_Parser.get_tosca_models_by_name(FakeTemplate)
-        self.assertIsInstance(res['model1'], FakeNode)
-        self.assertIsInstance(res['model2'], FakeNode)
+        self.assertIsInstance(res["model1"], FakeNode)
+        self.assertIsInstance(res["model2"], FakeNode)
 
-        self.assertEqual(res['model1'].name, 'model1')
-        self.assertEqual(res['model2'].name, 'model2')
+        self.assertEqual(res["model1"].name, "model1")
+        self.assertEqual(res["model2"].name, "model2")
 
     def test_populate_dependencies(self):
         """
-        [TOSCA_Parser] populate_dependencies: if a recipe has dependencies, it should find the ID of the requirements and add it to the model
+        [TOSCA_Parser] populate_dependencies: if a recipe has dependencies, it
+        should find the ID of the requirements and add it to the model
         """
+
         class FakeRecipe:
             requirements = [
                 {
-                    'site': {
-                        'node': 'site_onlab',
-                        'relationship': 'tosca.relationship.BelongsToOne'
+                    "site": {
+                        "node": "site_onlab",
+                        "relationship": "tosca.relationship.BelongsToOne",
                     }
                 }
             ]
 
         class FakeSite:
             id = 1
-            name = 'onlab'
+            name = "onlab"
 
         class FakeModel:
-            name = 'test@opencord.org'
+            name = "test@opencord.org"
 
-        saved_models = {
-            'site_onlab': FakeSite
-        }
+        saved_models = {"site_onlab": FakeSite}
 
-        model = TOSCA_Parser.populate_dependencies(FakeModel, FakeRecipe.requirements, saved_models)
+        model = TOSCA_Parser.populate_dependencies(
+            FakeModel, FakeRecipe.requirements, saved_models
+        )
         self.assertEqual(model.site_id, 1)
 
     def test_get_ordered_models_template(self):
         """
         [TOSCA_Parser] get_ordered_models_template: Create a list of templates based on topsorted models
         """
-        ordered_models = ['foo', 'bar']
+        ordered_models = ["foo", "bar"]
 
-        templates = {
-            'foo': 'foo_template',
-            'bar': 'bar_template'
-        }
+        templates = {"foo": "foo_template", "bar": "bar_template"}
 
-        ordered_templates = TOSCA_Parser.get_ordered_models_template(ordered_models, templates)
+        ordered_templates = TOSCA_Parser.get_ordered_models_template(
+            ordered_models, templates
+        )
 
-        self.assertEqual(ordered_templates[0], 'foo_template')
-        self.assertEqual(ordered_templates[1], 'bar_template')
+        self.assertEqual(ordered_templates[0], "foo_template")
+        self.assertEqual(ordered_templates[1], "bar_template")
 
     def test_topsort_dependencies(self):
         """
         [TOSCA_Parser] topsort_dependencies: Create a list of models based on dependencies
         """
+
         class FakeTemplate:
             def __init__(self, name, deps):
                 self.name = name
-                self.dependencies_names =  deps
-
+                self.dependencies_names = deps
 
         templates = {
-            'deps': FakeTemplate('deps', ['main']),
-            'main': FakeTemplate('main', []),
+            "deps": FakeTemplate("deps", ["main"]),
+            "main": FakeTemplate("main", []),
         }
 
         sorted = TOSCA_Parser.topsort_dependencies(templates)
 
-        self.assertEqual(sorted[0], 'main')
-        self.assertEqual(sorted[1], 'deps')
+        self.assertEqual(sorted[0], "main")
+        self.assertEqual(sorted[1], "deps")
 
     def test_compute_dependencies(self):
         """
-        [TOSCA_Parser] compute_dependencies: augment the TOSCA nodetemplate with information on requirements (aka related models)
+        [TOSCA_Parser] compute_dependencies: augment the TOSCA nodetemplate
+        with information on requirements (aka related models)
         """
 
-        parser = TOSCA_Parser('', 'user', 'pass')
+        parser = TOSCA_Parser("", "user", "pass")
 
         class FakeNode:
             def __init__(self, name, requirements):
                 self.name = name
                 self.requirements = requirements
 
-        main = FakeNode('main', [])
-        dep = FakeNode('dep', [{'relation': {'node': 'main'}}])
+        main = FakeNode("main", [])
+        dep = FakeNode("dep", [{"relation": {"node": "main"}}])
 
-        models_by_name = {
-            'main': main,
-            'dep': dep
-        }
+        models_by_name = {"main": main, "dep": dep}
 
         class FakeTemplate:
             nodetemplates = [dep, main]
@@ -137,8 +131,8 @@
         augmented_main = templates[1]
 
         self.assertIsInstance(augmented_dep.dependencies[0], FakeNode)
-        self.assertEqual(augmented_dep.dependencies[0].name, 'main')
-        self.assertEqual(augmented_dep.dependencies_names[0], 'main')
+        self.assertEqual(augmented_dep.dependencies[0].name, "main")
+        self.assertEqual(augmented_dep.dependencies_names[0], "main")
 
         self.assertEqual(len(augmented_main.dependencies), 0)
         self.assertEqual(len(augmented_main.dependencies_names), 0)
@@ -147,19 +141,16 @@
         """
         [TOSCA_Parser] populate_model: augment the GRPC model with data from TOSCA
         """
+
         class FakeModel:
             pass
 
-        data = {
-            'name': 'test',
-            'foo': 'bar',
-            'number': 1
-        }
+        data = {"name": "test", "foo": "bar", "number": 1}
 
         model = TOSCA_Parser.populate_model(FakeModel, data)
 
-        self.assertEqual(model.name, 'test')
-        self.assertEqual(model.foo, 'bar')
+        self.assertEqual(model.name, "test")
+        self.assertEqual(model.foo, "bar")
         self.assertEqual(model.number, 1)
 
     def test_populate_model_error(self):
@@ -172,23 +163,21 @@
             model_name = "FakeModel"
 
             def __setattr__(self, name, value):
-                if name == 'foo':
-                    raise TypeError('reported exception')
+                if name == "foo":
+                    raise TypeError("reported exception")
                 else:
                     super(FakeModel, self).__setattr__(name, value)
 
-        data = {
-            'name': 'test',
-            'foo': None,
-            'number': 1
-        }
-
+        data = {"name": "test", "foo": None, "number": 1}
 
         model = FakeModel()
 
         with self.assertRaises(Exception) as e:
             model = TOSCA_Parser.populate_model(model, data)
-        self.assertEqual(e.exception.message, 'Failed to set None on field foo for class FakeModel, Exception was: "reported exception"')
+        self.assertEqual(
+            str(e.exception),
+            'Failed to set None on field foo for class FakeModel, Exception was: "reported exception"',
+        )
 
     def test_translate_exception(self):
         """
@@ -197,7 +186,8 @@
         e = TOSCA_Parser._translate_exception("Non tosca exception")
         self.assertEqual(e, "Non tosca exception")
 
-        e = TOSCA_Parser._translate_exception("""        
+        e = TOSCA_Parser._translate_exception(
+            """
 MissingRequiredFieldError: some message
     followed by unreadable
     and mystic
@@ -212,10 +202,14 @@
     followed by useless things
 TypeMismatchError: with some message
     followed by useless things
-        """)
-        self.assertEqual(e, """MissingRequiredFieldError: some message
+        """
+        )
+        self.assertEqual(
+            e,
+            """MissingRequiredFieldError: some message
 UnknownFieldError: with some message
 ImportError: with some message
 InvalidTypeError: with some message
 TypeMismatchError: with some message
-""")
+""",
+        )
diff --git a/test/test_tosca_parser_e2e.py b/test/test_tosca_parser_e2e.py
index 8a1b7e8..4a34115 100644
--- a/test/test_tosca_parser_e2e.py
+++ b/test/test_tosca_parser_e2e.py
@@ -1,4 +1,3 @@
-
 # Copyright 2017-present Open Networking Foundation
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,54 +12,68 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from helpers import *
+from __future__ import absolute_import
+from . import helpers  # noqa: F401
 import unittest
-from mock import patch, MagicMock
+
+try:  # python 3
+    from unittest.mock import patch, MagicMock
+except ImportError:  # python 2
+    from mock import patch, MagicMock
+
 from tosca.parser import TOSCA_Parser
 from grpc_client.resources import RESOURCES
 
+
 class FakeObj:
     new = None
     filter = None
 
+
 class FakeModel:
     save = None
     delete = None
     is_new = False
     id = 1
 
+
 class FakeGuiExt:
     objects = FakeObj
 
+
 class FakeSite:
     objects = FakeObj
 
+
 class FakePrivilege:
     objects = FakeObj
 
+
 class FakeUser:
     objects = FakeObj
 
+
 class FakeNode:
     objects = FakeObj
 
+
 USERNAME = "username"
 PASSWORD = "pass"
 
 mock_resources = {}
 mock_resources["%s~%s" % (USERNAME, PASSWORD)] = {
-    'XOSGuiExtension': FakeGuiExt,
-    'Site': FakeSite,
-    'User': FakeUser,
-    'Privilege': FakePrivilege,
-    'Node': FakeNode
+    "XOSGuiExtension": FakeGuiExt,
+    "Site": FakeSite,
+    "User": FakeUser,
+    "Privilege": FakePrivilege,
+    "Node": FakeNode,
 }
 
-class TOSCA_Parser_E2E(unittest.TestCase):
 
+class TOSCA_Parser_E2E(unittest.TestCase):
     @patch.dict(RESOURCES, mock_resources, clear=True)
-    @patch.object(FakeGuiExt.objects, 'filter', MagicMock(return_value=[FakeModel]))
-    @patch.object(FakeModel, 'save')
+    @patch.object(FakeGuiExt.objects, "filter", MagicMock(return_value=[FakeModel]))
+    @patch.object(FakeModel, "save")
     def test_basic_creation(self, mock_save):
         """
         [TOSCA_Parser] Should save models defined in a TOSCA recipe
@@ -89,20 +102,23 @@
         parser.execute()
 
         # checking that the model has been saved
-        mock_save.assert_called()
+        mock_save.assert_called_with()
 
-        self.assertIsNotNone(parser.templates_by_model_name['test'])
-        self.assertEqual(parser.ordered_models_name, ['test'])
+        self.assertIsNotNone(parser.templates_by_model_name["test"])
+        self.assertEqual(parser.ordered_models_name, ["test"])
 
         # check that the model was saved with the expected values
-        saved_model = parser.saved_model_by_name['test']
-        self.assertEqual(saved_model.name, 'test')
-        self.assertEqual(saved_model.files, '/spa/extensions/test/vendor.js, /spa/extensions/test/app.js')
+        saved_model = parser.saved_model_by_name["test"]
+        self.assertEqual(saved_model.name, "test")
+        self.assertEqual(
+            saved_model.files,
+            "/spa/extensions/test/vendor.js, /spa/extensions/test/app.js",
+        )
 
     @patch.dict(RESOURCES, mock_resources, clear=True)
-    @patch.object(FakeGuiExt.objects, 'filter', MagicMock(return_value=[FakeModel]))
-    @patch.object(FakeNode.objects, 'filter', MagicMock(return_value=[FakeModel]))
-    @patch.object(FakeModel, 'delete')
+    @patch.object(FakeGuiExt.objects, "filter", MagicMock(return_value=[FakeModel]))
+    @patch.object(FakeNode.objects, "filter", MagicMock(return_value=[FakeModel]))
+    @patch.object(FakeModel, "delete")
     def test_basic_deletion(self, mock_model):
         """
         [TOSCA_Parser] Should delete models defined in a TOSCA recipe
@@ -137,15 +153,15 @@
         parser.execute()
 
         # checking that the model has been saved
-        mock_model.assert_called_once()
+        mock_model.assert_called_once_with()
 
-        self.assertIsNotNone(parser.templates_by_model_name['test'])
-        self.assertEqual(parser.ordered_models_name, ['test', 'should_stay'])
+        self.assertIsNotNone(parser.templates_by_model_name["test"])
+        self.assertEqual(parser.ordered_models_name, ["should_stay", "test"])
 
     @patch.dict(RESOURCES, mock_resources, clear=True)
-    @patch.object(FakeSite.objects, 'filter', MagicMock(return_value=[FakeModel]))
-    @patch.object(FakeUser.objects, 'filter', MagicMock(return_value=[FakeModel]))
-    @patch.object(FakeModel, 'save')
+    @patch.object(FakeSite.objects, "filter", MagicMock(return_value=[FakeModel]))
+    @patch.object(FakeUser.objects, "filter", MagicMock(return_value=[FakeModel]))
+    @patch.object(FakeModel, "save")
     def test_related_models_creation(self, mock_save):
         """
         [TOSCA_Parser] Should save related models defined in a TOSCA recipe
@@ -193,20 +209,20 @@
 
         self.assertEqual(mock_save.call_count, 2)
 
-        self.assertIsNotNone(parser.templates_by_model_name['site_onlab'])
-        self.assertIsNotNone(parser.templates_by_model_name['usertest'])
-        self.assertEqual(parser.ordered_models_name, ['site_onlab', 'usertest'])
+        self.assertIsNotNone(parser.templates_by_model_name["site_onlab"])
+        self.assertIsNotNone(parser.templates_by_model_name["usertest"])
+        self.assertEqual(parser.ordered_models_name, ["site_onlab", "usertest"])
 
         # check that the model was saved with the expected values
-        saved_site = parser.saved_model_by_name['site_onlab']
-        self.assertEqual(saved_site.name, 'Open Networking Lab')
+        saved_site = parser.saved_model_by_name["site_onlab"]
+        self.assertEqual(saved_site.name, "Open Networking Lab")
 
-        saved_user = parser.saved_model_by_name['usertest']
-        self.assertEqual(saved_user.firstname, 'User')
+        saved_user = parser.saved_model_by_name["usertest"]
+        self.assertEqual(saved_user.firstname, "User")
         self.assertEqual(saved_user.site_id, 1)
 
     @patch.dict(RESOURCES, mock_resources, clear=True)
-    @patch.object(FakeSite.objects, 'filter', MagicMock(return_value=[]))
+    @patch.object(FakeSite.objects, "filter", MagicMock(return_value=[]))
     def test_must_exist_fail(self):
         """
         [TOSCA_Parser] Should throw an error if an object with 'must_exist' does not exist
@@ -235,11 +251,16 @@
         with self.assertRaises(Exception) as e:
             parser.execute()
 
-        self.assertEqual(e.exception.message.message, "[XOS-TOSCA] Model of class Site and properties {'name': 'Open Networking Lab'} has property 'must-exist' but cannot be found")
+        self.assertEqual(
+            str(e.exception),
+            "[XOS-TOSCA] Failed to save or delete model Site [site_onlab]: "
+            "[XOS-TOSCA] Model of class Site and properties {'name': 'Open Networking Lab'} "
+            "has property 'must-exist' but cannot be found"
+        )
 
     @patch.dict(RESOURCES, mock_resources, clear=True)
-    @patch.object(FakePrivilege.objects, 'filter', MagicMock(return_value=[FakeModel]))
-    @patch.object(FakeModel, 'save')
+    @patch.object(FakePrivilege.objects, "filter", MagicMock(return_value=[FakeModel]))
+    @patch.object(FakeModel, "save")
     def test_number_param(self, mock_save):
         """
         [TOSCA_Parser] Should correctly parse number parameters
@@ -265,9 +286,9 @@
         parser.execute()
 
         # checking that the model has been saved
-        mock_save.assert_called()
+        mock_save.assert_called_with()
 
         # check that the model was saved with the expected values
-        saved_model = parser.saved_model_by_name['privilege#test_privilege']
-        self.assertEqual(saved_model.permission, 'whatever')
+        saved_model = parser.saved_model_by_name["privilege#test_privilege"]
+        self.assertEqual(saved_model.permission, "whatever")
         self.assertEqual(saved_model.accessor_id, 3)
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..3ba6407
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,49 @@
+; Copyright 2019-present Open Networking Foundation
+;
+; Licensed under the Apache License, Version 2.0 (the "License");
+; you may not use this file except in compliance with the License.
+; You may obtain a copy of the License at
+;
+; http://www.apache.org/licenses/LICENSE-2.0
+;
+; Unless required by applicable law or agreed to in writing, software
+; distributed under the License is distributed on an "AS IS" BASIS,
+; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+; See the License for the specific language governing permissions and
+; limitations under the License.
+
+[tox]
+envlist = py27,py35,py36,py37
+skip_missing_interpreters = true
+skipsdist = True
+
+[testenv]
+deps =
+  -r requirements.txt
+  nose2
+  flake8
+
+commands =
+  nose2 -c tox.ini --verbose --junit-xml
+  flake8 src test
+
+[flake8]
+max-line-length = 119
+exclude =
+  src/grpc_client/KEYS.py
+
+[unittest]
+plugins = nose2.plugins.junitxml
+
+[junit-xml]
+path = nose2-results.xml
+
+[coverage]
+always-on = True
+coverage =
+  src
+  test
+coverage-report =
+  term
+  xml
+
diff --git a/unittest.cfg b/unittest.cfg
deleted file mode 100644
index f7d64b7..0000000
--- a/unittest.cfg
+++ /dev/null
@@ -1,7 +0,0 @@
-[unittest]
-plugins=nose2.plugins.junitxml
-
-[coverage]
-always-on = True
-coverage-report = term
-coverage-report = xml