mlrun 1.7.0rc4__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 (235) 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 -1
  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 +31 -4
  25. mlrun/common/schemas/alert.py +202 -0
  26. mlrun/common/schemas/api_gateway.py +196 -0
  27. mlrun/common/schemas/artifact.py +28 -1
  28. mlrun/common/schemas/auth.py +13 -2
  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 +233 -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 +387 -119
  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 +245 -20
  75. mlrun/db/factory.py +1 -4
  76. mlrun/db/httpdb.py +909 -231
  77. mlrun/db/nopdb.py +279 -14
  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 +1176 -406
  164. mlrun/render.py +28 -22
  165. mlrun/run.py +208 -181
  166. mlrun/runtimes/__init__.py +76 -11
  167. mlrun/runtimes/base.py +54 -24
  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/__init__.py +1 -0
  178. mlrun/runtimes/nuclio/api_gateway.py +769 -0
  179. mlrun/runtimes/nuclio/application/__init__.py +15 -0
  180. mlrun/runtimes/nuclio/application/application.py +758 -0
  181. mlrun/runtimes/nuclio/application/reverse_proxy.go +95 -0
  182. mlrun/runtimes/nuclio/function.py +188 -68
  183. mlrun/runtimes/nuclio/serving.py +57 -60
  184. mlrun/runtimes/pod.py +191 -58
  185. mlrun/runtimes/remotesparkjob.py +11 -8
  186. mlrun/runtimes/sparkjob/spark3job.py +17 -18
  187. mlrun/runtimes/utils.py +40 -73
  188. mlrun/secrets.py +6 -2
  189. mlrun/serving/__init__.py +8 -1
  190. mlrun/serving/remote.py +2 -3
  191. mlrun/serving/routers.py +89 -64
  192. mlrun/serving/server.py +54 -26
  193. mlrun/serving/states.py +187 -56
  194. mlrun/serving/utils.py +19 -11
  195. mlrun/serving/v2_serving.py +136 -63
  196. mlrun/track/tracker.py +2 -1
  197. mlrun/track/trackers/mlflow_tracker.py +5 -0
  198. mlrun/utils/async_http.py +26 -6
  199. mlrun/utils/db.py +18 -0
  200. mlrun/utils/helpers.py +375 -105
  201. mlrun/utils/http.py +2 -2
  202. mlrun/utils/logger.py +75 -9
  203. mlrun/utils/notifications/notification/__init__.py +14 -10
  204. mlrun/utils/notifications/notification/base.py +48 -0
  205. mlrun/utils/notifications/notification/console.py +2 -0
  206. mlrun/utils/notifications/notification/git.py +24 -1
  207. mlrun/utils/notifications/notification/ipython.py +2 -0
  208. mlrun/utils/notifications/notification/slack.py +96 -21
  209. mlrun/utils/notifications/notification/webhook.py +63 -2
  210. mlrun/utils/notifications/notification_pusher.py +146 -16
  211. mlrun/utils/regex.py +9 -0
  212. mlrun/utils/retryer.py +3 -2
  213. mlrun/utils/v3io_clients.py +2 -3
  214. mlrun/utils/version/version.json +2 -2
  215. mlrun-1.7.2.dist-info/METADATA +390 -0
  216. mlrun-1.7.2.dist-info/RECORD +351 -0
  217. {mlrun-1.7.0rc4.dist-info → mlrun-1.7.2.dist-info}/WHEEL +1 -1
  218. mlrun/feature_store/retrieval/conversion.py +0 -271
  219. mlrun/kfpops.py +0 -868
  220. mlrun/model_monitoring/application.py +0 -310
  221. mlrun/model_monitoring/batch.py +0 -974
  222. mlrun/model_monitoring/controller_handler.py +0 -37
  223. mlrun/model_monitoring/prometheus.py +0 -216
  224. mlrun/model_monitoring/stores/__init__.py +0 -111
  225. mlrun/model_monitoring/stores/kv_model_endpoint_store.py +0 -574
  226. mlrun/model_monitoring/stores/model_endpoint_store.py +0 -145
  227. mlrun/model_monitoring/stores/models/__init__.py +0 -27
  228. mlrun/model_monitoring/stores/models/base.py +0 -84
  229. mlrun/model_monitoring/stores/sql_model_endpoint_store.py +0 -382
  230. mlrun/platforms/other.py +0 -305
  231. mlrun-1.7.0rc4.dist-info/METADATA +0 -269
  232. mlrun-1.7.0rc4.dist-info/RECORD +0 -321
  233. {mlrun-1.7.0rc4.dist-info → mlrun-1.7.2.dist-info}/LICENSE +0 -0
  234. {mlrun-1.7.0rc4.dist-info → mlrun-1.7.2.dist-info}/entry_points.txt +0 -0
  235. {mlrun-1.7.0rc4.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,26 +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
54
+ import mlrun.runtimes.nuclio.api_gateway
44
55
  import mlrun.runtimes.pod
45
56
  import mlrun.runtimes.utils
57
+ import mlrun.serving
58
+ import mlrun.utils
46
59
  import mlrun.utils.regex
60
+ from mlrun.alerts.alert import AlertConfig
61
+ from mlrun.common.schemas.alert import AlertTemplate
47
62
  from mlrun.datastore.datastore_profile import DatastoreProfile, DatastoreProfile2Json
48
63
  from mlrun.runtimes.nuclio.function import RemoteRuntime
49
64
 
@@ -52,20 +67,9 @@ from ..artifacts.manager import ArtifactManager, dict_to_artifact, extend_artifa
52
67
  from ..datastore import store_manager
53
68
  from ..features import Feature
54
69
  from ..model import EntrypointParam, ImageBuilder, ModelObj
55
- from ..model_monitoring.application import (
56
- ModelMonitoringApplicationBase,
57
- PushToMonitoringWriter,
58
- )
59
70
  from ..run import code_to_function, get_object, import_function, new_function
60
71
  from ..secrets import SecretsStore
61
- from ..utils import (
62
- is_ipython,
63
- is_legacy_artifact,
64
- is_relative_path,
65
- is_yaml_path,
66
- logger,
67
- update_in,
68
- )
72
+ from ..utils import is_jupyter, is_relative_path, is_yaml_path, logger, update_in
69
73
  from ..utils.clones import (
70
74
  add_credentials_git_remote_url,
71
75
  clone_git,
@@ -73,7 +77,10 @@ from ..utils.clones import (
73
77
  clone_zip,
74
78
  get_repo_url,
75
79
  )
76
- 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
+ )
77
84
  from ..utils.notifications import CustomNotificationPusher, NotificationTypes
78
85
  from .operations import (
79
86
  BuildStatus,
@@ -127,6 +134,7 @@ def new_project(
127
134
  save: bool = True,
128
135
  overwrite: bool = False,
129
136
  parameters: dict = None,
137
+ default_function_node_selector: dict = None,
130
138
  ) -> "MlrunProject":
131
139
  """Create a new MLRun project, optionally load it from a yaml/zip/git template
132
140
 
@@ -137,11 +145,15 @@ def new_project(
137
145
  example::
138
146
 
139
147
  # create a project with local and hub functions, a workflow, and an artifact
140
- project = mlrun.new_project("myproj", "./", init_git=True, description="my new project")
141
- project.set_function('prep_data.py', 'prep-data', image='mlrun/mlrun', handler='prep_data')
142
- project.set_function('hub://auto-trainer', 'train')
143
- project.set_artifact('data', Artifact(target_path=data_url))
144
- 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")
145
157
  project.save()
146
158
 
147
159
  # run the "main" workflow (watch=True to wait for run completion)
@@ -151,19 +163,25 @@ def new_project(
151
163
 
152
164
  # create a new project from a zip template (can also use yaml/git templates)
153
165
  # initialize a local git, and register the git remote path
154
- project = mlrun.new_project("myproj", "./", init_git=True,
155
- remote="git://github.com/mlrun/project-demo.git",
156
- 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
+ )
157
173
  project.run("main", watch=True)
158
174
 
159
175
 
160
176
  example using project_setup.py to init the project objects::
161
177
 
162
178
  def setup(project):
163
- project.set_function('prep_data.py', 'prep-data', image='mlrun/mlrun', handler='prep_data')
164
- project.set_function('hub://auto-trainer', 'train')
165
- project.set_artifact('data', Artifact(target_path=data_url))
166
- 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")
167
185
  return project
168
186
 
169
187
 
@@ -180,6 +198,7 @@ def new_project(
180
198
  :param overwrite: overwrite project using 'cascade' deletion strategy (deletes project resources)
181
199
  if project with name exists
182
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
183
202
 
184
203
  :returns: project object
185
204
  """
@@ -192,14 +211,16 @@ def new_project(
192
211
  "Unsupported option, cannot use subpath argument with project templates"
193
212
  )
194
213
  if from_template.endswith(".yaml"):
195
- project = _load_project_file(from_template, name, secrets)
214
+ project = _load_project_file(
215
+ from_template, name, secrets, allow_cross_project=True
216
+ )
196
217
  elif from_template.startswith("git://"):
197
218
  clone_git(from_template, context, secrets, clone=True)
198
219
  shutil.rmtree(path.join(context, ".git"))
199
- project = _load_project_dir(context, name)
220
+ project = _load_project_dir(context, name, allow_cross_project=True)
200
221
  elif from_template.endswith(".zip"):
201
222
  clone_zip(from_template, context, secrets)
202
- project = _load_project_dir(context, name)
223
+ project = _load_project_dir(context, name, allow_cross_project=True)
203
224
  else:
204
225
  raise ValueError("template must be a path to .yaml or .zip file")
205
226
  project.metadata.name = name
@@ -226,6 +247,10 @@ def new_project(
226
247
  project.spec.origin_url = url
227
248
  if description:
228
249
  project.spec.description = description
250
+
251
+ if default_function_node_selector:
252
+ project.spec.default_function_node_selector = default_function_node_selector
253
+
229
254
  if parameters:
230
255
  # Enable setting project parameters at load time, can be used to customize the project_setup
231
256
  for key, val in parameters.items():
@@ -276,6 +301,7 @@ def load_project(
276
301
  save: bool = True,
277
302
  sync_functions: bool = False,
278
303
  parameters: dict = None,
304
+ allow_cross_project: bool = None,
279
305
  ) -> "MlrunProject":
280
306
  """Load an MLRun project from git or tar or dir
281
307
 
@@ -289,7 +315,7 @@ def load_project(
289
315
  # When using git as the url source the context directory must be an empty or
290
316
  # non-existent folder as the git repo will be cloned there
291
317
  project = load_project("./demo_proj", "git://github.com/mlrun/project-demo.git")
292
- project.run("main", arguments={'data': data_url})
318
+ project.run("main", arguments={"data": data_url})
293
319
 
294
320
 
295
321
  project_setup.py example::
@@ -322,6 +348,8 @@ def load_project(
322
348
  :param save: whether to save the created project and artifact in the DB
323
349
  :param sync_functions: sync the project's functions into the project object (will be saved to the DB if save=True)
324
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
325
353
 
326
354
  :returns: project object
327
355
  """
@@ -337,7 +365,7 @@ def load_project(
337
365
  if url:
338
366
  url = str(url) # to support path objects
339
367
  if is_yaml_path(url):
340
- project = _load_project_file(url, name, secrets)
368
+ project = _load_project_file(url, name, secrets, allow_cross_project)
341
369
  project.spec.context = context
342
370
  elif url.startswith("git://"):
343
371
  url, repo = clone_git(url, context, secrets, clone)
@@ -364,7 +392,7 @@ def load_project(
364
392
  repo, url = init_repo(context, url, init_git)
365
393
 
366
394
  if not project:
367
- project = _load_project_dir(context, name, subpath)
395
+ project = _load_project_dir(context, name, subpath, allow_cross_project)
368
396
 
369
397
  if not project.metadata.name:
370
398
  raise ValueError("Project name must be specified")
@@ -418,6 +446,7 @@ def get_or_create_project(
418
446
  from_template: str = None,
419
447
  save: bool = True,
420
448
  parameters: dict = None,
449
+ allow_cross_project: bool = None,
421
450
  ) -> "MlrunProject":
422
451
  """Load a project from MLRun DB, or create/import if it does not exist
423
452
 
@@ -428,9 +457,11 @@ def get_or_create_project(
428
457
  Usage example::
429
458
 
430
459
  # load project from the DB (if exist) or the source repo
431
- 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
+ )
432
463
  project.pull("development") # pull the latest code from git
433
- project.run("main", arguments={'data': data_url}) # run the workflow "main"
464
+ project.run("main", arguments={"data": data_url}) # run the workflow "main"
434
465
 
435
466
 
436
467
  project_setup.py example::
@@ -460,12 +491,12 @@ def get_or_create_project(
460
491
  :param from_template: path to project YAML file that will be used as from_template (for new projects)
461
492
  :param save: whether to save the created project in the DB
462
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
463
496
 
464
497
  :returns: project object
465
498
  """
466
499
  context = context or "./"
467
- spec_path = path.join(context, subpath or "", "project.yaml")
468
- load_from_path = url or path.isfile(spec_path)
469
500
  try:
470
501
  # load project from the DB.
471
502
  # use `name` as `url` as we load the project from the DB
@@ -481,17 +512,26 @@ def get_or_create_project(
481
512
  # only loading project from db so no need to save it
482
513
  save=False,
483
514
  parameters=parameters,
515
+ allow_cross_project=allow_cross_project,
484
516
  )
485
- logger.info("Project loaded successfully", project_name=name)
517
+ logger.info("Project loaded successfully", project_name=project.name)
486
518
  return project
487
-
488
519
  except mlrun.errors.MLRunNotFoundError:
489
- 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
+ )
490
523
 
524
+ spec_path = path.join(context, subpath or "", "project.yaml")
525
+ load_from_path = url or path.isfile(spec_path)
491
526
  # do not nest under "try" or else the exceptions raised below will be logged along with the "not found" message
492
527
  if load_from_path:
493
528
  # loads a project from archive or local project.yaml
494
- 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
+ )
495
535
  project = load_project(
496
536
  context,
497
537
  url,
@@ -503,11 +543,12 @@ def get_or_create_project(
503
543
  user_project=user_project,
504
544
  save=save,
505
545
  parameters=parameters,
546
+ allow_cross_project=allow_cross_project,
506
547
  )
507
548
 
508
549
  logger.info(
509
550
  "Project loaded successfully",
510
- project_name=name,
551
+ project_name=project.name,
511
552
  path=url or context,
512
553
  stored_in_db=save,
513
554
  )
@@ -525,7 +566,9 @@ def get_or_create_project(
525
566
  save=save,
526
567
  parameters=parameters,
527
568
  )
528
- 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
+ )
529
572
  return project
530
573
 
531
574
 
@@ -563,6 +606,10 @@ def _run_project_setup(
563
606
  if hasattr(mod, "setup"):
564
607
  try:
565
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
+ )
566
613
  except Exception as exc:
567
614
  logger.error(
568
615
  "Failed to run project_setup script",
@@ -573,11 +620,13 @@ def _run_project_setup(
573
620
  if save:
574
621
  project.save()
575
622
  else:
576
- 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
+ )
577
626
  return project
578
627
 
579
628
 
580
- def _load_project_dir(context, name="", subpath=""):
629
+ def _load_project_dir(context, name="", subpath="", allow_cross_project=None):
581
630
  subpath_str = subpath or ""
582
631
 
583
632
  # support both .yaml and .yml file extensions
@@ -591,7 +640,7 @@ def _load_project_dir(context, name="", subpath=""):
591
640
  with open(project_file_path) as fp:
592
641
  data = fp.read()
593
642
  struct = yaml.load(data, Loader=yaml.FullLoader)
594
- project = _project_instance_from_struct(struct, name)
643
+ project = _project_instance_from_struct(struct, name, allow_cross_project)
595
644
  project.spec.context = context
596
645
  elif function_files := glob.glob(function_file_path):
597
646
  function_path = function_files[0]
@@ -661,22 +710,45 @@ def _load_project_from_db(url, secrets, user_project=False):
661
710
 
662
711
  def _delete_project_from_db(project_name, secrets, deletion_strategy):
663
712
  db = mlrun.db.get_run_db(secrets=secrets)
664
- return db.delete_project(project_name, deletion_strategy=deletion_strategy)
713
+ db.delete_project(project_name, deletion_strategy=deletion_strategy)
665
714
 
666
715
 
667
- def _load_project_file(url, name="", secrets=None):
716
+ def _load_project_file(url, name="", secrets=None, allow_cross_project=None):
668
717
  try:
669
718
  obj = get_object(url, secrets)
670
719
  except FileNotFoundError as exc:
671
720
  raise FileNotFoundError(f"cant find project file at {url}") from exc
672
721
  struct = yaml.load(obj, Loader=yaml.FullLoader)
673
- return _project_instance_from_struct(struct, name)
674
-
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
+ )
675
735
 
676
- def _project_instance_from_struct(struct, name):
677
- struct.setdefault("metadata", {})["name"] = name or struct.get("metadata", {}).get(
678
- "name", ""
679
- )
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
680
752
  return MlrunProject.from_dict(struct)
681
753
 
682
754
 
@@ -759,6 +831,7 @@ class ProjectSpec(ModelObj):
759
831
  default_image=None,
760
832
  build=None,
761
833
  custom_packagers: list[tuple[str, bool]] = None,
834
+ default_function_node_selector=None,
762
835
  ):
763
836
  self.repo = None
764
837
 
@@ -798,6 +871,7 @@ class ProjectSpec(ModelObj):
798
871
  # in a tuple where the first index is the packager module's path (str) and the second is a flag (bool) for
799
872
  # whether it is mandatory for a run (raise exception on collection error) or not.
800
873
  self.custom_packagers = custom_packagers or []
874
+ self._default_function_node_selector = default_function_node_selector or None
801
875
 
802
876
  @property
803
877
  def source(self) -> str:
@@ -933,19 +1007,29 @@ class ProjectSpec(ModelObj):
933
1007
 
934
1008
  artifacts_dict = {}
935
1009
  for artifact in artifacts:
936
- 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:
937
1018
  raise ValueError("artifacts must be a dict or class")
938
- if isinstance(artifact, dict):
939
- # Support legacy artifacts
940
- if is_legacy_artifact(artifact) or _is_imported_artifact(artifact):
941
- key = artifact.get("key")
942
- else:
943
- key = artifact.get("metadata").get("key", "")
944
- if not key:
945
- raise ValueError('artifacts "key" must be specified')
946
- else:
1019
+ elif is_artifact_model:
947
1020
  key = artifact.key
948
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')
949
1033
 
950
1034
  artifacts_dict[key] = artifact
951
1035
 
@@ -962,6 +1046,14 @@ class ProjectSpec(ModelObj):
962
1046
  if key in self._artifacts:
963
1047
  del self._artifacts[key]
964
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
+
965
1057
  @property
966
1058
  def build(self) -> ImageBuilder:
967
1059
  return self._build
@@ -1219,6 +1311,14 @@ class MlrunProject(ModelObj):
1219
1311
  def description(self, description):
1220
1312
  self.spec.description = description
1221
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
+
1222
1322
  @property
1223
1323
  def default_image(self) -> str:
1224
1324
  return self.spec.default_image
@@ -1333,13 +1433,15 @@ class MlrunProject(ModelObj):
1333
1433
  example::
1334
1434
 
1335
1435
  # register a simple file artifact
1336
- project.set_artifact('data', target_path=data_url)
1436
+ project.set_artifact("data", target_path=data_url)
1337
1437
  # register a model artifact
1338
- 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
+ )
1339
1441
 
1340
1442
  # register a path to artifact package (will be imported on project load)
1341
1443
  # to generate such package use `artifact.export(target_path)`
1342
- project.set_artifact('model', 'https://mystuff.com/models/mymodel.zip')
1444
+ project.set_artifact("model", "https://mystuff.com/models/mymodel.zip")
1343
1445
 
1344
1446
  :param key: artifact key/name
1345
1447
  :param artifact: mlrun Artifact object/dict (or its subclasses) or path to artifact
@@ -1374,14 +1476,7 @@ class MlrunProject(ModelObj):
1374
1476
  artifact_path = mlrun.utils.helpers.template_artifact_path(
1375
1477
  self.spec.artifact_path or mlrun.mlconf.artifact_path, self.metadata.name
1376
1478
  )
1377
- # TODO: To correctly maintain the list of artifacts from an exported project,
1378
- # we need to maintain the different trees that generated them
1379
- producer = ArtifactProducer(
1380
- "project",
1381
- self.metadata.name,
1382
- self.metadata.name,
1383
- tag=self._get_hexsha() or str(uuid.uuid4()),
1384
- )
1479
+ project_tag = self._get_project_tag()
1385
1480
  for artifact_dict in self.spec.artifacts:
1386
1481
  if _is_imported_artifact(artifact_dict):
1387
1482
  import_from = artifact_dict["import_from"]
@@ -1401,8 +1496,23 @@ class MlrunProject(ModelObj):
1401
1496
  artifact.src_path = path.join(
1402
1497
  self.spec.get_code_path(), artifact.src_path
1403
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
1404
1510
  artifact_manager.log_artifact(
1405
- 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,
1406
1516
  )
1407
1517
 
1408
1518
  def _get_artifact_manager(self):
@@ -1444,7 +1554,7 @@ class MlrunProject(ModelObj):
1444
1554
  url = path.normpath(path.join(self.spec.get_code_path(), url))
1445
1555
 
1446
1556
  if (not in_context or check_path_in_context) and not path.isfile(url):
1447
- raise mlrun.errors.MLRunNotFoundError(f"{url} not found")
1557
+ raise FileNotFoundError(f"{url} not found")
1448
1558
 
1449
1559
  return url, in_context
1450
1560
 
@@ -1452,15 +1562,15 @@ class MlrunProject(ModelObj):
1452
1562
  self,
1453
1563
  item,
1454
1564
  body=None,
1455
- tag="",
1456
- local_path="",
1457
- artifact_path=None,
1458
- format=None,
1459
- upload=None,
1460
- labels=None,
1461
- 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,
1462
1572
  **kwargs,
1463
- ):
1573
+ ) -> Artifact:
1464
1574
  """Log an output artifact and optionally upload it to datastore
1465
1575
 
1466
1576
  If the artifact already exists with the same key and tag, it will be overwritten.
@@ -1485,7 +1595,9 @@ class MlrunProject(ModelObj):
1485
1595
  :param format: artifact file format: csv, png, ..
1486
1596
  :param tag: version tag
1487
1597
  :param target_path: absolute target path (instead of using artifact_path + local_path)
1488
- :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`.
1489
1601
  :param labels: a set of key/value labels to tag the artifact with
1490
1602
 
1491
1603
  :returns: artifact object
@@ -1497,12 +1609,20 @@ class MlrunProject(ModelObj):
1497
1609
  artifact_path = mlrun.utils.helpers.template_artifact_path(
1498
1610
  artifact_path, self.metadata.name
1499
1611
  )
1500
- producer = ArtifactProducer(
1501
- "project",
1502
- self.metadata.name,
1503
- self.metadata.name,
1504
- tag=self._get_hexsha() or str(uuid.uuid4()),
1505
- )
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
1506
1626
  item = am.log_artifact(
1507
1627
  producer,
1508
1628
  item,
@@ -1514,10 +1634,29 @@ class MlrunProject(ModelObj):
1514
1634
  upload=upload,
1515
1635
  labels=labels,
1516
1636
  target_path=target_path,
1637
+ project=self.metadata.name,
1638
+ is_retained_producer=is_retained_producer,
1517
1639
  **kwargs,
1518
1640
  )
1519
1641
  return item
1520
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
+
1521
1660
  def log_dataset(
1522
1661
  self,
1523
1662
  key,
@@ -1532,7 +1671,7 @@ class MlrunProject(ModelObj):
1532
1671
  stats=None,
1533
1672
  target_path="",
1534
1673
  extra_data=None,
1535
- label_column: str = None,
1674
+ label_column: Optional[str] = None,
1536
1675
  **kwargs,
1537
1676
  ) -> DatasetArtifact:
1538
1677
  """
@@ -1548,7 +1687,9 @@ class MlrunProject(ModelObj):
1548
1687
  "age": [42, 52, 36, 24, 73],
1549
1688
  "testScore": [25, 94, 57, 62, 70],
1550
1689
  }
1551
- 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
+ )
1552
1693
  project.log_dataset("mydf", df=df, stats=True)
1553
1694
 
1554
1695
  :param key: artifact key
@@ -1607,28 +1748,31 @@ class MlrunProject(ModelObj):
1607
1748
  artifact_path=None,
1608
1749
  upload=None,
1609
1750
  labels=None,
1610
- inputs: list[Feature] = None,
1611
- outputs: list[Feature] = None,
1612
- feature_vector: str = None,
1613
- 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,
1614
1755
  training_set=None,
1615
1756
  label_column=None,
1616
1757
  extra_data=None,
1617
1758
  **kwargs,
1618
- ):
1759
+ ) -> ModelArtifact:
1619
1760
  """Log a model artifact and optionally upload it to datastore
1620
1761
 
1621
1762
  If the model already exists with the same key and tag, it will be overwritten.
1622
1763
 
1623
1764
  example::
1624
1765
 
1625
- project.log_model("model", body=dumps(model),
1626
- model_file="model.pkl",
1627
- metrics=context.results,
1628
- training_set=training_df,
1629
- label_column='label',
1630
- feature_vector=feature_vector_uri,
1631
- 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
+ )
1632
1776
 
1633
1777
  :param key: artifact key or artifact class ()
1634
1778
  :param body: will use the body as the artifact content
@@ -1738,14 +1882,16 @@ class MlrunProject(ModelObj):
1738
1882
  artifact = get_artifact(spec)
1739
1883
  with open(f"{temp_dir}/_body", "rb") as fp:
1740
1884
  artifact.spec._body = fp.read()
1741
- artifact.target_path = ""
1742
1885
 
1743
1886
  # if the dataitem is not a file, it means we downloaded it from a remote source to a temp file,
1744
1887
  # so we need to remove it after we're done with it
1745
1888
  dataitem.remove_local()
1746
1889
 
1747
1890
  return self.log_artifact(
1748
- artifact, local_path=temp_dir, artifact_path=artifact_path
1891
+ artifact,
1892
+ local_path=temp_dir,
1893
+ artifact_path=artifact_path,
1894
+ upload=True,
1749
1895
  )
1750
1896
 
1751
1897
  else:
@@ -1763,10 +1909,18 @@ class MlrunProject(ModelObj):
1763
1909
  """
1764
1910
  context = context or self.spec.context
1765
1911
  if context:
1766
- 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
+ )
1767
1918
  else:
1768
1919
  project = _load_project_file(
1769
- 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,
1770
1924
  )
1771
1925
  project.spec.source = self.spec.source
1772
1926
  project.spec.repo = self.spec.repo
@@ -1795,7 +1949,10 @@ class MlrunProject(ModelObj):
1795
1949
  def set_model_monitoring_function(
1796
1950
  self,
1797
1951
  func: typing.Union[str, mlrun.runtimes.BaseRuntime, None] = None,
1798
- application_class: typing.Union[str, ModelMonitoringApplicationBase] = None,
1952
+ application_class: typing.Union[
1953
+ str,
1954
+ mm_app.ModelMonitoringApplicationBase,
1955
+ ] = None,
1799
1956
  name: str = None,
1800
1957
  image: str = None,
1801
1958
  handler=None,
@@ -1811,6 +1968,7 @@ class MlrunProject(ModelObj):
1811
1968
  call `fn.deploy()` where `fn` is the object returned by this method.
1812
1969
 
1813
1970
  examples::
1971
+
1814
1972
  project.set_model_monitoring_function(
1815
1973
  name="myApp", application_class="MyApp", image="mlrun/mlrun"
1816
1974
  )
@@ -1833,11 +1991,6 @@ class MlrunProject(ModelObj):
1833
1991
  monitoring application's constructor.
1834
1992
  """
1835
1993
 
1836
- if name in mm_constants.MonitoringFunctionNames.all():
1837
- raise mlrun.errors.MLRunInvalidArgumentError(
1838
- f"Application name can not be on of the following name : "
1839
- f"{mm_constants.MonitoringFunctionNames.all()}"
1840
- )
1841
1994
  function_object: RemoteRuntime = None
1842
1995
  (
1843
1996
  resolved_function_name,
@@ -1855,16 +2008,6 @@ class MlrunProject(ModelObj):
1855
2008
  requirements_file,
1856
2009
  **application_kwargs,
1857
2010
  )
1858
- models_names = "all"
1859
- function_object.set_label(
1860
- mm_constants.ModelMonitoringAppLabel.KEY,
1861
- mm_constants.ModelMonitoringAppLabel.VAL,
1862
- )
1863
- function_object.set_label("models", models_names)
1864
-
1865
- if not mlrun.mlconf.is_ce_mode():
1866
- function_object.apply(mlrun.mount_v3io())
1867
-
1868
2011
  # save to project spec
1869
2012
  self.spec.set_function(resolved_function_name, function_object, func)
1870
2013
 
@@ -1873,7 +2016,10 @@ class MlrunProject(ModelObj):
1873
2016
  def create_model_monitoring_function(
1874
2017
  self,
1875
2018
  func: str = None,
1876
- application_class: typing.Union[str, ModelMonitoringApplicationBase] = None,
2019
+ application_class: typing.Union[
2020
+ str,
2021
+ mm_app.ModelMonitoringApplicationBase,
2022
+ ] = None,
1877
2023
  name: str = None,
1878
2024
  image: str = None,
1879
2025
  handler: str = None,
@@ -1887,8 +2033,10 @@ class MlrunProject(ModelObj):
1887
2033
  Create a monitoring function object without setting it to the project
1888
2034
 
1889
2035
  examples::
1890
- project.create_model_monitoring_function(application_class_name="MyApp",
1891
- image="mlrun/mlrun", name="myApp")
2036
+
2037
+ project.create_model_monitoring_function(
2038
+ application_class_name="MyApp", image="mlrun/mlrun", name="myApp"
2039
+ )
1892
2040
 
1893
2041
  :param func: Code url, None refers to current Notebook
1894
2042
  :param name: Name of the function, can be specified with a tag to support
@@ -1907,6 +2055,7 @@ class MlrunProject(ModelObj):
1907
2055
  :param application_kwargs: Additional keyword arguments to be passed to the
1908
2056
  monitoring application's constructor.
1909
2057
  """
2058
+
1910
2059
  _, function_object, _ = self._instantiate_model_monitoring_function(
1911
2060
  func,
1912
2061
  application_class,
@@ -1923,49 +2072,40 @@ class MlrunProject(ModelObj):
1923
2072
 
1924
2073
  def _instantiate_model_monitoring_function(
1925
2074
  self,
1926
- func: typing.Union[str, mlrun.runtimes.BaseRuntime] = None,
1927
- application_class: typing.Union[str, ModelMonitoringApplicationBase] = None,
1928
- name: str = None,
1929
- image: str = None,
1930
- handler: str = None,
1931
- with_repo: bool = None,
1932
- tag: str = None,
1933
- 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,
1934
2087
  requirements_file: str = "",
1935
2088
  **application_kwargs,
1936
2089
  ) -> tuple[str, mlrun.runtimes.BaseRuntime, dict]:
2090
+ import mlrun.model_monitoring.api
2091
+
1937
2092
  function_object: RemoteRuntime = None
1938
2093
  kind = None
1939
2094
  if (isinstance(func, str) or func is None) and application_class is not None:
1940
- kind = "serving"
1941
- if func is None:
1942
- func = ""
1943
- func = mlrun.code_to_function(
1944
- 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,
1945
2100
  name=name,
1946
- project=self.metadata.name,
1947
- tag=tag,
1948
- kind=kind,
1949
2101
  image=image,
2102
+ tag=tag,
1950
2103
  requirements=requirements,
1951
2104
  requirements_file=requirements_file,
2105
+ **application_kwargs,
1952
2106
  )
1953
- graph = func.set_topology("flow")
1954
- if isinstance(application_class, str):
1955
- first_step = graph.to(
1956
- class_name=application_class, **application_kwargs
1957
- )
1958
- else:
1959
- first_step = graph.to(class_name=application_class)
1960
- first_step.to(
1961
- class_name=PushToMonitoringWriter(
1962
- project=self.metadata.name,
1963
- writer_application_name=mm_constants.MonitoringFunctionNames.WRITER,
1964
- stream_uri=None,
1965
- ),
1966
- ).respond()
1967
2107
  elif isinstance(func, str) and isinstance(handler, str):
1968
- kind = "nuclio"
2108
+ kind = mlrun.run.RuntimeKinds.nuclio
1969
2109
 
1970
2110
  (
1971
2111
  resolved_function_name,
@@ -1983,24 +2123,36 @@ class MlrunProject(ModelObj):
1983
2123
  requirements,
1984
2124
  requirements_file,
1985
2125
  )
1986
- models_names = "all"
1987
2126
  function_object.set_label(
1988
2127
  mm_constants.ModelMonitoringAppLabel.KEY,
1989
2128
  mm_constants.ModelMonitoringAppLabel.VAL,
1990
2129
  )
1991
- function_object.set_label("models", models_names)
1992
2130
 
1993
2131
  if not mlrun.mlconf.is_ce_mode():
1994
2132
  function_object.apply(mlrun.mount_v3io())
1995
2133
 
1996
2134
  return resolved_function_name, function_object, func
1997
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
+
1998
2146
  def enable_model_monitoring(
1999
2147
  self,
2000
2148
  default_controller_image: str = "mlrun/mlrun",
2001
2149
  base_period: int = 10,
2002
2150
  image: str = "mlrun/mlrun",
2151
+ *,
2003
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,
2004
2156
  ) -> None:
2005
2157
  """
2006
2158
  Deploy model monitoring application controller, writer and stream functions.
@@ -2010,16 +2162,19 @@ class MlrunProject(ModelObj):
2010
2162
  The stream function goal is to monitor the log of the data stream. It is triggered when a new log entry
2011
2163
  is detected. It processes the new events into statistics that are then written to statistics databases.
2012
2164
 
2013
-
2014
- :param default_controller_image: Deprecated.
2015
- :param base_period: The time period in minutes in which the model monitoring controller
2016
- function is triggered. By default, the base period is 10 minutes.
2017
- :param image: The image of the model monitoring controller, writer, monitoring
2018
- stream & histogram data drift functions, which are real time nuclio
2019
- functions. By default, the image is mlrun/mlrun.
2020
- :param deploy_histogram_data_drift_app: If true, deploy the default histogram-based data drift application.
2021
-
2022
- :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.
2023
2178
  """
2024
2179
  if default_controller_image != "mlrun/mlrun":
2025
2180
  # TODO: Remove this in 1.9.0
@@ -2029,39 +2184,71 @@ class MlrunProject(ModelObj):
2029
2184
  FutureWarning,
2030
2185
  )
2031
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
+
2032
2193
  db = mlrun.db.get_run_db(secrets=self._secrets)
2033
2194
  db.enable_model_monitoring(
2034
2195
  project=self.name,
2035
2196
  image=image,
2036
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,
2037
2201
  )
2038
- if deploy_histogram_data_drift_app:
2039
- fn = self.set_model_monitoring_function(
2040
- func=str(
2041
- pathlib.Path(__file__).parent.parent
2042
- / "model_monitoring/applications/histogram_data_drift.py"
2043
- ),
2044
- name=mm_constants.MLRUN_HISTOGRAM_DATA_DRIFT_APP_NAME,
2045
- application_class="HistogramDataDriftApplication",
2046
- 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]
2047
2233
  )
2048
- fn.deploy()
2049
2234
 
2050
2235
  def update_model_monitoring_controller(
2051
2236
  self,
2052
2237
  base_period: int = 10,
2053
2238
  image: str = "mlrun/mlrun",
2239
+ *,
2240
+ wait_for_deployment: bool = False,
2054
2241
  ) -> None:
2055
2242
  """
2056
2243
  Redeploy model monitoring application controller functions.
2057
2244
 
2058
-
2059
- :param base_period: The time period in minutes in which the model monitoring controller function
2060
- is triggered. By default, the base period is 10 minutes.
2061
- :param image: The image of the model monitoring controller, writer & monitoring
2062
- stream functions, which are real time nuclio functions.
2063
- By default, the image is mlrun/mlrun.
2064
- :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.
2065
2252
  """
2066
2253
  db = mlrun.db.get_run_db(secrets=self._secrets)
2067
2254
  db.update_model_monitoring_controller(
@@ -2070,26 +2257,78 @@ class MlrunProject(ModelObj):
2070
2257
  image=image,
2071
2258
  )
2072
2259
 
2073
- 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
+
2074
2299
  db = mlrun.db.get_run_db(secrets=self._secrets)
2075
- db.delete_function(
2076
- project=self.name,
2077
- name=mm_constants.MonitoringFunctionNames.APPLICATION_CONTROLLER,
2078
- )
2079
- db.delete_function(
2300
+ succeed = db.disable_model_monitoring(
2080
2301
  project=self.name,
2081
- name=mm_constants.MonitoringFunctionNames.WRITER,
2082
- )
2083
- db.delete_function(
2084
- project=self.name,
2085
- 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,
2086
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
+ )
2087
2326
 
2088
2327
  def set_function(
2089
2328
  self,
2090
2329
  func: typing.Union[str, mlrun.runtimes.BaseRuntime] = None,
2091
2330
  name: str = "",
2092
- kind: str = "",
2331
+ kind: str = "job",
2093
2332
  image: str = None,
2094
2333
  handler: str = None,
2095
2334
  with_repo: bool = None,
@@ -2097,31 +2336,52 @@ class MlrunProject(ModelObj):
2097
2336
  requirements: typing.Union[str, list[str]] = None,
2098
2337
  requirements_file: str = "",
2099
2338
  ) -> mlrun.runtimes.BaseRuntime:
2100
- """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.
2101
2342
 
2102
- function can be provided as an object (func) or a .py/.ipynb/.yaml url
2103
- 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>)``.
2104
2349
 
2105
- object (s3://, v3io://, ..)
2106
- MLRun DB e.g. db://project/func:ver
2107
- functions hub/market: e.g. hub://auto-trainer:master
2350
+ Support URL prefixes:
2108
2351
 
2109
- 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::
2110
2357
 
2111
2358
  proj.set_function(func_object)
2112
- proj.set_function('./src/mycode.py', 'ingest',
2113
- image='myrepo/ing:latest', with_repo=True)
2114
- proj.set_function('http://.../mynb.ipynb', 'train')
2115
- proj.set_function('./func.yaml')
2116
- 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")
2117
2365
 
2118
- # set function requirements
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")
2119
2377
 
2120
- # by providing a list of packages
2121
- proj.set_function('my.py', requirements=["requests", "pandas"])
2378
+ # Set function requirements
2122
2379
 
2123
- # by providing a path to a pip requirements file
2124
- proj.set_function('my.py', requirements="requirements.txt")
2380
+ # By providing a list of packages
2381
+ proj.set_function("my.py", requirements=["requests", "pandas"])
2382
+
2383
+ # By providing a path to a pip requirements file
2384
+ proj.set_function("my.py", requirements="requirements.txt")
2125
2385
 
2126
2386
  :param func: Function object or spec/code url, None refers to current Notebook
2127
2387
  :param name: Name of the function (under the project), can be specified with a tag to support
@@ -2132,14 +2392,15 @@ class MlrunProject(ModelObj):
2132
2392
  Default: job
2133
2393
  :param image: Docker image to be used, can also be specified in the function object/yaml
2134
2394
  :param handler: Default function handler to invoke (can only be set with .py/.ipynb files)
2135
- :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).
2136
2397
  :param tag: Function version tag to set (none for current or 'latest')
2137
2398
  Specifying a tag as a parameter will update the project's tagged function
2138
2399
  (myfunc:v1) and the untagged function (myfunc)
2139
2400
  :param requirements: A list of python packages
2140
2401
  :param requirements_file: Path to a python requirements file
2141
2402
 
2142
- :returns: function object
2403
+ :returns: :py:class:`~mlrun.runtimes.BaseRuntime`
2143
2404
  """
2144
2405
  (
2145
2406
  resolved_function_name,
@@ -2173,19 +2434,20 @@ class MlrunProject(ModelObj):
2173
2434
  requirements: typing.Union[str, list[str]] = None,
2174
2435
  requirements_file: str = "",
2175
2436
  ) -> tuple[str, str, mlrun.runtimes.BaseRuntime, dict]:
2176
- 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
+ ):
2177
2442
  # if function path is not provided and it is not a module (no ".")
2178
2443
  # use the current notebook as default
2179
- if not is_ipython:
2180
- raise ValueError(
2181
- "Function path or module must be specified (when not running inside a Notebook)"
2182
- )
2183
- from IPython import get_ipython
2444
+ if is_jupyter:
2445
+ from IPython import get_ipython
2184
2446
 
2185
- kernel = get_ipython()
2186
- func = nuclio.utils.notebook_file_name(kernel)
2187
- if func.startswith(path.abspath(self.spec.context)):
2188
- 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)
2189
2451
 
2190
2452
  func = func or ""
2191
2453
 
@@ -2281,22 +2543,39 @@ class MlrunProject(ModelObj):
2281
2543
  """
2282
2544
  self.spec.remove_function(name)
2283
2545
 
2284
- def remove_model_monitoring_function(self, name):
2285
- """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
2286
2548
 
2287
- :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)
2288
2550
  """
2289
- function = self.get_function(key=name)
2290
- if (
2291
- function.metadata.labels.get(mm_constants.ModelMonitoringAppLabel.KEY)
2292
- == mm_constants.ModelMonitoringAppLabel.VAL
2293
- ):
2294
- self.remove_function(name=name)
2295
- mlrun.db.get_run_db().delete_function(name=name.lower())
2296
- 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
+ )
2297
2575
  else:
2298
- raise logger.error(
2299
- 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,
2300
2579
  )
2301
2580
 
2302
2581
  def get_function(
@@ -2402,13 +2681,47 @@ class MlrunProject(ModelObj):
2402
2681
  clone_zip(url, self.spec.context, self._secrets)
2403
2682
 
2404
2683
  def create_remote(self, url, name="origin", branch=None):
2405
- """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.
2406
2690
 
2407
2691
  :param url: remote git url
2408
2692
  :param name: name for the remote (default is 'origin')
2409
2693
  :param branch: Git branch to use as source
2410
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
+ """
2411
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
+ )
2412
2725
  self.spec.repo.create_remote(name, url=url)
2413
2726
  url = url.replace("https://", "git://")
2414
2727
  if not branch:
@@ -2421,6 +2734,22 @@ class MlrunProject(ModelObj):
2421
2734
  self.spec._source = self.spec.source or url
2422
2735
  self.spec.origin_url = self.spec.origin_url or url
2423
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
+
2424
2753
  def _ensure_git_repo(self):
2425
2754
  if self.spec.repo:
2426
2755
  return
@@ -2503,47 +2832,104 @@ class MlrunProject(ModelObj):
2503
2832
  secrets=secrets or {},
2504
2833
  )
2505
2834
 
2506
- def sync_functions(self, names: list = None, always=True, save=False):
2507
- """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
+ """
2508
2864
  if self._initialized and not always:
2509
2865
  return self.spec._function_objects
2510
2866
 
2511
- funcs = self.spec._function_objects
2867
+ functions = self.spec._function_objects
2512
2868
  if not names:
2513
2869
  names = self.spec._function_definitions.keys()
2514
- funcs = {}
2870
+ functions = {}
2871
+
2515
2872
  origin = mlrun.runtimes.utils.add_code_metadata(self.spec.context)
2516
2873
  for name in names:
2517
- f = self.spec._function_definitions.get(name)
2518
- if not f:
2519
- 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
+ )
2520
2888
  # If this function is already available locally, don't recreate it unless always=True
2521
- if (
2522
- isinstance(
2523
- self.spec._function_objects.get(name, None),
2524
- mlrun.runtimes.base.BaseRuntime,
2525
- )
2526
- and not always
2527
- ):
2528
- funcs[name] = self.spec._function_objects[name]
2889
+ if is_base_runtime and not always:
2890
+ functions[name] = function_object
2529
2891
  continue
2530
- if hasattr(f, "to_dict"):
2531
- name, func = _init_function_from_obj(f, self, name)
2532
- else:
2533
- if not isinstance(f, dict):
2534
- 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):
2535
2897
  try:
2536
- name, func = _init_function_from_dict(f, self, name)
2898
+ name, func = _init_function_from_dict(
2899
+ function_definition, self, name
2900
+ )
2537
2901
  except FileNotFoundError as exc:
2538
- raise mlrun.errors.MLRunMissingDependencyError(
2539
- f"File {exc.filename} not found while syncing project functions"
2540
- ) 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
+
2541
2927
  func.spec.build.code_origin = origin
2542
- funcs[name] = func
2928
+ functions[name] = func
2543
2929
  if save:
2544
2930
  func.save(versioned=False)
2545
2931
 
2546
- self.spec._function_objects = funcs
2932
+ self.spec._function_objects = functions
2547
2933
  self._initialized = True
2548
2934
  return self.spec._function_objects
2549
2935
 
@@ -2552,9 +2938,9 @@ class MlrunProject(ModelObj):
2552
2938
 
2553
2939
  read secrets from a source provider to be used in workflows, example::
2554
2940
 
2555
- proj.with_secrets('file', 'file.txt')
2556
- proj.with_secrets('inline', {'key': 'val'})
2557
- 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_")
2558
2944
 
2559
2945
  Vault secret source has several options::
2560
2946
 
@@ -2565,7 +2951,7 @@ class MlrunProject(ModelObj):
2565
2951
  The 2nd option uses the current project name as context.
2566
2952
  Can also use empty secret list::
2567
2953
 
2568
- proj.with_secrets('vault', [])
2954
+ proj.with_secrets("vault", [])
2569
2955
 
2570
2956
  This will enable access to all secrets in vault registered to the current project.
2571
2957
 
@@ -2596,17 +2982,20 @@ class MlrunProject(ModelObj):
2596
2982
  file_path: str = None,
2597
2983
  provider: typing.Union[str, mlrun.common.schemas.SecretProviderName] = None,
2598
2984
  ):
2599
- """set project secrets from dict or secrets env file
2985
+ """
2986
+ Set project secrets from dict or secrets env file
2600
2987
  when using a secrets file it should have lines in the form KEY=VALUE, comment line start with "#"
2601
2988
  V3IO paths/credentials and MLrun service API address are dropped from the secrets
2602
2989
 
2603
- example secrets file::
2990
+ example secrets file:
2991
+
2992
+ .. code-block:: shell
2604
2993
 
2605
2994
  # this is an env file
2606
- AWS_ACCESS_KEY_ID-XXXX
2995
+ AWS_ACCESS_KEY_ID=XXXX
2607
2996
  AWS_SECRET_ACCESS_KEY=YYYY
2608
2997
 
2609
- usage::
2998
+ usage:
2610
2999
 
2611
3000
  # read env vars from dict or file and set as project secrets
2612
3001
  project.set_secrets({"SECRET1": "value"})
@@ -2685,41 +3074,50 @@ class MlrunProject(ModelObj):
2685
3074
  source: str = None,
2686
3075
  cleanup_ttl: int = None,
2687
3076
  notifications: list[mlrun.model.Notification] = None,
3077
+ workflow_runner_node_selector: typing.Optional[dict[str, str]] = None,
2688
3078
  ) -> _PipelineRunStatus:
2689
- """run a workflow using kubeflow pipelines
2690
-
2691
- :param name: name of the workflow
2692
- :param workflow_path:
2693
- url to a workflow file, if not a project workflow
2694
- :param arguments:
2695
- kubeflow pipelines arguments (parameters)
2696
- :param artifact_path:
2697
- target path/url for workflow artifacts, the string
2698
- '{{workflow.uid}}' will be replaced by workflow id
2699
- :param workflow_handler:
2700
- workflow function handler (for running workflow function directly)
2701
- :param namespace: kubernetes namespace if other than default
2702
- :param sync: force functions sync before run
2703
- :param watch: wait for pipeline completion
2704
- :param dirty: allow running the workflow when the git repo is dirty
2705
- :param engine: workflow engine running the workflow.
2706
- supported values are 'kfp' (default), 'local' or 'remote'.
2707
- for setting engine for remote running use 'remote:local' or 'remote:kfp'.
2708
- :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())
2709
3095
  :param schedule: ScheduleCronTrigger class instance or a standard crontab expression string
2710
3096
  (which will be converted to the class using its `from_crontab` constructor),
2711
3097
  see this link for help:
2712
3098
  https://apscheduler.readthedocs.io/en/3.x/modules/triggers/cron.html#module-apscheduler.triggers.cron
2713
- for using the pre-defined workflow's schedule, set `schedule=True`
2714
- :param timeout: timeout in seconds to wait for pipeline completion (watch will be activated)
2715
- :param source: remote source to use instead of the actual `project.spec.source` (used when engine is remote).
2716
- 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
+
2717
3110
  :param cleanup_ttl:
2718
- 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
2719
3112
  workflow and all its resources are deleted)
2720
3113
  :param notifications:
2721
- list of notifications to send for workflow completion
2722
- :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
2723
3121
  """
2724
3122
 
2725
3123
  arguments = arguments or {}
@@ -2736,12 +3134,14 @@ class MlrunProject(ModelObj):
2736
3134
  "Remote repo is not defined, use .create_remote() + push()"
2737
3135
  )
2738
3136
 
2739
- self.sync_functions(always=sync)
2740
- if not self.spec._function_objects:
2741
- raise ValueError(
2742
- "There are no functions in the project."
2743
- " Make sure you've set your functions with project.set_function()."
2744
- )
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
+ )
2745
3145
 
2746
3146
  if not name and not workflow_path and not workflow_handler:
2747
3147
  raise ValueError("Workflow name, path, or handler must be specified")
@@ -2775,9 +3175,23 @@ class MlrunProject(ModelObj):
2775
3175
  engine = "remote"
2776
3176
  # The default engine is kfp if not given:
2777
3177
  workflow_engine = get_workflow_engine(engine or workflow_spec.engine, local)
2778
- if not inner_engine and engine == "remote":
2779
- 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
2780
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
+ )
2781
3195
 
2782
3196
  run = workflow_engine.run(
2783
3197
  self,
@@ -2791,7 +3205,7 @@ class MlrunProject(ModelObj):
2791
3205
  notifications=notifications,
2792
3206
  )
2793
3207
  # run is None when scheduling
2794
- if run and run.state == mlrun.run.RunStatuses.failed:
3208
+ if run and run.state == mlrun_pipelines.common.models.RunStatuses.failed:
2795
3209
  return run
2796
3210
  if not workflow_spec.schedule:
2797
3211
  # Failure and schedule messages already logged
@@ -2800,14 +3214,17 @@ class MlrunProject(ModelObj):
2800
3214
  )
2801
3215
  workflow_spec.clear_tmp()
2802
3216
  if (timeout or watch) and not workflow_spec.schedule:
3217
+ run_status_kwargs = {}
2803
3218
  status_engine = run._engine
2804
3219
  # run's engine gets replaced with inner engine if engine is remote,
2805
3220
  # so in that case we need to get the status from the remote engine manually
2806
- # TODO: support watch for remote:local
2807
- if engine == "remote" and status_engine.engine != "local":
3221
+ if workflow_engine.engine == "remote":
2808
3222
  status_engine = _RemoteRunner
3223
+ run_status_kwargs["inner_engine"] = run._engine
2809
3224
 
2810
- 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
+ )
2811
3228
  return run
2812
3229
 
2813
3230
  def save_workflow(self, name, target, artifact_path=None, ttl=None):
@@ -2907,43 +3324,66 @@ class MlrunProject(ModelObj):
2907
3324
 
2908
3325
  def set_model_monitoring_credentials(
2909
3326
  self,
2910
- access_key: str = None,
2911
- endpoint_store_connection: str = None,
2912
- 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,
2913
3332
  ):
2914
- """Set the credentials that will be used by the project's model monitoring
2915
- infrastructure functions.
2916
-
2917
- :param access_key: Model Monitoring access key for managing user permissions
2918
- :param access_key: Model Monitoring access key for managing user permissions
2919
- :param endpoint_store_connection: Endpoint store connection string
2920
- :param stream_path: Path to the model monitoring stream
2921
3333
  """
2922
-
2923
- secrets_dict = {}
2924
- if access_key:
2925
- secrets_dict[
2926
- mlrun.common.schemas.model_monitoring.ProjectSecretKeys.ACCESS_KEY
2927
- ] = access_key
2928
-
2929
- if endpoint_store_connection:
2930
- secrets_dict[
2931
- mlrun.common.schemas.model_monitoring.ProjectSecretKeys.ENDPOINT_STORE_CONNECTION
2932
- ] = endpoint_store_connection
2933
-
2934
- if stream_path:
2935
- if stream_path.startswith("kafka://") and "?topic" in stream_path:
2936
- raise mlrun.errors.MLRunInvalidArgumentError(
2937
- "Custom kafka topic is not allowed"
2938
- )
2939
- secrets_dict[
2940
- mlrun.common.schemas.model_monitoring.ProjectSecretKeys.STREAM_PATH
2941
- ] = stream_path
2942
-
2943
- self.set_secrets(
2944
- secrets=secrets_dict,
2945
- 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,
2946
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
+ )
2947
3387
 
2948
3388
  def run_function(
2949
3389
  self,
@@ -2968,7 +3408,8 @@ class MlrunProject(ModelObj):
2968
3408
  notifications: list[mlrun.model.Notification] = None,
2969
3409
  returns: Optional[list[Union[str, dict[str, str]]]] = None,
2970
3410
  builder_env: Optional[dict] = None,
2971
- ) -> typing.Union[mlrun.model.RunObject, kfp.dsl.ContainerOp]:
3411
+ reset_on_run: bool = None,
3412
+ ) -> typing.Union[mlrun.model.RunObject, PipelineNodeWrapper]:
2972
3413
  """Run a local or remote task as part of a local/kubeflow pipeline
2973
3414
 
2974
3415
  example (use with project)::
@@ -2980,8 +3421,11 @@ class MlrunProject(ModelObj):
2980
3421
 
2981
3422
  # run functions (refer to them by name)
2982
3423
  run1 = project.run_function("myfunc", params={"x": 7})
2983
- run2 = project.run_function("train", params={"label_columns": LABELS},
2984
- inputs={"dataset":run1.outputs["data"]})
3424
+ run2 = project.run_function(
3425
+ "train",
3426
+ params={"label_columns": LABELS},
3427
+ inputs={"dataset": run1.outputs["data"]},
3428
+ )
2985
3429
 
2986
3430
  :param function: name of the function (in the project) or function object
2987
3431
  :param handler: name of the function handler
@@ -3020,8 +3464,13 @@ class MlrunProject(ModelObj):
3020
3464
  * A dictionary of configurations to use when logging. Further info per object type and
3021
3465
  artifact type can be given there. The artifact key must appear in the dictionary as
3022
3466
  "key": "the_key".
3023
- :param builder_env: env vars dict for source archive config/credentials e.g. builder_env={"GIT_TOKEN": token}
3024
- :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
3025
3474
  """
3026
3475
  return run_function(
3027
3476
  function,
@@ -3046,6 +3495,7 @@ class MlrunProject(ModelObj):
3046
3495
  notifications=notifications,
3047
3496
  returns=returns,
3048
3497
  builder_env=builder_env,
3498
+ reset_on_run=reset_on_run,
3049
3499
  )
3050
3500
 
3051
3501
  def build_function(
@@ -3064,7 +3514,7 @@ class MlrunProject(ModelObj):
3064
3514
  requirements_file: str = None,
3065
3515
  extra_args: str = None,
3066
3516
  force_build: bool = False,
3067
- ) -> typing.Union[BuildStatus, kfp.dsl.ContainerOp]:
3517
+ ) -> typing.Union[BuildStatus, PipelineNodeWrapper]:
3068
3518
  """deploy ML function, build container with its dependencies
3069
3519
 
3070
3520
  :param function: name of the function (in the project) or function object
@@ -3118,6 +3568,7 @@ class MlrunProject(ModelObj):
3118
3568
  requirements_file: str = None,
3119
3569
  builder_env: dict = None,
3120
3570
  extra_args: str = None,
3571
+ source_code_target_dir: str = None,
3121
3572
  ):
3122
3573
  """specify builder configuration for the project
3123
3574
 
@@ -3138,6 +3589,8 @@ class MlrunProject(ModelObj):
3138
3589
  e.g. builder_env={"GIT_TOKEN": token}, does not work yet in KFP
3139
3590
  :param extra_args: A string containing additional builder arguments in the format of command-line options,
3140
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`)
3141
3594
  """
3142
3595
  if not overwrite_build_params:
3143
3596
  # TODO: change overwrite_build_params default to True in 1.8.0
@@ -3161,6 +3614,7 @@ class MlrunProject(ModelObj):
3161
3614
  overwrite=overwrite_build_params,
3162
3615
  builder_env=builder_env,
3163
3616
  extra_args=extra_args,
3617
+ source_code_target_dir=source_code_target_dir,
3164
3618
  )
3165
3619
 
3166
3620
  if set_as_default and image != self.default_image:
@@ -3171,7 +3625,6 @@ class MlrunProject(ModelObj):
3171
3625
  image: str = None,
3172
3626
  set_as_default: bool = True,
3173
3627
  with_mlrun: bool = None,
3174
- skip_deployed: bool = False,
3175
3628
  base_image: str = None,
3176
3629
  commands: list = None,
3177
3630
  secret_name: str = None,
@@ -3182,7 +3635,7 @@ class MlrunProject(ModelObj):
3182
3635
  requirements_file: str = None,
3183
3636
  extra_args: str = None,
3184
3637
  target_dir: str = None,
3185
- ) -> typing.Union[BuildStatus, kfp.dsl.ContainerOp]:
3638
+ ) -> typing.Union[BuildStatus, PipelineNodeWrapper]:
3186
3639
  """Builder docker image for the project, based on the project's build config. Parameters allow to override
3187
3640
  the build config.
3188
3641
  If the project has a source configured and pull_at_runtime is not configured, this source will be cloned to the
@@ -3192,7 +3645,6 @@ class MlrunProject(ModelObj):
3192
3645
  used. If not set, the `mlconf.default_project_image_name` value will be used
3193
3646
  :param set_as_default: set `image` to be the project's default image (default False)
3194
3647
  :param with_mlrun: add the current mlrun package to the container build
3195
- :param skip_deployed: *Deprecated* parameter is ignored
3196
3648
  :param base_image: base image name/path (commands and source code will be added to it) defaults to
3197
3649
  mlrun.mlconf.default_base_image
3198
3650
  :param commands: list of docker build (RUN) commands e.g. ['pip install pandas']
@@ -3207,7 +3659,7 @@ class MlrunProject(ModelObj):
3207
3659
  * False: The new params are merged with the existing
3208
3660
  * True: The existing params are replaced by the new ones
3209
3661
  :param extra_args: A string containing additional builder arguments in the format of command-line options,
3210
- e.g. extra_args="--skip-tls-verify --build-arg A=val"r
3662
+ e.g. extra_args="--skip-tls-verify --build-arg A=val"
3211
3663
  :param target_dir: Path on the image where source code would be extracted (by default `/home/mlrun_code`)
3212
3664
  """
3213
3665
  if not base_image:
@@ -3217,14 +3669,6 @@ class MlrunProject(ModelObj):
3217
3669
  base_image=base_image,
3218
3670
  )
3219
3671
 
3220
- if skip_deployed:
3221
- warnings.warn(
3222
- "The 'skip_deployed' parameter is deprecated and will be removed in 1.7.0. "
3223
- "This parameter is ignored.",
3224
- # TODO: remove in 1.7.0
3225
- FutureWarning,
3226
- )
3227
-
3228
3672
  if not overwrite_build_params:
3229
3673
  # TODO: change overwrite_build_params default to True in 1.8.0
3230
3674
  warnings.warn(
@@ -3275,6 +3719,11 @@ class MlrunProject(ModelObj):
3275
3719
  force_build=True,
3276
3720
  )
3277
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
+
3278
3727
  try:
3279
3728
  mlrun.db.get_run_db(secrets=self._secrets).delete_function(
3280
3729
  name=function.metadata.name
@@ -3283,7 +3732,7 @@ class MlrunProject(ModelObj):
3283
3732
  logger.warning(
3284
3733
  f"Image was successfully built, but failed to delete temporary function {function.metadata.name}."
3285
3734
  " To remove the function, attempt to manually delete it.",
3286
- exc=repr(exc),
3735
+ exc=mlrun.errors.err_to_str(exc),
3287
3736
  )
3288
3737
 
3289
3738
  return result
@@ -3297,7 +3746,7 @@ class MlrunProject(ModelObj):
3297
3746
  verbose: bool = None,
3298
3747
  builder_env: dict = None,
3299
3748
  mock: bool = None,
3300
- ) -> typing.Union[DeployStatus, kfp.dsl.ContainerOp]:
3749
+ ) -> typing.Union[DeployStatus, PipelineNodeWrapper]:
3301
3750
  """deploy real-time (nuclio based) functions
3302
3751
 
3303
3752
  :param function: name of the function (in the project) or function object
@@ -3332,7 +3781,12 @@ class MlrunProject(ModelObj):
3332
3781
  artifact = db.read_artifact(
3333
3782
  key, tag, iter=iter, project=self.metadata.name, tree=tree
3334
3783
  )
3335
- 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
3336
3790
 
3337
3791
  def list_artifacts(
3338
3792
  self,
@@ -3346,6 +3800,10 @@ class MlrunProject(ModelObj):
3346
3800
  kind: str = None,
3347
3801
  category: typing.Union[str, mlrun.common.schemas.ArtifactCategories] = None,
3348
3802
  tree: str = None,
3803
+ limit: int = None,
3804
+ format_: Optional[
3805
+ mlrun.common.formatters.ArtifactFormat
3806
+ ] = mlrun.common.formatters.ArtifactFormat.full,
3349
3807
  ) -> mlrun.lists.ArtifactList:
3350
3808
  """List artifacts filtered by various parameters.
3351
3809
 
@@ -3355,9 +3813,9 @@ class MlrunProject(ModelObj):
3355
3813
  Examples::
3356
3814
 
3357
3815
  # Get latest version of all artifacts in project
3358
- latest_artifacts = project.list_artifacts('', tag='latest')
3816
+ latest_artifacts = project.list_artifacts("", tag="latest")
3359
3817
  # check different artifact versions for a specific artifact, return as objects list
3360
- result_versions = project.list_artifacts('results', tag='*').to_objects()
3818
+ result_versions = project.list_artifacts("results", tag="*").to_objects()
3361
3819
 
3362
3820
  :param name: Name of artifacts to retrieve. Name with '~' prefix is used as a like query, and is not
3363
3821
  case-sensitive. This means that querying for ``~name`` may return artifacts named
@@ -3375,6 +3833,8 @@ class MlrunProject(ModelObj):
3375
3833
  :param kind: Return artifacts of the requested kind.
3376
3834
  :param category: Return artifacts of the requested category.
3377
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'.
3378
3838
  """
3379
3839
  db = mlrun.db.get_run_db(secrets=self._secrets)
3380
3840
  return db.list_artifacts(
@@ -3389,6 +3849,8 @@ class MlrunProject(ModelObj):
3389
3849
  kind=kind,
3390
3850
  category=category,
3391
3851
  tree=tree,
3852
+ format_=format_,
3853
+ limit=limit,
3392
3854
  )
3393
3855
 
3394
3856
  def list_models(
@@ -3401,13 +3863,17 @@ class MlrunProject(ModelObj):
3401
3863
  iter: int = None,
3402
3864
  best_iteration: bool = False,
3403
3865
  tree: str = None,
3866
+ limit: int = None,
3867
+ format_: Optional[
3868
+ mlrun.common.formatters.ArtifactFormat
3869
+ ] = mlrun.common.formatters.ArtifactFormat.full,
3404
3870
  ):
3405
3871
  """List models in project, filtered by various parameters.
3406
3872
 
3407
3873
  Examples::
3408
3874
 
3409
3875
  # Get latest version of all models in project
3410
- latest_models = project.list_models('', tag='latest')
3876
+ latest_models = project.list_models("", tag="latest")
3411
3877
 
3412
3878
 
3413
3879
  :param name: Name of artifacts to retrieve. Name with '~' prefix is used as a like query, and is not
@@ -3424,6 +3890,8 @@ class MlrunProject(ModelObj):
3424
3890
  artifacts generated from a hyper-param run. If only a single iteration exists, will return the artifact
3425
3891
  from that iteration. If using ``best_iter``, the ``iter`` parameter must not be used.
3426
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'.
3427
3895
  """
3428
3896
  db = mlrun.db.get_run_db(secrets=self._secrets)
3429
3897
  return db.list_artifacts(
@@ -3437,6 +3905,8 @@ class MlrunProject(ModelObj):
3437
3905
  best_iteration=best_iteration,
3438
3906
  kind="model",
3439
3907
  tree=tree,
3908
+ limit=limit,
3909
+ format_=format_,
3440
3910
  ).to_objects()
3441
3911
 
3442
3912
  def list_functions(self, name=None, tag=None, labels=None):
@@ -3448,7 +3918,7 @@ class MlrunProject(ModelObj):
3448
3918
 
3449
3919
 
3450
3920
  :param name: Return only functions with a specific name.
3451
- :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 ``"*"``.
3452
3922
  :param labels: Return functions that have specific labels assigned to them.
3453
3923
  :returns: List of function objects.
3454
3924
  """
@@ -3477,9 +3947,7 @@ class MlrunProject(ModelObj):
3477
3947
  :returns: List of function objects.
3478
3948
  """
3479
3949
 
3480
- model_monitoring_labels_list = [
3481
- f"{mm_constants.ModelMonitoringAppLabel.KEY}={mm_constants.ModelMonitoringAppLabel.VAL}"
3482
- ]
3950
+ model_monitoring_labels_list = [str(mm_constants.ModelMonitoringAppLabel())]
3483
3951
  if labels:
3484
3952
  model_monitoring_labels_list += labels
3485
3953
  return self.list_functions(
@@ -3493,7 +3961,10 @@ class MlrunProject(ModelObj):
3493
3961
  name: Optional[str] = None,
3494
3962
  uid: Optional[Union[str, list[str]]] = None,
3495
3963
  labels: Optional[Union[str, list[str]]] = None,
3496
- 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,
3497
3968
  sort: bool = True,
3498
3969
  last: int = 0,
3499
3970
  iter: bool = False,
@@ -3512,14 +3983,14 @@ class MlrunProject(ModelObj):
3512
3983
  Example::
3513
3984
 
3514
3985
  # return a list of runs matching the name and label and compare
3515
- runs = project.list_runs(name='download', labels='owner=admin')
3986
+ runs = project.list_runs(name="download", labels="owner=admin")
3516
3987
  runs.compare()
3517
3988
 
3518
3989
  # multi-label filter can also be provided
3519
- runs = project.list_runs(name='download', labels=["kind=job", "owner=admin"])
3990
+ runs = project.list_runs(name="download", labels=["kind=job", "owner=admin"])
3520
3991
 
3521
3992
  # If running in Jupyter, can use the .show() function to display the results
3522
- project.list_runs(name='').show()
3993
+ project.list_runs(name="").show()
3523
3994
 
3524
3995
 
3525
3996
  :param name: Name of the run to retrieve.
@@ -3527,10 +3998,11 @@ class MlrunProject(ModelObj):
3527
3998
  :param labels: A list of labels to filter by. Label filters work by either filtering a specific value
3528
3999
  of a label (i.e. list("key=value")) or by looking for the existence of a given
3529
4000
  key (i.e. "key").
3530
- :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.
3531
4003
  :param sort: Whether to sort the result according to their start time. Otherwise, results will be
3532
4004
  returned by their internal order in the DB (order will not be guaranteed).
3533
- :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).
3534
4006
  :param iter: If ``True`` return runs from all iterations. Otherwise, return only runs whose ``iter`` is 0.
3535
4007
  :param start_time_from: Filter by run start time in ``[start_time_from, start_time_to]``.
3536
4008
  :param start_time_to: Filter by run start time in ``[start_time_from, start_time_to]``.
@@ -3538,13 +4010,22 @@ class MlrunProject(ModelObj):
3538
4010
  last_update_time_to)``.
3539
4011
  :param last_update_time_to: Filter by run last update time in ``(last_update_time_from, last_update_time_to)``.
3540
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
+
3541
4020
  db = mlrun.db.get_run_db(secrets=self._secrets)
3542
4021
  return db.list_runs(
3543
4022
  name,
3544
4023
  uid,
3545
4024
  self.metadata.name,
3546
4025
  labels=labels,
3547
- state=state,
4026
+ states=mlrun.utils.helpers.as_list(state)
4027
+ if state is not None
4028
+ else states or None,
3548
4029
  sort=sort,
3549
4030
  last=last,
3550
4031
  iter=iter,
@@ -3625,12 +4106,194 @@ class MlrunProject(ModelObj):
3625
4106
  """
3626
4107
  self.spec.remove_custom_packager(packager=packager)
3627
4108
 
4109
+ def store_api_gateway(
4110
+ self,
4111
+ api_gateway: mlrun.runtimes.nuclio.api_gateway.APIGateway,
4112
+ wait_for_readiness=True,
4113
+ max_wait_time=90,
4114
+ ) -> mlrun.runtimes.nuclio.api_gateway.APIGateway:
4115
+ """
4116
+ Creates or updates a Nuclio API Gateway using the provided APIGateway object.
4117
+
4118
+ This method interacts with the MLRun service to create/update a Nuclio API Gateway based on the provided
4119
+ APIGateway object. Once done, it returns the updated APIGateway object containing all fields propagated
4120
+ on MLRun and Nuclio sides, such as the 'host' attribute.
4121
+ Nuclio docs here: https://docs.nuclio.io/en/latest/reference/api-gateway/http.html
4122
+
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
+
4129
+
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
4132
+ """
4133
+
4134
+ api_gateway_json = mlrun.db.get_run_db().store_api_gateway(
4135
+ api_gateway=api_gateway,
4136
+ project=self.name,
4137
+ )
4138
+
4139
+ if api_gateway_json:
4140
+ # fill in all the fields in the user's api_gateway object
4141
+ api_gateway = mlrun.runtimes.nuclio.api_gateway.APIGateway.from_scheme(
4142
+ api_gateway_json
4143
+ )
4144
+ if wait_for_readiness:
4145
+ api_gateway.wait_for_readiness(max_wait_time=max_wait_time)
4146
+
4147
+ return api_gateway
4148
+
4149
+ def list_api_gateways(self) -> list[mlrun.runtimes.nuclio.api_gateway.APIGateway]:
4150
+ """
4151
+ Retrieves a list of Nuclio API gateways associated with the project.
4152
+
4153
+ :returns: List of :py:class:`~mlrun.runtimes.nuclio.api_gateway.APIGateway` objects representing
4154
+ the Nuclio API gateways associated with the project.
4155
+ """
4156
+ gateways_list = mlrun.db.get_run_db().list_api_gateways(self.name)
4157
+ return [
4158
+ mlrun.runtimes.nuclio.api_gateway.APIGateway.from_scheme(gateway_dict)
4159
+ for gateway_dict in gateways_list.api_gateways.values()
4160
+ ]
4161
+
4162
+ def get_api_gateway(
4163
+ self,
4164
+ name: str,
4165
+ ) -> mlrun.runtimes.nuclio.api_gateway.APIGateway:
4166
+ """
4167
+ Retrieves an API gateway by name instance.
4168
+
4169
+ :param name: The name of the API gateway to retrieve.
4170
+
4171
+ Returns:
4172
+ mlrun.runtimes.nuclio.APIGateway: An instance of APIGateway.
4173
+ """
4174
+
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()
4290
+
3628
4291
  def _run_authenticated_git_action(
3629
4292
  self,
3630
4293
  action: Callable,
3631
4294
  remote: str,
3632
- args: list = [],
3633
- kwargs: dict = {},
4295
+ args: list = None,
4296
+ kwargs: dict = None,
3634
4297
  secrets: Union[SecretsStore, dict] = None,
3635
4298
  ):
3636
4299
  """Run an arbitrary Git routine while the remote is enriched with secrets
@@ -3650,6 +4313,8 @@ class MlrunProject(ModelObj):
3650
4313
  try:
3651
4314
  if is_remote_enriched:
3652
4315
  self.spec.repo.remotes[remote].set_url(enriched_remote, clean_remote)
4316
+ args = args or []
4317
+ kwargs = kwargs or {}
3653
4318
  action(*args, **kwargs)
3654
4319
  except RuntimeError as e:
3655
4320
  raise mlrun.errors.MLRunRuntimeError(
@@ -3702,6 +4367,97 @@ class MlrunProject(ModelObj):
3702
4367
  f"<project.spec.get_code_path()>/<{param_name}>)."
3703
4368
  )
3704
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
+
3705
4461
 
3706
4462
  def _set_as_current_default_project(project: MlrunProject):
3707
4463
  mlrun.mlconf.default_project = project.metadata.name
@@ -3724,10 +4480,6 @@ def _init_function_from_dict(
3724
4480
  tag = f.get("tag", None)
3725
4481
 
3726
4482
  has_module = _has_module(handler, kind)
3727
- if not url and "spec" not in f and not has_module:
3728
- # function must point to a file or a module or have a spec
3729
- raise ValueError("Function missing a url or a spec or a module")
3730
-
3731
4483
  relative_url = url
3732
4484
  url, in_context = project.get_item_absolute_path(url)
3733
4485
 
@@ -3757,18 +4509,17 @@ def _init_function_from_dict(
3757
4509
  )
3758
4510
 
3759
4511
  elif url.endswith(".py"):
3760
- # when load_source_on_run is used we allow not providing image as code will be loaded pre-run. ML-4994
3761
- if (
3762
- not image
3763
- and not project.default_image
3764
- and kind != "local"
3765
- and not project.spec.load_source_on_run
3766
- ):
3767
- raise ValueError(
3768
- "image must be provided with py code files which do not "
3769
- "run on 'local' engine kind"
3770
- )
3771
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
+ )
3772
4523
  func = new_function(
3773
4524
  name,
3774
4525
  command=relative_url,
@@ -3787,6 +4538,17 @@ def _init_function_from_dict(
3787
4538
  tag=tag,
3788
4539
  )
3789
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)
3790
4552
  else:
3791
4553
  raise ValueError(f"Unsupported function url:handler {url}:{handler} or no spec")
3792
4554
 
@@ -3832,9 +4594,17 @@ def _init_function_from_obj(
3832
4594
  def _has_module(handler, kind):
3833
4595
  if not handler:
3834
4596
  return False
3835
- return (
3836
- kind in mlrun.runtimes.RuntimeKinds.nuclio_runtimes() and ":" in handler
3837
- ) 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
3838
4608
 
3839
4609
 
3840
4610
  def _is_imported_artifact(artifact):