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.

Files changed (234) hide show
  1. mlrun/__init__.py +11 -1
  2. mlrun/__main__.py +39 -121
  3. mlrun/{datastore/helpers.py → alerts/__init__.py} +2 -5
  4. mlrun/alerts/alert.py +248 -0
  5. mlrun/api/schemas/__init__.py +4 -3
  6. mlrun/artifacts/__init__.py +8 -3
  7. mlrun/artifacts/base.py +39 -254
  8. mlrun/artifacts/dataset.py +9 -190
  9. mlrun/artifacts/manager.py +73 -46
  10. mlrun/artifacts/model.py +30 -158
  11. mlrun/artifacts/plots.py +23 -380
  12. mlrun/common/constants.py +73 -2
  13. mlrun/common/db/sql_session.py +3 -2
  14. mlrun/common/formatters/__init__.py +21 -0
  15. mlrun/common/formatters/artifact.py +46 -0
  16. mlrun/common/formatters/base.py +113 -0
  17. mlrun/common/formatters/feature_set.py +44 -0
  18. mlrun/common/formatters/function.py +46 -0
  19. mlrun/common/formatters/pipeline.py +53 -0
  20. mlrun/common/formatters/project.py +51 -0
  21. mlrun/common/formatters/run.py +29 -0
  22. mlrun/common/helpers.py +11 -1
  23. mlrun/{runtimes → common/runtimes}/constants.py +32 -4
  24. mlrun/common/schemas/__init__.py +21 -4
  25. mlrun/common/schemas/alert.py +202 -0
  26. mlrun/common/schemas/api_gateway.py +113 -2
  27. mlrun/common/schemas/artifact.py +28 -1
  28. mlrun/common/schemas/auth.py +11 -0
  29. mlrun/common/schemas/client_spec.py +2 -1
  30. mlrun/common/schemas/common.py +7 -4
  31. mlrun/common/schemas/constants.py +3 -0
  32. mlrun/common/schemas/feature_store.py +58 -28
  33. mlrun/common/schemas/frontend_spec.py +8 -0
  34. mlrun/common/schemas/function.py +11 -0
  35. mlrun/common/schemas/hub.py +7 -9
  36. mlrun/common/schemas/model_monitoring/__init__.py +21 -4
  37. mlrun/common/schemas/model_monitoring/constants.py +136 -42
  38. mlrun/common/schemas/model_monitoring/grafana.py +9 -5
  39. mlrun/common/schemas/model_monitoring/model_endpoints.py +89 -41
  40. mlrun/common/schemas/notification.py +69 -12
  41. mlrun/{runtimes/mpijob/v1alpha1.py → common/schemas/pagination.py} +10 -13
  42. mlrun/common/schemas/pipeline.py +7 -0
  43. mlrun/common/schemas/project.py +67 -16
  44. mlrun/common/schemas/runs.py +17 -0
  45. mlrun/common/schemas/schedule.py +1 -1
  46. mlrun/common/schemas/workflow.py +10 -2
  47. mlrun/common/types.py +14 -1
  48. mlrun/config.py +224 -58
  49. mlrun/data_types/data_types.py +11 -1
  50. mlrun/data_types/spark.py +5 -4
  51. mlrun/data_types/to_pandas.py +75 -34
  52. mlrun/datastore/__init__.py +8 -10
  53. mlrun/datastore/alibaba_oss.py +131 -0
  54. mlrun/datastore/azure_blob.py +131 -43
  55. mlrun/datastore/base.py +107 -47
  56. mlrun/datastore/datastore.py +17 -7
  57. mlrun/datastore/datastore_profile.py +91 -7
  58. mlrun/datastore/dbfs_store.py +3 -7
  59. mlrun/datastore/filestore.py +1 -3
  60. mlrun/datastore/google_cloud_storage.py +92 -32
  61. mlrun/datastore/hdfs.py +5 -0
  62. mlrun/datastore/inmem.py +6 -3
  63. mlrun/datastore/redis.py +3 -2
  64. mlrun/datastore/s3.py +30 -12
  65. mlrun/datastore/snowflake_utils.py +45 -0
  66. mlrun/datastore/sources.py +274 -59
  67. mlrun/datastore/spark_utils.py +30 -0
  68. mlrun/datastore/store_resources.py +9 -7
  69. mlrun/datastore/storeytargets.py +151 -0
  70. mlrun/datastore/targets.py +374 -102
  71. mlrun/datastore/utils.py +68 -5
  72. mlrun/datastore/v3io.py +28 -50
  73. mlrun/db/auth_utils.py +152 -0
  74. mlrun/db/base.py +231 -22
  75. mlrun/db/factory.py +1 -4
  76. mlrun/db/httpdb.py +864 -228
  77. mlrun/db/nopdb.py +268 -16
  78. mlrun/errors.py +35 -5
  79. mlrun/execution.py +111 -38
  80. mlrun/feature_store/__init__.py +0 -2
  81. mlrun/feature_store/api.py +46 -53
  82. mlrun/feature_store/common.py +6 -11
  83. mlrun/feature_store/feature_set.py +48 -23
  84. mlrun/feature_store/feature_vector.py +13 -2
  85. mlrun/feature_store/ingestion.py +7 -6
  86. mlrun/feature_store/retrieval/base.py +9 -4
  87. mlrun/feature_store/retrieval/dask_merger.py +2 -0
  88. mlrun/feature_store/retrieval/job.py +13 -4
  89. mlrun/feature_store/retrieval/local_merger.py +2 -0
  90. mlrun/feature_store/retrieval/spark_merger.py +24 -32
  91. mlrun/feature_store/steps.py +38 -19
  92. mlrun/features.py +6 -14
  93. mlrun/frameworks/_common/plan.py +3 -3
  94. mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +7 -12
  95. mlrun/frameworks/_ml_common/plan.py +1 -1
  96. mlrun/frameworks/auto_mlrun/auto_mlrun.py +2 -2
  97. mlrun/frameworks/lgbm/__init__.py +1 -1
  98. mlrun/frameworks/lgbm/callbacks/callback.py +2 -4
  99. mlrun/frameworks/lgbm/model_handler.py +1 -1
  100. mlrun/frameworks/parallel_coordinates.py +4 -4
  101. mlrun/frameworks/pytorch/__init__.py +2 -2
  102. mlrun/frameworks/sklearn/__init__.py +1 -1
  103. mlrun/frameworks/sklearn/mlrun_interface.py +13 -3
  104. mlrun/frameworks/tf_keras/__init__.py +5 -2
  105. mlrun/frameworks/tf_keras/callbacks/logging_callback.py +1 -1
  106. mlrun/frameworks/tf_keras/mlrun_interface.py +2 -2
  107. mlrun/frameworks/xgboost/__init__.py +1 -1
  108. mlrun/k8s_utils.py +57 -12
  109. mlrun/launcher/__init__.py +1 -1
  110. mlrun/launcher/base.py +6 -5
  111. mlrun/launcher/client.py +13 -11
  112. mlrun/launcher/factory.py +1 -1
  113. mlrun/launcher/local.py +15 -5
  114. mlrun/launcher/remote.py +10 -3
  115. mlrun/lists.py +6 -2
  116. mlrun/model.py +297 -48
  117. mlrun/model_monitoring/__init__.py +1 -1
  118. mlrun/model_monitoring/api.py +152 -357
  119. mlrun/model_monitoring/applications/__init__.py +10 -0
  120. mlrun/model_monitoring/applications/_application_steps.py +190 -0
  121. mlrun/model_monitoring/applications/base.py +108 -0
  122. mlrun/model_monitoring/applications/context.py +341 -0
  123. mlrun/model_monitoring/{evidently_application.py → applications/evidently_base.py} +27 -22
  124. mlrun/model_monitoring/applications/histogram_data_drift.py +227 -91
  125. mlrun/model_monitoring/applications/results.py +99 -0
  126. mlrun/model_monitoring/controller.py +130 -303
  127. mlrun/model_monitoring/{stores/models/sqlite.py → db/__init__.py} +5 -10
  128. mlrun/model_monitoring/db/stores/__init__.py +136 -0
  129. mlrun/model_monitoring/db/stores/base/__init__.py +15 -0
  130. mlrun/model_monitoring/db/stores/base/store.py +213 -0
  131. mlrun/model_monitoring/db/stores/sqldb/__init__.py +13 -0
  132. mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +71 -0
  133. mlrun/model_monitoring/db/stores/sqldb/models/base.py +190 -0
  134. mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +103 -0
  135. mlrun/model_monitoring/{stores/models/mysql.py → db/stores/sqldb/models/sqlite.py} +19 -13
  136. mlrun/model_monitoring/db/stores/sqldb/sql_store.py +659 -0
  137. mlrun/model_monitoring/db/stores/v3io_kv/__init__.py +13 -0
  138. mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +726 -0
  139. mlrun/model_monitoring/db/tsdb/__init__.py +105 -0
  140. mlrun/model_monitoring/db/tsdb/base.py +448 -0
  141. mlrun/model_monitoring/db/tsdb/helpers.py +30 -0
  142. mlrun/model_monitoring/db/tsdb/tdengine/__init__.py +15 -0
  143. mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +298 -0
  144. mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +42 -0
  145. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +522 -0
  146. mlrun/model_monitoring/db/tsdb/v3io/__init__.py +15 -0
  147. mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +158 -0
  148. mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +849 -0
  149. mlrun/model_monitoring/features_drift_table.py +34 -22
  150. mlrun/model_monitoring/helpers.py +177 -39
  151. mlrun/model_monitoring/model_endpoint.py +3 -2
  152. mlrun/model_monitoring/stream_processing.py +165 -398
  153. mlrun/model_monitoring/tracking_policy.py +7 -1
  154. mlrun/model_monitoring/writer.py +161 -125
  155. mlrun/package/packagers/default_packager.py +2 -2
  156. mlrun/package/packagers_manager.py +1 -0
  157. mlrun/package/utils/_formatter.py +2 -2
  158. mlrun/platforms/__init__.py +11 -10
  159. mlrun/platforms/iguazio.py +67 -228
  160. mlrun/projects/__init__.py +6 -1
  161. mlrun/projects/operations.py +47 -20
  162. mlrun/projects/pipelines.py +396 -249
  163. mlrun/projects/project.py +1125 -414
  164. mlrun/render.py +28 -22
  165. mlrun/run.py +207 -180
  166. mlrun/runtimes/__init__.py +76 -11
  167. mlrun/runtimes/base.py +40 -14
  168. mlrun/runtimes/daskjob.py +9 -2
  169. mlrun/runtimes/databricks_job/databricks_runtime.py +1 -0
  170. mlrun/runtimes/databricks_job/databricks_wrapper.py +1 -1
  171. mlrun/runtimes/funcdoc.py +1 -29
  172. mlrun/runtimes/kubejob.py +34 -128
  173. mlrun/runtimes/local.py +39 -10
  174. mlrun/runtimes/mpijob/__init__.py +0 -20
  175. mlrun/runtimes/mpijob/abstract.py +8 -8
  176. mlrun/runtimes/mpijob/v1.py +1 -1
  177. mlrun/runtimes/nuclio/api_gateway.py +646 -177
  178. mlrun/runtimes/nuclio/application/__init__.py +15 -0
  179. mlrun/runtimes/nuclio/application/application.py +758 -0
  180. mlrun/runtimes/nuclio/application/reverse_proxy.go +95 -0
  181. mlrun/runtimes/nuclio/function.py +188 -68
  182. mlrun/runtimes/nuclio/serving.py +57 -60
  183. mlrun/runtimes/pod.py +191 -58
  184. mlrun/runtimes/remotesparkjob.py +11 -8
  185. mlrun/runtimes/sparkjob/spark3job.py +17 -18
  186. mlrun/runtimes/utils.py +40 -73
  187. mlrun/secrets.py +6 -2
  188. mlrun/serving/__init__.py +8 -1
  189. mlrun/serving/remote.py +2 -3
  190. mlrun/serving/routers.py +89 -64
  191. mlrun/serving/server.py +54 -26
  192. mlrun/serving/states.py +187 -56
  193. mlrun/serving/utils.py +19 -11
  194. mlrun/serving/v2_serving.py +136 -63
  195. mlrun/track/tracker.py +2 -1
  196. mlrun/track/trackers/mlflow_tracker.py +5 -0
  197. mlrun/utils/async_http.py +26 -6
  198. mlrun/utils/db.py +18 -0
  199. mlrun/utils/helpers.py +375 -105
  200. mlrun/utils/http.py +2 -2
  201. mlrun/utils/logger.py +75 -9
  202. mlrun/utils/notifications/notification/__init__.py +14 -10
  203. mlrun/utils/notifications/notification/base.py +48 -0
  204. mlrun/utils/notifications/notification/console.py +2 -0
  205. mlrun/utils/notifications/notification/git.py +24 -1
  206. mlrun/utils/notifications/notification/ipython.py +2 -0
  207. mlrun/utils/notifications/notification/slack.py +96 -21
  208. mlrun/utils/notifications/notification/webhook.py +63 -2
  209. mlrun/utils/notifications/notification_pusher.py +146 -16
  210. mlrun/utils/regex.py +9 -0
  211. mlrun/utils/retryer.py +3 -2
  212. mlrun/utils/v3io_clients.py +2 -3
  213. mlrun/utils/version/version.json +2 -2
  214. mlrun-1.7.2.dist-info/METADATA +390 -0
  215. mlrun-1.7.2.dist-info/RECORD +351 -0
  216. {mlrun-1.7.0rc5.dist-info → mlrun-1.7.2.dist-info}/WHEEL +1 -1
  217. mlrun/feature_store/retrieval/conversion.py +0 -271
  218. mlrun/kfpops.py +0 -868
  219. mlrun/model_monitoring/application.py +0 -310
  220. mlrun/model_monitoring/batch.py +0 -974
  221. mlrun/model_monitoring/controller_handler.py +0 -37
  222. mlrun/model_monitoring/prometheus.py +0 -216
  223. mlrun/model_monitoring/stores/__init__.py +0 -111
  224. mlrun/model_monitoring/stores/kv_model_endpoint_store.py +0 -574
  225. mlrun/model_monitoring/stores/model_endpoint_store.py +0 -145
  226. mlrun/model_monitoring/stores/models/__init__.py +0 -27
  227. mlrun/model_monitoring/stores/models/base.py +0 -84
  228. mlrun/model_monitoring/stores/sql_model_endpoint_store.py +0 -382
  229. mlrun/platforms/other.py +0 -305
  230. mlrun-1.7.0rc5.dist-info/METADATA +0 -269
  231. mlrun-1.7.0rc5.dist-info/RECORD +0 -323
  232. {mlrun-1.7.0rc5.dist-info → mlrun-1.7.2.dist-info}/LICENSE +0 -0
  233. {mlrun-1.7.0rc5.dist-info → mlrun-1.7.2.dist-info}/entry_points.txt +0 -0
  234. {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, is_legacy_artifact
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 = 0 if is_legacy_artifact(artifact) else 1
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 specification"""
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=None,
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=None,
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 or mlrun.common.schemas.notification.NotificationKind.slack
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://[<remote-host>]/<data-container>/path/to/file")
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 self.status:
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 self.status.state in mlrun.runtimes.constants.RunStates.error_states():
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
- """return the value of a specific result or artifact by key"""
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.get(key)
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
- """return a dict of outputs, result values and artifact uris"""
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 = {k: v for k, v in self.status.results.items()}
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
- for a in self.status.artifacts:
1461
- key = a["key"] if is_legacy_artifact(a) else a["metadata"]["key"]
1462
- outputs[key] = get_artifact_target(a, self.metadata.project)
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
- """return artifact DataItem by key"""
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
- uri = get_artifact_target(artifact, self.metadata.project)
1471
- if uri:
1472
- return mlrun.get_dataitem(uri)
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
- """return artifact DataItem by key"""
1492
- if self.status.artifacts:
1493
- for a in self.status.artifacts:
1494
- if a["metadata"]["key"] == key:
1495
- return a
1496
- return None
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 self.status.state in mlrun.runtimes.constants.RunStates.terminal_states():
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
- self.status = RunStatus.from_dict(run.get("status", {}))
1519
- self.status.from_dict(run.get("status", {}))
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 not in mlrun.runtimes.constants.RunStates.terminal_states()
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 raise_on_failure and state != mlrun.runtimes.constants.RunStates.completed:
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
- uri_pattern = (
1614
- r"^(?P<project>.*)@(?P<uid>.*)\#(?P<iteration>.*?)(:(?P<tag>.*))?$"
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