mlrun 1.7.0rc14__py3-none-any.whl → 1.7.0rc15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mlrun might be problematic. Click here for more details.
- mlrun/__main__.py +0 -105
- mlrun/artifacts/__init__.py +1 -2
- mlrun/artifacts/base.py +8 -250
- mlrun/artifacts/dataset.py +1 -190
- mlrun/artifacts/manager.py +2 -41
- mlrun/artifacts/model.py +1 -140
- mlrun/artifacts/plots.py +1 -375
- mlrun/common/schemas/model_monitoring/__init__.py +4 -0
- mlrun/common/schemas/model_monitoring/constants.py +24 -3
- mlrun/common/schemas/model_monitoring/model_endpoints.py +13 -1
- mlrun/config.py +3 -3
- mlrun/data_types/to_pandas.py +4 -4
- mlrun/datastore/base.py +41 -9
- mlrun/datastore/datastore_profile.py +50 -3
- mlrun/datastore/inmem.py +2 -2
- mlrun/datastore/sources.py +43 -2
- mlrun/datastore/store_resources.py +2 -6
- mlrun/datastore/targets.py +106 -39
- mlrun/db/httpdb.py +4 -4
- mlrun/feature_store/__init__.py +0 -2
- mlrun/feature_store/api.py +12 -47
- mlrun/feature_store/feature_set.py +9 -0
- mlrun/feature_store/retrieval/base.py +9 -4
- mlrun/feature_store/retrieval/conversion.py +4 -4
- mlrun/feature_store/retrieval/dask_merger.py +2 -0
- mlrun/feature_store/retrieval/job.py +2 -0
- mlrun/feature_store/retrieval/local_merger.py +2 -0
- mlrun/feature_store/retrieval/spark_merger.py +5 -0
- mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +5 -10
- mlrun/kfpops.py +5 -10
- mlrun/launcher/base.py +1 -1
- mlrun/launcher/client.py +1 -1
- mlrun/lists.py +2 -2
- mlrun/model.py +18 -9
- mlrun/model_monitoring/api.py +41 -18
- mlrun/model_monitoring/application.py +5 -305
- mlrun/model_monitoring/applications/__init__.py +11 -0
- mlrun/model_monitoring/applications/_application_steps.py +158 -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 +92 -77
- mlrun/model_monitoring/applications/results.py +99 -0
- mlrun/model_monitoring/controller.py +3 -1
- mlrun/model_monitoring/db/stores/sqldb/models/base.py +7 -6
- mlrun/model_monitoring/db/stores/sqldb/sql_store.py +1 -1
- mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +67 -4
- mlrun/model_monitoring/evidently_application.py +6 -118
- mlrun/model_monitoring/helpers.py +1 -1
- mlrun/model_monitoring/model_endpoint.py +3 -2
- mlrun/model_monitoring/stream_processing.py +2 -3
- mlrun/model_monitoring/writer.py +69 -39
- mlrun/platforms/iguazio.py +2 -2
- mlrun/projects/project.py +18 -31
- mlrun/render.py +2 -10
- mlrun/run.py +1 -3
- mlrun/runtimes/__init__.py +3 -3
- mlrun/runtimes/base.py +3 -3
- mlrun/runtimes/funcdoc.py +0 -28
- mlrun/runtimes/local.py +1 -1
- mlrun/runtimes/mpijob/__init__.py +0 -20
- mlrun/runtimes/mpijob/v1.py +1 -1
- mlrun/runtimes/nuclio/function.py +1 -1
- mlrun/runtimes/utils.py +1 -1
- mlrun/utils/helpers.py +27 -40
- mlrun/utils/notifications/notification/slack.py +4 -2
- mlrun/utils/notifications/notification_pusher.py +133 -14
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc15.dist-info}/METADATA +2 -2
- {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc15.dist-info}/RECORD +75 -71
- mlrun/runtimes/mpijob/v1alpha1.py +0 -29
- /mlrun/{runtimes → common/runtimes}/constants.py +0 -0
- {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc15.dist-info}/LICENSE +0 -0
- {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc15.dist-info}/WHEEL +0 -0
- {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc15.dist-info}/entry_points.txt +0 -0
- {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc15.dist-info}/top_level.txt +0 -0
mlrun/artifacts/manager.py
CHANGED
|
@@ -24,7 +24,6 @@ from mlrun.utils.helpers import (
|
|
|
24
24
|
)
|
|
25
25
|
|
|
26
26
|
from ..utils import (
|
|
27
|
-
is_legacy_artifact,
|
|
28
27
|
is_relative_path,
|
|
29
28
|
logger,
|
|
30
29
|
validate_artifact_key_name,
|
|
@@ -33,56 +32,28 @@ from ..utils import (
|
|
|
33
32
|
from .base import (
|
|
34
33
|
Artifact,
|
|
35
34
|
DirArtifact,
|
|
36
|
-
LegacyArtifact,
|
|
37
|
-
LegacyDirArtifact,
|
|
38
|
-
LegacyLinkArtifact,
|
|
39
35
|
LinkArtifact,
|
|
40
36
|
)
|
|
41
37
|
from .dataset import (
|
|
42
38
|
DatasetArtifact,
|
|
43
|
-
LegacyDatasetArtifact,
|
|
44
|
-
LegacyTableArtifact,
|
|
45
39
|
TableArtifact,
|
|
46
40
|
)
|
|
47
|
-
from .model import
|
|
41
|
+
from .model import ModelArtifact
|
|
48
42
|
from .plots import (
|
|
49
|
-
BokehArtifact,
|
|
50
|
-
ChartArtifact,
|
|
51
|
-
LegacyBokehArtifact,
|
|
52
|
-
LegacyChartArtifact,
|
|
53
|
-
LegacyPlotArtifact,
|
|
54
|
-
LegacyPlotlyArtifact,
|
|
55
43
|
PlotArtifact,
|
|
56
44
|
PlotlyArtifact,
|
|
57
45
|
)
|
|
58
46
|
|
|
59
|
-
# TODO - Remove deprecated types when deleted in 1.7.0
|
|
60
47
|
artifact_types = {
|
|
61
48
|
"": Artifact,
|
|
62
49
|
"artifact": Artifact,
|
|
63
50
|
"dir": DirArtifact,
|
|
64
51
|
"link": LinkArtifact,
|
|
65
52
|
"plot": PlotArtifact,
|
|
66
|
-
"chart": ChartArtifact,
|
|
67
53
|
"table": TableArtifact,
|
|
68
54
|
"model": ModelArtifact,
|
|
69
55
|
"dataset": DatasetArtifact,
|
|
70
56
|
"plotly": PlotlyArtifact,
|
|
71
|
-
"bokeh": BokehArtifact,
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
# TODO - Remove this when legacy types are deleted in 1.7.0
|
|
75
|
-
legacy_artifact_types = {
|
|
76
|
-
"": LegacyArtifact,
|
|
77
|
-
"dir": LegacyDirArtifact,
|
|
78
|
-
"link": LegacyLinkArtifact,
|
|
79
|
-
"plot": LegacyPlotArtifact,
|
|
80
|
-
"chart": LegacyChartArtifact,
|
|
81
|
-
"table": LegacyTableArtifact,
|
|
82
|
-
"model": LegacyModelArtifact,
|
|
83
|
-
"dataset": LegacyDatasetArtifact,
|
|
84
|
-
"plotly": LegacyPlotlyArtifact,
|
|
85
|
-
"bokeh": LegacyBokehArtifact,
|
|
86
57
|
}
|
|
87
58
|
|
|
88
59
|
|
|
@@ -102,15 +73,8 @@ class ArtifactProducer:
|
|
|
102
73
|
|
|
103
74
|
|
|
104
75
|
def dict_to_artifact(struct: dict) -> Artifact:
|
|
105
|
-
# Need to distinguish between LegacyArtifact classes and Artifact classes. Use existence of the "metadata"
|
|
106
|
-
# property to make this distinction
|
|
107
76
|
kind = struct.get("kind", "")
|
|
108
|
-
|
|
109
|
-
if is_legacy_artifact(struct):
|
|
110
|
-
return mlrun.artifacts.base.convert_legacy_artifact_to_new_format(struct)
|
|
111
|
-
|
|
112
77
|
artifact_class = artifact_types[kind]
|
|
113
|
-
|
|
114
78
|
return artifact_class.from_dict(struct)
|
|
115
79
|
|
|
116
80
|
|
|
@@ -308,10 +272,7 @@ class ArtifactManager:
|
|
|
308
272
|
# before uploading the item, we want to ensure that its tags are valid,
|
|
309
273
|
# so that we don't upload something that won't be stored later
|
|
310
274
|
validate_tag_name(item.metadata.tag, "artifact.metadata.tag")
|
|
311
|
-
|
|
312
|
-
item.upload()
|
|
313
|
-
else:
|
|
314
|
-
item.upload(artifact_path=artifact_path)
|
|
275
|
+
item.upload(artifact_path=artifact_path)
|
|
315
276
|
|
|
316
277
|
if db_key:
|
|
317
278
|
self._log_to_db(db_key, project, producer.inputs, item)
|
mlrun/artifacts/model.py
CHANGED
|
@@ -18,7 +18,6 @@ from typing import Any, Optional
|
|
|
18
18
|
|
|
19
19
|
import pandas as pd
|
|
20
20
|
import yaml
|
|
21
|
-
from deprecated import deprecated
|
|
22
21
|
|
|
23
22
|
import mlrun
|
|
24
23
|
import mlrun.datastore
|
|
@@ -27,7 +26,7 @@ from ..data_types import InferOptions, get_infer_interface
|
|
|
27
26
|
from ..features import Feature
|
|
28
27
|
from ..model import ObjectList
|
|
29
28
|
from ..utils import StorePrefix, is_relative_path
|
|
30
|
-
from .base import Artifact, ArtifactSpec,
|
|
29
|
+
from .base import Artifact, ArtifactSpec, upload_extra_data
|
|
31
30
|
|
|
32
31
|
model_spec_filename = "model_spec.yaml"
|
|
33
32
|
|
|
@@ -397,144 +396,6 @@ class ModelArtifact(Artifact):
|
|
|
397
396
|
return mlrun.get_dataitem(target_model_path).get()
|
|
398
397
|
|
|
399
398
|
|
|
400
|
-
# TODO: remove in 1.7.0
|
|
401
|
-
@deprecated(
|
|
402
|
-
version="1.3.0",
|
|
403
|
-
reason="'LegacyModelArtifact' will be removed in 1.7.0, use 'ModelArtifact' instead",
|
|
404
|
-
category=FutureWarning,
|
|
405
|
-
)
|
|
406
|
-
class LegacyModelArtifact(LegacyArtifact):
|
|
407
|
-
"""ML Model artifact
|
|
408
|
-
|
|
409
|
-
Store link to ML model file(s) along with the model metrics, parameters, schema, and stats
|
|
410
|
-
"""
|
|
411
|
-
|
|
412
|
-
_dict_fields = LegacyArtifact._dict_fields + [
|
|
413
|
-
"model_file",
|
|
414
|
-
"metrics",
|
|
415
|
-
"parameters",
|
|
416
|
-
"inputs",
|
|
417
|
-
"outputs",
|
|
418
|
-
"framework",
|
|
419
|
-
"algorithm",
|
|
420
|
-
"extra_data",
|
|
421
|
-
"feature_vector",
|
|
422
|
-
"feature_weights",
|
|
423
|
-
"feature_stats",
|
|
424
|
-
"model_target_file",
|
|
425
|
-
]
|
|
426
|
-
kind = "model"
|
|
427
|
-
_store_prefix = StorePrefix.Model
|
|
428
|
-
|
|
429
|
-
def __init__(
|
|
430
|
-
self,
|
|
431
|
-
key=None,
|
|
432
|
-
body=None,
|
|
433
|
-
format=None,
|
|
434
|
-
model_file=None,
|
|
435
|
-
metrics=None,
|
|
436
|
-
target_path=None,
|
|
437
|
-
parameters=None,
|
|
438
|
-
inputs=None,
|
|
439
|
-
outputs=None,
|
|
440
|
-
framework=None,
|
|
441
|
-
algorithm=None,
|
|
442
|
-
feature_vector=None,
|
|
443
|
-
feature_weights=None,
|
|
444
|
-
extra_data=None,
|
|
445
|
-
model_target_file=None,
|
|
446
|
-
**kwargs,
|
|
447
|
-
):
|
|
448
|
-
super().__init__(key, body, format=format, target_path=target_path, **kwargs)
|
|
449
|
-
self._inputs: Optional[ObjectList] = None
|
|
450
|
-
self._outputs: Optional[ObjectList] = None
|
|
451
|
-
|
|
452
|
-
self.model_file = model_file
|
|
453
|
-
self.parameters = parameters or {}
|
|
454
|
-
self.metrics = metrics or {}
|
|
455
|
-
self.inputs = inputs or []
|
|
456
|
-
self.outputs = outputs or []
|
|
457
|
-
self.extra_data = extra_data or {}
|
|
458
|
-
self.framework = framework
|
|
459
|
-
self.algorithm = algorithm
|
|
460
|
-
self.feature_vector = feature_vector
|
|
461
|
-
self.feature_weights = feature_weights
|
|
462
|
-
self.feature_stats = None
|
|
463
|
-
self.model_target_file = model_target_file
|
|
464
|
-
|
|
465
|
-
@property
|
|
466
|
-
def inputs(self) -> Optional[ObjectList]:
|
|
467
|
-
"""input feature list"""
|
|
468
|
-
return self._inputs
|
|
469
|
-
|
|
470
|
-
@inputs.setter
|
|
471
|
-
def inputs(self, inputs: list[Feature]) -> None:
|
|
472
|
-
self._inputs = ObjectList.from_list(Feature, inputs)
|
|
473
|
-
|
|
474
|
-
@property
|
|
475
|
-
def outputs(self) -> Optional[ObjectList]:
|
|
476
|
-
"""output feature list"""
|
|
477
|
-
return self._outputs
|
|
478
|
-
|
|
479
|
-
@outputs.setter
|
|
480
|
-
def outputs(self, outputs: list[Feature]) -> None:
|
|
481
|
-
self._outputs = ObjectList.from_list(Feature, outputs)
|
|
482
|
-
|
|
483
|
-
def infer_from_df(self, df, label_columns=None, with_stats=True, num_bins=None):
|
|
484
|
-
"""infer inputs, outputs, and stats from provided df (training set)
|
|
485
|
-
|
|
486
|
-
:param df: dataframe to infer from
|
|
487
|
-
:param label_columns: name of the label (target) column
|
|
488
|
-
:param with_stats: infer statistics (min, max, .. histogram)
|
|
489
|
-
:param num_bins: number of bins for histogram
|
|
490
|
-
"""
|
|
491
|
-
subset = df
|
|
492
|
-
inferer = get_infer_interface(subset)
|
|
493
|
-
if label_columns:
|
|
494
|
-
if not isinstance(label_columns, list):
|
|
495
|
-
label_columns = [label_columns]
|
|
496
|
-
subset = df.drop(columns=label_columns)
|
|
497
|
-
inferer.infer_schema(subset, self.inputs, {}, options=InferOptions.Features)
|
|
498
|
-
if label_columns:
|
|
499
|
-
inferer.infer_schema(
|
|
500
|
-
df[label_columns], self.outputs, {}, options=InferOptions.Features
|
|
501
|
-
)
|
|
502
|
-
if with_stats:
|
|
503
|
-
self.feature_stats = inferer.get_stats(
|
|
504
|
-
df, options=InferOptions.Histogram, num_bins=num_bins
|
|
505
|
-
)
|
|
506
|
-
|
|
507
|
-
@property
|
|
508
|
-
def is_dir(self):
|
|
509
|
-
return True
|
|
510
|
-
|
|
511
|
-
def before_log(self):
|
|
512
|
-
if not self.model_file:
|
|
513
|
-
raise ValueError("model_file attr must be specified")
|
|
514
|
-
|
|
515
|
-
super().before_log()
|
|
516
|
-
|
|
517
|
-
if self.framework:
|
|
518
|
-
self.labels = self.labels or {}
|
|
519
|
-
self.labels["framework"] = self.framework
|
|
520
|
-
|
|
521
|
-
def upload(self):
|
|
522
|
-
target_model_path = path.join(self.target_path, self.model_file)
|
|
523
|
-
body = self.get_body()
|
|
524
|
-
if body:
|
|
525
|
-
self._upload_body(body, target=target_model_path)
|
|
526
|
-
else:
|
|
527
|
-
src_model_path = _get_src_path(self, self.model_file)
|
|
528
|
-
if not path.isfile(src_model_path):
|
|
529
|
-
raise ValueError(f"model file {src_model_path} not found")
|
|
530
|
-
self._upload_file(src_model_path, target=target_model_path)
|
|
531
|
-
|
|
532
|
-
upload_extra_data(self, self.extra_data)
|
|
533
|
-
|
|
534
|
-
spec_path = path.join(self.target_path, model_spec_filename)
|
|
535
|
-
mlrun.datastore.store_manager.object(url=spec_path).put(self.to_yaml())
|
|
536
|
-
|
|
537
|
-
|
|
538
399
|
def _get_src_path(model_spec: ModelArtifact, filename):
|
|
539
400
|
if model_spec.src_path:
|
|
540
401
|
return path.join(model_spec.src_path, filename)
|
mlrun/artifacts/plots.py
CHANGED
|
@@ -15,12 +15,9 @@ import base64
|
|
|
15
15
|
import typing
|
|
16
16
|
from io import BytesIO
|
|
17
17
|
|
|
18
|
-
from deprecated import deprecated
|
|
19
|
-
|
|
20
18
|
import mlrun
|
|
21
19
|
|
|
22
|
-
from
|
|
23
|
-
from .base import Artifact, LegacyArtifact
|
|
20
|
+
from .base import Artifact
|
|
24
21
|
|
|
25
22
|
if typing.TYPE_CHECKING:
|
|
26
23
|
from plotly.graph_objs import Figure
|
|
@@ -70,138 +67,6 @@ class PlotArtifact(Artifact):
|
|
|
70
67
|
)
|
|
71
68
|
|
|
72
69
|
|
|
73
|
-
# TODO: remove in 1.7.0
|
|
74
|
-
@deprecated(
|
|
75
|
-
version="1.5.0",
|
|
76
|
-
reason="'ChartArtifact' will be removed in 1.7.0, use 'Artifact' instead",
|
|
77
|
-
category=FutureWarning,
|
|
78
|
-
)
|
|
79
|
-
class ChartArtifact(Artifact):
|
|
80
|
-
kind = "chart"
|
|
81
|
-
|
|
82
|
-
_TEMPLATE = """
|
|
83
|
-
<html>
|
|
84
|
-
<head>
|
|
85
|
-
<script
|
|
86
|
-
type="text/javascript"
|
|
87
|
-
src="https://www.gstatic.com/charts/loader.js"></script>
|
|
88
|
-
<script type="text/javascript">
|
|
89
|
-
google.charts.load('current', {'packages':['corechart']});
|
|
90
|
-
google.charts.setOnLoadCallback(drawChart);
|
|
91
|
-
function drawChart() {
|
|
92
|
-
var data = google.visualization.arrayToDataTable($data$);
|
|
93
|
-
var options = $opts$;
|
|
94
|
-
var chart = new google.visualization.$chart$(
|
|
95
|
-
document.getElementById('chart_div'));
|
|
96
|
-
chart.draw(data, options);
|
|
97
|
-
}
|
|
98
|
-
</script>
|
|
99
|
-
</head>
|
|
100
|
-
<body>
|
|
101
|
-
<div id="chart_div" style="width: 100%; height: 500px;"></div>
|
|
102
|
-
</body>
|
|
103
|
-
</html>
|
|
104
|
-
"""
|
|
105
|
-
|
|
106
|
-
def __init__(
|
|
107
|
-
self,
|
|
108
|
-
key=None,
|
|
109
|
-
data=None,
|
|
110
|
-
header=None,
|
|
111
|
-
options=None,
|
|
112
|
-
title=None,
|
|
113
|
-
chart=None,
|
|
114
|
-
target_path=None,
|
|
115
|
-
):
|
|
116
|
-
data = [] if data is None else data
|
|
117
|
-
options = {} if options is None else options
|
|
118
|
-
super().__init__(key, target_path=target_path)
|
|
119
|
-
self.viewer = "chart"
|
|
120
|
-
self.header = header or []
|
|
121
|
-
self.title = title
|
|
122
|
-
self.rows = []
|
|
123
|
-
if data:
|
|
124
|
-
if header:
|
|
125
|
-
self.rows = data
|
|
126
|
-
else:
|
|
127
|
-
self.header = data[0]
|
|
128
|
-
self.rows = data[1:]
|
|
129
|
-
self.options = options
|
|
130
|
-
self.chart = chart or "LineChart"
|
|
131
|
-
self.format = "html"
|
|
132
|
-
|
|
133
|
-
def add_row(self, row):
|
|
134
|
-
self.rows += [row]
|
|
135
|
-
|
|
136
|
-
def get_body(self):
|
|
137
|
-
if not self.options.get("title"):
|
|
138
|
-
self.options["title"] = self.title or self.key
|
|
139
|
-
data = [self.header] + self.rows
|
|
140
|
-
return (
|
|
141
|
-
self._TEMPLATE.replace("$data$", dict_to_json(data))
|
|
142
|
-
.replace("$opts$", dict_to_json(self.options))
|
|
143
|
-
.replace("$chart$", self.chart)
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
# TODO: remove in 1.7.0
|
|
148
|
-
@deprecated(
|
|
149
|
-
version="1.5.0",
|
|
150
|
-
reason="'BokehArtifact' will be removed in 1.7.0, use 'Artifact' instead",
|
|
151
|
-
category=FutureWarning,
|
|
152
|
-
)
|
|
153
|
-
class BokehArtifact(Artifact):
|
|
154
|
-
"""
|
|
155
|
-
Bokeh artifact is an artifact for saving Bokeh generated figures. They will be stored in a html format.
|
|
156
|
-
"""
|
|
157
|
-
|
|
158
|
-
kind = "bokeh"
|
|
159
|
-
|
|
160
|
-
def __init__(
|
|
161
|
-
self,
|
|
162
|
-
figure=None,
|
|
163
|
-
key: str = None,
|
|
164
|
-
target_path: str = None,
|
|
165
|
-
):
|
|
166
|
-
"""
|
|
167
|
-
Initialize a Bokeh artifact with the given figure.
|
|
168
|
-
|
|
169
|
-
:param figure: Bokeh figure ('bokeh.plotting.Figure' object) to save as an artifact.
|
|
170
|
-
:param key: Key for the artifact to be stored in the database.
|
|
171
|
-
:param target_path: Path to save the artifact.
|
|
172
|
-
"""
|
|
173
|
-
# Validate input:
|
|
174
|
-
try:
|
|
175
|
-
from bokeh.plotting import Figure
|
|
176
|
-
except (ModuleNotFoundError, ImportError) as Error:
|
|
177
|
-
raise Error(
|
|
178
|
-
"Using 'BokehArtifact' requires bokeh package. Use pip install mlrun[bokeh] to install it."
|
|
179
|
-
)
|
|
180
|
-
if figure is not None and not isinstance(figure, Figure):
|
|
181
|
-
raise ValueError(
|
|
182
|
-
"BokehArtifact requires the figure parameter to be a "
|
|
183
|
-
f"'bokeh.plotting.Figure' but received '{type(figure)}'"
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
# Call the artifact initializer:
|
|
187
|
-
super().__init__(key=key, target_path=target_path, viewer="bokeh")
|
|
188
|
-
|
|
189
|
-
# Continue initializing the bokeh artifact:
|
|
190
|
-
self._figure = figure
|
|
191
|
-
self.spec.format = "html"
|
|
192
|
-
|
|
193
|
-
def get_body(self):
|
|
194
|
-
"""
|
|
195
|
-
Get the artifact's body - the bokeh figure's html code.
|
|
196
|
-
|
|
197
|
-
:return: The figure's html code.
|
|
198
|
-
"""
|
|
199
|
-
from bokeh.embed import file_html
|
|
200
|
-
from bokeh.resources import CDN
|
|
201
|
-
|
|
202
|
-
return file_html(self._figure, CDN, self.metadata.key)
|
|
203
|
-
|
|
204
|
-
|
|
205
70
|
class PlotlyArtifact(Artifact):
|
|
206
71
|
"""
|
|
207
72
|
Plotly artifact is an artifact for saving Plotly generated figures. They will be stored in a html format.
|
|
@@ -258,242 +123,3 @@ class PlotlyArtifact(Artifact):
|
|
|
258
123
|
:return: The figure's html code.
|
|
259
124
|
"""
|
|
260
125
|
return self._figure.to_html()
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
# TODO: remove in 1.7.0
|
|
264
|
-
@deprecated(
|
|
265
|
-
version="1.3.0",
|
|
266
|
-
reason="'LegacyPlotArtifact' will be removed in 1.7.0, use 'PlotArtifact' instead",
|
|
267
|
-
category=FutureWarning,
|
|
268
|
-
)
|
|
269
|
-
class LegacyPlotArtifact(LegacyArtifact):
|
|
270
|
-
_TEMPLATE = """
|
|
271
|
-
<h3 style="text-align:center">{}</h3>
|
|
272
|
-
<img title="{}" src="data:image/png;base64,{}">
|
|
273
|
-
"""
|
|
274
|
-
kind = "plot"
|
|
275
|
-
|
|
276
|
-
def __init__(
|
|
277
|
-
self, key=None, body=None, is_inline=False, target_path=None, title=None
|
|
278
|
-
):
|
|
279
|
-
super().__init__(key, body, format="html", target_path=target_path)
|
|
280
|
-
self.description = title
|
|
281
|
-
|
|
282
|
-
def before_log(self):
|
|
283
|
-
self.viewer = "chart"
|
|
284
|
-
import matplotlib
|
|
285
|
-
|
|
286
|
-
if not self._body or not isinstance(
|
|
287
|
-
self._body, (bytes, matplotlib.figure.Figure)
|
|
288
|
-
):
|
|
289
|
-
raise ValueError(
|
|
290
|
-
"matplotlib fig or png bytes must be provided as artifact body"
|
|
291
|
-
)
|
|
292
|
-
|
|
293
|
-
def get_body(self):
|
|
294
|
-
"""Convert Matplotlib figure 'fig' into a <img> tag for HTML use
|
|
295
|
-
using base64 encoding."""
|
|
296
|
-
if isinstance(self._body, bytes):
|
|
297
|
-
data = self._body
|
|
298
|
-
else:
|
|
299
|
-
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
|
|
300
|
-
|
|
301
|
-
canvas = FigureCanvas(self._body)
|
|
302
|
-
png_output = BytesIO()
|
|
303
|
-
canvas.print_png(png_output)
|
|
304
|
-
data = png_output.getvalue()
|
|
305
|
-
|
|
306
|
-
data_uri = base64.b64encode(data).decode("utf-8")
|
|
307
|
-
return self._TEMPLATE.format(self.description or self.key, self.key, data_uri)
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
# TODO: remove in 1.7.0
|
|
311
|
-
@deprecated(
|
|
312
|
-
version="1.3.0",
|
|
313
|
-
reason="'LegacyChartArtifact' will be removed in 1.7.0, use 'ChartArtifact' instead",
|
|
314
|
-
category=FutureWarning,
|
|
315
|
-
)
|
|
316
|
-
class LegacyChartArtifact(LegacyArtifact):
|
|
317
|
-
_TEMPLATE = """
|
|
318
|
-
<html>
|
|
319
|
-
<head>
|
|
320
|
-
<script
|
|
321
|
-
type="text/javascript"
|
|
322
|
-
src="https://www.gstatic.com/charts/loader.js"></script>
|
|
323
|
-
<script type="text/javascript">
|
|
324
|
-
google.charts.load('current', {'packages':['corechart']});
|
|
325
|
-
google.charts.setOnLoadCallback(drawChart);
|
|
326
|
-
function drawChart() {
|
|
327
|
-
var data = google.visualization.arrayToDataTable($data$);
|
|
328
|
-
var options = $opts$;
|
|
329
|
-
var chart = new google.visualization.$chart$(
|
|
330
|
-
document.getElementById('chart_div'));
|
|
331
|
-
chart.draw(data, options);
|
|
332
|
-
}
|
|
333
|
-
</script>
|
|
334
|
-
</head>
|
|
335
|
-
<body>
|
|
336
|
-
<div id="chart_div" style="width: 100%; height: 500px;"></div>
|
|
337
|
-
</body>
|
|
338
|
-
</html>
|
|
339
|
-
"""
|
|
340
|
-
|
|
341
|
-
kind = "chart"
|
|
342
|
-
|
|
343
|
-
def __init__(
|
|
344
|
-
self,
|
|
345
|
-
key=None,
|
|
346
|
-
data=None,
|
|
347
|
-
header=None,
|
|
348
|
-
options=None,
|
|
349
|
-
title=None,
|
|
350
|
-
chart=None,
|
|
351
|
-
target_path=None,
|
|
352
|
-
):
|
|
353
|
-
data = [] if data is None else data
|
|
354
|
-
options = {} if options is None else options
|
|
355
|
-
super().__init__(key, target_path=target_path)
|
|
356
|
-
self.viewer = "chart"
|
|
357
|
-
self.header = header or []
|
|
358
|
-
self.title = title
|
|
359
|
-
self.rows = []
|
|
360
|
-
if data:
|
|
361
|
-
if header:
|
|
362
|
-
self.rows = data
|
|
363
|
-
else:
|
|
364
|
-
self.header = data[0]
|
|
365
|
-
self.rows = data[1:]
|
|
366
|
-
self.options = options
|
|
367
|
-
self.chart = chart or "LineChart"
|
|
368
|
-
self.format = "html"
|
|
369
|
-
|
|
370
|
-
def add_row(self, row):
|
|
371
|
-
self.rows += [row]
|
|
372
|
-
|
|
373
|
-
def get_body(self):
|
|
374
|
-
if not self.options.get("title"):
|
|
375
|
-
self.options["title"] = self.title or self.key
|
|
376
|
-
data = [self.header] + self.rows
|
|
377
|
-
return (
|
|
378
|
-
self._TEMPLATE.replace("$data$", dict_to_json(data))
|
|
379
|
-
.replace("$opts$", dict_to_json(self.options))
|
|
380
|
-
.replace("$chart$", self.chart)
|
|
381
|
-
)
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
# TODO: remove in 1.7.0
|
|
385
|
-
@deprecated(
|
|
386
|
-
version="1.3.0",
|
|
387
|
-
reason="'LegacyBokehArtifact' will be removed in 1.7.0, use 'BokehArtifact' instead",
|
|
388
|
-
category=FutureWarning,
|
|
389
|
-
)
|
|
390
|
-
class LegacyBokehArtifact(LegacyArtifact):
|
|
391
|
-
"""
|
|
392
|
-
Bokeh artifact is an artifact for saving Bokeh generated figures. They will be stored in a html format.
|
|
393
|
-
"""
|
|
394
|
-
|
|
395
|
-
kind = "bokeh"
|
|
396
|
-
|
|
397
|
-
def __init__(
|
|
398
|
-
self,
|
|
399
|
-
figure=None,
|
|
400
|
-
key: str = None,
|
|
401
|
-
target_path: str = None,
|
|
402
|
-
):
|
|
403
|
-
"""
|
|
404
|
-
Initialize a Bokeh artifact with the given figure.
|
|
405
|
-
:param figure: Bokeh figure ('bokeh.plotting.Figure' object) to save as an artifact.
|
|
406
|
-
:param key: Key for the artifact to be stored in the database.
|
|
407
|
-
:param target_path: Path to save the artifact.
|
|
408
|
-
"""
|
|
409
|
-
# Validate input:
|
|
410
|
-
try:
|
|
411
|
-
from bokeh.plotting import Figure
|
|
412
|
-
except (ModuleNotFoundError, ImportError) as Error:
|
|
413
|
-
raise Error(
|
|
414
|
-
"Using 'BokehArtifact' requires bokeh package. Use pip install mlrun[bokeh] to install it."
|
|
415
|
-
)
|
|
416
|
-
if figure is not None and not isinstance(figure, Figure):
|
|
417
|
-
raise ValueError(
|
|
418
|
-
"BokehArtifact requires the figure parameter to be a "
|
|
419
|
-
f"'bokeh.plotting.Figure' but received '{type(figure)}'"
|
|
420
|
-
)
|
|
421
|
-
|
|
422
|
-
# Call the artifact initializer:
|
|
423
|
-
super().__init__(key=key, target_path=target_path, viewer="bokeh")
|
|
424
|
-
|
|
425
|
-
# Continue initializing the bokeh artifact:
|
|
426
|
-
self._figure = figure
|
|
427
|
-
self.format = "html"
|
|
428
|
-
|
|
429
|
-
def get_body(self):
|
|
430
|
-
"""
|
|
431
|
-
Get the artifact's body - the bokeh figure's html code.
|
|
432
|
-
:return: The figure's html code.
|
|
433
|
-
"""
|
|
434
|
-
from bokeh.embed import file_html
|
|
435
|
-
from bokeh.resources import CDN
|
|
436
|
-
|
|
437
|
-
return file_html(self._figure, CDN, self.key)
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
# TODO: remove in 1.7.0
|
|
441
|
-
@deprecated(
|
|
442
|
-
version="1.3.0",
|
|
443
|
-
reason="'LegacyPlotlyArtifact' will be removed in 1.7.0, use 'PlotlyArtifact' instead",
|
|
444
|
-
category=FutureWarning,
|
|
445
|
-
)
|
|
446
|
-
class LegacyPlotlyArtifact(LegacyArtifact):
|
|
447
|
-
"""
|
|
448
|
-
Plotly artifact is an artifact for saving Plotly generated figures. They will be stored in a html format.
|
|
449
|
-
"""
|
|
450
|
-
|
|
451
|
-
kind = "plotly"
|
|
452
|
-
|
|
453
|
-
def __init__(
|
|
454
|
-
self,
|
|
455
|
-
figure=None,
|
|
456
|
-
key: str = None,
|
|
457
|
-
target_path: str = None,
|
|
458
|
-
):
|
|
459
|
-
"""
|
|
460
|
-
Initialize a Plotly artifact with the given figure.
|
|
461
|
-
:param figure: Plotly figure ('plotly.graph_objs.Figure' object) to save as an artifact.
|
|
462
|
-
:param key: Key for the artifact to be stored in the database.
|
|
463
|
-
:param target_path: Path to save the artifact.
|
|
464
|
-
"""
|
|
465
|
-
# Validate the plotly package:
|
|
466
|
-
try:
|
|
467
|
-
from plotly.graph_objs import Figure
|
|
468
|
-
except ModuleNotFoundError:
|
|
469
|
-
raise mlrun.errors.MLRunMissingDependencyError(
|
|
470
|
-
"Using `PlotlyArtifact` requires plotly package. Use `pip install mlrun[plotly]` to install it."
|
|
471
|
-
)
|
|
472
|
-
except ImportError:
|
|
473
|
-
import plotly
|
|
474
|
-
|
|
475
|
-
raise mlrun.errors.MLRunMissingDependencyError(
|
|
476
|
-
f"Using `PlotlyArtifact` requires plotly version >= 5.4.0 but found version {plotly.__version__}. "
|
|
477
|
-
f"Use `pip install -U mlrun[plotly]` to install it."
|
|
478
|
-
)
|
|
479
|
-
|
|
480
|
-
# Call the artifact initializer:
|
|
481
|
-
super().__init__(key=key, target_path=target_path, viewer="plotly")
|
|
482
|
-
|
|
483
|
-
# Validate input:
|
|
484
|
-
if figure is not None and not isinstance(figure, Figure):
|
|
485
|
-
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
486
|
-
f"PlotlyArtifact requires the figure parameter to be a "
|
|
487
|
-
f"`plotly.graph_objs.Figure` but received '{type(figure)}'"
|
|
488
|
-
)
|
|
489
|
-
|
|
490
|
-
# Continue initializing the plotly artifact:
|
|
491
|
-
self._figure = figure
|
|
492
|
-
self.format = "html"
|
|
493
|
-
|
|
494
|
-
def get_body(self):
|
|
495
|
-
"""
|
|
496
|
-
Get the artifact's body - the Plotly figure's html code.
|
|
497
|
-
:return: The figure's html code.
|
|
498
|
-
"""
|
|
499
|
-
return self._figure.to_html()
|
|
@@ -25,6 +25,7 @@ from .constants import (
|
|
|
25
25
|
FeatureSetFeatures,
|
|
26
26
|
FileTargetKind,
|
|
27
27
|
FunctionURI,
|
|
28
|
+
MetricData,
|
|
28
29
|
ModelEndpointTarget,
|
|
29
30
|
ModelMonitoringMode,
|
|
30
31
|
ModelMonitoringStoreKinds,
|
|
@@ -32,6 +33,7 @@ from .constants import (
|
|
|
32
33
|
ProjectSecretKeys,
|
|
33
34
|
PrometheusEndpoints,
|
|
34
35
|
PrometheusMetric,
|
|
36
|
+
ResultData,
|
|
35
37
|
SchedulingKeys,
|
|
36
38
|
TimeSeriesTarget,
|
|
37
39
|
VersionedModel,
|
|
@@ -51,6 +53,8 @@ from .model_endpoints import (
|
|
|
51
53
|
ModelEndpoint,
|
|
52
54
|
ModelEndpointList,
|
|
53
55
|
ModelEndpointMetadata,
|
|
56
|
+
ModelEndpointMonitoringMetric,
|
|
57
|
+
ModelEndpointMonitoringMetricType,
|
|
54
58
|
ModelEndpointSpec,
|
|
55
59
|
ModelEndpointStatus,
|
|
56
60
|
)
|