Skip to content

Commit 48d6d12

Browse files
committed
Merge remote-tracking branch 'origin/release/10.0' into feature/dss100-sc-58209-public-api-for-api-keys
2 parents ab6086a + 9243611 commit 48d6d12

File tree

12 files changed

+213
-27
lines changed

12 files changed

+213
-27
lines changed

dataikuapi/apinode_client.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,16 @@ class APINodeClient(DSSBaseClient):
66
This is an API client for the user-facing API of DSS API Node server (user facing API)
77
"""
88

9-
def __init__(self, uri, service_id, api_key=None):
9+
def __init__(self, uri, service_id, api_key=None, bearer_token=None):
1010
"""
1111
Instantiate a new DSS API client on the given base URI with the given API key.
1212
1313
:param str uri: Base URI of the DSS API node server (http://host:port/ or https://host:port/)
1414
:param str service_id: Identifier of the service to query
15-
:param str api_key: Optional, API key for the service. Only required if the service has authentication
15+
:param str api_key: Optional, API key for the service. Only required if the service has its authorization setup to API keys
16+
:param str bearer_token: Optional, The bearer token. Only required if the service has its authorization setup to OAuth2/JWT
1617
"""
17-
DSSBaseClient.__init__(self, "%s/%s" % (uri, "public/api/v1/%s" % service_id), api_key)
18+
DSSBaseClient.__init__(self, "%s/%s" % (uri, "public/api/v1/%s" % service_id), api_key=api_key, bearer_token=bearer_token)
1819

1920
def predict_record(self, endpoint_id, features, forced_generation=None, dispatch_key=None, context=None,
2021
with_explanations=None, explanation_method=None, n_explanations=None, n_explanations_mc_steps=None):

dataikuapi/base_client.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
from .utils import DataikuException
66

77
class DSSBaseClient(object):
8-
def __init__(self, base_uri, api_key=None, internal_ticket=None):
8+
def __init__(self, base_uri, api_key=None, internal_ticket=None, bearer_token=None):
99
self.api_key = api_key
10+
self.bearer_token = bearer_token
1011
self.base_uri = base_uri
1112
self._session = Session()
1213

@@ -18,12 +19,18 @@ def _perform_http(self, method, path, params=None, body=None, stream=False):
1819
if body:
1920
body = json.dumps(body)
2021

21-
auth = HTTPBasicAuth(self.api_key, "")
22+
headers = None
23+
auth = None
24+
25+
if self.api_key:
26+
auth = HTTPBasicAuth(self.api_key, "")
27+
elif self.bearer_token:
28+
headers = {"Authorization": "Bearer " + self.bearer_token}
2229

2330
try:
2431
http_res = self._session.request(
2532
method, "%s/%s" % (self.base_uri, path),
26-
params=params, data=body,
33+
params=params, data=body, headers=headers,
2734
auth=auth, stream = stream)
2835
http_res.raise_for_status()
2936
return http_res

dataikuapi/dss/admin.py

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import datetime
2+
13
from .future import DSSFuture
24
import json, warnings
35

@@ -164,11 +166,22 @@ def delete(self):
164166
def get_settings(self):
165167
"""
166168
Gets the settings of the user
169+
167170
:rtype: :class:`DSSUserSettings`
168171
"""
169172
raw = self.client._perform_json("GET", "/admin/users/%s" % self.login)
170173
return DSSUserSettings(self.client, self.login, raw)
171174

175+
def get_activity(self):
176+
"""
177+
Gets the activity of the user
178+
179+
:return: the user's activity
180+
:rtype: :class:`DSSUserActivity`
181+
"""
182+
activity = self.client._perform_json("GET", "/admin/users/%s/activity" % self.login)
183+
return DSSUserActivity(self.client, self.login, activity)
184+
172185
########################################################
173186
# Legacy
174187
########################################################
@@ -224,6 +237,7 @@ def get_client_as(self):
224237
else:
225238
raise ValueError("Don't know how to proxy this client")
226239

240+
227241
class DSSOwnUser(object):
228242
"""
229243
A handle to interact with your own user
@@ -250,8 +264,7 @@ def __init__(self, settings):
250264

251265
def get_raw(self):
252266
"""
253-
:return: the raw settings of the user, as a dict. Modifications made to the returned object
254-
are reflected when saving
267+
:return: the raw settings of the user, as a dict. Modifications made to the returned object are reflected when saving
255268
:rtype: dict
256269
"""
257270
return self.settings
@@ -331,15 +344,15 @@ def admin_properties(self):
331344
"""
332345
The user properties (not editable by the user) for this user. Do not set this property, modify the dict in place
333346
334-
:rtype dict
347+
:rtype: dict
335348
"""
336349
return self.settings["adminProperties"]
337350

338351
@property
339352
def enabled(self):
340353
"""
341354
Whether this user is enabled
342-
:rtype boolean
355+
:rtype: boolean
343356
"""
344357
return self.settings["enabled"]
345358

@@ -365,6 +378,67 @@ def save(self):
365378
self.client._perform_empty("PUT", "/current-user", body = self.settings)
366379

367380

381+
class DSSUserActivity(object):
382+
"""
383+
Activity for a DSS user.
384+
Do not call this directly, use :meth:`DSSUser.get_activity` or :meth:`DSSClient.list_users_activity`
385+
"""
386+
387+
def __init__(self, client, login, activity):
388+
self.client = client
389+
self.login = login
390+
self.activity = activity
391+
392+
def get_raw(self):
393+
"""
394+
Get the raw activity of the user as a dict.
395+
396+
:return: the raw activity
397+
:rtype: dict
398+
"""
399+
return self.activity
400+
401+
@property
402+
def last_successful_login(self):
403+
"""
404+
Get the last successful login of the user as a :class:`datetime.datetime`
405+
406+
Returns None if there was no successful login for this user.
407+
408+
:return: the last successful login
409+
:rtype: :class:`datetime.datetime` or None
410+
"""
411+
timestamp = self.activity["lastSuccessfulLogin"]
412+
return datetime.datetime.fromtimestamp(timestamp / 1000) if timestamp > 0 else None
413+
414+
@property
415+
def last_failed_login(self):
416+
"""
417+
Get the last failed login of the user as a :class:`datetime.datetime`
418+
419+
Returns None if there were no failed login for this user.
420+
421+
:return: the last failed login
422+
:rtype: :class:`datetime.datetime` or None
423+
"""
424+
timestamp = self.activity["lastFailedLogin"]
425+
return datetime.datetime.fromtimestamp(timestamp / 1000) if timestamp > 0 else None
426+
427+
@property
428+
def last_session_activity(self):
429+
"""
430+
Get the last session activity of the user as a :class:`datetime.datetime`, i.e. the last time
431+
the user opened a new DSS tab or refreshed his session.
432+
433+
Returns None if there is no session activity yet.
434+
435+
:return: the last session activity
436+
:rtype: :class:`datetime.datetime` or None
437+
"""
438+
timestamp = self.activity["lastSessionActivity"]
439+
return datetime.datetime.fromtimestamp(timestamp / 1000) if timestamp > 0 else None
440+
441+
368442
class DSSGroup(object):
369443
"""
370444
A group on the DSS instance.

dataikuapi/dss/dataset.py

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import datetime
2+
13
from ..utils import DataikuException
24
from ..utils import DataikuUTF8CSVReader
35
from ..utils import DataikuStreamedHttpUTF8CSVReader
@@ -511,7 +513,6 @@ def get_last_metric_values(self, partition=''):
511513
return ComputedMetrics(self.client._perform_json(
512514
"GET", "/projects/%s/datasets/%s/metrics/last/%s" % (self.project_key, self.dataset_name, 'NP' if len(partition) == 0 else partition)))
513515

514-
515516
def get_metric_history(self, metric, partition=''):
516517
"""
517518
Get the history of the values of the metric on this dataset
@@ -523,6 +524,16 @@ def get_metric_history(self, metric, partition=''):
523524
"GET", "/projects/%s/datasets/%s/metrics/history/%s" % (self.project_key, self.dataset_name, 'NP' if len(partition) == 0 else partition),
524525
params={'metricLookup' : metric if isinstance(metric, str) or isinstance(metric, unicode) else json.dumps(metric)})
525526

527+
def get_info(self):
528+
"""
529+
Retrieve all the information about a dataset
530+
531+
:returns: a :class:`DSSDatasetInfo` containing all the information about a dataset.
532+
:rtype: :class:`DSSDatasetInfo`
533+
"""
534+
data = self.client._perform_json("GET", "/projects/%s/datasets/%s/info" % (self.project_key, self.dataset_name))
535+
return DSSDatasetInfo(self, data)
536+
526537
########################################################
527538
# Misc
528539
########################################################
@@ -542,7 +553,7 @@ def move_to_zone(self, zone):
542553
:param object zone: a :class:`dataikuapi.dss.flow.DSSFlowZone` where to move the object
543554
"""
544555
if isinstance(zone, basestring):
545-
zone = self.project.get_flow().get_zone(zone)
556+
zone = self.project.get_flow().get_zone(zone)
546557
zone.add_item(self)
547558

548559
def share_to_zone(self, zone):
@@ -859,3 +870,59 @@ def already_exists(self):
859870
return True
860871
except Exception as e:
861872
return False
873+
874+
875+
class DSSDatasetInfo(object):
876+
"""
877+
Info class for a DSS dataset (Read-Only).
878+
Do not instantiate this class directly, use :meth:`DSSDataset.get_info`
879+
"""
880+
881+
def __init__(self, dataset, info):
882+
self.dataset = dataset
883+
self.info = info
884+
885+
def get_raw(self):
886+
"""
887+
Get the raw dataset full information as a dict
888+
889+
:return: the raw dataset full information
890+
:rtype: dict
891+
"""
892+
return self.info
893+
894+
@property
895+
def last_build_start_time(self):
896+
"""
897+
The last build start time of the dataset as a :class:`datetime.datetime` or None if there is no last build information.
898+
899+
:return: the last build start time
900+
:rtype: :class:`datetime.datetime` or None
901+
"""
902+
last_build_info = self.info.get("lastBuild", dict())
903+
timestamp = last_build_info.get("buildStartTime", None)
904+
return datetime.datetime.fromtimestamp(timestamp / 1000) if timestamp is not None else None
905+
906+
@property
907+
def last_build_end_time(self):
908+
"""
909+
The last build end time of the dataset as a :class:`datetime.datetime` or None if there is no last build information.
910+
911+
:return: the last build end time
912+
:rtype: :class:`datetime.datetime` or None
913+
"""
914+
last_build_info = self.info.get("lastBuild", dict())
915+
timestamp = last_build_info.get("buildEndTime", None)
916+
return datetime.datetime.fromtimestamp(timestamp / 1000) if timestamp is not None else None
917+
918+
@property
919+
def is_last_build_successful(self):
920+
"""
921+
Get whether the last build of the dataset is successful.
922+
923+
:return: True if the last build is successful
924+
:rtype: bool
925+
"""
926+
last_build_info = self.info.get("lastBuild", dict())
927+
success = last_build_info.get("buildSuccess", False)
928+
return success

dataikuapi/dss/modelcomparison.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from dataikuapi.dss.discussion import DSSObjectDiscussions
1+
from .discussion import DSSObjectDiscussions
22
import re
33

44

dataikuapi/dss/modelevaluationstore.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import json
22
from io import BytesIO
33

4-
from dataikuapi.dss.metrics import ComputedMetrics
4+
from .metrics import ComputedMetrics
55
from .discussion import DSSObjectDiscussions
66
from .future import DSSFuture
77

dataikuapi/dss/recipe.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1411,6 +1411,12 @@ class StandaloneEvaluationRecipeCreator(DSSRecipeCreator):
14111411
builder.with_input("scored_dataset_to_evaluate")
14121412
builder.with_output_evaluation_store(evaluation_store_id)
14131413
1414+
# Add a reference dataset (optional) to compute data drift
1415+
1416+
builder.with_reference_dataset("reference_dataset")
1417+
1418+
# Finish creation of the recipe
1419+
14141420
new_recipe = builder.create()
14151421
14161422
# Modify the model parameters in the SER settings
@@ -1465,6 +1471,10 @@ def with_output_evaluation_store(self, mes_id):
14651471
"""Sets the output model evaluation store"""
14661472
return self._with_output(mes_id, role="main")
14671473

1474+
def with_reference_dataset(self, dataset_name):
1475+
"""Sets the dataset to use as a reference in data drift computation (optional)."""
1476+
return self._with_input(dataset_name, self.project.project_key, role="reference")
1477+
14681478

14691479
class ClusteringScoringRecipeCreator(SingleOutputRecipeCreator):
14701480
"""

dataikuapi/dss/savedmodel.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from .ml import DSSTrainedPredictionModelDetails
77
from .managedfolder import DSSManagedFolder
88

9-
from ..utils import _make_zipfile
9+
from ..utils import _make_zipfile, dku_basestring_type
1010

1111
try:
1212
basestring
@@ -166,10 +166,13 @@ def import_mlflow_version_from_managed_folder(self, version_id, managed_folder,
166166
"""
167167
# TODO: Add a check that it's indeed a MLFlow model folder
168168
folder_ref = None
169-
if type(managed_folder) is DSSManagedFolder:
169+
if isinstance(managed_folder, DSSManagedFolder):
170170
folder_ref = "{}.{}".format(managed_folder.project_key, managed_folder.id)
171-
else:
171+
elif isinstance(managed_folder, dku_basestring_type):
172172
folder_ref = managed_folder
173+
else:
174+
raise Exception("managed_folder should either be a string representing the identifier of the managed folder"
175+
" or an instance of dataikuapi.dss.managedfolder.DSSManagedFolder")
173176

174177
self.client._perform_empty(
175178
"POST", "/projects/{project_id}/savedmodels/{saved_model_id}/versions/{version_id}?codeEnvName={codeEnvName}".format(

dataikuapi/dss/scenario.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,15 +134,17 @@ def get_run(self, run_id):
134134
def get_status(self):
135135
"""
136136
Returns the status of this scenario
137-
:rtype :class:`DSSScenarioStatus`
137+
138+
:rtype: :class:`DSSScenarioStatus`
138139
"""
139140
data = self.client._perform_json("GET", "/projects/%s/scenarios/%s/light" % (self.project_key, self.id))
140141
return DSSScenarioStatus(self, data)
141142

142143
def get_settings(self):
143144
"""
144145
Returns the settings of this scenario
145-
:rtype :class:`StepBasedDSSScenarioSettings` or :class:`PythonScriptBasedScenarioSettings`
146+
147+
:rtype: :class:`StepBasedScenarioSettings` or :class:`PythonScriptBasedScenarioSettings`
146148
"""
147149
data = self.client._perform_json("GET", "/projects/%s/scenarios/%s" % (self.project_key, self.id))
148150

0 commit comments

Comments
 (0)