mlrun 1.3.1rc5__py3-none-any.whl → 1.4.0rc2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mlrun might be problematic. Click here for more details.
- mlrun/__main__.py +57 -4
- mlrun/api/api/endpoints/marketplace.py +57 -4
- mlrun/api/api/endpoints/runs.py +2 -0
- mlrun/api/api/utils.py +102 -0
- mlrun/api/crud/__init__.py +1 -0
- mlrun/api/crud/marketplace.py +133 -44
- mlrun/api/crud/notifications.py +80 -0
- mlrun/api/crud/runs.py +2 -0
- mlrun/api/crud/secrets.py +1 -0
- mlrun/api/db/base.py +32 -0
- mlrun/api/db/session.py +3 -11
- mlrun/api/db/sqldb/db.py +162 -1
- mlrun/api/db/sqldb/models/models_mysql.py +41 -0
- mlrun/api/db/sqldb/models/models_sqlite.py +35 -0
- mlrun/api/main.py +54 -1
- mlrun/api/migrations_mysql/versions/c905d15bd91d_notifications.py +70 -0
- mlrun/api/migrations_sqlite/versions/959ae00528ad_notifications.py +61 -0
- mlrun/api/schemas/__init__.py +1 -0
- mlrun/api/schemas/marketplace.py +18 -8
- mlrun/api/{db/filedb/__init__.py → schemas/notification.py} +17 -1
- mlrun/api/utils/singletons/db.py +8 -14
- mlrun/builder.py +37 -26
- mlrun/config.py +12 -2
- mlrun/data_types/spark.py +9 -2
- mlrun/datastore/base.py +10 -1
- mlrun/datastore/sources.py +1 -1
- mlrun/db/__init__.py +6 -4
- mlrun/db/base.py +1 -2
- mlrun/db/httpdb.py +32 -6
- mlrun/db/nopdb.py +463 -0
- mlrun/db/sqldb.py +47 -7
- mlrun/execution.py +3 -0
- mlrun/feature_store/api.py +26 -12
- mlrun/feature_store/common.py +1 -1
- mlrun/feature_store/steps.py +110 -13
- mlrun/k8s_utils.py +10 -0
- mlrun/model.py +43 -0
- mlrun/projects/operations.py +5 -2
- mlrun/projects/pipelines.py +4 -3
- mlrun/projects/project.py +50 -10
- mlrun/run.py +5 -4
- mlrun/runtimes/__init__.py +2 -6
- mlrun/runtimes/base.py +82 -31
- mlrun/runtimes/function.py +22 -0
- mlrun/runtimes/kubejob.py +10 -8
- mlrun/runtimes/serving.py +1 -1
- mlrun/runtimes/sparkjob/__init__.py +0 -1
- mlrun/runtimes/sparkjob/abstract.py +0 -2
- mlrun/serving/states.py +2 -2
- mlrun/utils/helpers.py +1 -1
- mlrun/utils/notifications/notification/__init__.py +1 -1
- mlrun/utils/notifications/notification/base.py +14 -13
- mlrun/utils/notifications/notification/console.py +6 -3
- mlrun/utils/notifications/notification/git.py +19 -12
- mlrun/utils/notifications/notification/ipython.py +6 -3
- mlrun/utils/notifications/notification/slack.py +13 -12
- mlrun/utils/notifications/notification_pusher.py +185 -37
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.3.1rc5.dist-info → mlrun-1.4.0rc2.dist-info}/METADATA +6 -2
- {mlrun-1.3.1rc5.dist-info → mlrun-1.4.0rc2.dist-info}/RECORD +64 -63
- mlrun/api/db/filedb/db.py +0 -518
- mlrun/db/filedb.py +0 -899
- mlrun/runtimes/sparkjob/spark2job.py +0 -59
- {mlrun-1.3.1rc5.dist-info → mlrun-1.4.0rc2.dist-info}/LICENSE +0 -0
- {mlrun-1.3.1rc5.dist-info → mlrun-1.4.0rc2.dist-info}/WHEEL +0 -0
- {mlrun-1.3.1rc5.dist-info → mlrun-1.4.0rc2.dist-info}/entry_points.txt +0 -0
- {mlrun-1.3.1rc5.dist-info → mlrun-1.4.0rc2.dist-info}/top_level.txt +0 -0
mlrun/feature_store/steps.py
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
#
|
|
15
|
+
import math
|
|
15
16
|
import re
|
|
16
17
|
import uuid
|
|
17
18
|
import warnings
|
|
@@ -55,7 +56,7 @@ class MLRunStep(MapClass):
|
|
|
55
56
|
engine = get_engine(event)
|
|
56
57
|
self.do = self._engine_to_do_method.get(engine, None)
|
|
57
58
|
if self.do is None:
|
|
58
|
-
raise mlrun.errors.
|
|
59
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
59
60
|
f"Unrecognized engine: {engine}. Available engines are: pandas, spark and storey"
|
|
60
61
|
)
|
|
61
62
|
|
|
@@ -136,7 +137,7 @@ class MapValues(StepToDict, MLRunStep):
|
|
|
136
137
|
|
|
137
138
|
def __init__(
|
|
138
139
|
self,
|
|
139
|
-
mapping: Dict[str, Dict[str, Any]],
|
|
140
|
+
mapping: Dict[str, Dict[Union[str, int, bool], Any]],
|
|
140
141
|
with_original_features: bool = False,
|
|
141
142
|
suffix: str = "mapped",
|
|
142
143
|
**kwargs,
|
|
@@ -226,34 +227,130 @@ class MapValues(StepToDict, MLRunStep):
|
|
|
226
227
|
def _do_spark(self, event):
|
|
227
228
|
from itertools import chain
|
|
228
229
|
|
|
229
|
-
from pyspark.sql.functions import col, create_map, lit, when
|
|
230
|
+
from pyspark.sql.functions import col, create_map, isnan, isnull, lit, when
|
|
231
|
+
from pyspark.sql.types import DecimalType, DoubleType, FloatType
|
|
232
|
+
from pyspark.sql.utils import AnalysisException
|
|
230
233
|
|
|
234
|
+
df = event
|
|
235
|
+
source_column_names = df.columns
|
|
231
236
|
for column, column_map in self.mapping.items():
|
|
232
237
|
new_column_name = self._get_feature_name(column)
|
|
233
|
-
if
|
|
238
|
+
if not self.get_ranges_key() in column_map:
|
|
239
|
+
if column not in source_column_names:
|
|
240
|
+
continue
|
|
234
241
|
mapping_expr = create_map([lit(x) for x in chain(*column_map.items())])
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
242
|
+
try:
|
|
243
|
+
df = df.withColumn(
|
|
244
|
+
new_column_name,
|
|
245
|
+
when(
|
|
246
|
+
col(column).isin(list(column_map.keys())),
|
|
247
|
+
mapping_expr.getItem(col(column)),
|
|
248
|
+
).otherwise(col(column)),
|
|
249
|
+
)
|
|
250
|
+
# if failed to use otherwise it is probably because the new column has different type
|
|
251
|
+
# then the original column.
|
|
252
|
+
# we will try to replace the values without using 'otherwise'.
|
|
253
|
+
except AnalysisException:
|
|
254
|
+
df = df.withColumn(
|
|
255
|
+
new_column_name, mapping_expr.getItem(col(column))
|
|
256
|
+
)
|
|
257
|
+
col_type = df.schema[column].dataType
|
|
258
|
+
new_col_type = df.schema[new_column_name].dataType
|
|
259
|
+
# in order to avoid exception at isna on non-decimal/float columns -
|
|
260
|
+
# we need to check their types before filtering.
|
|
261
|
+
if isinstance(col_type, (FloatType, DoubleType, DecimalType)):
|
|
262
|
+
column_filter = (~isnull(col(column))) & (~isnan(col(column)))
|
|
263
|
+
else:
|
|
264
|
+
column_filter = ~isnull(col(column))
|
|
265
|
+
if isinstance(new_col_type, (FloatType, DoubleType, DecimalType)):
|
|
266
|
+
new_column_filter = isnull(col(new_column_name)) | isnan(
|
|
267
|
+
col(new_column_name)
|
|
268
|
+
)
|
|
269
|
+
else:
|
|
270
|
+
# we need to check that every value replaced if we changed column type - except None or NaN.
|
|
271
|
+
new_column_filter = isnull(col(new_column_name))
|
|
272
|
+
mapping_to_null = [
|
|
273
|
+
k
|
|
274
|
+
for k, v in column_map.items()
|
|
275
|
+
if v is None
|
|
276
|
+
or (
|
|
277
|
+
isinstance(v, (float, np.float64, np.float32, np.float16))
|
|
278
|
+
and math.isnan(v)
|
|
279
|
+
)
|
|
280
|
+
]
|
|
281
|
+
turned_to_none_values = df.filter(
|
|
282
|
+
column_filter & new_column_filter
|
|
283
|
+
).filter(~col(column).isin(mapping_to_null))
|
|
284
|
+
|
|
285
|
+
if len(turned_to_none_values.head(1)) > 0:
|
|
286
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
287
|
+
f"MapValues - mapping that changes column type must change all values accordingly,"
|
|
288
|
+
f" which is not the case for column '{column}'"
|
|
289
|
+
)
|
|
238
290
|
else:
|
|
239
291
|
for val, val_range in column_map["ranges"].items():
|
|
240
292
|
min_val = val_range[0] if val_range[0] != "-inf" else -np.inf
|
|
241
293
|
max_val = val_range[1] if val_range[1] != "inf" else np.inf
|
|
242
294
|
otherwise = ""
|
|
243
|
-
if new_column_name in
|
|
244
|
-
otherwise =
|
|
245
|
-
|
|
295
|
+
if new_column_name in df.columns:
|
|
296
|
+
otherwise = df[new_column_name]
|
|
297
|
+
df = df.withColumn(
|
|
246
298
|
new_column_name,
|
|
247
299
|
when(
|
|
248
|
-
(
|
|
300
|
+
(df[column] < max_val) & (df[column] >= min_val),
|
|
249
301
|
lit(val),
|
|
250
302
|
).otherwise(otherwise),
|
|
251
303
|
)
|
|
252
304
|
|
|
253
305
|
if not self.with_original_features:
|
|
254
|
-
|
|
306
|
+
df = df.select(*self.mapping.keys())
|
|
255
307
|
|
|
256
|
-
return
|
|
308
|
+
return df
|
|
309
|
+
|
|
310
|
+
@classmethod
|
|
311
|
+
def validate_args(cls, feature_set, **kwargs):
|
|
312
|
+
mapping = kwargs.get("mapping", [])
|
|
313
|
+
for column, column_map in mapping.items():
|
|
314
|
+
if not cls.get_ranges_key() in column_map:
|
|
315
|
+
types = set(
|
|
316
|
+
type(val)
|
|
317
|
+
for val in column_map.values()
|
|
318
|
+
if type(val) is not None
|
|
319
|
+
and not (
|
|
320
|
+
isinstance(val, (float, np.float64, np.float32, np.float16))
|
|
321
|
+
and math.isnan(val)
|
|
322
|
+
)
|
|
323
|
+
)
|
|
324
|
+
else:
|
|
325
|
+
if len(column_map) > 1:
|
|
326
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
327
|
+
f"MapValues - mapping values of the same column can not combine ranges and "
|
|
328
|
+
f"single replacement, which is the case for column '{column}'"
|
|
329
|
+
)
|
|
330
|
+
ranges_dict = column_map[cls.get_ranges_key()]
|
|
331
|
+
types = set()
|
|
332
|
+
for ranges_mapping_values in ranges_dict.values():
|
|
333
|
+
range_types = set(
|
|
334
|
+
type(val)
|
|
335
|
+
for val in ranges_mapping_values
|
|
336
|
+
if type(val) is not None
|
|
337
|
+
and val != "-inf"
|
|
338
|
+
and val != "inf"
|
|
339
|
+
and not (
|
|
340
|
+
isinstance(val, (float, np.float64, np.float32, np.float16))
|
|
341
|
+
and math.isnan(val)
|
|
342
|
+
)
|
|
343
|
+
)
|
|
344
|
+
types = types.union(range_types)
|
|
345
|
+
if len(types) > 1:
|
|
346
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
347
|
+
f"MapValues - mapping values of the same column must be in the"
|
|
348
|
+
f" same type, which was not the case for Column '{column}'"
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
@staticmethod
|
|
352
|
+
def get_ranges_key():
|
|
353
|
+
return "ranges"
|
|
257
354
|
|
|
258
355
|
|
|
259
356
|
class Imputer(StepToDict, MLRunStep):
|
mlrun/k8s_utils.py
CHANGED
|
@@ -508,6 +508,16 @@ class K8sHelper:
|
|
|
508
508
|
k8s_secret.data = secret_data
|
|
509
509
|
self.v1api.replace_namespaced_secret(secret_name, namespace, k8s_secret)
|
|
510
510
|
|
|
511
|
+
def load_secret(self, secret_name, namespace=""):
|
|
512
|
+
namespace = namespace or self.resolve_namespace(namespace)
|
|
513
|
+
|
|
514
|
+
try:
|
|
515
|
+
k8s_secret = self.v1api.read_namespaced_secret(secret_name, namespace)
|
|
516
|
+
except ApiException:
|
|
517
|
+
return None
|
|
518
|
+
|
|
519
|
+
return k8s_secret.data
|
|
520
|
+
|
|
511
521
|
def delete_project_secrets(self, project, secrets, namespace=""):
|
|
512
522
|
secret_name = self.get_project_secret_name(project)
|
|
513
523
|
self.delete_secrets(secret_name, secrets, namespace)
|
mlrun/model.py
CHANGED
|
@@ -338,6 +338,7 @@ class ImageBuilder(ModelObj):
|
|
|
338
338
|
origin_filename=None,
|
|
339
339
|
with_mlrun=None,
|
|
340
340
|
auto_build=None,
|
|
341
|
+
requirements: list = None,
|
|
341
342
|
):
|
|
342
343
|
self.functionSourceCode = functionSourceCode #: functionSourceCode
|
|
343
344
|
self.codeEntryType = "" #: codeEntryType
|
|
@@ -355,6 +356,7 @@ class ImageBuilder(ModelObj):
|
|
|
355
356
|
self.with_mlrun = with_mlrun #: with_mlrun
|
|
356
357
|
self.auto_build = auto_build #: auto_build
|
|
357
358
|
self.build_pod = None
|
|
359
|
+
self.requirements = requirements or [] #: pip requirements
|
|
358
360
|
|
|
359
361
|
@property
|
|
360
362
|
def source(self):
|
|
@@ -376,6 +378,32 @@ class ImageBuilder(ModelObj):
|
|
|
376
378
|
self._source = source
|
|
377
379
|
|
|
378
380
|
|
|
381
|
+
class Notification(ModelObj):
|
|
382
|
+
"""Notification specification"""
|
|
383
|
+
|
|
384
|
+
def __init__(
|
|
385
|
+
self,
|
|
386
|
+
kind=None,
|
|
387
|
+
name=None,
|
|
388
|
+
message=None,
|
|
389
|
+
severity=None,
|
|
390
|
+
when=None,
|
|
391
|
+
condition=None,
|
|
392
|
+
params=None,
|
|
393
|
+
status=None,
|
|
394
|
+
sent_time=None,
|
|
395
|
+
):
|
|
396
|
+
self.kind = kind
|
|
397
|
+
self.name = name
|
|
398
|
+
self.message = message
|
|
399
|
+
self.severity = severity
|
|
400
|
+
self.when = when
|
|
401
|
+
self.condition = condition
|
|
402
|
+
self.params = params or {}
|
|
403
|
+
self.status = status
|
|
404
|
+
self.sent_time = sent_time
|
|
405
|
+
|
|
406
|
+
|
|
379
407
|
class RunMetadata(ModelObj):
|
|
380
408
|
"""Run metadata"""
|
|
381
409
|
|
|
@@ -492,6 +520,7 @@ class RunSpec(ModelObj):
|
|
|
492
520
|
allow_empty_resources=None,
|
|
493
521
|
inputs_type_hints=None,
|
|
494
522
|
returns=None,
|
|
523
|
+
notifications=None,
|
|
495
524
|
):
|
|
496
525
|
# A dictionary of parsing configurations that will be read from the inputs the user set. The keys are the inputs
|
|
497
526
|
# keys (parameter names) and the values are the type hint given in the input keys after the colon.
|
|
@@ -526,6 +555,7 @@ class RunSpec(ModelObj):
|
|
|
526
555
|
self.verbose = verbose
|
|
527
556
|
self.scrape_metrics = scrape_metrics
|
|
528
557
|
self.allow_empty_resources = allow_empty_resources
|
|
558
|
+
self._notifications = notifications or []
|
|
529
559
|
|
|
530
560
|
def to_dict(self, fields=None, exclude=None):
|
|
531
561
|
struct = super().to_dict(fields, exclude=["handler"])
|
|
@@ -678,6 +708,19 @@ class RunSpec(ModelObj):
|
|
|
678
708
|
return str(self.handler)
|
|
679
709
|
return ""
|
|
680
710
|
|
|
711
|
+
@property
|
|
712
|
+
def notifications(self):
|
|
713
|
+
return self._notifications
|
|
714
|
+
|
|
715
|
+
@notifications.setter
|
|
716
|
+
def notifications(self, notifications):
|
|
717
|
+
if isinstance(notifications, list):
|
|
718
|
+
self._notifications = ObjectList.from_list(Notification, notifications)
|
|
719
|
+
elif isinstance(notifications, ObjectList):
|
|
720
|
+
self._notifications = notifications
|
|
721
|
+
else:
|
|
722
|
+
raise ValueError("Notifications must be a list")
|
|
723
|
+
|
|
681
724
|
def extract_type_hints_from_inputs(self):
|
|
682
725
|
"""
|
|
683
726
|
This method extracts the type hints from the inputs keys in the input dictionary.
|
mlrun/projects/operations.py
CHANGED
|
@@ -72,6 +72,7 @@ def run_function(
|
|
|
72
72
|
auto_build: bool = None,
|
|
73
73
|
schedule: Union[str, mlrun.api.schemas.ScheduleCronTrigger] = None,
|
|
74
74
|
artifact_path: str = None,
|
|
75
|
+
notifications: List[mlrun.model.Notification] = None,
|
|
75
76
|
returns: Optional[List[Union[str, Dict[str, str]]]] = None,
|
|
76
77
|
) -> Union[mlrun.model.RunObject, kfp.dsl.ContainerOp]:
|
|
77
78
|
"""Run a local or remote task as part of a local/kubeflow pipeline
|
|
@@ -91,7 +92,7 @@ def run_function(
|
|
|
91
92
|
LABELS = "is_error"
|
|
92
93
|
MODEL_CLASS = "sklearn.ensemble.RandomForestClassifier"
|
|
93
94
|
DATA_PATH = "s3://bigdata/data.parquet"
|
|
94
|
-
function = mlrun.import_function("hub://
|
|
95
|
+
function = mlrun.import_function("hub://auto-trainer")
|
|
95
96
|
run1 = run_function(function, params={"label_columns": LABELS, "model_class": MODEL_CLASS},
|
|
96
97
|
inputs={"dataset": DATA_PATH})
|
|
97
98
|
|
|
@@ -100,7 +101,7 @@ def run_function(
|
|
|
100
101
|
# create a project with two functions (local and from marketplace)
|
|
101
102
|
project = mlrun.new_project(project_name, "./proj)
|
|
102
103
|
project.set_function("mycode.py", "myfunc", image="mlrun/mlrun")
|
|
103
|
-
project.set_function("hub://
|
|
104
|
+
project.set_function("hub://auto-trainer", "train")
|
|
104
105
|
|
|
105
106
|
# run functions (refer to them by name)
|
|
106
107
|
run1 = run_function("myfunc", params={"x": 7})
|
|
@@ -143,6 +144,7 @@ def run_function(
|
|
|
143
144
|
see this link for help:
|
|
144
145
|
https://apscheduler.readthedocs.io/en/3.x/modules/triggers/cron.html#module-apscheduler.triggers.cron
|
|
145
146
|
:param artifact_path: path to store artifacts, when running in a workflow this will be set automatically
|
|
147
|
+
:param notifications: list of notifications to push when the run is completed
|
|
146
148
|
:param returns: List of log hints - configurations for how to log the returning values from the handler's
|
|
147
149
|
run (as artifacts or results). The list's length must be equal to the amount of returning
|
|
148
150
|
objects. A log hint may be given as:
|
|
@@ -198,6 +200,7 @@ def run_function(
|
|
|
198
200
|
or (project.artifact_path if project else None),
|
|
199
201
|
auto_build=auto_build,
|
|
200
202
|
schedule=schedule,
|
|
203
|
+
notifications=notifications,
|
|
201
204
|
)
|
|
202
205
|
if run_result:
|
|
203
206
|
run_result._notified = False
|
mlrun/projects/pipelines.py
CHANGED
|
@@ -705,7 +705,7 @@ class _LocalRunner(_PipelineRunner):
|
|
|
705
705
|
trace = traceback.format_exc()
|
|
706
706
|
logger.error(trace)
|
|
707
707
|
project.notifiers.push(
|
|
708
|
-
f"Workflow {workflow_id} run failed!, error: {e}\n{trace}", "error"
|
|
708
|
+
f":x: Workflow {workflow_id} run failed!, error: {e}\n{trace}", "error"
|
|
709
709
|
)
|
|
710
710
|
state = mlrun.run.RunStatuses.failed
|
|
711
711
|
mlrun.run.wait_for_runs_completion(pipeline_context.runs_map.values())
|
|
@@ -874,7 +874,8 @@ class _RemoteRunner(_PipelineRunner):
|
|
|
874
874
|
trace = traceback.format_exc()
|
|
875
875
|
logger.error(trace)
|
|
876
876
|
project.notifiers.push(
|
|
877
|
-
f"Workflow {workflow_name} run failed!, error: {e}\n{trace}",
|
|
877
|
+
f":x: Workflow {workflow_name} run failed!, error: {e}\n{trace}",
|
|
878
|
+
"error",
|
|
878
879
|
)
|
|
879
880
|
state = mlrun.run.RunStatuses.failed
|
|
880
881
|
return _PipelineRunStatus(
|
|
@@ -1033,7 +1034,7 @@ def load_and_run(
|
|
|
1033
1034
|
try:
|
|
1034
1035
|
notification_pusher.push(
|
|
1035
1036
|
message=message,
|
|
1036
|
-
severity=mlrun.
|
|
1037
|
+
severity=mlrun.api.schemas.NotificationSeverity.ERROR,
|
|
1037
1038
|
)
|
|
1038
1039
|
|
|
1039
1040
|
except Exception as exc:
|
mlrun/projects/project.py
CHANGED
|
@@ -117,7 +117,7 @@ def new_project(
|
|
|
117
117
|
# create a project with local and marketplace functions, a workflow, and an artifact
|
|
118
118
|
project = mlrun.new_project("myproj", "./", init_git=True, description="my new project")
|
|
119
119
|
project.set_function('prep_data.py', 'prep-data', image='mlrun/mlrun', handler='prep_data')
|
|
120
|
-
project.set_function('hub://
|
|
120
|
+
project.set_function('hub://auto-trainer', 'train')
|
|
121
121
|
project.set_artifact('data', Artifact(target_path=data_url))
|
|
122
122
|
project.set_workflow('main', "./myflow.py")
|
|
123
123
|
project.save()
|
|
@@ -169,7 +169,13 @@ def new_project(
|
|
|
169
169
|
# Remove original owner name for avoiding possible conflicts
|
|
170
170
|
project.spec.owner = None
|
|
171
171
|
else:
|
|
172
|
-
project = MlrunProject(
|
|
172
|
+
project = MlrunProject.from_dict(
|
|
173
|
+
{
|
|
174
|
+
"metadata": {
|
|
175
|
+
"name": name,
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
)
|
|
173
179
|
project.spec.context = context
|
|
174
180
|
project.spec.subpath = subpath or project.spec.subpath
|
|
175
181
|
|
|
@@ -419,9 +425,15 @@ def _load_project_dir(context, name="", subpath=""):
|
|
|
419
425
|
|
|
420
426
|
elif path.isfile(path.join(context, subpath_str, "function.yaml")):
|
|
421
427
|
func = import_function(path.join(context, subpath_str, "function.yaml"))
|
|
422
|
-
project = MlrunProject(
|
|
423
|
-
|
|
424
|
-
|
|
428
|
+
project = MlrunProject.from_dict(
|
|
429
|
+
{
|
|
430
|
+
"metadata": {
|
|
431
|
+
"name": func.metadata.project,
|
|
432
|
+
},
|
|
433
|
+
"spec": {
|
|
434
|
+
"functions": [{"url": "function.yaml", "name": func.metadata.name}],
|
|
435
|
+
},
|
|
436
|
+
}
|
|
425
437
|
)
|
|
426
438
|
else:
|
|
427
439
|
raise mlrun.errors.MLRunNotFoundError(
|
|
@@ -769,6 +781,7 @@ class MlrunProject(ModelObj):
|
|
|
769
781
|
|
|
770
782
|
def __init__(
|
|
771
783
|
self,
|
|
784
|
+
# TODO: remove all arguments except metadata and spec in 1.6.0
|
|
772
785
|
name=None,
|
|
773
786
|
description=None,
|
|
774
787
|
params=None,
|
|
@@ -777,7 +790,7 @@ class MlrunProject(ModelObj):
|
|
|
777
790
|
artifacts=None,
|
|
778
791
|
artifact_path=None,
|
|
779
792
|
conda=None,
|
|
780
|
-
# all except these
|
|
793
|
+
# all except these metadata and spec are for backwards compatibility with MlrunProjectLegacy
|
|
781
794
|
metadata=None,
|
|
782
795
|
spec=None,
|
|
783
796
|
default_requirements: typing.Union[str, typing.List[str]] = None,
|
|
@@ -789,6 +802,26 @@ class MlrunProject(ModelObj):
|
|
|
789
802
|
self._status = None
|
|
790
803
|
self.status = None
|
|
791
804
|
|
|
805
|
+
if any(
|
|
806
|
+
[
|
|
807
|
+
name,
|
|
808
|
+
description,
|
|
809
|
+
params,
|
|
810
|
+
functions,
|
|
811
|
+
workflows,
|
|
812
|
+
artifacts,
|
|
813
|
+
artifact_path,
|
|
814
|
+
conda,
|
|
815
|
+
default_requirements,
|
|
816
|
+
]
|
|
817
|
+
):
|
|
818
|
+
# TODO: remove in 1.6.0 along with all arguments except metadata and spec
|
|
819
|
+
warnings.warn(
|
|
820
|
+
"Project constructor arguments are deprecated in 1.4.0 and will be removed in 1.6.0,"
|
|
821
|
+
" use metadata and spec instead",
|
|
822
|
+
FutureWarning,
|
|
823
|
+
)
|
|
824
|
+
|
|
792
825
|
# Handling the fields given in the legacy way
|
|
793
826
|
self.metadata.name = name or self.metadata.name
|
|
794
827
|
self.spec.description = description or self.spec.description
|
|
@@ -1436,12 +1469,15 @@ class MlrunProject(ModelObj):
|
|
|
1436
1469
|
with open(f"{temp_dir}/_body", "rb") as fp:
|
|
1437
1470
|
artifact.spec._body = fp.read()
|
|
1438
1471
|
artifact.target_path = ""
|
|
1472
|
+
|
|
1473
|
+
# if the dataitem is not a file, it means we downloaded it from a remote source to a temp file,
|
|
1474
|
+
# so we need to remove it after we're done with it
|
|
1475
|
+
dataitem.remove_local()
|
|
1476
|
+
|
|
1439
1477
|
return self.log_artifact(
|
|
1440
1478
|
artifact, local_path=temp_dir, artifact_path=artifact_path
|
|
1441
1479
|
)
|
|
1442
1480
|
|
|
1443
|
-
if dataitem.kind != "file":
|
|
1444
|
-
remove(item_file)
|
|
1445
1481
|
else:
|
|
1446
1482
|
raise ValueError("unsupported file suffix, use .yaml, .json, or .zip")
|
|
1447
1483
|
|
|
@@ -1489,7 +1525,7 @@ class MlrunProject(ModelObj):
|
|
|
1489
1525
|
|
|
1490
1526
|
object (s3://, v3io://, ..)
|
|
1491
1527
|
MLRun DB e.g. db://project/func:ver
|
|
1492
|
-
functions hub/market: e.g. hub://
|
|
1528
|
+
functions hub/market: e.g. hub://auto-trainer:master
|
|
1493
1529
|
|
|
1494
1530
|
examples::
|
|
1495
1531
|
|
|
@@ -1536,6 +1572,7 @@ class MlrunProject(ModelObj):
|
|
|
1536
1572
|
func = path.relpath(func, self.spec.context)
|
|
1537
1573
|
|
|
1538
1574
|
func = func or ""
|
|
1575
|
+
name = mlrun.utils.normalize_name(name) if name else name
|
|
1539
1576
|
if isinstance(func, str):
|
|
1540
1577
|
# in hub or db functions name defaults to the function name
|
|
1541
1578
|
if not name and not (func.startswith("db://") or func.startswith("hub://")):
|
|
@@ -2151,6 +2188,7 @@ class MlrunProject(ModelObj):
|
|
|
2151
2188
|
auto_build: bool = None,
|
|
2152
2189
|
schedule: typing.Union[str, mlrun.api.schemas.ScheduleCronTrigger] = None,
|
|
2153
2190
|
artifact_path: str = None,
|
|
2191
|
+
notifications: typing.List[mlrun.model.Notification] = None,
|
|
2154
2192
|
returns: Optional[List[Union[str, Dict[str, str]]]] = None,
|
|
2155
2193
|
) -> typing.Union[mlrun.model.RunObject, kfp.dsl.ContainerOp]:
|
|
2156
2194
|
"""Run a local or remote task as part of a local/kubeflow pipeline
|
|
@@ -2160,7 +2198,7 @@ class MlrunProject(ModelObj):
|
|
|
2160
2198
|
# create a project with two functions (local and from marketplace)
|
|
2161
2199
|
project = mlrun.new_project(project_name, "./proj")
|
|
2162
2200
|
project.set_function("mycode.py", "myfunc", image="mlrun/mlrun")
|
|
2163
|
-
project.set_function("hub://
|
|
2201
|
+
project.set_function("hub://auto-trainer", "train")
|
|
2164
2202
|
|
|
2165
2203
|
# run functions (refer to them by name)
|
|
2166
2204
|
run1 = project.run_function("myfunc", params={"x": 7})
|
|
@@ -2192,6 +2230,7 @@ class MlrunProject(ModelObj):
|
|
|
2192
2230
|
see this link for help:
|
|
2193
2231
|
https://apscheduler.readthedocs.io/en/3.x/modules/triggers/cron.html#module-apscheduler.triggers.cron
|
|
2194
2232
|
:param artifact_path: path to store artifacts, when running in a workflow this will be set automatically
|
|
2233
|
+
:param notifications: list of notifications to push when the run is completed
|
|
2195
2234
|
:param returns: List of log hints - configurations for how to log the returning values from the
|
|
2196
2235
|
handler's run (as artifacts or results). The list's length must be equal to the amount
|
|
2197
2236
|
of returning objects. A log hint may be given as:
|
|
@@ -2226,6 +2265,7 @@ class MlrunProject(ModelObj):
|
|
|
2226
2265
|
auto_build=auto_build,
|
|
2227
2266
|
schedule=schedule,
|
|
2228
2267
|
artifact_path=artifact_path,
|
|
2268
|
+
notifications=notifications,
|
|
2229
2269
|
returns=returns,
|
|
2230
2270
|
)
|
|
2231
2271
|
|
mlrun/run.py
CHANGED
|
@@ -57,7 +57,6 @@ from .runtimes import (
|
|
|
57
57
|
RemoteSparkRuntime,
|
|
58
58
|
RuntimeKinds,
|
|
59
59
|
ServingRuntime,
|
|
60
|
-
Spark2Runtime,
|
|
61
60
|
Spark3Runtime,
|
|
62
61
|
get_runtime_class,
|
|
63
62
|
)
|
|
@@ -127,6 +126,7 @@ def run_local(
|
|
|
127
126
|
artifact_path: str = "",
|
|
128
127
|
mode: str = None,
|
|
129
128
|
allow_empty_resources=None,
|
|
129
|
+
notifications: List[mlrun.model.Notification] = None,
|
|
130
130
|
returns: list = None,
|
|
131
131
|
):
|
|
132
132
|
"""Run a task on function/code (.py, .ipynb or .yaml) locally,
|
|
@@ -216,6 +216,7 @@ def run_local(
|
|
|
216
216
|
inputs=inputs,
|
|
217
217
|
returns=returns,
|
|
218
218
|
artifact_path=artifact_path,
|
|
219
|
+
notifications=notifications,
|
|
219
220
|
)
|
|
220
221
|
|
|
221
222
|
|
|
@@ -235,7 +236,7 @@ def function_to_module(code="", workdir=None, secrets=None, silent=False):
|
|
|
235
236
|
mod.my_job(context, p1=1, p2='x')
|
|
236
237
|
print(context.to_yaml())
|
|
237
238
|
|
|
238
|
-
fn = mlrun.import_function('hub://
|
|
239
|
+
fn = mlrun.import_function('hub://open-archive')
|
|
239
240
|
mod = mlrun.function_to_module(fn)
|
|
240
241
|
data = mlrun.run.get_dataitem("https://fpsignals-public.s3.amazonaws.com/catsndogs.tar.gz")
|
|
241
242
|
context = mlrun.get_or_create_ctx('myfunc')
|
|
@@ -457,7 +458,7 @@ def import_function(url="", secrets=None, db="", project=None, new_name=None):
|
|
|
457
458
|
|
|
458
459
|
examples::
|
|
459
460
|
|
|
460
|
-
function = mlrun.import_function("hub://
|
|
461
|
+
function = mlrun.import_function("hub://auto-trainer")
|
|
461
462
|
function = mlrun.import_function("./func.yaml")
|
|
462
463
|
function = mlrun.import_function("https://raw.githubusercontent.com/org/repo/func.yaml")
|
|
463
464
|
|
|
@@ -700,7 +701,6 @@ def code_to_function(
|
|
|
700
701
|
DaskCluster,
|
|
701
702
|
KubejobRuntime,
|
|
702
703
|
LocalRuntime,
|
|
703
|
-
Spark2Runtime,
|
|
704
704
|
Spark3Runtime,
|
|
705
705
|
RemoteSparkRuntime,
|
|
706
706
|
]:
|
|
@@ -793,6 +793,7 @@ def code_to_function(
|
|
|
793
793
|
|
|
794
794
|
def update_common(fn, spec):
|
|
795
795
|
fn.spec.image = image or get_in(spec, "spec.image", "")
|
|
796
|
+
fn.spec.filename = filename or get_in(spec, "spec.filename", "")
|
|
796
797
|
fn.spec.build.base_image = get_in(spec, "spec.build.baseImage")
|
|
797
798
|
fn.spec.build.commands = get_in(spec, "spec.build.commands")
|
|
798
799
|
fn.spec.build.secret = get_in(spec, "spec.build.secret")
|
mlrun/runtimes/__init__.py
CHANGED
|
@@ -47,7 +47,7 @@ from .mpijob import ( # noqa
|
|
|
47
47
|
from .nuclio import nuclio_init_hook
|
|
48
48
|
from .remotesparkjob import RemoteSparkRuntime, RemoteSparkRuntimeHandler
|
|
49
49
|
from .serving import ServingRuntime, new_v2_model_server
|
|
50
|
-
from .sparkjob import
|
|
50
|
+
from .sparkjob import Spark3Runtime, SparkRuntimeHandler
|
|
51
51
|
|
|
52
52
|
# for legacy imports (MLModelServer moved from here to /serving)
|
|
53
53
|
from ..serving import MLModelServer, new_v1_model_server # noqa isort: skip
|
|
@@ -260,11 +260,7 @@ def get_runtime_class(kind: str):
|
|
|
260
260
|
return crd_version_to_runtime[mpijob_crd_version]
|
|
261
261
|
|
|
262
262
|
if kind == RuntimeKinds.spark:
|
|
263
|
-
|
|
264
|
-
if spark_operator_version == 2:
|
|
265
|
-
return Spark2Runtime
|
|
266
|
-
elif spark_operator_version == 3:
|
|
267
|
-
return Spark3Runtime
|
|
263
|
+
return Spark3Runtime
|
|
268
264
|
|
|
269
265
|
kind_runtime_map = {
|
|
270
266
|
RuntimeKinds.remote: RemoteRuntime,
|