mlrun 1.6.4rc2__py3-none-any.whl → 1.7.0rc20__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 +26 -112
- mlrun/alerts/__init__.py +15 -0
- mlrun/alerts/alert.py +144 -0
- mlrun/api/schemas/__init__.py +5 -4
- mlrun/artifacts/__init__.py +8 -3
- mlrun/artifacts/base.py +46 -257
- mlrun/artifacts/dataset.py +11 -192
- mlrun/artifacts/manager.py +47 -48
- mlrun/artifacts/model.py +31 -159
- mlrun/artifacts/plots.py +23 -380
- mlrun/common/constants.py +69 -0
- mlrun/common/db/sql_session.py +2 -3
- mlrun/common/formatters/__init__.py +19 -0
- mlrun/common/formatters/artifact.py +21 -0
- mlrun/common/formatters/base.py +78 -0
- mlrun/common/formatters/function.py +41 -0
- mlrun/common/formatters/pipeline.py +53 -0
- mlrun/common/formatters/project.py +51 -0
- mlrun/common/helpers.py +1 -2
- mlrun/common/model_monitoring/helpers.py +9 -5
- mlrun/{runtimes → common/runtimes}/constants.py +37 -9
- mlrun/common/schemas/__init__.py +24 -4
- mlrun/common/schemas/alert.py +203 -0
- mlrun/common/schemas/api_gateway.py +148 -0
- mlrun/common/schemas/artifact.py +18 -8
- mlrun/common/schemas/auth.py +11 -5
- mlrun/common/schemas/background_task.py +1 -1
- mlrun/common/schemas/client_spec.py +4 -1
- mlrun/common/schemas/feature_store.py +16 -16
- mlrun/common/schemas/frontend_spec.py +8 -7
- mlrun/common/schemas/function.py +5 -1
- mlrun/common/schemas/hub.py +11 -18
- mlrun/common/schemas/memory_reports.py +2 -2
- mlrun/common/schemas/model_monitoring/__init__.py +18 -3
- mlrun/common/schemas/model_monitoring/constants.py +83 -26
- mlrun/common/schemas/model_monitoring/grafana.py +13 -9
- mlrun/common/schemas/model_monitoring/model_endpoints.py +99 -16
- mlrun/common/schemas/notification.py +4 -4
- mlrun/common/schemas/object.py +2 -2
- mlrun/{runtimes/mpijob/v1alpha1.py → common/schemas/pagination.py} +10 -13
- mlrun/common/schemas/pipeline.py +1 -10
- mlrun/common/schemas/project.py +24 -23
- mlrun/common/schemas/runtime_resource.py +8 -12
- mlrun/common/schemas/schedule.py +3 -3
- mlrun/common/schemas/tag.py +1 -2
- mlrun/common/schemas/workflow.py +2 -2
- mlrun/common/types.py +7 -1
- mlrun/config.py +54 -17
- mlrun/data_types/to_pandas.py +10 -12
- mlrun/datastore/__init__.py +5 -8
- mlrun/datastore/alibaba_oss.py +130 -0
- mlrun/datastore/azure_blob.py +17 -5
- mlrun/datastore/base.py +62 -39
- mlrun/datastore/datastore.py +28 -9
- mlrun/datastore/datastore_profile.py +146 -20
- mlrun/datastore/filestore.py +0 -1
- mlrun/datastore/google_cloud_storage.py +6 -2
- mlrun/datastore/hdfs.py +56 -0
- mlrun/datastore/inmem.py +2 -2
- mlrun/datastore/redis.py +6 -2
- mlrun/datastore/s3.py +9 -0
- mlrun/datastore/snowflake_utils.py +43 -0
- mlrun/datastore/sources.py +201 -96
- mlrun/datastore/spark_utils.py +1 -2
- mlrun/datastore/store_resources.py +7 -7
- mlrun/datastore/targets.py +358 -104
- mlrun/datastore/utils.py +72 -58
- mlrun/datastore/v3io.py +5 -1
- mlrun/db/base.py +185 -35
- mlrun/db/factory.py +1 -1
- mlrun/db/httpdb.py +614 -179
- mlrun/db/nopdb.py +210 -26
- mlrun/errors.py +12 -1
- mlrun/execution.py +41 -24
- mlrun/feature_store/__init__.py +0 -2
- mlrun/feature_store/api.py +40 -72
- mlrun/feature_store/common.py +1 -1
- mlrun/feature_store/feature_set.py +76 -55
- mlrun/feature_store/feature_vector.py +28 -30
- mlrun/feature_store/ingestion.py +7 -6
- mlrun/feature_store/retrieval/base.py +16 -11
- mlrun/feature_store/retrieval/conversion.py +11 -13
- mlrun/feature_store/retrieval/dask_merger.py +2 -0
- mlrun/feature_store/retrieval/job.py +9 -3
- mlrun/feature_store/retrieval/local_merger.py +2 -0
- mlrun/feature_store/retrieval/spark_merger.py +34 -24
- mlrun/feature_store/steps.py +37 -34
- mlrun/features.py +9 -20
- 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 +2 -3
- 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 +1 -1
- 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 +4 -3
- 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 +3 -6
- 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 +14 -16
- mlrun/launcher/__init__.py +1 -1
- mlrun/launcher/base.py +16 -15
- mlrun/launcher/client.py +8 -6
- mlrun/launcher/factory.py +1 -1
- mlrun/launcher/local.py +17 -11
- mlrun/launcher/remote.py +16 -10
- mlrun/lists.py +7 -6
- mlrun/model.py +238 -73
- mlrun/model_monitoring/__init__.py +1 -1
- mlrun/model_monitoring/api.py +138 -315
- mlrun/model_monitoring/application.py +5 -296
- mlrun/model_monitoring/applications/__init__.py +24 -0
- mlrun/model_monitoring/applications/_application_steps.py +157 -0
- mlrun/model_monitoring/applications/base.py +282 -0
- mlrun/model_monitoring/applications/context.py +214 -0
- mlrun/model_monitoring/applications/evidently_base.py +211 -0
- mlrun/model_monitoring/applications/histogram_data_drift.py +349 -0
- mlrun/model_monitoring/applications/results.py +99 -0
- mlrun/model_monitoring/controller.py +104 -84
- mlrun/model_monitoring/controller_handler.py +13 -5
- mlrun/model_monitoring/db/__init__.py +18 -0
- mlrun/model_monitoring/{stores → db/stores}/__init__.py +43 -36
- mlrun/model_monitoring/db/stores/base/__init__.py +15 -0
- mlrun/model_monitoring/{stores/model_endpoint_store.py → db/stores/base/store.py} +64 -40
- mlrun/model_monitoring/db/stores/sqldb/__init__.py +13 -0
- mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +71 -0
- mlrun/model_monitoring/{stores → db/stores/sqldb}/models/base.py +109 -5
- mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +88 -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 +684 -0
- mlrun/model_monitoring/db/stores/v3io_kv/__init__.py +13 -0
- mlrun/model_monitoring/{stores/kv_model_endpoint_store.py → db/stores/v3io_kv/kv_store.py} +310 -165
- mlrun/model_monitoring/db/tsdb/__init__.py +100 -0
- mlrun/model_monitoring/db/tsdb/base.py +329 -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 +240 -0
- mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +45 -0
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +397 -0
- mlrun/model_monitoring/db/tsdb/v3io/__init__.py +15 -0
- mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +117 -0
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +630 -0
- mlrun/model_monitoring/evidently_application.py +6 -118
- mlrun/model_monitoring/features_drift_table.py +134 -106
- mlrun/model_monitoring/helpers.py +127 -28
- 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/prometheus.py +1 -4
- mlrun/model_monitoring/stream_processing.py +62 -231
- mlrun/model_monitoring/tracking_policy.py +9 -2
- mlrun/model_monitoring/writer.py +152 -124
- 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 +6 -6
- 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 +35 -21
- mlrun/projects/pipelines.py +68 -99
- mlrun/projects/project.py +830 -266
- mlrun/render.py +3 -11
- mlrun/run.py +162 -166
- mlrun/runtimes/__init__.py +62 -7
- mlrun/runtimes/base.py +39 -32
- mlrun/runtimes/daskjob.py +8 -8
- mlrun/runtimes/databricks_job/databricks_cancel_task.py +1 -1
- mlrun/runtimes/databricks_job/databricks_runtime.py +7 -7
- mlrun/runtimes/databricks_job/databricks_wrapper.py +1 -1
- mlrun/runtimes/funcdoc.py +0 -28
- mlrun/runtimes/function_reference.py +1 -1
- mlrun/runtimes/kubejob.py +28 -122
- mlrun/runtimes/local.py +6 -3
- 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 +709 -0
- mlrun/runtimes/nuclio/application/__init__.py +15 -0
- mlrun/runtimes/nuclio/application/application.py +523 -0
- mlrun/runtimes/nuclio/application/reverse_proxy.go +95 -0
- mlrun/runtimes/{function.py → nuclio/function.py} +112 -73
- mlrun/runtimes/{nuclio.py → nuclio/nuclio.py} +6 -6
- mlrun/runtimes/{serving.py → nuclio/serving.py} +45 -51
- mlrun/runtimes/pod.py +286 -88
- mlrun/runtimes/remotesparkjob.py +2 -2
- mlrun/runtimes/sparkjob/spark3job.py +51 -34
- mlrun/runtimes/utils.py +7 -75
- mlrun/secrets.py +9 -5
- mlrun/serving/remote.py +2 -7
- mlrun/serving/routers.py +13 -10
- mlrun/serving/server.py +22 -26
- mlrun/serving/states.py +99 -25
- mlrun/serving/utils.py +3 -3
- mlrun/serving/v1_serving.py +6 -7
- mlrun/serving/v2_serving.py +59 -20
- mlrun/track/tracker.py +2 -1
- mlrun/track/tracker_manager.py +3 -3
- mlrun/track/trackers/mlflow_tracker.py +1 -2
- mlrun/utils/async_http.py +5 -7
- mlrun/utils/azure_vault.py +1 -1
- mlrun/utils/clones.py +1 -2
- mlrun/utils/condition_evaluator.py +3 -3
- mlrun/utils/db.py +3 -3
- mlrun/utils/helpers.py +183 -197
- mlrun/utils/http.py +2 -5
- mlrun/utils/logger.py +76 -14
- mlrun/utils/notifications/notification/__init__.py +17 -12
- mlrun/utils/notifications/notification/base.py +14 -2
- mlrun/utils/notifications/notification/console.py +2 -0
- mlrun/utils/notifications/notification/git.py +3 -1
- mlrun/utils/notifications/notification/ipython.py +3 -1
- mlrun/utils/notifications/notification/slack.py +101 -21
- mlrun/utils/notifications/notification/webhook.py +11 -1
- mlrun/utils/notifications/notification_pusher.py +155 -30
- mlrun/utils/retryer.py +208 -0
- mlrun/utils/singleton.py +1 -1
- mlrun/utils/v3io_clients.py +2 -4
- mlrun/utils/version/version.json +2 -2
- mlrun/utils/version/version.py +2 -6
- {mlrun-1.6.4rc2.dist-info → mlrun-1.7.0rc20.dist-info}/METADATA +31 -19
- mlrun-1.7.0rc20.dist-info/RECORD +353 -0
- mlrun/kfpops.py +0 -868
- mlrun/model_monitoring/batch.py +0 -1095
- mlrun/model_monitoring/stores/models/__init__.py +0 -27
- mlrun/model_monitoring/stores/sql_model_endpoint_store.py +0 -384
- mlrun/platforms/other.py +0 -306
- mlrun-1.6.4rc2.dist-info/RECORD +0 -314
- {mlrun-1.6.4rc2.dist-info → mlrun-1.7.0rc20.dist-info}/LICENSE +0 -0
- {mlrun-1.6.4rc2.dist-info → mlrun-1.7.0rc20.dist-info}/WHEEL +0 -0
- {mlrun-1.6.4rc2.dist-info → mlrun-1.7.0rc20.dist-info}/entry_points.txt +0 -0
- {mlrun-1.6.4rc2.dist-info → mlrun-1.7.0rc20.dist-info}/top_level.txt +0 -0
mlrun/utils/logger.py
CHANGED
|
@@ -12,7 +12,6 @@
|
|
|
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
|
|
17
16
|
import typing
|
|
18
17
|
from enum import Enum
|
|
@@ -20,15 +19,42 @@ from sys import stdout
|
|
|
20
19
|
from traceback import format_exception
|
|
21
20
|
from typing import IO, Optional, Union
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
import orjson
|
|
23
|
+
import pydantic
|
|
24
24
|
|
|
25
|
+
from mlrun.config import config
|
|
25
26
|
|
|
26
|
-
class JSONFormatter(logging.Formatter):
|
|
27
|
-
def __init__(self):
|
|
28
|
-
super(JSONFormatter, self).__init__()
|
|
29
|
-
self._json_encoder = json.JSONEncoder()
|
|
30
27
|
|
|
31
|
-
|
|
28
|
+
class _BaseFormatter(logging.Formatter):
|
|
29
|
+
def _json_dump(self, json_object):
|
|
30
|
+
def default(obj):
|
|
31
|
+
if isinstance(obj, pydantic.BaseModel):
|
|
32
|
+
return obj.dict()
|
|
33
|
+
|
|
34
|
+
# EAFP all the way.
|
|
35
|
+
# Leave the unused "exc" in for debugging ease
|
|
36
|
+
try:
|
|
37
|
+
return obj.__log__()
|
|
38
|
+
except Exception as exc: # noqa
|
|
39
|
+
try:
|
|
40
|
+
return obj.__repr__()
|
|
41
|
+
except Exception as exc: # noqa
|
|
42
|
+
try:
|
|
43
|
+
return str(obj)
|
|
44
|
+
except Exception as exc:
|
|
45
|
+
raise TypeError from exc
|
|
46
|
+
|
|
47
|
+
return orjson.dumps(
|
|
48
|
+
json_object,
|
|
49
|
+
option=orjson.OPT_NAIVE_UTC
|
|
50
|
+
| orjson.OPT_SERIALIZE_NUMPY
|
|
51
|
+
| orjson.OPT_SORT_KEYS,
|
|
52
|
+
default=default,
|
|
53
|
+
).decode()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class JSONFormatter(_BaseFormatter):
|
|
57
|
+
def format(self, record) -> str:
|
|
32
58
|
record_with = getattr(record, "with", {})
|
|
33
59
|
if record.exc_info:
|
|
34
60
|
record_with.update(exc_info=format_exception(*record.exc_info))
|
|
@@ -39,14 +65,24 @@ class JSONFormatter(logging.Formatter):
|
|
|
39
65
|
"with": record_with,
|
|
40
66
|
}
|
|
41
67
|
|
|
42
|
-
return self.
|
|
68
|
+
return self._json_dump(record_fields)
|
|
43
69
|
|
|
44
70
|
|
|
45
|
-
class HumanReadableFormatter(
|
|
71
|
+
class HumanReadableFormatter(_BaseFormatter):
|
|
46
72
|
def format(self, record) -> str:
|
|
73
|
+
more = self._resolve_more(record)
|
|
74
|
+
return (
|
|
75
|
+
f"> {self.formatTime(record, self.datefmt)} "
|
|
76
|
+
f"[{record.levelname.lower()}] "
|
|
77
|
+
f"{record.getMessage().rstrip()}"
|
|
78
|
+
f"{more}"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
def _resolve_more(self, record):
|
|
47
82
|
record_with = self._record_with(record)
|
|
48
|
-
|
|
49
|
-
|
|
83
|
+
record_with_encoded = self._json_dump(record_with) if record_with else ""
|
|
84
|
+
more = f": {record_with_encoded}" if record_with_encoded else ""
|
|
85
|
+
return more
|
|
50
86
|
|
|
51
87
|
def _record_with(self, record):
|
|
52
88
|
record_with = getattr(record, "with", {})
|
|
@@ -57,8 +93,25 @@ class HumanReadableFormatter(logging.Formatter):
|
|
|
57
93
|
|
|
58
94
|
class HumanReadableExtendedFormatter(HumanReadableFormatter):
|
|
59
95
|
def format(self, record) -> str:
|
|
96
|
+
more = ""
|
|
60
97
|
record_with = self._record_with(record)
|
|
61
|
-
|
|
98
|
+
if record_with:
|
|
99
|
+
|
|
100
|
+
def _format_value(val):
|
|
101
|
+
formatted_val = (
|
|
102
|
+
val
|
|
103
|
+
if isinstance(val, str)
|
|
104
|
+
else str(orjson.loads(self._json_dump(val)))
|
|
105
|
+
)
|
|
106
|
+
return (
|
|
107
|
+
formatted_val.replace("\n", "\n\t\t")
|
|
108
|
+
if len(formatted_val) < 4096
|
|
109
|
+
else repr(formatted_val)
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
more = "\n\t" + "\n\t".join(
|
|
113
|
+
[f"{key}: {_format_value(val)}" for key, val in record_with.items()]
|
|
114
|
+
)
|
|
62
115
|
return (
|
|
63
116
|
"> "
|
|
64
117
|
f"{self.formatTime(record, self.datefmt)} "
|
|
@@ -67,7 +120,7 @@ class HumanReadableExtendedFormatter(HumanReadableFormatter):
|
|
|
67
120
|
)
|
|
68
121
|
|
|
69
122
|
|
|
70
|
-
class Logger
|
|
123
|
+
class Logger:
|
|
71
124
|
def __init__(
|
|
72
125
|
self,
|
|
73
126
|
level,
|
|
@@ -189,7 +242,7 @@ class FormatterKinds(Enum):
|
|
|
189
242
|
|
|
190
243
|
def resolve_formatter_by_kind(
|
|
191
244
|
formatter_kind: FormatterKinds,
|
|
192
|
-
) ->
|
|
245
|
+
) -> type[
|
|
193
246
|
typing.Union[HumanReadableFormatter, HumanReadableExtendedFormatter, JSONFormatter]
|
|
194
247
|
]:
|
|
195
248
|
return {
|
|
@@ -199,6 +252,15 @@ def resolve_formatter_by_kind(
|
|
|
199
252
|
}[formatter_kind]
|
|
200
253
|
|
|
201
254
|
|
|
255
|
+
def create_test_logger(name: str = "mlrun", stream: IO[str] = stdout) -> Logger:
|
|
256
|
+
return create_logger(
|
|
257
|
+
level="debug",
|
|
258
|
+
formatter_kind=FormatterKinds.HUMAN_EXTENDED.name,
|
|
259
|
+
name=name,
|
|
260
|
+
stream=stream,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
|
|
202
264
|
def create_logger(
|
|
203
265
|
level: Optional[str] = None,
|
|
204
266
|
formatter_kind: str = FormatterKinds.HUMAN.name,
|
|
@@ -32,7 +32,7 @@ class NotificationTypes(str, enum.Enum):
|
|
|
32
32
|
slack = NotificationKind.slack.value
|
|
33
33
|
webhook = NotificationKind.webhook.value
|
|
34
34
|
|
|
35
|
-
def get_notification(self) ->
|
|
35
|
+
def get_notification(self) -> type[NotificationBase]:
|
|
36
36
|
return {
|
|
37
37
|
self.console: ConsoleNotification,
|
|
38
38
|
self.git: GitNotification,
|
|
@@ -41,7 +41,7 @@ class NotificationTypes(str, enum.Enum):
|
|
|
41
41
|
self.webhook: WebhookNotification,
|
|
42
42
|
}.get(self)
|
|
43
43
|
|
|
44
|
-
def inverse_dependencies(self) ->
|
|
44
|
+
def inverse_dependencies(self) -> list[str]:
|
|
45
45
|
"""
|
|
46
46
|
Some notifications should only run if another notification type didn't run.
|
|
47
47
|
Per given notification type, return a list of notification types that should not run in order for this
|
|
@@ -52,13 +52,18 @@ class NotificationTypes(str, enum.Enum):
|
|
|
52
52
|
}.get(self, [])
|
|
53
53
|
|
|
54
54
|
@classmethod
|
|
55
|
-
def
|
|
56
|
-
return
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
55
|
+
def local(cls) -> list[str]:
|
|
56
|
+
return [
|
|
57
|
+
cls.console,
|
|
58
|
+
cls.ipython,
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
@classmethod
|
|
62
|
+
def all(cls) -> list[str]:
|
|
63
|
+
return [
|
|
64
|
+
cls.console,
|
|
65
|
+
cls.git,
|
|
66
|
+
cls.ipython,
|
|
67
|
+
cls.slack,
|
|
68
|
+
cls.webhook,
|
|
69
|
+
]
|
|
@@ -23,7 +23,7 @@ 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 {}
|
|
@@ -44,12 +44,14 @@ class NotificationBase:
|
|
|
44
44
|
] = mlrun.common.schemas.NotificationSeverity.INFO,
|
|
45
45
|
runs: typing.Union[mlrun.lists.RunList, list] = None,
|
|
46
46
|
custom_html: str = None,
|
|
47
|
+
alert: mlrun.common.schemas.AlertConfig = None,
|
|
48
|
+
event_data: mlrun.common.schemas.Event = None,
|
|
47
49
|
):
|
|
48
50
|
raise NotImplementedError()
|
|
49
51
|
|
|
50
52
|
def load_notification(
|
|
51
53
|
self,
|
|
52
|
-
params:
|
|
54
|
+
params: dict[str, str],
|
|
53
55
|
) -> None:
|
|
54
56
|
self.params = params or {}
|
|
55
57
|
|
|
@@ -61,6 +63,8 @@ class NotificationBase:
|
|
|
61
63
|
] = mlrun.common.schemas.NotificationSeverity.INFO,
|
|
62
64
|
runs: typing.Union[mlrun.lists.RunList, list] = None,
|
|
63
65
|
custom_html: str = None,
|
|
66
|
+
alert: mlrun.common.schemas.AlertConfig = None,
|
|
67
|
+
event_data: mlrun.common.schemas.Event = None,
|
|
64
68
|
) -> str:
|
|
65
69
|
if custom_html:
|
|
66
70
|
return custom_html
|
|
@@ -68,6 +72,14 @@ class NotificationBase:
|
|
|
68
72
|
if self.name:
|
|
69
73
|
message = f"{self.name}: {message}"
|
|
70
74
|
|
|
75
|
+
if alert:
|
|
76
|
+
if not event_data:
|
|
77
|
+
return f"[{severity}] {message}"
|
|
78
|
+
return (
|
|
79
|
+
f"[{severity}] {message} for project {alert.project} "
|
|
80
|
+
f"UID {event_data.entity.ids[0]}. Values {event_data.value_dict}"
|
|
81
|
+
)
|
|
82
|
+
|
|
71
83
|
if not runs:
|
|
72
84
|
return f"[{severity}] {message}"
|
|
73
85
|
|
|
@@ -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}")
|
|
@@ -38,6 +38,8 @@ class GitNotification(NotificationBase):
|
|
|
38
38
|
] = mlrun.common.schemas.NotificationSeverity.INFO,
|
|
39
39
|
runs: typing.Union[mlrun.lists.RunList, list] = None,
|
|
40
40
|
custom_html: str = None,
|
|
41
|
+
alert: mlrun.common.schemas.AlertConfig = None,
|
|
42
|
+
event_data: mlrun.common.schemas.Event = None,
|
|
41
43
|
):
|
|
42
44
|
git_repo = self.params.get("repo", None)
|
|
43
45
|
git_issue = self.params.get("issue", None)
|
|
@@ -50,7 +52,7 @@ class GitNotification(NotificationBase):
|
|
|
50
52
|
server = self.params.get("server", None)
|
|
51
53
|
gitlab = self.params.get("gitlab", False)
|
|
52
54
|
await self._pr_comment(
|
|
53
|
-
self._get_html(message, severity, runs, custom_html),
|
|
55
|
+
self._get_html(message, severity, runs, custom_html, alert, event_data),
|
|
54
56
|
git_repo,
|
|
55
57
|
git_issue,
|
|
56
58
|
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,6 +32,7 @@ class SlackNotification(NotificationBase):
|
|
|
32
32
|
"completed": ":smiley:",
|
|
33
33
|
"running": ":man-running:",
|
|
34
34
|
"error": ":x:",
|
|
35
|
+
"skipped": ":zzz:",
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
async def push(
|
|
@@ -42,6 +43,8 @@ class SlackNotification(NotificationBase):
|
|
|
42
43
|
] = mlrun.common.schemas.NotificationSeverity.INFO,
|
|
43
44
|
runs: typing.Union[mlrun.lists.RunList, list] = None,
|
|
44
45
|
custom_html: str = None,
|
|
46
|
+
alert: mlrun.common.schemas.AlertConfig = None,
|
|
47
|
+
event_data: mlrun.common.schemas.Event = None,
|
|
45
48
|
):
|
|
46
49
|
webhook = self.params.get("webhook", None) or mlrun.get_secret_or_env(
|
|
47
50
|
"SLACK_WEBHOOK"
|
|
@@ -53,7 +56,7 @@ class SlackNotification(NotificationBase):
|
|
|
53
56
|
)
|
|
54
57
|
return
|
|
55
58
|
|
|
56
|
-
data = self._generate_slack_data(message, severity, runs)
|
|
59
|
+
data = self._generate_slack_data(message, severity, runs, alert, event_data)
|
|
57
60
|
|
|
58
61
|
async with aiohttp.ClientSession() as session:
|
|
59
62
|
async with session.post(webhook, json=data) as response:
|
|
@@ -66,57 +69,134 @@ class SlackNotification(NotificationBase):
|
|
|
66
69
|
mlrun.common.schemas.NotificationSeverity, str
|
|
67
70
|
] = mlrun.common.schemas.NotificationSeverity.INFO,
|
|
68
71
|
runs: typing.Union[mlrun.lists.RunList, list] = None,
|
|
72
|
+
alert: mlrun.common.schemas.AlertConfig = None,
|
|
73
|
+
event_data: mlrun.common.schemas.Event = None,
|
|
69
74
|
) -> dict:
|
|
70
75
|
data = {
|
|
71
|
-
"blocks":
|
|
72
|
-
{
|
|
73
|
-
"type": "section",
|
|
74
|
-
"text": self._get_slack_row(f"[{severity}] {message}"),
|
|
75
|
-
},
|
|
76
|
-
]
|
|
76
|
+
"blocks": self._generate_slack_header_blocks(severity, message),
|
|
77
77
|
}
|
|
78
78
|
if self.name:
|
|
79
79
|
data["blocks"].append(
|
|
80
80
|
{"type": "section", "text": self._get_slack_row(self.name)}
|
|
81
81
|
)
|
|
82
82
|
|
|
83
|
-
if
|
|
84
|
-
|
|
83
|
+
if alert:
|
|
84
|
+
fields = self._get_alert_fields(alert, event_data)
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
for i in range(len(fields)):
|
|
87
|
+
data["blocks"].append({"type": "section", "text": fields[i]})
|
|
88
|
+
else:
|
|
89
|
+
if not runs:
|
|
90
|
+
return data
|
|
88
91
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
fields.append(self._get_run_line(run))
|
|
92
|
-
fields.append(self._get_run_result(run))
|
|
92
|
+
if isinstance(runs, list):
|
|
93
|
+
runs = mlrun.lists.RunList(runs)
|
|
93
94
|
|
|
94
|
-
|
|
95
|
-
|
|
95
|
+
fields = [self._get_slack_row("*Runs*"), self._get_slack_row("*Results*")]
|
|
96
|
+
for run in runs:
|
|
97
|
+
fields.append(self._get_run_line(run))
|
|
98
|
+
fields.append(self._get_run_result(run))
|
|
99
|
+
|
|
100
|
+
for i in range(0, len(fields), 8):
|
|
101
|
+
data["blocks"].append({"type": "section", "fields": fields[i : i + 8]})
|
|
96
102
|
|
|
97
103
|
return data
|
|
98
104
|
|
|
105
|
+
def _generate_slack_header_blocks(self, severity: str, message: str):
|
|
106
|
+
header_text = block_text = f"[{severity}] {message}"
|
|
107
|
+
section_text = None
|
|
108
|
+
|
|
109
|
+
# Slack doesn't allow headers to be longer than 150 characters
|
|
110
|
+
# If there's a comma in the message, split the message at the comma
|
|
111
|
+
# Otherwise, split the message at 150 characters
|
|
112
|
+
if len(block_text) > 150:
|
|
113
|
+
if ", " in block_text and block_text.index(", ") < 149:
|
|
114
|
+
header_text = block_text.split(",")[0]
|
|
115
|
+
section_text = block_text[len(header_text) + 2 :]
|
|
116
|
+
else:
|
|
117
|
+
header_text = block_text[:150]
|
|
118
|
+
section_text = block_text[150:]
|
|
119
|
+
blocks = [
|
|
120
|
+
{"type": "header", "text": {"type": "plain_text", "text": header_text}}
|
|
121
|
+
]
|
|
122
|
+
if section_text:
|
|
123
|
+
blocks.append(
|
|
124
|
+
{
|
|
125
|
+
"type": "section",
|
|
126
|
+
"text": self._get_slack_row(section_text),
|
|
127
|
+
}
|
|
128
|
+
)
|
|
129
|
+
return blocks
|
|
130
|
+
|
|
131
|
+
def _get_alert_fields(
|
|
132
|
+
self,
|
|
133
|
+
alert: mlrun.common.schemas.AlertConfig,
|
|
134
|
+
event_data: mlrun.common.schemas.Event,
|
|
135
|
+
) -> list:
|
|
136
|
+
line = [
|
|
137
|
+
self._get_slack_row(f":bell: {alert.name} alert has occurred"),
|
|
138
|
+
self._get_slack_row(f"*Project:*\n{alert.project}"),
|
|
139
|
+
self._get_slack_row(f"*ID:*\n{event_data.entity.ids[0]}"),
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
if alert.summary:
|
|
143
|
+
line.append(
|
|
144
|
+
self._get_slack_row(
|
|
145
|
+
f"*Summary:*\n{mlrun.utils.helpers.format_alert_summary(alert, event_data)}"
|
|
146
|
+
)
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
if event_data.value_dict:
|
|
150
|
+
data_lines = []
|
|
151
|
+
for key, value in event_data.value_dict.items():
|
|
152
|
+
data_lines.append(f"{key}: {value}")
|
|
153
|
+
data_text = "\n".join(data_lines)
|
|
154
|
+
line.append(self._get_slack_row(f"*Event data:*\n{data_text}"))
|
|
155
|
+
|
|
156
|
+
if (
|
|
157
|
+
event_data.entity.kind == mlrun.common.schemas.alert.EventEntityKind.JOB
|
|
158
|
+
): # JOB entity
|
|
159
|
+
uid = event_data.value_dict.get("uid")
|
|
160
|
+
url = mlrun.utils.helpers.get_ui_url(alert.project, uid)
|
|
161
|
+
overview_type = "Job overview"
|
|
162
|
+
else: # MODEL entity
|
|
163
|
+
model_name = event_data.value_dict.get("model")
|
|
164
|
+
model_endpoint_id = event_data.value_dict.get("model_endpoint_id")
|
|
165
|
+
url = mlrun.utils.helpers.get_model_endpoint_url(
|
|
166
|
+
alert.project, model_name, model_endpoint_id
|
|
167
|
+
)
|
|
168
|
+
overview_type = "Model endpoint"
|
|
169
|
+
|
|
170
|
+
line.append(self._get_slack_row(f"*Overview:*\n<{url}|*{overview_type}*>"))
|
|
171
|
+
|
|
172
|
+
return line
|
|
173
|
+
|
|
99
174
|
def _get_run_line(self, run: dict) -> dict:
|
|
100
175
|
meta = run["metadata"]
|
|
101
176
|
url = mlrun.utils.helpers.get_ui_url(meta.get("project"), meta.get("uid"))
|
|
102
|
-
|
|
177
|
+
|
|
178
|
+
# Only show the URL if the run is not a function (serving or mlrun function)
|
|
179
|
+
kind = run.get("step_kind")
|
|
180
|
+
state = run["status"].get("state", "")
|
|
181
|
+
if state != "skipped" and (url and not kind or kind == "run"):
|
|
103
182
|
line = f'<{url}|*{meta.get("name")}*>'
|
|
104
183
|
else:
|
|
105
184
|
line = meta.get("name")
|
|
106
|
-
|
|
185
|
+
if kind:
|
|
186
|
+
line = f'{line} *({run.get("step_kind", run.get("kind", ""))})*'
|
|
107
187
|
line = f'{self.emojis.get(state, ":question:")} {line}'
|
|
108
188
|
return self._get_slack_row(line)
|
|
109
189
|
|
|
110
190
|
def _get_run_result(self, run: dict) -> dict:
|
|
111
191
|
state = run["status"].get("state", "")
|
|
112
192
|
if state == "error":
|
|
113
|
-
error_status = run["status"].get("error", "")
|
|
193
|
+
error_status = run["status"].get("error", "") or state
|
|
114
194
|
result = f"*{error_status}*"
|
|
115
195
|
else:
|
|
116
196
|
result = mlrun.utils.helpers.dict_to_str(
|
|
117
197
|
run["status"].get("results", {}), ", "
|
|
118
198
|
)
|
|
119
|
-
return self._get_slack_row(result or
|
|
199
|
+
return self._get_slack_row(result or state)
|
|
120
200
|
|
|
121
201
|
@staticmethod
|
|
122
202
|
def _get_slack_row(text: str) -> dict:
|
|
@@ -36,6 +36,8 @@ class WebhookNotification(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
|
url = self.params.get("url", None)
|
|
41
43
|
method = self.params.get("method", "post").lower()
|
|
@@ -46,9 +48,17 @@ class WebhookNotification(NotificationBase):
|
|
|
46
48
|
request_body = {
|
|
47
49
|
"message": message,
|
|
48
50
|
"severity": severity,
|
|
49
|
-
"runs": runs,
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
if runs:
|
|
54
|
+
request_body["runs"] = runs
|
|
55
|
+
|
|
56
|
+
if alert:
|
|
57
|
+
request_body["alert"] = alert.dict()
|
|
58
|
+
if event_data:
|
|
59
|
+
request_body["value"] = event_data.value_dict
|
|
60
|
+
request_body["id"] = event_data.entity.ids[0]
|
|
61
|
+
|
|
52
62
|
if custom_html:
|
|
53
63
|
request_body["custom_html"] = custom_html
|
|
54
64
|
|