From b2ee5157a66d6ee02d3082b8353547ee46d278c6 Mon Sep 17 00:00:00 2001 From: Mayeul Rousselet Date: Wed, 9 Oct 2019 10:35:02 +0200 Subject: [PATCH 1/7] Implemented webapp API client --- dataikuapi/dss/project.py | 26 +++++++++++++++ dataikuapi/dss/webapp.py | 67 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 dataikuapi/dss/webapp.py diff --git a/dataikuapi/dss/project.py b/dataikuapi/dss/project.py index fbd29a9f3..e8d50d7eb 100644 --- a/dataikuapi/dss/project.py +++ b/dataikuapi/dss/project.py @@ -15,6 +15,7 @@ from .discussion import DSSObjectDiscussions from .ml import DSSMLTask from .analysis import DSSAnalysis +from .webapp import DSSWebApp, DSSWebAppHead from dataikuapi.utils import DataikuException @@ -823,6 +824,31 @@ def get_macro(self, runnable_type): """ return DSSMacro(self.client, self.project_key, runnable_type) + ######################################################## + # Webapps + ######################################################## + + def list_webapps(self): + """ + List the webapps heads of this project + + :returns: the list of the webapps as :class:`dataikuapi.dss.webapp.DSSWebAppHead` + """ + webapps = self.client._perform_json( + "GET", "/projects/%s/webapps/" % self.project_key) + return [DSSWebAppHead(self.client, self.project_key, w["id"], w) for w in webapps] + + def get_webapp(self, webapp_id): + """ + Get a handle to interact with a specific webapp + + :param webapp_id: the identifier of a webapp + :returns: A :class:`dataikuapi.dss.macro.DSSWebApp` webapp handle + """ + definition = self.client._perform_json( + "GET", "/projects/%s/webapps/%s" % (self.project_key, webapp_id)) + return DSSWebApp(self.client, self.project_key, webapp_id, definition) + ######################################################## # Wiki ######################################################## diff --git a/dataikuapi/dss/webapp.py b/dataikuapi/dss/webapp.py new file mode 100644 index 000000000..632864ce9 --- /dev/null +++ b/dataikuapi/dss/webapp.py @@ -0,0 +1,67 @@ +import sys + +if sys.version_info >= (3,0): + import urllib.parse + dku_quote_fn = urllib.parse.quote +else: + import urllib + dku_quote_fn = urllib.quote + +class DSSWebApp(object): + """ + A handle to manage a webapp + """ + def __init__(self, client, project_key, webapp_id, definition): + """Do not call directly, use :meth:`dataikuapi.dss.project.DSSProject.get_webapps`""" + self.client = client + self.project_key = project_key + self.webapp_id = webapp_id + self.definition = definition + + """ + Update an existing webapp + + :returns: a webapp state excerpt + :rtype: :class:`dataikuapi.dss.webapp.DSSWebAppSaveResponse` + """ + def update(self): + response = self.client._perform_json("PUT", "/projects/%s/webapps/%s" % (self.project_key, self.webapp_id), body=self.definition) + return DSSWebAppSaveResponse(self.client, self.project_key, self.webapp_id, response) + + """ + Stop a webapp + """ + def stop_backend(self): + self.client._perform_json("PUT", "/projects/%s/webapps/%s/stop-backend" % (self.project_key, self.webapp_id)) + return + + """ + Restart a webapp + """ + def restart_backend(self): + self.client._perform_json("PUT", "/projects/%s/webapps/%s/restart-backend" % (self.project_key, self.webapp_id)) + return + + +class DSSWebAppSaveResponse(object): + """ + Response for the update method on a WebApp + """ + def __init__(self, client, project_key, webapp_id, definition): + """Do not call directly, use :meth:`dataikuapi.dss.webapp.DSSWebApp.update`""" + self.client = client + self.project_key = project_key + self.webapp_id = webapp_id + self.definition = definition + + +class DSSWebAppHead(object): + """ + A handle to manage a webapp head + """ + def __init__(self, client, project_key, webapp_id, definition): + """Do not call directly, use :meth:`dataikuapi.dss.project.DSSProject.get_webapps`""" + self.client = client + self.project_key = project_key + self.webapp_id = webapp_id + self.definition = definition From 152347cdc0d398fb2f6dc8533f7b72f4da642b31 Mon Sep 17 00:00:00 2001 From: Mayeul Rousselet Date: Wed, 9 Oct 2019 12:08:51 +0200 Subject: [PATCH 2/7] Unit test for webapp api written --- dataikuapi/dss/webapp.py | 6 ++-- tests/user_tests.py | 12 ++++---- tests/webapps_tests.py | 63 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 9 deletions(-) create mode 100644 tests/webapps_tests.py diff --git a/dataikuapi/dss/webapp.py b/dataikuapi/dss/webapp.py index 632864ce9..a05565122 100644 --- a/dataikuapi/dss/webapp.py +++ b/dataikuapi/dss/webapp.py @@ -32,14 +32,14 @@ def update(self): Stop a webapp """ def stop_backend(self): - self.client._perform_json("PUT", "/projects/%s/webapps/%s/stop-backend" % (self.project_key, self.webapp_id)) + self.client._perform_empty("PUT", "/projects/%s/webapps/%s/stop-backend" % (self.project_key, self.webapp_id)) return """ Restart a webapp """ def restart_backend(self): - self.client._perform_json("PUT", "/projects/%s/webapps/%s/restart-backend" % (self.project_key, self.webapp_id)) + self.client._perform_empty("PUT", "/projects/%s/webapps/%s/restart-backend" % (self.project_key, self.webapp_id)) return @@ -57,7 +57,7 @@ def __init__(self, client, project_key, webapp_id, definition): class DSSWebAppHead(object): """ - A handle to manage a webapp head + A handle to manage a WebApp head """ def __init__(self, client, project_key, webapp_id, definition): """Do not call directly, use :meth:`dataikuapi.dss.project.DSSProject.get_webapps`""" diff --git a/tests/user_tests.py b/tests/user_tests.py index 6bab7260e..19184cf29 100644 --- a/tests/user_tests.py +++ b/tests/user_tests.py @@ -13,23 +13,23 @@ def list_users_test(): def create_delete_user_test(): client = DSSClient(host, apiKey) - count = len(client.list_users()) - + count = len(client.list_users()) + user = client.create_user("toto", "password", "display name of toto", groups=['a','b']) eq_(count + 1, len(client.list_users())) - + user.delete() eq_(count, len(client.list_users())) - + def get_set_user_test(): client = DSSClient(host, apiKey) user = client.create_user("toto", "password", "display name of toto", groups=['a','b']) - + desc = user.get_definition() desc['displayName'] = 'tata' user.set_definition(desc) desc2 = user.get_definition() - + eq_('tata', desc2['displayName']) user.delete() diff --git a/tests/webapps_tests.py b/tests/webapps_tests.py new file mode 100644 index 000000000..4cdae680c --- /dev/null +++ b/tests/webapps_tests.py @@ -0,0 +1,63 @@ +from time import sleep +from dataikuapi.dssclient import DSSClient +from dataikuapi.dss.project import DSSProject +from nose.tools import ok_ +from nose.tools import eq_ + +host="http://localhost:8083" +apiKey="CMZBjFkUgcDh08S3awoPyVIweBelxPjy" +testProjectKey="WEBAPPS" +testWebAppPythonId="VCMN2ra" + +def remove_key(d, key): + r = dict(d) + del r[key] + return r + +class webapp_api_tests(object): + + def setUp(self): + self.client = DSSClient(host, apiKey) + self.project = DSSProject(self.client, testProjectKey) + + def list_webapps_test(self): + webapps = self.project.list_webapps(); + ok_(len(webapps) > 0) + + + def get_python_webapp_test(self): + webapp = self.project.get_webapp(testWebAppPythonId) + ok_(webapp is not None) + + def update_python_webapp_test(self): + webapp_pre = self.project.get_webapp(testWebAppPythonId) + webapp_pre.update() + webapp_post = self.project.get_webapp(testWebAppPythonId) + eq_(webapp_pre.project_key, webapp_post.project_key) + eq_(webapp_pre.webapp_id, webapp_post.webapp_id) + eq_(remove_key(webapp_pre.definition, "versionTag"), remove_key(webapp_post.definition, "versionTag")) + eq_(webapp_pre.definition["versionTag"]["versionNumber"]+1,webapp_post.definition["versionTag"]["versionNumber"]) + + def restart_backend_test(self): + """ + WARNING: you should manually stop the backend before this test + """ + filtered_webapps = [w for w in self.project.list_webapps() if w.webapp_id == testWebAppPythonId] + webapp = self.project.get_webapp(testWebAppPythonId) + ok_(not filtered_webapps[0].definition["backendRunning"],"The backend should be stopped before the test") + webapp.restart_backend() + sleep(2) + filtered_webapps = [w for w in self.project.list_webapps() if w.webapp_id == testWebAppPythonId] + ok_(filtered_webapps[0].definition["backendRunning"]) + + def stop_backend_test(self): + """ + WARNING: you should manually start the backend before this test + """ + filtered_webapps = [w for w in self.project.list_webapps() if w.webapp_id == testWebAppPythonId] + webapp = self.project.get_webapp(testWebAppPythonId) + ok_(filtered_webapps[0].definition["backendRunning"],"The backend should be started before the test") + webapp.stop_backend() + sleep(2) + filtered_webapps = [w for w in self.project.list_webapps() if w.webapp_id == testWebAppPythonId] + ok_(not filtered_webapps[0].definition["backendRunning"]) \ No newline at end of file From 1105823f63cba3c7e7730da67651ee809b9937cf Mon Sep 17 00:00:00 2001 From: Mayeul Rousselet Date: Wed, 9 Oct 2019 18:46:08 +0200 Subject: [PATCH 3/7] Correction to take Sam P remarks into account --- dataikuapi/dss/project.py | 15 +++---- dataikuapi/dss/webapp.py | 83 +++++++++++++++++++++++++-------------- tests/webapps_tests.py | 47 +++++++++++++--------- 3 files changed, 88 insertions(+), 57 deletions(-) diff --git a/dataikuapi/dss/project.py b/dataikuapi/dss/project.py index e8d50d7eb..5d3ce22f9 100644 --- a/dataikuapi/dss/project.py +++ b/dataikuapi/dss/project.py @@ -15,7 +15,7 @@ from .discussion import DSSObjectDiscussions from .ml import DSSMLTask from .analysis import DSSAnalysis -from .webapp import DSSWebApp, DSSWebAppHead +from .webapp import DSSWebApp from dataikuapi.utils import DataikuException @@ -832,22 +832,19 @@ def list_webapps(self): """ List the webapps heads of this project - :returns: the list of the webapps as :class:`dataikuapi.dss.webapp.DSSWebAppHead` + :returns: the list of the webapps as :class:`dataikuapi.dss.webapp.DSSWebApp` """ - webapps = self.client._perform_json( - "GET", "/projects/%s/webapps/" % self.project_key) - return [DSSWebAppHead(self.client, self.project_key, w["id"], w) for w in webapps] + webapps = self.client._perform_json("GET", "/projects/%s/webapps/" % self.project_key) + return [DSSWebApp(self.client, self.project_key, w["id"], w) for w in webapps] def get_webapp(self, webapp_id): """ Get a handle to interact with a specific webapp :param webapp_id: the identifier of a webapp - :returns: A :class:`dataikuapi.dss.macro.DSSWebApp` webapp handle + :returns: A :class:`dataikuapi.dss.webapp.DSSWebApp` webapp handle """ - definition = self.client._perform_json( - "GET", "/projects/%s/webapps/%s" % (self.project_key, webapp_id)) - return DSSWebApp(self.client, self.project_key, webapp_id, definition) + return DSSWebApp(self.client, self.project_key, webapp_id) ######################################################## # Wiki diff --git a/dataikuapi/dss/webapp.py b/dataikuapi/dss/webapp.py index a05565122..83bfcfb0e 100644 --- a/dataikuapi/dss/webapp.py +++ b/dataikuapi/dss/webapp.py @@ -1,4 +1,5 @@ import sys +from .future import DSSFuture if sys.version_info >= (3,0): import urllib.parse @@ -7,61 +8,83 @@ import urllib dku_quote_fn = urllib.quote + class DSSWebApp(object): """ A handle to manage a webapp """ - def __init__(self, client, project_key, webapp_id, definition): + def __init__(self, client, project_key, webapp_id, state=None): """Do not call directly, use :meth:`dataikuapi.dss.project.DSSProject.get_webapps`""" self.client = client self.project_key = project_key self.webapp_id = webapp_id - self.definition = definition + self.state = state - """ - Update an existing webapp + def get_state(self): + """ + Return the state of the webapp - :returns: a webapp state excerpt - :rtype: :class:`dataikuapi.dss.webapp.DSSWebAppSaveResponse` - """ - def update(self): - response = self.client._perform_json("PUT", "/projects/%s/webapps/%s" % (self.project_key, self.webapp_id), body=self.definition) - return DSSWebAppSaveResponse(self.client, self.project_key, self.webapp_id, response) + :return: the state of the webapp + """ + return self.state - """ - Stop a webapp - """ def stop_backend(self): + """ + Stop a webapp + """ self.client._perform_empty("PUT", "/projects/%s/webapps/%s/stop-backend" % (self.project_key, self.webapp_id)) return - """ - Restart a webapp - """ def restart_backend(self): - self.client._perform_empty("PUT", "/projects/%s/webapps/%s/restart-backend" % (self.project_key, self.webapp_id)) - return + """ + Restart a webapp + """ + future = self.client._perform_json("PUT", "/projects/%s/webapps/%s/restart-backend" % (self.project_key, self.webapp_id)) + return DSSFuture(self.client, future["jobId"]) + + def get_definition(self): + """ + Get a webapp definition + :returns: a handle to manage the webapp definition + :rtype: :class:`dataikuapi.dss.webapp.DSSWebAppDefinition` + """ + definition = self.client._perform_json("GET", "/projects/%s/webapps/%s" % (self.project_key, self.webapp_id)) + return DSSWebAppDefinition(self.client, self.project_key, self.webapp_id, definition) -class DSSWebAppSaveResponse(object): +class DSSWebAppDefinition(object): """ - Response for the update method on a WebApp + A handle to manage a WebApp definition """ def __init__(self, client, project_key, webapp_id, definition): - """Do not call directly, use :meth:`dataikuapi.dss.webapp.DSSWebApp.update`""" + """Do not call directly, use :meth:`dataikuapi.dss.webapp.DSSWebApp.get_definition`""" self.client = client self.project_key = project_key self.webapp_id = webapp_id self.definition = definition + def get_definition(self): + """ + Get the definition -class DSSWebAppHead(object): - """ - A handle to manage a WebApp head - """ - def __init__(self, client, project_key, webapp_id, definition): - """Do not call directly, use :meth:`dataikuapi.dss.project.DSSProject.get_webapps`""" - self.client = client - self.project_key = project_key - self.webapp_id = webapp_id + + :returns: the definition of the webapp + """ + return self.definition + + def set_definition(self, definition): + """ + Set the definition + + :param definition : the definition of the webapp + """ self.definition = definition + + def save(self): + """ + Save the current webapp definition and update + :returns: a wrapper to a future + """ + future = self.client._perform_json("PUT", "/projects/%s/webapps/%s" % (self.project_key, self.webapp_id), body=self.definition) + self.definition = self.client._perform_json("GET", "/projects/%s/webapps/%s" % (self.project_key, self.webapp_id)) + return future diff --git a/tests/webapps_tests.py b/tests/webapps_tests.py index 4cdae680c..30cb18557 100644 --- a/tests/webapps_tests.py +++ b/tests/webapps_tests.py @@ -1,6 +1,7 @@ from time import sleep from dataikuapi.dssclient import DSSClient from dataikuapi.dss.project import DSSProject +from dataikuapi.dss.webapp import DSSWebApp from nose.tools import ok_ from nose.tools import eq_ @@ -9,12 +10,18 @@ testProjectKey="WEBAPPS" testWebAppPythonId="VCMN2ra" + def remove_key(d, key): r = dict(d) del r[key] return r -class webapp_api_tests(object): + +class WebappApi_tests(object): + + def __init__(self): + self.client = None + self.project = None; def setUp(self): self.client = DSSClient(host, apiKey) @@ -24,40 +31,44 @@ def list_webapps_test(self): webapps = self.project.list_webapps(); ok_(len(webapps) > 0) - def get_python_webapp_test(self): webapp = self.project.get_webapp(testWebAppPythonId) ok_(webapp is not None) + def get_definition_test(self): + webapp = self.project.get_webapp(testWebAppPythonId) + definition = webapp.get_definition() + ok_(definition is not None) + eq_(definition.webapp_id, testWebAppPythonId) + eq_(definition.get_definition()["id"], testWebAppPythonId) + def update_python_webapp_test(self): - webapp_pre = self.project.get_webapp(testWebAppPythonId) - webapp_pre.update() - webapp_post = self.project.get_webapp(testWebAppPythonId) - eq_(webapp_pre.project_key, webapp_post.project_key) - eq_(webapp_pre.webapp_id, webapp_post.webapp_id) - eq_(remove_key(webapp_pre.definition, "versionTag"), remove_key(webapp_post.definition, "versionTag")) - eq_(webapp_pre.definition["versionTag"]["versionNumber"]+1,webapp_post.definition["versionTag"]["versionNumber"]) + webapp = self.project.get_webapp(testWebAppPythonId) + definition = webapp.get_definition() + old_def = dict(definition.get_definition()) + future = definition.save() + ok_(future is not None) + eq_(remove_key(definition.get_definition(), "versionTag"), remove_key(old_def, "versionTag")) + eq_(definition.get_definition()["versionTag"]["versionNumber"], old_def["versionTag"]["versionNumber"] + 1) def restart_backend_test(self): """ WARNING: you should manually stop the backend before this test """ filtered_webapps = [w for w in self.project.list_webapps() if w.webapp_id == testWebAppPythonId] - webapp = self.project.get_webapp(testWebAppPythonId) - ok_(not filtered_webapps[0].definition["backendRunning"],"The backend should be stopped before the test") - webapp.restart_backend() - sleep(2) + ok_(not filtered_webapps[0].get_state()["backendRunning"], "The backend should be stopped before the test") + future = filtered_webapps[0].restart_backend() + future.wait_for_result() filtered_webapps = [w for w in self.project.list_webapps() if w.webapp_id == testWebAppPythonId] - ok_(filtered_webapps[0].definition["backendRunning"]) + ok_(filtered_webapps[0].get_state()["backendRunning"]) def stop_backend_test(self): """ WARNING: you should manually start the backend before this test """ filtered_webapps = [w for w in self.project.list_webapps() if w.webapp_id == testWebAppPythonId] - webapp = self.project.get_webapp(testWebAppPythonId) - ok_(filtered_webapps[0].definition["backendRunning"],"The backend should be started before the test") - webapp.stop_backend() + ok_(filtered_webapps[0].get_state()["backendRunning"],"The backend should be started before the test") + filtered_webapps[0].stop_backend() sleep(2) filtered_webapps = [w for w in self.project.list_webapps() if w.webapp_id == testWebAppPythonId] - ok_(not filtered_webapps[0].definition["backendRunning"]) \ No newline at end of file + ok_(not filtered_webapps[0].get_state()["backendRunning"]) \ No newline at end of file From 455cdf997684d5136ead574dff57e48bb05aefc5 Mon Sep 17 00:00:00 2001 From: Mayeul Rousselet Date: Thu, 10 Oct 2019 11:07:23 +0200 Subject: [PATCH 4/7] Implemented state consistency on DSSWebApp --- dataikuapi/dss/webapp.py | 8 ++++++++ tests/webapps_tests.py | 20 +++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/dataikuapi/dss/webapp.py b/dataikuapi/dss/webapp.py index 83bfcfb0e..4ab5c9f81 100644 --- a/dataikuapi/dss/webapp.py +++ b/dataikuapi/dss/webapp.py @@ -26,6 +26,12 @@ def get_state(self): :return: the state of the webapp """ + if self.state is None: + webapps = self.client._perform_json("GET", "/projects/%s/webapps/" % self.project_key) + + filtered_webapps = [w for w in webapps if w["id"] == self.webapp_id] + if len(filtered_webapps) > 0: + self.state = filtered_webapps[0] return self.state def stop_backend(self): @@ -33,6 +39,7 @@ def stop_backend(self): Stop a webapp """ self.client._perform_empty("PUT", "/projects/%s/webapps/%s/stop-backend" % (self.project_key, self.webapp_id)) + self.state = None return def restart_backend(self): @@ -40,6 +47,7 @@ def restart_backend(self): Restart a webapp """ future = self.client._perform_json("PUT", "/projects/%s/webapps/%s/restart-backend" % (self.project_key, self.webapp_id)) + self.state = None return DSSFuture(self.client, future["jobId"]) def get_definition(self): diff --git a/tests/webapps_tests.py b/tests/webapps_tests.py index 30cb18557..05f1667ff 100644 --- a/tests/webapps_tests.py +++ b/tests/webapps_tests.py @@ -71,4 +71,22 @@ def stop_backend_test(self): filtered_webapps[0].stop_backend() sleep(2) filtered_webapps = [w for w in self.project.list_webapps() if w.webapp_id == testWebAppPythonId] - ok_(not filtered_webapps[0].get_state()["backendRunning"]) \ No newline at end of file + ok_(not filtered_webapps[0].get_state()["backendRunning"]) + + def get_state_test(self): + webapp = self.project.get_webapp(testWebAppPythonId) + filtered_webapps = [w for w in self.project.list_webapps() if w.webapp_id == testWebAppPythonId] + ok_(webapp.state is None) + ok_(webapp.get_state() is not None) + eq_(webapp.get_state(), filtered_webapps[0].state) + + def zz_state_consistency_test(self): + filtered_webapps = [w for w in self.project.list_webapps() if w.webapp_id == testWebAppPythonId] + webapp = filtered_webapps[0] + webapp.stop_backend() + eq_(webapp.get_state()["backendRunning"], False) + future = webapp.restart_backend() + future.wait_for_result() + eq_(webapp.get_state()["backendRunning"], True) + webapp.stop_backend() + eq_(webapp.get_state()["backendRunning"], False) \ No newline at end of file From 54ef4cf09ef836ab99ea84b2e52448a331a7f9f4 Mon Sep 17 00:00:00 2001 From: Mayeul Rousselet Date: Thu, 10 Oct 2019 11:21:58 +0200 Subject: [PATCH 5/7] DSSWebApp.save now returns a DSSFuture instance --- dataikuapi/dss/webapp.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dataikuapi/dss/webapp.py b/dataikuapi/dss/webapp.py index 4ab5c9f81..3d58fb2d4 100644 --- a/dataikuapi/dss/webapp.py +++ b/dataikuapi/dss/webapp.py @@ -92,7 +92,8 @@ def save(self): """ Save the current webapp definition and update :returns: a wrapper to a future + :rtype: :class:`dataikuapi.dss.future.DSSFuture` """ future = self.client._perform_json("PUT", "/projects/%s/webapps/%s" % (self.project_key, self.webapp_id), body=self.definition) self.definition = self.client._perform_json("GET", "/projects/%s/webapps/%s" % (self.project_key, self.webapp_id)) - return future + return DSSFuture(self.client, future["backendState"]["jobId"]) From 28bf8d7c941859cd32416af80ce9bee99d2ccf6d Mon Sep 17 00:00:00 2001 From: Mayeul Rousselet Date: Thu, 10 Oct 2019 15:23:16 +0200 Subject: [PATCH 6/7] Add call to webapp backend state --- dataikuapi/dss/project.py | 2 +- dataikuapi/dss/webapp.py | 59 +++++++++++++++++++++++++++++---------- tests/webapps_tests.py | 49 +++++++++++++------------------- 3 files changed, 64 insertions(+), 46 deletions(-) diff --git a/dataikuapi/dss/project.py b/dataikuapi/dss/project.py index 5d3ce22f9..354e2fe72 100644 --- a/dataikuapi/dss/project.py +++ b/dataikuapi/dss/project.py @@ -835,7 +835,7 @@ def list_webapps(self): :returns: the list of the webapps as :class:`dataikuapi.dss.webapp.DSSWebApp` """ webapps = self.client._perform_json("GET", "/projects/%s/webapps/" % self.project_key) - return [DSSWebApp(self.client, self.project_key, w["id"], w) for w in webapps] + return [DSSWebApp(self.client, self.project_key, w["id"]) for w in webapps] def get_webapp(self, webapp_id): """ diff --git a/dataikuapi/dss/webapp.py b/dataikuapi/dss/webapp.py index 3d58fb2d4..58017e26a 100644 --- a/dataikuapi/dss/webapp.py +++ b/dataikuapi/dss/webapp.py @@ -13,25 +13,23 @@ class DSSWebApp(object): """ A handle to manage a webapp """ - def __init__(self, client, project_key, webapp_id, state=None): + def __init__(self, client, project_key, webapp_id): """Do not call directly, use :meth:`dataikuapi.dss.project.DSSProject.get_webapps`""" self.client = client self.project_key = project_key self.webapp_id = webapp_id - self.state = state + self.state = None def get_state(self): """ Return the state of the webapp :return: the state of the webapp + :rtype: :class:`DSSWebAppBackendState` """ if self.state is None: - webapps = self.client._perform_json("GET", "/projects/%s/webapps/" % self.project_key) - - filtered_webapps = [w for w in webapps if w["id"] == self.webapp_id] - if len(filtered_webapps) > 0: - self.state = filtered_webapps[0] + state_definition = self.client._perform_json("GET", "/projects/%s/webapps/%s/backend" % (self.project_key, self.webapp_id)) + self.state = DSSWebAppBackendState(self.client, self.project_key, self.webapp_id, state_definition) return self.state def stop_backend(self): @@ -40,7 +38,6 @@ def stop_backend(self): """ self.client._perform_empty("PUT", "/projects/%s/webapps/%s/stop-backend" % (self.project_key, self.webapp_id)) self.state = None - return def restart_backend(self): """ @@ -53,19 +50,52 @@ def restart_backend(self): def get_definition(self): """ Get a webapp definition + :returns: a handle to manage the webapp definition :rtype: :class:`dataikuapi.dss.webapp.DSSWebAppDefinition` """ definition = self.client._perform_json("GET", "/projects/%s/webapps/%s" % (self.project_key, self.webapp_id)) - return DSSWebAppDefinition(self.client, self.project_key, self.webapp_id, definition) + return DSSWebAppDefinition(self, self.client, self.project_key, self.webapp_id, definition) + + +class DSSWebAppBackendState(object): + """ + A handle to manage WebApp backend state + """ + def __init__(self, client, project_key, webapp_id, definition): + """Do not call directly, use :meth:`dataikuapi.dss.webapp.DSSWebApp.get_state`""" + self.client = client + self.project_key = project_key + self.webapp_id = webapp_id + self.definition = definition + + def get_definition(self): + """ + Returns the dict containing the current state of the webapp backend. + Warning : this dict is replaced when webapp backend state changes + + :returns: a dict + """ + return self.definition + + def is_running(self): + """ + Tells if the webapp app backend is running or not + + :returns: a bool + """ + return "futureInfo" in self.definition and \ + "alive" in self.definition["futureInfo"] and \ + self.definition["futureInfo"]["alive"] class DSSWebAppDefinition(object): """ A handle to manage a WebApp definition """ - def __init__(self, client, project_key, webapp_id, definition): + def __init__(self, webapp, client, project_key, webapp_id, definition): """Do not call directly, use :meth:`dataikuapi.dss.webapp.DSSWebApp.get_definition`""" + self.webapp = webapp self.client = client self.project_key = project_key self.webapp_id = webapp_id @@ -75,7 +105,6 @@ def get_definition(self): """ Get the definition - :returns: the definition of the webapp """ return self.definition @@ -90,10 +119,10 @@ def set_definition(self, definition): def save(self): """ - Save the current webapp definition and update - :returns: a wrapper to a future + Save the current webapp definition and update it. + :rtype: :class:`dataikuapi.dss.future.DSSFuture` """ - future = self.client._perform_json("PUT", "/projects/%s/webapps/%s" % (self.project_key, self.webapp_id), body=self.definition) + self.client._perform_json("PUT", "/projects/%s/webapps/%s" % (self.project_key, self.webapp_id), body=self.definition) self.definition = self.client._perform_json("GET", "/projects/%s/webapps/%s" % (self.project_key, self.webapp_id)) - return DSSFuture(self.client, future["backendState"]["jobId"]) + self.webapp.state = None diff --git a/tests/webapps_tests.py b/tests/webapps_tests.py index 05f1667ff..fe41b9ffa 100644 --- a/tests/webapps_tests.py +++ b/tests/webapps_tests.py @@ -27,66 +27,55 @@ def setUp(self): self.client = DSSClient(host, apiKey) self.project = DSSProject(self.client, testProjectKey) - def list_webapps_test(self): + def t01_list_webapps_test(self): webapps = self.project.list_webapps(); ok_(len(webapps) > 0) - def get_python_webapp_test(self): + def t02_get_python_webapp_test(self): webapp = self.project.get_webapp(testWebAppPythonId) ok_(webapp is not None) - def get_definition_test(self): + def t03_get_definition_test(self): webapp = self.project.get_webapp(testWebAppPythonId) definition = webapp.get_definition() ok_(definition is not None) eq_(definition.webapp_id, testWebAppPythonId) eq_(definition.get_definition()["id"], testWebAppPythonId) - def update_python_webapp_test(self): + def t04_update_python_webapp_test(self): webapp = self.project.get_webapp(testWebAppPythonId) definition = webapp.get_definition() old_def = dict(definition.get_definition()) - future = definition.save() - ok_(future is not None) + definition.save() eq_(remove_key(definition.get_definition(), "versionTag"), remove_key(old_def, "versionTag")) eq_(definition.get_definition()["versionTag"]["versionNumber"], old_def["versionTag"]["versionNumber"] + 1) - def restart_backend_test(self): + def t05_restart_backend_test(self): """ WARNING: you should manually stop the backend before this test """ - filtered_webapps = [w for w in self.project.list_webapps() if w.webapp_id == testWebAppPythonId] - ok_(not filtered_webapps[0].get_state()["backendRunning"], "The backend should be stopped before the test") - future = filtered_webapps[0].restart_backend() + webapp = self.project.get_webapp(testWebAppPythonId) + ok_(not webapp.get_state().is_running(), "The backend should be stopped before the test") + future = webapp.restart_backend() future.wait_for_result() - filtered_webapps = [w for w in self.project.list_webapps() if w.webapp_id == testWebAppPythonId] - ok_(filtered_webapps[0].get_state()["backendRunning"]) + ok_(webapp.get_state().is_running()) - def stop_backend_test(self): + def t06_stop_backend_test(self): """ WARNING: you should manually start the backend before this test """ - filtered_webapps = [w for w in self.project.list_webapps() if w.webapp_id == testWebAppPythonId] - ok_(filtered_webapps[0].get_state()["backendRunning"],"The backend should be started before the test") - filtered_webapps[0].stop_backend() + webapp = self.project.get_webapp(testWebAppPythonId) + ok_(webapp.get_state().is_running(),"The backend should be started before the test") + webapp.stop_backend() sleep(2) - filtered_webapps = [w for w in self.project.list_webapps() if w.webapp_id == testWebAppPythonId] - ok_(not filtered_webapps[0].get_state()["backendRunning"]) + eq_(webapp.get_state().is_running(), False) - def get_state_test(self): + def t07_state_consistency_test(self): webapp = self.project.get_webapp(testWebAppPythonId) - filtered_webapps = [w for w in self.project.list_webapps() if w.webapp_id == testWebAppPythonId] - ok_(webapp.state is None) - ok_(webapp.get_state() is not None) - eq_(webapp.get_state(), filtered_webapps[0].state) - - def zz_state_consistency_test(self): - filtered_webapps = [w for w in self.project.list_webapps() if w.webapp_id == testWebAppPythonId] - webapp = filtered_webapps[0] webapp.stop_backend() - eq_(webapp.get_state()["backendRunning"], False) + eq_(webapp.get_state().is_running(), False) future = webapp.restart_backend() future.wait_for_result() - eq_(webapp.get_state()["backendRunning"], True) + eq_(webapp.get_state().is_running(), True) webapp.stop_backend() - eq_(webapp.get_state()["backendRunning"], False) \ No newline at end of file + eq_(webapp.get_state().is_running(), False) \ No newline at end of file From 53cd5bee7d3f9207c99a09caaa76e4eec4d85edd Mon Sep 17 00:00:00 2001 From: Mayeul Rousselet Date: Mon, 14 Oct 2019 11:12:54 +0200 Subject: [PATCH 7/7] Removal of state cache as it could change externally and smalls improvments --- dataikuapi/dss/webapp.py | 39 ++++++++++++++++----------------------- tests/webapps_tests.py | 1 + 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/dataikuapi/dss/webapp.py b/dataikuapi/dss/webapp.py index 58017e26a..07130c0ca 100644 --- a/dataikuapi/dss/webapp.py +++ b/dataikuapi/dss/webapp.py @@ -18,7 +18,6 @@ def __init__(self, client, project_key, webapp_id): self.client = client self.project_key = project_key self.webapp_id = webapp_id - self.state = None def get_state(self): """ @@ -27,24 +26,22 @@ def get_state(self): :return: the state of the webapp :rtype: :class:`DSSWebAppBackendState` """ - if self.state is None: - state_definition = self.client._perform_json("GET", "/projects/%s/webapps/%s/backend" % (self.project_key, self.webapp_id)) - self.state = DSSWebAppBackendState(self.client, self.project_key, self.webapp_id, state_definition) - return self.state + state = self.client._perform_json("GET", "/projects/%s/webapps/%s/backend/state" % (self.project_key, self.webapp_id)) + return DSSWebAppBackendState(self.client, self.project_key, self.webapp_id, state) def stop_backend(self): """ Stop a webapp """ - self.client._perform_empty("PUT", "/projects/%s/webapps/%s/stop-backend" % (self.project_key, self.webapp_id)) - self.state = None + self.client._perform_empty("PUT", "/projects/%s/webapps/%s/backend/actions/stop" % (self.project_key, self.webapp_id)) def restart_backend(self): """ Restart a webapp + :returns: a handle to a DSS future to track the progress of the restart + :rtype: :class:`dataikuapi.dss.future.DSSFuture` """ - future = self.client._perform_json("PUT", "/projects/%s/webapps/%s/restart-backend" % (self.project_key, self.webapp_id)) - self.state = None + future = self.client._perform_json("PUT", "/projects/%s/webapps/%s/backend/actions/restart" % (self.project_key, self.webapp_id)) return DSSFuture(self.client, future["jobId"]) def get_definition(self): @@ -54,29 +51,29 @@ def get_definition(self): :returns: a handle to manage the webapp definition :rtype: :class:`dataikuapi.dss.webapp.DSSWebAppDefinition` """ - definition = self.client._perform_json("GET", "/projects/%s/webapps/%s" % (self.project_key, self.webapp_id)) - return DSSWebAppDefinition(self, self.client, self.project_key, self.webapp_id, definition) + definition = self.client._perform_json("GET", "/projects/%s/webapps/%s/" % (self.project_key, self.webapp_id)) + return DSSWebAppDefinition(self.client, self.project_key, self.webapp_id, definition) class DSSWebAppBackendState(object): """ A handle to manage WebApp backend state """ - def __init__(self, client, project_key, webapp_id, definition): + def __init__(self, client, project_key, webapp_id, state): """Do not call directly, use :meth:`dataikuapi.dss.webapp.DSSWebApp.get_state`""" self.client = client self.project_key = project_key self.webapp_id = webapp_id - self.definition = definition + self.state = state - def get_definition(self): + def get_state(self): """ Returns the dict containing the current state of the webapp backend. Warning : this dict is replaced when webapp backend state changes :returns: a dict """ - return self.definition + return self.state def is_running(self): """ @@ -84,18 +81,17 @@ def is_running(self): :returns: a bool """ - return "futureInfo" in self.definition and \ - "alive" in self.definition["futureInfo"] and \ - self.definition["futureInfo"]["alive"] + return "futureInfo" in self.state and \ + "alive" in self.state["futureInfo"] and \ + self.state["futureInfo"]["alive"] class DSSWebAppDefinition(object): """ A handle to manage a WebApp definition """ - def __init__(self, webapp, client, project_key, webapp_id, definition): + def __init__(self, client, project_key, webapp_id, definition): """Do not call directly, use :meth:`dataikuapi.dss.webapp.DSSWebApp.get_definition`""" - self.webapp = webapp self.client = client self.project_key = project_key self.webapp_id = webapp_id @@ -120,9 +116,6 @@ def set_definition(self, definition): def save(self): """ Save the current webapp definition and update it. - - :rtype: :class:`dataikuapi.dss.future.DSSFuture` """ self.client._perform_json("PUT", "/projects/%s/webapps/%s" % (self.project_key, self.webapp_id), body=self.definition) self.definition = self.client._perform_json("GET", "/projects/%s/webapps/%s" % (self.project_key, self.webapp_id)) - self.webapp.state = None diff --git a/tests/webapps_tests.py b/tests/webapps_tests.py index fe41b9ffa..8313bf820 100644 --- a/tests/webapps_tests.py +++ b/tests/webapps_tests.py @@ -38,6 +38,7 @@ def t02_get_python_webapp_test(self): def t03_get_definition_test(self): webapp = self.project.get_webapp(testWebAppPythonId) definition = webapp.get_definition() + print "Definition " + str(definition) ok_(definition is not None) eq_(definition.webapp_id, testWebAppPythonId) eq_(definition.get_definition()["id"], testWebAppPythonId)