Skip to content

Commit 8567ff6

Browse files
committed
Merge branch 'release/8.0' into task/streaming-endpoints-public-api
2 parents 6c2f05c + 7d990f1 commit 8567ff6

File tree

13 files changed

+369
-121
lines changed

13 files changed

+369
-121
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
*.pyc
22
.idea
3-
.iml
3+
*.iml

dataikuapi/apinode_client.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ def __init__(self, uri, service_id, api_key=None):
1616
"""
1717
DSSBaseClient.__init__(self, "%s/%s" % (uri, "public/api/v1/%s" % service_id), api_key)
1818

19-
def predict_record(self, endpoint_id, features, forced_generation=None, dispatch_key=None, context=None):
19+
def predict_record(self, endpoint_id, features, forced_generation=None, dispatch_key=None, context=None,
20+
with_explanations=None, explanation_method=None, n_explanations=None, n_explanations_mc_steps=None):
2021
"""
2122
Predicts a single record on a DSS API node endpoint (standard or custom prediction)
2223
@@ -25,12 +26,24 @@ def predict_record(self, endpoint_id, features, forced_generation=None, dispatch
2526
:param forced_generation: See documentation about multi-version prediction
2627
:param dispatch_key: See documentation about multi-version prediction
2728
:param context: Optional, Python dictionary of additional context information. The context information is logged, but not directly used.
29+
:param with_explanations: Optional, whether individual explanations should be computed for each record. The prediction endpoint must be compatible. If None, will use the value configured in the endpoint.
30+
:param explanation_method: Optional, method to compute explanations. Valid values are 'SHAPLEY' or 'ICE'. If None, will use the value configured in the endpoint.
31+
:param n_explanations: Optional, number of explanations to output per prediction. If None, will use the value configured in the endpoint.
32+
:param n_explanations_mc_steps: Optional, precision parameter for SHAPLEY method, higher means more precise but slower (between 25 and 1000).
33+
If None, will use the value configured in the endpoint.
2834
2935
:return: a Python dict of the API answer. The answer contains a "result" key (itself a dict)
3036
"""
31-
obj = {
32-
"features" :features
37+
obj = {
38+
"features": features,
39+
"explanations": {
40+
"enabled": with_explanations,
41+
"method": explanation_method,
42+
"nExplanations": n_explanations,
43+
"nMonteCarloSteps": n_explanations_mc_steps
44+
}
3345
}
46+
3447
if context is not None:
3548
obj["context"] = context
3649
if forced_generation is not None:
@@ -40,14 +53,20 @@ def predict_record(self, endpoint_id, features, forced_generation=None, dispatch
4053

4154
return self._perform_json("POST", "%s/predict" % endpoint_id, body = obj)
4255

43-
def predict_records(self, endpoint_id, records, forced_generation=None, dispatch_key=None):
56+
def predict_records(self, endpoint_id, records, forced_generation=None, dispatch_key=None, with_explanations=None,
57+
explanation_method=None, n_explanations=None, n_explanations_mc_steps=None):
4458
"""
4559
Predicts a batch of records on a DSS API node endpoint (standard or custom prediction)
4660
4761
:param str endpoint_id: Identifier of the endpoint to query
4862
:param records: Python list of records. Each record must be a Python dict. Each record must contain a "features" dict (see predict_record) and optionally a "context" dict.
4963
:param forced_generation: See documentation about multi-version prediction
5064
:param dispatch_key: See documentation about multi-version prediction
65+
:param with_explanations: Optional, whether individual explanations should be computed for each record. The prediction endpoint must be compatible. If None, will use the value configured in the endpoint.
66+
:param explanation_method: Optional, method to compute explanations. Valid values are 'SHAPLEY' or 'ICE'. If None, will use the value configured in the endpoint.
67+
:param n_explanations: Optional, number of explanations to output per prediction. If None, will use the value configured in the endpoint.
68+
:param n_explanations_mc_steps: Optional, precision parameter for SHAPLEY method, higher means more precise but slower (between 25 and 1000).
69+
If None, will use the value configured in the endpoint.
5170
5271
:return: a Python dict of the API answer. The answer contains a "results" key (which is an array of result objects)
5372
"""
@@ -57,7 +76,13 @@ def predict_records(self, endpoint_id, records, forced_generation=None, dispatch
5776
raise ValueError("Each record must contain a 'features' dict")
5877

5978
obj = {
60-
"items" : records
79+
"items": records,
80+
"explanations": {
81+
"enabled": with_explanations,
82+
"method": explanation_method,
83+
"nExplanations": n_explanations,
84+
"nMonteCarloSteps": n_explanations_mc_steps
85+
}
6186
}
6287

6388
if forced_generation is not None:

dataikuapi/dss/app.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import sys
2+
import re
23
import os.path as osp
34
from .future import DSSFuture
45
from dataikuapi.utils import DataikuException
@@ -40,7 +41,8 @@ def create_instance(self, instance_key, instance_name, wait=True):
4041
return future
4142

4243
def make_random_project_key(self):
43-
return "%s_tmp_%s" % (self.app_id, random_string(10))
44+
slugified_app_id = re.sub(r'[^A-Za-z_0-9]+', '_', self.app_id)
45+
return "%s_tmp_%s" % (slugified_app_id, random_string(10))
4446

4547
def create_temporary_instance(self):
4648
"""
@@ -77,15 +79,17 @@ def get_instance(self, instance_key):
7779

7880
def get_manifest(self):
7981
raw_data = self.client._perform_json("GET", "/apps/%s/" % self.app_id)
80-
return DSSAppManifest(self.client, raw_data)
82+
project_key = self.app_id[8:] if self.app_id.startswith('PROJECT_') else None
83+
return DSSAppManifest(self.client, raw_data, project_key)
8184

8285

8386
class DSSAppManifest(object):
8487

85-
def __init__(self, client, raw_data):
88+
def __init__(self, client, raw_data, project_key=None):
8689
"""The manifest for an app. Do not create this class directly"""
8790
self.client = client
8891
self.raw_data = raw_data
92+
self.project_key = project_key
8993

9094
def get_raw(self):
9195
return self.raw_data
@@ -97,6 +101,13 @@ def get_runnable_scenarios(self):
97101
"""Return the scenario identifiers that are declared as actions for this app"""
98102
return [x["scenarioId"] for x in self.get_all_actions() if x["type"] == "SCENARIO_RUN"]
99103

104+
def save(self):
105+
"""Saves the changes to this manifest object back to the template project"""
106+
if self.project_key is None:
107+
raise Exception("This manifest object wasn't created from a project, cannot be saved back")
108+
self.client._perform_empty("PUT", "/projects/%s/app-manifest" % self.project_key, body=self.raw_data)
109+
110+
100111
class DSSAppInstance(object):
101112

102113
def __init__(self, client, project_key):
@@ -124,7 +135,7 @@ def __init__(self, client, project_key):
124135

125136

126137
def close(self):
127-
self.get_as_project().delete()
138+
self.get_as_project().delete(drop_data=True)
128139

129140
def __enter__(self,):
130141
return self

dataikuapi/dss/dataset.py

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from ..utils import DataikuStreamedHttpUTF8CSVReader
44
from .future import DSSFuture
55
import json, warnings
6-
from .utils import DSSTaggableObjectListItem
6+
from .utils import DSSTaggableObjectListItem, DSSTaggableObjectSettings
77
from .future import DSSFuture
88
from .metrics import ComputedMetrics
99
from .discussion import DSSObjectDiscussions
@@ -59,6 +59,10 @@ def __init__(self, client, project_key, dataset_name):
5959
self.project_key = project_key
6060
self.dataset_name = dataset_name
6161

62+
@property
63+
def name(self):
64+
return self.dataset_name
65+
6266
########################################################
6367
# Dataset deletion
6468
########################################################
@@ -398,13 +402,31 @@ def create_analysis(self):
398402
"""
399403
return self.project_create_analysis(self.dataset_name)
400404

401-
def list_analyses(self):
405+
def list_analyses(self, as_type="listitems"):
402406
"""
403407
List the visual analyses on this dataset
404-
:return list of dicts
408+
:param str as_type: How to return the list. Supported values are "listitems" and "objects".
409+
:returns: The list of the analyses. If "as_type" is "listitems", each one as a dict,
410+
If "as_type" is "objects", each one as a :class:`dataikuapi.dss.analysis.DSSAnalysis`
411+
:rtype: list
405412
"""
406-
analysis_list = self.project.list_analyses()
407-
return [desc for desc in analysis_list if self.dataset_name == desc.get('inputDataset')]
413+
analysis_list = [al for al in self.project.list_analyses() if self.dataset_name == al.get('inputDataset')]
414+
415+
if as_type == "listitems" or as_type == "listitem":
416+
return analysis_list
417+
elif as_type == "objects" or as_type == "object":
418+
return [self.project.get_analysis(item["analysisId"])for item in analysis_list]
419+
else:
420+
raise ValueError("Unknown as_type")
421+
422+
def delete_analyses(self, drop_data=False):
423+
"""
424+
Deletes all analyses that have this dataset as input dataset. Also deletes
425+
ML tasks that are part of the analysis
426+
427+
:param: bool drop_data: whether to drop data for all ML tasks in the analysis
428+
"""
429+
[analysis.delete(drop_data=drop_data) for analysis in self.list_analyses(as_type="objects")]
408430

409431
########################################################
410432
# Statistics worksheets
@@ -603,8 +625,9 @@ def new_recipe(self, type, recipe_name=None):
603625
builder.with_input(self.dataset_name)
604626
return builder
605627

606-
class DSSDatasetSettings(object):
628+
class DSSDatasetSettings(DSSTaggableObjectSettings):
607629
def __init__(self, dataset, settings):
630+
super(DSSDatasetSettings, self).__init__(settings)
608631
self.dataset = dataset
609632
self.settings = settings
610633

@@ -740,4 +763,4 @@ def already_exists(self):
740763
dataset.get_metadata()
741764
return True
742765
except Exception as e:
743-
return False
766+
return False

dataikuapi/dss/flow.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ def replace_input_computable(self, current_ref, new_ref, type="DATASET"):
4141
settings.save()
4242

4343
for recipe in self.project.list_recipes():
44-
fake_rap = DSSRecipeDefinitionAndPayload({"recipe" : recipe})
44+
recipe_handle = self.project.get_recipe(recipe["name"])
45+
fake_rap = DSSRecipeDefinitionAndPayload(recipe_handle, {"recipe" : recipe})
4546
if fake_rap.has_input(current_ref):
4647
logging.info("Recipe %s has %s as input, performing the replacement by %s"% \
4748
(recipe["name"], current_ref, new_ref))

dataikuapi/dss/managedfolder.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ def __init__(self, client, project_key, odb_id):
1515
self.project_key = project_key
1616
self.odb_id = odb_id
1717

18+
@property
19+
def id(self):
20+
return self.odb_id
21+
1822
########################################################
1923
# Managed folder deletion
2024
########################################################

0 commit comments

Comments
 (0)