mlrun 1.7.0rc5__py3-none-any.whl → 1.7.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of mlrun might be problematic. Click here for more details.

Files changed (234) hide show
  1. mlrun/__init__.py +11 -1
  2. mlrun/__main__.py +39 -121
  3. mlrun/{datastore/helpers.py → alerts/__init__.py} +2 -5
  4. mlrun/alerts/alert.py +248 -0
  5. mlrun/api/schemas/__init__.py +4 -3
  6. mlrun/artifacts/__init__.py +8 -3
  7. mlrun/artifacts/base.py +39 -254
  8. mlrun/artifacts/dataset.py +9 -190
  9. mlrun/artifacts/manager.py +73 -46
  10. mlrun/artifacts/model.py +30 -158
  11. mlrun/artifacts/plots.py +23 -380
  12. mlrun/common/constants.py +73 -2
  13. mlrun/common/db/sql_session.py +3 -2
  14. mlrun/common/formatters/__init__.py +21 -0
  15. mlrun/common/formatters/artifact.py +46 -0
  16. mlrun/common/formatters/base.py +113 -0
  17. mlrun/common/formatters/feature_set.py +44 -0
  18. mlrun/common/formatters/function.py +46 -0
  19. mlrun/common/formatters/pipeline.py +53 -0
  20. mlrun/common/formatters/project.py +51 -0
  21. mlrun/common/formatters/run.py +29 -0
  22. mlrun/common/helpers.py +11 -1
  23. mlrun/{runtimes → common/runtimes}/constants.py +32 -4
  24. mlrun/common/schemas/__init__.py +21 -4
  25. mlrun/common/schemas/alert.py +202 -0
  26. mlrun/common/schemas/api_gateway.py +113 -2
  27. mlrun/common/schemas/artifact.py +28 -1
  28. mlrun/common/schemas/auth.py +11 -0
  29. mlrun/common/schemas/client_spec.py +2 -1
  30. mlrun/common/schemas/common.py +7 -4
  31. mlrun/common/schemas/constants.py +3 -0
  32. mlrun/common/schemas/feature_store.py +58 -28
  33. mlrun/common/schemas/frontend_spec.py +8 -0
  34. mlrun/common/schemas/function.py +11 -0
  35. mlrun/common/schemas/hub.py +7 -9
  36. mlrun/common/schemas/model_monitoring/__init__.py +21 -4
  37. mlrun/common/schemas/model_monitoring/constants.py +136 -42
  38. mlrun/common/schemas/model_monitoring/grafana.py +9 -5
  39. mlrun/common/schemas/model_monitoring/model_endpoints.py +89 -41
  40. mlrun/common/schemas/notification.py +69 -12
  41. mlrun/{runtimes/mpijob/v1alpha1.py → common/schemas/pagination.py} +10 -13
  42. mlrun/common/schemas/pipeline.py +7 -0
  43. mlrun/common/schemas/project.py +67 -16
  44. mlrun/common/schemas/runs.py +17 -0
  45. mlrun/common/schemas/schedule.py +1 -1
  46. mlrun/common/schemas/workflow.py +10 -2
  47. mlrun/common/types.py +14 -1
  48. mlrun/config.py +224 -58
  49. mlrun/data_types/data_types.py +11 -1
  50. mlrun/data_types/spark.py +5 -4
  51. mlrun/data_types/to_pandas.py +75 -34
  52. mlrun/datastore/__init__.py +8 -10
  53. mlrun/datastore/alibaba_oss.py +131 -0
  54. mlrun/datastore/azure_blob.py +131 -43
  55. mlrun/datastore/base.py +107 -47
  56. mlrun/datastore/datastore.py +17 -7
  57. mlrun/datastore/datastore_profile.py +91 -7
  58. mlrun/datastore/dbfs_store.py +3 -7
  59. mlrun/datastore/filestore.py +1 -3
  60. mlrun/datastore/google_cloud_storage.py +92 -32
  61. mlrun/datastore/hdfs.py +5 -0
  62. mlrun/datastore/inmem.py +6 -3
  63. mlrun/datastore/redis.py +3 -2
  64. mlrun/datastore/s3.py +30 -12
  65. mlrun/datastore/snowflake_utils.py +45 -0
  66. mlrun/datastore/sources.py +274 -59
  67. mlrun/datastore/spark_utils.py +30 -0
  68. mlrun/datastore/store_resources.py +9 -7
  69. mlrun/datastore/storeytargets.py +151 -0
  70. mlrun/datastore/targets.py +374 -102
  71. mlrun/datastore/utils.py +68 -5
  72. mlrun/datastore/v3io.py +28 -50
  73. mlrun/db/auth_utils.py +152 -0
  74. mlrun/db/base.py +231 -22
  75. mlrun/db/factory.py +1 -4
  76. mlrun/db/httpdb.py +864 -228
  77. mlrun/db/nopdb.py +268 -16
  78. mlrun/errors.py +35 -5
  79. mlrun/execution.py +111 -38
  80. mlrun/feature_store/__init__.py +0 -2
  81. mlrun/feature_store/api.py +46 -53
  82. mlrun/feature_store/common.py +6 -11
  83. mlrun/feature_store/feature_set.py +48 -23
  84. mlrun/feature_store/feature_vector.py +13 -2
  85. mlrun/feature_store/ingestion.py +7 -6
  86. mlrun/feature_store/retrieval/base.py +9 -4
  87. mlrun/feature_store/retrieval/dask_merger.py +2 -0
  88. mlrun/feature_store/retrieval/job.py +13 -4
  89. mlrun/feature_store/retrieval/local_merger.py +2 -0
  90. mlrun/feature_store/retrieval/spark_merger.py +24 -32
  91. mlrun/feature_store/steps.py +38 -19
  92. mlrun/features.py +6 -14
  93. mlrun/frameworks/_common/plan.py +3 -3
  94. mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +7 -12
  95. mlrun/frameworks/_ml_common/plan.py +1 -1
  96. mlrun/frameworks/auto_mlrun/auto_mlrun.py +2 -2
  97. mlrun/frameworks/lgbm/__init__.py +1 -1
  98. mlrun/frameworks/lgbm/callbacks/callback.py +2 -4
  99. mlrun/frameworks/lgbm/model_handler.py +1 -1
  100. mlrun/frameworks/parallel_coordinates.py +4 -4
  101. mlrun/frameworks/pytorch/__init__.py +2 -2
  102. mlrun/frameworks/sklearn/__init__.py +1 -1
  103. mlrun/frameworks/sklearn/mlrun_interface.py +13 -3
  104. mlrun/frameworks/tf_keras/__init__.py +5 -2
  105. mlrun/frameworks/tf_keras/callbacks/logging_callback.py +1 -1
  106. mlrun/frameworks/tf_keras/mlrun_interface.py +2 -2
  107. mlrun/frameworks/xgboost/__init__.py +1 -1
  108. mlrun/k8s_utils.py +57 -12
  109. mlrun/launcher/__init__.py +1 -1
  110. mlrun/launcher/base.py +6 -5
  111. mlrun/launcher/client.py +13 -11
  112. mlrun/launcher/factory.py +1 -1
  113. mlrun/launcher/local.py +15 -5
  114. mlrun/launcher/remote.py +10 -3
  115. mlrun/lists.py +6 -2
  116. mlrun/model.py +297 -48
  117. mlrun/model_monitoring/__init__.py +1 -1
  118. mlrun/model_monitoring/api.py +152 -357
  119. mlrun/model_monitoring/applications/__init__.py +10 -0
  120. mlrun/model_monitoring/applications/_application_steps.py +190 -0
  121. mlrun/model_monitoring/applications/base.py +108 -0
  122. mlrun/model_monitoring/applications/context.py +341 -0
  123. mlrun/model_monitoring/{evidently_application.py → applications/evidently_base.py} +27 -22
  124. mlrun/model_monitoring/applications/histogram_data_drift.py +227 -91
  125. mlrun/model_monitoring/applications/results.py +99 -0
  126. mlrun/model_monitoring/controller.py +130 -303
  127. mlrun/model_monitoring/{stores/models/sqlite.py → db/__init__.py} +5 -10
  128. mlrun/model_monitoring/db/stores/__init__.py +136 -0
  129. mlrun/model_monitoring/db/stores/base/__init__.py +15 -0
  130. mlrun/model_monitoring/db/stores/base/store.py +213 -0
  131. mlrun/model_monitoring/db/stores/sqldb/__init__.py +13 -0
  132. mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +71 -0
  133. mlrun/model_monitoring/db/stores/sqldb/models/base.py +190 -0
  134. mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +103 -0
  135. mlrun/model_monitoring/{stores/models/mysql.py → db/stores/sqldb/models/sqlite.py} +19 -13
  136. mlrun/model_monitoring/db/stores/sqldb/sql_store.py +659 -0
  137. mlrun/model_monitoring/db/stores/v3io_kv/__init__.py +13 -0
  138. mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +726 -0
  139. mlrun/model_monitoring/db/tsdb/__init__.py +105 -0
  140. mlrun/model_monitoring/db/tsdb/base.py +448 -0
  141. mlrun/model_monitoring/db/tsdb/helpers.py +30 -0
  142. mlrun/model_monitoring/db/tsdb/tdengine/__init__.py +15 -0
  143. mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +298 -0
  144. mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +42 -0
  145. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +522 -0
  146. mlrun/model_monitoring/db/tsdb/v3io/__init__.py +15 -0
  147. mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +158 -0
  148. mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +849 -0
  149. mlrun/model_monitoring/features_drift_table.py +34 -22
  150. mlrun/model_monitoring/helpers.py +177 -39
  151. mlrun/model_monitoring/model_endpoint.py +3 -2
  152. mlrun/model_monitoring/stream_processing.py +165 -398
  153. mlrun/model_monitoring/tracking_policy.py +7 -1
  154. mlrun/model_monitoring/writer.py +161 -125
  155. mlrun/package/packagers/default_packager.py +2 -2
  156. mlrun/package/packagers_manager.py +1 -0
  157. mlrun/package/utils/_formatter.py +2 -2
  158. mlrun/platforms/__init__.py +11 -10
  159. mlrun/platforms/iguazio.py +67 -228
  160. mlrun/projects/__init__.py +6 -1
  161. mlrun/projects/operations.py +47 -20
  162. mlrun/projects/pipelines.py +396 -249
  163. mlrun/projects/project.py +1125 -414
  164. mlrun/render.py +28 -22
  165. mlrun/run.py +207 -180
  166. mlrun/runtimes/__init__.py +76 -11
  167. mlrun/runtimes/base.py +40 -14
  168. mlrun/runtimes/daskjob.py +9 -2
  169. mlrun/runtimes/databricks_job/databricks_runtime.py +1 -0
  170. mlrun/runtimes/databricks_job/databricks_wrapper.py +1 -1
  171. mlrun/runtimes/funcdoc.py +1 -29
  172. mlrun/runtimes/kubejob.py +34 -128
  173. mlrun/runtimes/local.py +39 -10
  174. mlrun/runtimes/mpijob/__init__.py +0 -20
  175. mlrun/runtimes/mpijob/abstract.py +8 -8
  176. mlrun/runtimes/mpijob/v1.py +1 -1
  177. mlrun/runtimes/nuclio/api_gateway.py +646 -177
  178. mlrun/runtimes/nuclio/application/__init__.py +15 -0
  179. mlrun/runtimes/nuclio/application/application.py +758 -0
  180. mlrun/runtimes/nuclio/application/reverse_proxy.go +95 -0
  181. mlrun/runtimes/nuclio/function.py +188 -68
  182. mlrun/runtimes/nuclio/serving.py +57 -60
  183. mlrun/runtimes/pod.py +191 -58
  184. mlrun/runtimes/remotesparkjob.py +11 -8
  185. mlrun/runtimes/sparkjob/spark3job.py +17 -18
  186. mlrun/runtimes/utils.py +40 -73
  187. mlrun/secrets.py +6 -2
  188. mlrun/serving/__init__.py +8 -1
  189. mlrun/serving/remote.py +2 -3
  190. mlrun/serving/routers.py +89 -64
  191. mlrun/serving/server.py +54 -26
  192. mlrun/serving/states.py +187 -56
  193. mlrun/serving/utils.py +19 -11
  194. mlrun/serving/v2_serving.py +136 -63
  195. mlrun/track/tracker.py +2 -1
  196. mlrun/track/trackers/mlflow_tracker.py +5 -0
  197. mlrun/utils/async_http.py +26 -6
  198. mlrun/utils/db.py +18 -0
  199. mlrun/utils/helpers.py +375 -105
  200. mlrun/utils/http.py +2 -2
  201. mlrun/utils/logger.py +75 -9
  202. mlrun/utils/notifications/notification/__init__.py +14 -10
  203. mlrun/utils/notifications/notification/base.py +48 -0
  204. mlrun/utils/notifications/notification/console.py +2 -0
  205. mlrun/utils/notifications/notification/git.py +24 -1
  206. mlrun/utils/notifications/notification/ipython.py +2 -0
  207. mlrun/utils/notifications/notification/slack.py +96 -21
  208. mlrun/utils/notifications/notification/webhook.py +63 -2
  209. mlrun/utils/notifications/notification_pusher.py +146 -16
  210. mlrun/utils/regex.py +9 -0
  211. mlrun/utils/retryer.py +3 -2
  212. mlrun/utils/v3io_clients.py +2 -3
  213. mlrun/utils/version/version.json +2 -2
  214. mlrun-1.7.2.dist-info/METADATA +390 -0
  215. mlrun-1.7.2.dist-info/RECORD +351 -0
  216. {mlrun-1.7.0rc5.dist-info → mlrun-1.7.2.dist-info}/WHEEL +1 -1
  217. mlrun/feature_store/retrieval/conversion.py +0 -271
  218. mlrun/kfpops.py +0 -868
  219. mlrun/model_monitoring/application.py +0 -310
  220. mlrun/model_monitoring/batch.py +0 -974
  221. mlrun/model_monitoring/controller_handler.py +0 -37
  222. mlrun/model_monitoring/prometheus.py +0 -216
  223. mlrun/model_monitoring/stores/__init__.py +0 -111
  224. mlrun/model_monitoring/stores/kv_model_endpoint_store.py +0 -574
  225. mlrun/model_monitoring/stores/model_endpoint_store.py +0 -145
  226. mlrun/model_monitoring/stores/models/__init__.py +0 -27
  227. mlrun/model_monitoring/stores/models/base.py +0 -84
  228. mlrun/model_monitoring/stores/sql_model_endpoint_store.py +0 -382
  229. mlrun/platforms/other.py +0 -305
  230. mlrun-1.7.0rc5.dist-info/METADATA +0 -269
  231. mlrun-1.7.0rc5.dist-info/RECORD +0 -323
  232. {mlrun-1.7.0rc5.dist-info → mlrun-1.7.2.dist-info}/LICENSE +0 -0
  233. {mlrun-1.7.0rc5.dist-info → mlrun-1.7.2.dist-info}/entry_points.txt +0 -0
  234. {mlrun-1.7.0rc5.dist-info → mlrun-1.7.2.dist-info}/top_level.txt +0 -0
mlrun/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,27 +26,39 @@ 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
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
 
43
+ import mlrun.common.formatters
38
44
  import mlrun.common.helpers
45
+ import mlrun.common.runtimes.constants
46
+ import mlrun.common.schemas.artifact
39
47
  import mlrun.common.schemas.model_monitoring.constants as mm_constants
40
48
  import mlrun.db
41
49
  import mlrun.errors
42
50
  import mlrun.k8s_utils
51
+ import mlrun.lists
52
+ import mlrun.model_monitoring.applications as mm_app
43
53
  import mlrun.runtimes
44
54
  import mlrun.runtimes.nuclio.api_gateway
45
55
  import mlrun.runtimes.pod
46
56
  import mlrun.runtimes.utils
57
+ import mlrun.serving
58
+ import mlrun.utils
47
59
  import mlrun.utils.regex
60
+ from mlrun.alerts.alert import AlertConfig
61
+ from mlrun.common.schemas.alert import AlertTemplate
48
62
  from mlrun.datastore.datastore_profile import DatastoreProfile, DatastoreProfile2Json
49
63
  from mlrun.runtimes.nuclio.function import RemoteRuntime
50
64
 
@@ -53,20 +67,9 @@ from ..artifacts.manager import ArtifactManager, dict_to_artifact, extend_artifa
53
67
  from ..datastore import store_manager
54
68
  from ..features import Feature
55
69
  from ..model import EntrypointParam, ImageBuilder, ModelObj
56
- from ..model_monitoring.application import (
57
- ModelMonitoringApplicationBase,
58
- PushToMonitoringWriter,
59
- )
60
70
  from ..run import code_to_function, get_object, import_function, new_function
61
71
  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
- )
72
+ from ..utils import is_jupyter, is_relative_path, is_yaml_path, logger, update_in
70
73
  from ..utils.clones import (
71
74
  add_credentials_git_remote_url,
72
75
  clone_git,
@@ -74,7 +77,10 @@ from ..utils.clones import (
74
77
  clone_zip,
75
78
  get_repo_url,
76
79
  )
77
- from ..utils.helpers import ensure_git_branch, resolve_git_reference_from_source
80
+ from ..utils.helpers import (
81
+ ensure_git_branch,
82
+ resolve_git_reference_from_source,
83
+ )
78
84
  from ..utils.notifications import CustomNotificationPusher, NotificationTypes
79
85
  from .operations import (
80
86
  BuildStatus,
@@ -128,6 +134,7 @@ def new_project(
128
134
  save: bool = True,
129
135
  overwrite: bool = False,
130
136
  parameters: dict = None,
137
+ default_function_node_selector: dict = None,
131
138
  ) -> "MlrunProject":
132
139
  """Create a new MLRun project, optionally load it from a yaml/zip/git template
133
140
 
@@ -138,11 +145,15 @@ def new_project(
138
145
  example::
139
146
 
140
147
  # 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")
148
+ project = mlrun.new_project(
149
+ "myproj", "./", init_git=True, description="my new project"
150
+ )
151
+ project.set_function(
152
+ "prep_data.py", "prep-data", image="mlrun/mlrun", handler="prep_data"
153
+ )
154
+ project.set_function("hub://auto-trainer", "train")
155
+ project.set_artifact("data", Artifact(target_path=data_url))
156
+ project.set_workflow("main", "./myflow.py")
146
157
  project.save()
147
158
 
148
159
  # run the "main" workflow (watch=True to wait for run completion)
@@ -152,19 +163,25 @@ def new_project(
152
163
 
153
164
  # create a new project from a zip template (can also use yaml/git templates)
154
165
  # 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")
166
+ project = mlrun.new_project(
167
+ "myproj",
168
+ "./",
169
+ init_git=True,
170
+ remote="git://github.com/mlrun/project-demo.git",
171
+ from_template="http://mysite/proj.zip",
172
+ )
158
173
  project.run("main", watch=True)
159
174
 
160
175
 
161
176
  example using project_setup.py to init the project objects::
162
177
 
163
178
  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")
179
+ project.set_function(
180
+ "prep_data.py", "prep-data", image="mlrun/mlrun", handler="prep_data"
181
+ )
182
+ project.set_function("hub://auto-trainer", "train")
183
+ project.set_artifact("data", Artifact(target_path=data_url))
184
+ project.set_workflow("main", "./myflow.py")
168
185
  return project
169
186
 
170
187
 
@@ -181,6 +198,7 @@ def new_project(
181
198
  :param overwrite: overwrite project using 'cascade' deletion strategy (deletes project resources)
182
199
  if project with name exists
183
200
  :param parameters: key/value pairs to add to the project.spec.params
201
+ :param default_function_node_selector: defines the default node selector for scheduling functions within the project
184
202
 
185
203
  :returns: project object
186
204
  """
@@ -193,14 +211,16 @@ def new_project(
193
211
  "Unsupported option, cannot use subpath argument with project templates"
194
212
  )
195
213
  if from_template.endswith(".yaml"):
196
- project = _load_project_file(from_template, name, secrets)
214
+ project = _load_project_file(
215
+ from_template, name, secrets, allow_cross_project=True
216
+ )
197
217
  elif from_template.startswith("git://"):
198
218
  clone_git(from_template, context, secrets, clone=True)
199
219
  shutil.rmtree(path.join(context, ".git"))
200
- project = _load_project_dir(context, name)
220
+ project = _load_project_dir(context, name, allow_cross_project=True)
201
221
  elif from_template.endswith(".zip"):
202
222
  clone_zip(from_template, context, secrets)
203
- project = _load_project_dir(context, name)
223
+ project = _load_project_dir(context, name, allow_cross_project=True)
204
224
  else:
205
225
  raise ValueError("template must be a path to .yaml or .zip file")
206
226
  project.metadata.name = name
@@ -227,6 +247,10 @@ def new_project(
227
247
  project.spec.origin_url = url
228
248
  if description:
229
249
  project.spec.description = description
250
+
251
+ if default_function_node_selector:
252
+ project.spec.default_function_node_selector = default_function_node_selector
253
+
230
254
  if parameters:
231
255
  # Enable setting project parameters at load time, can be used to customize the project_setup
232
256
  for key, val in parameters.items():
@@ -277,6 +301,7 @@ def load_project(
277
301
  save: bool = True,
278
302
  sync_functions: bool = False,
279
303
  parameters: dict = None,
304
+ allow_cross_project: bool = None,
280
305
  ) -> "MlrunProject":
281
306
  """Load an MLRun project from git or tar or dir
282
307
 
@@ -290,7 +315,7 @@ def load_project(
290
315
  # When using git as the url source the context directory must be an empty or
291
316
  # non-existent folder as the git repo will be cloned there
292
317
  project = load_project("./demo_proj", "git://github.com/mlrun/project-demo.git")
293
- project.run("main", arguments={'data': data_url})
318
+ project.run("main", arguments={"data": data_url})
294
319
 
295
320
 
296
321
  project_setup.py example::
@@ -323,6 +348,8 @@ def load_project(
323
348
  :param save: whether to save the created project and artifact in the DB
324
349
  :param sync_functions: sync the project's functions into the project object (will be saved to the DB if save=True)
325
350
  :param parameters: key/value pairs to add to the project.spec.params
351
+ :param allow_cross_project: if True, override the loaded project name. This flag ensures awareness of
352
+ loading an existing project yaml as a baseline for a new project with a different name
326
353
 
327
354
  :returns: project object
328
355
  """
@@ -338,7 +365,7 @@ def load_project(
338
365
  if url:
339
366
  url = str(url) # to support path objects
340
367
  if is_yaml_path(url):
341
- project = _load_project_file(url, name, secrets)
368
+ project = _load_project_file(url, name, secrets, allow_cross_project)
342
369
  project.spec.context = context
343
370
  elif url.startswith("git://"):
344
371
  url, repo = clone_git(url, context, secrets, clone)
@@ -365,7 +392,7 @@ def load_project(
365
392
  repo, url = init_repo(context, url, init_git)
366
393
 
367
394
  if not project:
368
- project = _load_project_dir(context, name, subpath)
395
+ project = _load_project_dir(context, name, subpath, allow_cross_project)
369
396
 
370
397
  if not project.metadata.name:
371
398
  raise ValueError("Project name must be specified")
@@ -419,6 +446,7 @@ def get_or_create_project(
419
446
  from_template: str = None,
420
447
  save: bool = True,
421
448
  parameters: dict = None,
449
+ allow_cross_project: bool = None,
422
450
  ) -> "MlrunProject":
423
451
  """Load a project from MLRun DB, or create/import if it does not exist
424
452
 
@@ -429,9 +457,11 @@ def get_or_create_project(
429
457
  Usage example::
430
458
 
431
459
  # 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")
460
+ project = get_or_create_project(
461
+ "myproj", "./", "git://github.com/mlrun/demo-xgb-project.git"
462
+ )
433
463
  project.pull("development") # pull the latest code from git
434
- project.run("main", arguments={'data': data_url}) # run the workflow "main"
464
+ project.run("main", arguments={"data": data_url}) # run the workflow "main"
435
465
 
436
466
 
437
467
  project_setup.py example::
@@ -461,12 +491,12 @@ def get_or_create_project(
461
491
  :param from_template: path to project YAML file that will be used as from_template (for new projects)
462
492
  :param save: whether to save the created project in the DB
463
493
  :param parameters: key/value pairs to add to the project.spec.params
494
+ :param allow_cross_project: if True, override the loaded project name. This flag ensures awareness of
495
+ loading an existing project yaml as a baseline for a new project with a different name
464
496
 
465
497
  :returns: project object
466
498
  """
467
499
  context = context or "./"
468
- spec_path = path.join(context, subpath or "", "project.yaml")
469
- load_from_path = url or path.isfile(spec_path)
470
500
  try:
471
501
  # load project from the DB.
472
502
  # use `name` as `url` as we load the project from the DB
@@ -482,17 +512,26 @@ def get_or_create_project(
482
512
  # only loading project from db so no need to save it
483
513
  save=False,
484
514
  parameters=parameters,
515
+ allow_cross_project=allow_cross_project,
485
516
  )
486
- logger.info("Project loaded successfully", project_name=name)
517
+ logger.info("Project loaded successfully", project_name=project.name)
487
518
  return project
488
-
489
519
  except mlrun.errors.MLRunNotFoundError:
490
- logger.debug("Project not found in db", project_name=name)
520
+ logger.debug(
521
+ "Project not found in db", project_name=name, user_project=user_project
522
+ )
491
523
 
524
+ spec_path = path.join(context, subpath or "", "project.yaml")
525
+ load_from_path = url or path.isfile(spec_path)
492
526
  # do not nest under "try" or else the exceptions raised below will be logged along with the "not found" message
493
527
  if load_from_path:
494
528
  # loads a project from archive or local project.yaml
495
- logger.info("Loading project from path", project_name=name, path=url or context)
529
+ logger.info(
530
+ "Loading project from path",
531
+ project_name=name,
532
+ user_project=user_project,
533
+ path=url or context,
534
+ )
496
535
  project = load_project(
497
536
  context,
498
537
  url,
@@ -504,11 +543,12 @@ def get_or_create_project(
504
543
  user_project=user_project,
505
544
  save=save,
506
545
  parameters=parameters,
546
+ allow_cross_project=allow_cross_project,
507
547
  )
508
548
 
509
549
  logger.info(
510
550
  "Project loaded successfully",
511
- project_name=name,
551
+ project_name=project.name,
512
552
  path=url or context,
513
553
  stored_in_db=save,
514
554
  )
@@ -526,7 +566,9 @@ def get_or_create_project(
526
566
  save=save,
527
567
  parameters=parameters,
528
568
  )
529
- logger.info("Project created successfully", project_name=name, stored_in_db=save)
569
+ logger.info(
570
+ "Project created successfully", project_name=project.name, stored_in_db=save
571
+ )
530
572
  return project
531
573
 
532
574
 
@@ -564,6 +606,10 @@ def _run_project_setup(
564
606
  if hasattr(mod, "setup"):
565
607
  try:
566
608
  project = getattr(mod, "setup")(project)
609
+ if not project or not isinstance(project, mlrun.projects.MlrunProject):
610
+ raise ValueError(
611
+ "MLRun project_setup:setup() must return a project object"
612
+ )
567
613
  except Exception as exc:
568
614
  logger.error(
569
615
  "Failed to run project_setup script",
@@ -574,11 +620,13 @@ def _run_project_setup(
574
620
  if save:
575
621
  project.save()
576
622
  else:
577
- logger.warn("skipping setup, setup() handler was not found in project_setup.py")
623
+ logger.warn(
624
+ f"skipping setup, setup() handler was not found in {path.basename(setup_file_path)}"
625
+ )
578
626
  return project
579
627
 
580
628
 
581
- def _load_project_dir(context, name="", subpath=""):
629
+ def _load_project_dir(context, name="", subpath="", allow_cross_project=None):
582
630
  subpath_str = subpath or ""
583
631
 
584
632
  # support both .yaml and .yml file extensions
@@ -592,7 +640,7 @@ def _load_project_dir(context, name="", subpath=""):
592
640
  with open(project_file_path) as fp:
593
641
  data = fp.read()
594
642
  struct = yaml.load(data, Loader=yaml.FullLoader)
595
- project = _project_instance_from_struct(struct, name)
643
+ project = _project_instance_from_struct(struct, name, allow_cross_project)
596
644
  project.spec.context = context
597
645
  elif function_files := glob.glob(function_file_path):
598
646
  function_path = function_files[0]
@@ -662,22 +710,45 @@ def _load_project_from_db(url, secrets, user_project=False):
662
710
 
663
711
  def _delete_project_from_db(project_name, secrets, deletion_strategy):
664
712
  db = mlrun.db.get_run_db(secrets=secrets)
665
- return db.delete_project(project_name, deletion_strategy=deletion_strategy)
713
+ db.delete_project(project_name, deletion_strategy=deletion_strategy)
666
714
 
667
715
 
668
- def _load_project_file(url, name="", secrets=None):
716
+ def _load_project_file(url, name="", secrets=None, allow_cross_project=None):
669
717
  try:
670
718
  obj = get_object(url, secrets)
671
719
  except FileNotFoundError as exc:
672
720
  raise FileNotFoundError(f"cant find project file at {url}") from exc
673
721
  struct = yaml.load(obj, Loader=yaml.FullLoader)
674
- return _project_instance_from_struct(struct, name)
675
-
722
+ return _project_instance_from_struct(struct, name, allow_cross_project)
723
+
724
+
725
+ def _project_instance_from_struct(struct, name, allow_cross_project):
726
+ name_from_struct = struct.get("metadata", {}).get("name", "")
727
+ if name and name_from_struct and name_from_struct != name:
728
+ error_message = (
729
+ f"Project name mismatch, {name_from_struct} != {name}, project is loaded from {name_from_struct} "
730
+ f"project yaml. To prevent/allow this, you can take one of the following actions:\n"
731
+ "1. Set the `allow_cross_project=True` when loading the project.\n"
732
+ f"2. Delete the existing project yaml, or ensure its name is equal to {name}.\n"
733
+ "3. Use different project context dir."
734
+ )
676
735
 
677
- def _project_instance_from_struct(struct, name):
678
- struct.setdefault("metadata", {})["name"] = name or struct.get("metadata", {}).get(
679
- "name", ""
680
- )
736
+ if allow_cross_project is None:
737
+ # TODO: Remove this warning in version 1.9.0 and also fix cli to support allow_cross_project
738
+ warnings.warn(
739
+ f"Project {name=} is different than specified on the context's project yaml. "
740
+ "This behavior is deprecated and will not be supported from version 1.9.0."
741
+ )
742
+ logger.warn(error_message)
743
+ elif allow_cross_project:
744
+ logger.debug(
745
+ "Project name is different than specified on the context's project yaml. Overriding.",
746
+ existing_name=name_from_struct,
747
+ overriding_name=name,
748
+ )
749
+ else:
750
+ raise ValueError(error_message)
751
+ struct.setdefault("metadata", {})["name"] = name or name_from_struct
681
752
  return MlrunProject.from_dict(struct)
682
753
 
683
754
 
@@ -760,6 +831,7 @@ class ProjectSpec(ModelObj):
760
831
  default_image=None,
761
832
  build=None,
762
833
  custom_packagers: list[tuple[str, bool]] = None,
834
+ default_function_node_selector=None,
763
835
  ):
764
836
  self.repo = None
765
837
 
@@ -799,6 +871,7 @@ class ProjectSpec(ModelObj):
799
871
  # in a tuple where the first index is the packager module's path (str) and the second is a flag (bool) for
800
872
  # whether it is mandatory for a run (raise exception on collection error) or not.
801
873
  self.custom_packagers = custom_packagers or []
874
+ self._default_function_node_selector = default_function_node_selector or None
802
875
 
803
876
  @property
804
877
  def source(self) -> str:
@@ -934,19 +1007,29 @@ class ProjectSpec(ModelObj):
934
1007
 
935
1008
  artifacts_dict = {}
936
1009
  for artifact in artifacts:
937
- if not isinstance(artifact, dict) and not hasattr(artifact, "to_dict"):
1010
+ invalid_object_type = not isinstance(artifact, dict) and not hasattr(
1011
+ artifact, "to_dict"
1012
+ )
1013
+ is_artifact_model = not isinstance(artifact, dict) and hasattr(
1014
+ artifact, "to_dict"
1015
+ )
1016
+
1017
+ if invalid_object_type:
938
1018
  raise ValueError("artifacts must be a dict or class")
939
- if isinstance(artifact, dict):
940
- # Support legacy artifacts
941
- if is_legacy_artifact(artifact) or _is_imported_artifact(artifact):
942
- key = artifact.get("key")
943
- else:
944
- key = artifact.get("metadata").get("key", "")
945
- if not key:
946
- raise ValueError('artifacts "key" must be specified')
947
- else:
1019
+ elif is_artifact_model:
948
1020
  key = artifact.key
949
1021
  artifact = artifact.to_dict()
1022
+ else: # artifact is a dict
1023
+ # imported/legacy artifacts don't have metadata,spec,status fields
1024
+ key_field = (
1025
+ "key"
1026
+ if _is_imported_artifact(artifact)
1027
+ or mlrun.utils.is_legacy_artifact(artifact)
1028
+ else "metadata.key"
1029
+ )
1030
+ key = mlrun.utils.get_in(artifact, key_field, "")
1031
+ if not key:
1032
+ raise ValueError(f'artifacts "{key_field}" must be specified')
950
1033
 
951
1034
  artifacts_dict[key] = artifact
952
1035
 
@@ -963,6 +1046,14 @@ class ProjectSpec(ModelObj):
963
1046
  if key in self._artifacts:
964
1047
  del self._artifacts[key]
965
1048
 
1049
+ @property
1050
+ def default_function_node_selector(self):
1051
+ return self._default_function_node_selector
1052
+
1053
+ @default_function_node_selector.setter
1054
+ def default_function_node_selector(self, node_selector: dict[str, str]):
1055
+ self._default_function_node_selector = deepcopy(node_selector)
1056
+
966
1057
  @property
967
1058
  def build(self) -> ImageBuilder:
968
1059
  return self._build
@@ -1220,6 +1311,14 @@ class MlrunProject(ModelObj):
1220
1311
  def description(self, description):
1221
1312
  self.spec.description = description
1222
1313
 
1314
+ @property
1315
+ def default_function_node_selector(self) -> dict:
1316
+ return self.spec.default_function_node_selector
1317
+
1318
+ @default_function_node_selector.setter
1319
+ def default_function_node_selector(self, default_function_node_selector):
1320
+ self.spec.default_function_node_selector = default_function_node_selector
1321
+
1223
1322
  @property
1224
1323
  def default_image(self) -> str:
1225
1324
  return self.spec.default_image
@@ -1334,13 +1433,15 @@ class MlrunProject(ModelObj):
1334
1433
  example::
1335
1434
 
1336
1435
  # register a simple file artifact
1337
- project.set_artifact('data', target_path=data_url)
1436
+ project.set_artifact("data", target_path=data_url)
1338
1437
  # register a model artifact
1339
- project.set_artifact('model', ModelArtifact(model_file="model.pkl"), target_path=model_dir_url)
1438
+ project.set_artifact(
1439
+ "model", ModelArtifact(model_file="model.pkl"), target_path=model_dir_url
1440
+ )
1340
1441
 
1341
1442
  # register a path to artifact package (will be imported on project load)
1342
1443
  # to generate such package use `artifact.export(target_path)`
1343
- project.set_artifact('model', 'https://mystuff.com/models/mymodel.zip')
1444
+ project.set_artifact("model", "https://mystuff.com/models/mymodel.zip")
1344
1445
 
1345
1446
  :param key: artifact key/name
1346
1447
  :param artifact: mlrun Artifact object/dict (or its subclasses) or path to artifact
@@ -1375,14 +1476,7 @@ class MlrunProject(ModelObj):
1375
1476
  artifact_path = mlrun.utils.helpers.template_artifact_path(
1376
1477
  self.spec.artifact_path or mlrun.mlconf.artifact_path, self.metadata.name
1377
1478
  )
1378
- # TODO: To correctly maintain the list of artifacts from an exported project,
1379
- # we need to maintain the different trees that generated them
1380
- producer = ArtifactProducer(
1381
- "project",
1382
- self.metadata.name,
1383
- self.metadata.name,
1384
- tag=self._get_hexsha() or str(uuid.uuid4()),
1385
- )
1479
+ project_tag = self._get_project_tag()
1386
1480
  for artifact_dict in self.spec.artifacts:
1387
1481
  if _is_imported_artifact(artifact_dict):
1388
1482
  import_from = artifact_dict["import_from"]
@@ -1402,8 +1496,23 @@ class MlrunProject(ModelObj):
1402
1496
  artifact.src_path = path.join(
1403
1497
  self.spec.get_code_path(), artifact.src_path
1404
1498
  )
1499
+ producer, is_retained_producer = self._resolve_artifact_producer(
1500
+ artifact, project_tag
1501
+ )
1502
+ # log the artifact only if it doesn't already exist
1503
+ if (
1504
+ producer.name != self.metadata.name
1505
+ and self._resolve_existing_artifact(
1506
+ artifact,
1507
+ )
1508
+ ):
1509
+ continue
1405
1510
  artifact_manager.log_artifact(
1406
- producer, artifact, artifact_path=artifact_path
1511
+ producer,
1512
+ artifact,
1513
+ artifact_path=artifact_path,
1514
+ project=self.metadata.name,
1515
+ is_retained_producer=is_retained_producer,
1407
1516
  )
1408
1517
 
1409
1518
  def _get_artifact_manager(self):
@@ -1445,7 +1554,7 @@ class MlrunProject(ModelObj):
1445
1554
  url = path.normpath(path.join(self.spec.get_code_path(), url))
1446
1555
 
1447
1556
  if (not in_context or check_path_in_context) and not path.isfile(url):
1448
- raise mlrun.errors.MLRunNotFoundError(f"{url} not found")
1557
+ raise FileNotFoundError(f"{url} not found")
1449
1558
 
1450
1559
  return url, in_context
1451
1560
 
@@ -1453,15 +1562,15 @@ class MlrunProject(ModelObj):
1453
1562
  self,
1454
1563
  item,
1455
1564
  body=None,
1456
- tag="",
1457
- local_path="",
1458
- artifact_path=None,
1459
- format=None,
1460
- upload=None,
1461
- labels=None,
1462
- target_path=None,
1565
+ tag: str = "",
1566
+ local_path: str = "",
1567
+ artifact_path: Optional[str] = None,
1568
+ format: Optional[str] = None,
1569
+ upload: Optional[bool] = None,
1570
+ labels: Optional[dict[str, str]] = None,
1571
+ target_path: Optional[str] = None,
1463
1572
  **kwargs,
1464
- ):
1573
+ ) -> Artifact:
1465
1574
  """Log an output artifact and optionally upload it to datastore
1466
1575
 
1467
1576
  If the artifact already exists with the same key and tag, it will be overwritten.
@@ -1486,7 +1595,9 @@ class MlrunProject(ModelObj):
1486
1595
  :param format: artifact file format: csv, png, ..
1487
1596
  :param tag: version tag
1488
1597
  :param target_path: absolute target path (instead of using artifact_path + local_path)
1489
- :param upload: upload to datastore (default is True)
1598
+ :param upload: Whether to upload the artifact to the datastore. If not provided, and the `local_path`
1599
+ is not a directory, upload occurs by default. Directories are uploaded only when this
1600
+ flag is explicitly set to `True`.
1490
1601
  :param labels: a set of key/value labels to tag the artifact with
1491
1602
 
1492
1603
  :returns: artifact object
@@ -1498,12 +1609,20 @@ class MlrunProject(ModelObj):
1498
1609
  artifact_path = mlrun.utils.helpers.template_artifact_path(
1499
1610
  artifact_path, self.metadata.name
1500
1611
  )
1501
- producer = ArtifactProducer(
1502
- "project",
1503
- self.metadata.name,
1504
- self.metadata.name,
1505
- tag=self._get_hexsha() or str(uuid.uuid4()),
1506
- )
1612
+ producer, is_retained_producer = self._resolve_artifact_producer(item)
1613
+ if producer.name != self.metadata.name:
1614
+ # the artifact producer is retained, log it only if it doesn't already exist
1615
+ if existing_artifact := self._resolve_existing_artifact(
1616
+ item,
1617
+ tag,
1618
+ ):
1619
+ artifact_key = item if isinstance(item, str) else item.key
1620
+ logger.info(
1621
+ "Artifact already exists, skipping logging",
1622
+ key=artifact_key,
1623
+ tag=tag,
1624
+ )
1625
+ return existing_artifact
1507
1626
  item = am.log_artifact(
1508
1627
  producer,
1509
1628
  item,
@@ -1515,10 +1634,29 @@ class MlrunProject(ModelObj):
1515
1634
  upload=upload,
1516
1635
  labels=labels,
1517
1636
  target_path=target_path,
1637
+ project=self.metadata.name,
1638
+ is_retained_producer=is_retained_producer,
1518
1639
  **kwargs,
1519
1640
  )
1520
1641
  return item
1521
1642
 
1643
+ def delete_artifact(
1644
+ self,
1645
+ item: Artifact,
1646
+ deletion_strategy: mlrun.common.schemas.artifact.ArtifactsDeletionStrategies = (
1647
+ mlrun.common.schemas.artifact.ArtifactsDeletionStrategies.metadata_only
1648
+ ),
1649
+ secrets: dict = None,
1650
+ ):
1651
+ """Delete an artifact object in the DB and optionally delete the artifact data
1652
+
1653
+ :param item: Artifact object (can be any type, such as dataset, model, feature store).
1654
+ :param deletion_strategy: The artifact deletion strategy types.
1655
+ :param secrets: Credentials needed to access the artifact data.
1656
+ """
1657
+ am = self._get_artifact_manager()
1658
+ am.delete_artifact(item, deletion_strategy, secrets)
1659
+
1522
1660
  def log_dataset(
1523
1661
  self,
1524
1662
  key,
@@ -1533,7 +1671,7 @@ class MlrunProject(ModelObj):
1533
1671
  stats=None,
1534
1672
  target_path="",
1535
1673
  extra_data=None,
1536
- label_column: str = None,
1674
+ label_column: Optional[str] = None,
1537
1675
  **kwargs,
1538
1676
  ) -> DatasetArtifact:
1539
1677
  """
@@ -1549,7 +1687,9 @@ class MlrunProject(ModelObj):
1549
1687
  "age": [42, 52, 36, 24, 73],
1550
1688
  "testScore": [25, 94, 57, 62, 70],
1551
1689
  }
1552
- df = pd.DataFrame(raw_data, columns=["first_name", "last_name", "age", "testScore"])
1690
+ df = pd.DataFrame(
1691
+ raw_data, columns=["first_name", "last_name", "age", "testScore"]
1692
+ )
1553
1693
  project.log_dataset("mydf", df=df, stats=True)
1554
1694
 
1555
1695
  :param key: artifact key
@@ -1608,28 +1748,31 @@ class MlrunProject(ModelObj):
1608
1748
  artifact_path=None,
1609
1749
  upload=None,
1610
1750
  labels=None,
1611
- inputs: list[Feature] = None,
1612
- outputs: list[Feature] = None,
1613
- feature_vector: str = None,
1614
- feature_weights: list = None,
1751
+ inputs: Optional[list[Feature]] = None,
1752
+ outputs: Optional[list[Feature]] = None,
1753
+ feature_vector: Optional[str] = None,
1754
+ feature_weights: Optional[list] = None,
1615
1755
  training_set=None,
1616
1756
  label_column=None,
1617
1757
  extra_data=None,
1618
1758
  **kwargs,
1619
- ):
1759
+ ) -> ModelArtifact:
1620
1760
  """Log a model artifact and optionally upload it to datastore
1621
1761
 
1622
1762
  If the model already exists with the same key and tag, it will be overwritten.
1623
1763
 
1624
1764
  example::
1625
1765
 
1626
- project.log_model("model", body=dumps(model),
1627
- model_file="model.pkl",
1628
- metrics=context.results,
1629
- training_set=training_df,
1630
- label_column='label',
1631
- feature_vector=feature_vector_uri,
1632
- labels={"app": "fraud"})
1766
+ project.log_model(
1767
+ "model",
1768
+ body=dumps(model),
1769
+ model_file="model.pkl",
1770
+ metrics=context.results,
1771
+ training_set=training_df,
1772
+ label_column="label",
1773
+ feature_vector=feature_vector_uri,
1774
+ labels={"app": "fraud"},
1775
+ )
1633
1776
 
1634
1777
  :param key: artifact key or artifact class ()
1635
1778
  :param body: will use the body as the artifact content
@@ -1739,14 +1882,16 @@ class MlrunProject(ModelObj):
1739
1882
  artifact = get_artifact(spec)
1740
1883
  with open(f"{temp_dir}/_body", "rb") as fp:
1741
1884
  artifact.spec._body = fp.read()
1742
- artifact.target_path = ""
1743
1885
 
1744
1886
  # if the dataitem is not a file, it means we downloaded it from a remote source to a temp file,
1745
1887
  # so we need to remove it after we're done with it
1746
1888
  dataitem.remove_local()
1747
1889
 
1748
1890
  return self.log_artifact(
1749
- artifact, local_path=temp_dir, artifact_path=artifact_path
1891
+ artifact,
1892
+ local_path=temp_dir,
1893
+ artifact_path=artifact_path,
1894
+ upload=True,
1750
1895
  )
1751
1896
 
1752
1897
  else:
@@ -1764,10 +1909,18 @@ class MlrunProject(ModelObj):
1764
1909
  """
1765
1910
  context = context or self.spec.context
1766
1911
  if context:
1767
- project = _load_project_dir(context, self.metadata.name, self.spec.subpath)
1912
+ project = _load_project_dir(
1913
+ context,
1914
+ self.metadata.name,
1915
+ self.spec.subpath,
1916
+ allow_cross_project=False,
1917
+ )
1768
1918
  else:
1769
1919
  project = _load_project_file(
1770
- self.spec.origin_url, self.metadata.name, self._secrets
1920
+ self.spec.origin_url,
1921
+ self.metadata.name,
1922
+ self._secrets,
1923
+ allow_cross_project=None,
1771
1924
  )
1772
1925
  project.spec.source = self.spec.source
1773
1926
  project.spec.repo = self.spec.repo
@@ -1796,7 +1949,10 @@ class MlrunProject(ModelObj):
1796
1949
  def set_model_monitoring_function(
1797
1950
  self,
1798
1951
  func: typing.Union[str, mlrun.runtimes.BaseRuntime, None] = None,
1799
- application_class: typing.Union[str, ModelMonitoringApplicationBase] = None,
1952
+ application_class: typing.Union[
1953
+ str,
1954
+ mm_app.ModelMonitoringApplicationBase,
1955
+ ] = None,
1800
1956
  name: str = None,
1801
1957
  image: str = None,
1802
1958
  handler=None,
@@ -1812,6 +1968,7 @@ class MlrunProject(ModelObj):
1812
1968
  call `fn.deploy()` where `fn` is the object returned by this method.
1813
1969
 
1814
1970
  examples::
1971
+
1815
1972
  project.set_model_monitoring_function(
1816
1973
  name="myApp", application_class="MyApp", image="mlrun/mlrun"
1817
1974
  )
@@ -1834,11 +1991,6 @@ class MlrunProject(ModelObj):
1834
1991
  monitoring application's constructor.
1835
1992
  """
1836
1993
 
1837
- if name in mm_constants.MonitoringFunctionNames.all():
1838
- raise mlrun.errors.MLRunInvalidArgumentError(
1839
- f"Application name can not be on of the following name : "
1840
- f"{mm_constants.MonitoringFunctionNames.all()}"
1841
- )
1842
1994
  function_object: RemoteRuntime = None
1843
1995
  (
1844
1996
  resolved_function_name,
@@ -1856,16 +2008,6 @@ class MlrunProject(ModelObj):
1856
2008
  requirements_file,
1857
2009
  **application_kwargs,
1858
2010
  )
1859
- models_names = "all"
1860
- function_object.set_label(
1861
- mm_constants.ModelMonitoringAppLabel.KEY,
1862
- mm_constants.ModelMonitoringAppLabel.VAL,
1863
- )
1864
- function_object.set_label("models", models_names)
1865
-
1866
- if not mlrun.mlconf.is_ce_mode():
1867
- function_object.apply(mlrun.mount_v3io())
1868
-
1869
2011
  # save to project spec
1870
2012
  self.spec.set_function(resolved_function_name, function_object, func)
1871
2013
 
@@ -1874,7 +2016,10 @@ class MlrunProject(ModelObj):
1874
2016
  def create_model_monitoring_function(
1875
2017
  self,
1876
2018
  func: str = None,
1877
- application_class: typing.Union[str, ModelMonitoringApplicationBase] = None,
2019
+ application_class: typing.Union[
2020
+ str,
2021
+ mm_app.ModelMonitoringApplicationBase,
2022
+ ] = None,
1878
2023
  name: str = None,
1879
2024
  image: str = None,
1880
2025
  handler: str = None,
@@ -1888,8 +2033,10 @@ class MlrunProject(ModelObj):
1888
2033
  Create a monitoring function object without setting it to the project
1889
2034
 
1890
2035
  examples::
1891
- project.create_model_monitoring_function(application_class_name="MyApp",
1892
- image="mlrun/mlrun", name="myApp")
2036
+
2037
+ project.create_model_monitoring_function(
2038
+ application_class_name="MyApp", image="mlrun/mlrun", name="myApp"
2039
+ )
1893
2040
 
1894
2041
  :param func: Code url, None refers to current Notebook
1895
2042
  :param name: Name of the function, can be specified with a tag to support
@@ -1908,6 +2055,7 @@ class MlrunProject(ModelObj):
1908
2055
  :param application_kwargs: Additional keyword arguments to be passed to the
1909
2056
  monitoring application's constructor.
1910
2057
  """
2058
+
1911
2059
  _, function_object, _ = self._instantiate_model_monitoring_function(
1912
2060
  func,
1913
2061
  application_class,
@@ -1924,49 +2072,40 @@ class MlrunProject(ModelObj):
1924
2072
 
1925
2073
  def _instantiate_model_monitoring_function(
1926
2074
  self,
1927
- func: typing.Union[str, mlrun.runtimes.BaseRuntime] = None,
1928
- application_class: typing.Union[str, ModelMonitoringApplicationBase] = None,
1929
- name: str = None,
1930
- image: str = None,
1931
- handler: str = None,
1932
- with_repo: bool = None,
1933
- tag: str = None,
1934
- requirements: typing.Union[str, list[str]] = None,
2075
+ func: typing.Union[str, mlrun.runtimes.BaseRuntime, None] = None,
2076
+ application_class: typing.Union[
2077
+ str,
2078
+ mm_app.ModelMonitoringApplicationBase,
2079
+ None,
2080
+ ] = None,
2081
+ name: typing.Optional[str] = None,
2082
+ image: typing.Optional[str] = None,
2083
+ handler: typing.Optional[str] = None,
2084
+ with_repo: typing.Optional[bool] = None,
2085
+ tag: typing.Optional[str] = None,
2086
+ requirements: typing.Union[str, list[str], None] = None,
1935
2087
  requirements_file: str = "",
1936
2088
  **application_kwargs,
1937
2089
  ) -> tuple[str, mlrun.runtimes.BaseRuntime, dict]:
2090
+ import mlrun.model_monitoring.api
2091
+
1938
2092
  function_object: RemoteRuntime = None
1939
2093
  kind = None
1940
2094
  if (isinstance(func, str) or func is None) and application_class is not None:
1941
- kind = "serving"
1942
- if func is None:
1943
- func = ""
1944
- func = mlrun.code_to_function(
1945
- filename=func,
2095
+ kind = mlrun.run.RuntimeKinds.serving
2096
+ func = mlrun.model_monitoring.api._create_model_monitoring_function_base(
2097
+ project=self.name,
2098
+ func=func,
2099
+ application_class=application_class,
1946
2100
  name=name,
1947
- project=self.metadata.name,
1948
- tag=tag,
1949
- kind=kind,
1950
2101
  image=image,
2102
+ tag=tag,
1951
2103
  requirements=requirements,
1952
2104
  requirements_file=requirements_file,
2105
+ **application_kwargs,
1953
2106
  )
1954
- graph = func.set_topology("flow")
1955
- if isinstance(application_class, str):
1956
- first_step = graph.to(
1957
- class_name=application_class, **application_kwargs
1958
- )
1959
- else:
1960
- first_step = graph.to(class_name=application_class)
1961
- first_step.to(
1962
- class_name=PushToMonitoringWriter(
1963
- project=self.metadata.name,
1964
- writer_application_name=mm_constants.MonitoringFunctionNames.WRITER,
1965
- stream_uri=None,
1966
- ),
1967
- ).respond()
1968
2107
  elif isinstance(func, str) and isinstance(handler, str):
1969
- kind = "nuclio"
2108
+ kind = mlrun.run.RuntimeKinds.nuclio
1970
2109
 
1971
2110
  (
1972
2111
  resolved_function_name,
@@ -1984,24 +2123,36 @@ class MlrunProject(ModelObj):
1984
2123
  requirements,
1985
2124
  requirements_file,
1986
2125
  )
1987
- models_names = "all"
1988
2126
  function_object.set_label(
1989
2127
  mm_constants.ModelMonitoringAppLabel.KEY,
1990
2128
  mm_constants.ModelMonitoringAppLabel.VAL,
1991
2129
  )
1992
- function_object.set_label("models", models_names)
1993
2130
 
1994
2131
  if not mlrun.mlconf.is_ce_mode():
1995
2132
  function_object.apply(mlrun.mount_v3io())
1996
2133
 
1997
2134
  return resolved_function_name, function_object, func
1998
2135
 
2136
+ def _wait_for_functions_deployment(self, function_names: list[str]) -> None:
2137
+ """
2138
+ Wait for the deployment of functions on the backend.
2139
+
2140
+ :param function_names: A list of function names.
2141
+ """
2142
+ for fn_name in function_names:
2143
+ fn = typing.cast(RemoteRuntime, self.get_function(key=fn_name))
2144
+ fn._wait_for_function_deployment(db=fn._get_db())
2145
+
1999
2146
  def enable_model_monitoring(
2000
2147
  self,
2001
2148
  default_controller_image: str = "mlrun/mlrun",
2002
2149
  base_period: int = 10,
2003
2150
  image: str = "mlrun/mlrun",
2151
+ *,
2004
2152
  deploy_histogram_data_drift_app: bool = True,
2153
+ wait_for_deployment: bool = False,
2154
+ rebuild_images: bool = False,
2155
+ fetch_credentials_from_sys_config: bool = False,
2005
2156
  ) -> None:
2006
2157
  """
2007
2158
  Deploy model monitoring application controller, writer and stream functions.
@@ -2011,16 +2162,19 @@ class MlrunProject(ModelObj):
2011
2162
  The stream function goal is to monitor the log of the data stream. It is triggered when a new log entry
2012
2163
  is detected. It processes the new events into statistics that are then written to statistics databases.
2013
2164
 
2014
-
2015
- :param default_controller_image: Deprecated.
2016
- :param base_period: The time period in minutes in which the model monitoring controller
2017
- function is triggered. By default, the base period is 10 minutes.
2018
- :param image: The image of the model monitoring controller, writer, monitoring
2019
- stream & histogram data drift functions, which are real time nuclio
2020
- functions. By default, the image is mlrun/mlrun.
2021
- :param deploy_histogram_data_drift_app: If true, deploy the default histogram-based data drift application.
2022
-
2023
- :returns: model monitoring controller job as a dictionary.
2165
+ :param default_controller_image: Deprecated.
2166
+ :param base_period: The time period in minutes in which the model monitoring controller
2167
+ function is triggered. By default, the base period is 10 minutes
2168
+ (which is also the minimum value for production environments).
2169
+ :param image: The image of the model monitoring controller, writer, monitoring
2170
+ stream & histogram data drift functions, which are real time nuclio
2171
+ functions. By default, the image is mlrun/mlrun.
2172
+ :param deploy_histogram_data_drift_app: If true, deploy the default histogram-based data drift application.
2173
+ :param wait_for_deployment: If true, return only after the deployment is done on the backend.
2174
+ Otherwise, deploy the model monitoring infrastructure on the
2175
+ background, including the histogram data drift app if selected.
2176
+ :param rebuild_images: If true, force rebuild of model monitoring infrastructure images.
2177
+ :param fetch_credentials_from_sys_config: If true, fetch the credentials from the system configuration.
2024
2178
  """
2025
2179
  if default_controller_image != "mlrun/mlrun":
2026
2180
  # TODO: Remove this in 1.9.0
@@ -2030,39 +2184,71 @@ class MlrunProject(ModelObj):
2030
2184
  FutureWarning,
2031
2185
  )
2032
2186
  image = default_controller_image
2187
+ if base_period < 10:
2188
+ logger.warn(
2189
+ "enable_model_monitoring: 'base_period' < 10 minutes is not supported in production environments",
2190
+ project=self.name,
2191
+ )
2192
+
2033
2193
  db = mlrun.db.get_run_db(secrets=self._secrets)
2034
2194
  db.enable_model_monitoring(
2035
2195
  project=self.name,
2036
2196
  image=image,
2037
2197
  base_period=base_period,
2198
+ deploy_histogram_data_drift_app=deploy_histogram_data_drift_app,
2199
+ rebuild_images=rebuild_images,
2200
+ fetch_credentials_from_sys_config=fetch_credentials_from_sys_config,
2038
2201
  )
2039
- if deploy_histogram_data_drift_app:
2040
- fn = self.set_model_monitoring_function(
2041
- func=str(
2042
- pathlib.Path(__file__).parent.parent
2043
- / "model_monitoring/applications/histogram_data_drift.py"
2044
- ),
2045
- name=mm_constants.MLRUN_HISTOGRAM_DATA_DRIFT_APP_NAME,
2046
- application_class="HistogramDataDriftApplication",
2047
- image=image,
2202
+
2203
+ if wait_for_deployment:
2204
+ deployment_functions = mm_constants.MonitoringFunctionNames.list()
2205
+ if deploy_histogram_data_drift_app:
2206
+ deployment_functions.append(
2207
+ mm_constants.HistogramDataDriftApplicationConstants.NAME
2208
+ )
2209
+ self._wait_for_functions_deployment(deployment_functions)
2210
+
2211
+ def deploy_histogram_data_drift_app(
2212
+ self,
2213
+ *,
2214
+ image: str = "mlrun/mlrun",
2215
+ db: Optional[mlrun.db.RunDBInterface] = None,
2216
+ wait_for_deployment: bool = False,
2217
+ ) -> None:
2218
+ """
2219
+ Deploy the histogram data drift application.
2220
+
2221
+ :param image: The image on which the application will run.
2222
+ :param db: An optional DB object.
2223
+ :param wait_for_deployment: If true, return only after the deployment is done on the backend.
2224
+ Otherwise, deploy the application on the background.
2225
+ """
2226
+ if db is None:
2227
+ db = mlrun.db.get_run_db(secrets=self._secrets)
2228
+ db.deploy_histogram_data_drift_app(project=self.name, image=image)
2229
+
2230
+ if wait_for_deployment:
2231
+ self._wait_for_functions_deployment(
2232
+ [mm_constants.HistogramDataDriftApplicationConstants.NAME]
2048
2233
  )
2049
- fn.deploy()
2050
2234
 
2051
2235
  def update_model_monitoring_controller(
2052
2236
  self,
2053
2237
  base_period: int = 10,
2054
2238
  image: str = "mlrun/mlrun",
2239
+ *,
2240
+ wait_for_deployment: bool = False,
2055
2241
  ) -> None:
2056
2242
  """
2057
2243
  Redeploy model monitoring application controller functions.
2058
2244
 
2059
-
2060
- :param base_period: The time period in minutes in which the model monitoring controller function
2061
- is triggered. By default, the base period is 10 minutes.
2062
- :param image: The image of the model monitoring controller, writer & monitoring
2063
- stream functions, which are real time nuclio functions.
2064
- By default, the image is mlrun/mlrun.
2065
- :returns: model monitoring controller job as a dictionary.
2245
+ :param base_period: The time period in minutes in which the model monitoring controller function
2246
+ is triggered. By default, the base period is 10 minutes.
2247
+ :param image: The image of the model monitoring controller, writer & monitoring
2248
+ stream functions, which are real time nuclio functions.
2249
+ By default, the image is mlrun/mlrun.
2250
+ :param wait_for_deployment: If true, return only after the deployment is done on the backend.
2251
+ Otherwise, deploy the controller on the background.
2066
2252
  """
2067
2253
  db = mlrun.db.get_run_db(secrets=self._secrets)
2068
2254
  db.update_model_monitoring_controller(
@@ -2071,26 +2257,78 @@ class MlrunProject(ModelObj):
2071
2257
  image=image,
2072
2258
  )
2073
2259
 
2074
- def disable_model_monitoring(self):
2260
+ if wait_for_deployment:
2261
+ self._wait_for_functions_deployment(
2262
+ [mm_constants.MonitoringFunctionNames.APPLICATION_CONTROLLER]
2263
+ )
2264
+
2265
+ def disable_model_monitoring(
2266
+ self,
2267
+ *,
2268
+ delete_resources: bool = True,
2269
+ delete_stream_function: bool = False,
2270
+ delete_histogram_data_drift_app: bool = True,
2271
+ delete_user_applications: bool = False,
2272
+ user_application_list: list[str] = None,
2273
+ ) -> None:
2274
+ """
2275
+ Disable model monitoring application controller, writer, stream, histogram data drift application
2276
+ and the user's applications functions, according to the given params.
2277
+
2278
+ :param delete_resources: If True, it would delete the model monitoring controller & writer
2279
+ functions. Default True
2280
+ :param delete_stream_function: If True, it would delete model monitoring stream function,
2281
+ need to use wisely because if you're deleting this function
2282
+ this can cause data loss in case you will want to
2283
+ enable the model monitoring capability to the project.
2284
+ Default False.
2285
+ :param delete_histogram_data_drift_app: If True, it would delete the default histogram-based data drift
2286
+ application. Default False.
2287
+ :param delete_user_applications: If True, it would delete the user's model monitoring
2288
+ application according to user_application_list, Default False.
2289
+ :param user_application_list: List of the user's model monitoring application to disable.
2290
+ Default all the applications.
2291
+ Note: you have to set delete_user_applications to True
2292
+ in order to delete the desired application.
2293
+ """
2294
+ if not delete_user_applications and user_application_list:
2295
+ raise mlrun.errors.MLRunInvalidArgumentError(
2296
+ "user_application_list can be specified only if delete_user_applications is set to True"
2297
+ )
2298
+
2075
2299
  db = mlrun.db.get_run_db(secrets=self._secrets)
2076
- db.delete_function(
2077
- project=self.name,
2078
- name=mm_constants.MonitoringFunctionNames.APPLICATION_CONTROLLER,
2079
- )
2080
- db.delete_function(
2081
- project=self.name,
2082
- name=mm_constants.MonitoringFunctionNames.WRITER,
2083
- )
2084
- db.delete_function(
2300
+ succeed = db.disable_model_monitoring(
2085
2301
  project=self.name,
2086
- name=mm_constants.MonitoringFunctionNames.STREAM,
2302
+ delete_resources=delete_resources,
2303
+ delete_stream_function=delete_stream_function,
2304
+ delete_histogram_data_drift_app=delete_histogram_data_drift_app,
2305
+ delete_user_applications=delete_user_applications,
2306
+ user_application_list=user_application_list,
2087
2307
  )
2308
+ if succeed and delete_resources:
2309
+ if delete_resources:
2310
+ logger.info("Model Monitoring disabled", project=self.name)
2311
+ if delete_user_applications:
2312
+ logger.info(
2313
+ "All the desired monitoring application were deleted",
2314
+ project=self.name,
2315
+ )
2316
+ else:
2317
+ if delete_resources:
2318
+ logger.info(
2319
+ "Model Monitoring was not disabled properly", project=self.name
2320
+ )
2321
+ if delete_user_applications:
2322
+ logger.info(
2323
+ "Some of the desired monitoring application were not deleted",
2324
+ project=self.name,
2325
+ )
2088
2326
 
2089
2327
  def set_function(
2090
2328
  self,
2091
2329
  func: typing.Union[str, mlrun.runtimes.BaseRuntime] = None,
2092
2330
  name: str = "",
2093
- kind: str = "",
2331
+ kind: str = "job",
2094
2332
  image: str = None,
2095
2333
  handler: str = None,
2096
2334
  with_repo: bool = None,
@@ -2098,31 +2336,52 @@ class MlrunProject(ModelObj):
2098
2336
  requirements: typing.Union[str, list[str]] = None,
2099
2337
  requirements_file: str = "",
2100
2338
  ) -> mlrun.runtimes.BaseRuntime:
2101
- """update or add a function object to the project
2339
+ """
2340
+ | Update or add a function object to the project.
2341
+ | Function can be provided as an object (func) or a .py/.ipynb/.yaml URL.
2102
2342
 
2103
- function can be provided as an object (func) or a .py/.ipynb/.yaml url
2104
- support url prefixes::
2343
+ | Creating a function from a single file is done by specifying ``func`` and disabling ``with_repo``.
2344
+ | Creating a function with project source (specify ``with_repo=True``):
2345
+ | 1. Specify a relative ``func`` path.
2346
+ | 2. Specify a module ``handler`` (e.g. ``handler=package.package.func``) without ``func``.
2347
+ | Creating a function with non project source is done by specifying a module ``handler`` and on the
2348
+ returned function set the source with ``function.with_source_archive(<source>)``.
2105
2349
 
2106
- object (s3://, v3io://, ..)
2107
- MLRun DB e.g. db://project/func:ver
2108
- functions hub/market: e.g. hub://auto-trainer:master
2350
+ Support URL prefixes:
2109
2351
 
2110
- examples::
2352
+ | Object (s3://, v3io://, ..)
2353
+ | MLRun DB e.g. db://project/func:ver
2354
+ | Functions hub/market: e.g. hub://auto-trainer:master
2355
+
2356
+ Examples::
2111
2357
 
2112
2358
  proj.set_function(func_object)
2113
- proj.set_function('./src/mycode.py', 'ingest',
2114
- image='myrepo/ing:latest', with_repo=True)
2115
- proj.set_function('http://.../mynb.ipynb', 'train')
2116
- proj.set_function('./func.yaml')
2117
- proj.set_function('hub://get_toy_data', 'getdata')
2359
+ proj.set_function("http://.../mynb.ipynb", "train")
2360
+ proj.set_function("./func.yaml")
2361
+ proj.set_function("hub://get_toy_data", "getdata")
2362
+
2363
+ # Create a function from a single file
2364
+ proj.set_function("./src/mycode.py", "ingest")
2365
+
2366
+ # Creating a function with project source
2367
+ proj.set_function(
2368
+ "./src/mycode.py", "ingest", image="myrepo/ing:latest", with_repo=True
2369
+ )
2370
+ proj.set_function("ingest", handler="package.package.func", with_repo=True)
2371
+
2372
+ # Creating a function with non project source
2373
+ func = proj.set_function(
2374
+ "ingest", handler="package.package.func", with_repo=False
2375
+ )
2376
+ func.with_source_archive("git://github.com/mlrun/something.git")
2118
2377
 
2119
- # set function requirements
2378
+ # Set function requirements
2120
2379
 
2121
- # by providing a list of packages
2122
- proj.set_function('my.py', requirements=["requests", "pandas"])
2380
+ # By providing a list of packages
2381
+ proj.set_function("my.py", requirements=["requests", "pandas"])
2123
2382
 
2124
- # by providing a path to a pip requirements file
2125
- proj.set_function('my.py', requirements="requirements.txt")
2383
+ # By providing a path to a pip requirements file
2384
+ proj.set_function("my.py", requirements="requirements.txt")
2126
2385
 
2127
2386
  :param func: Function object or spec/code url, None refers to current Notebook
2128
2387
  :param name: Name of the function (under the project), can be specified with a tag to support
@@ -2133,14 +2392,15 @@ class MlrunProject(ModelObj):
2133
2392
  Default: job
2134
2393
  :param image: Docker image to be used, can also be specified in the function object/yaml
2135
2394
  :param handler: Default function handler to invoke (can only be set with .py/.ipynb files)
2136
- :param with_repo: Add (clone) the current repo to the build source
2395
+ :param with_repo: Add (clone) the current repo to the build source - use when the function code is in
2396
+ the project repo (project.spec.source).
2137
2397
  :param tag: Function version tag to set (none for current or 'latest')
2138
2398
  Specifying a tag as a parameter will update the project's tagged function
2139
2399
  (myfunc:v1) and the untagged function (myfunc)
2140
2400
  :param requirements: A list of python packages
2141
2401
  :param requirements_file: Path to a python requirements file
2142
2402
 
2143
- :returns: function object
2403
+ :returns: :py:class:`~mlrun.runtimes.BaseRuntime`
2144
2404
  """
2145
2405
  (
2146
2406
  resolved_function_name,
@@ -2174,19 +2434,20 @@ class MlrunProject(ModelObj):
2174
2434
  requirements: typing.Union[str, list[str]] = None,
2175
2435
  requirements_file: str = "",
2176
2436
  ) -> tuple[str, str, mlrun.runtimes.BaseRuntime, dict]:
2177
- if func is None and not _has_module(handler, kind):
2437
+ if (
2438
+ func is None
2439
+ and not _has_module(handler, kind)
2440
+ and mlrun.runtimes.RuntimeKinds.supports_from_notebook(kind)
2441
+ ):
2178
2442
  # if function path is not provided and it is not a module (no ".")
2179
2443
  # use the current notebook as default
2180
- if not is_ipython:
2181
- raise ValueError(
2182
- "Function path or module must be specified (when not running inside a Notebook)"
2183
- )
2184
- from IPython import get_ipython
2444
+ if is_jupyter:
2445
+ from IPython import get_ipython
2185
2446
 
2186
- kernel = get_ipython()
2187
- func = nuclio.utils.notebook_file_name(kernel)
2188
- if func.startswith(path.abspath(self.spec.context)):
2189
- func = path.relpath(func, self.spec.context)
2447
+ kernel = get_ipython()
2448
+ func = nuclio.utils.notebook_file_name(kernel)
2449
+ if func.startswith(path.abspath(self.spec.context)):
2450
+ func = path.relpath(func, self.spec.context)
2190
2451
 
2191
2452
  func = func or ""
2192
2453
 
@@ -2282,22 +2543,39 @@ class MlrunProject(ModelObj):
2282
2543
  """
2283
2544
  self.spec.remove_function(name)
2284
2545
 
2285
- def remove_model_monitoring_function(self, name):
2286
- """remove the specified model-monitoring-app function from the project and from the db
2546
+ def remove_model_monitoring_function(self, name: Union[str, list[str]]):
2547
+ """delete the specified model-monitoring-app function/s
2287
2548
 
2288
- :param name: name of the model-monitoring-app function (under the project)
2549
+ :param name: name of the model-monitoring-function/s (under the project)
2289
2550
  """
2290
- function = self.get_function(key=name)
2291
- if (
2292
- function.metadata.labels.get(mm_constants.ModelMonitoringAppLabel.KEY)
2293
- == mm_constants.ModelMonitoringAppLabel.VAL
2294
- ):
2295
- self.remove_function(name=name)
2296
- mlrun.db.get_run_db().delete_function(name=name.lower())
2297
- logger.info(f"{name} function has been removed from {self.name} project")
2551
+ # TODO: Remove this in 1.9.0
2552
+ warnings.warn(
2553
+ "'remove_model_monitoring_function' is deprecated and will be removed in 1.9.0. "
2554
+ "Please use `delete_model_monitoring_function` instead.",
2555
+ FutureWarning,
2556
+ )
2557
+ self.delete_model_monitoring_function(name)
2558
+
2559
+ def delete_model_monitoring_function(self, name: Union[str, list[str]]):
2560
+ """delete the specified model-monitoring-app function/s
2561
+
2562
+ :param name: name of the model-monitoring-function/s (under the project)
2563
+ """
2564
+ db = mlrun.db.get_run_db(secrets=self._secrets)
2565
+ succeed = db.delete_model_monitoring_function(
2566
+ project=self.name,
2567
+ functions=name if isinstance(name, list) else [name],
2568
+ )
2569
+ if succeed:
2570
+ logger.info(
2571
+ "All the desired monitoring functions were deleted",
2572
+ project=self.name,
2573
+ functions=name,
2574
+ )
2298
2575
  else:
2299
- raise logger.error(
2300
- f"There is no model monitoring function with {name} name"
2576
+ logger.info(
2577
+ "Some of the desired monitoring functions were not deleted",
2578
+ project=self.name,
2301
2579
  )
2302
2580
 
2303
2581
  def get_function(
@@ -2403,13 +2681,47 @@ class MlrunProject(ModelObj):
2403
2681
  clone_zip(url, self.spec.context, self._secrets)
2404
2682
 
2405
2683
  def create_remote(self, url, name="origin", branch=None):
2406
- """create remote for the project git
2684
+ """Create remote for the project git
2685
+
2686
+ This method creates a new remote repository associated with the project's Git repository.
2687
+ If a remote with the specified name already exists, it will not be overwritten.
2688
+
2689
+ If you wish to update the URL of an existing remote, use the `set_remote` method instead.
2407
2690
 
2408
2691
  :param url: remote git url
2409
2692
  :param name: name for the remote (default is 'origin')
2410
2693
  :param branch: Git branch to use as source
2411
2694
  """
2695
+ self.set_remote(url, name=name, branch=branch, overwrite=False)
2696
+
2697
+ def set_remote(self, url, name="origin", branch=None, overwrite=True):
2698
+ """Create or update a remote for the project git repository.
2699
+
2700
+ This method allows you to manage remote repositories associated with the project.
2701
+ It checks if a remote with the specified name already exists.
2702
+
2703
+ If a remote with the same name does not exist, it will be created.
2704
+ If a remote with the same name already exists,
2705
+ the behavior depends on the value of the 'overwrite' flag.
2706
+
2707
+ :param url: remote git url
2708
+ :param name: name for the remote (default is 'origin')
2709
+ :param branch: Git branch to use as source
2710
+ :param overwrite: if True (default), updates the existing remote with the given URL if it already exists.
2711
+ if False, raises an error when attempting to create a remote with a name that already exists.
2712
+ :raises MLRunConflictError: If a remote with the same name already exists and overwrite
2713
+ is set to False.
2714
+ """
2412
2715
  self._ensure_git_repo()
2716
+ if self._remote_exists(name):
2717
+ if overwrite:
2718
+ self.spec.repo.delete_remote(name)
2719
+ else:
2720
+ raise mlrun.errors.MLRunConflictError(
2721
+ f"Remote '{name}' already exists in the project, "
2722
+ f"each remote in the project must have a unique name."
2723
+ "Use 'set_remote' with 'override=True' inorder to update the remote, or choose a different name."
2724
+ )
2413
2725
  self.spec.repo.create_remote(name, url=url)
2414
2726
  url = url.replace("https://", "git://")
2415
2727
  if not branch:
@@ -2422,6 +2734,22 @@ class MlrunProject(ModelObj):
2422
2734
  self.spec._source = self.spec.source or url
2423
2735
  self.spec.origin_url = self.spec.origin_url or url
2424
2736
 
2737
+ def remove_remote(self, name):
2738
+ """Remove a remote from the project's Git repository.
2739
+
2740
+ This method removes the remote repository associated with the specified name from the project's Git repository.
2741
+
2742
+ :param name: Name of the remote to remove.
2743
+ """
2744
+ if self._remote_exists(name):
2745
+ self.spec.repo.delete_remote(name)
2746
+ else:
2747
+ logger.warning(f"The remote '{name}' does not exist. Nothing to remove.")
2748
+
2749
+ def _remote_exists(self, name):
2750
+ """Check if a remote with the given name already exists"""
2751
+ return any(remote.name == name for remote in self.spec.repo.remotes)
2752
+
2425
2753
  def _ensure_git_repo(self):
2426
2754
  if self.spec.repo:
2427
2755
  return
@@ -2504,47 +2832,104 @@ class MlrunProject(ModelObj):
2504
2832
  secrets=secrets or {},
2505
2833
  )
2506
2834
 
2507
- def sync_functions(self, names: list = None, always=True, save=False):
2508
- """reload function objects from specs and files"""
2835
+ def sync_functions(
2836
+ self,
2837
+ names: list = None,
2838
+ always: bool = True,
2839
+ save: bool = False,
2840
+ silent: bool = False,
2841
+ ):
2842
+ """
2843
+ Reload function objects from specs and files.
2844
+ The function objects are synced against the definitions spec in `self.spec._function_definitions`.
2845
+ Referenced files/URLs in the function spec will be reloaded.
2846
+ Function definitions are parsed by the following precedence:
2847
+
2848
+ 1. Contains runtime spec.
2849
+ 2. Contains module in the project's context.
2850
+ 3. Contains path to function definition (yaml, DB, Hub).
2851
+ 4. Contains path to .ipynb or .py files.
2852
+ 5. Contains a Nuclio/Serving function image / an 'Application' kind definition.
2853
+
2854
+ If function definition is already an object, some project metadata updates will apply however,
2855
+ it will not be reloaded.
2856
+
2857
+ :param names: Names of functions to reload, defaults to `self.spec._function_definitions.keys()`.
2858
+ :param always: Force reloading the functions.
2859
+ :param save: Whether to save the loaded functions or not.
2860
+ :param silent: Whether to raise an exception when a function fails to load.
2861
+
2862
+ :returns: Dictionary of function objects
2863
+ """
2509
2864
  if self._initialized and not always:
2510
2865
  return self.spec._function_objects
2511
2866
 
2512
- funcs = self.spec._function_objects
2867
+ functions = self.spec._function_objects
2513
2868
  if not names:
2514
2869
  names = self.spec._function_definitions.keys()
2515
- funcs = {}
2870
+ functions = {}
2871
+
2516
2872
  origin = mlrun.runtimes.utils.add_code_metadata(self.spec.context)
2517
2873
  for name in names:
2518
- f = self.spec._function_definitions.get(name)
2519
- if not f:
2520
- raise ValueError(f"function named {name} not found")
2874
+ function_definition = self.spec._function_definitions.get(name)
2875
+ if not function_definition:
2876
+ if silent:
2877
+ logger.warn(
2878
+ "Function definition was not found, skipping reload", name=name
2879
+ )
2880
+ continue
2881
+
2882
+ raise ValueError(f"Function named {name} not found")
2883
+
2884
+ function_object = self.spec._function_objects.get(name, None)
2885
+ is_base_runtime = isinstance(
2886
+ function_object, mlrun.runtimes.base.BaseRuntime
2887
+ )
2521
2888
  # If this function is already available locally, don't recreate it unless always=True
2522
- if (
2523
- isinstance(
2524
- self.spec._function_objects.get(name, None),
2525
- mlrun.runtimes.base.BaseRuntime,
2526
- )
2527
- and not always
2528
- ):
2529
- funcs[name] = self.spec._function_objects[name]
2889
+ if is_base_runtime and not always:
2890
+ functions[name] = function_object
2530
2891
  continue
2531
- if hasattr(f, "to_dict"):
2532
- name, func = _init_function_from_obj(f, self, name)
2533
- else:
2534
- if not isinstance(f, dict):
2535
- raise ValueError("function must be an object or dict")
2892
+
2893
+ # Reload the function
2894
+ if hasattr(function_definition, "to_dict"):
2895
+ name, func = _init_function_from_obj(function_definition, self, name)
2896
+ elif isinstance(function_definition, dict):
2536
2897
  try:
2537
- name, func = _init_function_from_dict(f, self, name)
2898
+ name, func = _init_function_from_dict(
2899
+ function_definition, self, name
2900
+ )
2538
2901
  except FileNotFoundError as exc:
2539
- raise mlrun.errors.MLRunMissingDependencyError(
2540
- f"File {exc.filename} not found while syncing project functions"
2541
- ) from exc
2902
+ message = f"File {exc.filename} not found while syncing project functions."
2903
+ if silent:
2904
+ message += " Skipping function reload"
2905
+ logger.warn(message, name=name)
2906
+ continue
2907
+
2908
+ raise mlrun.errors.MLRunMissingDependencyError(message) from exc
2909
+
2910
+ except Exception as exc:
2911
+ if silent:
2912
+ logger.warn(
2913
+ "Failed to instantiate function",
2914
+ name=name,
2915
+ error=mlrun.utils.err_to_str(exc),
2916
+ )
2917
+ continue
2918
+ raise exc
2919
+ else:
2920
+ message = f"Function {name} must be an object or dict."
2921
+ if silent:
2922
+ message += " Skipping function reload"
2923
+ logger.warn(message, name=name)
2924
+ continue
2925
+ raise ValueError(message)
2926
+
2542
2927
  func.spec.build.code_origin = origin
2543
- funcs[name] = func
2928
+ functions[name] = func
2544
2929
  if save:
2545
2930
  func.save(versioned=False)
2546
2931
 
2547
- self.spec._function_objects = funcs
2932
+ self.spec._function_objects = functions
2548
2933
  self._initialized = True
2549
2934
  return self.spec._function_objects
2550
2935
 
@@ -2553,9 +2938,9 @@ class MlrunProject(ModelObj):
2553
2938
 
2554
2939
  read secrets from a source provider to be used in workflows, example::
2555
2940
 
2556
- proj.with_secrets('file', 'file.txt')
2557
- proj.with_secrets('inline', {'key': 'val'})
2558
- proj.with_secrets('env', 'ENV1,ENV2', prefix='PFX_')
2941
+ proj.with_secrets("file", "file.txt")
2942
+ proj.with_secrets("inline", {"key": "val"})
2943
+ proj.with_secrets("env", "ENV1,ENV2", prefix="PFX_")
2559
2944
 
2560
2945
  Vault secret source has several options::
2561
2946
 
@@ -2566,7 +2951,7 @@ class MlrunProject(ModelObj):
2566
2951
  The 2nd option uses the current project name as context.
2567
2952
  Can also use empty secret list::
2568
2953
 
2569
- proj.with_secrets('vault', [])
2954
+ proj.with_secrets("vault", [])
2570
2955
 
2571
2956
  This will enable access to all secrets in vault registered to the current project.
2572
2957
 
@@ -2597,17 +2982,20 @@ class MlrunProject(ModelObj):
2597
2982
  file_path: str = None,
2598
2983
  provider: typing.Union[str, mlrun.common.schemas.SecretProviderName] = None,
2599
2984
  ):
2600
- """set project secrets from dict or secrets env file
2985
+ """
2986
+ Set project secrets from dict or secrets env file
2601
2987
  when using a secrets file it should have lines in the form KEY=VALUE, comment line start with "#"
2602
2988
  V3IO paths/credentials and MLrun service API address are dropped from the secrets
2603
2989
 
2604
- example secrets file::
2990
+ example secrets file:
2991
+
2992
+ .. code-block:: shell
2605
2993
 
2606
2994
  # this is an env file
2607
- AWS_ACCESS_KEY_ID-XXXX
2995
+ AWS_ACCESS_KEY_ID=XXXX
2608
2996
  AWS_SECRET_ACCESS_KEY=YYYY
2609
2997
 
2610
- usage::
2998
+ usage:
2611
2999
 
2612
3000
  # read env vars from dict or file and set as project secrets
2613
3001
  project.set_secrets({"SECRET1": "value"})
@@ -2686,41 +3074,50 @@ class MlrunProject(ModelObj):
2686
3074
  source: str = None,
2687
3075
  cleanup_ttl: int = None,
2688
3076
  notifications: list[mlrun.model.Notification] = None,
3077
+ workflow_runner_node_selector: typing.Optional[dict[str, str]] = None,
2689
3078
  ) -> _PipelineRunStatus:
2690
- """run a workflow using kubeflow pipelines
2691
-
2692
- :param name: name of the workflow
2693
- :param workflow_path:
2694
- url to a workflow file, if not a project workflow
2695
- :param arguments:
2696
- kubeflow pipelines arguments (parameters)
2697
- :param artifact_path:
2698
- target path/url for workflow artifacts, the string
2699
- '{{workflow.uid}}' will be replaced by workflow id
2700
- :param workflow_handler:
2701
- workflow function handler (for running workflow function directly)
2702
- :param namespace: kubernetes namespace if other than default
2703
- :param sync: force functions sync before run
2704
- :param watch: wait for pipeline completion
2705
- :param dirty: allow running the workflow when the git repo is dirty
2706
- :param engine: workflow engine running the workflow.
2707
- supported values are 'kfp' (default), 'local' or 'remote'.
2708
- for setting engine for remote running use 'remote:local' or 'remote:kfp'.
2709
- :param local: run local pipeline with local functions (set local=True in function.run())
3079
+ """Run a workflow using kubeflow pipelines
3080
+
3081
+ :param name: Name of the workflow
3082
+ :param workflow_path: URL to a workflow file, if not a project workflow
3083
+ :param arguments: Kubeflow pipelines arguments (parameters)
3084
+ :param artifact_path: Target path/URL for workflow artifacts, the string '{{workflow.uid}}' will be
3085
+ replaced by workflow id.
3086
+ :param workflow_handler: Workflow function handler (for running workflow function directly)
3087
+ :param namespace: Kubernetes namespace if other than default
3088
+ :param sync: Force functions sync before run
3089
+ :param watch: Wait for pipeline completion
3090
+ :param dirty: Allow running the workflow when the git repo is dirty
3091
+ :param engine: Workflow engine running the workflow.
3092
+ Supported values are 'kfp' (default), 'local' or 'remote'.
3093
+ For setting engine for remote running use 'remote:local' or 'remote:kfp'.
3094
+ :param local: Run local pipeline with local functions (set local=True in function.run())
2710
3095
  :param schedule: ScheduleCronTrigger class instance or a standard crontab expression string
2711
3096
  (which will be converted to the class using its `from_crontab` constructor),
2712
3097
  see this link for help:
2713
3098
  https://apscheduler.readthedocs.io/en/3.x/modules/triggers/cron.html#module-apscheduler.triggers.cron
2714
- for using the pre-defined workflow's schedule, set `schedule=True`
2715
- :param timeout: timeout in seconds to wait for pipeline completion (watch will be activated)
2716
- :param source: remote source to use instead of the actual `project.spec.source` (used when engine is remote).
2717
- for other engines the source is to validate that the code is up-to-date
3099
+ For using the pre-defined workflow's schedule, set `schedule=True`
3100
+ :param timeout: Timeout in seconds to wait for pipeline completion (watch will be activated)
3101
+ :param source: Source to use instead of the actual `project.spec.source` (used when engine is remote).
3102
+ Can be one of:
3103
+
3104
+ * Remote URL which is loaded dynamically to the workflow runner.
3105
+ * A path to the project's context on the workflow runner's image.
3106
+ Path can be absolute or relative to `project.spec.build.source_code_target_dir` if defined
3107
+ (enriched when building a project image with source, see `MlrunProject.build_image`).
3108
+ For other engines the source is used to validate that the code is up-to-date.
3109
+
2718
3110
  :param cleanup_ttl:
2719
- pipeline cleanup ttl in secs (time to wait after workflow completion, at which point the
3111
+ Pipeline cleanup ttl in secs (time to wait after workflow completion, at which point the
2720
3112
  workflow and all its resources are deleted)
2721
3113
  :param notifications:
2722
- list of notifications to send for workflow completion
2723
- :returns: run id
3114
+ List of notifications to send for workflow completion
3115
+ :param workflow_runner_node_selector:
3116
+ Defines the node selector for the workflow runner pod when using a remote engine.
3117
+ This allows you to control and specify where the workflow runner pod will be scheduled.
3118
+ This setting is only relevant when the engine is set to 'remote' or for scheduled workflows,
3119
+ and it will be ignored if the workflow is not run on a remote engine.
3120
+ :returns: ~py:class:`~mlrun.projects.pipelines._PipelineRunStatus` instance
2724
3121
  """
2725
3122
 
2726
3123
  arguments = arguments or {}
@@ -2737,12 +3134,14 @@ class MlrunProject(ModelObj):
2737
3134
  "Remote repo is not defined, use .create_remote() + push()"
2738
3135
  )
2739
3136
 
2740
- self.sync_functions(always=sync)
2741
- if not self.spec._function_objects:
2742
- raise ValueError(
2743
- "There are no functions in the project."
2744
- " Make sure you've set your functions with project.set_function()."
2745
- )
3137
+ if engine not in ["remote"] and not schedule:
3138
+ # For remote/scheduled runs there is no need to sync functions as they can be loaded dynamically during run
3139
+ self.sync_functions(always=sync, silent=True)
3140
+ if not self.spec._function_objects:
3141
+ logger.warn(
3142
+ "There are no functions in the project."
3143
+ " Make sure you've set your functions with project.set_function()."
3144
+ )
2746
3145
 
2747
3146
  if not name and not workflow_path and not workflow_handler:
2748
3147
  raise ValueError("Workflow name, path, or handler must be specified")
@@ -2776,9 +3175,23 @@ class MlrunProject(ModelObj):
2776
3175
  engine = "remote"
2777
3176
  # The default engine is kfp if not given:
2778
3177
  workflow_engine = get_workflow_engine(engine or workflow_spec.engine, local)
2779
- if not inner_engine and engine == "remote":
2780
- inner_engine = get_workflow_engine(workflow_spec.engine, local).engine
3178
+ if not inner_engine and workflow_engine.engine == "remote":
3179
+ # if inner engine is set to remote, assume kfp as the default inner engine with remote as the runner
3180
+ engine_kind = (
3181
+ workflow_spec.engine if workflow_spec.engine != "remote" else "kfp"
3182
+ )
3183
+ inner_engine = get_workflow_engine(engine_kind, local).engine
2781
3184
  workflow_spec.engine = inner_engine or workflow_engine.engine
3185
+ if workflow_runner_node_selector:
3186
+ if workflow_engine.engine == "remote":
3187
+ workflow_spec.workflow_runner_node_selector = (
3188
+ workflow_runner_node_selector
3189
+ )
3190
+ else:
3191
+ logger.warn(
3192
+ "'workflow_runner_node_selector' applies only to remote engines"
3193
+ " and is ignored for non-remote runs."
3194
+ )
2782
3195
 
2783
3196
  run = workflow_engine.run(
2784
3197
  self,
@@ -2792,7 +3205,7 @@ class MlrunProject(ModelObj):
2792
3205
  notifications=notifications,
2793
3206
  )
2794
3207
  # run is None when scheduling
2795
- if run and run.state == mlrun.run.RunStatuses.failed:
3208
+ if run and run.state == mlrun_pipelines.common.models.RunStatuses.failed:
2796
3209
  return run
2797
3210
  if not workflow_spec.schedule:
2798
3211
  # Failure and schedule messages already logged
@@ -2801,14 +3214,17 @@ class MlrunProject(ModelObj):
2801
3214
  )
2802
3215
  workflow_spec.clear_tmp()
2803
3216
  if (timeout or watch) and not workflow_spec.schedule:
3217
+ run_status_kwargs = {}
2804
3218
  status_engine = run._engine
2805
3219
  # run's engine gets replaced with inner engine if engine is remote,
2806
3220
  # so in that case we need to get the status from the remote engine manually
2807
- # TODO: support watch for remote:local
2808
- if engine == "remote" and status_engine.engine != "local":
3221
+ if workflow_engine.engine == "remote":
2809
3222
  status_engine = _RemoteRunner
3223
+ run_status_kwargs["inner_engine"] = run._engine
2810
3224
 
2811
- status_engine.get_run_status(project=self, run=run, timeout=timeout)
3225
+ status_engine.get_run_status(
3226
+ project=self, run=run, timeout=timeout, **run_status_kwargs
3227
+ )
2812
3228
  return run
2813
3229
 
2814
3230
  def save_workflow(self, name, target, artifact_path=None, ttl=None):
@@ -2908,43 +3324,66 @@ class MlrunProject(ModelObj):
2908
3324
 
2909
3325
  def set_model_monitoring_credentials(
2910
3326
  self,
2911
- access_key: str = None,
2912
- endpoint_store_connection: str = None,
2913
- stream_path: str = None,
3327
+ access_key: Optional[str] = None,
3328
+ endpoint_store_connection: Optional[str] = None,
3329
+ stream_path: Optional[str] = None,
3330
+ tsdb_connection: Optional[str] = None,
3331
+ replace_creds: bool = False,
2914
3332
  ):
2915
- """Set the credentials that will be used by the project's model monitoring
2916
- infrastructure functions.
2917
-
2918
- :param access_key: Model Monitoring access key for managing user permissions
2919
- :param access_key: Model Monitoring access key for managing user permissions
2920
- :param endpoint_store_connection: Endpoint store connection string
2921
- :param stream_path: Path to the model monitoring stream
2922
3333
  """
2923
-
2924
- secrets_dict = {}
2925
- if access_key:
2926
- secrets_dict[
2927
- mlrun.common.schemas.model_monitoring.ProjectSecretKeys.ACCESS_KEY
2928
- ] = access_key
2929
-
2930
- if endpoint_store_connection:
2931
- secrets_dict[
2932
- mlrun.common.schemas.model_monitoring.ProjectSecretKeys.ENDPOINT_STORE_CONNECTION
2933
- ] = endpoint_store_connection
2934
-
2935
- if stream_path:
2936
- if stream_path.startswith("kafka://") and "?topic" in stream_path:
2937
- raise mlrun.errors.MLRunInvalidArgumentError(
2938
- "Custom kafka topic is not allowed"
2939
- )
2940
- secrets_dict[
2941
- mlrun.common.schemas.model_monitoring.ProjectSecretKeys.STREAM_PATH
2942
- ] = stream_path
2943
-
2944
- self.set_secrets(
2945
- secrets=secrets_dict,
2946
- provider=mlrun.common.schemas.SecretProviderName.kubernetes,
3334
+ Set the credentials that will be used by the project's model monitoring
3335
+ infrastructure functions. Important to note that you have to set the credentials before deploying any
3336
+ model monitoring or serving function.
3337
+
3338
+ :param access_key: Model monitoring access key for managing user permissions.
3339
+ :param endpoint_store_connection: Endpoint store connection string. By default, None. Options:
3340
+
3341
+ * None - will be set from the system configuration.
3342
+ * v3io - for v3io endpoint store, pass `v3io` and the system will generate the
3343
+ exact path.
3344
+ * MySQL/SQLite - for SQL endpoint store, provide the full connection string,
3345
+ for example: mysql+pymysql://<username>:<password>@<host>:<port>/<db_name>
3346
+ :param stream_path: Path to the model monitoring stream. By default, None. Options:
3347
+
3348
+ * None - will be set from the system configuration.
3349
+ * v3io - for v3io stream, pass `v3io` and the system will generate the exact
3350
+ path.
3351
+ * Kafka - for Kafka stream, provide the full connection string without custom
3352
+ topic, for example kafka://<some_kafka_broker>:<port>.
3353
+ :param tsdb_connection: Connection string to the time series database. By default, None.
3354
+ Options:
3355
+
3356
+ * None - will be set from the system configuration.
3357
+ * v3io - for v3io stream, pass `v3io` and the system will generate the exact
3358
+ path.
3359
+ * TDEngine - for TDEngine tsdb, provide the full websocket connection URL,
3360
+ for example taosws://<username>:<password>@<host>:<port>.
3361
+ :param replace_creds: If True, will override the existing credentials.
3362
+ Please keep in mind that if you already enabled model monitoring on
3363
+ your project this action can cause data loose and will require redeploying
3364
+ all model monitoring functions & model monitoring infra
3365
+ & tracked model server.
3366
+ """
3367
+ db = mlrun.db.get_run_db(secrets=self._secrets)
3368
+ db.set_model_monitoring_credentials(
3369
+ project=self.name,
3370
+ credentials={
3371
+ "access_key": access_key,
3372
+ "endpoint_store_connection": endpoint_store_connection,
3373
+ "stream_path": stream_path,
3374
+ "tsdb_connection": tsdb_connection,
3375
+ },
3376
+ replace_creds=replace_creds,
2947
3377
  )
3378
+ if replace_creds:
3379
+ logger.info(
3380
+ "Model monitoring credentials were set successfully. "
3381
+ "Please keep in mind that if you already had model monitoring functions "
3382
+ "/ model monitoring infra / tracked model server "
3383
+ "deployed on your project, you will need to redeploy them."
3384
+ "For redeploying the model monitoring infra, please use `enable_model_monitoring` API "
3385
+ "and set `rebuild_images=True`"
3386
+ )
2948
3387
 
2949
3388
  def run_function(
2950
3389
  self,
@@ -2969,7 +3408,8 @@ class MlrunProject(ModelObj):
2969
3408
  notifications: list[mlrun.model.Notification] = None,
2970
3409
  returns: Optional[list[Union[str, dict[str, str]]]] = None,
2971
3410
  builder_env: Optional[dict] = None,
2972
- ) -> typing.Union[mlrun.model.RunObject, kfp.dsl.ContainerOp]:
3411
+ reset_on_run: bool = None,
3412
+ ) -> typing.Union[mlrun.model.RunObject, PipelineNodeWrapper]:
2973
3413
  """Run a local or remote task as part of a local/kubeflow pipeline
2974
3414
 
2975
3415
  example (use with project)::
@@ -2981,8 +3421,11 @@ class MlrunProject(ModelObj):
2981
3421
 
2982
3422
  # run functions (refer to them by name)
2983
3423
  run1 = project.run_function("myfunc", params={"x": 7})
2984
- run2 = project.run_function("train", params={"label_columns": LABELS},
2985
- inputs={"dataset":run1.outputs["data"]})
3424
+ run2 = project.run_function(
3425
+ "train",
3426
+ params={"label_columns": LABELS},
3427
+ inputs={"dataset": run1.outputs["data"]},
3428
+ )
2986
3429
 
2987
3430
  :param function: name of the function (in the project) or function object
2988
3431
  :param handler: name of the function handler
@@ -3021,8 +3464,13 @@ class MlrunProject(ModelObj):
3021
3464
  * A dictionary of configurations to use when logging. Further info per object type and
3022
3465
  artifact type can be given there. The artifact key must appear in the dictionary as
3023
3466
  "key": "the_key".
3024
- :param builder_env: env vars dict for source archive config/credentials e.g. builder_env={"GIT_TOKEN": token}
3025
- :return: MLRun RunObject or KubeFlow containerOp
3467
+ :param builder_env: env vars dict for source archive config/credentials e.g. builder_env={"GIT_TOKEN":
3468
+ token}
3469
+ :param reset_on_run: When True, function python modules would reload prior to code execution.
3470
+ This ensures latest code changes are executed. This argument must be used in
3471
+ conjunction with the local=True argument.
3472
+
3473
+ :return: MLRun RunObject or PipelineNodeWrapper
3026
3474
  """
3027
3475
  return run_function(
3028
3476
  function,
@@ -3047,6 +3495,7 @@ class MlrunProject(ModelObj):
3047
3495
  notifications=notifications,
3048
3496
  returns=returns,
3049
3497
  builder_env=builder_env,
3498
+ reset_on_run=reset_on_run,
3050
3499
  )
3051
3500
 
3052
3501
  def build_function(
@@ -3065,7 +3514,7 @@ class MlrunProject(ModelObj):
3065
3514
  requirements_file: str = None,
3066
3515
  extra_args: str = None,
3067
3516
  force_build: bool = False,
3068
- ) -> typing.Union[BuildStatus, kfp.dsl.ContainerOp]:
3517
+ ) -> typing.Union[BuildStatus, PipelineNodeWrapper]:
3069
3518
  """deploy ML function, build container with its dependencies
3070
3519
 
3071
3520
  :param function: name of the function (in the project) or function object
@@ -3119,6 +3568,7 @@ class MlrunProject(ModelObj):
3119
3568
  requirements_file: str = None,
3120
3569
  builder_env: dict = None,
3121
3570
  extra_args: str = None,
3571
+ source_code_target_dir: str = None,
3122
3572
  ):
3123
3573
  """specify builder configuration for the project
3124
3574
 
@@ -3139,6 +3589,8 @@ class MlrunProject(ModelObj):
3139
3589
  e.g. builder_env={"GIT_TOKEN": token}, does not work yet in KFP
3140
3590
  :param extra_args: A string containing additional builder arguments in the format of command-line options,
3141
3591
  e.g. extra_args="--skip-tls-verify --build-arg A=val"
3592
+ :param source_code_target_dir: Path on the image where source code would be extracted
3593
+ (by default `/home/mlrun_code`)
3142
3594
  """
3143
3595
  if not overwrite_build_params:
3144
3596
  # TODO: change overwrite_build_params default to True in 1.8.0
@@ -3162,6 +3614,7 @@ class MlrunProject(ModelObj):
3162
3614
  overwrite=overwrite_build_params,
3163
3615
  builder_env=builder_env,
3164
3616
  extra_args=extra_args,
3617
+ source_code_target_dir=source_code_target_dir,
3165
3618
  )
3166
3619
 
3167
3620
  if set_as_default and image != self.default_image:
@@ -3172,7 +3625,6 @@ class MlrunProject(ModelObj):
3172
3625
  image: str = None,
3173
3626
  set_as_default: bool = True,
3174
3627
  with_mlrun: bool = None,
3175
- skip_deployed: bool = False,
3176
3628
  base_image: str = None,
3177
3629
  commands: list = None,
3178
3630
  secret_name: str = None,
@@ -3183,7 +3635,7 @@ class MlrunProject(ModelObj):
3183
3635
  requirements_file: str = None,
3184
3636
  extra_args: str = None,
3185
3637
  target_dir: str = None,
3186
- ) -> typing.Union[BuildStatus, kfp.dsl.ContainerOp]:
3638
+ ) -> typing.Union[BuildStatus, PipelineNodeWrapper]:
3187
3639
  """Builder docker image for the project, based on the project's build config. Parameters allow to override
3188
3640
  the build config.
3189
3641
  If the project has a source configured and pull_at_runtime is not configured, this source will be cloned to the
@@ -3193,7 +3645,6 @@ class MlrunProject(ModelObj):
3193
3645
  used. If not set, the `mlconf.default_project_image_name` value will be used
3194
3646
  :param set_as_default: set `image` to be the project's default image (default False)
3195
3647
  :param with_mlrun: add the current mlrun package to the container build
3196
- :param skip_deployed: *Deprecated* parameter is ignored
3197
3648
  :param base_image: base image name/path (commands and source code will be added to it) defaults to
3198
3649
  mlrun.mlconf.default_base_image
3199
3650
  :param commands: list of docker build (RUN) commands e.g. ['pip install pandas']
@@ -3208,7 +3659,7 @@ class MlrunProject(ModelObj):
3208
3659
  * False: The new params are merged with the existing
3209
3660
  * True: The existing params are replaced by the new ones
3210
3661
  :param extra_args: A string containing additional builder arguments in the format of command-line options,
3211
- e.g. extra_args="--skip-tls-verify --build-arg A=val"r
3662
+ e.g. extra_args="--skip-tls-verify --build-arg A=val"
3212
3663
  :param target_dir: Path on the image where source code would be extracted (by default `/home/mlrun_code`)
3213
3664
  """
3214
3665
  if not base_image:
@@ -3218,14 +3669,6 @@ class MlrunProject(ModelObj):
3218
3669
  base_image=base_image,
3219
3670
  )
3220
3671
 
3221
- if skip_deployed:
3222
- warnings.warn(
3223
- "The 'skip_deployed' parameter is deprecated and will be removed in 1.7.0. "
3224
- "This parameter is ignored.",
3225
- # TODO: remove in 1.7.0
3226
- FutureWarning,
3227
- )
3228
-
3229
3672
  if not overwrite_build_params:
3230
3673
  # TODO: change overwrite_build_params default to True in 1.8.0
3231
3674
  warnings.warn(
@@ -3276,6 +3719,11 @@ class MlrunProject(ModelObj):
3276
3719
  force_build=True,
3277
3720
  )
3278
3721
 
3722
+ # Get the enriched target dir from the function
3723
+ self.spec.build.source_code_target_dir = (
3724
+ function.spec.build.source_code_target_dir
3725
+ )
3726
+
3279
3727
  try:
3280
3728
  mlrun.db.get_run_db(secrets=self._secrets).delete_function(
3281
3729
  name=function.metadata.name
@@ -3284,7 +3732,7 @@ class MlrunProject(ModelObj):
3284
3732
  logger.warning(
3285
3733
  f"Image was successfully built, but failed to delete temporary function {function.metadata.name}."
3286
3734
  " To remove the function, attempt to manually delete it.",
3287
- exc=repr(exc),
3735
+ exc=mlrun.errors.err_to_str(exc),
3288
3736
  )
3289
3737
 
3290
3738
  return result
@@ -3298,7 +3746,7 @@ class MlrunProject(ModelObj):
3298
3746
  verbose: bool = None,
3299
3747
  builder_env: dict = None,
3300
3748
  mock: bool = None,
3301
- ) -> typing.Union[DeployStatus, kfp.dsl.ContainerOp]:
3749
+ ) -> typing.Union[DeployStatus, PipelineNodeWrapper]:
3302
3750
  """deploy real-time (nuclio based) functions
3303
3751
 
3304
3752
  :param function: name of the function (in the project) or function object
@@ -3333,7 +3781,12 @@ class MlrunProject(ModelObj):
3333
3781
  artifact = db.read_artifact(
3334
3782
  key, tag, iter=iter, project=self.metadata.name, tree=tree
3335
3783
  )
3336
- return dict_to_artifact(artifact)
3784
+
3785
+ # in tests, if an artifact is not found, the db returns None
3786
+ # in real usage, the db should raise an exception
3787
+ if artifact:
3788
+ return dict_to_artifact(artifact)
3789
+ return None
3337
3790
 
3338
3791
  def list_artifacts(
3339
3792
  self,
@@ -3347,6 +3800,10 @@ class MlrunProject(ModelObj):
3347
3800
  kind: str = None,
3348
3801
  category: typing.Union[str, mlrun.common.schemas.ArtifactCategories] = None,
3349
3802
  tree: str = None,
3803
+ limit: int = None,
3804
+ format_: Optional[
3805
+ mlrun.common.formatters.ArtifactFormat
3806
+ ] = mlrun.common.formatters.ArtifactFormat.full,
3350
3807
  ) -> mlrun.lists.ArtifactList:
3351
3808
  """List artifacts filtered by various parameters.
3352
3809
 
@@ -3356,9 +3813,9 @@ class MlrunProject(ModelObj):
3356
3813
  Examples::
3357
3814
 
3358
3815
  # Get latest version of all artifacts in project
3359
- latest_artifacts = project.list_artifacts('', tag='latest')
3816
+ latest_artifacts = project.list_artifacts("", tag="latest")
3360
3817
  # check different artifact versions for a specific artifact, return as objects list
3361
- result_versions = project.list_artifacts('results', tag='*').to_objects()
3818
+ result_versions = project.list_artifacts("results", tag="*").to_objects()
3362
3819
 
3363
3820
  :param name: Name of artifacts to retrieve. Name with '~' prefix is used as a like query, and is not
3364
3821
  case-sensitive. This means that querying for ``~name`` may return artifacts named
@@ -3376,6 +3833,8 @@ class MlrunProject(ModelObj):
3376
3833
  :param kind: Return artifacts of the requested kind.
3377
3834
  :param category: Return artifacts of the requested category.
3378
3835
  :param tree: Return artifacts of the requested tree.
3836
+ :param limit: Maximum number of artifacts to return.
3837
+ :param format_: The format in which to return the artifacts. Default is 'full'.
3379
3838
  """
3380
3839
  db = mlrun.db.get_run_db(secrets=self._secrets)
3381
3840
  return db.list_artifacts(
@@ -3390,6 +3849,8 @@ class MlrunProject(ModelObj):
3390
3849
  kind=kind,
3391
3850
  category=category,
3392
3851
  tree=tree,
3852
+ format_=format_,
3853
+ limit=limit,
3393
3854
  )
3394
3855
 
3395
3856
  def list_models(
@@ -3402,13 +3863,17 @@ class MlrunProject(ModelObj):
3402
3863
  iter: int = None,
3403
3864
  best_iteration: bool = False,
3404
3865
  tree: str = None,
3866
+ limit: int = None,
3867
+ format_: Optional[
3868
+ mlrun.common.formatters.ArtifactFormat
3869
+ ] = mlrun.common.formatters.ArtifactFormat.full,
3405
3870
  ):
3406
3871
  """List models in project, filtered by various parameters.
3407
3872
 
3408
3873
  Examples::
3409
3874
 
3410
3875
  # Get latest version of all models in project
3411
- latest_models = project.list_models('', tag='latest')
3876
+ latest_models = project.list_models("", tag="latest")
3412
3877
 
3413
3878
 
3414
3879
  :param name: Name of artifacts to retrieve. Name with '~' prefix is used as a like query, and is not
@@ -3425,6 +3890,8 @@ class MlrunProject(ModelObj):
3425
3890
  artifacts generated from a hyper-param run. If only a single iteration exists, will return the artifact
3426
3891
  from that iteration. If using ``best_iter``, the ``iter`` parameter must not be used.
3427
3892
  :param tree: Return artifacts of the requested tree.
3893
+ :param limit: Maximum number of artifacts to return.
3894
+ :param format_: The format in which to return the artifacts. Default is 'full'.
3428
3895
  """
3429
3896
  db = mlrun.db.get_run_db(secrets=self._secrets)
3430
3897
  return db.list_artifacts(
@@ -3438,6 +3905,8 @@ class MlrunProject(ModelObj):
3438
3905
  best_iteration=best_iteration,
3439
3906
  kind="model",
3440
3907
  tree=tree,
3908
+ limit=limit,
3909
+ format_=format_,
3441
3910
  ).to_objects()
3442
3911
 
3443
3912
  def list_functions(self, name=None, tag=None, labels=None):
@@ -3449,7 +3918,7 @@ class MlrunProject(ModelObj):
3449
3918
 
3450
3919
 
3451
3920
  :param name: Return only functions with a specific name.
3452
- :param tag: Return function versions with specific tags.
3921
+ :param tag: Return function versions with specific tags. To return only tagged functions, set tag to ``"*"``.
3453
3922
  :param labels: Return functions that have specific labels assigned to them.
3454
3923
  :returns: List of function objects.
3455
3924
  """
@@ -3478,9 +3947,7 @@ class MlrunProject(ModelObj):
3478
3947
  :returns: List of function objects.
3479
3948
  """
3480
3949
 
3481
- model_monitoring_labels_list = [
3482
- f"{mm_constants.ModelMonitoringAppLabel.KEY}={mm_constants.ModelMonitoringAppLabel.VAL}"
3483
- ]
3950
+ model_monitoring_labels_list = [str(mm_constants.ModelMonitoringAppLabel())]
3484
3951
  if labels:
3485
3952
  model_monitoring_labels_list += labels
3486
3953
  return self.list_functions(
@@ -3494,7 +3961,10 @@ class MlrunProject(ModelObj):
3494
3961
  name: Optional[str] = None,
3495
3962
  uid: Optional[Union[str, list[str]]] = None,
3496
3963
  labels: Optional[Union[str, list[str]]] = None,
3497
- state: Optional[str] = None,
3964
+ state: Optional[
3965
+ mlrun.common.runtimes.constants.RunStates
3966
+ ] = None, # Backward compatibility
3967
+ states: typing.Optional[list[mlrun.common.runtimes.constants.RunStates]] = None,
3498
3968
  sort: bool = True,
3499
3969
  last: int = 0,
3500
3970
  iter: bool = False,
@@ -3513,14 +3983,14 @@ class MlrunProject(ModelObj):
3513
3983
  Example::
3514
3984
 
3515
3985
  # return a list of runs matching the name and label and compare
3516
- runs = project.list_runs(name='download', labels='owner=admin')
3986
+ runs = project.list_runs(name="download", labels="owner=admin")
3517
3987
  runs.compare()
3518
3988
 
3519
3989
  # multi-label filter can also be provided
3520
- runs = project.list_runs(name='download', labels=["kind=job", "owner=admin"])
3990
+ runs = project.list_runs(name="download", labels=["kind=job", "owner=admin"])
3521
3991
 
3522
3992
  # If running in Jupyter, can use the .show() function to display the results
3523
- project.list_runs(name='').show()
3993
+ project.list_runs(name="").show()
3524
3994
 
3525
3995
 
3526
3996
  :param name: Name of the run to retrieve.
@@ -3528,10 +3998,11 @@ class MlrunProject(ModelObj):
3528
3998
  :param labels: A list of labels to filter by. Label filters work by either filtering a specific value
3529
3999
  of a label (i.e. list("key=value")) or by looking for the existence of a given
3530
4000
  key (i.e. "key").
3531
- :param state: List only runs whose state is specified.
4001
+ :param state: Deprecated - List only runs whose state is specified.
4002
+ :param states: List only runs whose state is one of the provided states.
3532
4003
  :param sort: Whether to sort the result according to their start time. Otherwise, results will be
3533
4004
  returned by their internal order in the DB (order will not be guaranteed).
3534
- :param last: Deprecated - currently not used (will be removed in 1.8.0).
4005
+ :param last: Deprecated - currently not used (will be removed in 1.9.0).
3535
4006
  :param iter: If ``True`` return runs from all iterations. Otherwise, return only runs whose ``iter`` is 0.
3536
4007
  :param start_time_from: Filter by run start time in ``[start_time_from, start_time_to]``.
3537
4008
  :param start_time_to: Filter by run start time in ``[start_time_from, start_time_to]``.
@@ -3539,13 +4010,22 @@ class MlrunProject(ModelObj):
3539
4010
  last_update_time_to)``.
3540
4011
  :param last_update_time_to: Filter by run last update time in ``(last_update_time_from, last_update_time_to)``.
3541
4012
  """
4013
+ if state:
4014
+ # TODO: Remove this in 1.9.0
4015
+ warnings.warn(
4016
+ "'state' is deprecated and will be removed in 1.9.0. Use 'states' instead.",
4017
+ FutureWarning,
4018
+ )
4019
+
3542
4020
  db = mlrun.db.get_run_db(secrets=self._secrets)
3543
4021
  return db.list_runs(
3544
4022
  name,
3545
4023
  uid,
3546
4024
  self.metadata.name,
3547
4025
  labels=labels,
3548
- state=state,
4026
+ states=mlrun.utils.helpers.as_list(state)
4027
+ if state is not None
4028
+ else states or None,
3549
4029
  sort=sort,
3550
4030
  last=last,
3551
4031
  iter=iter,
@@ -3627,7 +4107,10 @@ class MlrunProject(ModelObj):
3627
4107
  self.spec.remove_custom_packager(packager=packager)
3628
4108
 
3629
4109
  def store_api_gateway(
3630
- self, api_gateway: mlrun.runtimes.nuclio.api_gateway.APIGateway
4110
+ self,
4111
+ api_gateway: mlrun.runtimes.nuclio.api_gateway.APIGateway,
4112
+ wait_for_readiness=True,
4113
+ max_wait_time=90,
3631
4114
  ) -> mlrun.runtimes.nuclio.api_gateway.APIGateway:
3632
4115
  """
3633
4116
  Creates or updates a Nuclio API Gateway using the provided APIGateway object.
@@ -3637,11 +4120,15 @@ class MlrunProject(ModelObj):
3637
4120
  on MLRun and Nuclio sides, such as the 'host' attribute.
3638
4121
  Nuclio docs here: https://docs.nuclio.io/en/latest/reference/api-gateway/http.html
3639
4122
 
3640
- :param api_gateway: An instance of :py:class:`~mlrun.runtimes.nuclio.APIGateway` representing the configuration
3641
- of the API Gateway to be created
4123
+ :param api_gateway: An instance of :py:class:`~mlrun.runtimes.nuclio.APIGateway` representing the
4124
+ configuration of the API Gateway to be created or updated.
4125
+ :param wait_for_readiness: (Optional) A boolean indicating whether to wait for the API Gateway to become
4126
+ ready after creation or update (default is True).
4127
+ :param max_wait_time: (Optional) Maximum time to wait for API Gateway readiness in seconds (default is 90s)
4128
+
3642
4129
 
3643
- @return: An instance of :py:class:`~mlrun.runtimes.nuclio.APIGateway` with all fields populated based on the
3644
- information retrieved from the Nuclio API
4130
+ :returns: An instance of :py:class:`~mlrun.runtimes.nuclio.APIGateway` with all fields populated based on the
4131
+ information retrieved from the Nuclio API
3645
4132
  """
3646
4133
 
3647
4134
  api_gateway_json = mlrun.db.get_run_db().store_api_gateway(
@@ -3654,14 +4141,17 @@ class MlrunProject(ModelObj):
3654
4141
  api_gateway = mlrun.runtimes.nuclio.api_gateway.APIGateway.from_scheme(
3655
4142
  api_gateway_json
3656
4143
  )
4144
+ if wait_for_readiness:
4145
+ api_gateway.wait_for_readiness(max_wait_time=max_wait_time)
4146
+
3657
4147
  return api_gateway
3658
4148
 
3659
4149
  def list_api_gateways(self) -> list[mlrun.runtimes.nuclio.api_gateway.APIGateway]:
3660
4150
  """
3661
4151
  Retrieves a list of Nuclio API gateways associated with the project.
3662
4152
 
3663
- @return: List of :py:class:`~mlrun.runtimes.nuclio.api_gateway.APIGateway` objects representing
3664
- the Nuclio API gateways associated with the project.
4153
+ :returns: List of :py:class:`~mlrun.runtimes.nuclio.api_gateway.APIGateway` objects representing
4154
+ the Nuclio API gateways associated with the project.
3665
4155
  """
3666
4156
  gateways_list = mlrun.db.get_run_db().list_api_gateways(self.name)
3667
4157
  return [
@@ -3682,14 +4172,128 @@ class MlrunProject(ModelObj):
3682
4172
  mlrun.runtimes.nuclio.APIGateway: An instance of APIGateway.
3683
4173
  """
3684
4174
 
3685
- return mlrun.db.get_run_db().get_api_gateway(name=name, project=self.name)
4175
+ gateway = mlrun.db.get_run_db().get_api_gateway(name=name, project=self.name)
4176
+ return mlrun.runtimes.nuclio.api_gateway.APIGateway.from_scheme(gateway)
4177
+
4178
+ def delete_api_gateway(
4179
+ self,
4180
+ name: str,
4181
+ ):
4182
+ """
4183
+ Deletes an API gateway by name.
4184
+
4185
+ :param name: The name of the API gateway to delete.
4186
+ """
4187
+
4188
+ mlrun.db.get_run_db().delete_api_gateway(name=name, project=self.name)
4189
+
4190
+ def store_alert_config(
4191
+ self, alert_data: AlertConfig, alert_name: typing.Optional[str] = None
4192
+ ) -> AlertConfig:
4193
+ """
4194
+ Create/modify an alert.
4195
+
4196
+ :param alert_data: The data of the alert.
4197
+ :param alert_name: The name of the alert.
4198
+ :return: the created/modified alert.
4199
+ """
4200
+ if not alert_data:
4201
+ raise mlrun.errors.MLRunInvalidArgumentError("Alert data must be provided")
4202
+
4203
+ db = mlrun.db.get_run_db(secrets=self._secrets)
4204
+ alert_name = alert_name or alert_data.name
4205
+ if alert_data.project is not None and alert_data.project != self.metadata.name:
4206
+ logger.warn(
4207
+ "Project in alert does not match project in operation",
4208
+ project=alert_data.project,
4209
+ )
4210
+ alert_data.project = self.metadata.name
4211
+ return db.store_alert_config(alert_name, alert_data, project=self.metadata.name)
4212
+
4213
+ def get_alert_config(self, alert_name: str) -> AlertConfig:
4214
+ """
4215
+ Retrieve an alert.
4216
+
4217
+ :param alert_name: The name of the alert to retrieve.
4218
+ :return: The alert object.
4219
+ """
4220
+ db = mlrun.db.get_run_db(secrets=self._secrets)
4221
+ return db.get_alert_config(alert_name, self.metadata.name)
4222
+
4223
+ def list_alerts_configs(self) -> list[AlertConfig]:
4224
+ """
4225
+ Retrieve list of alerts of a project.
4226
+
4227
+ :return: All the alerts objects of the project.
4228
+ """
4229
+ db = mlrun.db.get_run_db(secrets=self._secrets)
4230
+ return db.list_alerts_configs(self.metadata.name)
4231
+
4232
+ def delete_alert_config(
4233
+ self, alert_data: AlertConfig = None, alert_name: str = None
4234
+ ):
4235
+ """
4236
+ Delete an alert.
4237
+
4238
+ :param alert_data: The data of the alert.
4239
+ :param alert_name: The name of the alert to delete.
4240
+ """
4241
+ if alert_data is None and alert_name is None:
4242
+ raise ValueError(
4243
+ "At least one of alert_data or alert_name must be provided"
4244
+ )
4245
+ if alert_data and alert_name and alert_data.name != alert_name:
4246
+ raise ValueError("Alert_data name does not match the provided alert_name")
4247
+ db = mlrun.db.get_run_db(secrets=self._secrets)
4248
+ if alert_data:
4249
+ alert_name = alert_data.name
4250
+ db.delete_alert_config(alert_name, self.metadata.name)
4251
+
4252
+ def reset_alert_config(
4253
+ self, alert_data: AlertConfig = None, alert_name: str = None
4254
+ ):
4255
+ """
4256
+ Reset an alert.
4257
+
4258
+ :param alert_data: The data of the alert.
4259
+ :param alert_name: The name of the alert to reset.
4260
+ """
4261
+ if alert_data is None and alert_name is None:
4262
+ raise ValueError(
4263
+ "At least one of alert_data or alert_name must be provided"
4264
+ )
4265
+ if alert_data and alert_name and alert_data.name != alert_name:
4266
+ raise ValueError("Alert_data name does not match the provided alert_name")
4267
+ db = mlrun.db.get_run_db(secrets=self._secrets)
4268
+ if alert_data:
4269
+ alert_name = alert_data.name
4270
+ db.reset_alert_config(alert_name, self.metadata.name)
4271
+
4272
+ def get_alert_template(self, template_name: str) -> AlertTemplate:
4273
+ """
4274
+ Retrieve a specific alert template.
4275
+
4276
+ :param template_name: The name of the template to retrieve.
4277
+ :return: The template object.
4278
+ """
4279
+ db = mlrun.db.get_run_db(secrets=self._secrets)
4280
+ return db.get_alert_template(template_name)
4281
+
4282
+ def list_alert_templates(self) -> list[AlertTemplate]:
4283
+ """
4284
+ Retrieve list of all alert templates.
4285
+
4286
+ :return: All the alert template objects in the database.
4287
+ """
4288
+ db = mlrun.db.get_run_db(secrets=self._secrets)
4289
+ return db.list_alert_templates()
3686
4290
 
3687
4291
  def _run_authenticated_git_action(
3688
4292
  self,
3689
4293
  action: Callable,
3690
4294
  remote: str,
3691
- args: list = [],
3692
- kwargs: dict = {},
4295
+ args: list = None,
4296
+ kwargs: dict = None,
3693
4297
  secrets: Union[SecretsStore, dict] = None,
3694
4298
  ):
3695
4299
  """Run an arbitrary Git routine while the remote is enriched with secrets
@@ -3709,6 +4313,8 @@ class MlrunProject(ModelObj):
3709
4313
  try:
3710
4314
  if is_remote_enriched:
3711
4315
  self.spec.repo.remotes[remote].set_url(enriched_remote, clean_remote)
4316
+ args = args or []
4317
+ kwargs = kwargs or {}
3712
4318
  action(*args, **kwargs)
3713
4319
  except RuntimeError as e:
3714
4320
  raise mlrun.errors.MLRunRuntimeError(
@@ -3761,6 +4367,97 @@ class MlrunProject(ModelObj):
3761
4367
  f"<project.spec.get_code_path()>/<{param_name}>)."
3762
4368
  )
3763
4369
 
4370
+ def _resolve_artifact_producer(
4371
+ self,
4372
+ artifact: typing.Union[str, Artifact],
4373
+ project_producer_tag: str = None,
4374
+ ) -> tuple[ArtifactProducer, bool]:
4375
+ """
4376
+ Resolve the artifact producer of the given artifact.
4377
+ If the artifact's producer is a run, the artifact is registered with the original producer.
4378
+ Otherwise, the artifact is registered with the current project as the producer.
4379
+
4380
+ :param artifact: The artifact to resolve its producer.
4381
+ :param project_producer_tag: The tag to use for the project as the producer. If not provided, a tag will be
4382
+ generated for the project.
4383
+ :return: A tuple of the resolved producer and whether it is retained or not.
4384
+ """
4385
+
4386
+ if not isinstance(artifact, str) and artifact.spec.producer:
4387
+ # if the artifact was imported from a yaml file, the producer can be a dict
4388
+ if isinstance(artifact.spec.producer, ArtifactProducer):
4389
+ producer_dict = artifact.spec.producer.get_meta()
4390
+ else:
4391
+ producer_dict = artifact.spec.producer
4392
+
4393
+ producer_tag = producer_dict.get("tag", None)
4394
+ producer_project = producer_dict.get("project", None)
4395
+ if not producer_tag or not producer_project:
4396
+ # try resolving the producer tag from the uri
4397
+ producer_uri = artifact.spec.producer.get("uri", "")
4398
+ producer_project, producer_tag, _ = ArtifactProducer.parse_uri(
4399
+ producer_uri
4400
+ )
4401
+
4402
+ if producer_dict.get("kind", "") == "run":
4403
+ return ArtifactProducer(
4404
+ name=producer_dict.get("name", ""),
4405
+ kind=producer_dict.get("kind", ""),
4406
+ project=producer_project,
4407
+ tag=producer_tag,
4408
+ owner=producer_dict.get("owner", ""),
4409
+ ), True
4410
+
4411
+ # do not retain the artifact's producer, replace it with the project as the producer
4412
+ project_producer_tag = project_producer_tag or self._get_project_tag()
4413
+ return ArtifactProducer(
4414
+ kind="project",
4415
+ name=self.metadata.name,
4416
+ project=self.metadata.name,
4417
+ tag=project_producer_tag,
4418
+ owner=self._resolve_artifact_owner(),
4419
+ ), False
4420
+
4421
+ def _resolve_existing_artifact(
4422
+ self,
4423
+ item: typing.Union[str, Artifact],
4424
+ tag: str = None,
4425
+ ) -> typing.Optional[Artifact]:
4426
+ """
4427
+ Check if there is and existing artifact with the given item and tag.
4428
+ If there is, return the existing artifact. Otherwise, return None.
4429
+
4430
+ :param item: The item (or key) to check if there is an existing artifact for.
4431
+ :param tag: The tag to check if there is an existing artifact for.
4432
+ :return: The existing artifact if there is one, otherwise None.
4433
+ """
4434
+ try:
4435
+ if isinstance(item, str):
4436
+ existing_artifact = self.get_artifact(key=item, tag=tag)
4437
+ else:
4438
+ existing_artifact = self.get_artifact(
4439
+ key=item.key,
4440
+ tag=item.tag,
4441
+ iter=item.iter,
4442
+ tree=item.tree,
4443
+ )
4444
+ if existing_artifact is not None:
4445
+ return existing_artifact.from_dict(existing_artifact)
4446
+ except mlrun.errors.MLRunNotFoundError:
4447
+ logger.debug(
4448
+ "No existing artifact was found",
4449
+ key=item if isinstance(item, str) else item.key,
4450
+ tag=tag if isinstance(item, str) else item.tag,
4451
+ tree=None if isinstance(item, str) else item.tree,
4452
+ )
4453
+ return None
4454
+
4455
+ def _get_project_tag(self):
4456
+ return self._get_hexsha() or str(uuid.uuid4())
4457
+
4458
+ def _resolve_artifact_owner(self):
4459
+ return os.getenv("V3IO_USERNAME") or self.spec.owner
4460
+
3764
4461
 
3765
4462
  def _set_as_current_default_project(project: MlrunProject):
3766
4463
  mlrun.mlconf.default_project = project.metadata.name
@@ -3783,10 +4480,6 @@ def _init_function_from_dict(
3783
4480
  tag = f.get("tag", None)
3784
4481
 
3785
4482
  has_module = _has_module(handler, kind)
3786
- if not url and "spec" not in f and not has_module:
3787
- # function must point to a file or a module or have a spec
3788
- raise ValueError("Function missing a url or a spec or a module")
3789
-
3790
4483
  relative_url = url
3791
4484
  url, in_context = project.get_item_absolute_path(url)
3792
4485
 
@@ -3816,18 +4509,17 @@ def _init_function_from_dict(
3816
4509
  )
3817
4510
 
3818
4511
  elif url.endswith(".py"):
3819
- # when load_source_on_run is used we allow not providing image as code will be loaded pre-run. ML-4994
3820
- if (
3821
- not image
3822
- and not project.default_image
3823
- and kind != "local"
3824
- and not project.spec.load_source_on_run
3825
- ):
3826
- raise ValueError(
3827
- "image must be provided with py code files which do not "
3828
- "run on 'local' engine kind"
3829
- )
3830
4512
  if in_context and with_repo:
4513
+ # when load_source_on_run is used we allow not providing image as code will be loaded pre-run. ML-4994
4514
+ if (
4515
+ not image
4516
+ and not project.default_image
4517
+ and kind != "local"
4518
+ and not project.spec.load_source_on_run
4519
+ ):
4520
+ raise ValueError(
4521
+ "image must be provided with py code files which do not run on 'local' engine kind"
4522
+ )
3831
4523
  func = new_function(
3832
4524
  name,
3833
4525
  command=relative_url,
@@ -3846,6 +4538,17 @@ def _init_function_from_dict(
3846
4538
  tag=tag,
3847
4539
  )
3848
4540
 
4541
+ elif kind in mlrun.runtimes.RuntimeKinds.nuclio_runtimes():
4542
+ func = new_function(
4543
+ name,
4544
+ image=image,
4545
+ kind=kind,
4546
+ handler=handler,
4547
+ tag=tag,
4548
+ )
4549
+ if image and kind != mlrun.runtimes.RuntimeKinds.application:
4550
+ logger.info("Function code not specified, setting entry point to image")
4551
+ func.from_image(image)
3849
4552
  else:
3850
4553
  raise ValueError(f"Unsupported function url:handler {url}:{handler} or no spec")
3851
4554
 
@@ -3891,9 +4594,17 @@ def _init_function_from_obj(
3891
4594
  def _has_module(handler, kind):
3892
4595
  if not handler:
3893
4596
  return False
3894
- return (
3895
- kind in mlrun.runtimes.RuntimeKinds.nuclio_runtimes() and ":" in handler
3896
- ) or "." in handler
4597
+
4598
+ if (
4599
+ kind in mlrun.runtimes.RuntimeKinds.pure_nuclio_deployed_runtimes()
4600
+ and ":" in handler
4601
+ ):
4602
+ return True
4603
+
4604
+ if "." in handler:
4605
+ return True
4606
+
4607
+ return False
3897
4608
 
3898
4609
 
3899
4610
  def _is_imported_artifact(artifact):