mlrun 1.7.0rc12__py3-none-any.whl → 1.7.0rc14__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.

@@ -13,6 +13,7 @@
13
13
  # limitations under the License.
14
14
  import abc
15
15
  import builtins
16
+ import http
16
17
  import importlib.util as imputil
17
18
  import os
18
19
  import tempfile
@@ -521,7 +522,7 @@ class _PipelineRunner(abc.ABC):
521
522
  @staticmethod
522
523
  def _get_handler(workflow_handler, workflow_spec, project, secrets):
523
524
  if not (workflow_handler and callable(workflow_handler)):
524
- workflow_file = workflow_spec.get_source_file(project.spec.context)
525
+ workflow_file = workflow_spec.get_source_file(project.spec.get_code_path())
525
526
  workflow_handler = create_pipeline(
526
527
  project,
527
528
  workflow_file,
@@ -553,7 +554,7 @@ class _KFPRunner(_PipelineRunner):
553
554
  @classmethod
554
555
  def save(cls, project, workflow_spec: WorkflowSpec, target, artifact_path=None):
555
556
  pipeline_context.set(project, workflow_spec)
556
- workflow_file = workflow_spec.get_source_file(project.spec.context)
557
+ workflow_file = workflow_spec.get_source_file(project.spec.get_code_path())
557
558
  functions = FunctionsDict(project)
558
559
  pipeline = create_pipeline(
559
560
  project,
@@ -882,17 +883,33 @@ class _RemoteRunner(_PipelineRunner):
882
883
  get_workflow_id_timeout=get_workflow_id_timeout,
883
884
  )
884
885
 
886
+ def _get_workflow_id_or_bail():
887
+ try:
888
+ return run_db.get_workflow_id(
889
+ project=project.name,
890
+ name=workflow_response.name,
891
+ run_id=workflow_response.run_id,
892
+ engine=workflow_spec.engine,
893
+ )
894
+ except mlrun.errors.MLRunHTTPStatusError as get_wf_exc:
895
+ # fail fast on specific errors
896
+ if get_wf_exc.error_status_code in [
897
+ http.HTTPStatus.PRECONDITION_FAILED
898
+ ]:
899
+ raise mlrun.errors.MLRunFatalFailureError(
900
+ original_exception=get_wf_exc
901
+ )
902
+
903
+ # raise for a retry (on other errors)
904
+ raise
905
+
885
906
  # Getting workflow id from run:
886
907
  response = retry_until_successful(
887
908
  1,
888
909
  get_workflow_id_timeout,
889
910
  logger,
890
911
  False,
891
- run_db.get_workflow_id,
892
- project=project.name,
893
- name=workflow_response.name,
894
- run_id=workflow_response.run_id,
895
- engine=workflow_spec.engine,
912
+ _get_workflow_id_or_bail,
896
913
  )
897
914
  workflow_id = response.workflow_id
898
915
  # After fetching the workflow_id the workflow executed successfully
mlrun/projects/project.py CHANGED
@@ -207,14 +207,16 @@ def new_project(
207
207
  "Unsupported option, cannot use subpath argument with project templates"
208
208
  )
209
209
  if from_template.endswith(".yaml"):
210
- project = _load_project_file(from_template, name, secrets)
210
+ project = _load_project_file(
211
+ from_template, name, secrets, allow_cross_project=True
212
+ )
211
213
  elif from_template.startswith("git://"):
212
214
  clone_git(from_template, context, secrets, clone=True)
213
215
  shutil.rmtree(path.join(context, ".git"))
214
- project = _load_project_dir(context, name)
216
+ project = _load_project_dir(context, name, allow_cross_project=True)
215
217
  elif from_template.endswith(".zip"):
216
218
  clone_zip(from_template, context, secrets)
217
- project = _load_project_dir(context, name)
219
+ project = _load_project_dir(context, name, allow_cross_project=True)
218
220
  else:
219
221
  raise ValueError("template must be a path to .yaml or .zip file")
220
222
  project.metadata.name = name
@@ -296,6 +298,7 @@ def load_project(
296
298
  save: bool = True,
297
299
  sync_functions: bool = False,
298
300
  parameters: dict = None,
301
+ allow_cross_project: bool = None,
299
302
  ) -> "MlrunProject":
300
303
  """Load an MLRun project from git or tar or dir
301
304
 
@@ -342,6 +345,8 @@ def load_project(
342
345
  :param save: whether to save the created project and artifact in the DB
343
346
  :param sync_functions: sync the project's functions into the project object (will be saved to the DB if save=True)
344
347
  :param parameters: key/value pairs to add to the project.spec.params
348
+ :param allow_cross_project: if True, override the loaded project name. This flag ensures awareness of
349
+ loading an existing project yaml as a baseline for a new project with a different name
345
350
 
346
351
  :returns: project object
347
352
  """
@@ -357,7 +362,7 @@ def load_project(
357
362
  if url:
358
363
  url = str(url) # to support path objects
359
364
  if is_yaml_path(url):
360
- project = _load_project_file(url, name, secrets)
365
+ project = _load_project_file(url, name, secrets, allow_cross_project)
361
366
  project.spec.context = context
362
367
  elif url.startswith("git://"):
363
368
  url, repo = clone_git(url, context, secrets, clone)
@@ -384,7 +389,7 @@ def load_project(
384
389
  repo, url = init_repo(context, url, init_git)
385
390
 
386
391
  if not project:
387
- project = _load_project_dir(context, name, subpath)
392
+ project = _load_project_dir(context, name, subpath, allow_cross_project)
388
393
 
389
394
  if not project.metadata.name:
390
395
  raise ValueError("Project name must be specified")
@@ -438,6 +443,7 @@ def get_or_create_project(
438
443
  from_template: str = None,
439
444
  save: bool = True,
440
445
  parameters: dict = None,
446
+ allow_cross_project: bool = None,
441
447
  ) -> "MlrunProject":
442
448
  """Load a project from MLRun DB, or create/import if it does not exist
443
449
 
@@ -482,12 +488,12 @@ def get_or_create_project(
482
488
  :param from_template: path to project YAML file that will be used as from_template (for new projects)
483
489
  :param save: whether to save the created project in the DB
484
490
  :param parameters: key/value pairs to add to the project.spec.params
491
+ :param allow_cross_project: if True, override the loaded project name. This flag ensures awareness of
492
+ loading an existing project yaml as a baseline for a new project with a different name
485
493
 
486
494
  :returns: project object
487
495
  """
488
496
  context = context or "./"
489
- spec_path = path.join(context, subpath or "", "project.yaml")
490
- load_from_path = url or path.isfile(spec_path)
491
497
  try:
492
498
  # load project from the DB.
493
499
  # use `name` as `url` as we load the project from the DB
@@ -503,13 +509,15 @@ def get_or_create_project(
503
509
  # only loading project from db so no need to save it
504
510
  save=False,
505
511
  parameters=parameters,
512
+ allow_cross_project=allow_cross_project,
506
513
  )
507
514
  logger.info("Project loaded successfully", project_name=name)
508
515
  return project
509
-
510
516
  except mlrun.errors.MLRunNotFoundError:
511
517
  logger.debug("Project not found in db", project_name=name)
512
518
 
519
+ spec_path = path.join(context, subpath or "", "project.yaml")
520
+ load_from_path = url or path.isfile(spec_path)
513
521
  # do not nest under "try" or else the exceptions raised below will be logged along with the "not found" message
514
522
  if load_from_path:
515
523
  # loads a project from archive or local project.yaml
@@ -525,6 +533,7 @@ def get_or_create_project(
525
533
  user_project=user_project,
526
534
  save=save,
527
535
  parameters=parameters,
536
+ allow_cross_project=allow_cross_project,
528
537
  )
529
538
 
530
539
  logger.info(
@@ -599,7 +608,7 @@ def _run_project_setup(
599
608
  return project
600
609
 
601
610
 
602
- def _load_project_dir(context, name="", subpath=""):
611
+ def _load_project_dir(context, name="", subpath="", allow_cross_project=None):
603
612
  subpath_str = subpath or ""
604
613
 
605
614
  # support both .yaml and .yml file extensions
@@ -613,7 +622,7 @@ def _load_project_dir(context, name="", subpath=""):
613
622
  with open(project_file_path) as fp:
614
623
  data = fp.read()
615
624
  struct = yaml.load(data, Loader=yaml.FullLoader)
616
- project = _project_instance_from_struct(struct, name)
625
+ project = _project_instance_from_struct(struct, name, allow_cross_project)
617
626
  project.spec.context = context
618
627
  elif function_files := glob.glob(function_file_path):
619
628
  function_path = function_files[0]
@@ -686,19 +695,41 @@ def _delete_project_from_db(project_name, secrets, deletion_strategy):
686
695
  return db.delete_project(project_name, deletion_strategy=deletion_strategy)
687
696
 
688
697
 
689
- def _load_project_file(url, name="", secrets=None):
698
+ def _load_project_file(url, name="", secrets=None, allow_cross_project=None):
690
699
  try:
691
700
  obj = get_object(url, secrets)
692
701
  except FileNotFoundError as exc:
693
702
  raise FileNotFoundError(f"cant find project file at {url}") from exc
694
703
  struct = yaml.load(obj, Loader=yaml.FullLoader)
695
- return _project_instance_from_struct(struct, name)
704
+ return _project_instance_from_struct(struct, name, allow_cross_project)
696
705
 
697
706
 
698
- def _project_instance_from_struct(struct, name):
699
- struct.setdefault("metadata", {})["name"] = name or struct.get("metadata", {}).get(
700
- "name", ""
701
- )
707
+ def _project_instance_from_struct(struct, name, allow_cross_project):
708
+ name_from_struct = struct.get("metadata", {}).get("name", "")
709
+ if name and name_from_struct and name_from_struct != name:
710
+ error_message = (
711
+ f"project name mismatch, {name_from_struct} != {name}, please do one of the following:\n"
712
+ "1. Set the `allow_cross_project=True` when loading the project.\n"
713
+ f"2. Delete the existing project yaml, or ensure its name is equal to {name}.\n"
714
+ "3. Use different project context dir."
715
+ )
716
+
717
+ if allow_cross_project is None:
718
+ # TODO: Remove this warning in version 1.9.0 and also fix cli to support allow_cross_project
719
+ logger.warn(
720
+ "Project name is different than specified on its project yaml."
721
+ "You should fix it until version 1.9.0",
722
+ description=error_message,
723
+ )
724
+ elif allow_cross_project:
725
+ logger.warn(
726
+ "Project name is different than specified on its project yaml. Overriding.",
727
+ existing_name=name_from_struct,
728
+ overriding_name=name,
729
+ )
730
+ else:
731
+ raise ValueError(error_message)
732
+ struct.setdefault("metadata", {})["name"] = name or name_from_struct
702
733
  return MlrunProject.from_dict(struct)
703
734
 
704
735
 
@@ -1814,10 +1845,18 @@ class MlrunProject(ModelObj):
1814
1845
  """
1815
1846
  context = context or self.spec.context
1816
1847
  if context:
1817
- project = _load_project_dir(context, self.metadata.name, self.spec.subpath)
1848
+ project = _load_project_dir(
1849
+ context,
1850
+ self.metadata.name,
1851
+ self.spec.subpath,
1852
+ allow_cross_project=False,
1853
+ )
1818
1854
  else:
1819
1855
  project = _load_project_file(
1820
- self.spec.origin_url, self.metadata.name, self._secrets
1856
+ self.spec.origin_url,
1857
+ self.metadata.name,
1858
+ self._secrets,
1859
+ allow_cross_project=None,
1821
1860
  )
1822
1861
  project.spec.source = self.spec.source
1823
1862
  project.spec.repo = self.spec.repo
@@ -2024,12 +2063,24 @@ class MlrunProject(ModelObj):
2024
2063
 
2025
2064
  return resolved_function_name, function_object, func
2026
2065
 
2066
+ def _wait_for_functions_deployment(self, function_names: list[str]) -> None:
2067
+ """
2068
+ Wait for the deployment of functions on the backend.
2069
+
2070
+ :param function_names: A list of function names.
2071
+ """
2072
+ for fn_name in function_names:
2073
+ fn = typing.cast(RemoteRuntime, self.get_function(key=fn_name))
2074
+ fn._wait_for_function_deployment(db=fn._get_db())
2075
+
2027
2076
  def enable_model_monitoring(
2028
2077
  self,
2029
2078
  default_controller_image: str = "mlrun/mlrun",
2030
2079
  base_period: int = 10,
2031
2080
  image: str = "mlrun/mlrun",
2081
+ *,
2032
2082
  deploy_histogram_data_drift_app: bool = True,
2083
+ wait_for_deployment: bool = False,
2033
2084
  ) -> None:
2034
2085
  """
2035
2086
  Deploy model monitoring application controller, writer and stream functions.
@@ -2039,7 +2090,6 @@ class MlrunProject(ModelObj):
2039
2090
  The stream function goal is to monitor the log of the data stream. It is triggered when a new log entry
2040
2091
  is detected. It processes the new events into statistics that are then written to statistics databases.
2041
2092
 
2042
-
2043
2093
  :param default_controller_image: Deprecated.
2044
2094
  :param base_period: The time period in minutes in which the model monitoring controller
2045
2095
  function is triggered. By default, the base period is 10 minutes.
@@ -2047,6 +2097,9 @@ class MlrunProject(ModelObj):
2047
2097
  stream & histogram data drift functions, which are real time nuclio
2048
2098
  functions. By default, the image is mlrun/mlrun.
2049
2099
  :param deploy_histogram_data_drift_app: If true, deploy the default histogram-based data drift application.
2100
+ :param wait_for_deployment: If true, return only after the deployment is done on the backend.
2101
+ Otherwise, deploy the model monitoring infrastructure on the
2102
+ background, including the histogram data drift app if selected.
2050
2103
  """
2051
2104
  if default_controller_image != "mlrun/mlrun":
2052
2105
  # TODO: Remove this in 1.9.0
@@ -2064,37 +2117,55 @@ class MlrunProject(ModelObj):
2064
2117
  deploy_histogram_data_drift_app=deploy_histogram_data_drift_app,
2065
2118
  )
2066
2119
 
2120
+ if wait_for_deployment:
2121
+ deployment_functions = mm_constants.MonitoringFunctionNames.list()
2122
+ if deploy_histogram_data_drift_app:
2123
+ deployment_functions.append(
2124
+ mm_constants.HistogramDataDriftApplicationConstants.NAME
2125
+ )
2126
+ self._wait_for_functions_deployment(deployment_functions)
2127
+
2067
2128
  def deploy_histogram_data_drift_app(
2068
2129
  self,
2069
2130
  *,
2070
2131
  image: str = "mlrun/mlrun",
2071
2132
  db: Optional[mlrun.db.RunDBInterface] = None,
2133
+ wait_for_deployment: bool = False,
2072
2134
  ) -> None:
2073
2135
  """
2074
2136
  Deploy the histogram data drift application.
2075
2137
 
2076
- :param image: The image on which the application will run.
2077
- :param db: An optional DB object.
2138
+ :param image: The image on which the application will run.
2139
+ :param db: An optional DB object.
2140
+ :param wait_for_deployment: If true, return only after the deployment is done on the backend.
2141
+ Otherwise, deploy the application on the background.
2078
2142
  """
2079
2143
  if db is None:
2080
2144
  db = mlrun.db.get_run_db(secrets=self._secrets)
2081
2145
  db.deploy_histogram_data_drift_app(project=self.name, image=image)
2082
2146
 
2147
+ if wait_for_deployment:
2148
+ self._wait_for_functions_deployment(
2149
+ [mm_constants.HistogramDataDriftApplicationConstants.NAME]
2150
+ )
2151
+
2083
2152
  def update_model_monitoring_controller(
2084
2153
  self,
2085
2154
  base_period: int = 10,
2086
2155
  image: str = "mlrun/mlrun",
2156
+ *,
2157
+ wait_for_deployment: bool = False,
2087
2158
  ) -> None:
2088
2159
  """
2089
2160
  Redeploy model monitoring application controller functions.
2090
2161
 
2091
-
2092
- :param base_period: The time period in minutes in which the model monitoring controller function
2093
- is triggered. By default, the base period is 10 minutes.
2094
- :param image: The image of the model monitoring controller, writer & monitoring
2095
- stream functions, which are real time nuclio functions.
2096
- By default, the image is mlrun/mlrun.
2097
- :returns: model monitoring controller job as a dictionary.
2162
+ :param base_period: The time period in minutes in which the model monitoring controller function
2163
+ is triggered. By default, the base period is 10 minutes.
2164
+ :param image: The image of the model monitoring controller, writer & monitoring
2165
+ stream functions, which are real time nuclio functions.
2166
+ By default, the image is mlrun/mlrun.
2167
+ :param wait_for_deployment: If true, return only after the deployment is done on the backend.
2168
+ Otherwise, deploy the controller on the background.
2098
2169
  """
2099
2170
  db = mlrun.db.get_run_db(secrets=self._secrets)
2100
2171
  db.update_model_monitoring_controller(
@@ -2103,6 +2174,11 @@ class MlrunProject(ModelObj):
2103
2174
  image=image,
2104
2175
  )
2105
2176
 
2177
+ if wait_for_deployment:
2178
+ self._wait_for_functions_deployment(
2179
+ [mm_constants.MonitoringFunctionNames.APPLICATION_CONTROLLER]
2180
+ )
2181
+
2106
2182
  def disable_model_monitoring(
2107
2183
  self, *, delete_histogram_data_drift_app: bool = True
2108
2184
  ) -> None:
@@ -2824,12 +2900,14 @@ class MlrunProject(ModelObj):
2824
2900
  "Remote repo is not defined, use .create_remote() + push()"
2825
2901
  )
2826
2902
 
2827
- self.sync_functions(always=sync)
2828
- if not self.spec._function_objects:
2829
- raise ValueError(
2830
- "There are no functions in the project."
2831
- " Make sure you've set your functions with project.set_function()."
2832
- )
2903
+ if engine not in ["remote"]:
2904
+ # for remote runs we don't require the functions to be synced as they can be loaded dynamically during run
2905
+ self.sync_functions(always=sync)
2906
+ if not self.spec._function_objects:
2907
+ raise ValueError(
2908
+ "There are no functions in the project."
2909
+ " Make sure you've set your functions with project.set_function()."
2910
+ )
2833
2911
 
2834
2912
  if not name and not workflow_path and not workflow_handler:
2835
2913
  raise ValueError("Workflow name, path, or handler must be specified")
mlrun/run.py CHANGED
@@ -661,7 +661,6 @@ def code_to_function(
661
661
  :param embed_code: indicates whether or not to inject the code directly into the function runtime spec,
662
662
  defaults to True
663
663
  :param description: short function description, defaults to ''
664
- :param requirements: list of python packages or pip requirements file path, defaults to None
665
664
  :param requirements: a list of python packages
666
665
  :param requirements_file: path to a python requirements file
667
666
  :param categories: list of categories for mlrun Function Hub, defaults to None