mlrun 1.7.0rc5__py3-none-any.whl → 1.7.0rc7__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 (75) hide show
  1. mlrun/artifacts/base.py +2 -1
  2. mlrun/artifacts/plots.py +9 -5
  3. mlrun/common/constants.py +6 -0
  4. mlrun/common/schemas/__init__.py +2 -0
  5. mlrun/common/schemas/model_monitoring/__init__.py +4 -0
  6. mlrun/common/schemas/model_monitoring/constants.py +35 -18
  7. mlrun/common/schemas/project.py +1 -0
  8. mlrun/common/types.py +7 -1
  9. mlrun/config.py +19 -6
  10. mlrun/data_types/data_types.py +4 -0
  11. mlrun/datastore/alibaba_oss.py +130 -0
  12. mlrun/datastore/azure_blob.py +4 -5
  13. mlrun/datastore/base.py +22 -16
  14. mlrun/datastore/datastore.py +4 -0
  15. mlrun/datastore/google_cloud_storage.py +1 -1
  16. mlrun/datastore/sources.py +7 -7
  17. mlrun/db/base.py +14 -6
  18. mlrun/db/factory.py +1 -1
  19. mlrun/db/httpdb.py +61 -56
  20. mlrun/db/nopdb.py +3 -0
  21. mlrun/launcher/__init__.py +1 -1
  22. mlrun/launcher/base.py +1 -1
  23. mlrun/launcher/client.py +1 -1
  24. mlrun/launcher/factory.py +1 -1
  25. mlrun/launcher/local.py +1 -1
  26. mlrun/launcher/remote.py +1 -1
  27. mlrun/model.py +1 -0
  28. mlrun/model_monitoring/__init__.py +1 -1
  29. mlrun/model_monitoring/api.py +104 -301
  30. mlrun/model_monitoring/application.py +21 -21
  31. mlrun/model_monitoring/applications/histogram_data_drift.py +130 -40
  32. mlrun/model_monitoring/controller.py +26 -33
  33. mlrun/model_monitoring/db/__init__.py +16 -0
  34. mlrun/model_monitoring/{stores → db/stores}/__init__.py +43 -34
  35. mlrun/model_monitoring/db/stores/base/__init__.py +15 -0
  36. mlrun/model_monitoring/{stores/model_endpoint_store.py → db/stores/base/store.py} +47 -6
  37. mlrun/model_monitoring/db/stores/sqldb/__init__.py +13 -0
  38. mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +49 -0
  39. mlrun/model_monitoring/{stores → db/stores/sqldb}/models/base.py +76 -3
  40. mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +68 -0
  41. mlrun/model_monitoring/{stores → db/stores/sqldb}/models/sqlite.py +13 -1
  42. mlrun/model_monitoring/db/stores/sqldb/sql_store.py +662 -0
  43. mlrun/model_monitoring/db/stores/v3io_kv/__init__.py +13 -0
  44. mlrun/model_monitoring/{stores/kv_model_endpoint_store.py → db/stores/v3io_kv/kv_store.py} +134 -3
  45. mlrun/model_monitoring/features_drift_table.py +34 -22
  46. mlrun/model_monitoring/helpers.py +45 -6
  47. mlrun/model_monitoring/stream_processing.py +43 -9
  48. mlrun/model_monitoring/tracking_policy.py +7 -1
  49. mlrun/model_monitoring/writer.py +4 -36
  50. mlrun/projects/pipelines.py +13 -1
  51. mlrun/projects/project.py +279 -117
  52. mlrun/run.py +72 -74
  53. mlrun/runtimes/__init__.py +35 -0
  54. mlrun/runtimes/base.py +7 -1
  55. mlrun/runtimes/nuclio/api_gateway.py +188 -61
  56. mlrun/runtimes/nuclio/application/__init__.py +15 -0
  57. mlrun/runtimes/nuclio/application/application.py +283 -0
  58. mlrun/runtimes/nuclio/application/reverse_proxy.go +87 -0
  59. mlrun/runtimes/nuclio/function.py +53 -1
  60. mlrun/runtimes/nuclio/serving.py +28 -32
  61. mlrun/runtimes/pod.py +27 -1
  62. mlrun/serving/server.py +4 -6
  63. mlrun/serving/states.py +41 -33
  64. mlrun/utils/helpers.py +34 -0
  65. mlrun/utils/version/version.json +2 -2
  66. {mlrun-1.7.0rc5.dist-info → mlrun-1.7.0rc7.dist-info}/METADATA +14 -5
  67. {mlrun-1.7.0rc5.dist-info → mlrun-1.7.0rc7.dist-info}/RECORD +71 -64
  68. mlrun/model_monitoring/batch.py +0 -974
  69. mlrun/model_monitoring/stores/models/__init__.py +0 -27
  70. mlrun/model_monitoring/stores/models/mysql.py +0 -34
  71. mlrun/model_monitoring/stores/sql_model_endpoint_store.py +0 -382
  72. {mlrun-1.7.0rc5.dist-info → mlrun-1.7.0rc7.dist-info}/LICENSE +0 -0
  73. {mlrun-1.7.0rc5.dist-info → mlrun-1.7.0rc7.dist-info}/WHEEL +0 -0
  74. {mlrun-1.7.0rc5.dist-info → mlrun-1.7.0rc7.dist-info}/entry_points.txt +0 -0
  75. {mlrun-1.7.0rc5.dist-info → mlrun-1.7.0rc7.dist-info}/top_level.txt +0 -0
mlrun/projects/project.py CHANGED
@@ -11,6 +11,7 @@
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
@@ -44,6 +45,7 @@ import mlrun.runtimes
44
45
  import mlrun.runtimes.nuclio.api_gateway
45
46
  import mlrun.runtimes.pod
46
47
  import mlrun.runtimes.utils
48
+ import mlrun.serving
47
49
  import mlrun.utils.regex
48
50
  from mlrun.datastore.datastore_profile import DatastoreProfile, DatastoreProfile2Json
49
51
  from mlrun.runtimes.nuclio.function import RemoteRuntime
@@ -55,7 +57,6 @@ from ..features import Feature
55
57
  from ..model import EntrypointParam, ImageBuilder, ModelObj
56
58
  from ..model_monitoring.application import (
57
59
  ModelMonitoringApplicationBase,
58
- PushToMonitoringWriter,
59
60
  )
60
61
  from ..run import code_to_function, get_object, import_function, new_function
61
62
  from ..secrets import SecretsStore
@@ -760,6 +761,7 @@ class ProjectSpec(ModelObj):
760
761
  default_image=None,
761
762
  build=None,
762
763
  custom_packagers: list[tuple[str, bool]] = None,
764
+ default_function_node_selector=None,
763
765
  ):
764
766
  self.repo = None
765
767
 
@@ -799,6 +801,7 @@ class ProjectSpec(ModelObj):
799
801
  # in a tuple where the first index is the packager module's path (str) and the second is a flag (bool) for
800
802
  # whether it is mandatory for a run (raise exception on collection error) or not.
801
803
  self.custom_packagers = custom_packagers or []
804
+ self.default_function_node_selector = default_function_node_selector or {}
802
805
 
803
806
  @property
804
807
  def source(self) -> str:
@@ -1375,14 +1378,7 @@ class MlrunProject(ModelObj):
1375
1378
  artifact_path = mlrun.utils.helpers.template_artifact_path(
1376
1379
  self.spec.artifact_path or mlrun.mlconf.artifact_path, self.metadata.name
1377
1380
  )
1378
- # TODO: To correctly maintain the list of artifacts from an exported project,
1379
- # we need to maintain the different trees that generated them
1380
- producer = ArtifactProducer(
1381
- "project",
1382
- self.metadata.name,
1383
- self.metadata.name,
1384
- tag=self._get_hexsha() or str(uuid.uuid4()),
1385
- )
1381
+ project_tag = self._get_project_tag()
1386
1382
  for artifact_dict in self.spec.artifacts:
1387
1383
  if _is_imported_artifact(artifact_dict):
1388
1384
  import_from = artifact_dict["import_from"]
@@ -1402,6 +1398,15 @@ class MlrunProject(ModelObj):
1402
1398
  artifact.src_path = path.join(
1403
1399
  self.spec.get_code_path(), artifact.src_path
1404
1400
  )
1401
+ producer = self._resolve_artifact_producer(artifact, project_tag)
1402
+ # log the artifact only if it doesn't already exist
1403
+ if (
1404
+ producer.name != self.metadata.name
1405
+ and self._resolve_existing_artifact(
1406
+ artifact,
1407
+ )
1408
+ ):
1409
+ continue
1405
1410
  artifact_manager.log_artifact(
1406
1411
  producer, artifact, artifact_path=artifact_path
1407
1412
  )
@@ -1498,12 +1503,20 @@ class MlrunProject(ModelObj):
1498
1503
  artifact_path = mlrun.utils.helpers.template_artifact_path(
1499
1504
  artifact_path, self.metadata.name
1500
1505
  )
1501
- producer = ArtifactProducer(
1502
- "project",
1503
- self.metadata.name,
1504
- self.metadata.name,
1505
- tag=self._get_hexsha() or str(uuid.uuid4()),
1506
- )
1506
+ producer = self._resolve_artifact_producer(item)
1507
+ if producer.name != self.metadata.name:
1508
+ # the artifact producer is retained, log it only if it doesn't already exist
1509
+ if existing_artifact := self._resolve_existing_artifact(
1510
+ item,
1511
+ tag,
1512
+ ):
1513
+ artifact_key = item if isinstance(item, str) else item.key
1514
+ logger.info(
1515
+ "Artifact already exists, skipping logging",
1516
+ key=artifact_key,
1517
+ tag=tag,
1518
+ )
1519
+ return existing_artifact
1507
1520
  item = am.log_artifact(
1508
1521
  producer,
1509
1522
  item,
@@ -1834,10 +1847,10 @@ class MlrunProject(ModelObj):
1834
1847
  monitoring application's constructor.
1835
1848
  """
1836
1849
 
1837
- if name in mm_constants.MonitoringFunctionNames.all():
1850
+ if name in mm_constants.MonitoringFunctionNames.list():
1838
1851
  raise mlrun.errors.MLRunInvalidArgumentError(
1839
- f"Application name can not be on of the following name : "
1840
- f"{mm_constants.MonitoringFunctionNames.all()}"
1852
+ f"An application cannot have the following names: "
1853
+ f"{mm_constants.MonitoringFunctionNames.list()}"
1841
1854
  )
1842
1855
  function_object: RemoteRuntime = None
1843
1856
  (
@@ -1856,16 +1869,6 @@ class MlrunProject(ModelObj):
1856
1869
  requirements_file,
1857
1870
  **application_kwargs,
1858
1871
  )
1859
- models_names = "all"
1860
- function_object.set_label(
1861
- mm_constants.ModelMonitoringAppLabel.KEY,
1862
- mm_constants.ModelMonitoringAppLabel.VAL,
1863
- )
1864
- function_object.set_label("models", models_names)
1865
-
1866
- if not mlrun.mlconf.is_ce_mode():
1867
- function_object.apply(mlrun.mount_v3io())
1868
-
1869
1872
  # save to project spec
1870
1873
  self.spec.set_function(resolved_function_name, function_object, func)
1871
1874
 
@@ -1924,49 +1927,38 @@ class MlrunProject(ModelObj):
1924
1927
 
1925
1928
  def _instantiate_model_monitoring_function(
1926
1929
  self,
1927
- func: typing.Union[str, mlrun.runtimes.BaseRuntime] = None,
1928
- application_class: typing.Union[str, ModelMonitoringApplicationBase] = None,
1929
- name: str = None,
1930
- image: str = None,
1931
- handler: str = None,
1932
- with_repo: bool = None,
1933
- tag: str = None,
1934
- requirements: typing.Union[str, list[str]] = None,
1930
+ func: typing.Union[str, mlrun.runtimes.BaseRuntime, None] = None,
1931
+ application_class: typing.Union[
1932
+ str, ModelMonitoringApplicationBase, None
1933
+ ] = None,
1934
+ name: typing.Optional[str] = None,
1935
+ image: typing.Optional[str] = None,
1936
+ handler: typing.Optional[str] = None,
1937
+ with_repo: typing.Optional[bool] = None,
1938
+ tag: typing.Optional[str] = None,
1939
+ requirements: typing.Union[str, list[str], None] = None,
1935
1940
  requirements_file: str = "",
1936
1941
  **application_kwargs,
1937
1942
  ) -> tuple[str, mlrun.runtimes.BaseRuntime, dict]:
1943
+ import mlrun.model_monitoring.api
1944
+
1938
1945
  function_object: RemoteRuntime = None
1939
1946
  kind = None
1940
1947
  if (isinstance(func, str) or func is None) and application_class is not None:
1941
- kind = "serving"
1942
- if func is None:
1943
- func = ""
1944
- func = mlrun.code_to_function(
1945
- filename=func,
1948
+ kind = mlrun.run.RuntimeKinds.serving
1949
+ func = mlrun.model_monitoring.api._create_model_monitoring_function_base(
1950
+ project=self.name,
1951
+ func=func,
1952
+ application_class=application_class,
1946
1953
  name=name,
1947
- project=self.metadata.name,
1948
- tag=tag,
1949
- kind=kind,
1950
1954
  image=image,
1955
+ tag=tag,
1951
1956
  requirements=requirements,
1952
1957
  requirements_file=requirements_file,
1958
+ **application_kwargs,
1953
1959
  )
1954
- graph = func.set_topology("flow")
1955
- if isinstance(application_class, str):
1956
- first_step = graph.to(
1957
- class_name=application_class, **application_kwargs
1958
- )
1959
- else:
1960
- first_step = graph.to(class_name=application_class)
1961
- first_step.to(
1962
- class_name=PushToMonitoringWriter(
1963
- project=self.metadata.name,
1964
- writer_application_name=mm_constants.MonitoringFunctionNames.WRITER,
1965
- stream_uri=None,
1966
- ),
1967
- ).respond()
1968
1960
  elif isinstance(func, str) and isinstance(handler, str):
1969
- kind = "nuclio"
1961
+ kind = mlrun.run.RuntimeKinds.nuclio
1970
1962
 
1971
1963
  (
1972
1964
  resolved_function_name,
@@ -1984,12 +1976,10 @@ class MlrunProject(ModelObj):
1984
1976
  requirements,
1985
1977
  requirements_file,
1986
1978
  )
1987
- models_names = "all"
1988
1979
  function_object.set_label(
1989
1980
  mm_constants.ModelMonitoringAppLabel.KEY,
1990
1981
  mm_constants.ModelMonitoringAppLabel.VAL,
1991
1982
  )
1992
- function_object.set_label("models", models_names)
1993
1983
 
1994
1984
  if not mlrun.mlconf.is_ce_mode():
1995
1985
  function_object.apply(mlrun.mount_v3io())
@@ -2019,8 +2009,6 @@ class MlrunProject(ModelObj):
2019
2009
  stream & histogram data drift functions, which are real time nuclio
2020
2010
  functions. By default, the image is mlrun/mlrun.
2021
2011
  :param deploy_histogram_data_drift_app: If true, deploy the default histogram-based data drift application.
2022
-
2023
- :returns: model monitoring controller job as a dictionary.
2024
2012
  """
2025
2013
  if default_controller_image != "mlrun/mlrun":
2026
2014
  # TODO: Remove this in 1.9.0
@@ -2035,18 +2023,24 @@ class MlrunProject(ModelObj):
2035
2023
  project=self.name,
2036
2024
  image=image,
2037
2025
  base_period=base_period,
2026
+ deploy_histogram_data_drift_app=deploy_histogram_data_drift_app,
2038
2027
  )
2039
- if deploy_histogram_data_drift_app:
2040
- fn = self.set_model_monitoring_function(
2041
- func=str(
2042
- pathlib.Path(__file__).parent.parent
2043
- / "model_monitoring/applications/histogram_data_drift.py"
2044
- ),
2045
- name=mm_constants.MLRUN_HISTOGRAM_DATA_DRIFT_APP_NAME,
2046
- application_class="HistogramDataDriftApplication",
2047
- image=image,
2048
- )
2049
- fn.deploy()
2028
+
2029
+ def deploy_histogram_data_drift_app(
2030
+ self,
2031
+ *,
2032
+ image: str = "mlrun/mlrun",
2033
+ db: Optional[mlrun.db.RunDBInterface] = None,
2034
+ ) -> None:
2035
+ """
2036
+ Deploy the histogram data drift application.
2037
+
2038
+ :param image: The image on which the application will run.
2039
+ :param db: An optional DB object.
2040
+ """
2041
+ if db is None:
2042
+ db = mlrun.db.get_run_db(secrets=self._secrets)
2043
+ db.deploy_histogram_data_drift_app(project=self.name, image=image)
2050
2044
 
2051
2045
  def update_model_monitoring_controller(
2052
2046
  self,
@@ -2071,20 +2065,22 @@ class MlrunProject(ModelObj):
2071
2065
  image=image,
2072
2066
  )
2073
2067
 
2074
- def disable_model_monitoring(self):
2068
+ def disable_model_monitoring(
2069
+ self, *, delete_histogram_data_drift_app: bool = True
2070
+ ) -> None:
2071
+ """
2072
+ Note: This method is currently not advised for use. See ML-3432.
2073
+ Disable model monitoring by deleting the underlying functions infrastructure from MLRun database.
2074
+
2075
+ :param delete_histogram_data_drift_app: Whether to delete the histogram data drift app.
2076
+ """
2075
2077
  db = mlrun.db.get_run_db(secrets=self._secrets)
2076
- db.delete_function(
2077
- project=self.name,
2078
- name=mm_constants.MonitoringFunctionNames.APPLICATION_CONTROLLER,
2079
- )
2080
- db.delete_function(
2081
- project=self.name,
2082
- name=mm_constants.MonitoringFunctionNames.WRITER,
2083
- )
2084
- db.delete_function(
2085
- project=self.name,
2086
- name=mm_constants.MonitoringFunctionNames.STREAM,
2087
- )
2078
+ for fn_name in mm_constants.MonitoringFunctionNames.list():
2079
+ db.delete_function(project=self.name, name=fn_name)
2080
+ if delete_histogram_data_drift_app:
2081
+ db.delete_function(
2082
+ project=self.name, name=mm_constants.MLRUN_HISTOGRAM_DATA_DRIFT_APP_NAME
2083
+ )
2088
2084
 
2089
2085
  def set_function(
2090
2086
  self,
@@ -2403,13 +2399,47 @@ class MlrunProject(ModelObj):
2403
2399
  clone_zip(url, self.spec.context, self._secrets)
2404
2400
 
2405
2401
  def create_remote(self, url, name="origin", branch=None):
2406
- """create remote for the project git
2402
+ """Create remote for the project git
2403
+
2404
+ This method creates a new remote repository associated with the project's Git repository.
2405
+ If a remote with the specified name already exists, it will not be overwritten.
2406
+
2407
+ If you wish to update the URL of an existing remote, use the `set_remote` method instead.
2407
2408
 
2408
2409
  :param url: remote git url
2409
2410
  :param name: name for the remote (default is 'origin')
2410
2411
  :param branch: Git branch to use as source
2411
2412
  """
2413
+ self.set_remote(url, name=name, branch=branch, overwrite=False)
2414
+
2415
+ def set_remote(self, url, name="origin", branch=None, overwrite=True):
2416
+ """Create or update a remote for the project git repository.
2417
+
2418
+ This method allows you to manage remote repositories associated with the project.
2419
+ It checks if a remote with the specified name already exists.
2420
+
2421
+ If a remote with the same name does not exist, it will be created.
2422
+ If a remote with the same name already exists,
2423
+ the behavior depends on the value of the 'overwrite' flag.
2424
+
2425
+ :param url: remote git url
2426
+ :param name: name for the remote (default is 'origin')
2427
+ :param branch: Git branch to use as source
2428
+ :param overwrite: if True (default), updates the existing remote with the given URL if it already exists.
2429
+ if False, raises an error when attempting to create a remote with a name that already exists.
2430
+ :raises MLRunConflictError: If a remote with the same name already exists and overwrite
2431
+ is set to False.
2432
+ """
2412
2433
  self._ensure_git_repo()
2434
+ if self._remote_exists(name):
2435
+ if overwrite:
2436
+ self.spec.repo.delete_remote(name)
2437
+ else:
2438
+ raise mlrun.errors.MLRunConflictError(
2439
+ f"Remote '{name}' already exists in the project, "
2440
+ f"each remote in the project must have a unique name."
2441
+ "Use 'set_remote' with 'override=True' inorder to update the remote, or choose a different name."
2442
+ )
2413
2443
  self.spec.repo.create_remote(name, url=url)
2414
2444
  url = url.replace("https://", "git://")
2415
2445
  if not branch:
@@ -2422,6 +2452,22 @@ class MlrunProject(ModelObj):
2422
2452
  self.spec._source = self.spec.source or url
2423
2453
  self.spec.origin_url = self.spec.origin_url or url
2424
2454
 
2455
+ def remove_remote(self, name):
2456
+ """Remove a remote from the project's Git repository.
2457
+
2458
+ This method removes the remote repository associated with the specified name from the project's Git repository.
2459
+
2460
+ :param name: Name of the remote to remove.
2461
+ """
2462
+ if self._remote_exists(name):
2463
+ self.spec.repo.delete_remote(name)
2464
+ else:
2465
+ logger.warning(f"The remote '{name}' does not exist. Nothing to remove.")
2466
+
2467
+ def _remote_exists(self, name):
2468
+ """Check if a remote with the given name already exists"""
2469
+ return any(remote.name == name for remote in self.spec.repo.remotes)
2470
+
2425
2471
  def _ensure_git_repo(self):
2426
2472
  if self.spec.repo:
2427
2473
  return
@@ -2687,40 +2733,41 @@ class MlrunProject(ModelObj):
2687
2733
  cleanup_ttl: int = None,
2688
2734
  notifications: list[mlrun.model.Notification] = None,
2689
2735
  ) -> _PipelineRunStatus:
2690
- """run a workflow using kubeflow pipelines
2691
-
2692
- :param name: name of the workflow
2693
- :param workflow_path:
2694
- url to a workflow file, if not a project workflow
2695
- :param arguments:
2696
- kubeflow pipelines arguments (parameters)
2697
- :param artifact_path:
2698
- target path/url for workflow artifacts, the string
2699
- '{{workflow.uid}}' will be replaced by workflow id
2700
- :param workflow_handler:
2701
- workflow function handler (for running workflow function directly)
2702
- :param namespace: kubernetes namespace if other than default
2703
- :param sync: force functions sync before run
2704
- :param watch: wait for pipeline completion
2705
- :param dirty: allow running the workflow when the git repo is dirty
2706
- :param engine: workflow engine running the workflow.
2707
- supported values are 'kfp' (default), 'local' or 'remote'.
2708
- for setting engine for remote running use 'remote:local' or 'remote:kfp'.
2709
- :param local: run local pipeline with local functions (set local=True in function.run())
2736
+ """Run a workflow using kubeflow pipelines
2737
+
2738
+ :param name: Name of the workflow
2739
+ :param workflow_path: URL to a workflow file, if not a project workflow
2740
+ :param arguments: Kubeflow pipelines arguments (parameters)
2741
+ :param artifact_path: Target path/URL for workflow artifacts, the string '{{workflow.uid}}' will be
2742
+ replaced by workflow id.
2743
+ :param workflow_handler: Workflow function handler (for running workflow function directly)
2744
+ :param namespace: Kubernetes namespace if other than default
2745
+ :param sync: Force functions sync before run
2746
+ :param watch: Wait for pipeline completion
2747
+ :param dirty: Allow running the workflow when the git repo is dirty
2748
+ :param engine: Workflow engine running the workflow.
2749
+ Supported values are 'kfp' (default), 'local' or 'remote'.
2750
+ For setting engine for remote running use 'remote:local' or 'remote:kfp'.
2751
+ :param local: Run local pipeline with local functions (set local=True in function.run())
2710
2752
  :param schedule: ScheduleCronTrigger class instance or a standard crontab expression string
2711
2753
  (which will be converted to the class using its `from_crontab` constructor),
2712
2754
  see this link for help:
2713
2755
  https://apscheduler.readthedocs.io/en/3.x/modules/triggers/cron.html#module-apscheduler.triggers.cron
2714
2756
  for using the pre-defined workflow's schedule, set `schedule=True`
2715
- :param timeout: timeout in seconds to wait for pipeline completion (watch will be activated)
2716
- :param source: remote source to use instead of the actual `project.spec.source` (used when engine is remote).
2717
- for other engines the source is to validate that the code is up-to-date
2757
+ :param timeout: Timeout in seconds to wait for pipeline completion (watch will be activated)
2758
+ :param source: Source to use instead of the actual `project.spec.source` (used when engine is remote).
2759
+ Can be a one of:
2760
+ 1. Remote URL which is loaded dynamically to the workflow runner.
2761
+ 2. A path to the project's context on the workflow runner's image.
2762
+ Path can be absolute or relative to `project.spec.build.source_code_target_dir` if defined
2763
+ (enriched when building a project image with source, see `MlrunProject.build_image`).
2764
+ For other engines the source is used to validate that the code is up-to-date.
2718
2765
  :param cleanup_ttl:
2719
- pipeline cleanup ttl in secs (time to wait after workflow completion, at which point the
2720
- workflow and all its resources are deleted)
2766
+ Pipeline cleanup ttl in secs (time to wait after workflow completion, at which point the
2767
+ Workflow and all its resources are deleted)
2721
2768
  :param notifications:
2722
- list of notifications to send for workflow completion
2723
- :returns: run id
2769
+ List of notifications to send for workflow completion
2770
+ :returns: Run id
2724
2771
  """
2725
2772
 
2726
2773
  arguments = arguments or {}
@@ -3119,6 +3166,7 @@ class MlrunProject(ModelObj):
3119
3166
  requirements_file: str = None,
3120
3167
  builder_env: dict = None,
3121
3168
  extra_args: str = None,
3169
+ source_code_target_dir: str = None,
3122
3170
  ):
3123
3171
  """specify builder configuration for the project
3124
3172
 
@@ -3139,6 +3187,8 @@ class MlrunProject(ModelObj):
3139
3187
  e.g. builder_env={"GIT_TOKEN": token}, does not work yet in KFP
3140
3188
  :param extra_args: A string containing additional builder arguments in the format of command-line options,
3141
3189
  e.g. extra_args="--skip-tls-verify --build-arg A=val"
3190
+ :param source_code_target_dir: Path on the image where source code would be extracted
3191
+ (by default `/home/mlrun_code`)
3142
3192
  """
3143
3193
  if not overwrite_build_params:
3144
3194
  # TODO: change overwrite_build_params default to True in 1.8.0
@@ -3162,6 +3212,7 @@ class MlrunProject(ModelObj):
3162
3212
  overwrite=overwrite_build_params,
3163
3213
  builder_env=builder_env,
3164
3214
  extra_args=extra_args,
3215
+ source_code_target_dir=source_code_target_dir,
3165
3216
  )
3166
3217
 
3167
3218
  if set_as_default and image != self.default_image:
@@ -3208,7 +3259,7 @@ class MlrunProject(ModelObj):
3208
3259
  * False: The new params are merged with the existing
3209
3260
  * True: The existing params are replaced by the new ones
3210
3261
  :param extra_args: A string containing additional builder arguments in the format of command-line options,
3211
- e.g. extra_args="--skip-tls-verify --build-arg A=val"r
3262
+ e.g. extra_args="--skip-tls-verify --build-arg A=val"
3212
3263
  :param target_dir: Path on the image where source code would be extracted (by default `/home/mlrun_code`)
3213
3264
  """
3214
3265
  if not base_image:
@@ -3276,6 +3327,11 @@ class MlrunProject(ModelObj):
3276
3327
  force_build=True,
3277
3328
  )
3278
3329
 
3330
+ # Get the enriched target dir from the function
3331
+ self.spec.build.source_code_target_dir = (
3332
+ function.spec.build.source_code_target_dir
3333
+ )
3334
+
3279
3335
  try:
3280
3336
  mlrun.db.get_run_db(secrets=self._secrets).delete_function(
3281
3337
  name=function.metadata.name
@@ -3333,7 +3389,12 @@ class MlrunProject(ModelObj):
3333
3389
  artifact = db.read_artifact(
3334
3390
  key, tag, iter=iter, project=self.metadata.name, tree=tree
3335
3391
  )
3336
- return dict_to_artifact(artifact)
3392
+
3393
+ # in tests, if an artifact is not found, the db returns None
3394
+ # in real usage, the db should raise an exception
3395
+ if artifact:
3396
+ return dict_to_artifact(artifact)
3397
+ return None
3337
3398
 
3338
3399
  def list_artifacts(
3339
3400
  self,
@@ -3684,6 +3745,18 @@ class MlrunProject(ModelObj):
3684
3745
 
3685
3746
  return mlrun.db.get_run_db().get_api_gateway(name=name, project=self.name)
3686
3747
 
3748
+ def delete_api_gateway(
3749
+ self,
3750
+ name: str,
3751
+ ):
3752
+ """
3753
+ Deletes an API gateway by name.
3754
+
3755
+ :param name: The name of the API gateway to delete.
3756
+ """
3757
+
3758
+ mlrun.db.get_run_db().delete_api_gateway(name=name, project=self.name)
3759
+
3687
3760
  def _run_authenticated_git_action(
3688
3761
  self,
3689
3762
  action: Callable,
@@ -3761,6 +3834,83 @@ class MlrunProject(ModelObj):
3761
3834
  f"<project.spec.get_code_path()>/<{param_name}>)."
3762
3835
  )
3763
3836
 
3837
+ def _resolve_artifact_producer(
3838
+ self,
3839
+ artifact: typing.Union[str, Artifact],
3840
+ project_producer_tag: str = None,
3841
+ ) -> typing.Optional[ArtifactProducer]:
3842
+ """
3843
+ Resolve the artifact producer of the given artifact.
3844
+ If the artifact's producer is a run, the artifact is registered with the original producer.
3845
+ Otherwise, the artifact is registered with the current project as the producer.
3846
+
3847
+ :param artifact: The artifact to resolve its producer.
3848
+ :param project_producer_tag: The tag to use for the project as the producer. If not provided, a tag will be
3849
+ generated for the project.
3850
+ :return: A tuple of the resolved producer and the resolved artifact.
3851
+ """
3852
+
3853
+ if not isinstance(artifact, str) and artifact.producer:
3854
+ # if the artifact was imported from a yaml file, the producer can be a dict
3855
+ if isinstance(artifact.spec.producer, ArtifactProducer):
3856
+ producer_dict = artifact.spec.producer.get_meta()
3857
+ else:
3858
+ producer_dict = artifact.spec.producer
3859
+
3860
+ if producer_dict.get("kind", "") == "run":
3861
+ return ArtifactProducer(
3862
+ name=producer_dict.get("name", ""),
3863
+ kind=producer_dict.get("kind", ""),
3864
+ project=producer_dict.get("project", ""),
3865
+ tag=producer_dict.get("tag", ""),
3866
+ )
3867
+
3868
+ # do not retain the artifact's producer, replace it with the project as the producer
3869
+ project_producer_tag = project_producer_tag or self._get_project_tag()
3870
+ return ArtifactProducer(
3871
+ kind="project",
3872
+ name=self.metadata.name,
3873
+ project=self.metadata.name,
3874
+ tag=project_producer_tag,
3875
+ )
3876
+
3877
+ def _resolve_existing_artifact(
3878
+ self,
3879
+ item: typing.Union[str, Artifact],
3880
+ tag: str = None,
3881
+ ) -> typing.Optional[Artifact]:
3882
+ """
3883
+ Check if there is and existing artifact with the given item and tag.
3884
+ If there is, return the existing artifact. Otherwise, return None.
3885
+
3886
+ :param item: The item (or key) to check if there is an existing artifact for.
3887
+ :param tag: The tag to check if there is an existing artifact for.
3888
+ :return: The existing artifact if there is one, otherwise None.
3889
+ """
3890
+ try:
3891
+ if isinstance(item, str):
3892
+ existing_artifact = self.get_artifact(key=item, tag=tag)
3893
+ else:
3894
+ existing_artifact = self.get_artifact(
3895
+ key=item.key,
3896
+ tag=item.tag,
3897
+ iter=item.iter,
3898
+ tree=item.tree,
3899
+ )
3900
+ if existing_artifact is not None:
3901
+ return existing_artifact.from_dict(existing_artifact)
3902
+ except mlrun.errors.MLRunNotFoundError:
3903
+ logger.debug(
3904
+ "No existing artifact was found",
3905
+ key=item if isinstance(item, str) else item.key,
3906
+ tag=tag if isinstance(item, str) else item.tag,
3907
+ tree=None if isinstance(item, str) else item.tree,
3908
+ )
3909
+ return None
3910
+
3911
+ def _get_project_tag(self):
3912
+ return self._get_hexsha() or str(uuid.uuid4())
3913
+
3764
3914
 
3765
3915
  def _set_as_current_default_project(project: MlrunProject):
3766
3916
  mlrun.mlconf.default_project = project.metadata.name
@@ -3846,6 +3996,18 @@ def _init_function_from_dict(
3846
3996
  tag=tag,
3847
3997
  )
3848
3998
 
3999
+ elif image and kind in mlrun.runtimes.RuntimeKinds.nuclio_runtimes():
4000
+ func = new_function(
4001
+ name,
4002
+ command=relative_url,
4003
+ image=image,
4004
+ kind=kind,
4005
+ handler=handler,
4006
+ tag=tag,
4007
+ )
4008
+ if kind != mlrun.runtimes.RuntimeKinds.application:
4009
+ logger.info("Function code not specified, setting entry point to image")
4010
+ func.from_image(image)
3849
4011
  else:
3850
4012
  raise ValueError(f"Unsupported function url:handler {url}:{handler} or no spec")
3851
4013