mlrun 1.7.2rc3__py3-none-any.whl → 1.8.0rc2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mlrun might be problematic. Click here for more details.
- mlrun/__init__.py +18 -18
- mlrun/__main__.py +3 -3
- mlrun/alerts/alert.py +19 -12
- mlrun/artifacts/__init__.py +0 -2
- mlrun/artifacts/base.py +34 -11
- mlrun/artifacts/dataset.py +16 -16
- mlrun/artifacts/manager.py +13 -13
- mlrun/artifacts/model.py +66 -53
- mlrun/common/constants.py +6 -0
- mlrun/common/formatters/__init__.py +1 -0
- mlrun/common/formatters/feature_set.py +1 -0
- mlrun/common/formatters/function.py +1 -0
- mlrun/common/formatters/model_endpoint.py +30 -0
- mlrun/common/formatters/pipeline.py +1 -2
- mlrun/common/formatters/project.py +9 -0
- mlrun/common/model_monitoring/__init__.py +0 -3
- mlrun/common/model_monitoring/helpers.py +1 -1
- mlrun/common/runtimes/constants.py +1 -2
- mlrun/common/schemas/__init__.py +7 -2
- mlrun/common/schemas/alert.py +31 -18
- mlrun/common/schemas/api_gateway.py +3 -3
- mlrun/common/schemas/artifact.py +7 -13
- mlrun/common/schemas/auth.py +6 -4
- mlrun/common/schemas/background_task.py +7 -7
- mlrun/common/schemas/client_spec.py +2 -2
- mlrun/common/schemas/clusterization_spec.py +2 -2
- mlrun/common/schemas/common.py +53 -3
- 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 +8 -1
- mlrun/common/schemas/model_monitoring/constants.py +62 -12
- mlrun/common/schemas/model_monitoring/grafana.py +1 -1
- mlrun/common/schemas/model_monitoring/model_endpoint_v2.py +149 -0
- mlrun/common/schemas/model_monitoring/model_endpoints.py +22 -6
- mlrun/common/schemas/notification.py +18 -3
- mlrun/common/schemas/object.py +1 -1
- mlrun/common/schemas/pagination.py +4 -4
- mlrun/common/schemas/partition.py +137 -0
- mlrun/common/schemas/pipeline.py +2 -2
- mlrun/common/schemas/project.py +22 -17
- mlrun/common/schemas/runs.py +2 -2
- mlrun/common/schemas/runtime_resource.py +5 -5
- mlrun/common/schemas/schedule.py +1 -1
- mlrun/common/schemas/secret.py +1 -1
- mlrun/common/schemas/tag.py +3 -3
- mlrun/common/schemas/workflow.py +5 -5
- mlrun/config.py +65 -15
- mlrun/data_types/__init__.py +0 -2
- mlrun/data_types/data_types.py +0 -1
- mlrun/data_types/infer.py +3 -1
- mlrun/data_types/spark.py +4 -4
- mlrun/data_types/to_pandas.py +2 -11
- mlrun/datastore/__init__.py +0 -2
- mlrun/datastore/alibaba_oss.py +4 -1
- mlrun/datastore/azure_blob.py +4 -1
- mlrun/datastore/base.py +12 -4
- mlrun/datastore/datastore.py +9 -3
- mlrun/datastore/datastore_profile.py +20 -20
- 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 +4 -1
- mlrun/datastore/sources.py +51 -49
- mlrun/datastore/store_resources.py +0 -2
- mlrun/datastore/targets.py +22 -23
- mlrun/datastore/utils.py +2 -2
- mlrun/datastore/v3io.py +4 -1
- mlrun/datastore/wasbfs/fs.py +13 -12
- mlrun/db/base.py +170 -64
- mlrun/db/factory.py +3 -0
- mlrun/db/httpdb.py +986 -238
- mlrun/db/nopdb.py +155 -57
- mlrun/errors.py +2 -2
- mlrun/execution.py +55 -29
- mlrun/feature_store/__init__.py +0 -2
- mlrun/feature_store/api.py +40 -40
- mlrun/feature_store/common.py +9 -9
- mlrun/feature_store/feature_set.py +20 -18
- mlrun/feature_store/feature_vector.py +27 -24
- mlrun/feature_store/retrieval/base.py +14 -9
- mlrun/feature_store/retrieval/job.py +2 -1
- mlrun/feature_store/steps.py +2 -2
- 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 +29 -27
- 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/launcher/base.py +3 -4
- mlrun/launcher/local.py +1 -1
- mlrun/launcher/remote.py +1 -1
- mlrun/lists.py +4 -3
- mlrun/model.py +110 -46
- mlrun/model_monitoring/__init__.py +1 -2
- mlrun/model_monitoring/api.py +6 -6
- mlrun/model_monitoring/applications/_application_steps.py +13 -15
- mlrun/model_monitoring/applications/histogram_data_drift.py +41 -15
- mlrun/model_monitoring/applications/results.py +55 -3
- mlrun/model_monitoring/controller.py +185 -223
- mlrun/model_monitoring/db/_schedules.py +156 -0
- mlrun/model_monitoring/db/_stats.py +189 -0
- mlrun/model_monitoring/db/stores/__init__.py +1 -1
- mlrun/model_monitoring/db/stores/base/store.py +6 -65
- mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +0 -25
- mlrun/model_monitoring/db/stores/sqldb/models/base.py +0 -97
- mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +2 -58
- mlrun/model_monitoring/db/stores/sqldb/models/sqlite.py +0 -15
- mlrun/model_monitoring/db/stores/sqldb/sql_store.py +6 -257
- mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +9 -271
- mlrun/model_monitoring/db/tsdb/base.py +76 -24
- mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +61 -6
- mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +33 -0
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +253 -28
- mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +1 -0
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +35 -17
- mlrun/model_monitoring/helpers.py +91 -1
- mlrun/model_monitoring/model_endpoint.py +4 -2
- mlrun/model_monitoring/stream_processing.py +16 -13
- mlrun/model_monitoring/tracking_policy.py +10 -3
- mlrun/model_monitoring/writer.py +47 -26
- mlrun/package/__init__.py +3 -6
- mlrun/package/context_handler.py +1 -1
- 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 +31 -14
- mlrun/package/utils/__init__.py +0 -3
- mlrun/package/utils/_pickler.py +6 -6
- mlrun/platforms/__init__.py +3 -16
- mlrun/platforms/iguazio.py +4 -1
- mlrun/projects/operations.py +27 -27
- mlrun/projects/pipelines.py +34 -35
- mlrun/projects/project.py +535 -182
- mlrun/run.py +13 -10
- mlrun/runtimes/__init__.py +1 -3
- mlrun/runtimes/base.py +15 -11
- mlrun/runtimes/daskjob.py +9 -9
- mlrun/runtimes/generators.py +2 -1
- mlrun/runtimes/kubejob.py +4 -5
- mlrun/runtimes/mounts.py +572 -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 -11
- mlrun/runtimes/nuclio/function.py +13 -13
- mlrun/runtimes/nuclio/serving.py +9 -9
- mlrun/runtimes/pod.py +154 -45
- mlrun/runtimes/remotesparkjob.py +3 -2
- mlrun/runtimes/sparkjob/__init__.py +0 -2
- mlrun/runtimes/sparkjob/spark3job.py +21 -11
- mlrun/runtimes/utils.py +6 -5
- mlrun/serving/merger.py +6 -4
- mlrun/serving/remote.py +18 -17
- mlrun/serving/routers.py +27 -27
- mlrun/serving/server.py +1 -1
- mlrun/serving/states.py +76 -71
- mlrun/serving/utils.py +13 -2
- mlrun/serving/v1_serving.py +3 -2
- mlrun/serving/v2_serving.py +4 -4
- 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 +1 -1
- mlrun/utils/helpers.py +70 -16
- mlrun/utils/logger.py +106 -4
- mlrun/utils/notifications/notification/__init__.py +22 -19
- mlrun/utils/notifications/notification/base.py +33 -14
- 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 +149 -0
- mlrun/utils/notifications/notification/slack.py +6 -6
- mlrun/utils/notifications/notification/webhook.py +18 -22
- mlrun/utils/notifications/notification_pusher.py +43 -31
- mlrun/utils/regex.py +3 -1
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.7.2rc3.dist-info → mlrun-1.8.0rc2.dist-info}/METADATA +18 -14
- mlrun-1.8.0rc2.dist-info/RECORD +358 -0
- {mlrun-1.7.2rc3.dist-info → mlrun-1.8.0rc2.dist-info}/WHEEL +1 -1
- mlrun-1.7.2rc3.dist-info/RECORD +0 -351
- {mlrun-1.7.2rc3.dist-info → mlrun-1.8.0rc2.dist-info}/LICENSE +0 -0
- {mlrun-1.7.2rc3.dist-info → mlrun-1.8.0rc2.dist-info}/entry_points.txt +0 -0
- {mlrun-1.7.2rc3.dist-info → mlrun-1.8.0rc2.dist-info}/top_level.txt +0 -0
mlrun/utils/helpers.py
CHANGED
|
@@ -40,7 +40,6 @@ import pandas
|
|
|
40
40
|
import semver
|
|
41
41
|
import yaml
|
|
42
42
|
from dateutil import parser
|
|
43
|
-
from mlrun_pipelines.models import PipelineRun
|
|
44
43
|
from pandas import Timedelta, Timestamp
|
|
45
44
|
from yaml.representer import RepresenterError
|
|
46
45
|
|
|
@@ -52,6 +51,7 @@ import mlrun.utils.regex
|
|
|
52
51
|
import mlrun.utils.version.version
|
|
53
52
|
from mlrun.common.constants import MYSQL_MEDIUMBLOB_SIZE_BYTES
|
|
54
53
|
from mlrun.config import config
|
|
54
|
+
from mlrun_pipelines.models import PipelineRun
|
|
55
55
|
|
|
56
56
|
from .logger import create_logger
|
|
57
57
|
from .retryer import ( # noqa: F401
|
|
@@ -167,6 +167,7 @@ class RunKeys:
|
|
|
167
167
|
inputs = "inputs"
|
|
168
168
|
returns = "returns"
|
|
169
169
|
artifacts = "artifacts"
|
|
170
|
+
artifact_uris = "artifact_uris"
|
|
170
171
|
outputs = "outputs"
|
|
171
172
|
data_stores = "data_stores"
|
|
172
173
|
secrets = "secret_sources"
|
|
@@ -220,7 +221,7 @@ def verify_field_regex(
|
|
|
220
221
|
|
|
221
222
|
|
|
222
223
|
def validate_builder_source(
|
|
223
|
-
source: str, pull_at_runtime: bool = False, workdir: str = None
|
|
224
|
+
source: str, pull_at_runtime: bool = False, workdir: Optional[str] = None
|
|
224
225
|
):
|
|
225
226
|
if pull_at_runtime or not source:
|
|
226
227
|
return
|
|
@@ -268,12 +269,14 @@ def validate_tag_name(
|
|
|
268
269
|
def validate_artifact_key_name(
|
|
269
270
|
artifact_key: str, field_name: str, raise_on_failure: bool = True
|
|
270
271
|
) -> bool:
|
|
272
|
+
field_type = "key" if field_name == "artifact.key" else "db_key"
|
|
271
273
|
return mlrun.utils.helpers.verify_field_regex(
|
|
272
274
|
field_name,
|
|
273
275
|
artifact_key,
|
|
274
276
|
mlrun.utils.regex.artifact_key,
|
|
275
277
|
raise_on_failure=raise_on_failure,
|
|
276
|
-
log_message="
|
|
278
|
+
log_message=f"Artifact {field_type} must start and end with an alphanumeric character, and may only contain "
|
|
279
|
+
"letters, numbers, hyphens, underscores, and dots.",
|
|
277
280
|
)
|
|
278
281
|
|
|
279
282
|
|
|
@@ -354,8 +357,8 @@ def verify_field_list_of_type(
|
|
|
354
357
|
def verify_dict_items_type(
|
|
355
358
|
name: str,
|
|
356
359
|
dictionary: dict,
|
|
357
|
-
expected_keys_types: list = None,
|
|
358
|
-
expected_values_types: list = None,
|
|
360
|
+
expected_keys_types: Optional[list] = None,
|
|
361
|
+
expected_values_types: Optional[list] = None,
|
|
359
362
|
):
|
|
360
363
|
if dictionary:
|
|
361
364
|
if not isinstance(dictionary, dict):
|
|
@@ -372,7 +375,7 @@ def verify_dict_items_type(
|
|
|
372
375
|
) from exc
|
|
373
376
|
|
|
374
377
|
|
|
375
|
-
def verify_list_items_type(list_, expected_types: list = None):
|
|
378
|
+
def verify_list_items_type(list_, expected_types: Optional[list] = None):
|
|
376
379
|
if list_ and expected_types:
|
|
377
380
|
list_items_types = set(map(type, list_))
|
|
378
381
|
expected_types = set(expected_types)
|
|
@@ -396,6 +399,10 @@ def now_date(tz: timezone = timezone.utc) -> datetime:
|
|
|
396
399
|
return datetime.now(tz=tz)
|
|
397
400
|
|
|
398
401
|
|
|
402
|
+
def datetime_min(tz: timezone = timezone.utc) -> datetime:
|
|
403
|
+
return datetime(1970, 1, 1, tzinfo=tz)
|
|
404
|
+
|
|
405
|
+
|
|
399
406
|
datetime_now = now_date
|
|
400
407
|
|
|
401
408
|
|
|
@@ -816,7 +823,9 @@ def _convert_python_package_version_to_image_tag(version: typing.Optional[str]):
|
|
|
816
823
|
|
|
817
824
|
|
|
818
825
|
def enrich_image_url(
|
|
819
|
-
image_url: str,
|
|
826
|
+
image_url: str,
|
|
827
|
+
client_version: Optional[str] = None,
|
|
828
|
+
client_python_version: Optional[str] = None,
|
|
820
829
|
) -> str:
|
|
821
830
|
client_version = _convert_python_package_version_to_image_tag(client_version)
|
|
822
831
|
server_version = _convert_python_package_version_to_image_tag(
|
|
@@ -856,7 +865,7 @@ def enrich_image_url(
|
|
|
856
865
|
|
|
857
866
|
|
|
858
867
|
def resolve_image_tag_suffix(
|
|
859
|
-
mlrun_version: str = None, python_version: str = None
|
|
868
|
+
mlrun_version: Optional[str] = None, python_version: Optional[str] = None
|
|
860
869
|
) -> str:
|
|
861
870
|
"""
|
|
862
871
|
resolves what suffix should be appended to the image tag
|
|
@@ -947,6 +956,36 @@ def fill_function_hash(function_dict, tag=""):
|
|
|
947
956
|
return fill_object_hash(function_dict, "hash", tag)
|
|
948
957
|
|
|
949
958
|
|
|
959
|
+
def fill_model_endpoint_hash(
|
|
960
|
+
model_endpoint: mlrun.common.schemas.ModelEndpointV2,
|
|
961
|
+
created_time: typing.Union[str, datetime],
|
|
962
|
+
) -> str:
|
|
963
|
+
"""
|
|
964
|
+
Fill the model endpoint uid field in the model endpoint object and returns it.
|
|
965
|
+
The uid is generated by hashing the following model endpoint fields:
|
|
966
|
+
- name
|
|
967
|
+
- model_tag
|
|
968
|
+
- function_name
|
|
969
|
+
- project
|
|
970
|
+
- created_time
|
|
971
|
+
"""
|
|
972
|
+
|
|
973
|
+
name = model_endpoint.metadata.name
|
|
974
|
+
model_tag = model_endpoint.spec.model_tag
|
|
975
|
+
function_name = model_endpoint.spec.function_name
|
|
976
|
+
project = model_endpoint.metadata.project
|
|
977
|
+
created_time = (
|
|
978
|
+
created_time
|
|
979
|
+
if isinstance(created_time, str)
|
|
980
|
+
else created_time.isoformat(sep=" ", timespec="microseconds")
|
|
981
|
+
)
|
|
982
|
+
|
|
983
|
+
unique_string = f"{name}_{model_tag}_{function_name}_{project}_{created_time}"
|
|
984
|
+
uid = hashlib.sha1(unique_string.encode("utf-8")).hexdigest()
|
|
985
|
+
model_endpoint.metadata.uid = uid
|
|
986
|
+
return uid
|
|
987
|
+
|
|
988
|
+
|
|
950
989
|
def retry_until_successful(
|
|
951
990
|
backoff: int, timeout: int, logger, verbose: bool, _function, *args, **kwargs
|
|
952
991
|
):
|
|
@@ -1175,7 +1214,7 @@ def get_function(function, namespaces, reload_modules: bool = False):
|
|
|
1175
1214
|
def get_handler_extended(
|
|
1176
1215
|
handler_path: str,
|
|
1177
1216
|
context=None,
|
|
1178
|
-
class_args: dict = None,
|
|
1217
|
+
class_args: Optional[dict] = None,
|
|
1179
1218
|
namespaces=None,
|
|
1180
1219
|
reload_modules: bool = False,
|
|
1181
1220
|
):
|
|
@@ -1694,17 +1733,22 @@ def merge_dicts_with_precedence(*dicts: dict) -> dict:
|
|
|
1694
1733
|
|
|
1695
1734
|
|
|
1696
1735
|
def validate_component_version_compatibility(
|
|
1697
|
-
component_name: typing.Literal["iguazio", "nuclio"],
|
|
1736
|
+
component_name: typing.Literal["iguazio", "nuclio", "mlrun-client"],
|
|
1737
|
+
*min_versions: str,
|
|
1738
|
+
mlrun_client_version: Optional[str] = None,
|
|
1698
1739
|
):
|
|
1699
1740
|
"""
|
|
1700
1741
|
:param component_name: Name of the component to validate compatibility for.
|
|
1701
1742
|
:param min_versions: Valid minimum version(s) required, assuming no 2 versions has equal major and minor.
|
|
1743
|
+
:param mlrun_client_version: Client version to validate when component_name is "mlrun-client".
|
|
1702
1744
|
"""
|
|
1703
1745
|
parsed_min_versions = [
|
|
1704
1746
|
semver.VersionInfo.parse(min_version) for min_version in min_versions
|
|
1705
1747
|
]
|
|
1706
1748
|
parsed_current_version = None
|
|
1707
1749
|
component_current_version = None
|
|
1750
|
+
# For mlrun client we don't assume compatability if we fail to parse the client version
|
|
1751
|
+
assume_compatible = component_name not in ["mlrun-client"]
|
|
1708
1752
|
try:
|
|
1709
1753
|
if component_name == "iguazio":
|
|
1710
1754
|
component_current_version = mlrun.mlconf.igz_version
|
|
@@ -1721,18 +1765,29 @@ def validate_component_version_compatibility(
|
|
|
1721
1765
|
parsed_current_version = semver.VersionInfo.parse(
|
|
1722
1766
|
mlrun.mlconf.nuclio_version
|
|
1723
1767
|
)
|
|
1768
|
+
if component_name == "mlrun-client":
|
|
1769
|
+
# dev version, assume compatible
|
|
1770
|
+
if mlrun_client_version and (
|
|
1771
|
+
mlrun_client_version.startswith("0.0.0+")
|
|
1772
|
+
or "unstable" in mlrun_client_version
|
|
1773
|
+
):
|
|
1774
|
+
return True
|
|
1775
|
+
|
|
1776
|
+
component_current_version = mlrun_client_version
|
|
1777
|
+
parsed_current_version = semver.Version.parse(mlrun_client_version)
|
|
1724
1778
|
if not parsed_current_version:
|
|
1725
|
-
return
|
|
1779
|
+
return assume_compatible
|
|
1726
1780
|
except ValueError:
|
|
1727
1781
|
# only log when version is set but invalid
|
|
1728
1782
|
if component_current_version:
|
|
1729
1783
|
logger.warning(
|
|
1730
|
-
"Unable to parse current version
|
|
1784
|
+
"Unable to parse current version",
|
|
1731
1785
|
component_name=component_name,
|
|
1732
1786
|
current_version=component_current_version,
|
|
1733
1787
|
min_versions=min_versions,
|
|
1788
|
+
assume_compatible=assume_compatible,
|
|
1734
1789
|
)
|
|
1735
|
-
return
|
|
1790
|
+
return assume_compatible
|
|
1736
1791
|
|
|
1737
1792
|
# Feature might have been back-ported e.g. nuclio node selection is supported from
|
|
1738
1793
|
# 1.5.20 and 1.6.10 but not in 1.6.9 - therefore we reverse sort to validate against 1.6.x 1st and
|
|
@@ -1797,9 +1852,8 @@ def _reload(module, max_recursion_depth):
|
|
|
1797
1852
|
def run_with_retry(
|
|
1798
1853
|
retry_count: int,
|
|
1799
1854
|
func: typing.Callable,
|
|
1800
|
-
retry_on_exceptions:
|
|
1801
|
-
type[Exception],
|
|
1802
|
-
tuple[type[Exception]],
|
|
1855
|
+
retry_on_exceptions: Optional[
|
|
1856
|
+
typing.Union[type[Exception], tuple[type[Exception]]]
|
|
1803
1857
|
] = None,
|
|
1804
1858
|
*args,
|
|
1805
1859
|
**kwargs,
|
mlrun/utils/logger.py
CHANGED
|
@@ -11,9 +11,11 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
-
|
|
14
|
+
import datetime
|
|
15
15
|
import logging
|
|
16
16
|
import os
|
|
17
|
+
import string
|
|
18
|
+
import sys
|
|
17
19
|
import typing
|
|
18
20
|
from enum import Enum
|
|
19
21
|
from functools import cached_property
|
|
@@ -22,15 +24,16 @@ from traceback import format_exception
|
|
|
22
24
|
from typing import IO, Optional, Union
|
|
23
25
|
|
|
24
26
|
import orjson
|
|
25
|
-
import pydantic
|
|
27
|
+
import pydantic.v1
|
|
26
28
|
|
|
29
|
+
from mlrun import errors
|
|
27
30
|
from mlrun.config import config
|
|
28
31
|
|
|
29
32
|
|
|
30
33
|
class _BaseFormatter(logging.Formatter):
|
|
31
34
|
def _json_dump(self, json_object):
|
|
32
35
|
def default(obj):
|
|
33
|
-
if isinstance(obj, pydantic.BaseModel):
|
|
36
|
+
if isinstance(obj, pydantic.v1.BaseModel):
|
|
34
37
|
return obj.dict()
|
|
35
38
|
|
|
36
39
|
# EAFP all the way.
|
|
@@ -93,6 +96,98 @@ class HumanReadableFormatter(_BaseFormatter):
|
|
|
93
96
|
return record_with
|
|
94
97
|
|
|
95
98
|
|
|
99
|
+
class CustomFormatter(HumanReadableFormatter):
|
|
100
|
+
"""
|
|
101
|
+
To enable custom logger formatter, configure MLRun with the following env variables:
|
|
102
|
+
1. "MLRUN_LOG_FORMATTER" = "custom" - change the default log formatter.
|
|
103
|
+
2. "MLRUN_LOG_FORMAT_OVERRIDE" = "> {timestamp} [{level}] Running module: {module} {message} {more}" - logger format
|
|
104
|
+
* Note that your custom format must include those 4 fields - timestamp, level, message and more
|
|
105
|
+
If the custom format is not configured properly , MLRun will use the default logger (human format).
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
# This attribute is used to solve an issue
|
|
109
|
+
# that causes the warning to be written numerous times(for any log generation).
|
|
110
|
+
# We want to print the errors just once, not for each logger generation.
|
|
111
|
+
fail_on_format_configuration = False # for issues that relates to unrecognized keys
|
|
112
|
+
fail_on_missing_default_keys_key = (
|
|
113
|
+
False # for issues that relates to missing default keys
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
def format(self, record) -> str:
|
|
117
|
+
more = self._resolve_more(record)
|
|
118
|
+
custom_format = config.log_format_override
|
|
119
|
+
_custom_format = None
|
|
120
|
+
current_time = datetime.datetime.now()
|
|
121
|
+
formatted_time = current_time.strftime("%Y-%m-%d %H:%M:%S,%f")[:-3]
|
|
122
|
+
try:
|
|
123
|
+
if custom_format:
|
|
124
|
+
default_keys = ["timestamp", "level", "message", "more"]
|
|
125
|
+
formatter = string.Formatter()
|
|
126
|
+
custom_format_keys = [
|
|
127
|
+
key
|
|
128
|
+
for _, key, _, _ in formatter.parse(custom_format)
|
|
129
|
+
if key is not None
|
|
130
|
+
]
|
|
131
|
+
missing_default_flags = list(
|
|
132
|
+
set(default_keys) - set(custom_format_keys)
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
if (
|
|
136
|
+
missing_default_flags
|
|
137
|
+
and not CustomFormatter.fail_on_missing_default_keys_key
|
|
138
|
+
):
|
|
139
|
+
print(
|
|
140
|
+
f'> {formatted_time} [warning] Custom loggers must '
|
|
141
|
+
f'include those keys within the logger format, {", ".join(default_keys)} '
|
|
142
|
+
f'your format is missing: {", ".join(missing_default_flags)}',
|
|
143
|
+
file=sys.stderr,
|
|
144
|
+
)
|
|
145
|
+
CustomFormatter.fail_on_missing_default_keys_key = True
|
|
146
|
+
record_dict = record.__dict__
|
|
147
|
+
missing_format_configuraiton_keys = list(
|
|
148
|
+
set(custom_format_keys)
|
|
149
|
+
- set(default_keys)
|
|
150
|
+
- set(record_dict.keys())
|
|
151
|
+
)
|
|
152
|
+
if missing_format_configuraiton_keys:
|
|
153
|
+
if not CustomFormatter.fail_on_format_configuration:
|
|
154
|
+
print(
|
|
155
|
+
f"> {formatted_time} [warning] Failed to create custom logger due "
|
|
156
|
+
f'to missing format key in the log record: {", ".join(missing_format_configuraiton_keys)}',
|
|
157
|
+
file=sys.stderr,
|
|
158
|
+
)
|
|
159
|
+
CustomFormatter.fail_on_format_configuration = True
|
|
160
|
+
_format = (
|
|
161
|
+
f"> {self.formatTime(record, self.datefmt)} "
|
|
162
|
+
f"[{record.levelname.lower()}] "
|
|
163
|
+
f"{record.getMessage().rstrip()}"
|
|
164
|
+
f"{more}"
|
|
165
|
+
)
|
|
166
|
+
_custom_format = custom_format.format(
|
|
167
|
+
timestamp=self.formatTime(record, self.datefmt),
|
|
168
|
+
level=record.levelname.lower(),
|
|
169
|
+
message=record.getMessage().rstrip(),
|
|
170
|
+
more=more or "",
|
|
171
|
+
**record_dict,
|
|
172
|
+
)
|
|
173
|
+
CustomFormatter.fail_on_format_configuration = True
|
|
174
|
+
except Exception as e:
|
|
175
|
+
if not CustomFormatter.fail_on_format_configuration:
|
|
176
|
+
print(
|
|
177
|
+
f"> {formatted_time} [warning] Failed to create custom logger, "
|
|
178
|
+
f"see Exception: {errors.err_to_str(e)}",
|
|
179
|
+
file=sys.stderr,
|
|
180
|
+
)
|
|
181
|
+
CustomFormatter.fail_on_format_configuration = True
|
|
182
|
+
_format = _custom_format or (
|
|
183
|
+
f"> {self.formatTime(record, self.datefmt)} "
|
|
184
|
+
f"[{record.levelname.lower()}] "
|
|
185
|
+
f"{record.getMessage().rstrip()}"
|
|
186
|
+
f"{more}"
|
|
187
|
+
)
|
|
188
|
+
return _format
|
|
189
|
+
|
|
190
|
+
|
|
96
191
|
class HumanReadableExtendedFormatter(HumanReadableFormatter):
|
|
97
192
|
_colors = {
|
|
98
193
|
logging.NOTSET: "",
|
|
@@ -272,17 +367,24 @@ class FormatterKinds(Enum):
|
|
|
272
367
|
HUMAN = "human"
|
|
273
368
|
HUMAN_EXTENDED = "human_extended"
|
|
274
369
|
JSON = "json"
|
|
370
|
+
CUSTOM = "custom"
|
|
275
371
|
|
|
276
372
|
|
|
277
373
|
def resolve_formatter_by_kind(
|
|
278
374
|
formatter_kind: FormatterKinds,
|
|
279
375
|
) -> type[
|
|
280
|
-
typing.Union[
|
|
376
|
+
typing.Union[
|
|
377
|
+
HumanReadableFormatter,
|
|
378
|
+
HumanReadableExtendedFormatter,
|
|
379
|
+
JSONFormatter,
|
|
380
|
+
CustomFormatter,
|
|
381
|
+
]
|
|
281
382
|
]:
|
|
282
383
|
return {
|
|
283
384
|
FormatterKinds.HUMAN: HumanReadableFormatter,
|
|
284
385
|
FormatterKinds.HUMAN_EXTENDED: HumanReadableExtendedFormatter,
|
|
285
386
|
FormatterKinds.JSON: JSONFormatter,
|
|
387
|
+
FormatterKinds.CUSTOM: CustomFormatter,
|
|
286
388
|
}[formatter_kind]
|
|
287
389
|
|
|
288
390
|
|
|
@@ -14,30 +14,32 @@
|
|
|
14
14
|
|
|
15
15
|
import enum
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
17
|
+
import mlrun.common.schemas.notification as notifications
|
|
18
|
+
import mlrun.utils.notifications.notification.base as base
|
|
19
|
+
import mlrun.utils.notifications.notification.console as console
|
|
20
|
+
import mlrun.utils.notifications.notification.git as git
|
|
21
|
+
import mlrun.utils.notifications.notification.ipython as ipython
|
|
22
|
+
import mlrun.utils.notifications.notification.mail as mail
|
|
23
|
+
import mlrun.utils.notifications.notification.slack as slack
|
|
24
|
+
import mlrun.utils.notifications.notification.webhook as webhook
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
class NotificationTypes(str, enum.Enum):
|
|
28
|
-
console = NotificationKind.console.value
|
|
29
|
-
git = NotificationKind.git.value
|
|
30
|
-
ipython = NotificationKind.ipython.value
|
|
31
|
-
slack = NotificationKind.slack.value
|
|
32
|
-
|
|
28
|
+
console = notifications.NotificationKind.console.value
|
|
29
|
+
git = notifications.NotificationKind.git.value
|
|
30
|
+
ipython = notifications.NotificationKind.ipython.value
|
|
31
|
+
slack = notifications.NotificationKind.slack.value
|
|
32
|
+
mail = notifications.NotificationKind.mail.value
|
|
33
|
+
webhook = notifications.NotificationKind.webhook.value
|
|
33
34
|
|
|
34
|
-
def get_notification(self) -> type[NotificationBase]:
|
|
35
|
+
def get_notification(self) -> type[base.NotificationBase]:
|
|
35
36
|
return {
|
|
36
|
-
self.console: ConsoleNotification,
|
|
37
|
-
self.git: GitNotification,
|
|
38
|
-
self.ipython: IPythonNotification,
|
|
39
|
-
self.slack: SlackNotification,
|
|
40
|
-
self.
|
|
37
|
+
self.console: console.ConsoleNotification,
|
|
38
|
+
self.git: git.GitNotification,
|
|
39
|
+
self.ipython: ipython.IPythonNotification,
|
|
40
|
+
self.slack: slack.SlackNotification,
|
|
41
|
+
self.mail: mail.MailNotification,
|
|
42
|
+
self.webhook: webhook.WebhookNotification,
|
|
41
43
|
}.get(self)
|
|
42
44
|
|
|
43
45
|
def inverse_dependencies(self) -> list[str]:
|
|
@@ -64,5 +66,6 @@ class NotificationTypes(str, enum.Enum):
|
|
|
64
66
|
cls.git,
|
|
65
67
|
cls.ipython,
|
|
66
68
|
cls.slack,
|
|
69
|
+
cls.mail,
|
|
67
70
|
cls.webhook,
|
|
68
71
|
]
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import asyncio
|
|
16
16
|
import typing
|
|
17
|
+
from copy import deepcopy
|
|
17
18
|
|
|
18
19
|
import mlrun.common.schemas
|
|
19
20
|
import mlrun.lists
|
|
@@ -22,11 +23,20 @@ import mlrun.lists
|
|
|
22
23
|
class NotificationBase:
|
|
23
24
|
def __init__(
|
|
24
25
|
self,
|
|
25
|
-
name: str = None,
|
|
26
|
-
params: dict[str, str] = None,
|
|
26
|
+
name: typing.Optional[str] = None,
|
|
27
|
+
params: typing.Optional[dict[str, str]] = None,
|
|
28
|
+
default_params: typing.Optional[dict[str, str]] = None,
|
|
27
29
|
):
|
|
30
|
+
"""
|
|
31
|
+
NotificationBase is the base class for all notification types.
|
|
32
|
+
|
|
33
|
+
:param name: The name of the notification.
|
|
34
|
+
:param params: The parameters of the notification.
|
|
35
|
+
:param default_params: The default parameters of the notification. Used for server-side enrichment purposes.
|
|
36
|
+
"""
|
|
28
37
|
self.name = name
|
|
29
38
|
self.params = params or {}
|
|
39
|
+
self.params = self.enrich_default_params(self.params, default_params)
|
|
30
40
|
|
|
31
41
|
@classmethod
|
|
32
42
|
def validate_params(cls, params):
|
|
@@ -43,13 +53,13 @@ class NotificationBase:
|
|
|
43
53
|
def push(
|
|
44
54
|
self,
|
|
45
55
|
message: str,
|
|
46
|
-
severity: typing.
|
|
47
|
-
mlrun.common.schemas.NotificationSeverity, str
|
|
56
|
+
severity: typing.Optional[
|
|
57
|
+
typing.Union[mlrun.common.schemas.NotificationSeverity, str]
|
|
48
58
|
] = mlrun.common.schemas.NotificationSeverity.INFO,
|
|
49
|
-
runs: typing.Union[mlrun.lists.RunList, list] = None,
|
|
50
|
-
custom_html: str = None,
|
|
51
|
-
alert: mlrun.common.schemas.AlertConfig = None,
|
|
52
|
-
event_data: mlrun.common.schemas.Event = None,
|
|
59
|
+
runs: typing.Optional[typing.Union[mlrun.lists.RunList, list]] = None,
|
|
60
|
+
custom_html: typing.Optional[typing.Optional[str]] = None,
|
|
61
|
+
alert: typing.Optional[mlrun.common.schemas.AlertConfig] = None,
|
|
62
|
+
event_data: typing.Optional[mlrun.common.schemas.Event] = None,
|
|
53
63
|
):
|
|
54
64
|
raise NotImplementedError()
|
|
55
65
|
|
|
@@ -59,16 +69,25 @@ class NotificationBase:
|
|
|
59
69
|
) -> None:
|
|
60
70
|
self.params = params or {}
|
|
61
71
|
|
|
72
|
+
@classmethod
|
|
73
|
+
def enrich_default_params(
|
|
74
|
+
cls, params: dict, default_params: typing.Optional[dict] = None
|
|
75
|
+
) -> dict:
|
|
76
|
+
default_params = default_params or {}
|
|
77
|
+
returned_params = deepcopy(default_params)
|
|
78
|
+
returned_params.update(params)
|
|
79
|
+
return returned_params
|
|
80
|
+
|
|
62
81
|
def _get_html(
|
|
63
82
|
self,
|
|
64
83
|
message: str,
|
|
65
|
-
severity: typing.
|
|
66
|
-
mlrun.common.schemas.NotificationSeverity, str
|
|
84
|
+
severity: typing.Optional[
|
|
85
|
+
typing.Union[mlrun.common.schemas.NotificationSeverity, str]
|
|
67
86
|
] = mlrun.common.schemas.NotificationSeverity.INFO,
|
|
68
|
-
runs: typing.Union[mlrun.lists.RunList, list] = None,
|
|
69
|
-
custom_html: str = None,
|
|
70
|
-
alert: mlrun.common.schemas.AlertConfig = None,
|
|
71
|
-
event_data: mlrun.common.schemas.Event = None,
|
|
87
|
+
runs: typing.Optional[typing.Union[mlrun.lists.RunList, list]] = None,
|
|
88
|
+
custom_html: typing.Optional[typing.Optional[str]] = None,
|
|
89
|
+
alert: typing.Optional[mlrun.common.schemas.AlertConfig] = None,
|
|
90
|
+
event_data: typing.Optional[mlrun.common.schemas.Event] = None,
|
|
72
91
|
) -> str:
|
|
73
92
|
if custom_html:
|
|
74
93
|
return custom_html
|
|
@@ -31,13 +31,13 @@ class ConsoleNotification(NotificationBase):
|
|
|
31
31
|
def push(
|
|
32
32
|
self,
|
|
33
33
|
message: str,
|
|
34
|
-
severity: typing.
|
|
35
|
-
mlrun.common.schemas.NotificationSeverity, str
|
|
34
|
+
severity: typing.Optional[
|
|
35
|
+
typing.Union[mlrun.common.schemas.NotificationSeverity, str]
|
|
36
36
|
] = mlrun.common.schemas.NotificationSeverity.INFO,
|
|
37
|
-
runs: typing.Union[mlrun.lists.RunList, list] = None,
|
|
38
|
-
custom_html: str = None,
|
|
39
|
-
alert: mlrun.common.schemas.AlertConfig = None,
|
|
40
|
-
event_data: mlrun.common.schemas.Event = None,
|
|
37
|
+
runs: typing.Optional[typing.Union[mlrun.lists.RunList, list]] = None,
|
|
38
|
+
custom_html: typing.Optional[typing.Optional[str]] = None,
|
|
39
|
+
alert: typing.Optional[mlrun.common.schemas.AlertConfig] = None,
|
|
40
|
+
event_data: typing.Optional[mlrun.common.schemas.Event] = None,
|
|
41
41
|
):
|
|
42
42
|
severity = self._resolve_severity(severity)
|
|
43
43
|
print(f"[{severity}] {message}")
|
|
@@ -54,13 +54,13 @@ class GitNotification(NotificationBase):
|
|
|
54
54
|
async def push(
|
|
55
55
|
self,
|
|
56
56
|
message: str,
|
|
57
|
-
severity: typing.
|
|
58
|
-
mlrun.common.schemas.NotificationSeverity, str
|
|
57
|
+
severity: typing.Optional[
|
|
58
|
+
typing.Union[mlrun.common.schemas.NotificationSeverity, str]
|
|
59
59
|
] = mlrun.common.schemas.NotificationSeverity.INFO,
|
|
60
|
-
runs: typing.Union[mlrun.lists.RunList, list] = None,
|
|
61
|
-
custom_html: str = None,
|
|
62
|
-
alert: mlrun.common.schemas.AlertConfig = None,
|
|
63
|
-
event_data: mlrun.common.schemas.Event = None,
|
|
60
|
+
runs: typing.Optional[typing.Union[mlrun.lists.RunList, list]] = None,
|
|
61
|
+
custom_html: typing.Optional[typing.Optional[str]] = None,
|
|
62
|
+
alert: typing.Optional[mlrun.common.schemas.AlertConfig] = None,
|
|
63
|
+
event_data: typing.Optional[mlrun.common.schemas.Event] = None,
|
|
64
64
|
):
|
|
65
65
|
git_repo = self.params.get("repo", None)
|
|
66
66
|
git_issue = self.params.get("issue", None)
|
|
@@ -85,11 +85,11 @@ class GitNotification(NotificationBase):
|
|
|
85
85
|
@staticmethod
|
|
86
86
|
async def _pr_comment(
|
|
87
87
|
message: str,
|
|
88
|
-
repo: str = None,
|
|
89
|
-
issue: int = None,
|
|
90
|
-
merge_request: int = None,
|
|
91
|
-
token: str = None,
|
|
92
|
-
server: str = None,
|
|
88
|
+
repo: typing.Optional[str] = None,
|
|
89
|
+
issue: typing.Optional[int] = None,
|
|
90
|
+
merge_request: typing.Optional[int] = None,
|
|
91
|
+
token: typing.Optional[str] = None,
|
|
92
|
+
server: typing.Optional[str] = None,
|
|
93
93
|
gitlab: bool = False,
|
|
94
94
|
) -> str:
|
|
95
95
|
"""push comment message to Git system PR/issue
|
|
@@ -28,10 +28,11 @@ class IPythonNotification(NotificationBase):
|
|
|
28
28
|
|
|
29
29
|
def __init__(
|
|
30
30
|
self,
|
|
31
|
-
name: str = None,
|
|
32
|
-
params: dict[str, str] = None,
|
|
31
|
+
name: typing.Optional[str] = None,
|
|
32
|
+
params: typing.Optional[dict[str, str]] = None,
|
|
33
|
+
default_params: typing.Optional[dict[str, str]] = None,
|
|
33
34
|
):
|
|
34
|
-
super().__init__(name, params)
|
|
35
|
+
super().__init__(name, params, default_params)
|
|
35
36
|
self._ipython = None
|
|
36
37
|
try:
|
|
37
38
|
import IPython
|
|
@@ -48,13 +49,13 @@ class IPythonNotification(NotificationBase):
|
|
|
48
49
|
def push(
|
|
49
50
|
self,
|
|
50
51
|
message: str,
|
|
51
|
-
severity: typing.
|
|
52
|
-
mlrun.common.schemas.NotificationSeverity, str
|
|
52
|
+
severity: typing.Optional[
|
|
53
|
+
typing.Union[mlrun.common.schemas.NotificationSeverity, str]
|
|
53
54
|
] = mlrun.common.schemas.NotificationSeverity.INFO,
|
|
54
|
-
runs: typing.Union[mlrun.lists.RunList, list] = None,
|
|
55
|
-
custom_html: str = None,
|
|
56
|
-
alert: mlrun.common.schemas.AlertConfig = None,
|
|
57
|
-
event_data: mlrun.common.schemas.Event = None,
|
|
55
|
+
runs: typing.Optional[typing.Union[mlrun.lists.RunList, list]] = None,
|
|
56
|
+
custom_html: typing.Optional[typing.Optional[str]] = None,
|
|
57
|
+
alert: typing.Optional[mlrun.common.schemas.AlertConfig] = None,
|
|
58
|
+
event_data: typing.Optional[mlrun.common.schemas.Event] = None,
|
|
58
59
|
):
|
|
59
60
|
if not self._ipython:
|
|
60
61
|
mlrun.utils.helpers.logger.debug(
|