Skip to content

Commit 47ca4ec

Browse files
committed
Richer scenario API
1 parent 74930dd commit 47ca4ec

File tree

2 files changed

+136
-11
lines changed

2 files changed

+136
-11
lines changed

dataikuapi/dss/project.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from .managedfolder import DSSManagedFolder
66
from .savedmodel import DSSSavedModel
77
from .job import DSSJob, DSSJobWaiter
8-
from .scenario import DSSScenario
8+
from .scenario import DSSScenario, DSSScenarioListItem
99
from .apiservice import DSSAPIService
1010
from .future import DSSFuture
1111
from .notebook import DSSNotebook
@@ -778,18 +778,22 @@ def preload_bundle(self, bundle_id):
778778
# Scenarios
779779
########################################################
780780

781-
def list_scenarios(self):
781+
def list_scenarios(self, as_type="listitems"):
782782
"""
783783
List the scenarios in this project.
784784
785-
This method returns a list of Python dictionaries. Each dictionary represents
786-
a scenario. Each dictionary contains at least a "id" field, that you can then pass
787-
to the :meth:`get_scenario`
788-
789-
:returns: the list of scenarios, each one as a Python dictionary
785+
:param str as_type: How to return the list. Supported values are "listitems" and "objects".
786+
:returns: The list of the datasets. If "rtype" is "listitems", each one as a :class:`scenario.DSSScenarioListItem`.
787+
If "rtype" is "objects", each one as a :class:`scenario.DSSScenario`
788+
:rtype: list
790789
"""
791-
return self.client._perform_json(
792-
"GET", "/projects/%s/scenarios/" % self.project_key)
790+
items = self.client._perform_json("GET", "/projects/%s/scenarios/" % self.project_key)
791+
if as_type == "listitems":
792+
return [DSSScenarioListItem(self.client, item) for item in items]
793+
elif as_type == "objects":
794+
return [DSSScenario(self.client, self.project_key, item["id"]) for item in items]
795+
else:
796+
raise ValueError("Unknown as_type")
793797

794798
def get_scenario(self, scenario_id):
795799
"""

dataikuapi/dss/scenario.py

Lines changed: 123 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import time, warnings
33
from dataikuapi.utils import DataikuException
44
from .discussion import DSSObjectDiscussions
5+
from .utils import DSSTaggableObjectListItem
56

67
class DSSScenario(object):
78
"""
@@ -87,6 +88,14 @@ def get_run(self, run_id):
8788
"GET", "/projects/%s/scenarios/%s/%s/" % (self.project_key, self.id, run_id))
8889
return DSSScenarioRun(self.client, run_details["scenarioRun"])
8990

91+
def get_status(self):
92+
"""
93+
Returns the status of this scenario
94+
:rtype :class:`DSSScenarioStatus`
95+
"""
96+
data = self.client._perform_json("GET", "/projects/%s/scenarios/%s/light" % (self.project_key, self.id))
97+
return DSSScenarioStatus(self, data)
98+
9099
def get_settings(self):
91100
"""
92101
Returns the settings of this scenario
@@ -198,6 +207,34 @@ def set_payload(self, script, with_status=True):
198207
return self.client._perform_json(
199208
"PUT", "/projects/%s/scenarios/%s/payload" % (self.project_key, self.id), body = {'script' : script})
200209

210+
class DSSScenarioStatus(object):
211+
"""Status of a scenario. Do not instantiate this class, use :meth:`DSSScenario.get_status`"""
212+
213+
def __init__(self, scenario, data):
214+
self.scenario = scenario
215+
self.data = data
216+
217+
def get_raw(self):
218+
return self.data
219+
220+
@property
221+
def running(self):
222+
return self.data["running"]
223+
224+
@property
225+
def next_run(self):
226+
"""
227+
Time at which the scenario is expected to next run based on its triggers.
228+
229+
May be None if there are no temporal triggers
230+
231+
This is an approximate indication as scenario run may be delayed, especially in the case of
232+
multiple triggers or high load
233+
"""
234+
if not "nextRun" in self.data or self.data["nextRun"] == 0:
235+
return None
236+
return datetime.fromtimestamp(self.data["nextRun"] / 1000)
237+
201238

202239
class DSSScenarioSettings(object):
203240
"""Settings of a scenario. Do not instantiate this class, use :meth:`DSSScenario.get_settings`"""
@@ -209,9 +246,46 @@ def __init__(self, client, scenario, data):
209246
def get_raw(self):
210247
return self.data
211248

212-
def set_active(self, active):
249+
@property
250+
def active(self):
251+
"""
252+
Whether this scenario is currently active, i.e. its auto-triggers are executing
253+
"""
254+
return self.data["active"]
255+
@active.setter
256+
def active(self, active):
213257
self.data["active"] = active
214258

259+
@property
260+
def run_as(self):
261+
"""
262+
The configured 'run as' of the scenario. None means that the scenario runs as its last modifier.
263+
Only administrators may set a non-None value
264+
"""
265+
return self.data.get("runAsUser", None)
266+
@run_as.setter
267+
def run_as(self, run_as):
268+
self.data["runAsUser"] = run_as
269+
270+
@property
271+
def effective_run_as(self):
272+
"""
273+
The effective 'run as' of the scenario. If a run_as has been configured by an administrator,
274+
this will be used. Else, this will be the last modifier of the scenario.
275+
276+
If this method returns None, it means that it was not possible to identify who this
277+
scenario runs as. This scenario is probably not currently functioning.
278+
"""
279+
# Note: this logic must match the one in ScenarioBaseService
280+
if "runAsUser" in self.data:
281+
return self.data["runAsUser"]
282+
elif "versionTag" in self.data:
283+
return self.data["versionTag"]["lastModifiedBy"]["login"]
284+
elif "creationTag" in self.data:
285+
return self.data["creationTag"]["lastModifiedBy"]["login"]
286+
else:
287+
return Non
288+
215289
@property
216290
def raw_triggers(self):
217291
return self.data["triggers"]
@@ -343,9 +417,17 @@ def get_details(self):
343417
344418
:rtype dict
345419
"""
346-
return self.client._perform_json(
420+
raw_data = self.client._perform_json(
347421
"GET", "/projects/%s/scenarios/%s/%s/" % (self.run['scenario']['projectKey'], self.run['scenario']['id'], self.run['runId']))
348422

423+
details = DSSScenarioRunDetails(raw_data)
424+
if "stepRuns" in details:
425+
structured_steps = []
426+
for step in details["stepRuns"]:
427+
structured_steps.append(DSSStepRunDetails(step))
428+
details["stepRuns"] = structured_steps
429+
return details
430+
349431
def get_start_time(self):
350432
"""
351433
Get the start time of the scenario run
@@ -376,6 +458,31 @@ def get_duration(self):
376458
return duration
377459
duration = property(get_duration)
378460

461+
class DSSScenarioRunDetails(dict):
462+
def __init__(self, data):
463+
super(DSSScenarioRunDetails, self).__init__(data)
464+
465+
@property
466+
def steps(self):
467+
return self["stepRuns"]
468+
469+
@property
470+
def last_step(self):
471+
return self["stepRuns"][len(self["stepRuns"]) - 1]
472+
473+
class DSSStepRunDetails(dict):
474+
def __init__(self, data):
475+
super(DSSStepRunDetails, self).__init__(data)
476+
477+
@property
478+
def outcome(self):
479+
return self["result"]["outcome"]
480+
481+
@property
482+
def job_ids(self):
483+
"""The list of DSS job ids that were ran as part of this step"""
484+
return [ri["jobId"] for ri in self["additionalReportItems"] if ri["type"] == "JOB_EXECUTED"]
485+
379486
class DSSScenarioRunWaiter(object):
380487
"""
381488
Helper to wait for a scenario to run to complete
@@ -459,3 +566,17 @@ def is_cancelled(self, refresh=False):
459566
})
460567
return self.trigger_fire["cancelled"]
461568

569+
570+
class DSSScenarioListItem(DSSTaggableObjectListItem):
571+
"""An item in a list of scenarios. Do not instantiate this class, use :meth:`dataikuapi.dss.project.DSSProject.list_scenarios"""
572+
def __init__(self, client, data):
573+
super(DSSScenarioListItem, self).__init__(data)
574+
self.client = client
575+
576+
def to_scenario(self):
577+
"""Gets the :class:`DSSScenario` corresponding to this scenario"""
578+
return DSSScenario(self.client, self._data["projectKey"], self._data["name"])
579+
580+
@property
581+
def id(self):
582+
return self._data["name"]

0 commit comments

Comments
 (0)