Skip to content

Commit 35846a2

Browse files
author
Valentin Thorey
committed
Merge branch 'release/10.0'
2 parents c948318 + 7ddb29c commit 35846a2

File tree

11 files changed

+404
-38
lines changed

11 files changed

+404
-38
lines changed

dataikuapi/dss/admin.py

Lines changed: 268 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -695,13 +695,18 @@ def set_definition(self, env):
695695
696696
* env.permissions, env.usableByAll, env.desc.owner
697697
* env.specCondaEnvironment, env.specPackageList, env.externalCondaEnvName, env.desc.installCorePackages,
698-
env.desc.installJupyterSupport, env.desc.yarnPythonBin
698+
env.desc.corePackagesSet, env.desc.installJupyterSupport, env.desc.yarnPythonBin, env.desc.yarnRBin
699+
env.desc.envSettings, env.desc.allContainerConfs, env.desc.containerConfs,
700+
env.desc.allSparkKubernetesConfs, env.desc.sparkKubernetesConfs
699701
700702
Fields that can be updated in automation node (where {version} is the updated version):
701703
702-
* env.permissions, env.usableByAll, env.owner
704+
* env.permissions, env.usableByAll, env.owner, env.envSettings
703705
* env.{version}.specCondaEnvironment, env.{version}.specPackageList, env.{version}.externalCondaEnvName,
704-
env.{version}.desc.installCorePackages, env.{version}.desc.installJupyterSupport, env.{version}.desc.yarnPythonBin
706+
env.{version}.desc.installCorePackages, env.{version}.corePackagesSet, env.{version}.desc.installJupyterSupport
707+
env.{version}.desc.yarnPythonBin, env.{version}.desc.yarnRBin, env.{version}.desc.allContainerConfs,
708+
env.{version}.desc.containerConfs, env.{version}.desc.allSparkKubernetesConfs,
709+
env.{version}.{version}.desc.sparkKubernetesConfs
705710
706711
Note: this call requires an API key with admin rights
707712
@@ -722,6 +727,39 @@ def get_version_for_project(self, project_key):
722727
return self.client._perform_json(
723728
"GET", "/admin/code-envs/%s/%s/%s/version" % (self.env_lang, self.env_name, project_key))
724729

730+
731+
def get_settings(self):
732+
"""
733+
Returns the settings of this code env as a :class:`DSSCodeEnvSettings`, or one of its subclasses.
734+
735+
Known subclasses of :class:`DSSCodeEnvSettings` include :class:`DSSDesignCodeEnvSettings`
736+
and :class:`DSSAutomationCodeEnvSettings`
737+
738+
You must use :meth:`~DSSCodeEnvSettings.save()` on the returned object to make your changes effective
739+
on the code env.
740+
741+
.. code-block:: python
742+
743+
# Example: setting the required packagd
744+
codeenv = client.get_code_env("PYTHON", "code_env_name")
745+
settings = codeenv.get_settings()
746+
settings.set_required_packages("dash==2.0.0", "bokeh<2.0")
747+
settings.save()
748+
# then proceed to update_packages()
749+
750+
:rtype: :class:`DSSCodeEnvSettings`
751+
"""
752+
data = self.client._perform_json(
753+
"GET", "/admin/code-envs/%s/%s" % (self.env_lang, self.env_name))
754+
755+
# you can't just use deploymentMode to check if it's an automation code
756+
# env, because some modes are common to both types of nodes. So we rely
757+
# on a non-null field that only the automation code envs have
758+
if data.get("versions", None) is not None:
759+
return DSSAutomationCodeEnvSettings(self, data)
760+
else:
761+
return DSSDesignCodeEnvSettings(self, data)
762+
725763

726764
########################################################
727765
# Code env actions
@@ -806,6 +844,171 @@ def get_log(self, log_name):
806844
"GET", "/admin/code-envs/%s/%s/logs/%s" % (self.env_lang, self.env_name, log_name))
807845

808846

847+
class DSSCodeEnvSettings(object):
848+
"""
849+
Base settings class for a DSS code env.
850+
Do not instantiate this class directly, use :meth:`DSSCodeEnv.get_settings`
851+
852+
Use :meth:`save` to save your changes
853+
"""
854+
855+
def __init__(self, codeenv, settings):
856+
self.codeenv = codeenv
857+
self.settings = settings
858+
859+
def get_raw(self):
860+
"""Get the raw code env settings as a dict"""
861+
return self.settings
862+
863+
@property
864+
def env_lang(self):
865+
return self.codeenv.env_lang
866+
867+
@property
868+
def env_name(self):
869+
return self.codeenv.env_name
870+
871+
def save(self):
872+
self.codeenv.client._perform_json(
873+
"PUT", "/admin/code-envs/%s/%s" % (self.env_lang, self.env_name), body=self.settings)
874+
875+
class DSSCodeEnvPackageListBearer(object):
876+
def get_required_packages(self, as_list=False):
877+
"""
878+
Return the list of required packages, as a single string
879+
880+
:param boolean as_list: if True, return the spec as a list of lines; if False, return as a single multiline string
881+
"""
882+
x = self.settings.get("specPackageList", "")
883+
return x.split('\n') if as_list else x
884+
def set_required_packages(self, *packages):
885+
"""
886+
Set the list of required packages
887+
"""
888+
self.settings["specPackageList"] = '\n'.join(packages)
889+
890+
def get_required_conda_spec(self, as_list=False):
891+
"""
892+
Return the list of required conda packages, as a single string
893+
894+
:param boolean as_list: if True, return the spec as a list of lines; if False, return as a single multiline string
895+
"""
896+
x = self.settings.get("specCondaEnvironment", "")
897+
return x.split('\n') if as_list else x
898+
def set_required_conda_spec(self, *spec):
899+
"""
900+
Set the list of required conda packages
901+
"""
902+
self.settings["specCondaEnvironment"] = '\n'.join(packages)
903+
904+
class DSSCodeEnvContainerConfsBearer(object):
905+
def get_built_for_all_container_confs(self):
906+
"""
907+
Return whether the code env creates an image for each container config
908+
"""
909+
return self.settings.get("allContainerConfs", False)
910+
def get_built_container_confs(self):
911+
"""
912+
Return the list of container configs for which the code env builds an image (if not all)
913+
"""
914+
return self.settings.get("containerConfs", [])
915+
def set_built_container_confs(self, *configs, **kwargs):
916+
"""
917+
Set the list of container configs for which the code env builds an image
918+
919+
:param boolean all: if True, an image is built for each config
920+
:param list configs: list of configuration names to build images for
921+
"""
922+
all = kwargs.get("all", False)
923+
self.settings['allContainerConfs'] = all
924+
if not all:
925+
self.settings['containerConfs'] = configs
926+
def built_for_all_spark_kubernetes_confs(self):
927+
"""
928+
Return whether the code env creates an image for each managed Spark over Kubernetes config
929+
"""
930+
return self.settings.get("allSparkKubernetesConfs", False)
931+
def get_built_spark_kubernetes_confs(self):
932+
"""
933+
Return the list of managed Spark over Kubernetes configs for which the code env builds an image (if not all)
934+
"""
935+
return self.settings.get("sparkKubernetesConfs", [])
936+
def set_built_spark_kubernetes_confs(self, *configs, **kwargs):
937+
"""
938+
Set the list of managed Spark over Kubernetes configs for which the code env builds an image
939+
940+
:param boolean all: if True, an image is built for each config
941+
:param list configs: list of configuration names to build images for
942+
"""
943+
all = kwargs.get("all", False)
944+
self.settings['allSparkKubernetesConfs'] = all
945+
if not all:
946+
self.settings['sparkKubernetesConfs'] = configs
947+
948+
949+
class DSSDesignCodeEnvSettings(DSSCodeEnvSettings, DSSCodeEnvPackageListBearer, DSSCodeEnvContainerConfsBearer):
950+
"""
951+
Base settings class for a DSS code env on a design node.
952+
Do not instantiate this class directly, use :meth:`DSSCodeEnv.get_settings`
953+
954+
Use :meth:`save` to save your changes
955+
"""
956+
957+
def __init__(self, codeenv, settings):
958+
super(DSSDesignCodeEnvSettings, self).__init__(codeenv, settings)
959+
960+
961+
class DSSAutomationCodeEnvSettings(DSSCodeEnvSettings, DSSCodeEnvContainerConfsBearer):
962+
"""
963+
Base settings class for a DSS code env on an automation node.
964+
Do not instantiate this class directly, use :meth:`DSSCodeEnv.get_settings`
965+
966+
Use :meth:`save` to save your changes
967+
"""
968+
969+
def __init__(self, codeenv, settings):
970+
super(DSSAutomationCodeEnvSettings, self).__init__(codeenv, settings)
971+
972+
973+
def get_version(self, version_id=None):
974+
"""
975+
Get a specific code env version (for versioned envs) or the single
976+
version
977+
978+
:param string version_id: for versioned code env, identifier of the desired version
979+
980+
:rtype: :class:`DSSAutomationCodeEnvVersionSettings`
981+
"""
982+
deployment_mode = self.settings.get("deploymentMode", None)
983+
if deployment_mode in ['AUTOMATION_SINGLE']:
984+
return DSSAutomationCodeEnvVersionSettings(self.codeenv, self.settings.get('currentVersion', {}))
985+
elif deployment_mode in ['AUTOMATION_VERSIONED']:
986+
versions = self.settings.get("versions", [])
987+
version_ids = [v.get('versionId') for v in versions]
988+
if version_id is None:
989+
raise Exception("A version id is required in a versioned code env. Existing ids: %s" % ', '.join(version_ids))
990+
for version in versions:
991+
if version_id == version.get("versionId"):
992+
return DSSAutomationCodeEnvVersionSettings(self.codeenv, version)
993+
raise Exception("Version %s not found in : %s" % (version_id, ', '.join(version_ids)))
994+
elif deployment_mode in ['PLUGIN_NON_MANAGED', 'PLUGIN_MANAGED', 'AUTOMATION_NON_MANAGED_PATH', 'EXTERNAL_CONDA_NAMED']:
995+
return DSSAutomationCodeEnvVersionSettings(self.codeenv, self.settings.get('noVersion', {}))
996+
else:
997+
raise Exception("Unexpected deployment mode %s for an automation node code env. Alter the settings directly with get_raw()", deployment_mode)
998+
999+
class DSSAutomationCodeEnvVersionSettings(DSSCodeEnvPackageListBearer):
1000+
"""
1001+
Base settings class for a DSS code env version on an automation node.
1002+
Do not instantiate this class directly, use :meth:`DSSAutomationCodeEnvSettings.get_version`
1003+
1004+
Use :meth:`save` on the :class:`DSSAutomationCodeEnvSettings` to save your changes
1005+
"""
1006+
1007+
def __init__(self, codeenv_settings, version_settings):
1008+
self.codeenv_settings = codeenv_settings
1009+
self.settings = version_settings
1010+
1011+
8091012
class DSSGlobalApiKey(object):
8101013
"""
8111014
A global API key on the DSS instance
@@ -1004,3 +1207,65 @@ def get_raw(self):
10041207
Gets the whole status as a raw dictionary.
10051208
"""
10061209
return self.status
1210+
1211+
1212+
class DSSInstanceVariables(dict):
1213+
"""
1214+
Dict containing the instance variables. The variables can be modified directly in the dict and persisted using its :meth:`save` method.
1215+
1216+
Do not create this directly, use :meth:`dataikuapi.DSSClient.get_global_variables`
1217+
"""
1218+
1219+
def __init__(self, client, variables):
1220+
super(dict, self).__init__()
1221+
self.update(variables)
1222+
self.client = client
1223+
1224+
def save(self):
1225+
"""
1226+
Save the changes made to the instance variables.
1227+
1228+
Note: this call requires an API key with admin rights.
1229+
"""
1230+
return self.client._perform_empty("PUT", "/admin/variables/", body=self)
1231+
1232+
1233+
class DSSGlobalUsageSummary(object):
1234+
"""
1235+
The summary of the usage of the DSS instance.
1236+
Do not create this directly, use :meth:`dataikuapi.dss.DSSClient.get_global_usage_summary`
1237+
"""
1238+
def __init__(self, data):
1239+
self.data = data
1240+
1241+
@property
1242+
def raw(self):
1243+
return self.data
1244+
1245+
@property
1246+
def projects_count(self):
1247+
return self.data["projects"]
1248+
1249+
@property
1250+
def total_datasets_count(self):
1251+
return self.data["datasets"]["all"]
1252+
1253+
@property
1254+
def total_recipes_count(self):
1255+
return self.data["recipes"]["all"]
1256+
1257+
@property
1258+
def total_jupyter_notebooks_count(self):
1259+
return self.data["notebooks"]["nbJupyterNotebooks"]
1260+
1261+
@property
1262+
def total_sql_notebooks_count(self):
1263+
return self.data["notebooks"]["nbSqlNotebooks"]
1264+
1265+
@property
1266+
def total_scenarios_count(self):
1267+
return self.data["scenarios"]["all"]
1268+
1269+
@property
1270+
def total_active_with_trigger_scenarios_count(self):
1271+
return self.data["scenarios"]["activeWithTriggers"]

dataikuapi/dss/app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ def __init__(self, client, project_key):
135135
DSSAppInstance.__init__(self, client,project_key)
136136

137137
def close(self):
138-
self.get_as_project().delete(drop_data=True)
138+
self.get_as_project().delete(clear_managed_datasets=True)
139139

140140
def __enter__(self,):
141141
return self

dataikuapi/dss/flow.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,22 @@ def add_item(self, obj):
338338
self._raw = self.client._perform_json("POST", "/projects/%s/flow/zones/%s/items" % (self.flow.project.project_key, self.id),
339339
body=self.flow._to_smart_ref(obj))
340340

341+
def add_items(self, items):
342+
"""
343+
Adds items to this zone.
344+
345+
The items will automatically be moved from their existing zones. Additional items may be moved to this zone
346+
as a result of the operations (notably the recipe generating the `items`).
347+
348+
:param list items: A list of objects, either :class:`dataikuapi.dss.dataset.DSSDataset`, :class:`dataikuapi.dss.managedfolder.DSSManagedFolder`,
349+
or :class:`dataikuapi.dss.savedmodel.DSSSavedModel` to add to the zone
350+
"""
351+
smart_refs = []
352+
for item in items:
353+
smart_refs.append(self.flow._to_smart_ref(item))
354+
self._raw = self.client._perform_json("POST", "/projects/%s/flow/zones/%s/add-items" % (self.flow.project.project_key, self.id),
355+
body=smart_refs)
356+
341357
@property
342358
def items(self):
343359
"""

dataikuapi/dss/managedfolder.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from ..utils import DataikuUTF8CSVReader
33
from ..utils import DataikuStreamedHttpUTF8CSVReader
44
import json
5+
import os
56
from requests import utils
67
from .metrics import ComputedMetrics
78
from .future import DSSFuture
@@ -99,7 +100,7 @@ def delete_file(self, path):
99100
def put_file(self, path, f):
100101
"""
101102
Upload the file to the managed folder
102-
103+
103104
Args:
104105
f: the file contents, as a stream
105106
path: the path of the file
@@ -108,6 +109,22 @@ def put_file(self, path, f):
108109
"POST", "/projects/%s/managedfolders/%s/contents/%s" % (self.project_key, self.odb_id, utils.quote(path)),
109110
"", f)
110111

112+
def upload_folder(self, path, folder):
113+
"""
114+
Upload the content of a folder at path in the managed folder.
115+
116+
Note: upload_folder("target", "source") will result in "target" containing the content
117+
of "source", not in "target" containing "source".
118+
119+
:param str path: the destination path of the folder in the managed folder
120+
:param str folder: path (absolute or relative) of the source folder to upload
121+
"""
122+
for root, _, files in os.walk(folder):
123+
for file in files:
124+
filename = os.path.join(root, file)
125+
with open(filename, "rb") as f:
126+
self.put_file(os.path.join(path, os.path.relpath(filename, folder)), f)
127+
111128
########################################################
112129
# Managed folder actions
113130
########################################################

0 commit comments

Comments
 (0)