mlrun 1.6.4rc8__py3-none-any.whl → 1.7.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 +11 -1
- mlrun/__main__.py +40 -122
- mlrun/alerts/__init__.py +15 -0
- mlrun/alerts/alert.py +248 -0
- mlrun/api/schemas/__init__.py +5 -4
- mlrun/artifacts/__init__.py +8 -3
- mlrun/artifacts/base.py +47 -257
- mlrun/artifacts/dataset.py +11 -192
- mlrun/artifacts/manager.py +79 -47
- mlrun/artifacts/model.py +31 -159
- mlrun/artifacts/plots.py +23 -380
- mlrun/common/constants.py +74 -1
- mlrun/common/db/sql_session.py +5 -5
- mlrun/common/formatters/__init__.py +21 -0
- mlrun/common/formatters/artifact.py +45 -0
- mlrun/common/formatters/base.py +113 -0
- mlrun/common/formatters/feature_set.py +33 -0
- mlrun/common/formatters/function.py +46 -0
- mlrun/common/formatters/pipeline.py +53 -0
- mlrun/common/formatters/project.py +51 -0
- mlrun/common/formatters/run.py +29 -0
- mlrun/common/helpers.py +12 -3
- mlrun/common/model_monitoring/helpers.py +9 -5
- mlrun/{runtimes → common/runtimes}/constants.py +37 -9
- mlrun/common/schemas/__init__.py +31 -5
- mlrun/common/schemas/alert.py +202 -0
- mlrun/common/schemas/api_gateway.py +196 -0
- mlrun/common/schemas/artifact.py +25 -4
- mlrun/common/schemas/auth.py +16 -5
- mlrun/common/schemas/background_task.py +1 -1
- mlrun/common/schemas/client_spec.py +4 -2
- mlrun/common/schemas/common.py +7 -4
- mlrun/common/schemas/constants.py +3 -0
- mlrun/common/schemas/feature_store.py +74 -44
- mlrun/common/schemas/frontend_spec.py +15 -7
- mlrun/common/schemas/function.py +12 -1
- mlrun/common/schemas/hub.py +11 -18
- mlrun/common/schemas/memory_reports.py +2 -2
- mlrun/common/schemas/model_monitoring/__init__.py +20 -4
- mlrun/common/schemas/model_monitoring/constants.py +123 -42
- mlrun/common/schemas/model_monitoring/grafana.py +13 -9
- mlrun/common/schemas/model_monitoring/model_endpoints.py +101 -54
- mlrun/common/schemas/notification.py +71 -14
- mlrun/common/schemas/object.py +2 -2
- mlrun/{model_monitoring/controller_handler.py → common/schemas/pagination.py} +9 -12
- mlrun/common/schemas/pipeline.py +8 -1
- mlrun/common/schemas/project.py +69 -18
- mlrun/common/schemas/runs.py +7 -1
- mlrun/common/schemas/runtime_resource.py +8 -12
- mlrun/common/schemas/schedule.py +4 -4
- mlrun/common/schemas/tag.py +1 -2
- mlrun/common/schemas/workflow.py +12 -4
- mlrun/common/types.py +14 -1
- mlrun/config.py +154 -69
- mlrun/data_types/data_types.py +6 -1
- mlrun/data_types/spark.py +2 -2
- mlrun/data_types/to_pandas.py +67 -37
- mlrun/datastore/__init__.py +6 -8
- mlrun/datastore/alibaba_oss.py +131 -0
- mlrun/datastore/azure_blob.py +143 -42
- mlrun/datastore/base.py +102 -58
- mlrun/datastore/datastore.py +34 -13
- mlrun/datastore/datastore_profile.py +146 -20
- mlrun/datastore/dbfs_store.py +3 -7
- mlrun/datastore/filestore.py +1 -4
- mlrun/datastore/google_cloud_storage.py +97 -33
- mlrun/datastore/hdfs.py +56 -0
- mlrun/datastore/inmem.py +6 -3
- mlrun/datastore/redis.py +7 -2
- mlrun/datastore/s3.py +34 -12
- mlrun/datastore/snowflake_utils.py +45 -0
- mlrun/datastore/sources.py +303 -111
- mlrun/datastore/spark_utils.py +31 -2
- mlrun/datastore/store_resources.py +9 -7
- mlrun/datastore/storeytargets.py +151 -0
- mlrun/datastore/targets.py +453 -176
- mlrun/datastore/utils.py +72 -58
- mlrun/datastore/v3io.py +6 -1
- mlrun/db/base.py +274 -41
- mlrun/db/factory.py +1 -1
- mlrun/db/httpdb.py +893 -225
- mlrun/db/nopdb.py +291 -33
- mlrun/errors.py +36 -6
- mlrun/execution.py +115 -42
- mlrun/feature_store/__init__.py +0 -2
- mlrun/feature_store/api.py +65 -73
- mlrun/feature_store/common.py +7 -12
- mlrun/feature_store/feature_set.py +76 -55
- mlrun/feature_store/feature_vector.py +39 -31
- mlrun/feature_store/ingestion.py +7 -6
- mlrun/feature_store/retrieval/base.py +16 -11
- mlrun/feature_store/retrieval/dask_merger.py +2 -0
- mlrun/feature_store/retrieval/job.py +13 -4
- mlrun/feature_store/retrieval/local_merger.py +2 -0
- mlrun/feature_store/retrieval/spark_merger.py +24 -32
- mlrun/feature_store/steps.py +45 -34
- mlrun/features.py +11 -21
- mlrun/frameworks/_common/artifacts_library.py +9 -9
- mlrun/frameworks/_common/mlrun_interface.py +5 -5
- mlrun/frameworks/_common/model_handler.py +48 -48
- mlrun/frameworks/_common/plan.py +5 -6
- mlrun/frameworks/_common/producer.py +3 -4
- mlrun/frameworks/_common/utils.py +5 -5
- mlrun/frameworks/_dl_common/loggers/logger.py +6 -7
- mlrun/frameworks/_dl_common/loggers/mlrun_logger.py +9 -9
- mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +23 -47
- mlrun/frameworks/_ml_common/artifacts_library.py +1 -2
- mlrun/frameworks/_ml_common/loggers/logger.py +3 -4
- mlrun/frameworks/_ml_common/loggers/mlrun_logger.py +4 -5
- mlrun/frameworks/_ml_common/model_handler.py +24 -24
- mlrun/frameworks/_ml_common/pkl_model_server.py +2 -2
- mlrun/frameworks/_ml_common/plan.py +2 -2
- mlrun/frameworks/_ml_common/plans/calibration_curve_plan.py +2 -3
- mlrun/frameworks/_ml_common/plans/confusion_matrix_plan.py +2 -3
- mlrun/frameworks/_ml_common/plans/dataset_plan.py +3 -3
- mlrun/frameworks/_ml_common/plans/feature_importance_plan.py +3 -3
- mlrun/frameworks/_ml_common/plans/roc_curve_plan.py +4 -4
- mlrun/frameworks/_ml_common/utils.py +4 -4
- mlrun/frameworks/auto_mlrun/auto_mlrun.py +9 -9
- mlrun/frameworks/huggingface/model_server.py +4 -4
- mlrun/frameworks/lgbm/__init__.py +33 -33
- mlrun/frameworks/lgbm/callbacks/callback.py +2 -4
- mlrun/frameworks/lgbm/callbacks/logging_callback.py +4 -5
- mlrun/frameworks/lgbm/callbacks/mlrun_logging_callback.py +4 -5
- mlrun/frameworks/lgbm/mlrun_interfaces/booster_mlrun_interface.py +1 -3
- mlrun/frameworks/lgbm/mlrun_interfaces/mlrun_interface.py +6 -6
- mlrun/frameworks/lgbm/model_handler.py +10 -10
- mlrun/frameworks/lgbm/model_server.py +6 -6
- mlrun/frameworks/lgbm/utils.py +5 -5
- mlrun/frameworks/onnx/dataset.py +8 -8
- mlrun/frameworks/onnx/mlrun_interface.py +3 -3
- mlrun/frameworks/onnx/model_handler.py +6 -6
- mlrun/frameworks/onnx/model_server.py +7 -7
- mlrun/frameworks/parallel_coordinates.py +6 -6
- mlrun/frameworks/pytorch/__init__.py +18 -18
- mlrun/frameworks/pytorch/callbacks/callback.py +4 -5
- mlrun/frameworks/pytorch/callbacks/logging_callback.py +17 -17
- mlrun/frameworks/pytorch/callbacks/mlrun_logging_callback.py +11 -11
- mlrun/frameworks/pytorch/callbacks/tensorboard_logging_callback.py +23 -29
- mlrun/frameworks/pytorch/callbacks_handler.py +38 -38
- mlrun/frameworks/pytorch/mlrun_interface.py +20 -20
- mlrun/frameworks/pytorch/model_handler.py +17 -17
- mlrun/frameworks/pytorch/model_server.py +7 -7
- mlrun/frameworks/sklearn/__init__.py +13 -13
- mlrun/frameworks/sklearn/estimator.py +4 -4
- mlrun/frameworks/sklearn/metrics_library.py +14 -14
- mlrun/frameworks/sklearn/mlrun_interface.py +16 -9
- mlrun/frameworks/sklearn/model_handler.py +2 -2
- mlrun/frameworks/tf_keras/__init__.py +10 -7
- mlrun/frameworks/tf_keras/callbacks/logging_callback.py +15 -15
- mlrun/frameworks/tf_keras/callbacks/mlrun_logging_callback.py +11 -11
- mlrun/frameworks/tf_keras/callbacks/tensorboard_logging_callback.py +19 -23
- mlrun/frameworks/tf_keras/mlrun_interface.py +9 -11
- mlrun/frameworks/tf_keras/model_handler.py +14 -14
- mlrun/frameworks/tf_keras/model_server.py +6 -6
- mlrun/frameworks/xgboost/__init__.py +13 -13
- mlrun/frameworks/xgboost/model_handler.py +6 -6
- mlrun/k8s_utils.py +61 -17
- mlrun/launcher/__init__.py +1 -1
- mlrun/launcher/base.py +16 -15
- mlrun/launcher/client.py +13 -11
- mlrun/launcher/factory.py +1 -1
- mlrun/launcher/local.py +23 -13
- mlrun/launcher/remote.py +17 -10
- mlrun/lists.py +7 -6
- mlrun/model.py +478 -103
- mlrun/model_monitoring/__init__.py +1 -1
- mlrun/model_monitoring/api.py +163 -371
- mlrun/{runtimes/mpijob/v1alpha1.py → model_monitoring/applications/__init__.py} +9 -15
- mlrun/model_monitoring/applications/_application_steps.py +188 -0
- mlrun/model_monitoring/applications/base.py +108 -0
- mlrun/model_monitoring/applications/context.py +341 -0
- mlrun/model_monitoring/{evidently_application.py → applications/evidently_base.py} +27 -22
- mlrun/model_monitoring/applications/histogram_data_drift.py +354 -0
- mlrun/model_monitoring/applications/results.py +99 -0
- mlrun/model_monitoring/controller.py +131 -278
- mlrun/model_monitoring/db/__init__.py +18 -0
- mlrun/model_monitoring/db/stores/__init__.py +136 -0
- mlrun/model_monitoring/db/stores/base/__init__.py +15 -0
- mlrun/model_monitoring/db/stores/base/store.py +213 -0
- mlrun/model_monitoring/db/stores/sqldb/__init__.py +13 -0
- mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +71 -0
- mlrun/model_monitoring/db/stores/sqldb/models/base.py +190 -0
- mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +103 -0
- mlrun/model_monitoring/{stores/models/mysql.py → db/stores/sqldb/models/sqlite.py} +19 -13
- mlrun/model_monitoring/db/stores/sqldb/sql_store.py +659 -0
- mlrun/model_monitoring/db/stores/v3io_kv/__init__.py +13 -0
- mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +726 -0
- mlrun/model_monitoring/db/tsdb/__init__.py +105 -0
- mlrun/model_monitoring/db/tsdb/base.py +448 -0
- mlrun/model_monitoring/db/tsdb/helpers.py +30 -0
- mlrun/model_monitoring/db/tsdb/tdengine/__init__.py +15 -0
- mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +279 -0
- mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +42 -0
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +507 -0
- mlrun/model_monitoring/db/tsdb/v3io/__init__.py +15 -0
- mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +158 -0
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +849 -0
- mlrun/model_monitoring/features_drift_table.py +134 -106
- mlrun/model_monitoring/helpers.py +199 -55
- mlrun/model_monitoring/metrics/__init__.py +13 -0
- mlrun/model_monitoring/metrics/histogram_distance.py +127 -0
- mlrun/model_monitoring/model_endpoint.py +3 -2
- mlrun/model_monitoring/stream_processing.py +134 -398
- mlrun/model_monitoring/tracking_policy.py +9 -2
- mlrun/model_monitoring/writer.py +161 -125
- mlrun/package/__init__.py +6 -6
- mlrun/package/context_handler.py +5 -5
- mlrun/package/packager.py +7 -7
- mlrun/package/packagers/default_packager.py +8 -8
- mlrun/package/packagers/numpy_packagers.py +15 -15
- mlrun/package/packagers/pandas_packagers.py +5 -5
- mlrun/package/packagers/python_standard_library_packagers.py +10 -10
- mlrun/package/packagers_manager.py +19 -23
- mlrun/package/utils/_formatter.py +6 -6
- mlrun/package/utils/_pickler.py +2 -2
- mlrun/package/utils/_supported_format.py +4 -4
- mlrun/package/utils/log_hint_utils.py +2 -2
- mlrun/package/utils/type_hint_utils.py +4 -9
- mlrun/platforms/__init__.py +11 -10
- mlrun/platforms/iguazio.py +24 -203
- mlrun/projects/operations.py +52 -25
- mlrun/projects/pipelines.py +191 -197
- mlrun/projects/project.py +1227 -400
- mlrun/render.py +16 -19
- mlrun/run.py +209 -184
- mlrun/runtimes/__init__.py +83 -15
- mlrun/runtimes/base.py +51 -35
- mlrun/runtimes/daskjob.py +17 -10
- mlrun/runtimes/databricks_job/databricks_cancel_task.py +1 -1
- mlrun/runtimes/databricks_job/databricks_runtime.py +8 -7
- mlrun/runtimes/databricks_job/databricks_wrapper.py +1 -1
- mlrun/runtimes/funcdoc.py +1 -29
- mlrun/runtimes/function_reference.py +1 -1
- mlrun/runtimes/kubejob.py +34 -128
- mlrun/runtimes/local.py +40 -11
- mlrun/runtimes/mpijob/__init__.py +0 -20
- mlrun/runtimes/mpijob/abstract.py +9 -10
- mlrun/runtimes/mpijob/v1.py +1 -1
- mlrun/{model_monitoring/stores/models/sqlite.py → runtimes/nuclio/__init__.py} +7 -9
- mlrun/runtimes/nuclio/api_gateway.py +769 -0
- mlrun/runtimes/nuclio/application/__init__.py +15 -0
- mlrun/runtimes/nuclio/application/application.py +758 -0
- mlrun/runtimes/nuclio/application/reverse_proxy.go +95 -0
- mlrun/runtimes/{function.py → nuclio/function.py} +200 -83
- mlrun/runtimes/{nuclio.py → nuclio/nuclio.py} +6 -6
- mlrun/runtimes/{serving.py → nuclio/serving.py} +65 -68
- mlrun/runtimes/pod.py +281 -101
- mlrun/runtimes/remotesparkjob.py +12 -9
- mlrun/runtimes/sparkjob/spark3job.py +67 -51
- mlrun/runtimes/utils.py +41 -75
- mlrun/secrets.py +9 -5
- mlrun/serving/__init__.py +8 -1
- mlrun/serving/remote.py +2 -7
- mlrun/serving/routers.py +85 -69
- mlrun/serving/server.py +69 -44
- mlrun/serving/states.py +209 -36
- mlrun/serving/utils.py +22 -14
- mlrun/serving/v1_serving.py +6 -7
- mlrun/serving/v2_serving.py +133 -54
- mlrun/track/tracker.py +2 -1
- mlrun/track/tracker_manager.py +3 -3
- mlrun/track/trackers/mlflow_tracker.py +6 -2
- mlrun/utils/async_http.py +6 -8
- mlrun/utils/azure_vault.py +1 -1
- mlrun/utils/clones.py +1 -2
- mlrun/utils/condition_evaluator.py +3 -3
- mlrun/utils/db.py +21 -3
- mlrun/utils/helpers.py +405 -225
- mlrun/utils/http.py +3 -6
- mlrun/utils/logger.py +112 -16
- mlrun/utils/notifications/notification/__init__.py +17 -13
- mlrun/utils/notifications/notification/base.py +50 -2
- mlrun/utils/notifications/notification/console.py +2 -0
- mlrun/utils/notifications/notification/git.py +24 -1
- mlrun/utils/notifications/notification/ipython.py +3 -1
- mlrun/utils/notifications/notification/slack.py +96 -21
- mlrun/utils/notifications/notification/webhook.py +59 -2
- mlrun/utils/notifications/notification_pusher.py +149 -30
- mlrun/utils/regex.py +9 -0
- mlrun/utils/retryer.py +208 -0
- mlrun/utils/singleton.py +1 -1
- mlrun/utils/v3io_clients.py +4 -6
- mlrun/utils/version/version.json +2 -2
- mlrun/utils/version/version.py +2 -6
- mlrun-1.7.0.dist-info/METADATA +378 -0
- mlrun-1.7.0.dist-info/RECORD +351 -0
- {mlrun-1.6.4rc8.dist-info → mlrun-1.7.0.dist-info}/WHEEL +1 -1
- mlrun/feature_store/retrieval/conversion.py +0 -273
- mlrun/kfpops.py +0 -868
- mlrun/model_monitoring/application.py +0 -310
- mlrun/model_monitoring/batch.py +0 -1095
- mlrun/model_monitoring/prometheus.py +0 -219
- mlrun/model_monitoring/stores/__init__.py +0 -111
- mlrun/model_monitoring/stores/kv_model_endpoint_store.py +0 -576
- mlrun/model_monitoring/stores/model_endpoint_store.py +0 -147
- mlrun/model_monitoring/stores/models/__init__.py +0 -27
- mlrun/model_monitoring/stores/models/base.py +0 -84
- mlrun/model_monitoring/stores/sql_model_endpoint_store.py +0 -384
- mlrun/platforms/other.py +0 -306
- mlrun-1.6.4rc8.dist-info/METADATA +0 -272
- mlrun-1.6.4rc8.dist-info/RECORD +0 -314
- {mlrun-1.6.4rc8.dist-info → mlrun-1.7.0.dist-info}/LICENSE +0 -0
- {mlrun-1.6.4rc8.dist-info → mlrun-1.7.0.dist-info}/entry_points.txt +0 -0
- {mlrun-1.6.4rc8.dist-info → mlrun-1.7.0.dist-info}/top_level.txt +0 -0
mlrun/utils/http.py
CHANGED
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
#
|
|
15
15
|
|
|
16
16
|
import time
|
|
17
|
-
import typing
|
|
18
17
|
|
|
19
18
|
import requests
|
|
20
19
|
import requests.adapters
|
|
@@ -96,7 +95,7 @@ class HTTPSessionWithRetry(requests.Session):
|
|
|
96
95
|
total=self.max_retries,
|
|
97
96
|
backoff_factor=self.retry_backoff_factor,
|
|
98
97
|
status_forcelist=config.http_retry_defaults.status_codes,
|
|
99
|
-
|
|
98
|
+
allowed_methods=self._retry_methods,
|
|
100
99
|
# we want to retry but not to raise since we do want that last response (to parse details on the
|
|
101
100
|
# error from response body) we'll handle raising ourselves
|
|
102
101
|
raise_on_status=False,
|
|
@@ -123,7 +122,7 @@ class HTTPSessionWithRetry(requests.Session):
|
|
|
123
122
|
|
|
124
123
|
self._logger.warning(
|
|
125
124
|
"Error during request handling, retrying",
|
|
126
|
-
exc=
|
|
125
|
+
exc=err_to_str(exc),
|
|
127
126
|
retry_count=retry_count,
|
|
128
127
|
url=url,
|
|
129
128
|
method=method,
|
|
@@ -202,9 +201,7 @@ class HTTPSessionWithRetry(requests.Session):
|
|
|
202
201
|
def _method_retryable(self, method: str):
|
|
203
202
|
return method in self._retry_methods
|
|
204
203
|
|
|
205
|
-
def _resolve_retry_methods(
|
|
206
|
-
self, retry_on_post: bool = False
|
|
207
|
-
) -> typing.FrozenSet[str]:
|
|
204
|
+
def _resolve_retry_methods(self, retry_on_post: bool = False) -> frozenset[str]:
|
|
208
205
|
methods = urllib3.util.retry.Retry.DEFAULT_ALLOWED_METHODS
|
|
209
206
|
methods = methods.union({"PATCH"})
|
|
210
207
|
if retry_on_post:
|
mlrun/utils/logger.py
CHANGED
|
@@ -12,23 +12,51 @@
|
|
|
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 logging
|
|
16
|
+
import os
|
|
17
17
|
import typing
|
|
18
18
|
from enum import Enum
|
|
19
|
+
from functools import cached_property
|
|
19
20
|
from sys import stdout
|
|
20
21
|
from traceback import format_exception
|
|
21
22
|
from typing import IO, Optional, Union
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
import orjson
|
|
25
|
+
import pydantic
|
|
24
26
|
|
|
27
|
+
from mlrun.config import config
|
|
25
28
|
|
|
26
|
-
class JSONFormatter(logging.Formatter):
|
|
27
|
-
def __init__(self):
|
|
28
|
-
super(JSONFormatter, self).__init__()
|
|
29
|
-
self._json_encoder = json.JSONEncoder()
|
|
30
29
|
|
|
31
|
-
|
|
30
|
+
class _BaseFormatter(logging.Formatter):
|
|
31
|
+
def _json_dump(self, json_object):
|
|
32
|
+
def default(obj):
|
|
33
|
+
if isinstance(obj, pydantic.BaseModel):
|
|
34
|
+
return obj.dict()
|
|
35
|
+
|
|
36
|
+
# EAFP all the way.
|
|
37
|
+
# Leave the unused "exc" in for debugging ease
|
|
38
|
+
try:
|
|
39
|
+
return obj.__log__()
|
|
40
|
+
except Exception as exc: # noqa
|
|
41
|
+
try:
|
|
42
|
+
return obj.__repr__()
|
|
43
|
+
except Exception as exc: # noqa
|
|
44
|
+
try:
|
|
45
|
+
return str(obj)
|
|
46
|
+
except Exception as exc:
|
|
47
|
+
raise TypeError from exc
|
|
48
|
+
|
|
49
|
+
return orjson.dumps(
|
|
50
|
+
json_object,
|
|
51
|
+
option=orjson.OPT_NAIVE_UTC
|
|
52
|
+
| orjson.OPT_SERIALIZE_NUMPY
|
|
53
|
+
| orjson.OPT_SORT_KEYS,
|
|
54
|
+
default=default,
|
|
55
|
+
).decode()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class JSONFormatter(_BaseFormatter):
|
|
59
|
+
def format(self, record) -> str:
|
|
32
60
|
record_with = getattr(record, "with", {})
|
|
33
61
|
if record.exc_info:
|
|
34
62
|
record_with.update(exc_info=format_exception(*record.exc_info))
|
|
@@ -39,14 +67,24 @@ class JSONFormatter(logging.Formatter):
|
|
|
39
67
|
"with": record_with,
|
|
40
68
|
}
|
|
41
69
|
|
|
42
|
-
return self.
|
|
70
|
+
return self._json_dump(record_fields)
|
|
43
71
|
|
|
44
72
|
|
|
45
|
-
class HumanReadableFormatter(
|
|
73
|
+
class HumanReadableFormatter(_BaseFormatter):
|
|
46
74
|
def format(self, record) -> str:
|
|
75
|
+
more = self._resolve_more(record)
|
|
76
|
+
return (
|
|
77
|
+
f"> {self.formatTime(record, self.datefmt)} "
|
|
78
|
+
f"[{record.levelname.lower()}] "
|
|
79
|
+
f"{record.getMessage().rstrip()}"
|
|
80
|
+
f"{more}"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def _resolve_more(self, record):
|
|
47
84
|
record_with = self._record_with(record)
|
|
48
|
-
|
|
49
|
-
|
|
85
|
+
record_with_encoded = self._json_dump(record_with) if record_with else ""
|
|
86
|
+
more = f": {record_with_encoded}" if record_with_encoded else ""
|
|
87
|
+
return more
|
|
50
88
|
|
|
51
89
|
def _record_with(self, record):
|
|
52
90
|
record_with = getattr(record, "with", {})
|
|
@@ -56,18 +94,67 @@ class HumanReadableFormatter(logging.Formatter):
|
|
|
56
94
|
|
|
57
95
|
|
|
58
96
|
class HumanReadableExtendedFormatter(HumanReadableFormatter):
|
|
97
|
+
_colors = {
|
|
98
|
+
logging.NOTSET: "",
|
|
99
|
+
logging.DEBUG: "\x1b[34m",
|
|
100
|
+
logging.INFO: "\x1b[36m",
|
|
101
|
+
logging.WARNING: "\x1b[33m",
|
|
102
|
+
logging.ERROR: "\x1b[0;31m",
|
|
103
|
+
logging.CRITICAL: "\x1b[1;31m",
|
|
104
|
+
}
|
|
105
|
+
_color_reset = "\x1b[0m"
|
|
106
|
+
|
|
59
107
|
def format(self, record) -> str:
|
|
108
|
+
more = ""
|
|
60
109
|
record_with = self._record_with(record)
|
|
61
|
-
|
|
110
|
+
if record_with:
|
|
111
|
+
|
|
112
|
+
def _format_value(val):
|
|
113
|
+
formatted_val = (
|
|
114
|
+
val
|
|
115
|
+
if isinstance(val, str)
|
|
116
|
+
else str(orjson.loads(self._json_dump(val)))
|
|
117
|
+
)
|
|
118
|
+
return (
|
|
119
|
+
formatted_val.replace("\n", "\n\t\t")
|
|
120
|
+
if len(formatted_val) < 4096
|
|
121
|
+
else repr(formatted_val)
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
more = "\n\t" + "\n\t".join(
|
|
125
|
+
[f"{key}: {_format_value(val)}" for key, val in record_with.items()]
|
|
126
|
+
)
|
|
62
127
|
return (
|
|
63
|
-
"> "
|
|
128
|
+
f"{self._get_message_color(record.levelno)}> "
|
|
64
129
|
f"{self.formatTime(record, self.datefmt)} "
|
|
65
130
|
f"[{record.name}:{record.levelname.lower()}] "
|
|
66
|
-
f"{record.getMessage()}{more}"
|
|
131
|
+
f"{record.getMessage()}{more}{self._get_color_reset()}"
|
|
67
132
|
)
|
|
68
133
|
|
|
134
|
+
def _get_color_reset(self):
|
|
135
|
+
if not self._have_color_support:
|
|
136
|
+
return ""
|
|
69
137
|
|
|
70
|
-
|
|
138
|
+
return self._color_reset
|
|
139
|
+
|
|
140
|
+
def _get_message_color(self, levelno):
|
|
141
|
+
if not self._have_color_support:
|
|
142
|
+
return ""
|
|
143
|
+
|
|
144
|
+
return self._colors[levelno]
|
|
145
|
+
|
|
146
|
+
@cached_property
|
|
147
|
+
def _have_color_support(self):
|
|
148
|
+
if os.environ.get("PYCHARM_HOSTED"):
|
|
149
|
+
return True
|
|
150
|
+
if os.environ.get("NO_COLOR"):
|
|
151
|
+
return False
|
|
152
|
+
if os.environ.get("CLICOLOR_FORCE"):
|
|
153
|
+
return True
|
|
154
|
+
return stdout.isatty()
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class Logger:
|
|
71
158
|
def __init__(
|
|
72
159
|
self,
|
|
73
160
|
level,
|
|
@@ -189,7 +276,7 @@ class FormatterKinds(Enum):
|
|
|
189
276
|
|
|
190
277
|
def resolve_formatter_by_kind(
|
|
191
278
|
formatter_kind: FormatterKinds,
|
|
192
|
-
) ->
|
|
279
|
+
) -> type[
|
|
193
280
|
typing.Union[HumanReadableFormatter, HumanReadableExtendedFormatter, JSONFormatter]
|
|
194
281
|
]:
|
|
195
282
|
return {
|
|
@@ -199,6 +286,15 @@ def resolve_formatter_by_kind(
|
|
|
199
286
|
}[formatter_kind]
|
|
200
287
|
|
|
201
288
|
|
|
289
|
+
def create_test_logger(name: str = "mlrun", stream: IO[str] = stdout) -> Logger:
|
|
290
|
+
return create_logger(
|
|
291
|
+
level="debug",
|
|
292
|
+
formatter_kind=FormatterKinds.HUMAN_EXTENDED.name,
|
|
293
|
+
name=name,
|
|
294
|
+
stream=stream,
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
|
|
202
298
|
def create_logger(
|
|
203
299
|
level: Optional[str] = None,
|
|
204
300
|
formatter_kind: str = FormatterKinds.HUMAN.name,
|
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
import enum
|
|
16
|
-
import typing
|
|
17
16
|
|
|
18
17
|
from mlrun.common.schemas.notification import NotificationKind
|
|
19
18
|
|
|
@@ -32,7 +31,7 @@ class NotificationTypes(str, enum.Enum):
|
|
|
32
31
|
slack = NotificationKind.slack.value
|
|
33
32
|
webhook = NotificationKind.webhook.value
|
|
34
33
|
|
|
35
|
-
def get_notification(self) ->
|
|
34
|
+
def get_notification(self) -> type[NotificationBase]:
|
|
36
35
|
return {
|
|
37
36
|
self.console: ConsoleNotification,
|
|
38
37
|
self.git: GitNotification,
|
|
@@ -41,7 +40,7 @@ class NotificationTypes(str, enum.Enum):
|
|
|
41
40
|
self.webhook: WebhookNotification,
|
|
42
41
|
}.get(self)
|
|
43
42
|
|
|
44
|
-
def inverse_dependencies(self) ->
|
|
43
|
+
def inverse_dependencies(self) -> list[str]:
|
|
45
44
|
"""
|
|
46
45
|
Some notifications should only run if another notification type didn't run.
|
|
47
46
|
Per given notification type, return a list of notification types that should not run in order for this
|
|
@@ -52,13 +51,18 @@ class NotificationTypes(str, enum.Enum):
|
|
|
52
51
|
}.get(self, [])
|
|
53
52
|
|
|
54
53
|
@classmethod
|
|
55
|
-
def
|
|
56
|
-
return
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
54
|
+
def local(cls) -> list[str]:
|
|
55
|
+
return [
|
|
56
|
+
cls.console,
|
|
57
|
+
cls.ipython,
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def all(cls) -> list[str]:
|
|
62
|
+
return [
|
|
63
|
+
cls.console,
|
|
64
|
+
cls.git,
|
|
65
|
+
cls.ipython,
|
|
66
|
+
cls.slack,
|
|
67
|
+
cls.webhook,
|
|
68
|
+
]
|
|
@@ -23,11 +23,15 @@ class NotificationBase:
|
|
|
23
23
|
def __init__(
|
|
24
24
|
self,
|
|
25
25
|
name: str = None,
|
|
26
|
-
params:
|
|
26
|
+
params: dict[str, str] = None,
|
|
27
27
|
):
|
|
28
28
|
self.name = name
|
|
29
29
|
self.params = params or {}
|
|
30
30
|
|
|
31
|
+
@classmethod
|
|
32
|
+
def validate_params(cls, params):
|
|
33
|
+
pass
|
|
34
|
+
|
|
31
35
|
@property
|
|
32
36
|
def active(self) -> bool:
|
|
33
37
|
return True
|
|
@@ -44,12 +48,14 @@ class NotificationBase:
|
|
|
44
48
|
] = mlrun.common.schemas.NotificationSeverity.INFO,
|
|
45
49
|
runs: typing.Union[mlrun.lists.RunList, list] = None,
|
|
46
50
|
custom_html: str = None,
|
|
51
|
+
alert: mlrun.common.schemas.AlertConfig = None,
|
|
52
|
+
event_data: mlrun.common.schemas.Event = None,
|
|
47
53
|
):
|
|
48
54
|
raise NotImplementedError()
|
|
49
55
|
|
|
50
56
|
def load_notification(
|
|
51
57
|
self,
|
|
52
|
-
params:
|
|
58
|
+
params: dict[str, str],
|
|
53
59
|
) -> None:
|
|
54
60
|
self.params = params or {}
|
|
55
61
|
|
|
@@ -61,10 +67,31 @@ class NotificationBase:
|
|
|
61
67
|
] = mlrun.common.schemas.NotificationSeverity.INFO,
|
|
62
68
|
runs: typing.Union[mlrun.lists.RunList, list] = None,
|
|
63
69
|
custom_html: str = None,
|
|
70
|
+
alert: mlrun.common.schemas.AlertConfig = None,
|
|
71
|
+
event_data: mlrun.common.schemas.Event = None,
|
|
64
72
|
) -> str:
|
|
65
73
|
if custom_html:
|
|
66
74
|
return custom_html
|
|
67
75
|
|
|
76
|
+
if alert:
|
|
77
|
+
if not event_data:
|
|
78
|
+
return f"[{severity}] {message}"
|
|
79
|
+
|
|
80
|
+
html = f"<h3>[{severity}] {message}</h3>"
|
|
81
|
+
html += f"<br>{alert.name} alert has occurred<br>"
|
|
82
|
+
html += f"<br><h4>Project:</h4>{alert.project}<br>"
|
|
83
|
+
html += f"<br><h4>ID:</h4>{event_data.entity.ids[0]}<br>"
|
|
84
|
+
html += f"<br><h4>Summary:</h4>{mlrun.utils.helpers.format_alert_summary(alert, event_data)}<br>"
|
|
85
|
+
|
|
86
|
+
if event_data.value_dict:
|
|
87
|
+
html += "<br><h4>Event data:</h4>"
|
|
88
|
+
for key, value in event_data.value_dict.items():
|
|
89
|
+
html += f"{key}: {value}<br>"
|
|
90
|
+
|
|
91
|
+
overview_type, url = self._get_overview_type_and_url(alert, event_data)
|
|
92
|
+
html += f"<br><h4>Overview:</h4><a href={url}>{overview_type}</a>"
|
|
93
|
+
return html
|
|
94
|
+
|
|
68
95
|
if self.name:
|
|
69
96
|
message = f"{self.name}: {message}"
|
|
70
97
|
|
|
@@ -78,3 +105,24 @@ class NotificationBase:
|
|
|
78
105
|
html += "<br>click the hyper links below to see detailed results<br>"
|
|
79
106
|
html += runs.show(display=False, short=True)
|
|
80
107
|
return html
|
|
108
|
+
|
|
109
|
+
def _get_overview_type_and_url(
|
|
110
|
+
self,
|
|
111
|
+
alert: mlrun.common.schemas.AlertConfig,
|
|
112
|
+
event_data: mlrun.common.schemas.Event,
|
|
113
|
+
) -> (str, str):
|
|
114
|
+
if (
|
|
115
|
+
event_data.entity.kind == mlrun.common.schemas.alert.EventEntityKind.JOB
|
|
116
|
+
): # JOB entity
|
|
117
|
+
uid = event_data.value_dict.get("uid")
|
|
118
|
+
url = mlrun.utils.helpers.get_ui_url(alert.project, uid)
|
|
119
|
+
overview_type = "Job overview"
|
|
120
|
+
else: # MODEL entity
|
|
121
|
+
model_name = event_data.value_dict.get("model")
|
|
122
|
+
model_endpoint_id = event_data.value_dict.get("model_endpoint_id")
|
|
123
|
+
url = mlrun.utils.helpers.get_model_endpoint_url(
|
|
124
|
+
alert.project, model_name, model_endpoint_id
|
|
125
|
+
)
|
|
126
|
+
overview_type = "Model endpoint"
|
|
127
|
+
|
|
128
|
+
return overview_type, url
|
|
@@ -36,6 +36,8 @@ class ConsoleNotification(NotificationBase):
|
|
|
36
36
|
] = mlrun.common.schemas.NotificationSeverity.INFO,
|
|
37
37
|
runs: typing.Union[mlrun.lists.RunList, list] = None,
|
|
38
38
|
custom_html: str = None,
|
|
39
|
+
alert: mlrun.common.schemas.AlertConfig = None,
|
|
40
|
+
event_data: mlrun.common.schemas.Event = None,
|
|
39
41
|
):
|
|
40
42
|
severity = self._resolve_severity(severity)
|
|
41
43
|
print(f"[{severity}] {message}")
|
|
@@ -30,6 +30,27 @@ class GitNotification(NotificationBase):
|
|
|
30
30
|
API/Client notification for setting a rich run statuses git issue comment (github/gitlab)
|
|
31
31
|
"""
|
|
32
32
|
|
|
33
|
+
@classmethod
|
|
34
|
+
def validate_params(cls, params):
|
|
35
|
+
git_repo = params.get("repo", None)
|
|
36
|
+
git_issue = params.get("issue", None)
|
|
37
|
+
git_merge_request = params.get("merge_request", None)
|
|
38
|
+
token = (
|
|
39
|
+
params.get("token", None)
|
|
40
|
+
or params.get("GIT_TOKEN", None)
|
|
41
|
+
or params.get("GITHUB_TOKEN", None)
|
|
42
|
+
)
|
|
43
|
+
if not git_repo:
|
|
44
|
+
raise ValueError("Parameter 'repo' is required for GitNotification")
|
|
45
|
+
|
|
46
|
+
if not token:
|
|
47
|
+
raise ValueError("Parameter 'token' is required for GitNotification")
|
|
48
|
+
|
|
49
|
+
if not git_issue and not git_merge_request:
|
|
50
|
+
raise ValueError(
|
|
51
|
+
"At least one of 'issue' or 'merge_request' is required for GitNotification"
|
|
52
|
+
)
|
|
53
|
+
|
|
33
54
|
async def push(
|
|
34
55
|
self,
|
|
35
56
|
message: str,
|
|
@@ -38,6 +59,8 @@ class GitNotification(NotificationBase):
|
|
|
38
59
|
] = mlrun.common.schemas.NotificationSeverity.INFO,
|
|
39
60
|
runs: typing.Union[mlrun.lists.RunList, list] = None,
|
|
40
61
|
custom_html: str = None,
|
|
62
|
+
alert: mlrun.common.schemas.AlertConfig = None,
|
|
63
|
+
event_data: mlrun.common.schemas.Event = None,
|
|
41
64
|
):
|
|
42
65
|
git_repo = self.params.get("repo", None)
|
|
43
66
|
git_issue = self.params.get("issue", None)
|
|
@@ -50,7 +73,7 @@ class GitNotification(NotificationBase):
|
|
|
50
73
|
server = self.params.get("server", None)
|
|
51
74
|
gitlab = self.params.get("gitlab", False)
|
|
52
75
|
await self._pr_comment(
|
|
53
|
-
self._get_html(message, severity, runs, custom_html),
|
|
76
|
+
self._get_html(message, severity, runs, custom_html, alert, event_data),
|
|
54
77
|
git_repo,
|
|
55
78
|
git_issue,
|
|
56
79
|
merge_request=git_merge_request,
|
|
@@ -29,7 +29,7 @@ class IPythonNotification(NotificationBase):
|
|
|
29
29
|
def __init__(
|
|
30
30
|
self,
|
|
31
31
|
name: str = None,
|
|
32
|
-
params:
|
|
32
|
+
params: dict[str, str] = None,
|
|
33
33
|
):
|
|
34
34
|
super().__init__(name, params)
|
|
35
35
|
self._ipython = None
|
|
@@ -53,6 +53,8 @@ class IPythonNotification(NotificationBase):
|
|
|
53
53
|
] = mlrun.common.schemas.NotificationSeverity.INFO,
|
|
54
54
|
runs: typing.Union[mlrun.lists.RunList, list] = None,
|
|
55
55
|
custom_html: str = None,
|
|
56
|
+
alert: mlrun.common.schemas.AlertConfig = None,
|
|
57
|
+
event_data: mlrun.common.schemas.Event = None,
|
|
56
58
|
):
|
|
57
59
|
if not self._ipython:
|
|
58
60
|
mlrun.utils.helpers.logger.debug(
|
|
@@ -32,8 +32,17 @@ class SlackNotification(NotificationBase):
|
|
|
32
32
|
"completed": ":smiley:",
|
|
33
33
|
"running": ":man-running:",
|
|
34
34
|
"error": ":x:",
|
|
35
|
+
"skipped": ":zzz:",
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
@classmethod
|
|
39
|
+
def validate_params(cls, params):
|
|
40
|
+
webhook = params.get("webhook", None) or mlrun.get_secret_or_env(
|
|
41
|
+
"SLACK_WEBHOOK"
|
|
42
|
+
)
|
|
43
|
+
if not webhook:
|
|
44
|
+
raise ValueError("Parameter 'webhook' is required for SlackNotification")
|
|
45
|
+
|
|
37
46
|
async def push(
|
|
38
47
|
self,
|
|
39
48
|
message: str,
|
|
@@ -42,6 +51,8 @@ class SlackNotification(NotificationBase):
|
|
|
42
51
|
] = mlrun.common.schemas.NotificationSeverity.INFO,
|
|
43
52
|
runs: typing.Union[mlrun.lists.RunList, list] = None,
|
|
44
53
|
custom_html: str = None,
|
|
54
|
+
alert: mlrun.common.schemas.AlertConfig = None,
|
|
55
|
+
event_data: mlrun.common.schemas.Event = None,
|
|
45
56
|
):
|
|
46
57
|
webhook = self.params.get("webhook", None) or mlrun.get_secret_or_env(
|
|
47
58
|
"SLACK_WEBHOOK"
|
|
@@ -53,7 +64,7 @@ class SlackNotification(NotificationBase):
|
|
|
53
64
|
)
|
|
54
65
|
return
|
|
55
66
|
|
|
56
|
-
data = self._generate_slack_data(message, severity, runs)
|
|
67
|
+
data = self._generate_slack_data(message, severity, runs, alert, event_data)
|
|
57
68
|
|
|
58
69
|
async with aiohttp.ClientSession() as session:
|
|
59
70
|
async with session.post(webhook, json=data) as response:
|
|
@@ -66,57 +77,121 @@ class SlackNotification(NotificationBase):
|
|
|
66
77
|
mlrun.common.schemas.NotificationSeverity, str
|
|
67
78
|
] = mlrun.common.schemas.NotificationSeverity.INFO,
|
|
68
79
|
runs: typing.Union[mlrun.lists.RunList, list] = None,
|
|
80
|
+
alert: mlrun.common.schemas.AlertConfig = None,
|
|
81
|
+
event_data: mlrun.common.schemas.Event = None,
|
|
69
82
|
) -> dict:
|
|
70
83
|
data = {
|
|
71
|
-
"blocks":
|
|
72
|
-
{
|
|
73
|
-
"type": "section",
|
|
74
|
-
"text": self._get_slack_row(f"[{severity}] {message}"),
|
|
75
|
-
},
|
|
76
|
-
]
|
|
84
|
+
"blocks": self._generate_slack_header_blocks(severity, message),
|
|
77
85
|
}
|
|
78
86
|
if self.name:
|
|
79
87
|
data["blocks"].append(
|
|
80
88
|
{"type": "section", "text": self._get_slack_row(self.name)}
|
|
81
89
|
)
|
|
82
90
|
|
|
83
|
-
if
|
|
84
|
-
|
|
91
|
+
if alert:
|
|
92
|
+
fields = self._get_alert_fields(alert, event_data)
|
|
85
93
|
|
|
86
|
-
|
|
87
|
-
|
|
94
|
+
for i in range(len(fields)):
|
|
95
|
+
data["blocks"].append({"type": "section", "text": fields[i]})
|
|
96
|
+
else:
|
|
97
|
+
if not runs:
|
|
98
|
+
return data
|
|
99
|
+
|
|
100
|
+
if isinstance(runs, list):
|
|
101
|
+
runs = mlrun.lists.RunList(runs)
|
|
88
102
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
103
|
+
fields = [self._get_slack_row("*Runs*"), self._get_slack_row("*Results*")]
|
|
104
|
+
for run in runs:
|
|
105
|
+
fields.append(self._get_run_line(run))
|
|
106
|
+
fields.append(self._get_run_result(run))
|
|
93
107
|
|
|
94
|
-
|
|
95
|
-
|
|
108
|
+
for i in range(0, len(fields), 8):
|
|
109
|
+
data["blocks"].append({"type": "section", "fields": fields[i : i + 8]})
|
|
96
110
|
|
|
97
111
|
return data
|
|
98
112
|
|
|
113
|
+
def _generate_slack_header_blocks(self, severity: str, message: str):
|
|
114
|
+
header_text = block_text = f"[{severity}] {message}"
|
|
115
|
+
section_text = None
|
|
116
|
+
|
|
117
|
+
# Slack doesn't allow headers to be longer than 150 characters
|
|
118
|
+
# If there's a comma in the message, split the message at the comma
|
|
119
|
+
# Otherwise, split the message at 150 characters
|
|
120
|
+
if len(block_text) > 150:
|
|
121
|
+
if ", " in block_text and block_text.index(", ") < 149:
|
|
122
|
+
header_text = block_text.split(",")[0]
|
|
123
|
+
section_text = block_text[len(header_text) + 2 :]
|
|
124
|
+
else:
|
|
125
|
+
header_text = block_text[:150]
|
|
126
|
+
section_text = block_text[150:]
|
|
127
|
+
blocks = [
|
|
128
|
+
{"type": "header", "text": {"type": "plain_text", "text": header_text}}
|
|
129
|
+
]
|
|
130
|
+
if section_text:
|
|
131
|
+
blocks.append(
|
|
132
|
+
{
|
|
133
|
+
"type": "section",
|
|
134
|
+
"text": self._get_slack_row(section_text),
|
|
135
|
+
}
|
|
136
|
+
)
|
|
137
|
+
return blocks
|
|
138
|
+
|
|
139
|
+
def _get_alert_fields(
|
|
140
|
+
self,
|
|
141
|
+
alert: mlrun.common.schemas.AlertConfig,
|
|
142
|
+
event_data: mlrun.common.schemas.Event,
|
|
143
|
+
) -> list:
|
|
144
|
+
line = [
|
|
145
|
+
self._get_slack_row(f":bell: {alert.name} alert has occurred"),
|
|
146
|
+
self._get_slack_row(f"*Project:*\n{alert.project}"),
|
|
147
|
+
self._get_slack_row(f"*ID:*\n{event_data.entity.ids[0]}"),
|
|
148
|
+
]
|
|
149
|
+
|
|
150
|
+
if alert.summary:
|
|
151
|
+
line.append(
|
|
152
|
+
self._get_slack_row(
|
|
153
|
+
f"*Summary:*\n{mlrun.utils.helpers.format_alert_summary(alert, event_data)}"
|
|
154
|
+
)
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
if event_data.value_dict:
|
|
158
|
+
data_lines = []
|
|
159
|
+
for key, value in event_data.value_dict.items():
|
|
160
|
+
data_lines.append(f"{key}: {value}")
|
|
161
|
+
data_text = "\n".join(data_lines)
|
|
162
|
+
line.append(self._get_slack_row(f"*Event data:*\n{data_text}"))
|
|
163
|
+
|
|
164
|
+
overview_type, url = self._get_overview_type_and_url(alert, event_data)
|
|
165
|
+
line.append(self._get_slack_row(f"*Overview:*\n<{url}|*{overview_type}*>"))
|
|
166
|
+
|
|
167
|
+
return line
|
|
168
|
+
|
|
99
169
|
def _get_run_line(self, run: dict) -> dict:
|
|
100
170
|
meta = run["metadata"]
|
|
101
171
|
url = mlrun.utils.helpers.get_ui_url(meta.get("project"), meta.get("uid"))
|
|
102
|
-
|
|
172
|
+
|
|
173
|
+
# Only show the URL if the run is not a function (serving or mlrun function)
|
|
174
|
+
kind = run.get("step_kind")
|
|
175
|
+
state = run["status"].get("state", "")
|
|
176
|
+
if state != "skipped" and (url and not kind or kind == "run"):
|
|
103
177
|
line = f'<{url}|*{meta.get("name")}*>'
|
|
104
178
|
else:
|
|
105
179
|
line = meta.get("name")
|
|
106
|
-
|
|
180
|
+
if kind:
|
|
181
|
+
line = f'{line} *({run.get("step_kind", run.get("kind", ""))})*'
|
|
107
182
|
line = f'{self.emojis.get(state, ":question:")} {line}'
|
|
108
183
|
return self._get_slack_row(line)
|
|
109
184
|
|
|
110
185
|
def _get_run_result(self, run: dict) -> dict:
|
|
111
186
|
state = run["status"].get("state", "")
|
|
112
187
|
if state == "error":
|
|
113
|
-
error_status = run["status"].get("error", "")
|
|
188
|
+
error_status = run["status"].get("error", "") or state
|
|
114
189
|
result = f"*{error_status}*"
|
|
115
190
|
else:
|
|
116
191
|
result = mlrun.utils.helpers.dict_to_str(
|
|
117
192
|
run["status"].get("results", {}), ", "
|
|
118
193
|
)
|
|
119
|
-
return self._get_slack_row(result or
|
|
194
|
+
return self._get_slack_row(result or state)
|
|
120
195
|
|
|
121
196
|
@staticmethod
|
|
122
197
|
def _get_slack_row(text: str) -> dict:
|