mlrun 1.7.0rc5__py3-none-any.whl → 1.7.2__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 +39 -121
- mlrun/{datastore/helpers.py → alerts/__init__.py} +2 -5
- mlrun/alerts/alert.py +248 -0
- mlrun/api/schemas/__init__.py +4 -3
- mlrun/artifacts/__init__.py +8 -3
- mlrun/artifacts/base.py +39 -254
- mlrun/artifacts/dataset.py +9 -190
- mlrun/artifacts/manager.py +73 -46
- mlrun/artifacts/model.py +30 -158
- mlrun/artifacts/plots.py +23 -380
- mlrun/common/constants.py +73 -2
- mlrun/common/db/sql_session.py +3 -2
- mlrun/common/formatters/__init__.py +21 -0
- mlrun/common/formatters/artifact.py +46 -0
- mlrun/common/formatters/base.py +113 -0
- mlrun/common/formatters/feature_set.py +44 -0
- mlrun/common/formatters/function.py +46 -0
- mlrun/common/formatters/pipeline.py +53 -0
- mlrun/common/formatters/project.py +51 -0
- mlrun/common/formatters/run.py +29 -0
- mlrun/common/helpers.py +11 -1
- mlrun/{runtimes → common/runtimes}/constants.py +32 -4
- mlrun/common/schemas/__init__.py +21 -4
- mlrun/common/schemas/alert.py +202 -0
- mlrun/common/schemas/api_gateway.py +113 -2
- mlrun/common/schemas/artifact.py +28 -1
- mlrun/common/schemas/auth.py +11 -0
- mlrun/common/schemas/client_spec.py +2 -1
- mlrun/common/schemas/common.py +7 -4
- mlrun/common/schemas/constants.py +3 -0
- mlrun/common/schemas/feature_store.py +58 -28
- mlrun/common/schemas/frontend_spec.py +8 -0
- mlrun/common/schemas/function.py +11 -0
- mlrun/common/schemas/hub.py +7 -9
- mlrun/common/schemas/model_monitoring/__init__.py +21 -4
- mlrun/common/schemas/model_monitoring/constants.py +136 -42
- mlrun/common/schemas/model_monitoring/grafana.py +9 -5
- mlrun/common/schemas/model_monitoring/model_endpoints.py +89 -41
- mlrun/common/schemas/notification.py +69 -12
- mlrun/{runtimes/mpijob/v1alpha1.py → common/schemas/pagination.py} +10 -13
- mlrun/common/schemas/pipeline.py +7 -0
- mlrun/common/schemas/project.py +67 -16
- mlrun/common/schemas/runs.py +17 -0
- mlrun/common/schemas/schedule.py +1 -1
- mlrun/common/schemas/workflow.py +10 -2
- mlrun/common/types.py +14 -1
- mlrun/config.py +224 -58
- mlrun/data_types/data_types.py +11 -1
- mlrun/data_types/spark.py +5 -4
- mlrun/data_types/to_pandas.py +75 -34
- mlrun/datastore/__init__.py +8 -10
- mlrun/datastore/alibaba_oss.py +131 -0
- mlrun/datastore/azure_blob.py +131 -43
- mlrun/datastore/base.py +107 -47
- mlrun/datastore/datastore.py +17 -7
- mlrun/datastore/datastore_profile.py +91 -7
- mlrun/datastore/dbfs_store.py +3 -7
- mlrun/datastore/filestore.py +1 -3
- mlrun/datastore/google_cloud_storage.py +92 -32
- mlrun/datastore/hdfs.py +5 -0
- mlrun/datastore/inmem.py +6 -3
- mlrun/datastore/redis.py +3 -2
- mlrun/datastore/s3.py +30 -12
- mlrun/datastore/snowflake_utils.py +45 -0
- mlrun/datastore/sources.py +274 -59
- mlrun/datastore/spark_utils.py +30 -0
- mlrun/datastore/store_resources.py +9 -7
- mlrun/datastore/storeytargets.py +151 -0
- mlrun/datastore/targets.py +374 -102
- mlrun/datastore/utils.py +68 -5
- mlrun/datastore/v3io.py +28 -50
- mlrun/db/auth_utils.py +152 -0
- mlrun/db/base.py +231 -22
- mlrun/db/factory.py +1 -4
- mlrun/db/httpdb.py +864 -228
- mlrun/db/nopdb.py +268 -16
- mlrun/errors.py +35 -5
- mlrun/execution.py +111 -38
- mlrun/feature_store/__init__.py +0 -2
- mlrun/feature_store/api.py +46 -53
- mlrun/feature_store/common.py +6 -11
- mlrun/feature_store/feature_set.py +48 -23
- mlrun/feature_store/feature_vector.py +13 -2
- mlrun/feature_store/ingestion.py +7 -6
- mlrun/feature_store/retrieval/base.py +9 -4
- mlrun/feature_store/retrieval/dask_merger.py +2 -0
- mlrun/feature_store/retrieval/job.py +13 -4
- mlrun/feature_store/retrieval/local_merger.py +2 -0
- mlrun/feature_store/retrieval/spark_merger.py +24 -32
- mlrun/feature_store/steps.py +38 -19
- mlrun/features.py +6 -14
- mlrun/frameworks/_common/plan.py +3 -3
- mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +7 -12
- mlrun/frameworks/_ml_common/plan.py +1 -1
- mlrun/frameworks/auto_mlrun/auto_mlrun.py +2 -2
- mlrun/frameworks/lgbm/__init__.py +1 -1
- mlrun/frameworks/lgbm/callbacks/callback.py +2 -4
- mlrun/frameworks/lgbm/model_handler.py +1 -1
- mlrun/frameworks/parallel_coordinates.py +4 -4
- mlrun/frameworks/pytorch/__init__.py +2 -2
- mlrun/frameworks/sklearn/__init__.py +1 -1
- mlrun/frameworks/sklearn/mlrun_interface.py +13 -3
- mlrun/frameworks/tf_keras/__init__.py +5 -2
- mlrun/frameworks/tf_keras/callbacks/logging_callback.py +1 -1
- mlrun/frameworks/tf_keras/mlrun_interface.py +2 -2
- mlrun/frameworks/xgboost/__init__.py +1 -1
- mlrun/k8s_utils.py +57 -12
- mlrun/launcher/__init__.py +1 -1
- mlrun/launcher/base.py +6 -5
- mlrun/launcher/client.py +13 -11
- mlrun/launcher/factory.py +1 -1
- mlrun/launcher/local.py +15 -5
- mlrun/launcher/remote.py +10 -3
- mlrun/lists.py +6 -2
- mlrun/model.py +297 -48
- mlrun/model_monitoring/__init__.py +1 -1
- mlrun/model_monitoring/api.py +152 -357
- mlrun/model_monitoring/applications/__init__.py +10 -0
- mlrun/model_monitoring/applications/_application_steps.py +190 -0
- mlrun/model_monitoring/applications/base.py +108 -0
- mlrun/model_monitoring/applications/context.py +341 -0
- mlrun/model_monitoring/{evidently_application.py → applications/evidently_base.py} +27 -22
- mlrun/model_monitoring/applications/histogram_data_drift.py +227 -91
- mlrun/model_monitoring/applications/results.py +99 -0
- mlrun/model_monitoring/controller.py +130 -303
- mlrun/model_monitoring/{stores/models/sqlite.py → db/__init__.py} +5 -10
- mlrun/model_monitoring/db/stores/__init__.py +136 -0
- mlrun/model_monitoring/db/stores/base/__init__.py +15 -0
- mlrun/model_monitoring/db/stores/base/store.py +213 -0
- mlrun/model_monitoring/db/stores/sqldb/__init__.py +13 -0
- mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +71 -0
- mlrun/model_monitoring/db/stores/sqldb/models/base.py +190 -0
- mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +103 -0
- mlrun/model_monitoring/{stores/models/mysql.py → db/stores/sqldb/models/sqlite.py} +19 -13
- mlrun/model_monitoring/db/stores/sqldb/sql_store.py +659 -0
- mlrun/model_monitoring/db/stores/v3io_kv/__init__.py +13 -0
- mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +726 -0
- mlrun/model_monitoring/db/tsdb/__init__.py +105 -0
- mlrun/model_monitoring/db/tsdb/base.py +448 -0
- mlrun/model_monitoring/db/tsdb/helpers.py +30 -0
- mlrun/model_monitoring/db/tsdb/tdengine/__init__.py +15 -0
- mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +298 -0
- mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +42 -0
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +522 -0
- mlrun/model_monitoring/db/tsdb/v3io/__init__.py +15 -0
- mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +158 -0
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +849 -0
- mlrun/model_monitoring/features_drift_table.py +34 -22
- mlrun/model_monitoring/helpers.py +177 -39
- mlrun/model_monitoring/model_endpoint.py +3 -2
- mlrun/model_monitoring/stream_processing.py +165 -398
- mlrun/model_monitoring/tracking_policy.py +7 -1
- mlrun/model_monitoring/writer.py +161 -125
- mlrun/package/packagers/default_packager.py +2 -2
- mlrun/package/packagers_manager.py +1 -0
- mlrun/package/utils/_formatter.py +2 -2
- mlrun/platforms/__init__.py +11 -10
- mlrun/platforms/iguazio.py +67 -228
- mlrun/projects/__init__.py +6 -1
- mlrun/projects/operations.py +47 -20
- mlrun/projects/pipelines.py +396 -249
- mlrun/projects/project.py +1125 -414
- mlrun/render.py +28 -22
- mlrun/run.py +207 -180
- mlrun/runtimes/__init__.py +76 -11
- mlrun/runtimes/base.py +40 -14
- mlrun/runtimes/daskjob.py +9 -2
- mlrun/runtimes/databricks_job/databricks_runtime.py +1 -0
- mlrun/runtimes/databricks_job/databricks_wrapper.py +1 -1
- mlrun/runtimes/funcdoc.py +1 -29
- mlrun/runtimes/kubejob.py +34 -128
- mlrun/runtimes/local.py +39 -10
- mlrun/runtimes/mpijob/__init__.py +0 -20
- mlrun/runtimes/mpijob/abstract.py +8 -8
- mlrun/runtimes/mpijob/v1.py +1 -1
- mlrun/runtimes/nuclio/api_gateway.py +646 -177
- mlrun/runtimes/nuclio/application/__init__.py +15 -0
- mlrun/runtimes/nuclio/application/application.py +758 -0
- mlrun/runtimes/nuclio/application/reverse_proxy.go +95 -0
- mlrun/runtimes/nuclio/function.py +188 -68
- mlrun/runtimes/nuclio/serving.py +57 -60
- mlrun/runtimes/pod.py +191 -58
- mlrun/runtimes/remotesparkjob.py +11 -8
- mlrun/runtimes/sparkjob/spark3job.py +17 -18
- mlrun/runtimes/utils.py +40 -73
- mlrun/secrets.py +6 -2
- mlrun/serving/__init__.py +8 -1
- mlrun/serving/remote.py +2 -3
- mlrun/serving/routers.py +89 -64
- mlrun/serving/server.py +54 -26
- mlrun/serving/states.py +187 -56
- mlrun/serving/utils.py +19 -11
- mlrun/serving/v2_serving.py +136 -63
- mlrun/track/tracker.py +2 -1
- mlrun/track/trackers/mlflow_tracker.py +5 -0
- mlrun/utils/async_http.py +26 -6
- mlrun/utils/db.py +18 -0
- mlrun/utils/helpers.py +375 -105
- mlrun/utils/http.py +2 -2
- mlrun/utils/logger.py +75 -9
- mlrun/utils/notifications/notification/__init__.py +14 -10
- mlrun/utils/notifications/notification/base.py +48 -0
- mlrun/utils/notifications/notification/console.py +2 -0
- mlrun/utils/notifications/notification/git.py +24 -1
- mlrun/utils/notifications/notification/ipython.py +2 -0
- mlrun/utils/notifications/notification/slack.py +96 -21
- mlrun/utils/notifications/notification/webhook.py +63 -2
- mlrun/utils/notifications/notification_pusher.py +146 -16
- mlrun/utils/regex.py +9 -0
- mlrun/utils/retryer.py +3 -2
- mlrun/utils/v3io_clients.py +2 -3
- mlrun/utils/version/version.json +2 -2
- mlrun-1.7.2.dist-info/METADATA +390 -0
- mlrun-1.7.2.dist-info/RECORD +351 -0
- {mlrun-1.7.0rc5.dist-info → mlrun-1.7.2.dist-info}/WHEEL +1 -1
- mlrun/feature_store/retrieval/conversion.py +0 -271
- mlrun/kfpops.py +0 -868
- mlrun/model_monitoring/application.py +0 -310
- mlrun/model_monitoring/batch.py +0 -974
- mlrun/model_monitoring/controller_handler.py +0 -37
- mlrun/model_monitoring/prometheus.py +0 -216
- mlrun/model_monitoring/stores/__init__.py +0 -111
- mlrun/model_monitoring/stores/kv_model_endpoint_store.py +0 -574
- mlrun/model_monitoring/stores/model_endpoint_store.py +0 -145
- mlrun/model_monitoring/stores/models/__init__.py +0 -27
- mlrun/model_monitoring/stores/models/base.py +0 -84
- mlrun/model_monitoring/stores/sql_model_endpoint_store.py +0 -382
- mlrun/platforms/other.py +0 -305
- mlrun-1.7.0rc5.dist-info/METADATA +0 -269
- mlrun-1.7.0rc5.dist-info/RECORD +0 -323
- {mlrun-1.7.0rc5.dist-info → mlrun-1.7.2.dist-info}/LICENSE +0 -0
- {mlrun-1.7.0rc5.dist-info → mlrun-1.7.2.dist-info}/entry_points.txt +0 -0
- {mlrun-1.7.0rc5.dist-info → mlrun-1.7.2.dist-info}/top_level.txt +0 -0
mlrun/lists.py
CHANGED
|
@@ -21,7 +21,7 @@ import mlrun.frameworks
|
|
|
21
21
|
from .artifacts import Artifact, dict_to_artifact
|
|
22
22
|
from .config import config
|
|
23
23
|
from .render import artifacts_to_html, runs_to_html
|
|
24
|
-
from .utils import flatten, get_artifact_target, get_in
|
|
24
|
+
from .utils import flatten, get_artifact_target, get_in
|
|
25
25
|
|
|
26
26
|
list_header = [
|
|
27
27
|
"project",
|
|
@@ -29,12 +29,14 @@ list_header = [
|
|
|
29
29
|
"iter",
|
|
30
30
|
"start",
|
|
31
31
|
"state",
|
|
32
|
+
"kind",
|
|
32
33
|
"name",
|
|
33
34
|
"labels",
|
|
34
35
|
"inputs",
|
|
35
36
|
"parameters",
|
|
36
37
|
"results",
|
|
37
38
|
"artifacts",
|
|
39
|
+
"artifact_uris",
|
|
38
40
|
"error",
|
|
39
41
|
]
|
|
40
42
|
|
|
@@ -56,12 +58,14 @@ class RunList(list):
|
|
|
56
58
|
get_in(run, "metadata.iteration", ""),
|
|
57
59
|
get_in(run, "status.start_time", ""),
|
|
58
60
|
get_in(run, "status.state", ""),
|
|
61
|
+
get_in(run, "step_kind", get_in(run, "kind", "")),
|
|
59
62
|
get_in(run, "metadata.name", ""),
|
|
60
63
|
get_in(run, "metadata.labels", ""),
|
|
61
64
|
get_in(run, "spec.inputs", ""),
|
|
62
65
|
get_in(run, "spec.parameters", ""),
|
|
63
66
|
get_in(run, "status.results", ""),
|
|
64
67
|
get_in(run, "status.artifacts", []),
|
|
68
|
+
get_in(run, "status.artifact_uris", {}),
|
|
65
69
|
get_in(run, "status.error", ""),
|
|
66
70
|
]
|
|
67
71
|
if extend_iterations and iterations:
|
|
@@ -184,7 +188,7 @@ class ArtifactList(list):
|
|
|
184
188
|
"uri": ["uri", "uri"],
|
|
185
189
|
}
|
|
186
190
|
for artifact in self:
|
|
187
|
-
fields_index =
|
|
191
|
+
fields_index = 1
|
|
188
192
|
row = [get_in(artifact, v[fields_index], "") for k, v in head.items()]
|
|
189
193
|
artifact_uri = dict_to_artifact(artifact).uri
|
|
190
194
|
last_index = len(row) - 1
|
mlrun/model.py
CHANGED
|
@@ -27,13 +27,14 @@ from typing import Any, Optional, Union
|
|
|
27
27
|
import pydantic.error_wrappers
|
|
28
28
|
|
|
29
29
|
import mlrun
|
|
30
|
+
import mlrun.common.constants as mlrun_constants
|
|
30
31
|
import mlrun.common.schemas.notification
|
|
32
|
+
import mlrun.utils.regex
|
|
31
33
|
|
|
32
34
|
from .utils import (
|
|
33
35
|
dict_to_json,
|
|
34
36
|
dict_to_yaml,
|
|
35
37
|
get_artifact_target,
|
|
36
|
-
is_legacy_artifact,
|
|
37
38
|
logger,
|
|
38
39
|
template_artifact_path,
|
|
39
40
|
)
|
|
@@ -71,6 +72,7 @@ class ModelObj:
|
|
|
71
72
|
return new_type.from_dict(param)
|
|
72
73
|
return param
|
|
73
74
|
|
|
75
|
+
@mlrun.utils.filter_warnings("ignore", FutureWarning)
|
|
74
76
|
def to_dict(
|
|
75
77
|
self, fields: list = None, exclude: list = None, strip: bool = False
|
|
76
78
|
) -> dict:
|
|
@@ -485,7 +487,7 @@ class ImageBuilder(ModelObj):
|
|
|
485
487
|
|
|
486
488
|
def __init__(
|
|
487
489
|
self,
|
|
488
|
-
functionSourceCode=None,
|
|
490
|
+
functionSourceCode=None, # noqa: N803 - should be "snake_case", kept for BC
|
|
489
491
|
source=None,
|
|
490
492
|
image=None,
|
|
491
493
|
base_image=None,
|
|
@@ -677,14 +679,36 @@ class ImageBuilder(ModelObj):
|
|
|
677
679
|
|
|
678
680
|
|
|
679
681
|
class Notification(ModelObj):
|
|
680
|
-
"""Notification
|
|
682
|
+
"""Notification object
|
|
683
|
+
|
|
684
|
+
:param kind: notification implementation kind - slack, webhook, etc. See
|
|
685
|
+
:py:class:`mlrun.common.schemas.notification.NotificationKind`
|
|
686
|
+
:param name: for logging and identification
|
|
687
|
+
:param message: message content in the notification
|
|
688
|
+
:param severity: severity to display in the notification
|
|
689
|
+
:param when: list of statuses to trigger the notification: 'running', 'completed', 'error'
|
|
690
|
+
:param condition: optional condition to trigger the notification, a jinja2 expression that can use run data
|
|
691
|
+
to evaluate if the notification should be sent in addition to the 'when' statuses.
|
|
692
|
+
e.g.: '{{ run["status"]["results"]["accuracy"] < 0.9}}'
|
|
693
|
+
:param params: Implementation specific parameters for the notification implementation (e.g. slack webhook url,
|
|
694
|
+
git repository details, etc.)
|
|
695
|
+
:param secret_params: secret parameters for the notification implementation, same as params but will be stored
|
|
696
|
+
in a k8s secret and passed as a secret reference to the implementation.
|
|
697
|
+
:param status: notification status - pending, sent, error
|
|
698
|
+
:param sent_time: time the notification was sent
|
|
699
|
+
:param reason: failure reason if the notification failed to send
|
|
700
|
+
"""
|
|
681
701
|
|
|
682
702
|
def __init__(
|
|
683
703
|
self,
|
|
684
|
-
kind=
|
|
704
|
+
kind: mlrun.common.schemas.notification.NotificationKind = (
|
|
705
|
+
mlrun.common.schemas.notification.NotificationKind.slack
|
|
706
|
+
),
|
|
685
707
|
name=None,
|
|
686
708
|
message=None,
|
|
687
|
-
severity=
|
|
709
|
+
severity: mlrun.common.schemas.notification.NotificationSeverity = (
|
|
710
|
+
mlrun.common.schemas.notification.NotificationSeverity.INFO
|
|
711
|
+
),
|
|
688
712
|
when=None,
|
|
689
713
|
condition=None,
|
|
690
714
|
secret_params=None,
|
|
@@ -693,12 +717,10 @@ class Notification(ModelObj):
|
|
|
693
717
|
sent_time=None,
|
|
694
718
|
reason=None,
|
|
695
719
|
):
|
|
696
|
-
self.kind = kind
|
|
720
|
+
self.kind = kind
|
|
697
721
|
self.name = name or ""
|
|
698
722
|
self.message = message or ""
|
|
699
|
-
self.severity =
|
|
700
|
-
severity or mlrun.common.schemas.notification.NotificationSeverity.INFO
|
|
701
|
-
)
|
|
723
|
+
self.severity = severity
|
|
702
724
|
self.when = when or ["completed"]
|
|
703
725
|
self.condition = condition or ""
|
|
704
726
|
self.secret_params = secret_params or {}
|
|
@@ -728,6 +750,47 @@ class Notification(ModelObj):
|
|
|
728
750
|
"Notification params size exceeds max size of 1 MB"
|
|
729
751
|
)
|
|
730
752
|
|
|
753
|
+
def validate_notification_params(self):
|
|
754
|
+
notification_class = mlrun.utils.notifications.NotificationTypes(
|
|
755
|
+
self.kind
|
|
756
|
+
).get_notification()
|
|
757
|
+
|
|
758
|
+
secret_params = self.secret_params or {}
|
|
759
|
+
params = self.params or {}
|
|
760
|
+
|
|
761
|
+
# if the secret_params are already masked - no need to validate
|
|
762
|
+
params_secret = secret_params.get("secret", "")
|
|
763
|
+
if params_secret:
|
|
764
|
+
if len(secret_params) > 1:
|
|
765
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
766
|
+
"When the 'secret' key is present, 'secret_params' should not contain any other keys."
|
|
767
|
+
)
|
|
768
|
+
return
|
|
769
|
+
|
|
770
|
+
if not secret_params and not params:
|
|
771
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
772
|
+
"Both 'secret_params' and 'params' are empty, at least one must be defined."
|
|
773
|
+
)
|
|
774
|
+
|
|
775
|
+
notification_class.validate_params(secret_params | params)
|
|
776
|
+
|
|
777
|
+
def enrich_unmasked_secret_params_from_project_secret(self):
|
|
778
|
+
"""
|
|
779
|
+
Fill the notification secret params from the project secret.
|
|
780
|
+
We are using this function instead of unmask_secret_params_from_project_secret when we run inside the
|
|
781
|
+
workflow runner pod that doesn't have access to the k8s secrets (but have access to the project secret)
|
|
782
|
+
"""
|
|
783
|
+
secret = self.secret_params.get("secret")
|
|
784
|
+
if secret:
|
|
785
|
+
secret_value = mlrun.get_secret_or_env(secret)
|
|
786
|
+
if secret_value:
|
|
787
|
+
try:
|
|
788
|
+
self.secret_params = json.loads(secret_value)
|
|
789
|
+
except ValueError as exc:
|
|
790
|
+
raise mlrun.errors.MLRunValueError(
|
|
791
|
+
"Failed to parse secret value"
|
|
792
|
+
) from exc
|
|
793
|
+
|
|
731
794
|
@staticmethod
|
|
732
795
|
def validate_notification_uniqueness(notifications: list["Notification"]):
|
|
733
796
|
"""Validate that all notifications in the list are unique by name"""
|
|
@@ -765,6 +828,14 @@ class RunMetadata(ModelObj):
|
|
|
765
828
|
def iteration(self, iteration):
|
|
766
829
|
self._iteration = iteration
|
|
767
830
|
|
|
831
|
+
def is_workflow_runner(self):
|
|
832
|
+
if not self.labels:
|
|
833
|
+
return False
|
|
834
|
+
return (
|
|
835
|
+
self.labels.get(mlrun_constants.MLRunInternalLabels.job_type, "")
|
|
836
|
+
== "workflow-runner"
|
|
837
|
+
)
|
|
838
|
+
|
|
768
839
|
|
|
769
840
|
class HyperParamStrategies:
|
|
770
841
|
grid = "grid"
|
|
@@ -860,6 +931,8 @@ class RunSpec(ModelObj):
|
|
|
860
931
|
returns=None,
|
|
861
932
|
notifications=None,
|
|
862
933
|
state_thresholds=None,
|
|
934
|
+
reset_on_run=None,
|
|
935
|
+
node_selector=None,
|
|
863
936
|
):
|
|
864
937
|
# A dictionary of parsing configurations that will be read from the inputs the user set. The keys are the inputs
|
|
865
938
|
# keys (parameter names) and the values are the type hint given in the input keys after the colon.
|
|
@@ -896,6 +969,8 @@ class RunSpec(ModelObj):
|
|
|
896
969
|
self.allow_empty_resources = allow_empty_resources
|
|
897
970
|
self._notifications = notifications or []
|
|
898
971
|
self.state_thresholds = state_thresholds or {}
|
|
972
|
+
self.reset_on_run = reset_on_run
|
|
973
|
+
self.node_selector = node_selector or {}
|
|
899
974
|
|
|
900
975
|
def _serialize_field(
|
|
901
976
|
self, struct: dict, field_name: str = None, strip: bool = False
|
|
@@ -930,7 +1005,7 @@ class RunSpec(ModelObj):
|
|
|
930
1005
|
|
|
931
1006
|
>>> run_spec.inputs = {
|
|
932
1007
|
... "my_input": "...",
|
|
933
|
-
... "my_hinted_input : pandas.DataFrame": "..."
|
|
1008
|
+
... "my_hinted_input : pandas.DataFrame": "...",
|
|
934
1009
|
... }
|
|
935
1010
|
|
|
936
1011
|
:param inputs: The inputs to set.
|
|
@@ -1202,6 +1277,7 @@ class RunStatus(ModelObj):
|
|
|
1202
1277
|
ui_url=None,
|
|
1203
1278
|
reason: str = None,
|
|
1204
1279
|
notifications: dict[str, Notification] = None,
|
|
1280
|
+
artifact_uris: dict[str, str] = None,
|
|
1205
1281
|
):
|
|
1206
1282
|
self.state = state or "created"
|
|
1207
1283
|
self.status_text = status_text
|
|
@@ -1216,6 +1292,21 @@ class RunStatus(ModelObj):
|
|
|
1216
1292
|
self.ui_url = ui_url
|
|
1217
1293
|
self.reason = reason
|
|
1218
1294
|
self.notifications = notifications or {}
|
|
1295
|
+
# Artifact key -> URI mapping, since the full artifacts are not stored in the runs DB table
|
|
1296
|
+
self.artifact_uris = artifact_uris or {}
|
|
1297
|
+
|
|
1298
|
+
def is_failed(self) -> Optional[bool]:
|
|
1299
|
+
"""
|
|
1300
|
+
This method returns whether a run has failed.
|
|
1301
|
+
Returns none if state has yet to be defined. callee is responsible for handling None.
|
|
1302
|
+
(e.g wait for state to be defined)
|
|
1303
|
+
"""
|
|
1304
|
+
if not self.state:
|
|
1305
|
+
return None
|
|
1306
|
+
return self.state.casefold() in [
|
|
1307
|
+
mlrun.run.RunStatuses.failed.casefold(),
|
|
1308
|
+
mlrun.run.RunStatuses.error.casefold(),
|
|
1309
|
+
]
|
|
1219
1310
|
|
|
1220
1311
|
|
|
1221
1312
|
class RunTemplate(ModelObj):
|
|
@@ -1255,7 +1346,7 @@ class RunTemplate(ModelObj):
|
|
|
1255
1346
|
|
|
1256
1347
|
task.with_input("data", "/file-dir/path/to/file")
|
|
1257
1348
|
task.with_input("data", "s3://<bucket>/path/to/file")
|
|
1258
|
-
task.with_input("data", "v3io
|
|
1349
|
+
task.with_input("data", "v3io://<data-container>/path/to/file")
|
|
1259
1350
|
"""
|
|
1260
1351
|
if not self.spec.inputs:
|
|
1261
1352
|
self.spec.inputs = {}
|
|
@@ -1274,7 +1365,7 @@ class RunTemplate(ModelObj):
|
|
|
1274
1365
|
|
|
1275
1366
|
example::
|
|
1276
1367
|
|
|
1277
|
-
grid_params = {"p1": [2,4,1], "p2": [10,20]}
|
|
1368
|
+
grid_params = {"p1": [2, 4, 1], "p2": [10, 20]}
|
|
1278
1369
|
task = mlrun.new_task("grid-search")
|
|
1279
1370
|
task.with_hyper_params(grid_params, selector="max.accuracy")
|
|
1280
1371
|
"""
|
|
@@ -1412,33 +1503,63 @@ class RunObject(RunTemplate):
|
|
|
1412
1503
|
@property
|
|
1413
1504
|
def error(self) -> str:
|
|
1414
1505
|
"""error string if failed"""
|
|
1415
|
-
if
|
|
1506
|
+
if (
|
|
1507
|
+
self.status
|
|
1508
|
+
and self.status.state
|
|
1509
|
+
in mlrun.common.runtimes.constants.RunStates.error_and_abortion_states()
|
|
1510
|
+
):
|
|
1416
1511
|
unknown_error = ""
|
|
1417
1512
|
if (
|
|
1418
1513
|
self.status.state
|
|
1419
|
-
in mlrun.runtimes.constants.RunStates.abortion_states()
|
|
1514
|
+
in mlrun.common.runtimes.constants.RunStates.abortion_states()
|
|
1420
1515
|
):
|
|
1421
1516
|
unknown_error = "Run was aborted"
|
|
1422
1517
|
|
|
1423
|
-
elif
|
|
1518
|
+
elif (
|
|
1519
|
+
self.status.state
|
|
1520
|
+
in mlrun.common.runtimes.constants.RunStates.error_states()
|
|
1521
|
+
):
|
|
1424
1522
|
unknown_error = "Unknown error"
|
|
1425
1523
|
|
|
1426
1524
|
return (
|
|
1427
1525
|
self.status.error
|
|
1428
|
-
or self.status.reason
|
|
1429
1526
|
or self.status.status_text
|
|
1527
|
+
or self.status.reason
|
|
1430
1528
|
or unknown_error
|
|
1431
1529
|
)
|
|
1432
1530
|
return ""
|
|
1433
1531
|
|
|
1434
|
-
def output(self, key):
|
|
1435
|
-
"""
|
|
1532
|
+
def output(self, key: str):
|
|
1533
|
+
"""
|
|
1534
|
+
Return the value of a specific result or artifact by key.
|
|
1535
|
+
|
|
1536
|
+
This method waits for the outputs to complete and retrieves the value corresponding to the provided key.
|
|
1537
|
+
If the key exists in the results, it returns the corresponding result value.
|
|
1538
|
+
If not found in results, it attempts to fetch the artifact by key (cached in the run status).
|
|
1539
|
+
If the artifact is not found, it tries to fetch the artifact URI by key.
|
|
1540
|
+
If no artifact or result is found for the key, returns None.
|
|
1541
|
+
|
|
1542
|
+
:param key: The key of the result or artifact to retrieve.
|
|
1543
|
+
:return: The value of the result or the artifact URI corresponding to the key, or None if not found.
|
|
1544
|
+
"""
|
|
1436
1545
|
self._outputs_wait_for_completion()
|
|
1546
|
+
|
|
1547
|
+
# Check if the key exists in results and return the result value
|
|
1437
1548
|
if self.status.results and key in self.status.results:
|
|
1438
|
-
return self.status.results
|
|
1549
|
+
return self.status.results[key]
|
|
1550
|
+
|
|
1551
|
+
# Artifacts are usually cached in the run object under `status.artifacts`. However, the artifacts are not
|
|
1552
|
+
# stored in the DB as part of the run. The server may enrich the run with the artifacts or provide
|
|
1553
|
+
# `status.artifact_uris` instead. See mlrun.common.formatters.run.RunFormat.
|
|
1554
|
+
# When running locally - `status.artifact_uri` does not exist in the run.
|
|
1555
|
+
# When listing runs - `status.artifacts` does not exist in the run.
|
|
1439
1556
|
artifact = self._artifact(key)
|
|
1440
1557
|
if artifact:
|
|
1441
1558
|
return get_artifact_target(artifact, self.metadata.project)
|
|
1559
|
+
|
|
1560
|
+
if self.status.artifact_uris and key in self.status.artifact_uris:
|
|
1561
|
+
return self.status.artifact_uris[key]
|
|
1562
|
+
|
|
1442
1563
|
return None
|
|
1443
1564
|
|
|
1444
1565
|
@property
|
|
@@ -1451,26 +1572,50 @@ class RunObject(RunTemplate):
|
|
|
1451
1572
|
|
|
1452
1573
|
@property
|
|
1453
1574
|
def outputs(self):
|
|
1454
|
-
"""
|
|
1455
|
-
outputs
|
|
1575
|
+
"""
|
|
1576
|
+
Return a dictionary of outputs, including result values and artifact URIs.
|
|
1577
|
+
|
|
1578
|
+
This method waits for the outputs to complete and combines result values
|
|
1579
|
+
and artifact URIs into a single dictionary. If there are multiple artifacts
|
|
1580
|
+
for the same key, only include the artifact that does not have the "latest" tag.
|
|
1581
|
+
If there is no other tag, include the "latest" tag as a fallback.
|
|
1582
|
+
|
|
1583
|
+
:return: Dictionary containing result values and artifact URIs.
|
|
1584
|
+
"""
|
|
1456
1585
|
self._outputs_wait_for_completion()
|
|
1586
|
+
outputs = {}
|
|
1587
|
+
|
|
1588
|
+
# Add results if available
|
|
1457
1589
|
if self.status.results:
|
|
1458
|
-
outputs
|
|
1590
|
+
outputs.update(self.status.results)
|
|
1591
|
+
|
|
1592
|
+
# Artifacts are usually cached in the run object under `status.artifacts`. However, the artifacts are not
|
|
1593
|
+
# stored in the DB as part of the run. The server may enrich the run with the artifacts or provide
|
|
1594
|
+
# `status.artifact_uris` instead. See mlrun.common.formatters.run.RunFormat.
|
|
1595
|
+
# When running locally - `status.artifact_uri` does not exist in the run.
|
|
1596
|
+
# When listing runs - `status.artifacts` does not exist in the run.
|
|
1459
1597
|
if self.status.artifacts:
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1598
|
+
outputs.update(self._process_artifacts(self.status.artifacts))
|
|
1599
|
+
elif self.status.artifact_uris:
|
|
1600
|
+
outputs.update(self.status.artifact_uris)
|
|
1601
|
+
|
|
1463
1602
|
return outputs
|
|
1464
1603
|
|
|
1465
|
-
def artifact(self, key) -> "mlrun.DataItem":
|
|
1466
|
-
"""
|
|
1604
|
+
def artifact(self, key: str) -> "mlrun.DataItem":
|
|
1605
|
+
"""Return artifact DataItem by key.
|
|
1606
|
+
|
|
1607
|
+
This method waits for the outputs to complete, searches for the artifact matching the given key,
|
|
1608
|
+
and returns a DataItem if the artifact is found.
|
|
1609
|
+
|
|
1610
|
+
:param key: The key of the artifact to find.
|
|
1611
|
+
:return: A DataItem corresponding to the artifact with the given key, or None if no such artifact is found.
|
|
1612
|
+
"""
|
|
1467
1613
|
self._outputs_wait_for_completion()
|
|
1468
1614
|
artifact = self._artifact(key)
|
|
1469
|
-
if artifact:
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
return None
|
|
1615
|
+
if not artifact:
|
|
1616
|
+
return None
|
|
1617
|
+
uri = get_artifact_target(artifact, self.metadata.project)
|
|
1618
|
+
return mlrun.get_dataitem(uri) if uri else None
|
|
1474
1619
|
|
|
1475
1620
|
def _outputs_wait_for_completion(
|
|
1476
1621
|
self,
|
|
@@ -1488,12 +1633,85 @@ class RunObject(RunTemplate):
|
|
|
1488
1633
|
)
|
|
1489
1634
|
|
|
1490
1635
|
def _artifact(self, key):
|
|
1491
|
-
"""
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1636
|
+
"""
|
|
1637
|
+
Return the last artifact DataItem that matches the given key.
|
|
1638
|
+
|
|
1639
|
+
If multiple artifacts with the same key exist, return the last one in the list.
|
|
1640
|
+
If there are artifacts with different tags, the method will return the one with a tag other than 'latest'
|
|
1641
|
+
if available.
|
|
1642
|
+
If no artifact with the given key is found, return None.
|
|
1643
|
+
|
|
1644
|
+
:param key: The key of the artifact to retrieve.
|
|
1645
|
+
:return: The last artifact DataItem with the given key, or None if no such artifact is found.
|
|
1646
|
+
"""
|
|
1647
|
+
if not self.status.artifacts:
|
|
1648
|
+
return None
|
|
1649
|
+
|
|
1650
|
+
# Collect artifacts that match the key
|
|
1651
|
+
matching_artifacts = [
|
|
1652
|
+
artifact
|
|
1653
|
+
for artifact in self.status.artifacts
|
|
1654
|
+
if artifact["metadata"].get("key") == key
|
|
1655
|
+
]
|
|
1656
|
+
|
|
1657
|
+
if not matching_artifacts:
|
|
1658
|
+
return None
|
|
1659
|
+
|
|
1660
|
+
# Sort matching artifacts by creation date in ascending order.
|
|
1661
|
+
# The last element in the list will be the one created most recently.
|
|
1662
|
+
# In case the `created` field does not exist in the artifact, that artifact will appear first in the sorted list
|
|
1663
|
+
matching_artifacts.sort(
|
|
1664
|
+
key=lambda artifact: artifact["metadata"].get("created", datetime.min)
|
|
1665
|
+
)
|
|
1666
|
+
|
|
1667
|
+
# Filter out artifacts with 'latest' tag
|
|
1668
|
+
non_latest_artifacts = [
|
|
1669
|
+
artifact
|
|
1670
|
+
for artifact in matching_artifacts
|
|
1671
|
+
if artifact["metadata"].get("tag") != "latest"
|
|
1672
|
+
]
|
|
1673
|
+
|
|
1674
|
+
# Return the last non-'latest' artifact if available, otherwise return the last artifact
|
|
1675
|
+
# In the case of only one tag, `status.artifacts` includes [v1, latest]. In that case, we want to return v1.
|
|
1676
|
+
# In the case of multiple tags, `status.artifacts` includes [v1, latest, v2, v3].
|
|
1677
|
+
# In that case, we need to return the last one (v3).
|
|
1678
|
+
return (non_latest_artifacts or matching_artifacts)[-1]
|
|
1679
|
+
|
|
1680
|
+
def _process_artifacts(self, artifacts):
|
|
1681
|
+
artifacts_by_key = {}
|
|
1682
|
+
|
|
1683
|
+
# Organize artifacts by key
|
|
1684
|
+
for artifact in artifacts:
|
|
1685
|
+
key = artifact["metadata"]["key"]
|
|
1686
|
+
if key not in artifacts_by_key:
|
|
1687
|
+
artifacts_by_key[key] = []
|
|
1688
|
+
artifacts_by_key[key].append(artifact)
|
|
1689
|
+
|
|
1690
|
+
outputs = {}
|
|
1691
|
+
for key, artifacts in artifacts_by_key.items():
|
|
1692
|
+
# Sort matching artifacts by creation date in ascending order.
|
|
1693
|
+
# The last element in the list will be the one created most recently.
|
|
1694
|
+
# In case the `created` field does not exist in the artifactthat artifact will appear
|
|
1695
|
+
# first in the sorted list
|
|
1696
|
+
artifacts.sort(
|
|
1697
|
+
key=lambda artifact: artifact["metadata"].get("created", datetime.min)
|
|
1698
|
+
)
|
|
1699
|
+
|
|
1700
|
+
# Filter out artifacts with 'latest' tag
|
|
1701
|
+
non_latest_artifacts = [
|
|
1702
|
+
artifact
|
|
1703
|
+
for artifact in artifacts
|
|
1704
|
+
if artifact["metadata"].get("tag") != "latest"
|
|
1705
|
+
]
|
|
1706
|
+
|
|
1707
|
+
# Save the last non-'latest' artifact if available, otherwise save the last artifact
|
|
1708
|
+
# In the case of only one tag, `artifacts` includes [v1, latest], in that case, we want to save v1.
|
|
1709
|
+
# In the case of multiple tags, `artifacts` includes [v1, latest, v2, v3].
|
|
1710
|
+
# In that case, we need to save the last one (v3).
|
|
1711
|
+
artifact_to_save = (non_latest_artifacts or artifacts)[-1]
|
|
1712
|
+
outputs[key] = get_artifact_target(artifact_to_save, self.metadata.project)
|
|
1713
|
+
|
|
1714
|
+
return outputs
|
|
1497
1715
|
|
|
1498
1716
|
def uid(self):
|
|
1499
1717
|
"""run unique id"""
|
|
@@ -1501,7 +1719,10 @@ class RunObject(RunTemplate):
|
|
|
1501
1719
|
|
|
1502
1720
|
def state(self):
|
|
1503
1721
|
"""current run state"""
|
|
1504
|
-
if
|
|
1722
|
+
if (
|
|
1723
|
+
self.status.state
|
|
1724
|
+
in mlrun.common.runtimes.constants.RunStates.terminal_states()
|
|
1725
|
+
):
|
|
1505
1726
|
return self.status.state
|
|
1506
1727
|
self.refresh()
|
|
1507
1728
|
return self.status.state or "unknown"
|
|
@@ -1515,8 +1736,10 @@ class RunObject(RunTemplate):
|
|
|
1515
1736
|
iter=self.metadata.iteration,
|
|
1516
1737
|
)
|
|
1517
1738
|
if run:
|
|
1518
|
-
|
|
1519
|
-
|
|
1739
|
+
run_status = run.get("status", {})
|
|
1740
|
+
# Artifacts are not stored in the DB, so we need to preserve them here
|
|
1741
|
+
run_status["artifacts"] = self.status.artifacts
|
|
1742
|
+
self.status = RunStatus.from_dict(run_status)
|
|
1520
1743
|
return self
|
|
1521
1744
|
|
|
1522
1745
|
def show(self):
|
|
@@ -1563,7 +1786,7 @@ class RunObject(RunTemplate):
|
|
|
1563
1786
|
last_pull_log_time = None
|
|
1564
1787
|
logs_enabled = show_logs is not False
|
|
1565
1788
|
state = self.state()
|
|
1566
|
-
if state not in mlrun.runtimes.constants.RunStates.terminal_states():
|
|
1789
|
+
if state not in mlrun.common.runtimes.constants.RunStates.terminal_states():
|
|
1567
1790
|
logger.info(
|
|
1568
1791
|
f"run {self.metadata.name} is not completed yet, waiting for it to complete",
|
|
1569
1792
|
current_state=state,
|
|
@@ -1573,7 +1796,8 @@ class RunObject(RunTemplate):
|
|
|
1573
1796
|
if (
|
|
1574
1797
|
logs_enabled
|
|
1575
1798
|
and logs_interval
|
|
1576
|
-
and state
|
|
1799
|
+
and state
|
|
1800
|
+
not in mlrun.common.runtimes.constants.RunStates.terminal_states()
|
|
1577
1801
|
and (
|
|
1578
1802
|
last_pull_log_time is None
|
|
1579
1803
|
or (datetime.now() - last_pull_log_time).seconds > logs_interval
|
|
@@ -1582,7 +1806,7 @@ class RunObject(RunTemplate):
|
|
|
1582
1806
|
last_pull_log_time = datetime.now()
|
|
1583
1807
|
state, offset = self.logs(watch=False, offset=offset)
|
|
1584
1808
|
|
|
1585
|
-
if state in mlrun.runtimes.constants.RunStates.terminal_states():
|
|
1809
|
+
if state in mlrun.common.runtimes.constants.RunStates.terminal_states():
|
|
1586
1810
|
if logs_enabled and logs_interval:
|
|
1587
1811
|
self.logs(watch=False, offset=offset)
|
|
1588
1812
|
break
|
|
@@ -1594,13 +1818,21 @@ class RunObject(RunTemplate):
|
|
|
1594
1818
|
)
|
|
1595
1819
|
if logs_enabled and not logs_interval:
|
|
1596
1820
|
self.logs(watch=False)
|
|
1597
|
-
if
|
|
1821
|
+
if (
|
|
1822
|
+
raise_on_failure
|
|
1823
|
+
and state != mlrun.common.runtimes.constants.RunStates.completed
|
|
1824
|
+
):
|
|
1598
1825
|
raise mlrun.errors.MLRunRuntimeError(
|
|
1599
1826
|
f"Task {self.metadata.name} did not complete (state={state})"
|
|
1600
1827
|
)
|
|
1601
1828
|
|
|
1602
1829
|
return state
|
|
1603
1830
|
|
|
1831
|
+
def abort(self):
|
|
1832
|
+
"""abort the run"""
|
|
1833
|
+
db = mlrun.get_run_db()
|
|
1834
|
+
db.abort_run(self.metadata.uid, self.metadata.project)
|
|
1835
|
+
|
|
1604
1836
|
@staticmethod
|
|
1605
1837
|
def create_uri(project: str, uid: str, iteration: Union[int, str], tag: str = ""):
|
|
1606
1838
|
if tag:
|
|
@@ -1610,9 +1842,12 @@ class RunObject(RunTemplate):
|
|
|
1610
1842
|
|
|
1611
1843
|
@staticmethod
|
|
1612
1844
|
def parse_uri(uri: str) -> tuple[str, str, str, str]:
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1845
|
+
"""Parse the run's uri
|
|
1846
|
+
|
|
1847
|
+
:param uri: run uri in the format of <project>@<uid>#<iteration>[:tag]
|
|
1848
|
+
:return: project, uid, iteration, tag
|
|
1849
|
+
"""
|
|
1850
|
+
uri_pattern = mlrun.utils.regex.run_uri_pattern
|
|
1616
1851
|
match = re.match(uri_pattern, uri)
|
|
1617
1852
|
if not match:
|
|
1618
1853
|
raise ValueError(
|
|
@@ -1826,6 +2061,8 @@ class DataSource(ModelObj):
|
|
|
1826
2061
|
]
|
|
1827
2062
|
kind = None
|
|
1828
2063
|
|
|
2064
|
+
_fields_to_serialize = ["start_time", "end_time"]
|
|
2065
|
+
|
|
1829
2066
|
def __init__(
|
|
1830
2067
|
self,
|
|
1831
2068
|
name: str = None,
|
|
@@ -1854,6 +2091,16 @@ class DataSource(ModelObj):
|
|
|
1854
2091
|
def set_secrets(self, secrets):
|
|
1855
2092
|
self._secrets = secrets
|
|
1856
2093
|
|
|
2094
|
+
def _serialize_field(
|
|
2095
|
+
self, struct: dict, field_name: str = None, strip: bool = False
|
|
2096
|
+
) -> typing.Any:
|
|
2097
|
+
value = super()._serialize_field(struct, field_name, strip)
|
|
2098
|
+
# We pull the field from self and not from struct because it was excluded from the struct when looping over
|
|
2099
|
+
# the fields to save.
|
|
2100
|
+
if field_name in ("start_time", "end_time") and isinstance(value, datetime):
|
|
2101
|
+
return value.isoformat()
|
|
2102
|
+
return value
|
|
2103
|
+
|
|
1857
2104
|
|
|
1858
2105
|
class DataTargetBase(ModelObj):
|
|
1859
2106
|
"""data target spec, specify a destination for the feature set data"""
|
|
@@ -1944,6 +2191,7 @@ class DataTarget(DataTargetBase):
|
|
|
1944
2191
|
"name",
|
|
1945
2192
|
"kind",
|
|
1946
2193
|
"path",
|
|
2194
|
+
"attributes",
|
|
1947
2195
|
"start_time",
|
|
1948
2196
|
"online",
|
|
1949
2197
|
"status",
|
|
@@ -1975,6 +2223,7 @@ class DataTarget(DataTargetBase):
|
|
|
1975
2223
|
self.last_written = None
|
|
1976
2224
|
self._producer = None
|
|
1977
2225
|
self.producer = {}
|
|
2226
|
+
self.attributes = {}
|
|
1978
2227
|
|
|
1979
2228
|
@property
|
|
1980
2229
|
def producer(self) -> FeatureSetProducer:
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
# flake8: noqa - this is until we take care of the F401 violations with respect to __all__ & sphinx
|
|
16
16
|
# for backwards compatibility
|
|
17
17
|
|
|
18
|
+
from .db import get_store_object, get_tsdb_connector
|
|
18
19
|
from .helpers import get_stream_path
|
|
19
20
|
from .model_endpoint import ModelEndpoint
|
|
20
|
-
from .stores import ModelEndpointStore, ModelEndpointStoreType, get_model_endpoint_store
|
|
21
21
|
from .tracking_policy import TrackingPolicy
|