mlrun 1.7.2rc4__py3-none-any.whl → 1.8.0__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/__init__.py +26 -22
- mlrun/__main__.py +15 -16
- mlrun/alerts/alert.py +150 -15
- mlrun/api/schemas/__init__.py +1 -9
- mlrun/artifacts/__init__.py +2 -3
- mlrun/artifacts/base.py +62 -19
- mlrun/artifacts/dataset.py +17 -17
- mlrun/artifacts/document.py +454 -0
- mlrun/artifacts/manager.py +28 -18
- mlrun/artifacts/model.py +91 -59
- mlrun/artifacts/plots.py +2 -2
- mlrun/common/constants.py +8 -0
- mlrun/common/formatters/__init__.py +1 -0
- mlrun/common/formatters/artifact.py +1 -1
- mlrun/common/formatters/feature_set.py +2 -0
- mlrun/common/formatters/function.py +1 -0
- mlrun/{model_monitoring/db/stores/v3io_kv/__init__.py → common/formatters/model_endpoint.py} +17 -0
- mlrun/common/formatters/pipeline.py +1 -2
- mlrun/common/formatters/project.py +9 -0
- mlrun/common/model_monitoring/__init__.py +0 -5
- mlrun/common/model_monitoring/helpers.py +12 -62
- mlrun/common/runtimes/constants.py +25 -4
- mlrun/common/schemas/__init__.py +9 -5
- mlrun/common/schemas/alert.py +114 -19
- mlrun/common/schemas/api_gateway.py +3 -3
- mlrun/common/schemas/artifact.py +22 -9
- mlrun/common/schemas/auth.py +8 -4
- mlrun/common/schemas/background_task.py +7 -7
- mlrun/common/schemas/client_spec.py +4 -4
- mlrun/common/schemas/clusterization_spec.py +2 -2
- mlrun/common/schemas/common.py +53 -3
- mlrun/common/schemas/constants.py +15 -0
- mlrun/common/schemas/datastore_profile.py +1 -1
- mlrun/common/schemas/feature_store.py +9 -9
- mlrun/common/schemas/frontend_spec.py +4 -4
- mlrun/common/schemas/function.py +10 -10
- mlrun/common/schemas/hub.py +1 -1
- mlrun/common/schemas/k8s.py +3 -3
- mlrun/common/schemas/memory_reports.py +3 -3
- mlrun/common/schemas/model_monitoring/__init__.py +4 -8
- mlrun/common/schemas/model_monitoring/constants.py +127 -46
- mlrun/common/schemas/model_monitoring/grafana.py +18 -12
- mlrun/common/schemas/model_monitoring/model_endpoints.py +154 -160
- mlrun/common/schemas/notification.py +24 -3
- mlrun/common/schemas/object.py +1 -1
- mlrun/common/schemas/pagination.py +4 -4
- mlrun/common/schemas/partition.py +142 -0
- mlrun/common/schemas/pipeline.py +3 -3
- mlrun/common/schemas/project.py +26 -18
- mlrun/common/schemas/runs.py +3 -3
- mlrun/common/schemas/runtime_resource.py +5 -5
- mlrun/common/schemas/schedule.py +1 -1
- mlrun/common/schemas/secret.py +1 -1
- mlrun/{model_monitoring/db/stores/sqldb/__init__.py → common/schemas/serving.py} +10 -1
- mlrun/common/schemas/tag.py +3 -3
- mlrun/common/schemas/workflow.py +6 -5
- mlrun/common/types.py +1 -0
- mlrun/config.py +157 -89
- mlrun/data_types/__init__.py +5 -3
- mlrun/data_types/infer.py +13 -3
- mlrun/data_types/spark.py +2 -1
- mlrun/datastore/__init__.py +59 -18
- mlrun/datastore/alibaba_oss.py +4 -1
- mlrun/datastore/azure_blob.py +4 -1
- mlrun/datastore/base.py +19 -24
- mlrun/datastore/datastore.py +10 -4
- mlrun/datastore/datastore_profile.py +178 -45
- mlrun/datastore/dbfs_store.py +4 -1
- mlrun/datastore/filestore.py +4 -1
- mlrun/datastore/google_cloud_storage.py +4 -1
- mlrun/datastore/hdfs.py +4 -1
- mlrun/datastore/inmem.py +4 -1
- mlrun/datastore/redis.py +4 -1
- mlrun/datastore/s3.py +14 -3
- mlrun/datastore/sources.py +89 -92
- mlrun/datastore/store_resources.py +7 -4
- mlrun/datastore/storeytargets.py +51 -16
- mlrun/datastore/targets.py +38 -31
- mlrun/datastore/utils.py +87 -4
- mlrun/datastore/v3io.py +4 -1
- mlrun/datastore/vectorstore.py +291 -0
- mlrun/datastore/wasbfs/fs.py +13 -12
- mlrun/db/base.py +286 -100
- mlrun/db/httpdb.py +1562 -490
- mlrun/db/nopdb.py +250 -83
- mlrun/errors.py +6 -2
- mlrun/execution.py +194 -50
- mlrun/feature_store/__init__.py +2 -10
- mlrun/feature_store/api.py +20 -458
- mlrun/feature_store/common.py +9 -9
- mlrun/feature_store/feature_set.py +20 -18
- mlrun/feature_store/feature_vector.py +105 -479
- mlrun/feature_store/feature_vector_utils.py +466 -0
- mlrun/feature_store/retrieval/base.py +15 -11
- mlrun/feature_store/retrieval/job.py +2 -1
- mlrun/feature_store/retrieval/storey_merger.py +1 -1
- mlrun/feature_store/steps.py +3 -3
- mlrun/features.py +30 -13
- mlrun/frameworks/__init__.py +1 -2
- mlrun/frameworks/_common/__init__.py +1 -2
- mlrun/frameworks/_common/artifacts_library.py +2 -2
- mlrun/frameworks/_common/mlrun_interface.py +10 -6
- mlrun/frameworks/_common/model_handler.py +31 -31
- mlrun/frameworks/_common/producer.py +3 -1
- mlrun/frameworks/_dl_common/__init__.py +1 -2
- mlrun/frameworks/_dl_common/loggers/__init__.py +1 -2
- mlrun/frameworks/_dl_common/loggers/mlrun_logger.py +4 -4
- mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +3 -3
- mlrun/frameworks/_ml_common/__init__.py +1 -2
- mlrun/frameworks/_ml_common/loggers/__init__.py +1 -2
- mlrun/frameworks/_ml_common/model_handler.py +21 -21
- mlrun/frameworks/_ml_common/plans/__init__.py +1 -2
- mlrun/frameworks/_ml_common/plans/confusion_matrix_plan.py +3 -1
- mlrun/frameworks/_ml_common/plans/dataset_plan.py +3 -3
- mlrun/frameworks/_ml_common/plans/roc_curve_plan.py +4 -4
- mlrun/frameworks/auto_mlrun/__init__.py +1 -2
- mlrun/frameworks/auto_mlrun/auto_mlrun.py +22 -15
- mlrun/frameworks/huggingface/__init__.py +1 -2
- mlrun/frameworks/huggingface/model_server.py +9 -9
- mlrun/frameworks/lgbm/__init__.py +47 -44
- mlrun/frameworks/lgbm/callbacks/__init__.py +1 -2
- mlrun/frameworks/lgbm/callbacks/logging_callback.py +4 -2
- mlrun/frameworks/lgbm/callbacks/mlrun_logging_callback.py +4 -2
- mlrun/frameworks/lgbm/mlrun_interfaces/__init__.py +1 -2
- mlrun/frameworks/lgbm/mlrun_interfaces/mlrun_interface.py +5 -5
- mlrun/frameworks/lgbm/model_handler.py +15 -11
- mlrun/frameworks/lgbm/model_server.py +11 -7
- mlrun/frameworks/lgbm/utils.py +2 -2
- mlrun/frameworks/onnx/__init__.py +1 -2
- mlrun/frameworks/onnx/dataset.py +3 -3
- mlrun/frameworks/onnx/mlrun_interface.py +2 -2
- mlrun/frameworks/onnx/model_handler.py +7 -5
- mlrun/frameworks/onnx/model_server.py +8 -6
- mlrun/frameworks/parallel_coordinates.py +11 -11
- mlrun/frameworks/pytorch/__init__.py +22 -23
- mlrun/frameworks/pytorch/callbacks/__init__.py +1 -2
- mlrun/frameworks/pytorch/callbacks/callback.py +2 -1
- mlrun/frameworks/pytorch/callbacks/logging_callback.py +15 -8
- mlrun/frameworks/pytorch/callbacks/mlrun_logging_callback.py +19 -12
- mlrun/frameworks/pytorch/callbacks/tensorboard_logging_callback.py +22 -15
- mlrun/frameworks/pytorch/callbacks_handler.py +36 -30
- mlrun/frameworks/pytorch/mlrun_interface.py +17 -17
- mlrun/frameworks/pytorch/model_handler.py +21 -17
- mlrun/frameworks/pytorch/model_server.py +13 -9
- mlrun/frameworks/sklearn/__init__.py +19 -18
- mlrun/frameworks/sklearn/estimator.py +2 -2
- mlrun/frameworks/sklearn/metric.py +3 -3
- mlrun/frameworks/sklearn/metrics_library.py +8 -6
- mlrun/frameworks/sklearn/mlrun_interface.py +3 -2
- mlrun/frameworks/sklearn/model_handler.py +4 -3
- mlrun/frameworks/tf_keras/__init__.py +11 -12
- mlrun/frameworks/tf_keras/callbacks/__init__.py +1 -2
- mlrun/frameworks/tf_keras/callbacks/logging_callback.py +17 -14
- mlrun/frameworks/tf_keras/callbacks/mlrun_logging_callback.py +15 -12
- mlrun/frameworks/tf_keras/callbacks/tensorboard_logging_callback.py +21 -18
- mlrun/frameworks/tf_keras/model_handler.py +17 -13
- mlrun/frameworks/tf_keras/model_server.py +12 -8
- mlrun/frameworks/xgboost/__init__.py +19 -18
- mlrun/frameworks/xgboost/model_handler.py +13 -9
- mlrun/k8s_utils.py +2 -5
- mlrun/launcher/base.py +3 -4
- mlrun/launcher/client.py +2 -2
- mlrun/launcher/local.py +6 -2
- mlrun/launcher/remote.py +1 -1
- mlrun/lists.py +8 -4
- mlrun/model.py +132 -46
- mlrun/model_monitoring/__init__.py +3 -5
- mlrun/model_monitoring/api.py +113 -98
- mlrun/model_monitoring/applications/__init__.py +0 -5
- mlrun/model_monitoring/applications/_application_steps.py +81 -50
- mlrun/model_monitoring/applications/base.py +467 -14
- mlrun/model_monitoring/applications/context.py +212 -134
- mlrun/model_monitoring/{db/stores/base → applications/evidently}/__init__.py +6 -2
- mlrun/model_monitoring/applications/evidently/base.py +146 -0
- mlrun/model_monitoring/applications/histogram_data_drift.py +89 -56
- mlrun/model_monitoring/applications/results.py +67 -15
- mlrun/model_monitoring/controller.py +701 -315
- mlrun/model_monitoring/db/__init__.py +0 -2
- mlrun/model_monitoring/db/_schedules.py +242 -0
- mlrun/model_monitoring/db/_stats.py +189 -0
- mlrun/model_monitoring/db/tsdb/__init__.py +33 -22
- mlrun/model_monitoring/db/tsdb/base.py +243 -49
- mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +76 -36
- mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +33 -0
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connection.py +213 -0
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +534 -88
- mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +1 -0
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +436 -106
- mlrun/model_monitoring/helpers.py +356 -114
- mlrun/model_monitoring/stream_processing.py +190 -345
- mlrun/model_monitoring/tracking_policy.py +11 -4
- mlrun/model_monitoring/writer.py +49 -90
- mlrun/package/__init__.py +3 -6
- mlrun/package/context_handler.py +2 -2
- mlrun/package/packager.py +12 -9
- mlrun/package/packagers/__init__.py +0 -2
- mlrun/package/packagers/default_packager.py +14 -11
- mlrun/package/packagers/numpy_packagers.py +16 -7
- mlrun/package/packagers/pandas_packagers.py +18 -18
- mlrun/package/packagers/python_standard_library_packagers.py +25 -11
- mlrun/package/packagers_manager.py +35 -32
- mlrun/package/utils/__init__.py +0 -3
- mlrun/package/utils/_pickler.py +6 -6
- mlrun/platforms/__init__.py +47 -16
- mlrun/platforms/iguazio.py +4 -1
- mlrun/projects/operations.py +30 -30
- mlrun/projects/pipelines.py +116 -47
- mlrun/projects/project.py +1292 -329
- mlrun/render.py +5 -9
- mlrun/run.py +57 -14
- mlrun/runtimes/__init__.py +1 -3
- mlrun/runtimes/base.py +30 -22
- mlrun/runtimes/daskjob.py +9 -9
- mlrun/runtimes/databricks_job/databricks_runtime.py +6 -5
- mlrun/runtimes/function_reference.py +5 -2
- mlrun/runtimes/generators.py +3 -2
- mlrun/runtimes/kubejob.py +6 -7
- mlrun/runtimes/mounts.py +574 -0
- mlrun/runtimes/mpijob/__init__.py +0 -2
- mlrun/runtimes/mpijob/abstract.py +7 -6
- mlrun/runtimes/nuclio/api_gateway.py +7 -7
- mlrun/runtimes/nuclio/application/application.py +11 -13
- mlrun/runtimes/nuclio/application/reverse_proxy.go +66 -64
- mlrun/runtimes/nuclio/function.py +127 -70
- mlrun/runtimes/nuclio/serving.py +105 -37
- mlrun/runtimes/pod.py +159 -54
- mlrun/runtimes/remotesparkjob.py +3 -2
- mlrun/runtimes/sparkjob/__init__.py +0 -2
- mlrun/runtimes/sparkjob/spark3job.py +22 -12
- mlrun/runtimes/utils.py +7 -6
- mlrun/secrets.py +2 -2
- mlrun/serving/__init__.py +8 -0
- mlrun/serving/merger.py +7 -5
- mlrun/serving/remote.py +35 -22
- mlrun/serving/routers.py +186 -240
- mlrun/serving/server.py +41 -10
- mlrun/serving/states.py +432 -118
- mlrun/serving/utils.py +13 -2
- mlrun/serving/v1_serving.py +3 -2
- mlrun/serving/v2_serving.py +161 -203
- mlrun/track/__init__.py +1 -1
- mlrun/track/tracker.py +2 -2
- mlrun/track/trackers/mlflow_tracker.py +6 -5
- mlrun/utils/async_http.py +35 -22
- mlrun/utils/clones.py +7 -4
- mlrun/utils/helpers.py +511 -58
- mlrun/utils/logger.py +119 -13
- mlrun/utils/notifications/notification/__init__.py +22 -19
- mlrun/utils/notifications/notification/base.py +39 -15
- mlrun/utils/notifications/notification/console.py +6 -6
- mlrun/utils/notifications/notification/git.py +11 -11
- mlrun/utils/notifications/notification/ipython.py +10 -9
- mlrun/utils/notifications/notification/mail.py +176 -0
- mlrun/utils/notifications/notification/slack.py +16 -8
- mlrun/utils/notifications/notification/webhook.py +24 -8
- mlrun/utils/notifications/notification_pusher.py +191 -200
- mlrun/utils/regex.py +12 -2
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.7.2rc4.dist-info → mlrun-1.8.0.dist-info}/METADATA +69 -54
- mlrun-1.8.0.dist-info/RECORD +351 -0
- {mlrun-1.7.2rc4.dist-info → mlrun-1.8.0.dist-info}/WHEEL +1 -1
- mlrun/model_monitoring/applications/evidently_base.py +0 -137
- mlrun/model_monitoring/db/stores/__init__.py +0 -136
- mlrun/model_monitoring/db/stores/base/store.py +0 -213
- mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +0 -71
- mlrun/model_monitoring/db/stores/sqldb/models/base.py +0 -190
- mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +0 -103
- mlrun/model_monitoring/db/stores/sqldb/models/sqlite.py +0 -40
- mlrun/model_monitoring/db/stores/sqldb/sql_store.py +0 -659
- mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +0 -726
- mlrun/model_monitoring/model_endpoint.py +0 -118
- mlrun-1.7.2rc4.dist-info/RECORD +0 -351
- {mlrun-1.7.2rc4.dist-info → mlrun-1.8.0.dist-info}/entry_points.txt +0 -0
- {mlrun-1.7.2rc4.dist-info → mlrun-1.8.0.dist-info/licenses}/LICENSE +0 -0
- {mlrun-1.7.2rc4.dist-info → mlrun-1.8.0.dist-info}/top_level.txt +0 -0
|
@@ -16,6 +16,7 @@ import re
|
|
|
16
16
|
import typing
|
|
17
17
|
|
|
18
18
|
import aiohttp
|
|
19
|
+
import orjson
|
|
19
20
|
|
|
20
21
|
import mlrun.common.schemas
|
|
21
22
|
import mlrun.lists
|
|
@@ -38,13 +39,13 @@ class WebhookNotification(NotificationBase):
|
|
|
38
39
|
async def push(
|
|
39
40
|
self,
|
|
40
41
|
message: str,
|
|
41
|
-
severity: typing.
|
|
42
|
-
mlrun.common.schemas.NotificationSeverity, str
|
|
42
|
+
severity: typing.Optional[
|
|
43
|
+
typing.Union[mlrun.common.schemas.NotificationSeverity, str]
|
|
43
44
|
] = mlrun.common.schemas.NotificationSeverity.INFO,
|
|
44
|
-
runs: typing.Union[mlrun.lists.RunList, list] = None,
|
|
45
|
-
custom_html: str = None,
|
|
46
|
-
alert: mlrun.common.schemas.AlertConfig = None,
|
|
47
|
-
event_data: mlrun.common.schemas.Event = None,
|
|
45
|
+
runs: typing.Optional[typing.Union[mlrun.lists.RunList, list]] = None,
|
|
46
|
+
custom_html: typing.Optional[typing.Optional[str]] = None,
|
|
47
|
+
alert: typing.Optional[mlrun.common.schemas.AlertConfig] = None,
|
|
48
|
+
event_data: typing.Optional[mlrun.common.schemas.Event] = None,
|
|
48
49
|
):
|
|
49
50
|
url = self.params.get("url", None)
|
|
50
51
|
method = self.params.get("method", "post").lower()
|
|
@@ -86,9 +87,14 @@ class WebhookNotification(NotificationBase):
|
|
|
86
87
|
# we automatically handle it as `ssl=None` for their convenience.
|
|
87
88
|
verify_ssl = verify_ssl and None if url.startswith("https") else None
|
|
88
89
|
|
|
89
|
-
async with aiohttp.ClientSession(
|
|
90
|
+
async with aiohttp.ClientSession(
|
|
91
|
+
json_serialize=self._encoder,
|
|
92
|
+
) as session:
|
|
90
93
|
response = await getattr(session, method)(
|
|
91
|
-
url,
|
|
94
|
+
url,
|
|
95
|
+
headers=headers,
|
|
96
|
+
json=request_body,
|
|
97
|
+
ssl=verify_ssl,
|
|
92
98
|
)
|
|
93
99
|
response.raise_for_status()
|
|
94
100
|
|
|
@@ -128,3 +134,13 @@ class WebhookNotification(NotificationBase):
|
|
|
128
134
|
)
|
|
129
135
|
|
|
130
136
|
return override_body
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def _encoder(self):
|
|
140
|
+
return lambda body: orjson.dumps(
|
|
141
|
+
body,
|
|
142
|
+
option=orjson.OPT_NAIVE_UTC
|
|
143
|
+
| orjson.OPT_SERIALIZE_NUMPY
|
|
144
|
+
| orjson.OPT_NON_STR_KEYS
|
|
145
|
+
| orjson.OPT_SORT_KEYS,
|
|
146
|
+
).decode()
|
|
@@ -15,17 +15,12 @@
|
|
|
15
15
|
import asyncio
|
|
16
16
|
import datetime
|
|
17
17
|
import os
|
|
18
|
-
import re
|
|
19
18
|
import traceback
|
|
20
19
|
import typing
|
|
21
20
|
from concurrent.futures import ThreadPoolExecutor
|
|
22
21
|
|
|
23
|
-
import mlrun_pipelines.common.ops
|
|
24
|
-
import mlrun_pipelines.models
|
|
25
|
-
import mlrun_pipelines.utils
|
|
26
|
-
|
|
27
22
|
import mlrun.common.constants as mlrun_constants
|
|
28
|
-
import mlrun.common.runtimes.constants
|
|
23
|
+
import mlrun.common.runtimes.constants as runtimes_constants
|
|
29
24
|
import mlrun.common.schemas
|
|
30
25
|
import mlrun.config
|
|
31
26
|
import mlrun.db.base
|
|
@@ -33,11 +28,11 @@ import mlrun.errors
|
|
|
33
28
|
import mlrun.lists
|
|
34
29
|
import mlrun.model
|
|
35
30
|
import mlrun.utils.helpers
|
|
36
|
-
|
|
31
|
+
import mlrun.utils.notifications.notification as notification_module
|
|
32
|
+
import mlrun.utils.notifications.notification.base as base
|
|
33
|
+
from mlrun.utils import Workflow, logger
|
|
37
34
|
from mlrun.utils.condition_evaluator import evaluate_condition_in_separate_process
|
|
38
35
|
|
|
39
|
-
from .notification import NotificationBase, NotificationTypes
|
|
40
|
-
|
|
41
36
|
|
|
42
37
|
class _NotificationPusherBase:
|
|
43
38
|
def _push(
|
|
@@ -60,6 +55,7 @@ class _NotificationPusherBase:
|
|
|
60
55
|
event_loop = asyncio.get_event_loop()
|
|
61
56
|
except RuntimeError:
|
|
62
57
|
event_loop = asyncio.new_event_loop()
|
|
58
|
+
asyncio.set_event_loop(event_loop)
|
|
63
59
|
|
|
64
60
|
if not event_loop.is_running():
|
|
65
61
|
event_loop.run_until_complete(async_push_callback())
|
|
@@ -98,35 +94,71 @@ class NotificationPusher(_NotificationPusherBase):
|
|
|
98
94
|
"completed": "{resource} completed",
|
|
99
95
|
"error": "{resource} failed",
|
|
100
96
|
"aborted": "{resource} aborted",
|
|
97
|
+
"running": "{resource} started",
|
|
101
98
|
}
|
|
102
99
|
|
|
103
|
-
def __init__(
|
|
100
|
+
def __init__(
|
|
101
|
+
self,
|
|
102
|
+
runs: typing.Union[mlrun.lists.RunList, list],
|
|
103
|
+
default_params: typing.Optional[dict] = None,
|
|
104
|
+
):
|
|
104
105
|
self._runs = runs
|
|
106
|
+
self._default_params = default_params or {}
|
|
105
107
|
self._sync_notifications: list[
|
|
106
|
-
tuple[
|
|
108
|
+
tuple[
|
|
109
|
+
base.NotificationBase, mlrun.model.RunObject, mlrun.model.Notification
|
|
110
|
+
]
|
|
107
111
|
] = []
|
|
108
112
|
self._async_notifications: list[
|
|
109
|
-
tuple[
|
|
113
|
+
tuple[
|
|
114
|
+
base.NotificationBase, mlrun.model.RunObject, mlrun.model.Notification
|
|
115
|
+
]
|
|
110
116
|
] = []
|
|
111
117
|
|
|
112
118
|
for run in self._runs:
|
|
113
|
-
|
|
114
|
-
|
|
119
|
+
try:
|
|
120
|
+
self._process_run(run)
|
|
121
|
+
except Exception as exc:
|
|
122
|
+
logger.warning(
|
|
123
|
+
"Failed to process run",
|
|
124
|
+
run_uid=run.metadata.uid,
|
|
125
|
+
error=mlrun.errors.err_to_str(exc),
|
|
126
|
+
)
|
|
115
127
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
notification.name
|
|
120
|
-
).get("status", mlrun.common.schemas.NotificationStatus.PENDING)
|
|
121
|
-
except (AttributeError, KeyError):
|
|
122
|
-
notification.status = (
|
|
123
|
-
mlrun.common.schemas.NotificationStatus.PENDING
|
|
124
|
-
)
|
|
128
|
+
def _process_run(self, run):
|
|
129
|
+
if isinstance(run, dict):
|
|
130
|
+
run = mlrun.model.RunObject.from_dict(run)
|
|
125
131
|
|
|
126
|
-
|
|
127
|
-
|
|
132
|
+
for notification in run.spec.notifications:
|
|
133
|
+
try:
|
|
134
|
+
self._process_notification(notification, run)
|
|
135
|
+
except Exception as exc:
|
|
136
|
+
logger.warning(
|
|
137
|
+
"Failed to process notification",
|
|
138
|
+
run_uid=run.metadata.uid,
|
|
139
|
+
notification=notification,
|
|
140
|
+
error=mlrun.errors.err_to_str(exc),
|
|
141
|
+
)
|
|
128
142
|
|
|
129
|
-
def
|
|
143
|
+
def _process_notification(self, notification_object, run):
|
|
144
|
+
notification_object.status = run.status.notifications.get(
|
|
145
|
+
notification_object.name, {}
|
|
146
|
+
).get(
|
|
147
|
+
"status",
|
|
148
|
+
mlrun.common.schemas.NotificationStatus.PENDING,
|
|
149
|
+
)
|
|
150
|
+
if self._should_notify(run, notification_object):
|
|
151
|
+
notification = self._load_notification(notification_object)
|
|
152
|
+
if notification.is_async:
|
|
153
|
+
self._async_notifications.append(
|
|
154
|
+
(notification, run, notification_object)
|
|
155
|
+
)
|
|
156
|
+
else:
|
|
157
|
+
self._sync_notifications.append(
|
|
158
|
+
(notification, run, notification_object)
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
def push(self, sync_push_callback=None, async_push_callback=None):
|
|
130
162
|
"""
|
|
131
163
|
Asynchronously push notifications for all runs in the initialized runs list (if they should be pushed).
|
|
132
164
|
When running from a sync environment, the notifications will be pushed asynchronously however the function will
|
|
@@ -169,7 +201,7 @@ class NotificationPusher(_NotificationPusherBase):
|
|
|
169
201
|
"Failed to push notification async",
|
|
170
202
|
error=mlrun.errors.err_to_str(result),
|
|
171
203
|
traceback=traceback.format_exception(
|
|
172
|
-
|
|
204
|
+
result,
|
|
173
205
|
value=result,
|
|
174
206
|
tb=result.__traceback__,
|
|
175
207
|
),
|
|
@@ -180,8 +212,9 @@ class NotificationPusher(_NotificationPusherBase):
|
|
|
180
212
|
notifications_amount=len(self._sync_notifications)
|
|
181
213
|
+ len(self._async_notifications),
|
|
182
214
|
)
|
|
183
|
-
|
|
184
|
-
|
|
215
|
+
sync_push_callback = sync_push_callback or sync_push
|
|
216
|
+
async_push_callback = async_push_callback or async_push
|
|
217
|
+
self._push(sync_push_callback, async_push_callback)
|
|
185
218
|
|
|
186
219
|
@staticmethod
|
|
187
220
|
def _should_notify(
|
|
@@ -202,7 +235,7 @@ class NotificationPusher(_NotificationPusherBase):
|
|
|
202
235
|
for when_state in when_states:
|
|
203
236
|
if when_state == run_state:
|
|
204
237
|
if (
|
|
205
|
-
run_state ==
|
|
238
|
+
run_state == runtimes_constants.RunStates.completed
|
|
206
239
|
and evaluate_condition_in_separate_process(
|
|
207
240
|
notification.condition,
|
|
208
241
|
context={
|
|
@@ -210,27 +243,29 @@ class NotificationPusher(_NotificationPusherBase):
|
|
|
210
243
|
"notification": notification.to_dict(),
|
|
211
244
|
},
|
|
212
245
|
)
|
|
213
|
-
) or run_state in [
|
|
246
|
+
) or run_state in [
|
|
247
|
+
runtimes_constants.RunStates.error,
|
|
248
|
+
runtimes_constants.RunStates.aborted,
|
|
249
|
+
runtimes_constants.RunStates.running,
|
|
250
|
+
]:
|
|
214
251
|
return True
|
|
215
252
|
|
|
216
253
|
return False
|
|
217
254
|
|
|
218
255
|
def _load_notification(
|
|
219
|
-
self,
|
|
220
|
-
) -> NotificationBase:
|
|
256
|
+
self, notification_object: mlrun.model.Notification
|
|
257
|
+
) -> base.NotificationBase:
|
|
221
258
|
name = notification_object.name
|
|
222
|
-
notification_type = NotificationTypes(
|
|
223
|
-
notification_object.kind or NotificationTypes.console
|
|
259
|
+
notification_type = notification_module.NotificationTypes(
|
|
260
|
+
notification_object.kind or notification_module.NotificationTypes.console
|
|
224
261
|
)
|
|
225
262
|
params = {}
|
|
226
|
-
params.update(notification_object.secret_params)
|
|
227
|
-
params.update(notification_object.params)
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
self._sync_notifications.append((notification, run, notification_object))
|
|
233
|
-
|
|
263
|
+
params.update(notification_object.secret_params or {})
|
|
264
|
+
params.update(notification_object.params or {})
|
|
265
|
+
default_params = self._default_params.get(notification_type.value, {})
|
|
266
|
+
notification = notification_type.get_notification()(
|
|
267
|
+
name, params, default_params
|
|
268
|
+
)
|
|
234
269
|
logger.debug(
|
|
235
270
|
"Loaded notification", notification=name, type=notification_type.value
|
|
236
271
|
)
|
|
@@ -250,10 +285,14 @@ class NotificationPusher(_NotificationPusherBase):
|
|
|
250
285
|
custom_message = (
|
|
251
286
|
f" (workflow: {run.metadata.labels['workflow']}){custom_message}"
|
|
252
287
|
)
|
|
253
|
-
|
|
288
|
+
project = run.metadata.project
|
|
289
|
+
workflow_id = run.status.results.get("workflow_id", None)
|
|
290
|
+
db = mlrun.get_run_db()
|
|
291
|
+
runs.extend(Workflow.get_workflow_steps(db, workflow_id, project))
|
|
254
292
|
|
|
255
293
|
message = (
|
|
256
294
|
self.messages.get(run.state(), "").format(resource=resource)
|
|
295
|
+
+ f" in project {run.metadata.project}"
|
|
257
296
|
+ custom_message
|
|
258
297
|
)
|
|
259
298
|
|
|
@@ -265,13 +304,14 @@ class NotificationPusher(_NotificationPusherBase):
|
|
|
265
304
|
|
|
266
305
|
def _push_notification_sync(
|
|
267
306
|
self,
|
|
268
|
-
notification: NotificationBase,
|
|
307
|
+
notification: base.NotificationBase,
|
|
269
308
|
run: mlrun.model.RunObject,
|
|
270
309
|
notification_object: mlrun.model.Notification,
|
|
271
310
|
):
|
|
272
311
|
message, severity, runs = self._prepare_notification_args(
|
|
273
312
|
run, notification_object
|
|
274
313
|
)
|
|
314
|
+
|
|
275
315
|
logger.debug(
|
|
276
316
|
"Pushing sync notification",
|
|
277
317
|
notification=sanitize_notification(notification_object.to_dict()),
|
|
@@ -282,6 +322,7 @@ class NotificationPusher(_NotificationPusherBase):
|
|
|
282
322
|
"project": run.metadata.project,
|
|
283
323
|
"notification": notification_object,
|
|
284
324
|
"status": mlrun.common.schemas.NotificationStatus.SENT,
|
|
325
|
+
"run_state": run.state(),
|
|
285
326
|
}
|
|
286
327
|
try:
|
|
287
328
|
notification.push(message, severity, runs)
|
|
@@ -313,13 +354,14 @@ class NotificationPusher(_NotificationPusherBase):
|
|
|
313
354
|
|
|
314
355
|
async def _push_notification_async(
|
|
315
356
|
self,
|
|
316
|
-
notification: NotificationBase,
|
|
357
|
+
notification: base.NotificationBase,
|
|
317
358
|
run: mlrun.model.RunObject,
|
|
318
359
|
notification_object: mlrun.model.Notification,
|
|
319
360
|
):
|
|
320
361
|
message, severity, runs = self._prepare_notification_args(
|
|
321
362
|
run, notification_object
|
|
322
363
|
)
|
|
364
|
+
|
|
323
365
|
logger.debug(
|
|
324
366
|
"Pushing async notification",
|
|
325
367
|
notification=sanitize_notification(notification_object.to_dict()),
|
|
@@ -329,6 +371,7 @@ class NotificationPusher(_NotificationPusherBase):
|
|
|
329
371
|
"run_uid": run.metadata.uid,
|
|
330
372
|
"project": run.metadata.project,
|
|
331
373
|
"notification": notification_object,
|
|
374
|
+
"run_state": run.state(),
|
|
332
375
|
"status": mlrun.common.schemas.NotificationStatus.SENT,
|
|
333
376
|
}
|
|
334
377
|
try:
|
|
@@ -366,10 +409,29 @@ class NotificationPusher(_NotificationPusherBase):
|
|
|
366
409
|
run_uid: str,
|
|
367
410
|
project: str,
|
|
368
411
|
notification: mlrun.model.Notification,
|
|
369
|
-
|
|
412
|
+
run_state: runtimes_constants.RunStates,
|
|
413
|
+
status: typing.Optional[str] = None,
|
|
370
414
|
sent_time: typing.Optional[datetime.datetime] = None,
|
|
371
415
|
reason: typing.Optional[str] = None,
|
|
372
416
|
):
|
|
417
|
+
# Skip update the notification state if the following conditions are met:
|
|
418
|
+
# 1. the run is not in a terminal state
|
|
419
|
+
# 2. the when contains only one state (which is the current state)
|
|
420
|
+
# Skip updating because currently each notification has only one row in the db, even if it has multiple when.
|
|
421
|
+
# This means that if the notification is updated to sent for running state for example, it will not send for
|
|
422
|
+
# The terminal state
|
|
423
|
+
# TODO: Change this behavior after implementing ML-8723
|
|
424
|
+
if (
|
|
425
|
+
run_state not in runtimes_constants.RunStates.terminal_states()
|
|
426
|
+
and len(notification.when) > 1
|
|
427
|
+
):
|
|
428
|
+
logger.debug(
|
|
429
|
+
"Skip updating notification status - run not in terminal state",
|
|
430
|
+
run_uid=run_uid,
|
|
431
|
+
state=run_state,
|
|
432
|
+
)
|
|
433
|
+
return
|
|
434
|
+
|
|
373
435
|
db = mlrun.get_run_db()
|
|
374
436
|
notification.status = status or notification.status
|
|
375
437
|
notification.sent_time = sent_time or notification.sent_time
|
|
@@ -394,136 +456,13 @@ class NotificationPusher(_NotificationPusherBase):
|
|
|
394
456
|
mask_params=False,
|
|
395
457
|
)
|
|
396
458
|
|
|
397
|
-
def get_workflow_steps(self, run: mlrun.model.RunObject) -> list:
|
|
398
|
-
steps = []
|
|
399
|
-
db = mlrun.get_run_db()
|
|
400
|
-
|
|
401
|
-
def _add_run_step(_step: mlrun_pipelines.models.PipelineStep):
|
|
402
|
-
try:
|
|
403
|
-
_run = db.list_runs(
|
|
404
|
-
project=run.metadata.project,
|
|
405
|
-
labels=f"{mlrun_constants.MLRunInternalLabels.runner_pod}={_step.node_name}",
|
|
406
|
-
)[0]
|
|
407
|
-
except IndexError:
|
|
408
|
-
_run = {
|
|
409
|
-
"metadata": {
|
|
410
|
-
"name": _step.display_name,
|
|
411
|
-
"project": run.metadata.project,
|
|
412
|
-
},
|
|
413
|
-
}
|
|
414
|
-
_run["step_kind"] = _step.step_type
|
|
415
|
-
if _step.skipped:
|
|
416
|
-
_run.setdefault("status", {})["state"] = (
|
|
417
|
-
mlrun.common.runtimes.constants.RunStates.skipped
|
|
418
|
-
)
|
|
419
|
-
steps.append(_run)
|
|
420
|
-
|
|
421
|
-
def _add_deploy_function_step(_step: mlrun_pipelines.models.PipelineStep):
|
|
422
|
-
project, name, hash_key = self._extract_function_uri(
|
|
423
|
-
_step.get_annotation("mlrun/function-uri")
|
|
424
|
-
)
|
|
425
|
-
if name:
|
|
426
|
-
try:
|
|
427
|
-
function = db.get_function(
|
|
428
|
-
project=project, name=name, hash_key=hash_key
|
|
429
|
-
)
|
|
430
|
-
except mlrun.errors.MLRunNotFoundError:
|
|
431
|
-
# If the function is not found (if build failed for example), we will create a dummy
|
|
432
|
-
# function object for the notification to display the function name
|
|
433
|
-
function = {
|
|
434
|
-
"metadata": {
|
|
435
|
-
"name": name,
|
|
436
|
-
"project": project,
|
|
437
|
-
"hash_key": hash_key,
|
|
438
|
-
},
|
|
439
|
-
}
|
|
440
|
-
pod_phase = _step.phase
|
|
441
|
-
if _step.skipped:
|
|
442
|
-
state = mlrun.common.schemas.FunctionState.skipped
|
|
443
|
-
else:
|
|
444
|
-
state = mlrun.common.runtimes.constants.PodPhases.pod_phase_to_run_state(
|
|
445
|
-
pod_phase
|
|
446
|
-
)
|
|
447
|
-
function["status"] = {"state": state}
|
|
448
|
-
if isinstance(function["metadata"].get("updated"), datetime.datetime):
|
|
449
|
-
function["metadata"]["updated"] = function["metadata"][
|
|
450
|
-
"updated"
|
|
451
|
-
].isoformat()
|
|
452
|
-
function["step_kind"] = _step.step_type
|
|
453
|
-
steps.append(function)
|
|
454
|
-
|
|
455
|
-
step_methods = {
|
|
456
|
-
mlrun_pipelines.common.ops.PipelineRunType.run: _add_run_step,
|
|
457
|
-
mlrun_pipelines.common.ops.PipelineRunType.build: _add_deploy_function_step,
|
|
458
|
-
mlrun_pipelines.common.ops.PipelineRunType.deploy: _add_deploy_function_step,
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
workflow_id = run.status.results.get("workflow_id", None)
|
|
462
|
-
if not workflow_id:
|
|
463
|
-
return steps
|
|
464
|
-
|
|
465
|
-
workflow_manifest = self._get_workflow_manifest(workflow_id)
|
|
466
|
-
if not workflow_manifest:
|
|
467
|
-
return steps
|
|
468
|
-
|
|
469
|
-
try:
|
|
470
|
-
for step in workflow_manifest.get_steps():
|
|
471
|
-
step_method = step_methods.get(step.step_type)
|
|
472
|
-
if step_method:
|
|
473
|
-
step_method(step)
|
|
474
|
-
return steps
|
|
475
|
-
except Exception:
|
|
476
|
-
# If we fail to read the pipeline steps, we will return the list of runs that have the same workflow id
|
|
477
|
-
logger.warning(
|
|
478
|
-
"Failed to extract workflow steps from workflow manifest, "
|
|
479
|
-
"returning all runs with the workflow id label",
|
|
480
|
-
workflow_id=workflow_id,
|
|
481
|
-
traceback=traceback.format_exc(),
|
|
482
|
-
)
|
|
483
|
-
return db.list_runs(
|
|
484
|
-
project=run.metadata.project,
|
|
485
|
-
labels=f"workflow={workflow_id}",
|
|
486
|
-
)
|
|
487
|
-
|
|
488
|
-
@staticmethod
|
|
489
|
-
def _get_workflow_manifest(
|
|
490
|
-
workflow_id: str,
|
|
491
|
-
) -> typing.Optional[mlrun_pipelines.models.PipelineManifest]:
|
|
492
|
-
kfp_client = mlrun_pipelines.utils.get_client(mlrun.mlconf.kfp_url)
|
|
493
|
-
|
|
494
|
-
# arbitrary timeout of 5 seconds, the workflow should be done by now
|
|
495
|
-
kfp_run = kfp_client.wait_for_run_completion(workflow_id, 5)
|
|
496
|
-
if not kfp_run:
|
|
497
|
-
return None
|
|
498
|
-
|
|
499
|
-
kfp_run = mlrun_pipelines.models.PipelineRun(kfp_run)
|
|
500
|
-
return kfp_run.workflow_manifest()
|
|
501
|
-
|
|
502
|
-
def _extract_function_uri(self, function_uri: str) -> tuple[str, str, str]:
|
|
503
|
-
"""
|
|
504
|
-
Extract the project, name, and hash key from a function uri.
|
|
505
|
-
Examples:
|
|
506
|
-
- "project/name@hash_key" returns project, name, hash_key
|
|
507
|
-
- "project/name returns" project, name, ""
|
|
508
|
-
"""
|
|
509
|
-
project, name, hash_key = None, None, None
|
|
510
|
-
hashed_pattern = r"^(.+)/(.+)@(.+)$"
|
|
511
|
-
pattern = r"^(.+)/(.+)$"
|
|
512
|
-
match = re.match(hashed_pattern, function_uri)
|
|
513
|
-
if match:
|
|
514
|
-
project, name, hash_key = match.groups()
|
|
515
|
-
else:
|
|
516
|
-
match = re.match(pattern, function_uri)
|
|
517
|
-
if match:
|
|
518
|
-
project, name = match.groups()
|
|
519
|
-
hash_key = ""
|
|
520
|
-
return project, name, hash_key
|
|
521
|
-
|
|
522
459
|
|
|
523
460
|
class CustomNotificationPusher(_NotificationPusherBase):
|
|
524
|
-
def __init__(self, notification_types: list[str] = None):
|
|
461
|
+
def __init__(self, notification_types: typing.Optional[list[str]] = None):
|
|
525
462
|
notifications = {
|
|
526
|
-
notification_type: NotificationTypes(
|
|
463
|
+
notification_type: notification_module.NotificationTypes(
|
|
464
|
+
notification_type
|
|
465
|
+
).get_notification()()
|
|
527
466
|
for notification_type in notification_types
|
|
528
467
|
}
|
|
529
468
|
self._sync_notifications = {
|
|
@@ -536,6 +475,7 @@ class CustomNotificationPusher(_NotificationPusherBase):
|
|
|
536
475
|
for notification_type, notification in notifications.items()
|
|
537
476
|
if notification.is_async
|
|
538
477
|
}
|
|
478
|
+
self._server_notifications = []
|
|
539
479
|
|
|
540
480
|
@property
|
|
541
481
|
def notifications(self):
|
|
@@ -543,6 +483,10 @@ class CustomNotificationPusher(_NotificationPusherBase):
|
|
|
543
483
|
notifications.update(self._async_notifications)
|
|
544
484
|
return notifications
|
|
545
485
|
|
|
486
|
+
@property
|
|
487
|
+
def server_notifications(self):
|
|
488
|
+
return self._server_notifications
|
|
489
|
+
|
|
546
490
|
def push(
|
|
547
491
|
self,
|
|
548
492
|
message: str,
|
|
@@ -550,7 +494,7 @@ class CustomNotificationPusher(_NotificationPusherBase):
|
|
|
550
494
|
mlrun.common.schemas.NotificationSeverity, str
|
|
551
495
|
] = mlrun.common.schemas.NotificationSeverity.INFO,
|
|
552
496
|
runs: typing.Union[mlrun.lists.RunList, list] = None,
|
|
553
|
-
custom_html: str = None,
|
|
497
|
+
custom_html: typing.Optional[str] = None,
|
|
554
498
|
):
|
|
555
499
|
def sync_push():
|
|
556
500
|
for notification_type, notification in self._sync_notifications.items():
|
|
@@ -572,14 +516,43 @@ class CustomNotificationPusher(_NotificationPusherBase):
|
|
|
572
516
|
def add_notification(
|
|
573
517
|
self,
|
|
574
518
|
notification_type: str,
|
|
575
|
-
params: dict[str, str] = None,
|
|
519
|
+
params: typing.Optional[dict[str, str]] = None,
|
|
520
|
+
name: typing.Optional[str] = None,
|
|
521
|
+
message: typing.Optional[str] = None,
|
|
522
|
+
severity: mlrun.common.schemas.notification.NotificationSeverity = (
|
|
523
|
+
mlrun.common.schemas.notification.NotificationSeverity.INFO
|
|
524
|
+
),
|
|
525
|
+
when: typing.Optional[list[str]] = None,
|
|
526
|
+
condition: typing.Optional[str] = None,
|
|
527
|
+
secret_params: typing.Optional[dict[str, str]] = None,
|
|
576
528
|
):
|
|
529
|
+
if notification_type not in [
|
|
530
|
+
notification_module.NotificationTypes.console,
|
|
531
|
+
notification_module.NotificationTypes.ipython,
|
|
532
|
+
]:
|
|
533
|
+
# We want that only the console and ipython notifications will be notified by the client.
|
|
534
|
+
# The rest of the notifications will be notified by the BE.
|
|
535
|
+
self._server_notifications.append(
|
|
536
|
+
mlrun.model.Notification(
|
|
537
|
+
kind=notification_type,
|
|
538
|
+
name=name,
|
|
539
|
+
message=message,
|
|
540
|
+
severity=severity,
|
|
541
|
+
when=when or runtimes_constants.RunStates.notification_states(),
|
|
542
|
+
params=params,
|
|
543
|
+
secret_params=secret_params,
|
|
544
|
+
)
|
|
545
|
+
)
|
|
546
|
+
return
|
|
547
|
+
|
|
577
548
|
if notification_type in self._async_notifications:
|
|
578
549
|
self._async_notifications[notification_type].load_notification(params)
|
|
579
550
|
elif notification_type in self._sync_notifications:
|
|
580
551
|
self._sync_notifications[notification_type].load_notification(params)
|
|
581
552
|
else:
|
|
582
|
-
notification = NotificationTypes(
|
|
553
|
+
notification = notification_module.NotificationTypes(
|
|
554
|
+
notification_type
|
|
555
|
+
).get_notification()(
|
|
583
556
|
params=params,
|
|
584
557
|
)
|
|
585
558
|
if notification.is_async:
|
|
@@ -597,7 +570,9 @@ class CustomNotificationPusher(_NotificationPusherBase):
|
|
|
597
570
|
else:
|
|
598
571
|
logger.warning(f"No notification of type {notification_type} in project")
|
|
599
572
|
|
|
600
|
-
def edit_notification(
|
|
573
|
+
def edit_notification(
|
|
574
|
+
self, notification_type: str, params: typing.Optional[dict[str, str]] = None
|
|
575
|
+
):
|
|
601
576
|
self.remove_notification(notification_type)
|
|
602
577
|
self.add_notification(notification_type, params)
|
|
603
578
|
|
|
@@ -611,7 +586,7 @@ class CustomNotificationPusher(_NotificationPusherBase):
|
|
|
611
586
|
|
|
612
587
|
# get notification's inverse dependencies, and only push the notification if
|
|
613
588
|
# none of its inverse dependencies are being sent
|
|
614
|
-
inverse_dependencies = NotificationTypes(
|
|
589
|
+
inverse_dependencies = notification_module.NotificationTypes(
|
|
615
590
|
notification_type
|
|
616
591
|
).inverse_dependencies()
|
|
617
592
|
for inverse_dependency in inverse_dependencies:
|
|
@@ -627,36 +602,28 @@ class CustomNotificationPusher(_NotificationPusherBase):
|
|
|
627
602
|
def push_pipeline_start_message(
|
|
628
603
|
self,
|
|
629
604
|
project: str,
|
|
630
|
-
|
|
631
|
-
|
|
605
|
+
pipeline_id: typing.Optional[str] = None,
|
|
606
|
+
):
|
|
607
|
+
db = mlrun.get_run_db()
|
|
608
|
+
db.push_run_notifications(pipeline_id, project)
|
|
609
|
+
|
|
610
|
+
def push_pipeline_start_message_from_client(
|
|
611
|
+
self,
|
|
612
|
+
project: str,
|
|
613
|
+
commit_id: typing.Optional[str] = None,
|
|
614
|
+
pipeline_id: typing.Optional[str] = None,
|
|
632
615
|
has_workflow_url: bool = False,
|
|
633
616
|
):
|
|
634
|
-
message =
|
|
635
|
-
|
|
636
|
-
message += f" id={pipeline_id}"
|
|
637
|
-
commit_id = (
|
|
638
|
-
commit_id or os.environ.get("GITHUB_SHA") or os.environ.get("CI_COMMIT_SHA")
|
|
617
|
+
html, message = self.generate_start_message(
|
|
618
|
+
commit_id, has_workflow_url, pipeline_id, project
|
|
639
619
|
)
|
|
640
|
-
if commit_id:
|
|
641
|
-
message += f", commit={commit_id}"
|
|
642
|
-
if has_workflow_url:
|
|
643
|
-
url = mlrun.utils.helpers.get_workflow_url(project, pipeline_id)
|
|
644
|
-
else:
|
|
645
|
-
url = mlrun.utils.helpers.get_ui_url(project)
|
|
646
|
-
html = ""
|
|
647
|
-
if url:
|
|
648
|
-
html = (
|
|
649
|
-
message
|
|
650
|
-
+ f'<div><a href="{url}" target="_blank">click here to view progress</a></div>'
|
|
651
|
-
)
|
|
652
|
-
message = message + f", check progress in {url}"
|
|
653
620
|
self.push(message, "info", custom_html=html)
|
|
654
621
|
|
|
655
622
|
def push_pipeline_run_results(
|
|
656
623
|
self,
|
|
657
624
|
runs: typing.Union[mlrun.lists.RunList, list],
|
|
658
625
|
push_all: bool = False,
|
|
659
|
-
state: str = None,
|
|
626
|
+
state: typing.Optional[str] = None,
|
|
660
627
|
):
|
|
661
628
|
"""
|
|
662
629
|
push a structured table with run results to notification targets
|
|
@@ -682,6 +649,30 @@ class CustomNotificationPusher(_NotificationPusherBase):
|
|
|
682
649
|
text += f", state={state}"
|
|
683
650
|
self.push(text, "info", runs=runs_list)
|
|
684
651
|
|
|
652
|
+
def generate_start_message(
|
|
653
|
+
self, commit_id=None, has_workflow_url=None, pipeline_id=None, project=None
|
|
654
|
+
):
|
|
655
|
+
message = f"Workflow started in project {project}"
|
|
656
|
+
if pipeline_id:
|
|
657
|
+
message += f" id={pipeline_id}"
|
|
658
|
+
commit_id = (
|
|
659
|
+
commit_id or os.environ.get("GITHUB_SHA") or os.environ.get("CI_COMMIT_SHA")
|
|
660
|
+
)
|
|
661
|
+
if commit_id:
|
|
662
|
+
message += f", commit={commit_id}"
|
|
663
|
+
if has_workflow_url:
|
|
664
|
+
url = mlrun.utils.helpers.get_workflow_url(project, pipeline_id)
|
|
665
|
+
else:
|
|
666
|
+
url = mlrun.utils.helpers.get_runs_url(project)
|
|
667
|
+
html = ""
|
|
668
|
+
if url:
|
|
669
|
+
html = (
|
|
670
|
+
message
|
|
671
|
+
+ f'<div><a href="{url}" target="_blank">click here to view progress</a></div>'
|
|
672
|
+
)
|
|
673
|
+
message = message + f", check progress in {url}"
|
|
674
|
+
return html, message
|
|
675
|
+
|
|
685
676
|
|
|
686
677
|
def sanitize_notification(notification_dict: dict):
|
|
687
678
|
notification_dict.pop("secret_params", None)
|