mlrun 1.6.4rc7__py3-none-any.whl → 1.7.0__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 (305) hide show
  1. mlrun/__init__.py +11 -1
  2. mlrun/__main__.py +40 -122
  3. mlrun/alerts/__init__.py +15 -0
  4. mlrun/alerts/alert.py +248 -0
  5. mlrun/api/schemas/__init__.py +5 -4
  6. mlrun/artifacts/__init__.py +8 -3
  7. mlrun/artifacts/base.py +47 -257
  8. mlrun/artifacts/dataset.py +11 -192
  9. mlrun/artifacts/manager.py +79 -47
  10. mlrun/artifacts/model.py +31 -159
  11. mlrun/artifacts/plots.py +23 -380
  12. mlrun/common/constants.py +74 -1
  13. mlrun/common/db/sql_session.py +5 -5
  14. mlrun/common/formatters/__init__.py +21 -0
  15. mlrun/common/formatters/artifact.py +45 -0
  16. mlrun/common/formatters/base.py +113 -0
  17. mlrun/common/formatters/feature_set.py +33 -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 +12 -3
  23. mlrun/common/model_monitoring/helpers.py +9 -5
  24. mlrun/{runtimes → common/runtimes}/constants.py +37 -9
  25. mlrun/common/schemas/__init__.py +31 -5
  26. mlrun/common/schemas/alert.py +202 -0
  27. mlrun/common/schemas/api_gateway.py +196 -0
  28. mlrun/common/schemas/artifact.py +25 -4
  29. mlrun/common/schemas/auth.py +16 -5
  30. mlrun/common/schemas/background_task.py +1 -1
  31. mlrun/common/schemas/client_spec.py +4 -2
  32. mlrun/common/schemas/common.py +7 -4
  33. mlrun/common/schemas/constants.py +3 -0
  34. mlrun/common/schemas/feature_store.py +74 -44
  35. mlrun/common/schemas/frontend_spec.py +15 -7
  36. mlrun/common/schemas/function.py +12 -1
  37. mlrun/common/schemas/hub.py +11 -18
  38. mlrun/common/schemas/memory_reports.py +2 -2
  39. mlrun/common/schemas/model_monitoring/__init__.py +20 -4
  40. mlrun/common/schemas/model_monitoring/constants.py +123 -42
  41. mlrun/common/schemas/model_monitoring/grafana.py +13 -9
  42. mlrun/common/schemas/model_monitoring/model_endpoints.py +101 -54
  43. mlrun/common/schemas/notification.py +71 -14
  44. mlrun/common/schemas/object.py +2 -2
  45. mlrun/{model_monitoring/controller_handler.py → common/schemas/pagination.py} +9 -12
  46. mlrun/common/schemas/pipeline.py +8 -1
  47. mlrun/common/schemas/project.py +69 -18
  48. mlrun/common/schemas/runs.py +7 -1
  49. mlrun/common/schemas/runtime_resource.py +8 -12
  50. mlrun/common/schemas/schedule.py +4 -4
  51. mlrun/common/schemas/tag.py +1 -2
  52. mlrun/common/schemas/workflow.py +12 -4
  53. mlrun/common/types.py +14 -1
  54. mlrun/config.py +154 -69
  55. mlrun/data_types/data_types.py +6 -1
  56. mlrun/data_types/spark.py +2 -2
  57. mlrun/data_types/to_pandas.py +67 -37
  58. mlrun/datastore/__init__.py +6 -8
  59. mlrun/datastore/alibaba_oss.py +131 -0
  60. mlrun/datastore/azure_blob.py +143 -42
  61. mlrun/datastore/base.py +102 -58
  62. mlrun/datastore/datastore.py +34 -13
  63. mlrun/datastore/datastore_profile.py +146 -20
  64. mlrun/datastore/dbfs_store.py +3 -7
  65. mlrun/datastore/filestore.py +1 -4
  66. mlrun/datastore/google_cloud_storage.py +97 -33
  67. mlrun/datastore/hdfs.py +56 -0
  68. mlrun/datastore/inmem.py +6 -3
  69. mlrun/datastore/redis.py +7 -2
  70. mlrun/datastore/s3.py +34 -12
  71. mlrun/datastore/snowflake_utils.py +45 -0
  72. mlrun/datastore/sources.py +303 -111
  73. mlrun/datastore/spark_utils.py +31 -2
  74. mlrun/datastore/store_resources.py +9 -7
  75. mlrun/datastore/storeytargets.py +151 -0
  76. mlrun/datastore/targets.py +453 -176
  77. mlrun/datastore/utils.py +72 -58
  78. mlrun/datastore/v3io.py +6 -1
  79. mlrun/db/base.py +274 -41
  80. mlrun/db/factory.py +1 -1
  81. mlrun/db/httpdb.py +893 -225
  82. mlrun/db/nopdb.py +291 -33
  83. mlrun/errors.py +36 -6
  84. mlrun/execution.py +115 -42
  85. mlrun/feature_store/__init__.py +0 -2
  86. mlrun/feature_store/api.py +65 -73
  87. mlrun/feature_store/common.py +7 -12
  88. mlrun/feature_store/feature_set.py +76 -55
  89. mlrun/feature_store/feature_vector.py +39 -31
  90. mlrun/feature_store/ingestion.py +7 -6
  91. mlrun/feature_store/retrieval/base.py +16 -11
  92. mlrun/feature_store/retrieval/dask_merger.py +2 -0
  93. mlrun/feature_store/retrieval/job.py +13 -4
  94. mlrun/feature_store/retrieval/local_merger.py +2 -0
  95. mlrun/feature_store/retrieval/spark_merger.py +24 -32
  96. mlrun/feature_store/steps.py +45 -34
  97. mlrun/features.py +11 -21
  98. mlrun/frameworks/_common/artifacts_library.py +9 -9
  99. mlrun/frameworks/_common/mlrun_interface.py +5 -5
  100. mlrun/frameworks/_common/model_handler.py +48 -48
  101. mlrun/frameworks/_common/plan.py +5 -6
  102. mlrun/frameworks/_common/producer.py +3 -4
  103. mlrun/frameworks/_common/utils.py +5 -5
  104. mlrun/frameworks/_dl_common/loggers/logger.py +6 -7
  105. mlrun/frameworks/_dl_common/loggers/mlrun_logger.py +9 -9
  106. mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +23 -47
  107. mlrun/frameworks/_ml_common/artifacts_library.py +1 -2
  108. mlrun/frameworks/_ml_common/loggers/logger.py +3 -4
  109. mlrun/frameworks/_ml_common/loggers/mlrun_logger.py +4 -5
  110. mlrun/frameworks/_ml_common/model_handler.py +24 -24
  111. mlrun/frameworks/_ml_common/pkl_model_server.py +2 -2
  112. mlrun/frameworks/_ml_common/plan.py +2 -2
  113. mlrun/frameworks/_ml_common/plans/calibration_curve_plan.py +2 -3
  114. mlrun/frameworks/_ml_common/plans/confusion_matrix_plan.py +2 -3
  115. mlrun/frameworks/_ml_common/plans/dataset_plan.py +3 -3
  116. mlrun/frameworks/_ml_common/plans/feature_importance_plan.py +3 -3
  117. mlrun/frameworks/_ml_common/plans/roc_curve_plan.py +4 -4
  118. mlrun/frameworks/_ml_common/utils.py +4 -4
  119. mlrun/frameworks/auto_mlrun/auto_mlrun.py +9 -9
  120. mlrun/frameworks/huggingface/model_server.py +4 -4
  121. mlrun/frameworks/lgbm/__init__.py +33 -33
  122. mlrun/frameworks/lgbm/callbacks/callback.py +2 -4
  123. mlrun/frameworks/lgbm/callbacks/logging_callback.py +4 -5
  124. mlrun/frameworks/lgbm/callbacks/mlrun_logging_callback.py +4 -5
  125. mlrun/frameworks/lgbm/mlrun_interfaces/booster_mlrun_interface.py +1 -3
  126. mlrun/frameworks/lgbm/mlrun_interfaces/mlrun_interface.py +6 -6
  127. mlrun/frameworks/lgbm/model_handler.py +10 -10
  128. mlrun/frameworks/lgbm/model_server.py +6 -6
  129. mlrun/frameworks/lgbm/utils.py +5 -5
  130. mlrun/frameworks/onnx/dataset.py +8 -8
  131. mlrun/frameworks/onnx/mlrun_interface.py +3 -3
  132. mlrun/frameworks/onnx/model_handler.py +6 -6
  133. mlrun/frameworks/onnx/model_server.py +7 -7
  134. mlrun/frameworks/parallel_coordinates.py +6 -6
  135. mlrun/frameworks/pytorch/__init__.py +18 -18
  136. mlrun/frameworks/pytorch/callbacks/callback.py +4 -5
  137. mlrun/frameworks/pytorch/callbacks/logging_callback.py +17 -17
  138. mlrun/frameworks/pytorch/callbacks/mlrun_logging_callback.py +11 -11
  139. mlrun/frameworks/pytorch/callbacks/tensorboard_logging_callback.py +23 -29
  140. mlrun/frameworks/pytorch/callbacks_handler.py +38 -38
  141. mlrun/frameworks/pytorch/mlrun_interface.py +20 -20
  142. mlrun/frameworks/pytorch/model_handler.py +17 -17
  143. mlrun/frameworks/pytorch/model_server.py +7 -7
  144. mlrun/frameworks/sklearn/__init__.py +13 -13
  145. mlrun/frameworks/sklearn/estimator.py +4 -4
  146. mlrun/frameworks/sklearn/metrics_library.py +14 -14
  147. mlrun/frameworks/sklearn/mlrun_interface.py +16 -9
  148. mlrun/frameworks/sklearn/model_handler.py +2 -2
  149. mlrun/frameworks/tf_keras/__init__.py +10 -7
  150. mlrun/frameworks/tf_keras/callbacks/logging_callback.py +15 -15
  151. mlrun/frameworks/tf_keras/callbacks/mlrun_logging_callback.py +11 -11
  152. mlrun/frameworks/tf_keras/callbacks/tensorboard_logging_callback.py +19 -23
  153. mlrun/frameworks/tf_keras/mlrun_interface.py +9 -11
  154. mlrun/frameworks/tf_keras/model_handler.py +14 -14
  155. mlrun/frameworks/tf_keras/model_server.py +6 -6
  156. mlrun/frameworks/xgboost/__init__.py +13 -13
  157. mlrun/frameworks/xgboost/model_handler.py +6 -6
  158. mlrun/k8s_utils.py +61 -17
  159. mlrun/launcher/__init__.py +1 -1
  160. mlrun/launcher/base.py +16 -15
  161. mlrun/launcher/client.py +13 -11
  162. mlrun/launcher/factory.py +1 -1
  163. mlrun/launcher/local.py +23 -13
  164. mlrun/launcher/remote.py +17 -10
  165. mlrun/lists.py +7 -6
  166. mlrun/model.py +478 -103
  167. mlrun/model_monitoring/__init__.py +1 -1
  168. mlrun/model_monitoring/api.py +163 -371
  169. mlrun/{runtimes/mpijob/v1alpha1.py → model_monitoring/applications/__init__.py} +9 -15
  170. mlrun/model_monitoring/applications/_application_steps.py +188 -0
  171. mlrun/model_monitoring/applications/base.py +108 -0
  172. mlrun/model_monitoring/applications/context.py +341 -0
  173. mlrun/model_monitoring/{evidently_application.py → applications/evidently_base.py} +27 -22
  174. mlrun/model_monitoring/applications/histogram_data_drift.py +354 -0
  175. mlrun/model_monitoring/applications/results.py +99 -0
  176. mlrun/model_monitoring/controller.py +131 -278
  177. mlrun/model_monitoring/db/__init__.py +18 -0
  178. mlrun/model_monitoring/db/stores/__init__.py +136 -0
  179. mlrun/model_monitoring/db/stores/base/__init__.py +15 -0
  180. mlrun/model_monitoring/db/stores/base/store.py +213 -0
  181. mlrun/model_monitoring/db/stores/sqldb/__init__.py +13 -0
  182. mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +71 -0
  183. mlrun/model_monitoring/db/stores/sqldb/models/base.py +190 -0
  184. mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +103 -0
  185. mlrun/model_monitoring/{stores/models/mysql.py → db/stores/sqldb/models/sqlite.py} +19 -13
  186. mlrun/model_monitoring/db/stores/sqldb/sql_store.py +659 -0
  187. mlrun/model_monitoring/db/stores/v3io_kv/__init__.py +13 -0
  188. mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +726 -0
  189. mlrun/model_monitoring/db/tsdb/__init__.py +105 -0
  190. mlrun/model_monitoring/db/tsdb/base.py +448 -0
  191. mlrun/model_monitoring/db/tsdb/helpers.py +30 -0
  192. mlrun/model_monitoring/db/tsdb/tdengine/__init__.py +15 -0
  193. mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +279 -0
  194. mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +42 -0
  195. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +507 -0
  196. mlrun/model_monitoring/db/tsdb/v3io/__init__.py +15 -0
  197. mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +158 -0
  198. mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +849 -0
  199. mlrun/model_monitoring/features_drift_table.py +134 -106
  200. mlrun/model_monitoring/helpers.py +199 -55
  201. mlrun/model_monitoring/metrics/__init__.py +13 -0
  202. mlrun/model_monitoring/metrics/histogram_distance.py +127 -0
  203. mlrun/model_monitoring/model_endpoint.py +3 -2
  204. mlrun/model_monitoring/stream_processing.py +131 -398
  205. mlrun/model_monitoring/tracking_policy.py +9 -2
  206. mlrun/model_monitoring/writer.py +161 -125
  207. mlrun/package/__init__.py +6 -6
  208. mlrun/package/context_handler.py +5 -5
  209. mlrun/package/packager.py +7 -7
  210. mlrun/package/packagers/default_packager.py +8 -8
  211. mlrun/package/packagers/numpy_packagers.py +15 -15
  212. mlrun/package/packagers/pandas_packagers.py +5 -5
  213. mlrun/package/packagers/python_standard_library_packagers.py +10 -10
  214. mlrun/package/packagers_manager.py +19 -23
  215. mlrun/package/utils/_formatter.py +6 -6
  216. mlrun/package/utils/_pickler.py +2 -2
  217. mlrun/package/utils/_supported_format.py +4 -4
  218. mlrun/package/utils/log_hint_utils.py +2 -2
  219. mlrun/package/utils/type_hint_utils.py +4 -9
  220. mlrun/platforms/__init__.py +11 -10
  221. mlrun/platforms/iguazio.py +24 -203
  222. mlrun/projects/operations.py +52 -25
  223. mlrun/projects/pipelines.py +191 -197
  224. mlrun/projects/project.py +1227 -400
  225. mlrun/render.py +16 -19
  226. mlrun/run.py +209 -184
  227. mlrun/runtimes/__init__.py +83 -15
  228. mlrun/runtimes/base.py +51 -35
  229. mlrun/runtimes/daskjob.py +17 -10
  230. mlrun/runtimes/databricks_job/databricks_cancel_task.py +1 -1
  231. mlrun/runtimes/databricks_job/databricks_runtime.py +8 -7
  232. mlrun/runtimes/databricks_job/databricks_wrapper.py +1 -1
  233. mlrun/runtimes/funcdoc.py +1 -29
  234. mlrun/runtimes/function_reference.py +1 -1
  235. mlrun/runtimes/kubejob.py +34 -128
  236. mlrun/runtimes/local.py +40 -11
  237. mlrun/runtimes/mpijob/__init__.py +0 -20
  238. mlrun/runtimes/mpijob/abstract.py +9 -10
  239. mlrun/runtimes/mpijob/v1.py +1 -1
  240. mlrun/{model_monitoring/stores/models/sqlite.py → runtimes/nuclio/__init__.py} +7 -9
  241. mlrun/runtimes/nuclio/api_gateway.py +769 -0
  242. mlrun/runtimes/nuclio/application/__init__.py +15 -0
  243. mlrun/runtimes/nuclio/application/application.py +758 -0
  244. mlrun/runtimes/nuclio/application/reverse_proxy.go +95 -0
  245. mlrun/runtimes/{function.py → nuclio/function.py} +200 -83
  246. mlrun/runtimes/{nuclio.py → nuclio/nuclio.py} +6 -6
  247. mlrun/runtimes/{serving.py → nuclio/serving.py} +65 -68
  248. mlrun/runtimes/pod.py +281 -101
  249. mlrun/runtimes/remotesparkjob.py +12 -9
  250. mlrun/runtimes/sparkjob/spark3job.py +67 -51
  251. mlrun/runtimes/utils.py +41 -75
  252. mlrun/secrets.py +9 -5
  253. mlrun/serving/__init__.py +8 -1
  254. mlrun/serving/remote.py +2 -7
  255. mlrun/serving/routers.py +85 -69
  256. mlrun/serving/server.py +69 -44
  257. mlrun/serving/states.py +209 -36
  258. mlrun/serving/utils.py +22 -14
  259. mlrun/serving/v1_serving.py +6 -7
  260. mlrun/serving/v2_serving.py +129 -54
  261. mlrun/track/tracker.py +2 -1
  262. mlrun/track/tracker_manager.py +3 -3
  263. mlrun/track/trackers/mlflow_tracker.py +6 -2
  264. mlrun/utils/async_http.py +6 -8
  265. mlrun/utils/azure_vault.py +1 -1
  266. mlrun/utils/clones.py +1 -2
  267. mlrun/utils/condition_evaluator.py +3 -3
  268. mlrun/utils/db.py +21 -3
  269. mlrun/utils/helpers.py +405 -225
  270. mlrun/utils/http.py +3 -6
  271. mlrun/utils/logger.py +112 -16
  272. mlrun/utils/notifications/notification/__init__.py +17 -13
  273. mlrun/utils/notifications/notification/base.py +50 -2
  274. mlrun/utils/notifications/notification/console.py +2 -0
  275. mlrun/utils/notifications/notification/git.py +24 -1
  276. mlrun/utils/notifications/notification/ipython.py +3 -1
  277. mlrun/utils/notifications/notification/slack.py +96 -21
  278. mlrun/utils/notifications/notification/webhook.py +59 -2
  279. mlrun/utils/notifications/notification_pusher.py +149 -30
  280. mlrun/utils/regex.py +9 -0
  281. mlrun/utils/retryer.py +208 -0
  282. mlrun/utils/singleton.py +1 -1
  283. mlrun/utils/v3io_clients.py +4 -6
  284. mlrun/utils/version/version.json +2 -2
  285. mlrun/utils/version/version.py +2 -6
  286. mlrun-1.7.0.dist-info/METADATA +378 -0
  287. mlrun-1.7.0.dist-info/RECORD +351 -0
  288. {mlrun-1.6.4rc7.dist-info → mlrun-1.7.0.dist-info}/WHEEL +1 -1
  289. mlrun/feature_store/retrieval/conversion.py +0 -273
  290. mlrun/kfpops.py +0 -868
  291. mlrun/model_monitoring/application.py +0 -310
  292. mlrun/model_monitoring/batch.py +0 -1095
  293. mlrun/model_monitoring/prometheus.py +0 -219
  294. mlrun/model_monitoring/stores/__init__.py +0 -111
  295. mlrun/model_monitoring/stores/kv_model_endpoint_store.py +0 -576
  296. mlrun/model_monitoring/stores/model_endpoint_store.py +0 -147
  297. mlrun/model_monitoring/stores/models/__init__.py +0 -27
  298. mlrun/model_monitoring/stores/models/base.py +0 -84
  299. mlrun/model_monitoring/stores/sql_model_endpoint_store.py +0 -384
  300. mlrun/platforms/other.py +0 -306
  301. mlrun-1.6.4rc7.dist-info/METADATA +0 -272
  302. mlrun-1.6.4rc7.dist-info/RECORD +0 -314
  303. {mlrun-1.6.4rc7.dist-info → mlrun-1.7.0.dist-info}/LICENSE +0 -0
  304. {mlrun-1.6.4rc7.dist-info → mlrun-1.7.0.dist-info}/entry_points.txt +0 -0
  305. {mlrun-1.6.4rc7.dist-info → mlrun-1.7.0.dist-info}/top_level.txt +0 -0
mlrun/projects/project.py CHANGED
@@ -11,12 +11,14 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
+
14
15
  import datetime
15
16
  import getpass
16
17
  import glob
17
18
  import http
18
19
  import importlib.util as imputil
19
20
  import json
21
+ import os
20
22
  import pathlib
21
23
  import shutil
22
24
  import tempfile
@@ -24,49 +26,48 @@ import typing
24
26
  import uuid
25
27
  import warnings
26
28
  import zipfile
29
+ from copy import deepcopy
27
30
  from os import environ, makedirs, path
28
- from typing import Callable, Dict, List, Optional, Union
31
+ from typing import Callable, Optional, Union
29
32
 
30
33
  import dotenv
31
34
  import git
32
35
  import git.exc
33
- import kfp
34
- import nuclio
36
+ import mlrun_pipelines.common.models
37
+ import mlrun_pipelines.mounts
38
+ import nuclio.utils
35
39
  import requests
36
40
  import yaml
41
+ from mlrun_pipelines.models import PipelineNodeWrapper
37
42
 
38
43
  import mlrun.common.helpers
39
- import mlrun.common.schemas.model_monitoring
44
+ import mlrun.common.runtimes.constants
45
+ import mlrun.common.schemas.artifact
40
46
  import mlrun.common.schemas.model_monitoring.constants as mm_constants
41
47
  import mlrun.db
42
48
  import mlrun.errors
43
49
  import mlrun.k8s_utils
50
+ import mlrun.model_monitoring.applications as mm_app
44
51
  import mlrun.runtimes
52
+ import mlrun.runtimes.nuclio.api_gateway
45
53
  import mlrun.runtimes.pod
46
54
  import mlrun.runtimes.utils
55
+ import mlrun.serving
56
+ import mlrun.utils
47
57
  import mlrun.utils.regex
58
+ from mlrun.alerts.alert import AlertConfig
59
+ from mlrun.common.schemas.alert import AlertTemplate
48
60
  from mlrun.datastore.datastore_profile import DatastoreProfile, DatastoreProfile2Json
61
+ from mlrun.runtimes.nuclio.function import RemoteRuntime
49
62
 
50
63
  from ..artifacts import Artifact, ArtifactProducer, DatasetArtifact, ModelArtifact
51
64
  from ..artifacts.manager import ArtifactManager, dict_to_artifact, extend_artifact_path
52
65
  from ..datastore import store_manager
53
66
  from ..features import Feature
54
67
  from ..model import EntrypointParam, ImageBuilder, ModelObj
55
- from ..model_monitoring.application import (
56
- ModelMonitoringApplicationBase,
57
- PushToMonitoringWriter,
58
- )
59
68
  from ..run import code_to_function, get_object, import_function, new_function
60
- from ..runtimes.function import RemoteRuntime
61
69
  from ..secrets import SecretsStore
62
- from ..utils import (
63
- is_ipython,
64
- is_legacy_artifact,
65
- is_relative_path,
66
- is_yaml_path,
67
- logger,
68
- update_in,
69
- )
70
+ from ..utils import is_jupyter, is_relative_path, is_yaml_path, logger, update_in
70
71
  from ..utils.clones import (
71
72
  add_credentials_git_remote_url,
72
73
  clone_git,
@@ -74,7 +75,10 @@ from ..utils.clones import (
74
75
  clone_zip,
75
76
  get_repo_url,
76
77
  )
77
- from ..utils.helpers import ensure_git_branch, resolve_git_reference_from_source
78
+ from ..utils.helpers import (
79
+ ensure_git_branch,
80
+ resolve_git_reference_from_source,
81
+ )
78
82
  from ..utils.notifications import CustomNotificationPusher, NotificationTypes
79
83
  from .operations import (
80
84
  BuildStatus,
@@ -128,6 +132,7 @@ def new_project(
128
132
  save: bool = True,
129
133
  overwrite: bool = False,
130
134
  parameters: dict = None,
135
+ default_function_node_selector: dict = None,
131
136
  ) -> "MlrunProject":
132
137
  """Create a new MLRun project, optionally load it from a yaml/zip/git template
133
138
 
@@ -138,11 +143,15 @@ def new_project(
138
143
  example::
139
144
 
140
145
  # create a project with local and hub functions, a workflow, and an artifact
141
- project = mlrun.new_project("myproj", "./", init_git=True, description="my new project")
142
- project.set_function('prep_data.py', 'prep-data', image='mlrun/mlrun', handler='prep_data')
143
- project.set_function('hub://auto-trainer', 'train')
144
- project.set_artifact('data', Artifact(target_path=data_url))
145
- project.set_workflow('main', "./myflow.py")
146
+ project = mlrun.new_project(
147
+ "myproj", "./", init_git=True, description="my new project"
148
+ )
149
+ project.set_function(
150
+ "prep_data.py", "prep-data", image="mlrun/mlrun", handler="prep_data"
151
+ )
152
+ project.set_function("hub://auto-trainer", "train")
153
+ project.set_artifact("data", Artifact(target_path=data_url))
154
+ project.set_workflow("main", "./myflow.py")
146
155
  project.save()
147
156
 
148
157
  # run the "main" workflow (watch=True to wait for run completion)
@@ -152,19 +161,25 @@ def new_project(
152
161
 
153
162
  # create a new project from a zip template (can also use yaml/git templates)
154
163
  # initialize a local git, and register the git remote path
155
- project = mlrun.new_project("myproj", "./", init_git=True,
156
- remote="git://github.com/mlrun/project-demo.git",
157
- from_template="http://mysite/proj.zip")
164
+ project = mlrun.new_project(
165
+ "myproj",
166
+ "./",
167
+ init_git=True,
168
+ remote="git://github.com/mlrun/project-demo.git",
169
+ from_template="http://mysite/proj.zip",
170
+ )
158
171
  project.run("main", watch=True)
159
172
 
160
173
 
161
174
  example using project_setup.py to init the project objects::
162
175
 
163
176
  def setup(project):
164
- project.set_function('prep_data.py', 'prep-data', image='mlrun/mlrun', handler='prep_data')
165
- project.set_function('hub://auto-trainer', 'train')
166
- project.set_artifact('data', Artifact(target_path=data_url))
167
- project.set_workflow('main', "./myflow.py")
177
+ project.set_function(
178
+ "prep_data.py", "prep-data", image="mlrun/mlrun", handler="prep_data"
179
+ )
180
+ project.set_function("hub://auto-trainer", "train")
181
+ project.set_artifact("data", Artifact(target_path=data_url))
182
+ project.set_workflow("main", "./myflow.py")
168
183
  return project
169
184
 
170
185
 
@@ -181,6 +196,7 @@ def new_project(
181
196
  :param overwrite: overwrite project using 'cascade' deletion strategy (deletes project resources)
182
197
  if project with name exists
183
198
  :param parameters: key/value pairs to add to the project.spec.params
199
+ :param default_function_node_selector: defines the default node selector for scheduling functions within the project
184
200
 
185
201
  :returns: project object
186
202
  """
@@ -193,14 +209,16 @@ def new_project(
193
209
  "Unsupported option, cannot use subpath argument with project templates"
194
210
  )
195
211
  if from_template.endswith(".yaml"):
196
- project = _load_project_file(from_template, name, secrets)
212
+ project = _load_project_file(
213
+ from_template, name, secrets, allow_cross_project=True
214
+ )
197
215
  elif from_template.startswith("git://"):
198
216
  clone_git(from_template, context, secrets, clone=True)
199
217
  shutil.rmtree(path.join(context, ".git"))
200
- project = _load_project_dir(context, name)
218
+ project = _load_project_dir(context, name, allow_cross_project=True)
201
219
  elif from_template.endswith(".zip"):
202
220
  clone_zip(from_template, context, secrets)
203
- project = _load_project_dir(context, name)
221
+ project = _load_project_dir(context, name, allow_cross_project=True)
204
222
  else:
205
223
  raise ValueError("template must be a path to .yaml or .zip file")
206
224
  project.metadata.name = name
@@ -227,6 +245,10 @@ def new_project(
227
245
  project.spec.origin_url = url
228
246
  if description:
229
247
  project.spec.description = description
248
+
249
+ if default_function_node_selector:
250
+ project.spec.default_function_node_selector = default_function_node_selector
251
+
230
252
  if parameters:
231
253
  # Enable setting project parameters at load time, can be used to customize the project_setup
232
254
  for key, val in parameters.items():
@@ -277,6 +299,7 @@ def load_project(
277
299
  save: bool = True,
278
300
  sync_functions: bool = False,
279
301
  parameters: dict = None,
302
+ allow_cross_project: bool = None,
280
303
  ) -> "MlrunProject":
281
304
  """Load an MLRun project from git or tar or dir
282
305
 
@@ -290,7 +313,7 @@ def load_project(
290
313
  # When using git as the url source the context directory must be an empty or
291
314
  # non-existent folder as the git repo will be cloned there
292
315
  project = load_project("./demo_proj", "git://github.com/mlrun/project-demo.git")
293
- project.run("main", arguments={'data': data_url})
316
+ project.run("main", arguments={"data": data_url})
294
317
 
295
318
 
296
319
  project_setup.py example::
@@ -323,6 +346,8 @@ def load_project(
323
346
  :param save: whether to save the created project and artifact in the DB
324
347
  :param sync_functions: sync the project's functions into the project object (will be saved to the DB if save=True)
325
348
  :param parameters: key/value pairs to add to the project.spec.params
349
+ :param allow_cross_project: if True, override the loaded project name. This flag ensures awareness of
350
+ loading an existing project yaml as a baseline for a new project with a different name
326
351
 
327
352
  :returns: project object
328
353
  """
@@ -338,7 +363,7 @@ def load_project(
338
363
  if url:
339
364
  url = str(url) # to support path objects
340
365
  if is_yaml_path(url):
341
- project = _load_project_file(url, name, secrets)
366
+ project = _load_project_file(url, name, secrets, allow_cross_project)
342
367
  project.spec.context = context
343
368
  elif url.startswith("git://"):
344
369
  url, repo = clone_git(url, context, secrets, clone)
@@ -365,7 +390,7 @@ def load_project(
365
390
  repo, url = init_repo(context, url, init_git)
366
391
 
367
392
  if not project:
368
- project = _load_project_dir(context, name, subpath)
393
+ project = _load_project_dir(context, name, subpath, allow_cross_project)
369
394
 
370
395
  if not project.metadata.name:
371
396
  raise ValueError("Project name must be specified")
@@ -419,6 +444,7 @@ def get_or_create_project(
419
444
  from_template: str = None,
420
445
  save: bool = True,
421
446
  parameters: dict = None,
447
+ allow_cross_project: bool = None,
422
448
  ) -> "MlrunProject":
423
449
  """Load a project from MLRun DB, or create/import if it does not exist
424
450
 
@@ -429,9 +455,11 @@ def get_or_create_project(
429
455
  Usage example::
430
456
 
431
457
  # load project from the DB (if exist) or the source repo
432
- project = get_or_create_project("myproj", "./", "git://github.com/mlrun/demo-xgb-project.git")
458
+ project = get_or_create_project(
459
+ "myproj", "./", "git://github.com/mlrun/demo-xgb-project.git"
460
+ )
433
461
  project.pull("development") # pull the latest code from git
434
- project.run("main", arguments={'data': data_url}) # run the workflow "main"
462
+ project.run("main", arguments={"data": data_url}) # run the workflow "main"
435
463
 
436
464
 
437
465
  project_setup.py example::
@@ -461,12 +489,12 @@ def get_or_create_project(
461
489
  :param from_template: path to project YAML file that will be used as from_template (for new projects)
462
490
  :param save: whether to save the created project in the DB
463
491
  :param parameters: key/value pairs to add to the project.spec.params
492
+ :param allow_cross_project: if True, override the loaded project name. This flag ensures awareness of
493
+ loading an existing project yaml as a baseline for a new project with a different name
464
494
 
465
495
  :returns: project object
466
496
  """
467
497
  context = context or "./"
468
- spec_path = path.join(context, subpath or "", "project.yaml")
469
- load_from_path = url or path.isfile(spec_path)
470
498
  try:
471
499
  # load project from the DB.
472
500
  # use `name` as `url` as we load the project from the DB
@@ -482,17 +510,26 @@ def get_or_create_project(
482
510
  # only loading project from db so no need to save it
483
511
  save=False,
484
512
  parameters=parameters,
513
+ allow_cross_project=allow_cross_project,
485
514
  )
486
- logger.info("Project loaded successfully", project_name=name)
515
+ logger.info("Project loaded successfully", project_name=project.name)
487
516
  return project
488
-
489
517
  except mlrun.errors.MLRunNotFoundError:
490
- logger.debug("Project not found in db", project_name=name)
518
+ logger.debug(
519
+ "Project not found in db", project_name=name, user_project=user_project
520
+ )
491
521
 
522
+ spec_path = path.join(context, subpath or "", "project.yaml")
523
+ load_from_path = url or path.isfile(spec_path)
492
524
  # do not nest under "try" or else the exceptions raised below will be logged along with the "not found" message
493
525
  if load_from_path:
494
526
  # loads a project from archive or local project.yaml
495
- logger.info("Loading project from path", project_name=name, path=url or context)
527
+ logger.info(
528
+ "Loading project from path",
529
+ project_name=name,
530
+ user_project=user_project,
531
+ path=url or context,
532
+ )
496
533
  project = load_project(
497
534
  context,
498
535
  url,
@@ -504,11 +541,12 @@ def get_or_create_project(
504
541
  user_project=user_project,
505
542
  save=save,
506
543
  parameters=parameters,
544
+ allow_cross_project=allow_cross_project,
507
545
  )
508
546
 
509
547
  logger.info(
510
548
  "Project loaded successfully",
511
- project_name=name,
549
+ project_name=project.name,
512
550
  path=url or context,
513
551
  stored_in_db=save,
514
552
  )
@@ -526,7 +564,9 @@ def get_or_create_project(
526
564
  save=save,
527
565
  parameters=parameters,
528
566
  )
529
- logger.info("Project created successfully", project_name=name, stored_in_db=save)
567
+ logger.info(
568
+ "Project created successfully", project_name=project.name, stored_in_db=save
569
+ )
530
570
  return project
531
571
 
532
572
 
@@ -564,6 +604,10 @@ def _run_project_setup(
564
604
  if hasattr(mod, "setup"):
565
605
  try:
566
606
  project = getattr(mod, "setup")(project)
607
+ if not project or not isinstance(project, mlrun.projects.MlrunProject):
608
+ raise ValueError(
609
+ "MLRun project_setup:setup() must return a project object"
610
+ )
567
611
  except Exception as exc:
568
612
  logger.error(
569
613
  "Failed to run project_setup script",
@@ -574,30 +618,44 @@ def _run_project_setup(
574
618
  if save:
575
619
  project.save()
576
620
  else:
577
- logger.warn("skipping setup, setup() handler was not found in project_setup.py")
621
+ logger.warn(
622
+ f"skipping setup, setup() handler was not found in {path.basename(setup_file_path)}"
623
+ )
578
624
  return project
579
625
 
580
626
 
581
- def _load_project_dir(context, name="", subpath=""):
627
+ def _load_project_dir(context, name="", subpath="", allow_cross_project=None):
582
628
  subpath_str = subpath or ""
583
- fpath = path.join(context, subpath_str, "project.yaml")
629
+
630
+ # support both .yaml and .yml file extensions
631
+ project_file_path = path.join(context, subpath_str, "project.y*ml")
632
+ function_file_path = path.join(context, subpath_str, "function.y*ml")
584
633
  setup_file_path = path.join(context, subpath_str, "project_setup.py")
585
- if path.isfile(fpath):
586
- with open(fpath) as fp:
634
+
635
+ if project_files := glob.glob(project_file_path):
636
+ # if there are multiple project files, use the first one
637
+ project_file_path = project_files[0]
638
+ with open(project_file_path) as fp:
587
639
  data = fp.read()
588
640
  struct = yaml.load(data, Loader=yaml.FullLoader)
589
- project = _project_instance_from_struct(struct, name)
641
+ project = _project_instance_from_struct(struct, name, allow_cross_project)
590
642
  project.spec.context = context
591
-
592
- elif path.isfile(path.join(context, subpath_str, "function.yaml")):
593
- func = import_function(path.join(context, subpath_str, "function.yaml"))
643
+ elif function_files := glob.glob(function_file_path):
644
+ function_path = function_files[0]
645
+ func = import_function(function_path)
646
+ function_file_name = path.basename(path.normpath(function_path))
594
647
  project = MlrunProject.from_dict(
595
648
  {
596
649
  "metadata": {
597
650
  "name": func.metadata.project,
598
651
  },
599
652
  "spec": {
600
- "functions": [{"url": "function.yaml", "name": func.metadata.name}],
653
+ "functions": [
654
+ {
655
+ "url": function_file_name,
656
+ "name": func.metadata.name,
657
+ },
658
+ ],
601
659
  },
602
660
  }
603
661
  )
@@ -650,22 +708,45 @@ def _load_project_from_db(url, secrets, user_project=False):
650
708
 
651
709
  def _delete_project_from_db(project_name, secrets, deletion_strategy):
652
710
  db = mlrun.db.get_run_db(secrets=secrets)
653
- return db.delete_project(project_name, deletion_strategy=deletion_strategy)
711
+ db.delete_project(project_name, deletion_strategy=deletion_strategy)
654
712
 
655
713
 
656
- def _load_project_file(url, name="", secrets=None):
714
+ def _load_project_file(url, name="", secrets=None, allow_cross_project=None):
657
715
  try:
658
716
  obj = get_object(url, secrets)
659
717
  except FileNotFoundError as exc:
660
718
  raise FileNotFoundError(f"cant find project file at {url}") from exc
661
719
  struct = yaml.load(obj, Loader=yaml.FullLoader)
662
- return _project_instance_from_struct(struct, name)
663
-
720
+ return _project_instance_from_struct(struct, name, allow_cross_project)
721
+
722
+
723
+ def _project_instance_from_struct(struct, name, allow_cross_project):
724
+ name_from_struct = struct.get("metadata", {}).get("name", "")
725
+ if name and name_from_struct and name_from_struct != name:
726
+ error_message = (
727
+ f"Project name mismatch, {name_from_struct} != {name}, project is loaded from {name_from_struct} "
728
+ f"project yaml. To prevent/allow this, you can take one of the following actions:\n"
729
+ "1. Set the `allow_cross_project=True` when loading the project.\n"
730
+ f"2. Delete the existing project yaml, or ensure its name is equal to {name}.\n"
731
+ "3. Use different project context dir."
732
+ )
664
733
 
665
- def _project_instance_from_struct(struct, name):
666
- struct.setdefault("metadata", {})["name"] = name or struct.get("metadata", {}).get(
667
- "name", ""
668
- )
734
+ if allow_cross_project is None:
735
+ # TODO: Remove this warning in version 1.9.0 and also fix cli to support allow_cross_project
736
+ warnings.warn(
737
+ f"Project {name=} is different than specified on the context's project yaml. "
738
+ "This behavior is deprecated and will not be supported from version 1.9.0."
739
+ )
740
+ logger.warn(error_message)
741
+ elif allow_cross_project:
742
+ logger.debug(
743
+ "Project name is different than specified on the context's project yaml. Overriding.",
744
+ existing_name=name_from_struct,
745
+ overriding_name=name,
746
+ )
747
+ else:
748
+ raise ValueError(error_message)
749
+ struct.setdefault("metadata", {})["name"] = name or name_from_struct
669
750
  return MlrunProject.from_dict(struct)
670
751
 
671
752
 
@@ -740,14 +821,15 @@ class ProjectSpec(ModelObj):
740
821
  origin_url=None,
741
822
  goals=None,
742
823
  load_source_on_run=None,
743
- default_requirements: typing.Union[str, typing.List[str]] = None,
824
+ default_requirements: typing.Union[str, list[str]] = None,
744
825
  desired_state=mlrun.common.schemas.ProjectState.online.value,
745
826
  owner=None,
746
827
  disable_auto_mount=None,
747
828
  workdir=None,
748
829
  default_image=None,
749
830
  build=None,
750
- custom_packagers: typing.List[typing.Tuple[str, bool]] = None,
831
+ custom_packagers: list[tuple[str, bool]] = None,
832
+ default_function_node_selector=None,
751
833
  ):
752
834
  self.repo = None
753
835
 
@@ -787,6 +869,7 @@ class ProjectSpec(ModelObj):
787
869
  # in a tuple where the first index is the packager module's path (str) and the second is a flag (bool) for
788
870
  # whether it is mandatory for a run (raise exception on collection error) or not.
789
871
  self.custom_packagers = custom_packagers or []
872
+ self._default_function_node_selector = default_function_node_selector or None
790
873
 
791
874
  @property
792
875
  def source(self) -> str:
@@ -864,14 +947,14 @@ class ProjectSpec(ModelObj):
864
947
  del self._function_definitions[name]
865
948
 
866
949
  @property
867
- def workflows(self) -> typing.List[dict]:
950
+ def workflows(self) -> list[dict]:
868
951
  """
869
952
  :returns: list of workflows specs dicts used in this project
870
953
  """
871
954
  return [workflow.to_dict() for workflow in self._workflows.values()]
872
955
 
873
956
  @workflows.setter
874
- def workflows(self, workflows: typing.List[typing.Union[dict, WorkflowSpec]]):
957
+ def workflows(self, workflows: list[typing.Union[dict, WorkflowSpec]]):
875
958
  if not workflows:
876
959
  workflows = []
877
960
  if not isinstance(workflows, list):
@@ -922,19 +1005,29 @@ class ProjectSpec(ModelObj):
922
1005
 
923
1006
  artifacts_dict = {}
924
1007
  for artifact in artifacts:
925
- if not isinstance(artifact, dict) and not hasattr(artifact, "to_dict"):
1008
+ invalid_object_type = not isinstance(artifact, dict) and not hasattr(
1009
+ artifact, "to_dict"
1010
+ )
1011
+ is_artifact_model = not isinstance(artifact, dict) and hasattr(
1012
+ artifact, "to_dict"
1013
+ )
1014
+
1015
+ if invalid_object_type:
926
1016
  raise ValueError("artifacts must be a dict or class")
927
- if isinstance(artifact, dict):
928
- # Support legacy artifacts
929
- if is_legacy_artifact(artifact) or _is_imported_artifact(artifact):
930
- key = artifact.get("key")
931
- else:
932
- key = artifact.get("metadata").get("key", "")
933
- if not key:
934
- raise ValueError('artifacts "key" must be specified')
935
- else:
1017
+ elif is_artifact_model:
936
1018
  key = artifact.key
937
1019
  artifact = artifact.to_dict()
1020
+ else: # artifact is a dict
1021
+ # imported/legacy artifacts don't have metadata,spec,status fields
1022
+ key_field = (
1023
+ "key"
1024
+ if _is_imported_artifact(artifact)
1025
+ or mlrun.utils.is_legacy_artifact(artifact)
1026
+ else "metadata.key"
1027
+ )
1028
+ key = mlrun.utils.get_in(artifact, key_field, "")
1029
+ if not key:
1030
+ raise ValueError(f'artifacts "{key_field}" must be specified')
938
1031
 
939
1032
  artifacts_dict[key] = artifact
940
1033
 
@@ -951,6 +1044,14 @@ class ProjectSpec(ModelObj):
951
1044
  if key in self._artifacts:
952
1045
  del self._artifacts[key]
953
1046
 
1047
+ @property
1048
+ def default_function_node_selector(self):
1049
+ return self._default_function_node_selector
1050
+
1051
+ @default_function_node_selector.setter
1052
+ def default_function_node_selector(self, node_selector: dict[str, str]):
1053
+ self._default_function_node_selector = deepcopy(node_selector)
1054
+
954
1055
  @property
955
1056
  def build(self) -> ImageBuilder:
956
1057
  return self._build
@@ -987,7 +1088,7 @@ class ProjectSpec(ModelObj):
987
1088
  :raise MLRunInvalidArgumentError: In case the packager was not in the list.
988
1089
  """
989
1090
  # Look for the packager tuple in the list to remove it:
990
- packager_tuple: typing.Tuple[str, bool] = None
1091
+ packager_tuple: tuple[str, bool] = None
991
1092
  for custom_packager in self.custom_packagers:
992
1093
  if custom_packager[0] == packager:
993
1094
  packager_tuple = custom_packager
@@ -1038,8 +1139,8 @@ class MlrunProject(ModelObj):
1038
1139
 
1039
1140
  def __init__(
1040
1141
  self,
1041
- metadata: Optional[Union[ProjectMetadata, Dict]] = None,
1042
- spec: Optional[Union[ProjectSpec, Dict]] = None,
1142
+ metadata: Optional[Union[ProjectMetadata, dict]] = None,
1143
+ spec: Optional[Union[ProjectSpec, dict]] = None,
1043
1144
  ):
1044
1145
  self.metadata: ProjectMetadata = metadata
1045
1146
  self.spec: ProjectSpec = spec
@@ -1208,6 +1309,14 @@ class MlrunProject(ModelObj):
1208
1309
  def description(self, description):
1209
1310
  self.spec.description = description
1210
1311
 
1312
+ @property
1313
+ def default_function_node_selector(self) -> dict:
1314
+ return self.spec.default_function_node_selector
1315
+
1316
+ @default_function_node_selector.setter
1317
+ def default_function_node_selector(self, default_function_node_selector):
1318
+ self.spec.default_function_node_selector = default_function_node_selector
1319
+
1211
1320
  @property
1212
1321
  def default_image(self) -> str:
1213
1322
  return self.spec.default_image
@@ -1287,7 +1396,7 @@ class MlrunProject(ModelObj):
1287
1396
  and not workflow_path.startswith(self.context)
1288
1397
  ):
1289
1398
  workflow_path = path.join(self.context, workflow_path)
1290
- with open(workflow_path, "r") as fp:
1399
+ with open(workflow_path) as fp:
1291
1400
  txt = fp.read()
1292
1401
  workflow = {"name": name, "code": txt}
1293
1402
  else:
@@ -1322,13 +1431,15 @@ class MlrunProject(ModelObj):
1322
1431
  example::
1323
1432
 
1324
1433
  # register a simple file artifact
1325
- project.set_artifact('data', target_path=data_url)
1434
+ project.set_artifact("data", target_path=data_url)
1326
1435
  # register a model artifact
1327
- project.set_artifact('model', ModelArtifact(model_file="model.pkl"), target_path=model_dir_url)
1436
+ project.set_artifact(
1437
+ "model", ModelArtifact(model_file="model.pkl"), target_path=model_dir_url
1438
+ )
1328
1439
 
1329
1440
  # register a path to artifact package (will be imported on project load)
1330
1441
  # to generate such package use `artifact.export(target_path)`
1331
- project.set_artifact('model', 'https://mystuff.com/models/mymodel.zip')
1442
+ project.set_artifact("model", "https://mystuff.com/models/mymodel.zip")
1332
1443
 
1333
1444
  :param key: artifact key/name
1334
1445
  :param artifact: mlrun Artifact object/dict (or its subclasses) or path to artifact
@@ -1363,14 +1474,7 @@ class MlrunProject(ModelObj):
1363
1474
  artifact_path = mlrun.utils.helpers.template_artifact_path(
1364
1475
  self.spec.artifact_path or mlrun.mlconf.artifact_path, self.metadata.name
1365
1476
  )
1366
- # TODO: To correctly maintain the list of artifacts from an exported project,
1367
- # we need to maintain the different trees that generated them
1368
- producer = ArtifactProducer(
1369
- "project",
1370
- self.metadata.name,
1371
- self.metadata.name,
1372
- tag=self._get_hexsha() or str(uuid.uuid4()),
1373
- )
1477
+ project_tag = self._get_project_tag()
1374
1478
  for artifact_dict in self.spec.artifacts:
1375
1479
  if _is_imported_artifact(artifact_dict):
1376
1480
  import_from = artifact_dict["import_from"]
@@ -1390,8 +1494,23 @@ class MlrunProject(ModelObj):
1390
1494
  artifact.src_path = path.join(
1391
1495
  self.spec.get_code_path(), artifact.src_path
1392
1496
  )
1497
+ producer, is_retained_producer = self._resolve_artifact_producer(
1498
+ artifact, project_tag
1499
+ )
1500
+ # log the artifact only if it doesn't already exist
1501
+ if (
1502
+ producer.name != self.metadata.name
1503
+ and self._resolve_existing_artifact(
1504
+ artifact,
1505
+ )
1506
+ ):
1507
+ continue
1393
1508
  artifact_manager.log_artifact(
1394
- producer, artifact, artifact_path=artifact_path
1509
+ producer,
1510
+ artifact,
1511
+ artifact_path=artifact_path,
1512
+ project=self.metadata.name,
1513
+ is_retained_producer=is_retained_producer,
1395
1514
  )
1396
1515
 
1397
1516
  def _get_artifact_manager(self):
@@ -1414,7 +1533,7 @@ class MlrunProject(ModelObj):
1414
1533
  self,
1415
1534
  url: str,
1416
1535
  check_path_in_context: bool = False,
1417
- ) -> typing.Tuple[str, bool]:
1536
+ ) -> tuple[str, bool]:
1418
1537
  """
1419
1538
  Get the absolute path of the artifact or function file
1420
1539
  :param url: remote url, absolute path or relative path
@@ -1433,7 +1552,7 @@ class MlrunProject(ModelObj):
1433
1552
  url = path.normpath(path.join(self.spec.get_code_path(), url))
1434
1553
 
1435
1554
  if (not in_context or check_path_in_context) and not path.isfile(url):
1436
- raise mlrun.errors.MLRunNotFoundError(f"{url} not found")
1555
+ raise FileNotFoundError(f"{url} not found")
1437
1556
 
1438
1557
  return url, in_context
1439
1558
 
@@ -1441,15 +1560,15 @@ class MlrunProject(ModelObj):
1441
1560
  self,
1442
1561
  item,
1443
1562
  body=None,
1444
- tag="",
1445
- local_path="",
1446
- artifact_path=None,
1447
- format=None,
1448
- upload=None,
1449
- labels=None,
1450
- target_path=None,
1563
+ tag: str = "",
1564
+ local_path: str = "",
1565
+ artifact_path: Optional[str] = None,
1566
+ format: Optional[str] = None,
1567
+ upload: Optional[bool] = None,
1568
+ labels: Optional[dict[str, str]] = None,
1569
+ target_path: Optional[str] = None,
1451
1570
  **kwargs,
1452
- ):
1571
+ ) -> Artifact:
1453
1572
  """Log an output artifact and optionally upload it to datastore
1454
1573
 
1455
1574
  If the artifact already exists with the same key and tag, it will be overwritten.
@@ -1474,7 +1593,9 @@ class MlrunProject(ModelObj):
1474
1593
  :param format: artifact file format: csv, png, ..
1475
1594
  :param tag: version tag
1476
1595
  :param target_path: absolute target path (instead of using artifact_path + local_path)
1477
- :param upload: upload to datastore (default is True)
1596
+ :param upload: Whether to upload the artifact to the datastore. If not provided, and the `local_path`
1597
+ is not a directory, upload occurs by default. Directories are uploaded only when this
1598
+ flag is explicitly set to `True`.
1478
1599
  :param labels: a set of key/value labels to tag the artifact with
1479
1600
 
1480
1601
  :returns: artifact object
@@ -1486,12 +1607,20 @@ class MlrunProject(ModelObj):
1486
1607
  artifact_path = mlrun.utils.helpers.template_artifact_path(
1487
1608
  artifact_path, self.metadata.name
1488
1609
  )
1489
- producer = ArtifactProducer(
1490
- "project",
1491
- self.metadata.name,
1492
- self.metadata.name,
1493
- tag=self._get_hexsha() or str(uuid.uuid4()),
1494
- )
1610
+ producer, is_retained_producer = self._resolve_artifact_producer(item)
1611
+ if producer.name != self.metadata.name:
1612
+ # the artifact producer is retained, log it only if it doesn't already exist
1613
+ if existing_artifact := self._resolve_existing_artifact(
1614
+ item,
1615
+ tag,
1616
+ ):
1617
+ artifact_key = item if isinstance(item, str) else item.key
1618
+ logger.info(
1619
+ "Artifact already exists, skipping logging",
1620
+ key=artifact_key,
1621
+ tag=tag,
1622
+ )
1623
+ return existing_artifact
1495
1624
  item = am.log_artifact(
1496
1625
  producer,
1497
1626
  item,
@@ -1503,10 +1632,29 @@ class MlrunProject(ModelObj):
1503
1632
  upload=upload,
1504
1633
  labels=labels,
1505
1634
  target_path=target_path,
1635
+ project=self.metadata.name,
1636
+ is_retained_producer=is_retained_producer,
1506
1637
  **kwargs,
1507
1638
  )
1508
1639
  return item
1509
1640
 
1641
+ def delete_artifact(
1642
+ self,
1643
+ item: Artifact,
1644
+ deletion_strategy: mlrun.common.schemas.artifact.ArtifactsDeletionStrategies = (
1645
+ mlrun.common.schemas.artifact.ArtifactsDeletionStrategies.metadata_only
1646
+ ),
1647
+ secrets: dict = None,
1648
+ ):
1649
+ """Delete an artifact object in the DB and optionally delete the artifact data
1650
+
1651
+ :param item: Artifact object (can be any type, such as dataset, model, feature store).
1652
+ :param deletion_strategy: The artifact deletion strategy types.
1653
+ :param secrets: Credentials needed to access the artifact data.
1654
+ """
1655
+ am = self._get_artifact_manager()
1656
+ am.delete_artifact(item, deletion_strategy, secrets)
1657
+
1510
1658
  def log_dataset(
1511
1659
  self,
1512
1660
  key,
@@ -1521,7 +1669,7 @@ class MlrunProject(ModelObj):
1521
1669
  stats=None,
1522
1670
  target_path="",
1523
1671
  extra_data=None,
1524
- label_column: str = None,
1672
+ label_column: Optional[str] = None,
1525
1673
  **kwargs,
1526
1674
  ) -> DatasetArtifact:
1527
1675
  """
@@ -1537,7 +1685,9 @@ class MlrunProject(ModelObj):
1537
1685
  "age": [42, 52, 36, 24, 73],
1538
1686
  "testScore": [25, 94, 57, 62, 70],
1539
1687
  }
1540
- df = pd.DataFrame(raw_data, columns=["first_name", "last_name", "age", "testScore"])
1688
+ df = pd.DataFrame(
1689
+ raw_data, columns=["first_name", "last_name", "age", "testScore"]
1690
+ )
1541
1691
  project.log_dataset("mydf", df=df, stats=True)
1542
1692
 
1543
1693
  :param key: artifact key
@@ -1596,28 +1746,31 @@ class MlrunProject(ModelObj):
1596
1746
  artifact_path=None,
1597
1747
  upload=None,
1598
1748
  labels=None,
1599
- inputs: typing.List[Feature] = None,
1600
- outputs: typing.List[Feature] = None,
1601
- feature_vector: str = None,
1602
- feature_weights: list = None,
1749
+ inputs: Optional[list[Feature]] = None,
1750
+ outputs: Optional[list[Feature]] = None,
1751
+ feature_vector: Optional[str] = None,
1752
+ feature_weights: Optional[list] = None,
1603
1753
  training_set=None,
1604
1754
  label_column=None,
1605
1755
  extra_data=None,
1606
1756
  **kwargs,
1607
- ):
1757
+ ) -> ModelArtifact:
1608
1758
  """Log a model artifact and optionally upload it to datastore
1609
1759
 
1610
1760
  If the model already exists with the same key and tag, it will be overwritten.
1611
1761
 
1612
1762
  example::
1613
1763
 
1614
- project.log_model("model", body=dumps(model),
1615
- model_file="model.pkl",
1616
- metrics=context.results,
1617
- training_set=training_df,
1618
- label_column='label',
1619
- feature_vector=feature_vector_uri,
1620
- labels={"app": "fraud"})
1764
+ project.log_model(
1765
+ "model",
1766
+ body=dumps(model),
1767
+ model_file="model.pkl",
1768
+ metrics=context.results,
1769
+ training_set=training_df,
1770
+ label_column="label",
1771
+ feature_vector=feature_vector_uri,
1772
+ labels={"app": "fraud"},
1773
+ )
1621
1774
 
1622
1775
  :param key: artifact key or artifact class ()
1623
1776
  :param body: will use the body as the artifact content
@@ -1721,20 +1874,22 @@ class MlrunProject(ModelObj):
1721
1874
  with tempfile.TemporaryDirectory() as temp_dir:
1722
1875
  with zipfile.ZipFile(item_file, "r") as zf:
1723
1876
  zf.extractall(temp_dir)
1724
- with open(f"{temp_dir}/_spec.yaml", "r") as fp:
1877
+ with open(f"{temp_dir}/_spec.yaml") as fp:
1725
1878
  data = fp.read()
1726
1879
  spec = yaml.load(data, Loader=yaml.FullLoader)
1727
1880
  artifact = get_artifact(spec)
1728
1881
  with open(f"{temp_dir}/_body", "rb") as fp:
1729
1882
  artifact.spec._body = fp.read()
1730
- artifact.target_path = ""
1731
1883
 
1732
1884
  # if the dataitem is not a file, it means we downloaded it from a remote source to a temp file,
1733
1885
  # so we need to remove it after we're done with it
1734
1886
  dataitem.remove_local()
1735
1887
 
1736
1888
  return self.log_artifact(
1737
- artifact, local_path=temp_dir, artifact_path=artifact_path
1889
+ artifact,
1890
+ local_path=temp_dir,
1891
+ artifact_path=artifact_path,
1892
+ upload=True,
1738
1893
  )
1739
1894
 
1740
1895
  else:
@@ -1752,10 +1907,18 @@ class MlrunProject(ModelObj):
1752
1907
  """
1753
1908
  context = context or self.spec.context
1754
1909
  if context:
1755
- project = _load_project_dir(context, self.metadata.name, self.spec.subpath)
1910
+ project = _load_project_dir(
1911
+ context,
1912
+ self.metadata.name,
1913
+ self.spec.subpath,
1914
+ allow_cross_project=False,
1915
+ )
1756
1916
  else:
1757
1917
  project = _load_project_file(
1758
- self.spec.origin_url, self.metadata.name, self._secrets
1918
+ self.spec.origin_url,
1919
+ self.metadata.name,
1920
+ self._secrets,
1921
+ allow_cross_project=None,
1759
1922
  )
1760
1923
  project.spec.source = self.spec.source
1761
1924
  project.spec.repo = self.spec.repo
@@ -1784,22 +1947,29 @@ class MlrunProject(ModelObj):
1784
1947
  def set_model_monitoring_function(
1785
1948
  self,
1786
1949
  func: typing.Union[str, mlrun.runtimes.BaseRuntime, None] = None,
1787
- application_class: typing.Union[str, ModelMonitoringApplicationBase] = None,
1950
+ application_class: typing.Union[
1951
+ str,
1952
+ mm_app.ModelMonitoringApplicationBase,
1953
+ ] = None,
1788
1954
  name: str = None,
1789
1955
  image: str = None,
1790
1956
  handler=None,
1791
1957
  with_repo: bool = None,
1792
1958
  tag: str = None,
1793
- requirements: typing.Union[str, typing.List[str]] = None,
1959
+ requirements: typing.Union[str, list[str]] = None,
1794
1960
  requirements_file: str = "",
1795
1961
  **application_kwargs,
1796
1962
  ) -> mlrun.runtimes.BaseRuntime:
1797
1963
  """
1798
1964
  Update or add a monitoring function to the project.
1965
+ Note: to deploy the function after linking it to the project,
1966
+ call `fn.deploy()` where `fn` is the object returned by this method.
1799
1967
 
1800
1968
  examples::
1801
- project.set_model_monitoring_function(application_class_name="MyApp",
1802
- image="mlrun/mlrun", name="myApp")
1969
+
1970
+ project.set_model_monitoring_function(
1971
+ name="myApp", application_class="MyApp", image="mlrun/mlrun"
1972
+ )
1803
1973
 
1804
1974
  :param func: Function object or spec/code url, None refers to current Notebook
1805
1975
  :param name: Name of the function (under the project), can be specified with a tag to support
@@ -1814,7 +1984,7 @@ class MlrunProject(ModelObj):
1814
1984
  will be enriched with the tag value. (i.e. 'function-name:tag')
1815
1985
  :param requirements: A list of python packages
1816
1986
  :param requirements_file: Path to a python requirements file
1817
- :param application_class: Name or an Instance of a class that implementing the monitoring application.
1987
+ :param application_class: Name or an Instance of a class that implements the monitoring application.
1818
1988
  :param application_kwargs: Additional keyword arguments to be passed to the
1819
1989
  monitoring application's constructor.
1820
1990
  """
@@ -1836,16 +2006,6 @@ class MlrunProject(ModelObj):
1836
2006
  requirements_file,
1837
2007
  **application_kwargs,
1838
2008
  )
1839
- models_names = "all"
1840
- function_object.set_label(
1841
- mm_constants.ModelMonitoringAppLabel.KEY,
1842
- mm_constants.ModelMonitoringAppLabel.VAL,
1843
- )
1844
- function_object.set_label("models", models_names)
1845
-
1846
- if not mlrun.mlconf.is_ce_mode():
1847
- function_object.apply(mlrun.mount_v3io())
1848
-
1849
2009
  # save to project spec
1850
2010
  self.spec.set_function(resolved_function_name, function_object, func)
1851
2011
 
@@ -1854,13 +2014,16 @@ class MlrunProject(ModelObj):
1854
2014
  def create_model_monitoring_function(
1855
2015
  self,
1856
2016
  func: str = None,
1857
- application_class: typing.Union[str, ModelMonitoringApplicationBase] = None,
2017
+ application_class: typing.Union[
2018
+ str,
2019
+ mm_app.ModelMonitoringApplicationBase,
2020
+ ] = None,
1858
2021
  name: str = None,
1859
2022
  image: str = None,
1860
2023
  handler: str = None,
1861
2024
  with_repo: bool = None,
1862
2025
  tag: str = None,
1863
- requirements: typing.Union[str, typing.List[str]] = None,
2026
+ requirements: typing.Union[str, list[str]] = None,
1864
2027
  requirements_file: str = "",
1865
2028
  **application_kwargs,
1866
2029
  ) -> mlrun.runtimes.BaseRuntime:
@@ -1868,8 +2031,10 @@ class MlrunProject(ModelObj):
1868
2031
  Create a monitoring function object without setting it to the project
1869
2032
 
1870
2033
  examples::
1871
- project.create_model_monitoring_function(application_class_name="MyApp",
1872
- image="mlrun/mlrun", name="myApp")
2034
+
2035
+ project.create_model_monitoring_function(
2036
+ application_class_name="MyApp", image="mlrun/mlrun", name="myApp"
2037
+ )
1873
2038
 
1874
2039
  :param func: Code url, None refers to current Notebook
1875
2040
  :param name: Name of the function, can be specified with a tag to support
@@ -1888,6 +2053,7 @@ class MlrunProject(ModelObj):
1888
2053
  :param application_kwargs: Additional keyword arguments to be passed to the
1889
2054
  monitoring application's constructor.
1890
2055
  """
2056
+
1891
2057
  _, function_object, _ = self._instantiate_model_monitoring_function(
1892
2058
  func,
1893
2059
  application_class,
@@ -1904,49 +2070,40 @@ class MlrunProject(ModelObj):
1904
2070
 
1905
2071
  def _instantiate_model_monitoring_function(
1906
2072
  self,
1907
- func: typing.Union[str, mlrun.runtimes.BaseRuntime] = None,
1908
- application_class: typing.Union[str, ModelMonitoringApplicationBase] = None,
1909
- name: str = None,
1910
- image: str = None,
1911
- handler: str = None,
1912
- with_repo: bool = None,
1913
- tag: str = None,
1914
- requirements: typing.Union[str, typing.List[str]] = None,
2073
+ func: typing.Union[str, mlrun.runtimes.BaseRuntime, None] = None,
2074
+ application_class: typing.Union[
2075
+ str,
2076
+ mm_app.ModelMonitoringApplicationBase,
2077
+ None,
2078
+ ] = None,
2079
+ name: typing.Optional[str] = None,
2080
+ image: typing.Optional[str] = None,
2081
+ handler: typing.Optional[str] = None,
2082
+ with_repo: typing.Optional[bool] = None,
2083
+ tag: typing.Optional[str] = None,
2084
+ requirements: typing.Union[str, list[str], None] = None,
1915
2085
  requirements_file: str = "",
1916
2086
  **application_kwargs,
1917
- ) -> typing.Tuple[str, mlrun.runtimes.BaseRuntime, dict]:
2087
+ ) -> tuple[str, mlrun.runtimes.BaseRuntime, dict]:
2088
+ import mlrun.model_monitoring.api
2089
+
1918
2090
  function_object: RemoteRuntime = None
1919
2091
  kind = None
1920
2092
  if (isinstance(func, str) or func is None) and application_class is not None:
1921
- kind = "serving"
1922
- if func is None:
1923
- func = ""
1924
- func = mlrun.code_to_function(
1925
- filename=func,
2093
+ kind = mlrun.run.RuntimeKinds.serving
2094
+ func = mlrun.model_monitoring.api._create_model_monitoring_function_base(
2095
+ project=self.name,
2096
+ func=func,
2097
+ application_class=application_class,
1926
2098
  name=name,
1927
- project=self.metadata.name,
1928
- tag=tag,
1929
- kind=kind,
1930
2099
  image=image,
2100
+ tag=tag,
1931
2101
  requirements=requirements,
1932
2102
  requirements_file=requirements_file,
2103
+ **application_kwargs,
1933
2104
  )
1934
- graph = func.set_topology("flow")
1935
- if isinstance(application_class, str):
1936
- first_step = graph.to(
1937
- class_name=application_class, **application_kwargs
1938
- )
1939
- else:
1940
- first_step = graph.to(class_name=application_class)
1941
- first_step.to(
1942
- class_name=PushToMonitoringWriter(
1943
- project=self.metadata.name,
1944
- writer_application_name=mm_constants.MonitoringFunctionNames.WRITER,
1945
- stream_uri=None,
1946
- ),
1947
- ).respond()
1948
2105
  elif isinstance(func, str) and isinstance(handler, str):
1949
- kind = "nuclio"
2106
+ kind = mlrun.run.RuntimeKinds.nuclio
1950
2107
 
1951
2108
  (
1952
2109
  resolved_function_name,
@@ -1964,88 +2121,265 @@ class MlrunProject(ModelObj):
1964
2121
  requirements,
1965
2122
  requirements_file,
1966
2123
  )
1967
- models_names = "all"
1968
2124
  function_object.set_label(
1969
2125
  mm_constants.ModelMonitoringAppLabel.KEY,
1970
2126
  mm_constants.ModelMonitoringAppLabel.VAL,
1971
2127
  )
1972
- function_object.set_label("models", models_names)
1973
2128
 
1974
2129
  if not mlrun.mlconf.is_ce_mode():
1975
2130
  function_object.apply(mlrun.mount_v3io())
1976
2131
 
1977
2132
  return resolved_function_name, function_object, func
1978
2133
 
2134
+ def _wait_for_functions_deployment(self, function_names: list[str]) -> None:
2135
+ """
2136
+ Wait for the deployment of functions on the backend.
2137
+
2138
+ :param function_names: A list of function names.
2139
+ """
2140
+ for fn_name in function_names:
2141
+ fn = typing.cast(RemoteRuntime, self.get_function(key=fn_name))
2142
+ fn._wait_for_function_deployment(db=fn._get_db())
2143
+
1979
2144
  def enable_model_monitoring(
1980
2145
  self,
1981
2146
  default_controller_image: str = "mlrun/mlrun",
1982
2147
  base_period: int = 10,
1983
- ) -> dict:
1984
- r"""
1985
- Submit model monitoring application controller job along with deploying the model monitoring writer function.
1986
- While the main goal of the controller job is to handle the monitoring processing and triggering applications,
1987
- the goal of the model monitoring writer function is to write all the monitoring application results to the
1988
- databases. Note that the default scheduling policy of the controller job is to run every 10 min.
1989
-
1990
- :param default_controller_image: The default image of the model monitoring controller job. Note that the writer
1991
- function, which is a real time nuclio functino, will be deployed with the same
1992
- image. By default, the image is mlrun/mlrun.
1993
- :param base_period: The time period in minutes in which the model monitoring controller job
1994
- runs. By default, the base period is 10 minutes. The schedule for the job
1995
- will be the following cron expression: "\*/{base_period} \* \* \* \*".
1996
- :returns: model monitoring controller job as a dictionary.
2148
+ image: str = "mlrun/mlrun",
2149
+ *,
2150
+ deploy_histogram_data_drift_app: bool = True,
2151
+ wait_for_deployment: bool = False,
2152
+ rebuild_images: bool = False,
2153
+ fetch_credentials_from_sys_config: bool = False,
2154
+ ) -> None:
2155
+ """
2156
+ Deploy model monitoring application controller, writer and stream functions.
2157
+ While the main goal of the controller function is to handle the monitoring processing and triggering
2158
+ applications, the goal of the model monitoring writer function is to write all the monitoring
2159
+ application results to the databases.
2160
+ The stream function goal is to monitor the log of the data stream. It is triggered when a new log entry
2161
+ is detected. It processes the new events into statistics that are then written to statistics databases.
2162
+
2163
+ :param default_controller_image: Deprecated.
2164
+ :param base_period: The time period in minutes in which the model monitoring controller
2165
+ function is triggered. By default, the base period is 10 minutes
2166
+ (which is also the minimum value for production environments).
2167
+ :param image: The image of the model monitoring controller, writer, monitoring
2168
+ stream & histogram data drift functions, which are real time nuclio
2169
+ functions. By default, the image is mlrun/mlrun.
2170
+ :param deploy_histogram_data_drift_app: If true, deploy the default histogram-based data drift application.
2171
+ :param wait_for_deployment: If true, return only after the deployment is done on the backend.
2172
+ Otherwise, deploy the model monitoring infrastructure on the
2173
+ background, including the histogram data drift app if selected.
2174
+ :param rebuild_images: If true, force rebuild of model monitoring infrastructure images.
2175
+ :param fetch_credentials_from_sys_config: If true, fetch the credentials from the system configuration.
2176
+ """
2177
+ if default_controller_image != "mlrun/mlrun":
2178
+ # TODO: Remove this in 1.9.0
2179
+ warnings.warn(
2180
+ "'default_controller_image' is deprecated and will be removed in 1.9.0, "
2181
+ "use 'image' instead",
2182
+ FutureWarning,
2183
+ )
2184
+ image = default_controller_image
2185
+ if base_period < 10:
2186
+ logger.warn(
2187
+ "enable_model_monitoring: 'base_period' < 10 minutes is not supported in production environments",
2188
+ project=self.name,
2189
+ )
2190
+
2191
+ db = mlrun.db.get_run_db(secrets=self._secrets)
2192
+ db.enable_model_monitoring(
2193
+ project=self.name,
2194
+ image=image,
2195
+ base_period=base_period,
2196
+ deploy_histogram_data_drift_app=deploy_histogram_data_drift_app,
2197
+ rebuild_images=rebuild_images,
2198
+ fetch_credentials_from_sys_config=fetch_credentials_from_sys_config,
2199
+ )
2200
+
2201
+ if wait_for_deployment:
2202
+ deployment_functions = mm_constants.MonitoringFunctionNames.list()
2203
+ if deploy_histogram_data_drift_app:
2204
+ deployment_functions.append(
2205
+ mm_constants.HistogramDataDriftApplicationConstants.NAME
2206
+ )
2207
+ self._wait_for_functions_deployment(deployment_functions)
2208
+
2209
+ def deploy_histogram_data_drift_app(
2210
+ self,
2211
+ *,
2212
+ image: str = "mlrun/mlrun",
2213
+ db: Optional[mlrun.db.RunDBInterface] = None,
2214
+ wait_for_deployment: bool = False,
2215
+ ) -> None:
2216
+ """
2217
+ Deploy the histogram data drift application.
2218
+
2219
+ :param image: The image on which the application will run.
2220
+ :param db: An optional DB object.
2221
+ :param wait_for_deployment: If true, return only after the deployment is done on the backend.
2222
+ Otherwise, deploy the application on the background.
2223
+ """
2224
+ if db is None:
2225
+ db = mlrun.db.get_run_db(secrets=self._secrets)
2226
+ db.deploy_histogram_data_drift_app(project=self.name, image=image)
2227
+
2228
+ if wait_for_deployment:
2229
+ self._wait_for_functions_deployment(
2230
+ [mm_constants.HistogramDataDriftApplicationConstants.NAME]
2231
+ )
2232
+
2233
+ def update_model_monitoring_controller(
2234
+ self,
2235
+ base_period: int = 10,
2236
+ image: str = "mlrun/mlrun",
2237
+ *,
2238
+ wait_for_deployment: bool = False,
2239
+ ) -> None:
2240
+ """
2241
+ Redeploy model monitoring application controller functions.
2242
+
2243
+ :param base_period: The time period in minutes in which the model monitoring controller function
2244
+ is triggered. By default, the base period is 10 minutes.
2245
+ :param image: The image of the model monitoring controller, writer & monitoring
2246
+ stream functions, which are real time nuclio functions.
2247
+ By default, the image is mlrun/mlrun.
2248
+ :param wait_for_deployment: If true, return only after the deployment is done on the backend.
2249
+ Otherwise, deploy the controller on the background.
1997
2250
  """
1998
2251
  db = mlrun.db.get_run_db(secrets=self._secrets)
1999
- return db.create_model_monitoring_controller(
2252
+ db.update_model_monitoring_controller(
2000
2253
  project=self.name,
2001
- default_controller_image=default_controller_image,
2002
2254
  base_period=base_period,
2255
+ image=image,
2003
2256
  )
2004
2257
 
2005
- def disable_model_monitoring(self):
2258
+ if wait_for_deployment:
2259
+ self._wait_for_functions_deployment(
2260
+ [mm_constants.MonitoringFunctionNames.APPLICATION_CONTROLLER]
2261
+ )
2262
+
2263
+ def disable_model_monitoring(
2264
+ self,
2265
+ *,
2266
+ delete_resources: bool = True,
2267
+ delete_stream_function: bool = False,
2268
+ delete_histogram_data_drift_app: bool = True,
2269
+ delete_user_applications: bool = False,
2270
+ user_application_list: list[str] = None,
2271
+ ) -> None:
2272
+ """
2273
+ Disable model monitoring application controller, writer, stream, histogram data drift application
2274
+ and the user's applications functions, according to the given params.
2275
+
2276
+ :param delete_resources: If True, it would delete the model monitoring controller & writer
2277
+ functions. Default True
2278
+ :param delete_stream_function: If True, it would delete model monitoring stream function,
2279
+ need to use wisely because if you're deleting this function
2280
+ this can cause data loss in case you will want to
2281
+ enable the model monitoring capability to the project.
2282
+ Default False.
2283
+ :param delete_histogram_data_drift_app: If True, it would delete the default histogram-based data drift
2284
+ application. Default False.
2285
+ :param delete_user_applications: If True, it would delete the user's model monitoring
2286
+ application according to user_application_list, Default False.
2287
+ :param user_application_list: List of the user's model monitoring application to disable.
2288
+ Default all the applications.
2289
+ Note: you have to set delete_user_applications to True
2290
+ in order to delete the desired application.
2291
+ """
2292
+ if not delete_user_applications and user_application_list:
2293
+ raise mlrun.errors.MLRunInvalidArgumentError(
2294
+ "user_application_list can be specified only if delete_user_applications is set to True"
2295
+ )
2296
+
2006
2297
  db = mlrun.db.get_run_db(secrets=self._secrets)
2007
- db.delete_function(
2298
+ succeed = db.disable_model_monitoring(
2008
2299
  project=self.name,
2009
- name=mm_constants.MonitoringFunctionNames.APPLICATION_CONTROLLER,
2300
+ delete_resources=delete_resources,
2301
+ delete_stream_function=delete_stream_function,
2302
+ delete_histogram_data_drift_app=delete_histogram_data_drift_app,
2303
+ delete_user_applications=delete_user_applications,
2304
+ user_application_list=user_application_list,
2010
2305
  )
2306
+ if succeed and delete_resources:
2307
+ if delete_resources:
2308
+ logger.info("Model Monitoring disabled", project=self.name)
2309
+ if delete_user_applications:
2310
+ logger.info(
2311
+ "All the desired monitoring application were deleted",
2312
+ project=self.name,
2313
+ )
2314
+ else:
2315
+ if delete_resources:
2316
+ logger.info(
2317
+ "Model Monitoring was not disabled properly", project=self.name
2318
+ )
2319
+ if delete_user_applications:
2320
+ logger.info(
2321
+ "Some of the desired monitoring application were not deleted",
2322
+ project=self.name,
2323
+ )
2011
2324
 
2012
2325
  def set_function(
2013
2326
  self,
2014
2327
  func: typing.Union[str, mlrun.runtimes.BaseRuntime] = None,
2015
2328
  name: str = "",
2016
- kind: str = "",
2329
+ kind: str = "job",
2017
2330
  image: str = None,
2018
2331
  handler: str = None,
2019
2332
  with_repo: bool = None,
2020
2333
  tag: str = None,
2021
- requirements: typing.Union[str, typing.List[str]] = None,
2334
+ requirements: typing.Union[str, list[str]] = None,
2022
2335
  requirements_file: str = "",
2023
2336
  ) -> mlrun.runtimes.BaseRuntime:
2024
- """update or add a function object to the project
2337
+ """
2338
+ | Update or add a function object to the project.
2339
+ | Function can be provided as an object (func) or a .py/.ipynb/.yaml URL.
2025
2340
 
2026
- function can be provided as an object (func) or a .py/.ipynb/.yaml url
2027
- support url prefixes::
2341
+ | Creating a function from a single file is done by specifying ``func`` and disabling ``with_repo``.
2342
+ | Creating a function with project source (specify ``with_repo=True``):
2343
+ | 1. Specify a relative ``func`` path.
2344
+ | 2. Specify a module ``handler`` (e.g. ``handler=package.package.func``) without ``func``.
2345
+ | Creating a function with non project source is done by specifying a module ``handler`` and on the
2346
+ returned function set the source with ``function.with_source_archive(<source>)``.
2028
2347
 
2029
- object (s3://, v3io://, ..)
2030
- MLRun DB e.g. db://project/func:ver
2031
- functions hub/market: e.g. hub://auto-trainer:master
2348
+ Support URL prefixes:
2032
2349
 
2033
- examples::
2350
+ | Object (s3://, v3io://, ..)
2351
+ | MLRun DB e.g. db://project/func:ver
2352
+ | Functions hub/market: e.g. hub://auto-trainer:master
2353
+
2354
+ Examples::
2034
2355
 
2035
2356
  proj.set_function(func_object)
2036
- proj.set_function('./src/mycode.py', 'ingest',
2037
- image='myrepo/ing:latest', with_repo=True)
2038
- proj.set_function('http://.../mynb.ipynb', 'train')
2039
- proj.set_function('./func.yaml')
2040
- proj.set_function('hub://get_toy_data', 'getdata')
2357
+ proj.set_function("http://.../mynb.ipynb", "train")
2358
+ proj.set_function("./func.yaml")
2359
+ proj.set_function("hub://get_toy_data", "getdata")
2041
2360
 
2042
- # set function requirements
2361
+ # Create a function from a single file
2362
+ proj.set_function("./src/mycode.py", "ingest")
2043
2363
 
2044
- # by providing a list of packages
2045
- proj.set_function('my.py', requirements=["requests", "pandas"])
2364
+ # Creating a function with project source
2365
+ proj.set_function(
2366
+ "./src/mycode.py", "ingest", image="myrepo/ing:latest", with_repo=True
2367
+ )
2368
+ proj.set_function("ingest", handler="package.package.func", with_repo=True)
2369
+
2370
+ # Creating a function with non project source
2371
+ func = proj.set_function(
2372
+ "ingest", handler="package.package.func", with_repo=False
2373
+ )
2374
+ func.with_source_archive("git://github.com/mlrun/something.git")
2046
2375
 
2047
- # by providing a path to a pip requirements file
2048
- proj.set_function('my.py', requirements="requirements.txt")
2376
+ # Set function requirements
2377
+
2378
+ # By providing a list of packages
2379
+ proj.set_function("my.py", requirements=["requests", "pandas"])
2380
+
2381
+ # By providing a path to a pip requirements file
2382
+ proj.set_function("my.py", requirements="requirements.txt")
2049
2383
 
2050
2384
  :param func: Function object or spec/code url, None refers to current Notebook
2051
2385
  :param name: Name of the function (under the project), can be specified with a tag to support
@@ -2056,14 +2390,15 @@ class MlrunProject(ModelObj):
2056
2390
  Default: job
2057
2391
  :param image: Docker image to be used, can also be specified in the function object/yaml
2058
2392
  :param handler: Default function handler to invoke (can only be set with .py/.ipynb files)
2059
- :param with_repo: Add (clone) the current repo to the build source
2393
+ :param with_repo: Add (clone) the current repo to the build source - use when the function code is in
2394
+ the project repo (project.spec.source).
2060
2395
  :param tag: Function version tag to set (none for current or 'latest')
2061
2396
  Specifying a tag as a parameter will update the project's tagged function
2062
2397
  (myfunc:v1) and the untagged function (myfunc)
2063
2398
  :param requirements: A list of python packages
2064
2399
  :param requirements_file: Path to a python requirements file
2065
2400
 
2066
- :returns: function object
2401
+ :returns: :py:class:`~mlrun.runtimes.BaseRuntime`
2067
2402
  """
2068
2403
  (
2069
2404
  resolved_function_name,
@@ -2094,22 +2429,23 @@ class MlrunProject(ModelObj):
2094
2429
  handler: str = None,
2095
2430
  with_repo: bool = None,
2096
2431
  tag: str = None,
2097
- requirements: typing.Union[str, typing.List[str]] = None,
2432
+ requirements: typing.Union[str, list[str]] = None,
2098
2433
  requirements_file: str = "",
2099
- ) -> typing.Tuple[str, str, mlrun.runtimes.BaseRuntime, dict]:
2100
- if func is None and not _has_module(handler, kind):
2434
+ ) -> tuple[str, str, mlrun.runtimes.BaseRuntime, dict]:
2435
+ if (
2436
+ func is None
2437
+ and not _has_module(handler, kind)
2438
+ and mlrun.runtimes.RuntimeKinds.supports_from_notebook(kind)
2439
+ ):
2101
2440
  # if function path is not provided and it is not a module (no ".")
2102
2441
  # use the current notebook as default
2103
- if not is_ipython:
2104
- raise ValueError(
2105
- "Function path or module must be specified (when not running inside a Notebook)"
2106
- )
2107
- from IPython import get_ipython
2442
+ if is_jupyter:
2443
+ from IPython import get_ipython
2108
2444
 
2109
- kernel = get_ipython()
2110
- func = nuclio.utils.notebook_file_name(kernel)
2111
- if func.startswith(path.abspath(self.spec.context)):
2112
- func = path.relpath(func, self.spec.context)
2445
+ kernel = get_ipython()
2446
+ func = nuclio.utils.notebook_file_name(kernel)
2447
+ if func.startswith(path.abspath(self.spec.context)):
2448
+ func = path.relpath(func, self.spec.context)
2113
2449
 
2114
2450
  func = func or ""
2115
2451
 
@@ -2205,22 +2541,39 @@ class MlrunProject(ModelObj):
2205
2541
  """
2206
2542
  self.spec.remove_function(name)
2207
2543
 
2208
- def remove_model_monitoring_function(self, name):
2209
- """remove the specified model-monitoring-app function from the project and from the db
2544
+ def remove_model_monitoring_function(self, name: Union[str, list[str]]):
2545
+ """delete the specified model-monitoring-app function/s
2210
2546
 
2211
- :param name: name of the model-monitoring-app function (under the project)
2547
+ :param name: name of the model-monitoring-function/s (under the project)
2212
2548
  """
2213
- function = self.get_function(key=name)
2214
- if (
2215
- function.metadata.labels.get(mm_constants.ModelMonitoringAppLabel.KEY)
2216
- == mm_constants.ModelMonitoringAppLabel.VAL
2217
- ):
2218
- self.remove_function(name=name)
2219
- mlrun.db.get_run_db().delete_function(name=name.lower())
2220
- logger.info(f"{name} function has been removed from {self.name} project")
2549
+ # TODO: Remove this in 1.9.0
2550
+ warnings.warn(
2551
+ "'remove_model_monitoring_function' is deprecated and will be removed in 1.9.0. "
2552
+ "Please use `delete_model_monitoring_function` instead.",
2553
+ FutureWarning,
2554
+ )
2555
+ self.delete_model_monitoring_function(name)
2556
+
2557
+ def delete_model_monitoring_function(self, name: Union[str, list[str]]):
2558
+ """delete the specified model-monitoring-app function/s
2559
+
2560
+ :param name: name of the model-monitoring-function/s (under the project)
2561
+ """
2562
+ db = mlrun.db.get_run_db(secrets=self._secrets)
2563
+ succeed = db.delete_model_monitoring_function(
2564
+ project=self.name,
2565
+ functions=name if isinstance(name, list) else [name],
2566
+ )
2567
+ if succeed:
2568
+ logger.info(
2569
+ "All the desired monitoring functions were deleted",
2570
+ project=self.name,
2571
+ functions=name,
2572
+ )
2221
2573
  else:
2222
- raise logger.error(
2223
- f"There is no model monitoring function with {name} name"
2574
+ logger.info(
2575
+ "Some of the desired monitoring functions were not deleted",
2576
+ project=self.name,
2224
2577
  )
2225
2578
 
2226
2579
  def get_function(
@@ -2293,7 +2646,7 @@ class MlrunProject(ModelObj):
2293
2646
  self.sync_functions()
2294
2647
  return FunctionsDict(self)
2295
2648
 
2296
- def get_function_names(self) -> typing.List[str]:
2649
+ def get_function_names(self) -> list[str]:
2297
2650
  """get a list of all the project function names"""
2298
2651
  return [func["name"] for func in self.spec.functions]
2299
2652
 
@@ -2326,13 +2679,47 @@ class MlrunProject(ModelObj):
2326
2679
  clone_zip(url, self.spec.context, self._secrets)
2327
2680
 
2328
2681
  def create_remote(self, url, name="origin", branch=None):
2329
- """create remote for the project git
2682
+ """Create remote for the project git
2683
+
2684
+ This method creates a new remote repository associated with the project's Git repository.
2685
+ If a remote with the specified name already exists, it will not be overwritten.
2686
+
2687
+ If you wish to update the URL of an existing remote, use the `set_remote` method instead.
2330
2688
 
2331
2689
  :param url: remote git url
2332
2690
  :param name: name for the remote (default is 'origin')
2333
2691
  :param branch: Git branch to use as source
2334
2692
  """
2693
+ self.set_remote(url, name=name, branch=branch, overwrite=False)
2694
+
2695
+ def set_remote(self, url, name="origin", branch=None, overwrite=True):
2696
+ """Create or update a remote for the project git repository.
2697
+
2698
+ This method allows you to manage remote repositories associated with the project.
2699
+ It checks if a remote with the specified name already exists.
2700
+
2701
+ If a remote with the same name does not exist, it will be created.
2702
+ If a remote with the same name already exists,
2703
+ the behavior depends on the value of the 'overwrite' flag.
2704
+
2705
+ :param url: remote git url
2706
+ :param name: name for the remote (default is 'origin')
2707
+ :param branch: Git branch to use as source
2708
+ :param overwrite: if True (default), updates the existing remote with the given URL if it already exists.
2709
+ if False, raises an error when attempting to create a remote with a name that already exists.
2710
+ :raises MLRunConflictError: If a remote with the same name already exists and overwrite
2711
+ is set to False.
2712
+ """
2335
2713
  self._ensure_git_repo()
2714
+ if self._remote_exists(name):
2715
+ if overwrite:
2716
+ self.spec.repo.delete_remote(name)
2717
+ else:
2718
+ raise mlrun.errors.MLRunConflictError(
2719
+ f"Remote '{name}' already exists in the project, "
2720
+ f"each remote in the project must have a unique name."
2721
+ "Use 'set_remote' with 'override=True' inorder to update the remote, or choose a different name."
2722
+ )
2336
2723
  self.spec.repo.create_remote(name, url=url)
2337
2724
  url = url.replace("https://", "git://")
2338
2725
  if not branch:
@@ -2345,6 +2732,22 @@ class MlrunProject(ModelObj):
2345
2732
  self.spec._source = self.spec.source or url
2346
2733
  self.spec.origin_url = self.spec.origin_url or url
2347
2734
 
2735
+ def remove_remote(self, name):
2736
+ """Remove a remote from the project's Git repository.
2737
+
2738
+ This method removes the remote repository associated with the specified name from the project's Git repository.
2739
+
2740
+ :param name: Name of the remote to remove.
2741
+ """
2742
+ if self._remote_exists(name):
2743
+ self.spec.repo.delete_remote(name)
2744
+ else:
2745
+ logger.warning(f"The remote '{name}' does not exist. Nothing to remove.")
2746
+
2747
+ def _remote_exists(self, name):
2748
+ """Check if a remote with the given name already exists"""
2749
+ return any(remote.name == name for remote in self.spec.repo.remotes)
2750
+
2348
2751
  def _ensure_git_repo(self):
2349
2752
  if self.spec.repo:
2350
2753
  return
@@ -2427,37 +2830,104 @@ class MlrunProject(ModelObj):
2427
2830
  secrets=secrets or {},
2428
2831
  )
2429
2832
 
2430
- def sync_functions(self, names: list = None, always=True, save=False):
2431
- """reload function objects from specs and files"""
2833
+ def sync_functions(
2834
+ self,
2835
+ names: list = None,
2836
+ always: bool = True,
2837
+ save: bool = False,
2838
+ silent: bool = False,
2839
+ ):
2840
+ """
2841
+ Reload function objects from specs and files.
2842
+ The function objects are synced against the definitions spec in `self.spec._function_definitions`.
2843
+ Referenced files/URLs in the function spec will be reloaded.
2844
+ Function definitions are parsed by the following precedence:
2845
+
2846
+ 1. Contains runtime spec.
2847
+ 2. Contains module in the project's context.
2848
+ 3. Contains path to function definition (yaml, DB, Hub).
2849
+ 4. Contains path to .ipynb or .py files.
2850
+ 5. Contains a Nuclio/Serving function image / an 'Application' kind definition.
2851
+
2852
+ If function definition is already an object, some project metadata updates will apply however,
2853
+ it will not be reloaded.
2854
+
2855
+ :param names: Names of functions to reload, defaults to `self.spec._function_definitions.keys()`.
2856
+ :param always: Force reloading the functions.
2857
+ :param save: Whether to save the loaded functions or not.
2858
+ :param silent: Whether to raise an exception when a function fails to load.
2859
+
2860
+ :returns: Dictionary of function objects
2861
+ """
2432
2862
  if self._initialized and not always:
2433
2863
  return self.spec._function_objects
2434
2864
 
2435
- funcs = self.spec._function_objects
2865
+ functions = self.spec._function_objects
2436
2866
  if not names:
2437
2867
  names = self.spec._function_definitions.keys()
2438
- funcs = {}
2868
+ functions = {}
2869
+
2439
2870
  origin = mlrun.runtimes.utils.add_code_metadata(self.spec.context)
2440
2871
  for name in names:
2441
- f = self.spec._function_definitions.get(name)
2442
- if not f:
2443
- raise ValueError(f"function named {name} not found")
2444
- if hasattr(f, "to_dict"):
2445
- name, func = _init_function_from_obj(f, self, name)
2446
- else:
2447
- if not isinstance(f, dict):
2448
- raise ValueError("function must be an object or dict")
2872
+ function_definition = self.spec._function_definitions.get(name)
2873
+ if not function_definition:
2874
+ if silent:
2875
+ logger.warn(
2876
+ "Function definition was not found, skipping reload", name=name
2877
+ )
2878
+ continue
2879
+
2880
+ raise ValueError(f"Function named {name} not found")
2881
+
2882
+ function_object = self.spec._function_objects.get(name, None)
2883
+ is_base_runtime = isinstance(
2884
+ function_object, mlrun.runtimes.base.BaseRuntime
2885
+ )
2886
+ # If this function is already available locally, don't recreate it unless always=True
2887
+ if is_base_runtime and not always:
2888
+ functions[name] = function_object
2889
+ continue
2890
+
2891
+ # Reload the function
2892
+ if hasattr(function_definition, "to_dict"):
2893
+ name, func = _init_function_from_obj(function_definition, self, name)
2894
+ elif isinstance(function_definition, dict):
2449
2895
  try:
2450
- name, func = _init_function_from_dict(f, self, name)
2896
+ name, func = _init_function_from_dict(
2897
+ function_definition, self, name
2898
+ )
2451
2899
  except FileNotFoundError as exc:
2452
- raise mlrun.errors.MLRunMissingDependencyError(
2453
- f"File {exc.filename} not found while syncing project functions"
2454
- ) from exc
2900
+ message = f"File {exc.filename} not found while syncing project functions."
2901
+ if silent:
2902
+ message += " Skipping function reload"
2903
+ logger.warn(message, name=name)
2904
+ continue
2905
+
2906
+ raise mlrun.errors.MLRunMissingDependencyError(message) from exc
2907
+
2908
+ except Exception as exc:
2909
+ if silent:
2910
+ logger.warn(
2911
+ "Failed to instantiate function",
2912
+ name=name,
2913
+ error=mlrun.utils.err_to_str(exc),
2914
+ )
2915
+ continue
2916
+ raise exc
2917
+ else:
2918
+ message = f"Function {name} must be an object or dict."
2919
+ if silent:
2920
+ message += " Skipping function reload"
2921
+ logger.warn(message, name=name)
2922
+ continue
2923
+ raise ValueError(message)
2924
+
2455
2925
  func.spec.build.code_origin = origin
2456
- funcs[name] = func
2926
+ functions[name] = func
2457
2927
  if save:
2458
2928
  func.save(versioned=False)
2459
2929
 
2460
- self.spec._function_objects = funcs
2930
+ self.spec._function_objects = functions
2461
2931
  self._initialized = True
2462
2932
  return self.spec._function_objects
2463
2933
 
@@ -2466,9 +2936,9 @@ class MlrunProject(ModelObj):
2466
2936
 
2467
2937
  read secrets from a source provider to be used in workflows, example::
2468
2938
 
2469
- proj.with_secrets('file', 'file.txt')
2470
- proj.with_secrets('inline', {'key': 'val'})
2471
- proj.with_secrets('env', 'ENV1,ENV2', prefix='PFX_')
2939
+ proj.with_secrets("file", "file.txt")
2940
+ proj.with_secrets("inline", {"key": "val"})
2941
+ proj.with_secrets("env", "ENV1,ENV2", prefix="PFX_")
2472
2942
 
2473
2943
  Vault secret source has several options::
2474
2944
 
@@ -2479,7 +2949,7 @@ class MlrunProject(ModelObj):
2479
2949
  The 2nd option uses the current project name as context.
2480
2950
  Can also use empty secret list::
2481
2951
 
2482
- proj.with_secrets('vault', [])
2952
+ proj.with_secrets("vault", [])
2483
2953
 
2484
2954
  This will enable access to all secrets in vault registered to the current project.
2485
2955
 
@@ -2510,17 +2980,20 @@ class MlrunProject(ModelObj):
2510
2980
  file_path: str = None,
2511
2981
  provider: typing.Union[str, mlrun.common.schemas.SecretProviderName] = None,
2512
2982
  ):
2513
- """set project secrets from dict or secrets env file
2983
+ """
2984
+ Set project secrets from dict or secrets env file
2514
2985
  when using a secrets file it should have lines in the form KEY=VALUE, comment line start with "#"
2515
2986
  V3IO paths/credentials and MLrun service API address are dropped from the secrets
2516
2987
 
2517
- example secrets file::
2988
+ example secrets file:
2989
+
2990
+ .. code-block:: shell
2518
2991
 
2519
2992
  # this is an env file
2520
- AWS_ACCESS_KEY_ID-XXXX
2993
+ AWS_ACCESS_KEY_ID=XXXX
2521
2994
  AWS_SECRET_ACCESS_KEY=YYYY
2522
2995
 
2523
- usage::
2996
+ usage:
2524
2997
 
2525
2998
  # read env vars from dict or file and set as project secrets
2526
2999
  project.set_secrets({"SECRET1": "value"})
@@ -2583,7 +3056,7 @@ class MlrunProject(ModelObj):
2583
3056
  self,
2584
3057
  name: str = None,
2585
3058
  workflow_path: str = None,
2586
- arguments: typing.Dict[str, typing.Any] = None,
3059
+ arguments: dict[str, typing.Any] = None,
2587
3060
  artifact_path: str = None,
2588
3061
  workflow_handler: typing.Union[str, typing.Callable] = None,
2589
3062
  namespace: str = None,
@@ -2598,7 +3071,8 @@ class MlrunProject(ModelObj):
2598
3071
  timeout: int = None,
2599
3072
  source: str = None,
2600
3073
  cleanup_ttl: int = None,
2601
- notifications: typing.List[mlrun.model.Notification] = None,
3074
+ notifications: list[mlrun.model.Notification] = None,
3075
+ workflow_runner_node_selector: typing.Optional[dict[str, str]] = None,
2602
3076
  ) -> _PipelineRunStatus:
2603
3077
  """Run a workflow using kubeflow pipelines
2604
3078
 
@@ -2623,16 +3097,24 @@ class MlrunProject(ModelObj):
2623
3097
  For using the pre-defined workflow's schedule, set `schedule=True`
2624
3098
  :param timeout: Timeout in seconds to wait for pipeline completion (watch will be activated)
2625
3099
  :param source: Source to use instead of the actual `project.spec.source` (used when engine is remote).
2626
- Can be a one of:
2627
- 1. Remote URL which is loaded dynamically to the workflow runner.
2628
- 2. A path to the project's context on the workflow runner's image.
2629
- Path can be absolute or relative to `project.spec.build.source_code_target_dir` if defined
2630
- (enriched when building a project image with source, see `MlrunProject.build_image`).
2631
- For other engines the source is used to validate that the code is up-to-date.
2632
- :param cleanup_ttl: Pipeline cleanup ttl in secs (time to wait after workflow completion, at which point the
2633
- workflow and all its resources are deleted)
2634
- :param notifications: List of notifications to send for workflow completion
2635
-
3100
+ Can be one of:
3101
+
3102
+ * Remote URL which is loaded dynamically to the workflow runner.
3103
+ * A path to the project's context on the workflow runner's image.
3104
+ Path can be absolute or relative to `project.spec.build.source_code_target_dir` if defined
3105
+ (enriched when building a project image with source, see `MlrunProject.build_image`).
3106
+ For other engines the source is used to validate that the code is up-to-date.
3107
+
3108
+ :param cleanup_ttl:
3109
+ Pipeline cleanup ttl in secs (time to wait after workflow completion, at which point the
3110
+ workflow and all its resources are deleted)
3111
+ :param notifications:
3112
+ List of notifications to send for workflow completion
3113
+ :param workflow_runner_node_selector:
3114
+ Defines the node selector for the workflow runner pod when using a remote engine.
3115
+ This allows you to control and specify where the workflow runner pod will be scheduled.
3116
+ This setting is only relevant when the engine is set to 'remote' or for scheduled workflows,
3117
+ and it will be ignored if the workflow is not run on a remote engine.
2636
3118
  :returns: ~py:class:`~mlrun.projects.pipelines._PipelineRunStatus` instance
2637
3119
  """
2638
3120
 
@@ -2650,11 +3132,11 @@ class MlrunProject(ModelObj):
2650
3132
  "Remote repo is not defined, use .create_remote() + push()"
2651
3133
  )
2652
3134
 
2653
- if engine not in ["remote"]:
2654
- # for remote runs we don't require the functions to be synced as they can be loaded dynamically during run
2655
- self.sync_functions(always=sync)
3135
+ if engine not in ["remote"] and not schedule:
3136
+ # For remote/scheduled runs there is no need to sync functions as they can be loaded dynamically during run
3137
+ self.sync_functions(always=sync, silent=True)
2656
3138
  if not self.spec._function_objects:
2657
- raise ValueError(
3139
+ logger.warn(
2658
3140
  "There are no functions in the project."
2659
3141
  " Make sure you've set your functions with project.set_function()."
2660
3142
  )
@@ -2691,9 +3173,23 @@ class MlrunProject(ModelObj):
2691
3173
  engine = "remote"
2692
3174
  # The default engine is kfp if not given:
2693
3175
  workflow_engine = get_workflow_engine(engine or workflow_spec.engine, local)
2694
- if not inner_engine and engine == "remote":
2695
- inner_engine = get_workflow_engine(workflow_spec.engine, local).engine
3176
+ if not inner_engine and workflow_engine.engine == "remote":
3177
+ # if inner engine is set to remote, assume kfp as the default inner engine with remote as the runner
3178
+ engine_kind = (
3179
+ workflow_spec.engine if workflow_spec.engine != "remote" else "kfp"
3180
+ )
3181
+ inner_engine = get_workflow_engine(engine_kind, local).engine
2696
3182
  workflow_spec.engine = inner_engine or workflow_engine.engine
3183
+ if workflow_runner_node_selector:
3184
+ if workflow_engine.engine == "remote":
3185
+ workflow_spec.workflow_runner_node_selector = (
3186
+ workflow_runner_node_selector
3187
+ )
3188
+ else:
3189
+ logger.warn(
3190
+ "'workflow_runner_node_selector' applies only to remote engines"
3191
+ " and is ignored for non-remote runs."
3192
+ )
2697
3193
 
2698
3194
  run = workflow_engine.run(
2699
3195
  self,
@@ -2707,7 +3203,7 @@ class MlrunProject(ModelObj):
2707
3203
  notifications=notifications,
2708
3204
  )
2709
3205
  # run is None when scheduling
2710
- if run and run.state == mlrun.run.RunStatuses.failed:
3206
+ if run and run.state == mlrun_pipelines.common.models.RunStatuses.failed:
2711
3207
  return run
2712
3208
  if not workflow_spec.schedule:
2713
3209
  # Failure and schedule messages already logged
@@ -2716,14 +3212,17 @@ class MlrunProject(ModelObj):
2716
3212
  )
2717
3213
  workflow_spec.clear_tmp()
2718
3214
  if (timeout or watch) and not workflow_spec.schedule:
3215
+ run_status_kwargs = {}
2719
3216
  status_engine = run._engine
2720
3217
  # run's engine gets replaced with inner engine if engine is remote,
2721
3218
  # so in that case we need to get the status from the remote engine manually
2722
- # TODO: support watch for remote:local
2723
- if engine == "remote" and status_engine.engine != "local":
3219
+ if workflow_engine.engine == "remote":
2724
3220
  status_engine = _RemoteRunner
3221
+ run_status_kwargs["inner_engine"] = run._engine
2725
3222
 
2726
- status_engine.get_run_status(project=self, run=run, timeout=timeout)
3223
+ status_engine.get_run_status(
3224
+ project=self, run=run, timeout=timeout, **run_status_kwargs
3225
+ )
2727
3226
  return run
2728
3227
 
2729
3228
  def save_workflow(self, name, target, artifact_path=None, ttl=None):
@@ -2823,43 +3322,66 @@ class MlrunProject(ModelObj):
2823
3322
 
2824
3323
  def set_model_monitoring_credentials(
2825
3324
  self,
2826
- access_key: str = None,
2827
- endpoint_store_connection: str = None,
2828
- stream_path: str = None,
3325
+ access_key: Optional[str] = None,
3326
+ endpoint_store_connection: Optional[str] = None,
3327
+ stream_path: Optional[str] = None,
3328
+ tsdb_connection: Optional[str] = None,
3329
+ replace_creds: bool = False,
2829
3330
  ):
2830
- """Set the credentials that will be used by the project's model monitoring
2831
- infrastructure functions.
2832
-
2833
- :param access_key: Model Monitoring access key for managing user permissions
2834
- :param access_key: Model Monitoring access key for managing user permissions
2835
- :param endpoint_store_connection: Endpoint store connection string
2836
- :param stream_path: Path to the model monitoring stream
2837
3331
  """
2838
-
2839
- secrets_dict = {}
2840
- if access_key:
2841
- secrets_dict[
2842
- mlrun.common.schemas.model_monitoring.ProjectSecretKeys.ACCESS_KEY
2843
- ] = access_key
2844
-
2845
- if endpoint_store_connection:
2846
- secrets_dict[
2847
- mlrun.common.schemas.model_monitoring.ProjectSecretKeys.ENDPOINT_STORE_CONNECTION
2848
- ] = endpoint_store_connection
2849
-
2850
- if stream_path:
2851
- if stream_path.startswith("kafka://") and "?topic" in stream_path:
2852
- raise mlrun.errors.MLRunInvalidArgumentError(
2853
- "Custom kafka topic is not allowed"
2854
- )
2855
- secrets_dict[
2856
- mlrun.common.schemas.model_monitoring.ProjectSecretKeys.STREAM_PATH
2857
- ] = stream_path
2858
-
2859
- self.set_secrets(
2860
- secrets=secrets_dict,
2861
- provider=mlrun.common.schemas.SecretProviderName.kubernetes,
3332
+ Set the credentials that will be used by the project's model monitoring
3333
+ infrastructure functions. Important to note that you have to set the credentials before deploying any
3334
+ model monitoring or serving function.
3335
+
3336
+ :param access_key: Model monitoring access key for managing user permissions.
3337
+ :param endpoint_store_connection: Endpoint store connection string. By default, None. Options:
3338
+
3339
+ * None - will be set from the system configuration.
3340
+ * v3io - for v3io endpoint store, pass `v3io` and the system will generate the
3341
+ exact path.
3342
+ * MySQL/SQLite - for SQL endpoint store, provide the full connection string,
3343
+ for example: mysql+pymysql://<username>:<password>@<host>:<port>/<db_name>
3344
+ :param stream_path: Path to the model monitoring stream. By default, None. Options:
3345
+
3346
+ * None - will be set from the system configuration.
3347
+ * v3io - for v3io stream, pass `v3io` and the system will generate the exact
3348
+ path.
3349
+ * Kafka - for Kafka stream, provide the full connection string without custom
3350
+ topic, for example kafka://<some_kafka_broker>:<port>.
3351
+ :param tsdb_connection: Connection string to the time series database. By default, None.
3352
+ Options:
3353
+
3354
+ * None - will be set from the system configuration.
3355
+ * v3io - for v3io stream, pass `v3io` and the system will generate the exact
3356
+ path.
3357
+ * TDEngine - for TDEngine tsdb, provide the full websocket connection URL,
3358
+ for example taosws://<username>:<password>@<host>:<port>.
3359
+ :param replace_creds: If True, will override the existing credentials.
3360
+ Please keep in mind that if you already enabled model monitoring on
3361
+ your project this action can cause data loose and will require redeploying
3362
+ all model monitoring functions & model monitoring infra
3363
+ & tracked model server.
3364
+ """
3365
+ db = mlrun.db.get_run_db(secrets=self._secrets)
3366
+ db.set_model_monitoring_credentials(
3367
+ project=self.name,
3368
+ credentials={
3369
+ "access_key": access_key,
3370
+ "endpoint_store_connection": endpoint_store_connection,
3371
+ "stream_path": stream_path,
3372
+ "tsdb_connection": tsdb_connection,
3373
+ },
3374
+ replace_creds=replace_creds,
2862
3375
  )
3376
+ if replace_creds:
3377
+ logger.info(
3378
+ "Model monitoring credentials were set successfully. "
3379
+ "Please keep in mind that if you already had model monitoring functions "
3380
+ "/ model monitoring infra / tracked model server "
3381
+ "deployed on your project, you will need to redeploy them."
3382
+ "For redeploying the model monitoring infra, please use `enable_model_monitoring` API "
3383
+ "and set `rebuild_images=True`"
3384
+ )
2863
3385
 
2864
3386
  def run_function(
2865
3387
  self,
@@ -2870,7 +3392,7 @@ class MlrunProject(ModelObj):
2870
3392
  hyperparams: dict = None,
2871
3393
  hyper_param_options: mlrun.model.HyperParamOptions = None,
2872
3394
  inputs: dict = None,
2873
- outputs: typing.List[str] = None,
3395
+ outputs: list[str] = None,
2874
3396
  workdir: str = "",
2875
3397
  labels: dict = None,
2876
3398
  base_task: mlrun.model.RunTemplate = None,
@@ -2881,10 +3403,11 @@ class MlrunProject(ModelObj):
2881
3403
  auto_build: bool = None,
2882
3404
  schedule: typing.Union[str, mlrun.common.schemas.ScheduleCronTrigger] = None,
2883
3405
  artifact_path: str = None,
2884
- notifications: typing.List[mlrun.model.Notification] = None,
2885
- returns: Optional[List[Union[str, Dict[str, str]]]] = None,
3406
+ notifications: list[mlrun.model.Notification] = None,
3407
+ returns: Optional[list[Union[str, dict[str, str]]]] = None,
2886
3408
  builder_env: Optional[dict] = None,
2887
- ) -> typing.Union[mlrun.model.RunObject, kfp.dsl.ContainerOp]:
3409
+ reset_on_run: bool = None,
3410
+ ) -> typing.Union[mlrun.model.RunObject, PipelineNodeWrapper]:
2888
3411
  """Run a local or remote task as part of a local/kubeflow pipeline
2889
3412
 
2890
3413
  example (use with project)::
@@ -2896,8 +3419,11 @@ class MlrunProject(ModelObj):
2896
3419
 
2897
3420
  # run functions (refer to them by name)
2898
3421
  run1 = project.run_function("myfunc", params={"x": 7})
2899
- run2 = project.run_function("train", params={"label_columns": LABELS},
2900
- inputs={"dataset":run1.outputs["data"]})
3422
+ run2 = project.run_function(
3423
+ "train",
3424
+ params={"label_columns": LABELS},
3425
+ inputs={"dataset": run1.outputs["data"]},
3426
+ )
2901
3427
 
2902
3428
  :param function: name of the function (in the project) or function object
2903
3429
  :param handler: name of the function handler
@@ -2936,8 +3462,13 @@ class MlrunProject(ModelObj):
2936
3462
  * A dictionary of configurations to use when logging. Further info per object type and
2937
3463
  artifact type can be given there. The artifact key must appear in the dictionary as
2938
3464
  "key": "the_key".
2939
- :param builder_env: env vars dict for source archive config/credentials e.g. builder_env={"GIT_TOKEN": token}
2940
- :return: MLRun RunObject or KubeFlow containerOp
3465
+ :param builder_env: env vars dict for source archive config/credentials e.g. builder_env={"GIT_TOKEN":
3466
+ token}
3467
+ :param reset_on_run: When True, function python modules would reload prior to code execution.
3468
+ This ensures latest code changes are executed. This argument must be used in
3469
+ conjunction with the local=True argument.
3470
+
3471
+ :return: MLRun RunObject or PipelineNodeWrapper
2941
3472
  """
2942
3473
  return run_function(
2943
3474
  function,
@@ -2962,6 +3493,7 @@ class MlrunProject(ModelObj):
2962
3493
  notifications=notifications,
2963
3494
  returns=returns,
2964
3495
  builder_env=builder_env,
3496
+ reset_on_run=reset_on_run,
2965
3497
  )
2966
3498
 
2967
3499
  def build_function(
@@ -2973,14 +3505,14 @@ class MlrunProject(ModelObj):
2973
3505
  base_image: str = None,
2974
3506
  commands: list = None,
2975
3507
  secret_name: str = None,
2976
- requirements: typing.Union[str, typing.List[str]] = None,
3508
+ requirements: typing.Union[str, list[str]] = None,
2977
3509
  mlrun_version_specifier: str = None,
2978
3510
  builder_env: dict = None,
2979
3511
  overwrite_build_params: bool = False,
2980
3512
  requirements_file: str = None,
2981
3513
  extra_args: str = None,
2982
3514
  force_build: bool = False,
2983
- ) -> typing.Union[BuildStatus, kfp.dsl.ContainerOp]:
3515
+ ) -> typing.Union[BuildStatus, PipelineNodeWrapper]:
2984
3516
  """deploy ML function, build container with its dependencies
2985
3517
 
2986
3518
  :param function: name of the function (in the project) or function object
@@ -3029,7 +3561,7 @@ class MlrunProject(ModelObj):
3029
3561
  base_image: str = None,
3030
3562
  commands: list = None,
3031
3563
  secret_name: str = None,
3032
- requirements: typing.Union[str, typing.List[str]] = None,
3564
+ requirements: typing.Union[str, list[str]] = None,
3033
3565
  overwrite_build_params: bool = False,
3034
3566
  requirements_file: str = None,
3035
3567
  builder_env: dict = None,
@@ -3091,18 +3623,17 @@ class MlrunProject(ModelObj):
3091
3623
  image: str = None,
3092
3624
  set_as_default: bool = True,
3093
3625
  with_mlrun: bool = None,
3094
- skip_deployed: bool = False,
3095
3626
  base_image: str = None,
3096
3627
  commands: list = None,
3097
3628
  secret_name: str = None,
3098
- requirements: typing.Union[str, typing.List[str]] = None,
3629
+ requirements: typing.Union[str, list[str]] = None,
3099
3630
  mlrun_version_specifier: str = None,
3100
3631
  builder_env: dict = None,
3101
3632
  overwrite_build_params: bool = False,
3102
3633
  requirements_file: str = None,
3103
3634
  extra_args: str = None,
3104
3635
  target_dir: str = None,
3105
- ) -> typing.Union[BuildStatus, kfp.dsl.ContainerOp]:
3636
+ ) -> typing.Union[BuildStatus, PipelineNodeWrapper]:
3106
3637
  """Builder docker image for the project, based on the project's build config. Parameters allow to override
3107
3638
  the build config.
3108
3639
  If the project has a source configured and pull_at_runtime is not configured, this source will be cloned to the
@@ -3112,7 +3643,6 @@ class MlrunProject(ModelObj):
3112
3643
  used. If not set, the `mlconf.default_project_image_name` value will be used
3113
3644
  :param set_as_default: set `image` to be the project's default image (default False)
3114
3645
  :param with_mlrun: add the current mlrun package to the container build
3115
- :param skip_deployed: *Deprecated* parameter is ignored
3116
3646
  :param base_image: base image name/path (commands and source code will be added to it) defaults to
3117
3647
  mlrun.mlconf.default_base_image
3118
3648
  :param commands: list of docker build (RUN) commands e.g. ['pip install pandas']
@@ -3137,14 +3667,6 @@ class MlrunProject(ModelObj):
3137
3667
  base_image=base_image,
3138
3668
  )
3139
3669
 
3140
- if skip_deployed:
3141
- warnings.warn(
3142
- "The 'skip_deployed' parameter is deprecated and will be removed in 1.7.0. "
3143
- "This parameter is ignored.",
3144
- # TODO: remove in 1.7.0
3145
- FutureWarning,
3146
- )
3147
-
3148
3670
  if not overwrite_build_params:
3149
3671
  # TODO: change overwrite_build_params default to True in 1.8.0
3150
3672
  warnings.warn(
@@ -3208,7 +3730,7 @@ class MlrunProject(ModelObj):
3208
3730
  logger.warning(
3209
3731
  f"Image was successfully built, but failed to delete temporary function {function.metadata.name}."
3210
3732
  " To remove the function, attempt to manually delete it.",
3211
- exc=repr(exc),
3733
+ exc=mlrun.errors.err_to_str(exc),
3212
3734
  )
3213
3735
 
3214
3736
  return result
@@ -3222,7 +3744,7 @@ class MlrunProject(ModelObj):
3222
3744
  verbose: bool = None,
3223
3745
  builder_env: dict = None,
3224
3746
  mock: bool = None,
3225
- ) -> typing.Union[DeployStatus, kfp.dsl.ContainerOp]:
3747
+ ) -> typing.Union[DeployStatus, PipelineNodeWrapper]:
3226
3748
  """deploy real-time (nuclio based) functions
3227
3749
 
3228
3750
  :param function: name of the function (in the project) or function object
@@ -3257,13 +3779,18 @@ class MlrunProject(ModelObj):
3257
3779
  artifact = db.read_artifact(
3258
3780
  key, tag, iter=iter, project=self.metadata.name, tree=tree
3259
3781
  )
3260
- return dict_to_artifact(artifact)
3782
+
3783
+ # in tests, if an artifact is not found, the db returns None
3784
+ # in real usage, the db should raise an exception
3785
+ if artifact:
3786
+ return dict_to_artifact(artifact)
3787
+ return None
3261
3788
 
3262
3789
  def list_artifacts(
3263
3790
  self,
3264
3791
  name=None,
3265
3792
  tag=None,
3266
- labels: Optional[Union[Dict[str, str], List[str]]] = None,
3793
+ labels: Optional[Union[dict[str, str], list[str]]] = None,
3267
3794
  since=None,
3268
3795
  until=None,
3269
3796
  iter: int = None,
@@ -3281,9 +3808,9 @@ class MlrunProject(ModelObj):
3281
3808
  Examples::
3282
3809
 
3283
3810
  # Get latest version of all artifacts in project
3284
- latest_artifacts = project.list_artifacts('', tag='latest')
3811
+ latest_artifacts = project.list_artifacts("", tag="latest")
3285
3812
  # check different artifact versions for a specific artifact, return as objects list
3286
- result_versions = project.list_artifacts('results', tag='*').to_objects()
3813
+ result_versions = project.list_artifacts("results", tag="*").to_objects()
3287
3814
 
3288
3815
  :param name: Name of artifacts to retrieve. Name with '~' prefix is used as a like query, and is not
3289
3816
  case-sensitive. This means that querying for ``~name`` may return artifacts named
@@ -3323,7 +3850,7 @@ class MlrunProject(ModelObj):
3323
3850
  self,
3324
3851
  name=None,
3325
3852
  tag=None,
3326
- labels: Optional[Union[Dict[str, str], List[str]]] = None,
3853
+ labels: Optional[Union[dict[str, str], list[str]]] = None,
3327
3854
  since=None,
3328
3855
  until=None,
3329
3856
  iter: int = None,
@@ -3335,7 +3862,7 @@ class MlrunProject(ModelObj):
3335
3862
  Examples::
3336
3863
 
3337
3864
  # Get latest version of all models in project
3338
- latest_models = project.list_models('', tag='latest')
3865
+ latest_models = project.list_models("", tag="latest")
3339
3866
 
3340
3867
 
3341
3868
  :param name: Name of artifacts to retrieve. Name with '~' prefix is used as a like query, and is not
@@ -3376,7 +3903,7 @@ class MlrunProject(ModelObj):
3376
3903
 
3377
3904
 
3378
3905
  :param name: Return only functions with a specific name.
3379
- :param tag: Return function versions with specific tags.
3906
+ :param tag: Return function versions with specific tags. To return only tagged functions, set tag to ``"*"``.
3380
3907
  :param labels: Return functions that have specific labels assigned to them.
3381
3908
  :returns: List of function objects.
3382
3909
  """
@@ -3405,9 +3932,7 @@ class MlrunProject(ModelObj):
3405
3932
  :returns: List of function objects.
3406
3933
  """
3407
3934
 
3408
- model_monitoring_labels_list = [
3409
- f"{mm_constants.ModelMonitoringAppLabel.KEY}={mm_constants.ModelMonitoringAppLabel.VAL}"
3410
- ]
3935
+ model_monitoring_labels_list = [str(mm_constants.ModelMonitoringAppLabel())]
3411
3936
  if labels:
3412
3937
  model_monitoring_labels_list += labels
3413
3938
  return self.list_functions(
@@ -3419,9 +3944,12 @@ class MlrunProject(ModelObj):
3419
3944
  def list_runs(
3420
3945
  self,
3421
3946
  name: Optional[str] = None,
3422
- uid: Optional[Union[str, List[str]]] = None,
3423
- labels: Optional[Union[str, List[str]]] = None,
3424
- state: Optional[str] = None,
3947
+ uid: Optional[Union[str, list[str]]] = None,
3948
+ labels: Optional[Union[str, list[str]]] = None,
3949
+ state: Optional[
3950
+ mlrun.common.runtimes.constants.RunStates
3951
+ ] = None, # Backward compatibility
3952
+ states: typing.Optional[list[mlrun.common.runtimes.constants.RunStates]] = None,
3425
3953
  sort: bool = True,
3426
3954
  last: int = 0,
3427
3955
  iter: bool = False,
@@ -3440,14 +3968,14 @@ class MlrunProject(ModelObj):
3440
3968
  Example::
3441
3969
 
3442
3970
  # return a list of runs matching the name and label and compare
3443
- runs = project.list_runs(name='download', labels='owner=admin')
3971
+ runs = project.list_runs(name="download", labels="owner=admin")
3444
3972
  runs.compare()
3445
3973
 
3446
3974
  # multi-label filter can also be provided
3447
- runs = project.list_runs(name='download', labels=["kind=job", "owner=admin"])
3975
+ runs = project.list_runs(name="download", labels=["kind=job", "owner=admin"])
3448
3976
 
3449
3977
  # If running in Jupyter, can use the .show() function to display the results
3450
- project.list_runs(name='').show()
3978
+ project.list_runs(name="").show()
3451
3979
 
3452
3980
 
3453
3981
  :param name: Name of the run to retrieve.
@@ -3455,10 +3983,11 @@ class MlrunProject(ModelObj):
3455
3983
  :param labels: A list of labels to filter by. Label filters work by either filtering a specific value
3456
3984
  of a label (i.e. list("key=value")) or by looking for the existence of a given
3457
3985
  key (i.e. "key").
3458
- :param state: List only runs whose state is specified.
3986
+ :param state: Deprecated - List only runs whose state is specified.
3987
+ :param states: List only runs whose state is one of the provided states.
3459
3988
  :param sort: Whether to sort the result according to their start time. Otherwise, results will be
3460
3989
  returned by their internal order in the DB (order will not be guaranteed).
3461
- :param last: Deprecated - currently not used (will be removed in 1.8.0).
3990
+ :param last: Deprecated - currently not used (will be removed in 1.9.0).
3462
3991
  :param iter: If ``True`` return runs from all iterations. Otherwise, return only runs whose ``iter`` is 0.
3463
3992
  :param start_time_from: Filter by run start time in ``[start_time_from, start_time_to]``.
3464
3993
  :param start_time_to: Filter by run start time in ``[start_time_from, start_time_to]``.
@@ -3466,13 +3995,22 @@ class MlrunProject(ModelObj):
3466
3995
  last_update_time_to)``.
3467
3996
  :param last_update_time_to: Filter by run last update time in ``(last_update_time_from, last_update_time_to)``.
3468
3997
  """
3998
+ if state:
3999
+ # TODO: Remove this in 1.9.0
4000
+ warnings.warn(
4001
+ "'state' is deprecated and will be removed in 1.9.0. Use 'states' instead.",
4002
+ FutureWarning,
4003
+ )
4004
+
3469
4005
  db = mlrun.db.get_run_db(secrets=self._secrets)
3470
4006
  return db.list_runs(
3471
4007
  name,
3472
4008
  uid,
3473
4009
  self.metadata.name,
3474
4010
  labels=labels,
3475
- state=state,
4011
+ states=mlrun.utils.helpers.as_list(state)
4012
+ if state is not None
4013
+ else states or None,
3476
4014
  sort=sort,
3477
4015
  last=last,
3478
4016
  iter=iter,
@@ -3508,7 +4046,7 @@ class MlrunProject(ModelObj):
3508
4046
  profile, self.name
3509
4047
  )
3510
4048
 
3511
- def list_datastore_profiles(self) -> List[DatastoreProfile]:
4049
+ def list_datastore_profiles(self) -> list[DatastoreProfile]:
3512
4050
  """
3513
4051
  Returns a list of datastore profiles associated with the project.
3514
4052
  The information excludes private details, showcasing only public data.
@@ -3517,7 +4055,7 @@ class MlrunProject(ModelObj):
3517
4055
  self.name
3518
4056
  )
3519
4057
 
3520
- def get_custom_packagers(self) -> typing.List[typing.Tuple[str, bool]]:
4058
+ def get_custom_packagers(self) -> list[tuple[str, bool]]:
3521
4059
  """
3522
4060
  Get the custom packagers registered in the project.
3523
4061
 
@@ -3553,12 +4091,194 @@ class MlrunProject(ModelObj):
3553
4091
  """
3554
4092
  self.spec.remove_custom_packager(packager=packager)
3555
4093
 
4094
+ def store_api_gateway(
4095
+ self,
4096
+ api_gateway: mlrun.runtimes.nuclio.api_gateway.APIGateway,
4097
+ wait_for_readiness=True,
4098
+ max_wait_time=90,
4099
+ ) -> mlrun.runtimes.nuclio.api_gateway.APIGateway:
4100
+ """
4101
+ Creates or updates a Nuclio API Gateway using the provided APIGateway object.
4102
+
4103
+ This method interacts with the MLRun service to create/update a Nuclio API Gateway based on the provided
4104
+ APIGateway object. Once done, it returns the updated APIGateway object containing all fields propagated
4105
+ on MLRun and Nuclio sides, such as the 'host' attribute.
4106
+ Nuclio docs here: https://docs.nuclio.io/en/latest/reference/api-gateway/http.html
4107
+
4108
+ :param api_gateway: An instance of :py:class:`~mlrun.runtimes.nuclio.APIGateway` representing the
4109
+ configuration of the API Gateway to be created or updated.
4110
+ :param wait_for_readiness: (Optional) A boolean indicating whether to wait for the API Gateway to become
4111
+ ready after creation or update (default is True).
4112
+ :param max_wait_time: (Optional) Maximum time to wait for API Gateway readiness in seconds (default is 90s)
4113
+
4114
+
4115
+ :returns: An instance of :py:class:`~mlrun.runtimes.nuclio.APIGateway` with all fields populated based on the
4116
+ information retrieved from the Nuclio API
4117
+ """
4118
+
4119
+ api_gateway_json = mlrun.db.get_run_db().store_api_gateway(
4120
+ api_gateway=api_gateway,
4121
+ project=self.name,
4122
+ )
4123
+
4124
+ if api_gateway_json:
4125
+ # fill in all the fields in the user's api_gateway object
4126
+ api_gateway = mlrun.runtimes.nuclio.api_gateway.APIGateway.from_scheme(
4127
+ api_gateway_json
4128
+ )
4129
+ if wait_for_readiness:
4130
+ api_gateway.wait_for_readiness(max_wait_time=max_wait_time)
4131
+
4132
+ return api_gateway
4133
+
4134
+ def list_api_gateways(self) -> list[mlrun.runtimes.nuclio.api_gateway.APIGateway]:
4135
+ """
4136
+ Retrieves a list of Nuclio API gateways associated with the project.
4137
+
4138
+ :returns: List of :py:class:`~mlrun.runtimes.nuclio.api_gateway.APIGateway` objects representing
4139
+ the Nuclio API gateways associated with the project.
4140
+ """
4141
+ gateways_list = mlrun.db.get_run_db().list_api_gateways(self.name)
4142
+ return [
4143
+ mlrun.runtimes.nuclio.api_gateway.APIGateway.from_scheme(gateway_dict)
4144
+ for gateway_dict in gateways_list.api_gateways.values()
4145
+ ]
4146
+
4147
+ def get_api_gateway(
4148
+ self,
4149
+ name: str,
4150
+ ) -> mlrun.runtimes.nuclio.api_gateway.APIGateway:
4151
+ """
4152
+ Retrieves an API gateway by name instance.
4153
+
4154
+ :param name: The name of the API gateway to retrieve.
4155
+
4156
+ Returns:
4157
+ mlrun.runtimes.nuclio.APIGateway: An instance of APIGateway.
4158
+ """
4159
+
4160
+ gateway = mlrun.db.get_run_db().get_api_gateway(name=name, project=self.name)
4161
+ return mlrun.runtimes.nuclio.api_gateway.APIGateway.from_scheme(gateway)
4162
+
4163
+ def delete_api_gateway(
4164
+ self,
4165
+ name: str,
4166
+ ):
4167
+ """
4168
+ Deletes an API gateway by name.
4169
+
4170
+ :param name: The name of the API gateway to delete.
4171
+ """
4172
+
4173
+ mlrun.db.get_run_db().delete_api_gateway(name=name, project=self.name)
4174
+
4175
+ def store_alert_config(
4176
+ self, alert_data: AlertConfig, alert_name: typing.Optional[str] = None
4177
+ ) -> AlertConfig:
4178
+ """
4179
+ Create/modify an alert.
4180
+
4181
+ :param alert_data: The data of the alert.
4182
+ :param alert_name: The name of the alert.
4183
+ :return: the created/modified alert.
4184
+ """
4185
+ if not alert_data:
4186
+ raise mlrun.errors.MLRunInvalidArgumentError("Alert data must be provided")
4187
+
4188
+ db = mlrun.db.get_run_db(secrets=self._secrets)
4189
+ alert_name = alert_name or alert_data.name
4190
+ if alert_data.project is not None and alert_data.project != self.metadata.name:
4191
+ logger.warn(
4192
+ "Project in alert does not match project in operation",
4193
+ project=alert_data.project,
4194
+ )
4195
+ alert_data.project = self.metadata.name
4196
+ return db.store_alert_config(alert_name, alert_data, project=self.metadata.name)
4197
+
4198
+ def get_alert_config(self, alert_name: str) -> AlertConfig:
4199
+ """
4200
+ Retrieve an alert.
4201
+
4202
+ :param alert_name: The name of the alert to retrieve.
4203
+ :return: The alert object.
4204
+ """
4205
+ db = mlrun.db.get_run_db(secrets=self._secrets)
4206
+ return db.get_alert_config(alert_name, self.metadata.name)
4207
+
4208
+ def list_alerts_configs(self) -> list[AlertConfig]:
4209
+ """
4210
+ Retrieve list of alerts of a project.
4211
+
4212
+ :return: All the alerts objects of the project.
4213
+ """
4214
+ db = mlrun.db.get_run_db(secrets=self._secrets)
4215
+ return db.list_alerts_configs(self.metadata.name)
4216
+
4217
+ def delete_alert_config(
4218
+ self, alert_data: AlertConfig = None, alert_name: str = None
4219
+ ):
4220
+ """
4221
+ Delete an alert.
4222
+
4223
+ :param alert_data: The data of the alert.
4224
+ :param alert_name: The name of the alert to delete.
4225
+ """
4226
+ if alert_data is None and alert_name is None:
4227
+ raise ValueError(
4228
+ "At least one of alert_data or alert_name must be provided"
4229
+ )
4230
+ if alert_data and alert_name and alert_data.name != alert_name:
4231
+ raise ValueError("Alert_data name does not match the provided alert_name")
4232
+ db = mlrun.db.get_run_db(secrets=self._secrets)
4233
+ if alert_data:
4234
+ alert_name = alert_data.name
4235
+ db.delete_alert_config(alert_name, self.metadata.name)
4236
+
4237
+ def reset_alert_config(
4238
+ self, alert_data: AlertConfig = None, alert_name: str = None
4239
+ ):
4240
+ """
4241
+ Reset an alert.
4242
+
4243
+ :param alert_data: The data of the alert.
4244
+ :param alert_name: The name of the alert to reset.
4245
+ """
4246
+ if alert_data is None and alert_name is None:
4247
+ raise ValueError(
4248
+ "At least one of alert_data or alert_name must be provided"
4249
+ )
4250
+ if alert_data and alert_name and alert_data.name != alert_name:
4251
+ raise ValueError("Alert_data name does not match the provided alert_name")
4252
+ db = mlrun.db.get_run_db(secrets=self._secrets)
4253
+ if alert_data:
4254
+ alert_name = alert_data.name
4255
+ db.reset_alert_config(alert_name, self.metadata.name)
4256
+
4257
+ def get_alert_template(self, template_name: str) -> AlertTemplate:
4258
+ """
4259
+ Retrieve a specific alert template.
4260
+
4261
+ :param template_name: The name of the template to retrieve.
4262
+ :return: The template object.
4263
+ """
4264
+ db = mlrun.db.get_run_db(secrets=self._secrets)
4265
+ return db.get_alert_template(template_name)
4266
+
4267
+ def list_alert_templates(self) -> list[AlertTemplate]:
4268
+ """
4269
+ Retrieve list of all alert templates.
4270
+
4271
+ :return: All the alert template objects in the database.
4272
+ """
4273
+ db = mlrun.db.get_run_db(secrets=self._secrets)
4274
+ return db.list_alert_templates()
4275
+
3556
4276
  def _run_authenticated_git_action(
3557
4277
  self,
3558
4278
  action: Callable,
3559
4279
  remote: str,
3560
- args: list = [],
3561
- kwargs: dict = {},
4280
+ args: list = None,
4281
+ kwargs: dict = None,
3562
4282
  secrets: Union[SecretsStore, dict] = None,
3563
4283
  ):
3564
4284
  """Run an arbitrary Git routine while the remote is enriched with secrets
@@ -3578,6 +4298,8 @@ class MlrunProject(ModelObj):
3578
4298
  try:
3579
4299
  if is_remote_enriched:
3580
4300
  self.spec.repo.remotes[remote].set_url(enriched_remote, clean_remote)
4301
+ args = args or []
4302
+ kwargs = kwargs or {}
3581
4303
  action(*args, **kwargs)
3582
4304
  except RuntimeError as e:
3583
4305
  raise mlrun.errors.MLRunRuntimeError(
@@ -3630,6 +4352,97 @@ class MlrunProject(ModelObj):
3630
4352
  f"<project.spec.get_code_path()>/<{param_name}>)."
3631
4353
  )
3632
4354
 
4355
+ def _resolve_artifact_producer(
4356
+ self,
4357
+ artifact: typing.Union[str, Artifact],
4358
+ project_producer_tag: str = None,
4359
+ ) -> tuple[ArtifactProducer, bool]:
4360
+ """
4361
+ Resolve the artifact producer of the given artifact.
4362
+ If the artifact's producer is a run, the artifact is registered with the original producer.
4363
+ Otherwise, the artifact is registered with the current project as the producer.
4364
+
4365
+ :param artifact: The artifact to resolve its producer.
4366
+ :param project_producer_tag: The tag to use for the project as the producer. If not provided, a tag will be
4367
+ generated for the project.
4368
+ :return: A tuple of the resolved producer and whether it is retained or not.
4369
+ """
4370
+
4371
+ if not isinstance(artifact, str) and artifact.spec.producer:
4372
+ # if the artifact was imported from a yaml file, the producer can be a dict
4373
+ if isinstance(artifact.spec.producer, ArtifactProducer):
4374
+ producer_dict = artifact.spec.producer.get_meta()
4375
+ else:
4376
+ producer_dict = artifact.spec.producer
4377
+
4378
+ producer_tag = producer_dict.get("tag", None)
4379
+ producer_project = producer_dict.get("project", None)
4380
+ if not producer_tag or not producer_project:
4381
+ # try resolving the producer tag from the uri
4382
+ producer_uri = artifact.spec.producer.get("uri", "")
4383
+ producer_project, producer_tag, _ = ArtifactProducer.parse_uri(
4384
+ producer_uri
4385
+ )
4386
+
4387
+ if producer_dict.get("kind", "") == "run":
4388
+ return ArtifactProducer(
4389
+ name=producer_dict.get("name", ""),
4390
+ kind=producer_dict.get("kind", ""),
4391
+ project=producer_project,
4392
+ tag=producer_tag,
4393
+ owner=producer_dict.get("owner", ""),
4394
+ ), True
4395
+
4396
+ # do not retain the artifact's producer, replace it with the project as the producer
4397
+ project_producer_tag = project_producer_tag or self._get_project_tag()
4398
+ return ArtifactProducer(
4399
+ kind="project",
4400
+ name=self.metadata.name,
4401
+ project=self.metadata.name,
4402
+ tag=project_producer_tag,
4403
+ owner=self._resolve_artifact_owner(),
4404
+ ), False
4405
+
4406
+ def _resolve_existing_artifact(
4407
+ self,
4408
+ item: typing.Union[str, Artifact],
4409
+ tag: str = None,
4410
+ ) -> typing.Optional[Artifact]:
4411
+ """
4412
+ Check if there is and existing artifact with the given item and tag.
4413
+ If there is, return the existing artifact. Otherwise, return None.
4414
+
4415
+ :param item: The item (or key) to check if there is an existing artifact for.
4416
+ :param tag: The tag to check if there is an existing artifact for.
4417
+ :return: The existing artifact if there is one, otherwise None.
4418
+ """
4419
+ try:
4420
+ if isinstance(item, str):
4421
+ existing_artifact = self.get_artifact(key=item, tag=tag)
4422
+ else:
4423
+ existing_artifact = self.get_artifact(
4424
+ key=item.key,
4425
+ tag=item.tag,
4426
+ iter=item.iter,
4427
+ tree=item.tree,
4428
+ )
4429
+ if existing_artifact is not None:
4430
+ return existing_artifact.from_dict(existing_artifact)
4431
+ except mlrun.errors.MLRunNotFoundError:
4432
+ logger.debug(
4433
+ "No existing artifact was found",
4434
+ key=item if isinstance(item, str) else item.key,
4435
+ tag=tag if isinstance(item, str) else item.tag,
4436
+ tree=None if isinstance(item, str) else item.tree,
4437
+ )
4438
+ return None
4439
+
4440
+ def _get_project_tag(self):
4441
+ return self._get_hexsha() or str(uuid.uuid4())
4442
+
4443
+ def _resolve_artifact_owner(self):
4444
+ return os.getenv("V3IO_USERNAME") or self.spec.owner
4445
+
3633
4446
 
3634
4447
  def _set_as_current_default_project(project: MlrunProject):
3635
4448
  mlrun.mlconf.default_project = project.metadata.name
@@ -3640,7 +4453,7 @@ def _init_function_from_dict(
3640
4453
  f: dict,
3641
4454
  project: MlrunProject,
3642
4455
  name: typing.Optional[str] = None,
3643
- ) -> typing.Tuple[str, mlrun.runtimes.BaseRuntime]:
4456
+ ) -> tuple[str, mlrun.runtimes.BaseRuntime]:
3644
4457
  name = name or f.get("name", "")
3645
4458
  url = f.get("url", "")
3646
4459
  kind = f.get("kind", "")
@@ -3652,10 +4465,6 @@ def _init_function_from_dict(
3652
4465
  tag = f.get("tag", None)
3653
4466
 
3654
4467
  has_module = _has_module(handler, kind)
3655
- if not url and "spec" not in f and not has_module:
3656
- # function must point to a file or a module or have a spec
3657
- raise ValueError("Function missing a url or a spec or a module")
3658
-
3659
4468
  relative_url = url
3660
4469
  url, in_context = project.get_item_absolute_path(url)
3661
4470
 
@@ -3685,18 +4494,17 @@ def _init_function_from_dict(
3685
4494
  )
3686
4495
 
3687
4496
  elif url.endswith(".py"):
3688
- # when load_source_on_run is used we allow not providing image as code will be loaded pre-run. ML-4994
3689
- if (
3690
- not image
3691
- and not project.default_image
3692
- and kind != "local"
3693
- and not project.spec.load_source_on_run
3694
- ):
3695
- raise ValueError(
3696
- "image must be provided with py code files which do not "
3697
- "run on 'local' engine kind"
3698
- )
3699
4497
  if in_context and with_repo:
4498
+ # when load_source_on_run is used we allow not providing image as code will be loaded pre-run. ML-4994
4499
+ if (
4500
+ not image
4501
+ and not project.default_image
4502
+ and kind != "local"
4503
+ and not project.spec.load_source_on_run
4504
+ ):
4505
+ raise ValueError(
4506
+ "image must be provided with py code files which do not run on 'local' engine kind"
4507
+ )
3700
4508
  func = new_function(
3701
4509
  name,
3702
4510
  command=relative_url,
@@ -3715,6 +4523,17 @@ def _init_function_from_dict(
3715
4523
  tag=tag,
3716
4524
  )
3717
4525
 
4526
+ elif kind in mlrun.runtimes.RuntimeKinds.nuclio_runtimes():
4527
+ func = new_function(
4528
+ name,
4529
+ image=image,
4530
+ kind=kind,
4531
+ handler=handler,
4532
+ tag=tag,
4533
+ )
4534
+ if image and kind != mlrun.runtimes.RuntimeKinds.application:
4535
+ logger.info("Function code not specified, setting entry point to image")
4536
+ func.from_image(image)
3718
4537
  else:
3719
4538
  raise ValueError(f"Unsupported function url:handler {url}:{handler} or no spec")
3720
4539
 
@@ -3735,7 +4554,7 @@ def _init_function_from_obj(
3735
4554
  func: mlrun.runtimes.BaseRuntime,
3736
4555
  project: MlrunProject,
3737
4556
  name: typing.Optional[str] = None,
3738
- ) -> typing.Tuple[str, mlrun.runtimes.BaseRuntime]:
4557
+ ) -> tuple[str, mlrun.runtimes.BaseRuntime]:
3739
4558
  build = func.spec.build
3740
4559
  if project.spec.origin_url:
3741
4560
  origin = project.spec.origin_url
@@ -3760,9 +4579,17 @@ def _init_function_from_obj(
3760
4579
  def _has_module(handler, kind):
3761
4580
  if not handler:
3762
4581
  return False
3763
- return (
3764
- kind in mlrun.runtimes.RuntimeKinds.nuclio_runtimes() and ":" in handler
3765
- ) or "." in handler
4582
+
4583
+ if (
4584
+ kind in mlrun.runtimes.RuntimeKinds.pure_nuclio_deployed_runtimes()
4585
+ and ":" in handler
4586
+ ):
4587
+ return True
4588
+
4589
+ if "." in handler:
4590
+ return True
4591
+
4592
+ return False
3766
4593
 
3767
4594
 
3768
4595
  def _is_imported_artifact(artifact):