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/runtimes/base.py
CHANGED
|
@@ -15,7 +15,6 @@ import enum
|
|
|
15
15
|
import getpass
|
|
16
16
|
import http
|
|
17
17
|
import os.path
|
|
18
|
-
import shlex
|
|
19
18
|
import traceback
|
|
20
19
|
import typing
|
|
21
20
|
import uuid
|
|
@@ -33,8 +32,11 @@ from kubernetes.client.rest import ApiException
|
|
|
33
32
|
from nuclio.build import mlrun_footer
|
|
34
33
|
from sqlalchemy.orm import Session
|
|
35
34
|
|
|
35
|
+
import mlrun.api.db.sqldb.session
|
|
36
|
+
import mlrun.api.utils.singletons.db
|
|
36
37
|
import mlrun.errors
|
|
37
38
|
import mlrun.utils.helpers
|
|
39
|
+
import mlrun.utils.notifications
|
|
38
40
|
import mlrun.utils.regex
|
|
39
41
|
from mlrun.api import schemas
|
|
40
42
|
from mlrun.api.constants import LogSources
|
|
@@ -345,6 +347,7 @@ class BaseRuntime(ModelObj):
|
|
|
345
347
|
local_code_path=None,
|
|
346
348
|
auto_build=None,
|
|
347
349
|
param_file_secrets: Dict[str, str] = None,
|
|
350
|
+
notifications: List[mlrun.model.Notification] = None,
|
|
348
351
|
returns: Optional[List[Union[str, Dict[str, str]]]] = None,
|
|
349
352
|
) -> RunObject:
|
|
350
353
|
"""
|
|
@@ -379,6 +382,7 @@ class BaseRuntime(ModelObj):
|
|
|
379
382
|
function run, use only if you dont plan on changing the build config between runs
|
|
380
383
|
:param param_file_secrets: dictionary of secrets to be used only for accessing the hyper-param parameter file.
|
|
381
384
|
These secrets are only used locally and will not be stored anywhere
|
|
385
|
+
:param notifications: list of notifications to push when the run is completed
|
|
382
386
|
:param returns: List of log hints - configurations for how to log the returning values from the handler's run
|
|
383
387
|
(as artifacts or results). The list's length must be equal to the amount of returning objects. A
|
|
384
388
|
log hint may be given as:
|
|
@@ -408,7 +412,7 @@ class BaseRuntime(ModelObj):
|
|
|
408
412
|
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
409
413
|
"local and schedule cannot be used together"
|
|
410
414
|
)
|
|
411
|
-
|
|
415
|
+
result = self._run_local(
|
|
412
416
|
run,
|
|
413
417
|
local_code_path,
|
|
414
418
|
project,
|
|
@@ -419,7 +423,10 @@ class BaseRuntime(ModelObj):
|
|
|
419
423
|
inputs,
|
|
420
424
|
returns,
|
|
421
425
|
artifact_path,
|
|
426
|
+
notifications=notifications,
|
|
422
427
|
)
|
|
428
|
+
self._save_or_push_notifications(result, local)
|
|
429
|
+
return result
|
|
423
430
|
|
|
424
431
|
run = self._enrich_run(
|
|
425
432
|
run,
|
|
@@ -436,6 +443,7 @@ class BaseRuntime(ModelObj):
|
|
|
436
443
|
out_path,
|
|
437
444
|
artifact_path,
|
|
438
445
|
workdir,
|
|
446
|
+
notifications,
|
|
439
447
|
)
|
|
440
448
|
self._validate_output_path(run)
|
|
441
449
|
db = self._get_db()
|
|
@@ -533,6 +541,8 @@ class BaseRuntime(ModelObj):
|
|
|
533
541
|
last_err = err
|
|
534
542
|
result = self._update_run_state(task=run, err=err)
|
|
535
543
|
|
|
544
|
+
self._save_or_push_notifications(run)
|
|
545
|
+
|
|
536
546
|
self._post_run(result, execution) # hook for runtime specific cleanup
|
|
537
547
|
|
|
538
548
|
return self._wrap_run_result(result, run, schedule=schedule, err=last_err)
|
|
@@ -630,6 +640,7 @@ class BaseRuntime(ModelObj):
|
|
|
630
640
|
inputs,
|
|
631
641
|
returns,
|
|
632
642
|
artifact_path,
|
|
643
|
+
notifications: List[mlrun.model.Notification] = None,
|
|
633
644
|
):
|
|
634
645
|
# allow local run simulation with a flip of a flag
|
|
635
646
|
command = self
|
|
@@ -650,6 +661,7 @@ class BaseRuntime(ModelObj):
|
|
|
650
661
|
artifact_path=artifact_path,
|
|
651
662
|
mode=self.spec.mode,
|
|
652
663
|
allow_empty_resources=self.spec.allow_empty_resources,
|
|
664
|
+
notifications=notifications,
|
|
653
665
|
returns=returns,
|
|
654
666
|
)
|
|
655
667
|
|
|
@@ -688,6 +700,7 @@ class BaseRuntime(ModelObj):
|
|
|
688
700
|
out_path,
|
|
689
701
|
artifact_path,
|
|
690
702
|
workdir,
|
|
703
|
+
notifications: List[mlrun.model.Notification] = None,
|
|
691
704
|
):
|
|
692
705
|
runspec.spec.handler = (
|
|
693
706
|
handler or runspec.spec.handler or self.spec.default_handler or ""
|
|
@@ -789,6 +802,8 @@ class BaseRuntime(ModelObj):
|
|
|
789
802
|
runspec.spec.output_path = mlrun.utils.helpers.fill_artifact_path_template(
|
|
790
803
|
runspec.spec.output_path, runspec.metadata.project
|
|
791
804
|
)
|
|
805
|
+
|
|
806
|
+
runspec.spec.notifications = notifications or runspec.spec.notifications or []
|
|
792
807
|
return runspec
|
|
793
808
|
|
|
794
809
|
def _submit_job(self, run: RunObject, schedule, db, watch):
|
|
@@ -1049,6 +1064,47 @@ class BaseRuntime(ModelObj):
|
|
|
1049
1064
|
|
|
1050
1065
|
return resp
|
|
1051
1066
|
|
|
1067
|
+
def _save_or_push_notifications(self, runobj: RunObject, local: bool = False):
|
|
1068
|
+
|
|
1069
|
+
if not runobj.spec.notifications:
|
|
1070
|
+
logger.debug(
|
|
1071
|
+
"No notifications to push for run", run_uid=runobj.metadata.uid
|
|
1072
|
+
)
|
|
1073
|
+
return
|
|
1074
|
+
|
|
1075
|
+
# TODO: add support for other notifications per run iteration
|
|
1076
|
+
if runobj.metadata.iteration and runobj.metadata.iteration > 0:
|
|
1077
|
+
logger.debug(
|
|
1078
|
+
"Notifications per iteration are not supported, skipping",
|
|
1079
|
+
run_uid=runobj.metadata.uid,
|
|
1080
|
+
)
|
|
1081
|
+
return
|
|
1082
|
+
|
|
1083
|
+
# If the run is remote, and we are in the SDK, we let the api deal with the notifications
|
|
1084
|
+
# so there's nothing to do here.
|
|
1085
|
+
# Otherwise, we continue on.
|
|
1086
|
+
if is_running_as_api():
|
|
1087
|
+
|
|
1088
|
+
# import here to avoid circular imports and to avoid importing api requirements
|
|
1089
|
+
from mlrun.api.crud import Notifications
|
|
1090
|
+
|
|
1091
|
+
# If in the api server, we can assume that watch=False, so we save notification
|
|
1092
|
+
# configs to the DB, for the run monitor to later pick up and push.
|
|
1093
|
+
session = mlrun.api.db.sqldb.session.create_session()
|
|
1094
|
+
Notifications().store_run_notifications(
|
|
1095
|
+
session,
|
|
1096
|
+
runobj.spec.notifications,
|
|
1097
|
+
runobj.metadata.uid,
|
|
1098
|
+
runobj.metadata.project,
|
|
1099
|
+
)
|
|
1100
|
+
|
|
1101
|
+
elif local:
|
|
1102
|
+
# If the run is local, we can assume that watch=True, therefore this code runs
|
|
1103
|
+
# once the run is completed, and we can just push the notifications.
|
|
1104
|
+
# TODO: add store_notifications API endpoint so we can store notifications pushed from the
|
|
1105
|
+
# SDK for documentation purposes.
|
|
1106
|
+
mlrun.utils.notifications.NotificationPusher([runobj]).push()
|
|
1107
|
+
|
|
1052
1108
|
def _force_handler(self, handler):
|
|
1053
1109
|
if not handler:
|
|
1054
1110
|
raise RunError(f"handler must be provided for {self.kind} runtime")
|
|
@@ -1220,15 +1276,19 @@ class BaseRuntime(ModelObj):
|
|
|
1220
1276
|
:param verify_base_image: verify that the base image is configured
|
|
1221
1277
|
:return: function object
|
|
1222
1278
|
"""
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
# make sure we
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1279
|
+
resolved_requirements = self._resolve_requirements(requirements)
|
|
1280
|
+
requirements = self.spec.build.requirements or [] if not overwrite else []
|
|
1281
|
+
|
|
1282
|
+
# make sure we don't append the same line twice
|
|
1283
|
+
for requirement in resolved_requirements:
|
|
1284
|
+
if requirement not in requirements:
|
|
1285
|
+
requirements.append(requirement)
|
|
1286
|
+
|
|
1287
|
+
self.spec.build.requirements = requirements
|
|
1288
|
+
|
|
1230
1289
|
if verify_base_image:
|
|
1231
1290
|
self.verify_base_image()
|
|
1291
|
+
|
|
1232
1292
|
return self
|
|
1233
1293
|
|
|
1234
1294
|
def with_commands(
|
|
@@ -1270,8 +1330,10 @@ class BaseRuntime(ModelObj):
|
|
|
1270
1330
|
|
|
1271
1331
|
def verify_base_image(self):
|
|
1272
1332
|
build = self.spec.build
|
|
1273
|
-
require_build =
|
|
1274
|
-
build.
|
|
1333
|
+
require_build = (
|
|
1334
|
+
build.commands
|
|
1335
|
+
or build.requirements
|
|
1336
|
+
or (build.source and not build.load_source_on_run)
|
|
1275
1337
|
)
|
|
1276
1338
|
image = self.spec.image
|
|
1277
1339
|
# we allow users to not set an image, in that case we'll use the default
|
|
@@ -1396,15 +1458,16 @@ class BaseRuntime(ModelObj):
|
|
|
1396
1458
|
line += f", default={p['default']}"
|
|
1397
1459
|
print(" " + line)
|
|
1398
1460
|
|
|
1399
|
-
|
|
1400
|
-
|
|
1461
|
+
@staticmethod
|
|
1462
|
+
def _resolve_requirements(requirements_to_resolve: typing.Union[str, list]) -> list:
|
|
1401
1463
|
# if a string, read the file then encode
|
|
1402
|
-
if isinstance(
|
|
1403
|
-
with open(
|
|
1404
|
-
|
|
1464
|
+
if isinstance(requirements_to_resolve, str):
|
|
1465
|
+
with open(requirements_to_resolve, "r") as fp:
|
|
1466
|
+
requirements_to_resolve = fp.read().splitlines()
|
|
1405
1467
|
|
|
1406
1468
|
requirements = []
|
|
1407
|
-
for requirement in
|
|
1469
|
+
for requirement in requirements_to_resolve:
|
|
1470
|
+
# clean redundant leading and trailing whitespaces
|
|
1408
1471
|
requirement = requirement.strip()
|
|
1409
1472
|
|
|
1410
1473
|
# ignore empty lines
|
|
@@ -1417,21 +1480,9 @@ class BaseRuntime(ModelObj):
|
|
|
1417
1480
|
if len(inline_comment) > 1:
|
|
1418
1481
|
requirement = inline_comment[0].strip()
|
|
1419
1482
|
|
|
1420
|
-
|
|
1421
|
-
# we allow such flags (could be passed within the requirements.txt file) and do not
|
|
1422
|
-
# try to open the file and include its content since it might be a remote file
|
|
1423
|
-
# given on the base image.
|
|
1424
|
-
for req_flag in ["-r", "--requirement"]:
|
|
1425
|
-
if requirement.startswith(req_flag):
|
|
1426
|
-
requirement = requirement[len(req_flag) :].strip()
|
|
1427
|
-
requirements.append(req_flag)
|
|
1428
|
-
break
|
|
1429
|
-
|
|
1430
|
-
# wrap in single quote to ensure that the requirement is treated as a single string
|
|
1431
|
-
# quote the requirement to avoid issues with special characters, double quotes, etc.
|
|
1432
|
-
requirements.append(shlex.quote(requirement))
|
|
1483
|
+
requirements.append(requirement)
|
|
1433
1484
|
|
|
1434
|
-
return
|
|
1485
|
+
return requirements
|
|
1435
1486
|
|
|
1436
1487
|
def _validate_output_path(self, run):
|
|
1437
1488
|
if is_local(run.spec.output_path):
|
mlrun/runtimes/function.py
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import asyncio
|
|
16
16
|
import json
|
|
17
|
+
import shlex
|
|
17
18
|
import typing
|
|
18
19
|
import warnings
|
|
19
20
|
from base64 import b64encode
|
|
@@ -1389,6 +1390,27 @@ def compile_function_config(
|
|
|
1389
1390
|
config=function.spec.config,
|
|
1390
1391
|
)
|
|
1391
1392
|
nuclio_spec.cmd = function.spec.build.commands or []
|
|
1393
|
+
|
|
1394
|
+
if function.spec.build.requirements:
|
|
1395
|
+
resolved_requirements = []
|
|
1396
|
+
# wrap in single quote to ensure that the requirement is treated as a single string
|
|
1397
|
+
# quote the requirement to avoid issues with special characters, double quotes, etc.
|
|
1398
|
+
for requirement in function.spec.build.requirements:
|
|
1399
|
+
# -r / --requirement are flags and should not be escaped
|
|
1400
|
+
# we allow such flags (could be passed within the requirements.txt file) and do not
|
|
1401
|
+
# try to open the file and include its content since it might be a remote file
|
|
1402
|
+
# given on the base image.
|
|
1403
|
+
for req_flag in ["-r", "--requirement"]:
|
|
1404
|
+
if requirement.startswith(req_flag):
|
|
1405
|
+
requirement = requirement[len(req_flag) :].strip()
|
|
1406
|
+
resolved_requirements.append(req_flag)
|
|
1407
|
+
break
|
|
1408
|
+
|
|
1409
|
+
resolved_requirements.append(shlex.quote(requirement))
|
|
1410
|
+
|
|
1411
|
+
encoded_requirements = " ".join(resolved_requirements)
|
|
1412
|
+
nuclio_spec.cmd.append(f"python -m pip install {encoded_requirements}")
|
|
1413
|
+
|
|
1392
1414
|
project = function.metadata.project or "default"
|
|
1393
1415
|
tag = function.metadata.tag
|
|
1394
1416
|
handler = function.spec.function_handler
|
mlrun/runtimes/kubejob.py
CHANGED
|
@@ -137,16 +137,12 @@ class KubejobRuntime(KubeResource):
|
|
|
137
137
|
self.spec.build.image = image
|
|
138
138
|
if base_image:
|
|
139
139
|
self.spec.build.base_image = base_image
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (requirements or commands) and overwrite:
|
|
143
|
-
self.spec.build.commands = None
|
|
140
|
+
if commands:
|
|
141
|
+
self.with_commands(commands, overwrite=overwrite, verify_base_image=False)
|
|
144
142
|
if requirements:
|
|
145
143
|
self.with_requirements(
|
|
146
|
-
requirements, overwrite=
|
|
144
|
+
requirements, overwrite=overwrite, verify_base_image=False
|
|
147
145
|
)
|
|
148
|
-
if commands:
|
|
149
|
-
self.with_commands(commands, overwrite=False, verify_base_image=False)
|
|
150
146
|
if extra:
|
|
151
147
|
self.spec.build.extra = extra
|
|
152
148
|
if secret is not None:
|
|
@@ -198,7 +194,13 @@ class KubejobRuntime(KubeResource):
|
|
|
198
194
|
or "/mlrun/" in build.base_image
|
|
199
195
|
)
|
|
200
196
|
|
|
201
|
-
if
|
|
197
|
+
if (
|
|
198
|
+
not build.source
|
|
199
|
+
and not build.commands
|
|
200
|
+
and not build.requirements
|
|
201
|
+
and not build.extra
|
|
202
|
+
and with_mlrun
|
|
203
|
+
):
|
|
202
204
|
logger.info(
|
|
203
205
|
"running build to add mlrun package, set "
|
|
204
206
|
"with_mlrun=False to skip if its already in the image"
|
mlrun/runtimes/serving.py
CHANGED
|
@@ -319,7 +319,7 @@ class ServingRuntime(RemoteRuntime):
|
|
|
319
319
|
example::
|
|
320
320
|
|
|
321
321
|
# initialize a new serving function
|
|
322
|
-
serving_fn = mlrun.import_function("hub://
|
|
322
|
+
serving_fn = mlrun.import_function("hub://v2-model-server", new_name="serving")
|
|
323
323
|
# apply model monitoring and set monitoring batch job to run every 3 hours
|
|
324
324
|
tracking_policy = {'default_batch_intervals':"0 */3 * * *"}
|
|
325
325
|
serving_fn.set_tracking(tracking_policy=tracking_policy)
|
|
@@ -963,8 +963,6 @@ class SparkRuntimeHandler(BaseRuntimeHandler):
|
|
|
963
963
|
"""
|
|
964
964
|
Handling config maps deletion
|
|
965
965
|
"""
|
|
966
|
-
if grace_period is None:
|
|
967
|
-
grace_period = config.runtime_resources_deletion_grace_period
|
|
968
966
|
uids = []
|
|
969
967
|
for crd_dict in deleted_resources:
|
|
970
968
|
uid = crd_dict["metadata"].get("labels", {}).get("mlrun/uid", None)
|
mlrun/serving/states.py
CHANGED
|
@@ -28,7 +28,7 @@ from ..errors import MLRunInvalidArgumentError, err_to_str
|
|
|
28
28
|
from ..model import ModelObj, ObjectDict
|
|
29
29
|
from ..platforms.iguazio import parse_path
|
|
30
30
|
from ..utils import get_class, get_function
|
|
31
|
-
from .utils import _extract_input_data, _update_result_body
|
|
31
|
+
from .utils import StepToDict, _extract_input_data, _update_result_body
|
|
32
32
|
|
|
33
33
|
callable_prefix = "_"
|
|
34
34
|
path_splitter = "/"
|
|
@@ -279,7 +279,7 @@ class BaseStep(ModelObj):
|
|
|
279
279
|
|
|
280
280
|
def to(
|
|
281
281
|
self,
|
|
282
|
-
class_name: Union[str,
|
|
282
|
+
class_name: Union[str, StepToDict] = None,
|
|
283
283
|
name: str = None,
|
|
284
284
|
handler: str = None,
|
|
285
285
|
graph_shape: str = None,
|
mlrun/utils/helpers.py
CHANGED
|
@@ -1000,7 +1000,7 @@ def create_class(pkg_class: str):
|
|
|
1000
1000
|
return class_
|
|
1001
1001
|
|
|
1002
1002
|
|
|
1003
|
-
def create_function(pkg_func:
|
|
1003
|
+
def create_function(pkg_func: str):
|
|
1004
1004
|
"""Create a function from a package.module.function string
|
|
1005
1005
|
|
|
1006
1006
|
:param pkg_func: full function location,
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import enum
|
|
16
16
|
import typing
|
|
17
17
|
|
|
18
|
-
from .base import NotificationBase
|
|
18
|
+
from .base import NotificationBase
|
|
19
19
|
from .console import ConsoleNotification
|
|
20
20
|
from .git import GitNotification
|
|
21
21
|
from .ipython import IPythonNotification
|
|
@@ -12,35 +12,31 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
import enum
|
|
16
15
|
import typing
|
|
17
16
|
|
|
17
|
+
import mlrun.api.schemas
|
|
18
18
|
import mlrun.lists
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
class NotificationSeverity(str, enum.Enum):
|
|
22
|
-
INFO = "info"
|
|
23
|
-
DEBUG = "debug"
|
|
24
|
-
VERBOSE = "verbose"
|
|
25
|
-
WARNING = "warning"
|
|
26
|
-
ERROR = "error"
|
|
27
|
-
|
|
28
|
-
|
|
29
21
|
class NotificationBase:
|
|
30
22
|
def __init__(
|
|
31
23
|
self,
|
|
24
|
+
name: str = None,
|
|
32
25
|
params: typing.Dict[str, str] = None,
|
|
33
26
|
):
|
|
27
|
+
self.name = name
|
|
34
28
|
self.params = params or {}
|
|
35
29
|
|
|
36
30
|
@property
|
|
37
31
|
def active(self) -> bool:
|
|
38
32
|
return True
|
|
39
33
|
|
|
40
|
-
def
|
|
34
|
+
def push(
|
|
41
35
|
self,
|
|
42
36
|
message: str,
|
|
43
|
-
severity: typing.Union[
|
|
37
|
+
severity: typing.Union[
|
|
38
|
+
mlrun.api.schemas.NotificationSeverity, str
|
|
39
|
+
] = mlrun.api.schemas.NotificationSeverity.INFO,
|
|
44
40
|
runs: typing.Union[mlrun.lists.RunList, list] = None,
|
|
45
41
|
custom_html: str = None,
|
|
46
42
|
):
|
|
@@ -52,16 +48,21 @@ class NotificationBase:
|
|
|
52
48
|
) -> None:
|
|
53
49
|
self.params = params or {}
|
|
54
50
|
|
|
55
|
-
@staticmethod
|
|
56
51
|
def _get_html(
|
|
52
|
+
self,
|
|
57
53
|
message: str,
|
|
58
|
-
severity: typing.Union[
|
|
54
|
+
severity: typing.Union[
|
|
55
|
+
mlrun.api.schemas.NotificationSeverity, str
|
|
56
|
+
] = mlrun.api.schemas.NotificationSeverity.INFO,
|
|
59
57
|
runs: typing.Union[mlrun.lists.RunList, list] = None,
|
|
60
58
|
custom_html: str = None,
|
|
61
59
|
) -> str:
|
|
62
60
|
if custom_html:
|
|
63
61
|
return custom_html
|
|
64
62
|
|
|
63
|
+
if self.name:
|
|
64
|
+
message = f"{self.name}: {message}"
|
|
65
|
+
|
|
65
66
|
if not runs:
|
|
66
67
|
return f"[{severity}] {message}"
|
|
67
68
|
|
|
@@ -16,10 +16,11 @@ import typing
|
|
|
16
16
|
|
|
17
17
|
import tabulate
|
|
18
18
|
|
|
19
|
+
import mlrun.api.schemas
|
|
19
20
|
import mlrun.lists
|
|
20
21
|
import mlrun.utils.helpers
|
|
21
22
|
|
|
22
|
-
from .base import NotificationBase
|
|
23
|
+
from .base import NotificationBase
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
class ConsoleNotification(NotificationBase):
|
|
@@ -27,10 +28,12 @@ class ConsoleNotification(NotificationBase):
|
|
|
27
28
|
Client only notification for printing run status notifications in console
|
|
28
29
|
"""
|
|
29
30
|
|
|
30
|
-
def
|
|
31
|
+
def push(
|
|
31
32
|
self,
|
|
32
33
|
message: str,
|
|
33
|
-
severity: typing.Union[
|
|
34
|
+
severity: typing.Union[
|
|
35
|
+
mlrun.api.schemas.NotificationSeverity, str
|
|
36
|
+
] = mlrun.api.schemas.NotificationSeverity.INFO,
|
|
34
37
|
runs: typing.Union[mlrun.lists.RunList, list] = None,
|
|
35
38
|
custom_html: str = None,
|
|
36
39
|
):
|
|
@@ -16,12 +16,13 @@ import json
|
|
|
16
16
|
import os
|
|
17
17
|
import typing
|
|
18
18
|
|
|
19
|
-
import
|
|
19
|
+
import aiohttp
|
|
20
20
|
|
|
21
|
+
import mlrun.api.schemas
|
|
21
22
|
import mlrun.errors
|
|
22
23
|
import mlrun.lists
|
|
23
24
|
|
|
24
|
-
from .base import NotificationBase
|
|
25
|
+
from .base import NotificationBase
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
class GitNotification(NotificationBase):
|
|
@@ -29,10 +30,12 @@ class GitNotification(NotificationBase):
|
|
|
29
30
|
API/Client notification for setting a rich run statuses git issue comment (github/gitlab)
|
|
30
31
|
"""
|
|
31
32
|
|
|
32
|
-
def
|
|
33
|
+
async def push(
|
|
33
34
|
self,
|
|
34
35
|
message: str,
|
|
35
|
-
severity: typing.Union[
|
|
36
|
+
severity: typing.Union[
|
|
37
|
+
mlrun.api.schemas.NotificationSeverity, str
|
|
38
|
+
] = mlrun.api.schemas.NotificationSeverity.INFO,
|
|
36
39
|
runs: typing.Union[mlrun.lists.RunList, list] = None,
|
|
37
40
|
custom_html: str = None,
|
|
38
41
|
):
|
|
@@ -45,7 +48,7 @@ class GitNotification(NotificationBase):
|
|
|
45
48
|
)
|
|
46
49
|
server = self.params.get("server", None)
|
|
47
50
|
gitlab = self.params.get("gitlab", False)
|
|
48
|
-
self._pr_comment(
|
|
51
|
+
await self._pr_comment(
|
|
49
52
|
self._get_html(message, severity, runs, custom_html),
|
|
50
53
|
git_repo,
|
|
51
54
|
git_issue,
|
|
@@ -55,7 +58,7 @@ class GitNotification(NotificationBase):
|
|
|
55
58
|
)
|
|
56
59
|
|
|
57
60
|
@staticmethod
|
|
58
|
-
def _pr_comment(
|
|
61
|
+
async def _pr_comment(
|
|
59
62
|
message: str,
|
|
60
63
|
repo: str = None,
|
|
61
64
|
issue: int = None,
|
|
@@ -111,9 +114,13 @@ class GitNotification(NotificationBase):
|
|
|
111
114
|
"Authorization": f"token {token}",
|
|
112
115
|
}
|
|
113
116
|
url = f"https://{server}/repos/{repo}/issues/{issue}/comments"
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
|
|
118
|
+
async with aiohttp.ClientSession() as session:
|
|
119
|
+
resp = await session.post(url, headers=headers, json={"body": message})
|
|
120
|
+
if not resp.ok:
|
|
121
|
+
resp_text = await resp.text()
|
|
122
|
+
raise mlrun.errors.MLRunBadRequestError(
|
|
123
|
+
f"Failed commenting on PR: {resp_text}", status=resp.status
|
|
124
|
+
)
|
|
125
|
+
data = await resp.json()
|
|
126
|
+
return data.get("id")
|
|
@@ -14,10 +14,11 @@
|
|
|
14
14
|
|
|
15
15
|
import typing
|
|
16
16
|
|
|
17
|
+
import mlrun.api.schemas
|
|
17
18
|
import mlrun.lists
|
|
18
19
|
import mlrun.utils.helpers
|
|
19
20
|
|
|
20
|
-
from .base import NotificationBase
|
|
21
|
+
from .base import NotificationBase
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
class IPythonNotification(NotificationBase):
|
|
@@ -45,10 +46,12 @@ class IPythonNotification(NotificationBase):
|
|
|
45
46
|
def active(self) -> bool:
|
|
46
47
|
return self._ipython is not None
|
|
47
48
|
|
|
48
|
-
def
|
|
49
|
+
def push(
|
|
49
50
|
self,
|
|
50
51
|
message: str,
|
|
51
|
-
severity: typing.Union[
|
|
52
|
+
severity: typing.Union[
|
|
53
|
+
mlrun.api.schemas.NotificationSeverity, str
|
|
54
|
+
] = mlrun.api.schemas.NotificationSeverity.INFO,
|
|
52
55
|
runs: typing.Union[mlrun.lists.RunList, list] = None,
|
|
53
56
|
custom_html: str = None,
|
|
54
57
|
):
|
|
@@ -12,15 +12,15 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
import json
|
|
16
15
|
import typing
|
|
17
16
|
|
|
18
|
-
import
|
|
17
|
+
import aiohttp
|
|
19
18
|
|
|
19
|
+
import mlrun.api.schemas
|
|
20
20
|
import mlrun.lists
|
|
21
21
|
import mlrun.utils.helpers
|
|
22
22
|
|
|
23
|
-
from .base import NotificationBase
|
|
23
|
+
from .base import NotificationBase
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class SlackNotification(NotificationBase):
|
|
@@ -34,10 +34,12 @@ class SlackNotification(NotificationBase):
|
|
|
34
34
|
"error": ":x:",
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
def
|
|
37
|
+
async def push(
|
|
38
38
|
self,
|
|
39
39
|
message: str,
|
|
40
|
-
severity: typing.Union[
|
|
40
|
+
severity: typing.Union[
|
|
41
|
+
mlrun.api.schemas.NotificationSeverity, str
|
|
42
|
+
] = mlrun.api.schemas.NotificationSeverity.INFO,
|
|
41
43
|
runs: typing.Union[mlrun.lists.RunList, list] = None,
|
|
42
44
|
custom_html: str = None,
|
|
43
45
|
):
|
|
@@ -53,17 +55,16 @@ class SlackNotification(NotificationBase):
|
|
|
53
55
|
|
|
54
56
|
data = self._generate_slack_data(message, severity, runs)
|
|
55
57
|
|
|
56
|
-
|
|
57
|
-
webhook,
|
|
58
|
-
|
|
59
|
-
headers={"Content-Type": "application/json"},
|
|
60
|
-
)
|
|
61
|
-
response.raise_for_status()
|
|
58
|
+
async with aiohttp.ClientSession() as session:
|
|
59
|
+
async with session.post(webhook, json=data) as response:
|
|
60
|
+
response.raise_for_status()
|
|
62
61
|
|
|
63
62
|
def _generate_slack_data(
|
|
64
63
|
self,
|
|
65
64
|
message: str,
|
|
66
|
-
severity: typing.Union[
|
|
65
|
+
severity: typing.Union[
|
|
66
|
+
mlrun.api.schemas.NotificationSeverity, str
|
|
67
|
+
] = mlrun.api.schemas.NotificationSeverity.INFO,
|
|
67
68
|
runs: typing.Union[mlrun.lists.RunList, list] = None,
|
|
68
69
|
) -> dict:
|
|
69
70
|
data = {
|