From b43e04e4e1922bcd6e84186fe68d15b507c3fd63 Mon Sep 17 00:00:00 2001
From: groetznerFortiss <groetzner@fortiss.org>
Date: Wed, 13 Nov 2019 14:44:26 +0100
Subject: [PATCH] [NRRPLT-7722] Python3 compatibility

[NRRPLT-7719] I executed the future module on VirtualCoach. Some builtin python functions are redefined, so pylint will issue warnings.

[NRRPLT-7719] Fixed bugs occuring in run_tests.sh: Checking for string types must use string types from the six module.

The W0622 (redefining builtin function) warnings of pylint are now disabled.

Additionally, the integration test now uses absolute paths for loading data.

[NRRPLT-7722] Made the code compatible with Python3.

[NRRPLT-7722] Adding some minor bugfixes.

[NRRPLT-7722] Adding more minor bugfixes.

[NRRPLT-7722] Update setup.py

Change-Id: Ibb59a6d2b71ed77170c636f2357c011f3e875ea0

 [NRRPLT-7722] Fix Makefile

Change-Id: Id723122f896e3d20a79bd1603a75452beeaa56f2

[NRRPLT-7722] Upgrade pip version

[NRRPLT-7722] fix pip version specifier in Makefile

[NRRPLT-7722] fix pylint

[NRRPLT-7722] Quote pip version in user_makefile. Force python2-compatible ipywidgets version

[NRRPLT-7722] fix version

Change-Id: I74c6ea95a537cfe03786c8cc15484581ca573e1c

[NRRPLT-7722] fix type check in oidc_http_client

Change-Id: I74d6d9c5059b659b50b9b6f2328a2c8832c3e330

[NRRPLT-7722] force check for pip version in pipeline

Change-Id: Ie09870af0dcf4f9af66f27d784a28f4eee285f22

[NRRPLT-7722] More robust ipywidget version requirement. Move test dependencies in separate file

Change-Id: I20c186cd07a7c34175b2b25cd4ff3a9222b7d782
---
 Makefile                                      |   8 +-
 bitbucket-pipelines.yml                       |   3 +
 examples/integration_test/it.py               |  90 ++++++-----
 .../hbp_nrp_virtual_coach/config.py           |  11 +-
 .../hbp_nrp_virtual_coach/http_client.py      |   4 +
 .../hbp_nrp_virtual_coach/oidc_http_client.py |  28 ++--
 .../hbp_nrp_virtual_coach/requests_client.py  |  14 +-
 .../hbp_nrp_virtual_coach/simulation.py       | 150 ++++++++++--------
 .../tests/test_config.py                      |   4 +-
 .../tests/test_simulation.py                  | 111 ++++++++-----
 .../tests/test_virtual_coach.py               |  29 +++-
 .../hbp_nrp_virtual_coach/virtual_coach.py    | 142 +++++++++--------
 hbp_nrp_virtual_coach/requirements.txt        |   6 +-
 .../requirements_extension_tests.txt          |   2 +
 hbp_nrp_virtual_coach/setup.py                |  24 +--
 15 files changed, 371 insertions(+), 255 deletions(-)
 create mode 100644 hbp_nrp_virtual_coach/requirements_extension_tests.txt

diff --git a/Makefile b/Makefile
index b379286..b4d809e 100644
--- a/Makefile
+++ b/Makefile
@@ -11,15 +11,17 @@ COVER_PACKAGES=hbp_nrp_virtual_coach
 DOC_MODULES=hbp_nrp_virtual_coach/doc
 DOC_REPO=--doc-repo ssh://bbpcode.epfl.ch/infra/jekylltest
 
-PYTHON_PIP_VERSION?=pip==9.0.3
+PYTHON_PIP_VERSION?='pip>=19'
 
 ##### DO NOT MODIFY BELOW #####################
 
+python_version_full := $(shell python -c "import sys; maj, min = sys.version_info[:2]; print('{}.{}'.format(maj, min))" 2>&1)
+
 ifeq ($(NRP_INSTALL_MODE),user)
-        $(shell cp -af $(HBP)/user-scripts/config_files/platform_venv/* $(VIRTUAL_ENV)/lib/python2.7/site-packages/ )
+        $(shell cp -af $(HBP)/user-scripts/config_files/platform_venv/* $(VIRTUAL_ENV)/lib/python$(python_version_full)/site-packages/ )
         include user_makefile
 else
-        $(shell cp -af $(HBP)/user-scripts/config_files/platform_venv/* $(VIRTUAL_ENV)/lib/python2.7/site-packages/ )
+        $(shell cp -af $(HBP)/user-scripts/config_files/platform_venv/* $(VIRTUAL_ENV)/lib/python$(python_version_full)/site-packages/ )
         CI_REPO?=git@bitbucket.org:hbpneurorobotics/admin-scripts.git
         CI_DIR?=$(HBP)/admin-scripts/ContinuousIntegration
         THIS_DIR:=$(PWD)
diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml
index 54ea51d..109868b 100644
--- a/bitbucket-pipelines.yml
+++ b/bitbucket-pipelines.yml
@@ -28,6 +28,9 @@ pipelines:
           # Copy bbp-client from user-scripts (before make devinstall)
           - cp -af $HBP/user-scripts/config_files/platform_venv/* $VIRTUAL_ENV_PATH/lib/python2.7/site-packages/
 
+          # Delete pip lock in VIRTUAL_ENV so to force checking for pip upgrade
+          - rm $VIRTUAL_ENV/new-pip.txt
+
           # Build
           - export IGNORE_LINT='platform_venv|config_files|examples/integration_test/test_experiment_folder'
           - make verify_base || { if [ -f pylint.txt ]; then echo "----------"; echo "PYLINT.TXT"; echo "----------";cat pylint.txt; fi; if [ -f pep8.txt ]; then echo "----------"; echo "PEP8.TXT"; echo "----------";cat pep8.txt; fi; exit 1; }
diff --git a/examples/integration_test/it.py b/examples/integration_test/it.py
index acdac7b..6f159e0 100644
--- a/examples/integration_test/it.py
+++ b/examples/integration_test/it.py
@@ -25,12 +25,20 @@ platform (e.g. specific text values, timings, etc.), but SHOULD interact with al
 interfaces/APIs that are supported.
 """
 
+# pylint: disable=W0622
+
+from __future__ import print_function
+
+from builtins import str
+from builtins import zip
+from builtins import object
 import argparse
 import logging
 import json
 import sys
 import time
 import traceback
+import os
 
 # virtualcoach and platform_venv specific packages, ensure environment is correct
 try:
@@ -38,14 +46,14 @@ try:
     import progressbar
 
 except ImportError:
-    print
+    print()
     print('Failed to import the VirtualCoach or access packages in the platform_venv! Aborting.')
-    print
+    print()
     print('Please make sure you have:')
-    print
+    print()
     print('\t1. run "make devinstall" in the VirtualCoach repo')
     print('\t2. use "cle-virtual-coach it.py" to run this script')
-    print
+    print()
     sys.exit(-1)
 
 ##
@@ -117,7 +125,7 @@ def run(oidc_username, storage_username):
 
         # the simulation we will launch, defined here so we have a cleanup reference at any point
         sim = None
-
+        path = os.path.dirname(os.path.abspath(__file__))
         ##
         ## Server Information and Experiment List Interaction
         ##
@@ -137,7 +145,7 @@ def run(oidc_username, storage_username):
         # ensure there is a running server that is not currently running an experiment
         results.start('Checking For Available Backend')
         available_servers = vc._VirtualCoach__get_available_server_list()
-        if len(available_servers) == 0:
+        if not available_servers:
             raise TestCaseError('No available backends to run test on.')
         results.done(True)
 
@@ -145,7 +153,7 @@ def run(oidc_username, storage_username):
         # the experiment first if not.
         results.start('Cloning a new Empty Template Husky Experiment from the Storage Server')
 
-        if 'ExDTemplateHusky' not in [s[0] for s in server_info.iteritems()]:
+        if 'ExDTemplateHusky' not in [s[0] for s in server_info.items()]:
             raise TestCaseError('Husky Template Experiment is not available on the server to be'
                                 ' cloned.')
         experiment_id = vc.clone_experiment_to_storage('ExDTemplateHusky')
@@ -179,12 +187,11 @@ def run(oidc_username, storage_username):
         ##
 
         results.start('Importing an experiment folder')
-        response = vc.import_experiment('test_experiment_folder')
+        response = vc.import_experiment(path + '/test_experiment_folder')
         new_experiment_id = json.loads(response.text)['destFolderName']
         if new_experiment_id not in vc._VirtualCoach__get_experiment_list(cloned=True):
             raise TestCaseError('Importing an experiment folder failed')
-        else:
-            vc.delete_cloned_experiment(new_experiment_id)
+        vc.delete_cloned_experiment(new_experiment_id)
         results.done(True)
 
         ##
@@ -192,12 +199,11 @@ def run(oidc_username, storage_username):
         ##
 
         results.start('Importing a zipped experiment folder')
-        response = vc.import_experiment('test_experiment_folder.zip')
+        response = vc.import_experiment(path + '/test_experiment_folder.zip')
         new_experiment_id = json.loads(response.text)['destFolderName']
         if new_experiment_id not in vc._VirtualCoach__get_experiment_list(cloned=True):
             raise TestCaseError('Importing an experiment folder failed')
-        else:
-            vc.delete_cloned_experiment(new_experiment_id)
+        vc.delete_cloned_experiment(new_experiment_id)
         results.done(True)
 
         ##
@@ -267,12 +273,12 @@ def run(oidc_username, storage_username):
         # retrieve transfer functions
         results.start('Retrieving Transfer Functions')
         tfs = sim._Simulation__get_simulation_scripts('transfer-function')['data']
-        if len(tfs) == 0:
+        if not tfs:
             raise TestCaseError('No transfer functions returned for experiment.')
         results.done(True)
 
         # create a valid tf with a modified import and an invalid tf for testing
-        tf_name = str(tfs.keys()[0])
+        tf_name = str(list(tfs.keys())[0])
         valid_tf = 'import os\n' + tfs[tf_name]
         invalid_tf = 'this is invalid!\n' + tfs[tf_name]
 
@@ -448,23 +454,23 @@ def tf(t):
         results.done(True)
 
     # handle test case or unexpected failures, attempt to cleanup and terminate
-    except Exception, e:
+    except Exception as e:
 
         # fail the last test case
         results.abort()
 
         # check if the test case has just failed
-        print
+        print()
         if isinstance(e, TestCaseError):
-            print 'Test case failed: %s - attempting to cleanup.' % e
+            print('Test case failed: %s - attempting to cleanup.' % e)
 
         # unhandled exception means a virtual coach failure, log it
         else:
-            print
-            print 'Unexpected exception encountered during execution, attempting to cleanup.'
-            print
+            print()
+            print('Unexpected exception encountered during execution, attempting to cleanup.')
+            print()
             traceback.print_exc(e)
-        print
+        print()
 
         # if a sim is running, try to shut it down cleanly, we can't do much more
         if sim:
@@ -508,29 +514,29 @@ if __name__ == '__main__':
         logging.disable(logging.CRITICAL)
 
     # banner with user warnings / prompts
-    print ''.center(80, '=')
-    print '|%s|' % ''.center(78, ' ')
-    print '|%s|' % 'VirtualCoach Integration Test Script'.center(78, ' ')
-    print '|%s|' % ''.center(78, ' ')
-    print '|%s|' % ''.center(78, ' ')
+    print(''.center(80, '='))
+    print('|%s|' % ''.center(78, ' '))
+    print('|%s|' % 'VirtualCoach Integration Test Script'.center(78, ' '))
+    print('|%s|' % ''.center(78, ' '))
+    print('|%s|' % ''.center(78, ' '))
     if args.oidc_username:
-        print '|%s|' % ('OIDC is enabled, user: %s' % args.oidc_username).center(78, ' ')
-        print '|%s|' % 'Note: you may be prompted for a password.'.center(78, ' ')
+        print('|%s|' % ('OIDC is enabled, user: %s' % args.oidc_username).center(78, ' '))
+        print('|%s|' % 'Note: you may be prompted for a password.'.center(78, ' '))
     elif args.storage_username:
-        print '|%s|' % ('Local Storage is enabled, user: %s' % args.storage_username).\
-            center(78, ' ')
-        print '|%s|' % 'Note: you may be prompted for a password.'.center(78, ' ')
+        print('|%s|' % ('Local Storage is enabled, user: %s' % args.storage_username)
+              .center(78, ' '))
+        print('|%s|' % 'Note: you may be prompted for a password.'.center(78, ' '))
     else:
         raise ValueError('|%s|' % 'No OIDC or local Storage credentials were provided. Please run '
                          'the script with either oidc_username or storage_username as arguments'.
                          center(78, ' '))
-    print '|%s|' % ''.center(78, ' ')
-    print '=%s=' % ''.center(78, '=')
+    print('|%s|' % ''.center(78, ' '))
+    print('=%s=' % ''.center(78, '='))
 
     # run all of the sequential tests, results are returned upon success or first failure
-    print
-    print 'Running Test Cases, This May Take A Few Moments...'.center(80, ' ')
-    print
+    print()
+    print('Running Test Cases, This May Take A Few Moments...'.center(80, ' '))
+    print()
     cumulative = TestCase('Summary'.center(52, ' '))
     results = run(args.oidc_username, args.storage_username)
     cumulative.done(all([r.success for r in results]))
@@ -540,9 +546,9 @@ if __name__ == '__main__':
     # so we just do it manually here instead, not the most readable but functional
     widths = [75, 12, 12]
     headers = ['Description', 'Duration', 'Result']
-    print '\n\n+{0}+{1}+{2}+'.format(*(''.center(w, '-') for w in widths))
-    print '|{0}|{1}|{2}|'.format(*(h.center(w, ' ') for h, w in zip(headers, widths)))
-    print '+{0}+{1}+{2}+'.format(*(''.center(w, '=') for w in widths))
+    print('\n\n+{0}+{1}+{2}+'.format(*(''.center(w, '-') for w in widths)))
+    print('|{0}|{1}|{2}|'.format(*(h.center(w, ' ') for h, w in zip(headers, widths))))
+    print('+{0}+{1}+{2}+'.format(*(''.center(w, '=') for w in widths)))
 
     # print all of the data rows in easy to visually scan colors (since no one will look at the
     # individual test cases and output otherwise
@@ -563,8 +569,8 @@ if __name__ == '__main__':
                      formatted[2].center(widths[2] + 9, ' ')]
 
         # print the row and next table separator
-        print '|{0}|{1}|{2}|'.format(*formatted)
-        print '+{0}+{1}+{2}+'.format(*(''.center(w, '-') for w in widths))
+        print('|{0}|{1}|{2}|'.format(*formatted))
+        print('+{0}+{1}+{2}+'.format(*(''.center(w, '-') for w in widths)))
 
     # change the exit code so it can be checked automatically if needed
     sys.exit(0 if results[-1].success else -1)
diff --git a/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/config.py b/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/config.py
index 118d9a7..268adb5 100644
--- a/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/config.py
+++ b/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/config.py
@@ -25,12 +25,17 @@
 A strongly validated configuration implementation for the VirtualCoach.
 """
 
-from hbp_nrp_virtual_coach.version import VERSION
+# pylint: disable=W0622
+
+from builtins import str
 
 import json
 import logging
 import os
 
+from hbp_nrp_virtual_coach.version import VERSION
+
+
 logger = logging.getLogger('Configuration')
 
 
@@ -61,7 +66,7 @@ class Config(dict):
         path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'config.json')
         if not os.path.isfile(path):
             raise IOError('[config] config.json not found, terminating.')
-        elif not os.access(path, os.R_OK):
+        if not os.access(path, os.R_OK):
             raise IOError('[config] config.json is not readable, terminating.')
 
         # parse the config.json and store all values in this object
@@ -95,7 +100,7 @@ class Config(dict):
 
         # convenience, prepend the proxy url to all proxy services, we cannot do this
         # for the simulation services because they are backend/experiment id specific
-        for k, v in self['proxy-services'].iteritems():
+        for k, v in self['proxy-services'].items():
             self['proxy-services'][k] = '%s/%s' % (self['proxy'][environment], v)
 
     def __validate(self, key, values):
diff --git a/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/http_client.py b/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/http_client.py
index e37ce2f..d6d1e48 100644
--- a/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/http_client.py
+++ b/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/http_client.py
@@ -3,6 +3,10 @@ Base HTTP Client class used so that no matter what http client is used
 the interface to do the request will always be the same
 """
 
+# pylint: disable=W0622
+
+from builtins import object
+
 
 class HTTPClient(object):
     """ Base HTTP Client class """
diff --git a/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/oidc_http_client.py b/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/oidc_http_client.py
index 4539d77..c8f9570 100644
--- a/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/oidc_http_client.py
+++ b/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/oidc_http_client.py
@@ -1,7 +1,7 @@
 """ Class which uses the BBP oidc client to do http calls """
 
-from hbp_nrp_virtual_coach.http_client import HTTPClient
 import json
+from hbp_nrp_virtual_coach.http_client import HTTPClient
 
 
 class OIDCHTTPClient(HTTPClient):
@@ -41,14 +41,11 @@ class OIDCHTTPClient(HTTPClient):
         :return: the status of the post request and the content
         :rtype: integer, string
         """
-        if (type(body) == dict):
-            response, content = self.__oidc_client.request(
-                url, method='POST', body=json.dumps(body), headers=self.__headers
-            )
-        else:
-            response, content = self.__oidc_client.request(
-                url, method='POST', body=body, headers=self.__headers
-            )
+        body_ = body if isinstance(body, dict) else json.dumps(body)
+
+        response, content = self.__oidc_client.request(
+            url, method='POST', body=body_, headers=self.__headers
+        )
         return int(response['status']), content
 
     def put(self, url, body):
@@ -58,14 +55,11 @@ class OIDCHTTPClient(HTTPClient):
         :return: the status of the put request and the content
         :rtype: integer, string
         """
-        if (type(body) == dict):
-            response, content = self.__oidc_client.request(
-                url, method='PUT', body=json.dumps(body), headers=self.__headers
-            )
-        else:
-            response, content = self.__oidc_client.request(
-                url, method='PUT', body=body, headers=self.__headers
-            )
+        body_ = body if isinstance(body, dict) else json.dumps(body)
+
+        response, content = self.__oidc_client.request(
+            url, method='PUT', body=body_, headers=self.__headers
+        )
         return int(response['status']), content
 
     def delete(self, url, body):
diff --git a/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/requests_client.py b/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/requests_client.py
index 9008494..a5ee308 100644
--- a/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/requests_client.py
+++ b/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/requests_client.py
@@ -1,7 +1,7 @@
 """ Class which uses requests to do http calls """
 
-from hbp_nrp_virtual_coach.http_client import HTTPClient
 import requests
+from hbp_nrp_virtual_coach.http_client import HTTPClient
 
 
 class RequestsClient(HTTPClient):
@@ -25,7 +25,11 @@ class RequestsClient(HTTPClient):
         :param url: The url to do a post to
         :param body: The content to post to the url
         """
-        if (type(body) != dict):
+        try:
+            typeB = isinstance(body)
+        except TypeError:
+            typeB = type(body)
+        if typeB != dict:
             response = requests.post(url, headers=self.__headers, data=body)
         else:
             response = requests.post(url, headers=self.__headers, json=body)
@@ -36,7 +40,11 @@ class RequestsClient(HTTPClient):
         :param url: The url to do a request to
         :param body: The content to put to
         """
-        if (type(body) != dict):
+        try:
+            typeB = isinstance(body)
+        except TypeError:
+            typeB = type(body)
+        if typeB != dict:
             response = requests.put(url, headers=self.__headers, data=body)
         else:
             response = requests.put(url, headers=self.__headers, json=body)
diff --git a/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/simulation.py b/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/simulation.py
index 6bba85b..e9043ba 100644
--- a/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/simulation.py
+++ b/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/simulation.py
@@ -25,15 +25,31 @@
 An interface to launch or control a simulation instance.
 """
 
-from hbp_nrp_virtual_coach.config import Config
-from hbp_nrp_virtual_coach.http_client import HTTPClient
+# pylint: disable=W0622
+
+
+from __future__ import print_function
+
+from builtins import str
+from builtins import object
 
-import httplib
-import json
-import logging
-from urllib2 import HTTPError
 import traceback
 import os
+import http.client
+import json
+import logging
+
+from six import string_types
+from future import standard_library
+
+from hbp_nrp_virtual_coach.http_client import HTTPClient
+from hbp_nrp_virtual_coach.config import Config
+try:
+    from urllib2 import HTTPError
+except ImportError:
+    from urllib.error import HTTPError # pylint: disable=F0401
+
+standard_library.install_aliases()
 
 
 class Simulation(object):
@@ -84,10 +100,10 @@ class Simulation(object):
         :param server: A string representing the name of the server to try to launch on.
         :param reservation: A string representing a cluster resource reservation (if any).
         """
-        assert isinstance(experiment_id, str)
-        assert isinstance(experiment_conf, str)
-        assert isinstance(server, str)
-        assert isinstance(reservation, (str, type(None)))
+        assert isinstance(experiment_id, string_types)
+        assert isinstance(experiment_conf, string_types)
+        assert isinstance(server, string_types)
+        assert isinstance(reservation, (string_types, type(None)))
 
         # do not allow reuse of this instance if a simulation has been launched
         if self.__server:
@@ -123,11 +139,11 @@ class Simulation(object):
 
             # check to see if the launch was successful, any other failure return codes
             # such as 404 will trigger an exception by the OIDCClient itself
-            if status_code == httplib.CONFLICT:
+            if status_code == http.client.CONFLICT:
                 raise Exception(
                     'Simulation server is launching another experiment.')
 
-            elif status_code != httplib.CREATED:
+            if status_code != http.client.CREATED:
                 raise Exception(
                     "Simulation responded with HTTP status %s" % status_code)
 
@@ -237,7 +253,7 @@ class Simulation(object):
                       ExDBackend for valid states.
         """
 
-        assert isinstance(state, str)
+        assert isinstance(state, string_types)
 
         # ensure the simulation is started and valid
         if not self.__server or not self.__sim_url:
@@ -251,7 +267,7 @@ class Simulation(object):
         status_code, _ = self.__http_client.put(url, body={'state': state})
 
         # check the return code, this will return OK if the REST call succeeds
-        if status_code != httplib.OK:
+        if status_code != http.client.OK:
             raise Exception(
                 "Unable to set simulation state, HTTP status %s" % status_code)
         self.__logger.info('Simulation state: %s', state)
@@ -283,7 +299,7 @@ class Simulation(object):
             return
 
         # a loading or shutdown message from the simulation, log it to console, but don't propagate
-        elif 'progress' in status:
+        if 'progress' in status:
 
             # ignore duplicate done messages that are used to clear progress bars on the frontend
             if 'subtask' not in status['progress']:
@@ -335,7 +351,7 @@ class Simulation(object):
                          self.__config['simulation-services']['state'])
         status_code, content = self.__http_client.get(url)
 
-        if status_code != httplib.OK:
+        if status_code != http.client.OK:
             raise Exception("Unable to get current simulation state, HTTP status %s"
                             % status_code)
 
@@ -349,7 +365,7 @@ class Simulation(object):
         :param script_type: The script type to be retrieved. Either transfer-functions,
                             state-machines or the brain
         """
-        assert isinstance(script_type, (str, unicode))
+        assert isinstance(string_types, string_types)
 
         if not self.__server or not self.__sim_url:
             raise Exception(
@@ -359,14 +375,14 @@ class Simulation(object):
             raise ValueError("Script type %s not defined. Possible values are: %s"
                              % (script_type, ", ".join(self.__config['simulation-scripts'])))
 
-        self.__logger.info("Attempting to retrieve %s" % script_type)
+        self.__logger.info("Attempting to retrieve %s", script_type)
 
         url = '%s/%s' % (self.__sim_url,
                          self.__config['simulation-scripts'][script_type])
 
         status_code, content = self.__http_client.get(url)
 
-        if status_code != httplib.OK:
+        if status_code != http.client.OK:
             raise Exception("Unable to get simulation %s, HTTP status %s"
                             % (script_type, status_code))
         return json.loads(content)
@@ -379,11 +395,11 @@ class Simulation(object):
         :param script_type: The type of the script to be retrieved (transfer-function, brain,
                             state-machine)
         """
-        assert isinstance(script_name, str)
+        assert isinstance(script_name, string_types)
         script_type_print_format = ' '.join(script_type.split('-')).title()
 
-        self.__logger.info('Attempting to retrieve %s: %s' % (script_type_print_format,
-                                                              script_name))
+        self.__logger.info('Attempting to retrieve %s: %s', script_type_print_format,
+                           script_name)
 
         if not self.__server or not self.__sim_url:
             raise Exception("Simulation has not been created, cannot get %s!"
@@ -393,7 +409,7 @@ class Simulation(object):
         if script_name not in defined_scripts:
             raise ValueError('%s: "%s" does not exist. Please check the %ss ids: \n%s'
                              % (script_type_print_format, script_name, script_type_print_format,
-                                str('\n').join(defined_scripts.keys())))
+                                str('\n').join(list(defined_scripts.keys()))))
 
         return defined_scripts[script_name]
 
@@ -410,8 +426,8 @@ class Simulation(object):
         :param script: A string containing the code of the new script.
         :param new: A flag indicating whether the script is being newly added or modified.
         """
-        assert isinstance(script_name, str)
-        assert isinstance(script, (str, unicode))
+        assert isinstance(script_name, string_types)
+        assert isinstance(script, string_types)
 
         script_type_display = ' '.join(script_type.split('-')).title()
 
@@ -421,8 +437,7 @@ class Simulation(object):
 
         defined_scripts = self.__get_simulation_scripts(script_type)['data']
 
-        self.__logger.info('Attempting to set %s %s' %
-                           (script_type_display, script_name))
+        self.__logger.info('Attempting to set %s %s', script_type_display, script_name)
         url = '%s/%s/%s' % (self.__sim_url, self.__config['simulation-scripts'][script_type],
                             script_name)
         started = False
@@ -451,19 +466,18 @@ class Simulation(object):
                 url = '%s/%s/%s' % (self.__sim_url,
                                     self.__config['simulation-scripts'][script_type], script_name)
                 status_code, _ = self.__http_client.put(url, body=script)
-            if status_code != httplib.OK:
+            if status_code != http.client.OK:
                 raise Exception("Unable to set %s, HTTP status %s"
                                 % (script_type_display, status_code))
-            self.__logger.info("%s '%s' successfully updated" %
-                               (script_type_display, script_name))
+            self.__logger.info("%s '%s' successfully updated", script_type_display, script_name)
         except HTTPError as err:
             self.__logger.info(err)
             if not new:
                 self.__logger.info(
-                    'Attempting to restore the old %s.' % script_type_display)
+                    'Attempting to restore the old %s.', script_type_display)
                 status_code, _ = self.__http_client.put(
                     url, body=script_original)
-                if status_code != httplib.OK:
+                if status_code != http.client.OK:
                     raise Exception("Unable to restore %s, HTTP status %s"
                                     % (script_type_display, status_code))
 
@@ -480,7 +494,7 @@ class Simulation(object):
         :param script_type: A string containing the type of the script to be deleted.
         :param script_name: A string containing the name of the script to be deleted.
         """
-        assert isinstance(script_name, str)
+        assert isinstance(script_name, string_types)
 
         script_type_display = ' '.join(script_type.split('-')).title()
 
@@ -495,31 +509,37 @@ class Simulation(object):
                              % (script_type_display, script_name, script_type_display,
                                 str('\n'.join(script_list))))
 
-        self.__logger.info('Attempting to delete %s %s' %
-                           (script_type_display, script_name))
+        self.__logger.info('Attempting to delete %s %s', script_type_display, script_name)
         url = '%s/%s/%s' % (self.__sim_url, self.__config['simulation-scripts'][script_type],
                             script_name)
         status_code = self.__http_client.delete(url, body=script_name)
 
-        if status_code != httplib.OK:
+        if status_code != http.client.OK:
             raise Exception("Unable to delete %s, HTTP status %s"
                             % (script_type_display, status_code))
-        self.__logger.info("%s '%s' successfully deleted" %
-                           (script_type_display, script_name))
+        self.__logger.info("%s '%s' successfully deleted", script_type_display, script_name)
 
     def print_transfer_functions(self):
         """
         Prints a list of the transfer-function names defined in the experiment.
         """
-        print "\n".join(self.__get_simulation_scripts(
-            'transfer-function')['data'].keys())
+        printStr = '\n'.join(list(self.__get_simulation_scripts(
+            'transfer-function')['data'].keys()))
+        try:
+            print(unicode(printStr))
+        except NameError:
+            print(printStr)
 
     def print_state_machines(self):
         """
         Prints a list of the state-machine names defined in the experiment.
         """
-        print "\n".join(self.__get_simulation_scripts(
-            'state-machine')['data'].keys())
+        printStr = "\n".join(list(self.__get_simulation_scripts(
+            'state-machine')['data'].keys()))
+        try:
+            print(unicode(printStr))
+        except NameError:
+            print(printStr)
 
     def get_transfer_function(self, transfer_function_name):
         """
@@ -575,15 +595,15 @@ class Simulation(object):
                              self.__config['simulation-scripts'][experiment_data_type])
         else:
             url = '%s/%s/%s' % (self.__config['proxy-services']['save-data'],
-                                           self.__experiment_id,
-                                           self.__config['proxy-save'][experiment_data_type])
+                                self.__experiment_id,
+                                self.__config['proxy-save'][experiment_data_type])
         try:
             method_fun = getattr(self.__http_client, method)
             status_code, _ = method_fun(url, body=experiment_data)
 
-            if status_code != httplib.OK:
+            if status_code != http.client.OK:
                 raise Exception('Error status code %s' % status_code)
-            self.__logger.info("Saved %s." % experiment_data_type)
+            self.__logger.info("Saved %s.", experiment_data_type)
         except Exception as err:
             self.__logger.info(err)
             raise Exception("Failed to save %s." % experiment_data_type)
@@ -592,8 +612,8 @@ class Simulation(object):
         """
         Saves the current transfer functions to the storage
         """
-        transfer_functions = self.__get_simulation_scripts(
-            'transfer-function')['data'].keys()
+        transfer_functions = list(self.__get_simulation_scripts(
+            'transfer-function')['data'].keys())
         transfer_functions_list = []
         for tf in transfer_functions:
             transfer_functions_list.append(self.get_transfer_function(str(tf)))
@@ -607,7 +627,7 @@ class Simulation(object):
         Saves the current state machines to the storage
         """
         state_machines_dict = {}
-        for sm in self.__get_simulation_scripts('state-machine')['data'].keys():
+        for sm in list(self.__get_simulation_scripts('state-machine')['data'].keys()):
             state_machines_dict[sm] = self.get_state_machine(str(sm))
 
         self.__save_experiment_data('state-machine', {'experiment': self.__experiment_id,
@@ -623,7 +643,7 @@ class Simulation(object):
         # Remove the regex entry from the populations dictionary if it is available. The regex
         # entry is provided by the frontend to check for duplicate population names and should be
         # refactored.
-        if 'regex' in populations.values()[0]:
+        if 'regex' in list(populations.values())[0]:
             for pop in populations:
                 del populations[pop]['regex']
 
@@ -714,7 +734,7 @@ class Simulation(object):
             started = True
         try:
             status_code, _ = self.__http_client.put(url, body=body)
-            if status_code != httplib.OK:
+            if status_code != http.client.OK:
                 raise Exception(
                     "Unable to set Populations, HTTP status %s" % status_code)
             self.__logger.info("Populations successfully updated.")
@@ -726,7 +746,7 @@ class Simulation(object):
             body['brain_type'] = old_brain_type
 
             status_code, _ = self.__http_client.put(url, body=body)
-            if status_code != httplib.OK:
+            if status_code != http.client.OK:
                 raise Exception(
                     "Unable to restore Brain, HTTP status %s" % status_code)
 
@@ -746,7 +766,7 @@ class Simulation(object):
                                    integers, lists of integers or python slices.
         """
 
-        assert isinstance(brain_script, (str, unicode))
+        assert isinstance(brain_script, (string_types, string_types))
         assert isinstance(neural_population, dict)
 
         if not self.__server or not self.__sim_url:
@@ -776,7 +796,7 @@ class Simulation(object):
         # check for current simulation state and pause simulation if it's running
         try:
             status_code, _ = self.__http_client.put(url, body=body)
-            if status_code != httplib.OK:
+            if status_code != http.client.OK:
                 raise Exception(
                     "Unable to set Brain, HTTP status %s" % status_code)
             self.__logger.info("Brain successfully updated.")
@@ -786,7 +806,7 @@ class Simulation(object):
             body['data'] = old_brain
             body['populations'] = old_populations
             status_code, _ = self.__http_client.put(url, body=body)
-            if status_code != httplib.OK:
+            if status_code != http.client.OK:
                 raise Exception(
                     "Unable to restore Brain, HTTP status %s" % status_code)
 
@@ -842,27 +862,27 @@ class Simulation(object):
                            config file.
         """
 
-        assert isinstance(reset_type, (str, unicode))
+        assert isinstance(reset_type, (string_types, string_types))
 
         # NRRPLT-7855
         if reset_type in ['full', 'world']:
             raise ValueError('Reset %s temporarily disabled due to known Gazebo issue' % reset_type)
 
-        if reset_type not in self.__config['reset-services'].keys():
+        if reset_type not in self.__config['reset-services']:
             raise ValueError('Undefined reset type. Possible values are: %s'
-                             % ", ".join(self.__config['reset-services'].keys()))
+                             % ", ".join(list(self.__config['reset-services'].keys())))
 
         # pausing simulation before attempting to reset
         self.pause()
 
-        self.__logger.info("Attempting to reset %s" % reset_type)
+        self.__logger.info("Attempting to reset %s", reset_type)
         url = '%s/%s/%s' % (self.__sim_url, self.__experiment_id,
                             self.__config['simulation-services']['reset'])
         body = {'resetType': self.__config['reset-services'][reset_type]}
         status_code, _ = self.__http_client.put(url, body=body)
 
         # check the return code, this will return OK if the REST call succeeds
-        if status_code != httplib.OK:
+        if status_code != http.client.OK:
             self.start()
             raise Exception(
                 "Unable to reset simulation, HTTP status %s" % status_code)
@@ -885,7 +905,7 @@ class Simulation(object):
 
         """
 
-        assert isinstance(cmd, str)
+        assert isinstance(cmd, string_types)
 
         # ensure the simulation is started and valid
         if not self.__server or not self.__sim_url:
@@ -901,7 +921,7 @@ class Simulation(object):
         status_code, result = self.__http_client.post(url, body=cmd)
 
         # check the return code, this will return OK if the REST call succeeds
-        if status_code != httplib.OK:
+        if status_code != http.client.OK:
             raise Exception(
                 "Unable to send command to recorder, HTTP status %s" % status_code)
 
@@ -932,9 +952,9 @@ class Simulation(object):
                 filename = os.path.splitext(filename)[0]
                 filename = filename + '.txt'
                 self.__vc.set_experiment_file(self.__experiment_id,
-                                                os.path.join('recordings',
-                                                             filename),
-                                                description)
+                                              os.path.join('recordings',
+                                                           filename),
+                                              description)
 
         self.__send_recorder_cmd('reset')
 
diff --git a/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/tests/test_config.py b/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/tests/test_config.py
index 6f13d21..2bb14bd 100644
--- a/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/tests/test_config.py
+++ b/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/tests/test_config.py
@@ -64,7 +64,7 @@ class TestConfig(unittest.TestCase):
     def test_update_proxy_services(self):
 
         config = Config('local')
-        for k, v in config['proxy-services'].iteritems():
+        for k, v in config['proxy-services'].items():
             self.assertEqual(v, '%s/%s' % (self._conf['proxy']['local'], self._conf['proxy-services'][k]))
 
     def test_environment_from_version(self):
@@ -73,7 +73,7 @@ class TestConfig(unittest.TestCase):
         environment = 'dev' if 'dev' in VERSION else 'staging'
 
         config = Config(None)
-        for k, v in config['proxy-services'].iteritems():
+        for k, v in config['proxy-services'].items():
             self.assertEqual(v, '%s/%s' % (self._conf['proxy'][environment], self._conf['proxy-services'][k]))
 
     @patch('hbp_nrp_virtual_coach.config.json.load')
diff --git a/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/tests/test_simulation.py b/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/tests/test_simulation.py
index a69ff87..e474710 100644
--- a/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/tests/test_simulation.py
+++ b/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/tests/test_simulation.py
@@ -25,6 +25,9 @@
 Unit tests for the Virtual Coach simulation interface.
 """
 
+from future import standard_library
+standard_library.install_aliases()
+from builtins import object
 from hbp_nrp_virtual_coach.simulation import Simulation
 
 from hbp_nrp_virtual_coach.requests_client import RequestsClient
@@ -33,10 +36,10 @@ from hbp_nrp_virtual_coach.config import Config
 from mock import Mock, patch, call
 import unittest
 
-import httplib
-from urllib2 import HTTPError
+import http.client
+from urllib.error import HTTPError
 import json
-from StringIO import StringIO
+from io import StringIO
 
 
 class TestSimulation(unittest.TestCase):
@@ -48,7 +51,7 @@ class TestSimulation(unittest.TestCase):
         get_response = (None,
             '{"serverJobLocation": "mock-location",' \
             '"gzweb": {"nrp-services": "mock-services"}}')
-        post_response = (httplib.CREATED, '{"simulationID": "12"}')
+        post_response = (http.client.CREATED, '{"simulationID": "12"}')
         self._sim._Simulation__http_client.get = Mock(return_value=get_response)
         self._sim._Simulation__http_client.post = Mock(return_value=post_response)
 
@@ -78,7 +81,7 @@ class TestSimulation(unittest.TestCase):
     @patch('hbp_nrp_virtual_coach.simulation.traceback')
     def test_failed_create_conflict(self, mocked_traceback):
         self.setUpForLaunch()
-        self._sim._Simulation__http_client.post = Mock(return_value=(httplib.CONFLICT, '{}'))
+        self._sim._Simulation__http_client.post = Mock(return_value=(http.client.CONFLICT, '{}'))
 
         self.assertEqual(self._sim.launch('id', 'conf', 'server-name', None), False)
         self._sim._Simulation__http_client.post.assert_called_once()
@@ -87,7 +90,7 @@ class TestSimulation(unittest.TestCase):
     @patch('hbp_nrp_virtual_coach.simulation.traceback')
     def test_failed_create_other(self, mocked_traceback):
         self.setUpForLaunch()
-        self._sim._Simulation__http_client.post = Mock(return_value=(httplib.NOT_FOUND, '{}'))
+        self._sim._Simulation__http_client.post = Mock(return_value=(http.client.NOT_FOUND, '{}'))
 
         self.assertEqual(self._sim.launch('id', 'conf', 'server-name', None), False)
         self._sim._Simulation__http_client.post.assert_called_once()
@@ -127,15 +130,25 @@ class TestSimulation(unittest.TestCase):
             if name == 'rospy' or name == 'std_msgs.msg' or name == 'cle_ros_msgs.msg':
                 return mock_rospy
             return real_import(name, *args)
-
-        with patch('__builtin__.__import__', side_effect=mock_import):
-            self.assertEqual(self._sim.launch('id', 'conf', 'server-name', 'reservation'), True)
-            mock_rospy.Subscriber.assert_has_calls([call('/ros_cle_simulation/status',
-                                                         mock_rospy.String,
-                                                         self._sim._Simulation__on_status),
-                                                   call('/ros_cle_simulation/cle_error',
-                                                        mock_rospy.CLEError,
-                                                        self._sim._Simulation__on_error)])
+       
+        try:
+            with patch('__builtin__.__import__', side_effect=mock_import):
+                self.assertEqual(self._sim.launch('id', 'conf', 'server-name', 'reservation'), True)
+                mock_rospy.Subscriber.assert_has_calls([call('/ros_cle_simulation/status',
+                                                             mock_rospy.String,
+                                                             self._sim._Simulation__on_status),
+                                                       call('/ros_cle_simulation/cle_error',
+                                                            mock_rospy.CLEError,
+                                                            self._sim._Simulation__on_error)])
+        except ModuleNotFoundError:
+            with patch('builtins.__import__', side_effect=mock_import):
+                self.assertEqual(self._sim.launch('id', 'conf', 'server-name', 'reservation'), True)
+                mock_rospy.Subscriber.assert_has_calls([call('/ros_cle_simulation/status',
+                                                             mock_rospy.String,
+                                                             self._sim._Simulation__on_status),
+                                                       call('/ros_cle_simulation/cle_error',
+                                                            mock_rospy.CLEError,
+                                                            self._sim._Simulation__on_error)])
 
     def test_create_without_rospy(self):
 
@@ -159,9 +172,14 @@ class TestSimulation(unittest.TestCase):
 
         self._sim._Simulation__logger = Mock()
 
-        with patch('__builtin__.__import__', side_effect=mock_import_fail):
-            self.assertEqual(self._sim.launch('id', 'conf', 'server-name', 'reservation'), True)
-            mock_rospy.assert_not_called()
+        try:
+            with patch('__builtin__.__import__', side_effect=mock_import_fail):
+                self.assertEqual(self._sim.launch('id', 'conf', 'server-name', 'reservation'), True)
+                mock_rospy.assert_not_called()
+        except ModuleNotFoundError:
+            with patch('builtins.__import__', side_effect=mock_import_fail):
+                self.assertEqual(self._sim.launch('id', 'conf', 'server-name', 'reservation'), True)
+                mock_rospy.assert_not_called()
 
     def test_set_state_asserts(self):
         self.assertRaises(AssertionError, self._sim._Simulation__set_state, None)
@@ -172,7 +190,7 @@ class TestSimulation(unittest.TestCase):
         self._sim._Simulation__sim_url = 'url'
 
         # mock the HTTP call
-        self._sim._Simulation__http_client.put = Mock(return_value=(httplib.OK, None))
+        self._sim._Simulation__http_client.put = Mock(return_value=(http.client.OK, None))
 
         self._sim._Simulation__set_state('initialized')
         self._sim._Simulation__http_client.put.assert_called_once()
@@ -182,7 +200,7 @@ class TestSimulation(unittest.TestCase):
         self._sim._Simulation__sim_url = 'url'
 
         # mock the HTTP call
-        self._sim._Simulation__http_client.put = Mock(return_value=(httplib.NOT_FOUND, None))
+        self._sim._Simulation__http_client.put = Mock(return_value=(http.client.NOT_FOUND, None))
 
         self.assertRaises(Exception, self._sim._Simulation__set_state, 'started')
         self._sim._Simulation__http_client.put.assert_called_once()
@@ -215,7 +233,7 @@ class TestSimulation(unittest.TestCase):
         self._sim._Simulation__sim_url = 'url'
 
         # mock the OIDC call
-        self._sim._Simulation__http_client.get = Mock(return_value=(httplib.OK,
+        self._sim._Simulation__http_client.get = Mock(return_value=(http.client.OK,
                                                              '{"state": "{}"}'))
 
         self._sim.get_state()
@@ -228,7 +246,7 @@ class TestSimulation(unittest.TestCase):
         self._sim._Simulation__sim_url = 'url'
 
         # mock the OIDC call
-        self._sim._Simulation__http_client.get = Mock(return_value=(httplib.NOT_FOUND,
+        self._sim._Simulation__http_client.get = Mock(return_value=(http.client.NOT_FOUND,
                                                              None))
         self.assertRaises(Exception, self._sim.get_state)
         self._sim._Simulation__http_client.get.assert_called_once()
@@ -248,7 +266,7 @@ class TestSimulation(unittest.TestCase):
 
         # mock the oidc call, the get_all_transfer_function call, the start call, the pause call
         # and the get_state call
-        self._sim._Simulation__http_client.get = Mock(return_value=(httplib.NOT_FOUND,
+        self._sim._Simulation__http_client.get = Mock(return_value=(http.client.NOT_FOUND,
                                                              None))
 
         self.assertRaises(Exception, self._sim._Simulation__get_simulation_scripts, 'foo')
@@ -277,16 +295,16 @@ class TestSimulation(unittest.TestCase):
 
         self.assertRaises(Exception, self._sim.add_transfer_function, 'foo')
 
-        http_error = HTTPError("url", 404, "message", {}, file)
+        http_error = HTTPError("url", 404, "message", {}, None)
 
         self._sim._Simulation__http_client.put.side_effect = [http_error,
-                                                            (httplib.NOT_FOUND,
+                                                            (http.client.NOT_FOUND,
                                                              None)]
 
         self.assertRaises(Exception, self._sim.edit_state_machine, 'foo', 'import os\n')
 
         self._sim._Simulation__http_client.put.side_effect = [http_error,
-                                                            (httplib.OK, None)]
+                                                            (http.client.OK, None)]
         self.assertRaises(Exception, self._sim.add_state_machine, 'bar', 'import os\n')
 
     def test_get_transfer_function_asserts(self):
@@ -332,7 +350,7 @@ class TestSimulation(unittest.TestCase):
         self._sim._Simulation__get_simulation_scripts.return_value = {'brain_type': 'py',
                                                                 'data_type': 'text'}
         self._sim._Simulation__http_client.put = Mock()
-        self._sim._Simulation__http_client.put.return_value = (httplib.OK,
+        self._sim._Simulation__http_client.put.return_value = (http.client.OK,
                                                              '{"data": {"foo": ""}}')
         self._sim.get_state = Mock()
         self._sim.get_state.return_value = 'started'
@@ -352,15 +370,15 @@ class TestSimulation(unittest.TestCase):
         self._sim._Simulation__http_client.put.return_value = (HTTPError, None)
         self.assertRaises(Exception, self._sim.edit_brain, 'import os\n')
 
-        http_error = HTTPError("url", 404, "message", {}, file)
+        http_error = HTTPError("url", 404, "message", {}, None)
 
         self._sim._Simulation__http_client.put.side_effect = [http_error,
-                                                            (httplib.NOT_FOUND,
+                                                            (http.client.NOT_FOUND,
                                                              None)]
         self.assertRaises(Exception, self._sim.edit_brain, 'foo')
 
         self._sim._Simulation__http_client.put.side_effect = [http_error,
-                                                            (httplib.OK, None)]
+                                                            (http.client.OK, None)]
         self.assertRaises(Exception, self._sim.edit_brain, 'foo')
 
     def test_edit_populations(self):
@@ -376,7 +394,7 @@ class TestSimulation(unittest.TestCase):
         self._sim._Simulation__get_simulation_scripts = Mock(return_value={'data_type': 'foo',
                                                                            'brain_type': 'foo',
                                                                            'brain_populations': 'bar'})
-        self._sim._Simulation__http_client.put = Mock(return_value=(httplib.OK,
+        self._sim._Simulation__http_client.put = Mock(return_value=(http.client.OK,
                                                                     {}))
 
         self._sim.edit_populations({'foo': 'bar'})
@@ -401,9 +419,9 @@ class TestSimulation(unittest.TestCase):
 
         # mock the http call, the get_all_transfer_function call, the start call, the pause call
         # and the get_state call
-        self._sim._Simulation__http_client.get = Mock(return_value=(httplib.OK,
+        self._sim._Simulation__http_client.get = Mock(return_value=(http.client.OK,
                                                              '{"data": {"foo": ""}}'))
-        self._sim._Simulation__http_client.put = Mock(return_value=(httplib.OK,None))
+        self._sim._Simulation__http_client.put = Mock(return_value=(http.client.OK,None))
 
         self._sim._Simulation__get_all_transfer_functions = Mock()
         self._sim._Simulation__get_all_transfer_functions.return_value = {'foo': ''}
@@ -432,7 +450,7 @@ class TestSimulation(unittest.TestCase):
 
         # mock the http call, the get_all_transfer_function call, the start call, the pause call
         # and the get_state call
-        self._sim._Simulation__http_client.delete = Mock(return_value=httplib.OK)
+        self._sim._Simulation__http_client.delete = Mock(return_value=http.client.OK)
 
         self._sim._Simulation__get_simulation_scripts = Mock()
         self._sim._Simulation__get_simulation_scripts.return_value = {'data': {'foo': '', 'bar': ''}}
@@ -445,7 +463,7 @@ class TestSimulation(unittest.TestCase):
 
         self.assertRaises(ValueError, self._sim.delete_state_machine, 'nonExistentScript')
 
-        self._sim._Simulation__http_client.delete.return_value = (httplib.NOT_FOUND,
+        self._sim._Simulation__http_client.delete.return_value = (http.client.NOT_FOUND,
                                                              None)
         self.assertRaises(Exception, self._sim.delete_state_machine, 'foo')
 
@@ -475,7 +493,7 @@ class TestSimulation(unittest.TestCase):
 
         self._sim._Simulation__headers = {}
 
-        self._sim._Simulation__http_client.put = Mock(return_value=(httplib.OK, None))
+        self._sim._Simulation__http_client.put = Mock(return_value=(http.client.OK, None))
 
         self._sim.save_transfer_functions()
         self._sim._Simulation__http_client.put.assert_called_once_with(
@@ -504,7 +522,7 @@ class TestSimulation(unittest.TestCase):
             '%s/%s/brain' % (proxyurl, exp_id),
             body={'populations': populations,'brain': 'some brain code'})
 
-        self._sim._Simulation__http_client.post = Mock(return_value=(httplib.OK, None))
+        self._sim._Simulation__http_client.post = Mock(return_value=(http.client.OK, None))
         self._sim.save_world()
         self._sim._Simulation__http_client.post.assert_called_once_with(
             '%s/sdf_world' % (self._sim._Simulation__sim_url),
@@ -521,7 +539,18 @@ class TestSimulation(unittest.TestCase):
 
         self._sim.print_transfer_functions()
         self._sim.print_state_machines()
-        self.assertEqual(mock_stdout.getvalue().strip(), 'foobar\nfoo\nbar\nfoobar\nfoo\nbar')
+        # The order in which 'foobar', 'foo' and 'bar' appear is no longer guaranteed in python 3.3+
+        printStr = mock_stdout.getvalue().strip()
+        self.assertEqual(len(printStr), 29)
+        n = printStr.count('\n')
+        self.assertEqual(n, 5)
+        n = printStr.count('foobar')
+        self.assertEqual(n, 2)
+        n = printStr.count('foo')
+        self.assertEqual(n, 4)
+        n = printStr.count('bar')
+        self.assertEqual(n, 4)
+        #self.assertEqual(mock_stdout.getvalue().strip(), 'foobar\nfoo\nbar\nfoobar\nfoo\nbar')
         self._sim._Simulation__get_simulation_scripts.assert_called_twice()
 
     def test_register_status_callback(self):
@@ -621,14 +650,14 @@ class TestSimulation(unittest.TestCase):
         self._sim.pause = Mock()
 
         self._sim._Simulation__http_client.put = Mock()
-        self._sim._Simulation__http_client.put.return_value = (httplib.OK, None)
+        self._sim._Simulation__http_client.put.return_value = (http.client.OK, None)
 
         # NRRPLT-7855
         self.assertRaises(ValueError, self._sim.reset, 'world')
         # self._sim.reset('world')
         # self._sim._Simulation__http_client.put.assert_called_once()
 
-        self._sim._Simulation__http_client.put.return_value = (httplib.NOT_FOUND,
+        self._sim._Simulation__http_client.put.return_value = (http.client.NOT_FOUND,
                                                              None)
         self._sim.start = Mock()
 
@@ -653,7 +682,7 @@ class TestSimulation(unittest.TestCase):
         self._sim._Simulation__sim_url = 'url'
         self._sim._Simulation__vc = Mock()
         self._sim._Simulation__http_client.post = Mock()
-        self._sim._Simulation__http_client.post.return_value = (httplib.OK, '{"filename":"name.txt"}')
+        self._sim._Simulation__http_client.post.return_value = (http.client.OK, '{"filename":"name.txt"}')
         self._sim.start_recording()
         self._sim._Simulation__http_client.post.assert_called_with(u'url/recorder/start', body='start')
         self._sim.pause_recording()
diff --git a/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/tests/test_virtual_coach.py b/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/tests/test_virtual_coach.py
index 5a98e3a..48fa333 100644
--- a/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/tests/test_virtual_coach.py
+++ b/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/tests/test_virtual_coach.py
@@ -25,9 +25,28 @@
 Unit tests for the Virtual Coach main interface.
 """
 
+from future import standard_library
+standard_library.install_aliases()
+from builtins import object
 from hbp_nrp_virtual_coach.virtual_coach import VirtualCoach
 
-from bbp_client.oidc.client import BBPOIDCClient
+try:
+    from bbp_client.oidc.client import BBPOIDCClient
+except ImportError: 
+    # Currently, bbp_client is still Python2
+    import urllib.parse, urllib.error, sys, os, builtins
+    from bbp_client.oidc.openidconnect import error
+    from bbp_client.oidc.oauth2client import clientsecrets
+    # Python3 only allows absolute paths. In order to allow bbp_client to import 
+    # using relative paths, the following two folders have to be added to the import path:
+    sys.path.append(os.path.dirname(error.__file__))
+    sys.path.append(os.path.dirname(clientsecrets.__file__))
+    # Rename the following Python2-packages such that they refer to their Python3 counterparts:
+    sys.modules['urlparse'] = urllib.parse
+    sys.modules['urllib2'] = urllib.error
+    sys.modules['__builtin__'] = builtins
+    from bbp_client.oidc.client import BBPOIDCClient
+    
 
 from mock import Mock, patch, MagicMock
 import unittest
@@ -38,7 +57,7 @@ import logging
 import copy
 from dateutil import parser
 import json
-from StringIO import StringIO
+from io import StringIO
 import os
 
 
@@ -56,7 +75,11 @@ class TestVirtualCoach(unittest.TestCase):
         def rospy_import_fail(name, globals=globals(), locals=locals(), fromlist=[], level=-1):
             if name == 'rospy':
                 raise ImportError('no ROS for tests')
-            return realimport(name, globals, locals, fromlist, level)
+            try:
+                return realimport(name, globals, locals, fromlist, level)
+            except:
+                pass
+            return realimport(name, globals, locals, fromlist, 0)
         builtins.__import__ = rospy_import_fail
 
         self._tests_path = os.path.dirname(os.path.realpath(__file__))
diff --git a/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/virtual_coach.py b/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/virtual_coach.py
index b5a5e89..b59b32b 100644
--- a/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/virtual_coach.py
+++ b/hbp_nrp_virtual_coach/hbp_nrp_virtual_coach/virtual_coach.py
@@ -25,25 +25,42 @@
 Virtual Coach main entry point for interacting with the experiment list and launching simulations.
 """
 
-from hbp_nrp_virtual_coach.config import Config
-from hbp_nrp_virtual_coach.simulation import Simulation
-from hbp_nrp_virtual_coach.requests_client import RequestsClient
-from hbp_nrp_virtual_coach.oidc_http_client import OIDCHTTPClient
+# pylint: disable=W0622
+
+from __future__ import print_function
+
+from builtins import str
+from builtins import object
 
-from datetime import datetime, timedelta
-from collections import defaultdict
-from dateutil import parser, tz
 import json
-import requests
 import logging
 import getpass
-import httplib
+import http.client
 import os
 import zipfile
 import tempfile
-from texttable import Texttable
+
 from copy import copy
 
+from datetime import datetime, timedelta
+from collections import defaultdict
+from dateutil import parser, tz
+
+import requests
+
+from texttable import Texttable
+
+from six import string_types
+
+from future import standard_library
+
+from hbp_nrp_virtual_coach.config import Config
+from hbp_nrp_virtual_coach.simulation import Simulation
+from hbp_nrp_virtual_coach.requests_client import RequestsClient
+from hbp_nrp_virtual_coach.oidc_http_client import OIDCHTTPClient
+
+standard_library.install_aliases()
+
 logger_format = '%(levelname)s: [%(asctime)s - %(name)s] %(message)s'
 logging.basicConfig(format=logger_format, level=logging.INFO)
 logger = logging.getLogger('VirtualCoach')
@@ -82,10 +99,10 @@ class VirtualCoach(object):
         :param storage_password: (optional) A string representing the Storage Server password. If
                                  not supplied, the user will be prompted to enter a password.
         """
-        assert isinstance(environment, (str, type(None)))
-        assert isinstance(oidc_username, (str, type(None)))
-        assert isinstance(oidc_token, (str, type(None)))
-        assert isinstance(storage_username, (str, type(None)))
+        assert isinstance(environment, (string_types, type(None)))
+        assert isinstance(oidc_username, (string_types, type(None)))
+        assert isinstance(oidc_token, (string_types, type(None)))
+        assert isinstance(storage_username, (string_types, type(None)))
 
         # ROS node and logger configuration only if rospy is available
         # pylint: disable=import-error
@@ -124,7 +141,7 @@ class VirtualCoach(object):
             # Set self.__http_headers: it is also used
             # as an argument of requests.post() and requests.get()
             self.__http_headers = {'Content-Type': 'application/json',
-                                    'Authorization': authorization}
+                                   'Authorization': authorization}
             self.__http_client.set_headers(self.__http_headers)
         # if a Storage Server username is provided, attempt to login
         elif storage_username:
@@ -138,7 +155,7 @@ class VirtualCoach(object):
                 storage_username, storage_password
             )
             self.__http_headers = {'Content-Type': 'application/json',
-                                    'Authorization': authorization}
+                                   'Authorization': authorization}
             self.__http_client = RequestsClient(self.__http_headers)
         else:
             raise Exception('Virtual Coach instantiated without storage server credentials or oidc'
@@ -165,7 +182,7 @@ class VirtualCoach(object):
         # construct the table of experiments with only minimal useful information
         table = Texttable()
         table.header(['Configuration', 'Name', 'Configuration Path', 'Description'])
-        for name, v in sorted(exp_list.iteritems(), key=lambda x: x[1]['configuration']['name']):
+        for name, v in sorted(iter(exp_list.items()), key=lambda x: x[1]['configuration']['name']):
             if v['configuration']['maturity'] != 'production' and not dev:
                 continue
             if dev:
@@ -176,7 +193,7 @@ class VirtualCoach(object):
 
         # display the table
         logger.info('List of production%s experiments:', '' if not dev else ' and development')
-        print table.draw()
+        print(table.draw())
 
     def print_running_experiments(self, cloned=False):
         """
@@ -189,7 +206,7 @@ class VirtualCoach(object):
         # construct a table with minimal useful information
         table = Texttable()
         table.header(['Configuration', 'Owner', 'Time', 'Timeout', 'State', 'Server'])
-        for name, v in sorted(exp_list.iteritems(), key=lambda x: x[1]['configuration']['name']):
+        for name, v in sorted(iter(exp_list.items()), key=lambda x: x[1]['configuration']['name']):
 
             # for any running experiments of this type
             for server in v['joinableServers']:
@@ -219,7 +236,7 @@ class VirtualCoach(object):
 
         # display the table
         logger.info('All running experiments:')
-        print table.draw()
+        print(table.draw())
 
     def print_available_servers(self):
         """
@@ -232,12 +249,16 @@ class VirtualCoach(object):
         servers = [server['id'] for server in available_servers]
 
         # add a display value if there are no available servers
-        if len(servers) == 0:
+        if not servers:
             servers = ['No available servers.']
 
         # print the list of available servers
         logger.info('Available servers:')
-        print '\n'.join(servers)
+        printStr = '\n'.join(servers)
+        try:
+            print(unicode(printStr))
+        except NameError:
+            print(printStr)
         return servers
 
     def launch_experiment(self, experiment_id, server=None, reservation=None, cloned=True):
@@ -255,9 +276,9 @@ class VirtualCoach(object):
         :param cloned: (optional) True or False depending on if the launched
                        is a cloned experiment or not.
         """
-        assert isinstance(experiment_id, str)
-        assert isinstance(server, (str, type(None)))
-        assert isinstance(reservation, (str, type(None)))
+        assert isinstance(experiment_id, string_types)
+        assert isinstance(server, (string_types, type(None)))
+        assert isinstance(reservation, (string_types, type(None)))
 
         # retrieve the list of cloned experiments to verify that the given id is valid for the
         # backend
@@ -286,15 +307,15 @@ class VirtualCoach(object):
             servers = [server]
 
         # if there are no available servers, abort
-        if len(servers) == 0:
+        if not servers:
             raise ValueError('No available servers for %s, try again later.' % experiment_id)
 
         # attempt to launch the simulation on all server targets, on success return an interface
         # to the simulation
         sim = Simulation(self.__http_client, self.__config, self)
-        for server in servers:
+        for server_i in servers:
             try:
-                if sim.launch(experiment_id, str(experiment_conf), str(server),
+                if sim.launch(experiment_id, str(experiment_conf), str(server_i),
                               reservation, cloned):
                     return sim
 
@@ -314,11 +335,11 @@ class VirtualCoach(object):
         :param exp_id: The id of the experiment to be cloned
         :returns: The ID of the cloned experiment
         """
-        assert isinstance(exp_id, str)
+        assert isinstance(exp_id, string_types)
         exp = self.__get_experiment_list()
-        if exp_id not in exp.keys():
+        if exp_id not in list(exp.keys()):
             raise ValueError('Experiment ID: "%s" is invalid, please check the list of all '
-                             'experiments: \n%s' % (exp_id, '\n'.join(exp.keys())))
+                             'experiments: \n%s' % (exp_id, '\n'.join(list(exp.keys()))))
 
         exp_config_path = exp[exp_id]['configuration']['experimentConfiguration']
         body = {'expPath': exp_config_path}
@@ -326,9 +347,8 @@ class VirtualCoach(object):
             self.__config['proxy-services']['experiment-clone'], body=body)
         if status_code != 200:
             raise Exception('Cloning Experiment failed, Status Code: %s' % status_code)
-        else:
-            logger.info('Experiment "%s" cloned successfully', exp_id)
-            return content
+        logger.info('Experiment "%s" cloned successfully', exp_id)
+        return content
 
     def delete_cloned_experiment(self, exp_id):
         """
@@ -353,7 +373,7 @@ class VirtualCoach(object):
         :returns: A dict containing the ID of the cloned experiment and the ID of the original
                   experiment. Dict Keys are: 'clonedExp' and 'originalExp'
         """
-        assert isinstance(experiment_id, str)
+        assert isinstance(experiment_id, string_types)
 
         exp_list = self.__get_experiment_list(cloned=True)
         if experiment_id not in exp_list:
@@ -373,9 +393,8 @@ class VirtualCoach(object):
                             headers=self.__http_headers)
         if res.status_code != 200:
             raise Exception('Cloning Experiment failed, Status Code: %s' % res.status_code)
-        else:
-            logger.info('Experiment "%s" cloned successfully', experiment_id)
-            return res.content
+        logger.info('Experiment "%s" cloned successfully', experiment_id)
+        return res.content
 
     def print_cloned_experiments(self):
         """
@@ -387,7 +406,7 @@ class VirtualCoach(object):
         table.header(['Name'])
         for experiment in exp_list:
             table.add_row([experiment])
-        print table.draw()
+        print(table.draw())
 
     def __get_storage_token(self, user_name, password):
         """
@@ -396,16 +415,15 @@ class VirtualCoach(object):
         :param user_name:  string representing the Storage Server username
         :param password:  string representing the Storage Server password
         """
-        assert isinstance(user_name, str)
-        assert isinstance(password, str)
+        assert isinstance(user_name, string_types)
+        assert isinstance(password, string_types)
 
         response = requests.post(self.__config['proxy-services']['storage-authentication'],
                                  json={'user': user_name, 'password': password})
         if response.status_code != 200:
             raise Exception('Storage Server authentication failed, Status Code: %d'
                             % response.status_code)
-        else:
-            return response.content
+        return response.content
 
     def __get_experiment_list(self, cloned=False):
         """
@@ -422,10 +440,9 @@ class VirtualCoach(object):
             # return a simple list containing only experiment names since this is the only
             # information in the dictionary anyway
             return {experiment['name']: experiment for experiment in json.loads(response.content)}
-        else:
-            _, response = self.__http_client.get(
-                self.__config['proxy-services']['experiment-list'])
-            return json.loads(response)
+        _, response = self.__http_client.get(
+            self.__config['proxy-services']['experiment-list'])
+        return json.loads(response)
 
     def __get_available_server_list(self):
         """
@@ -435,7 +452,7 @@ class VirtualCoach(object):
         status_code, response = self.__http_client.get(
             self.__config['proxy-services']['available-servers'])
 
-        if str(status_code) != str(httplib.OK):
+        if str(status_code) != str(http.client.OK):
             raise Exception('Error when getting server list, Status Code: %d. Error: %s'
                             % (status_code, response))
         return json.loads(response)
@@ -449,7 +466,7 @@ class VirtualCoach(object):
         response = requests.get(self.__config['proxy-services']['csv-files'] % (experiment_id,),
                                 headers=self.__http_headers)
 
-        if response.status_code != httplib.OK:
+        if response.status_code != http.client.OK:
             raise Exception('Error when getting CSV files Status Code: %d. Error: %s'
                             % (response.status_code, response))
         csv_files = json.loads(response.content)
@@ -471,11 +488,11 @@ class VirtualCoach(object):
         table.set_cols_align(['r', 'c', 'r'])
 
         for i, run_date in enumerate(sorted(csv_files.keys())):
-            run_size = sum(file['size'] for file in csv_files[run_date].values())
+            run_size = sum(file['size'] for file in list(csv_files[run_date].values()))
             table.add_row([i, run_date, run_size])
 
         logger.info('List of simulation runs')
-        print table.draw()
+        print(table.draw())
 
     def print_run_csv_files(self, exp_id, run_id):
         """
@@ -495,11 +512,11 @@ class VirtualCoach(object):
             raise Exception('Could not find run %i, %i runs were found' %
                             (run_id, len(sorted_runs)))
 
-        for csv_file in csv_files[sorted_runs[run_id]].values():
+        for csv_file in list(csv_files[sorted_runs[run_id]].values()):
             table.add_row([csv_file['name'], csv_file['size']])
 
         logger.info('Run %i list of files.', run_id)
-        print table.draw()
+        print(table.draw())
 
     def print_last_run_csv_files(self, exp_id):
         """
@@ -517,11 +534,11 @@ class VirtualCoach(object):
         if not sorted_runs:
             raise Exception('Could not find any run')
 
-        for csv_file in csv_files[sorted_runs[-1]].values():
+        for csv_file in list(csv_files[sorted_runs[-1]].values()):
             table.add_row([csv_file['name'], csv_file['size']])
 
         logger.info('Last run list of files')
-        print table.draw()
+        print(table.draw())
 
     def __get_csv_file_content(self, exp_id, file_uuid):
         """
@@ -533,10 +550,10 @@ class VirtualCoach(object):
         logger.info('Retrieving CSV file.')
 
         response = requests.get(self.__config['proxy-services']['experiment-file'] %
-                                    (exp_id, file_uuid),
+                                (exp_id, file_uuid),
                                 headers=self.__http_headers)
 
-        if response.status_code != httplib.OK:
+        if response.status_code != http.client.OK:
             raise Exception('Error when getting CSV file Status Code: %d. Error: %s'
                             % (response.status_code, response))
 
@@ -557,7 +574,7 @@ class VirtualCoach(object):
                             (run_id, len(sorted_runs)))
 
         if file_name not in csv_files[sorted_runs[run_id]]:
-            file_names = ', '.join(f['name'] for f in csv_files[sorted_runs[run_id]].values())
+            file_names = ', '.join(f['name'] for f in list(csv_files[sorted_runs[run_id]].values()))
             raise Exception('Could not find file \'%s\' in run %i, available file names are: %s' %
                             (file_name, run_id, file_names))
 
@@ -578,7 +595,8 @@ class VirtualCoach(object):
             raise Exception('Could not find any run')
 
         if file_name not in csv_files[sorted_runs[-1]]:
-            file_names = ', '.join(file['name'] for file in csv_files[sorted_runs[-1]].values())
+            file_names = ', '.join(
+                file['name'] for file in list(csv_files[sorted_runs[-1]].values()))
             raise Exception('Could not find file \'%s\' in last run, available file names are: %s' %
                             (file_name, file_names))
 
@@ -601,7 +619,7 @@ class VirtualCoach(object):
         file_headers['Content-Type'] = 'text/plain'
         response = requests.post(url, data=file_content, headers=file_headers)
 
-        if response.status_code != httplib.OK:
+        if response.status_code != http.client.OK:
             raise Exception('Error when setting file: %d. Error: %s'
                             % (response.status_code, response))
 
@@ -640,7 +658,7 @@ class VirtualCoach(object):
             logger.error('The folder %s could not be zipped', dirpath)
             raise e
         zip_file.close()
-        content = open(temp, 'r').read()
+        content = open(temp, 'rb').read()
         os.remove(temp)
         return content
 
@@ -651,7 +669,7 @@ class VirtualCoach(object):
         :param path: path to the experiment folder or to the zip file to be imported
         :type path: str
         """
-        if not isinstance(path, str):
+        if not isinstance(path, string_types):
             raise TypeError('The provided argument is not a string.')
         if not os.path.isfile(path) and not os.path.isdir(path):
             raise ValueError('The file or folder named %(path)s does not exist.' % {'path': path})
diff --git a/hbp_nrp_virtual_coach/requirements.txt b/hbp_nrp_virtual_coach/requirements.txt
index 4da393e..2f5d8d5 100644
--- a/hbp_nrp_virtual_coach/requirements.txt
+++ b/hbp_nrp_virtual_coach/requirements.txt
@@ -2,14 +2,12 @@
 bbp-client==0.4.4  # EPFL_SPECIFIC
 
 # third-party dependencies
+future==0.18.2
 texttable==0.8.7
 progressbar2==3.34.0
 httplib2==0.12.1
 pandas==0.22.0
-ipywidgets==7.5.1
+ipywidgets<=7.5.1; python_version<"3.0"  # v8.0.0 incompatible with python 2
 jupyter==1.0.0
 requests
 ipykernel==4.6.0
-
-# unit test dependencies
-mock==1.0.1
diff --git a/hbp_nrp_virtual_coach/requirements_extension_tests.txt b/hbp_nrp_virtual_coach/requirements_extension_tests.txt
new file mode 100644
index 0000000..a8a6bd0
--- /dev/null
+++ b/hbp_nrp_virtual_coach/requirements_extension_tests.txt
@@ -0,0 +1,2 @@
+#the following is required for the unit testing
+mock==1.0.1
\ No newline at end of file
diff --git a/hbp_nrp_virtual_coach/setup.py b/hbp_nrp_virtual_coach/setup.py
index 1bab640..0aaba71 100644
--- a/hbp_nrp_virtual_coach/setup.py
+++ b/hbp_nrp_virtual_coach/setup.py
@@ -1,31 +1,30 @@
 '''setup.py'''
 
-# pylint: disable=F0401,E0611,W0142
+# pylint: disable=F0401,E0611,W0622,E0012,W0142,W0402
 
+from builtins import str
 try:
     from setuptools import setup
 except ImportError:
     from distutils.core import setup
 
-import hbp_nrp_virtual_coach
+from optparse import Option  # pylint:disable=deprecated-module
 import pip
+import hbp_nrp_virtual_coach
 
-from optparse import Option
 options = Option('--workaround')
 options.skip_requirements_regex = None
 reqs_file = './requirements.txt'
+
+pip_version_major = int(pip.__version__.split('.')[0])
 # Hack for old pip versions
-if pip.__version__.startswith('10.'):
-    # Versions greater or equal to 10.x don't rely on pip.req.parse_requirements
-    install_reqs = list(val.strip() for val in open(reqs_file))
-    reqs = install_reqs
-elif pip.__version__.startswith('1.'):
+if pip_version_major == 1:
     # Versions 1.x rely on pip.req.parse_requirements
     # but don't require a "session" parameter
     from pip.req import parse_requirements # pylint:disable=no-name-in-module, import-error
     install_reqs = parse_requirements(reqs_file, options=options)
     reqs = [str(ir.req) for ir in install_reqs]
-else:
+elif 10 > pip_version_major > 1:
     # Versions greater than 1.x but smaller than 10.x rely on pip.req.parse_requirements
     # and requires a "session" parameter
     from pip.req import parse_requirements # pylint:disable=no-name-in-module, import-error
@@ -37,7 +36,10 @@ else:
         options=options
     )
     reqs = [str(ir.req) for ir in install_reqs]
-
+elif pip_version_major >= 10:
+    # Versions greater or equal to 10.x don't rely on pip.req.parse_requirements
+    install_reqs = list(val.strip() for val in open(reqs_file))
+    reqs = install_reqs
 config = {
     'description': 'Virtual Coach command-line Neurorobotics Platform interface for HBP SP10',
     'author': 'HBP Neurorobotics',
@@ -49,6 +51,8 @@ config = {
     'package_data': {
         'hbp_nrp_virtual_coach': ['config.json']
     },
+    'classifiers': ['Programming Language :: Python :: 3',
+                    "Programming Language :: Python :: 2.7"],
     'scripts': [],
     'name': 'hbp-nrp-virtual-coach',
     'include_package_data': True,
-- 
GitLab