mlrun 1.7.0rc28__py3-none-any.whl → 1.7.0rc55__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 (135) hide show
  1. mlrun/__main__.py +4 -2
  2. mlrun/alerts/alert.py +75 -8
  3. mlrun/artifacts/base.py +1 -0
  4. mlrun/artifacts/manager.py +9 -2
  5. mlrun/common/constants.py +4 -1
  6. mlrun/common/db/sql_session.py +3 -2
  7. mlrun/common/formatters/__init__.py +1 -0
  8. mlrun/common/formatters/artifact.py +1 -0
  9. mlrun/{model_monitoring/application.py → common/formatters/feature_set.py} +20 -6
  10. mlrun/common/formatters/run.py +3 -0
  11. mlrun/common/helpers.py +0 -1
  12. mlrun/common/schemas/__init__.py +3 -1
  13. mlrun/common/schemas/alert.py +15 -12
  14. mlrun/common/schemas/api_gateway.py +6 -6
  15. mlrun/common/schemas/auth.py +5 -0
  16. mlrun/common/schemas/client_spec.py +0 -1
  17. mlrun/common/schemas/common.py +7 -4
  18. mlrun/common/schemas/frontend_spec.py +7 -0
  19. mlrun/common/schemas/function.py +7 -0
  20. mlrun/common/schemas/model_monitoring/__init__.py +4 -3
  21. mlrun/common/schemas/model_monitoring/constants.py +41 -26
  22. mlrun/common/schemas/model_monitoring/model_endpoints.py +23 -47
  23. mlrun/common/schemas/notification.py +69 -12
  24. mlrun/common/schemas/project.py +45 -12
  25. mlrun/common/schemas/workflow.py +10 -2
  26. mlrun/common/types.py +1 -0
  27. mlrun/config.py +91 -35
  28. mlrun/data_types/data_types.py +6 -1
  29. mlrun/data_types/spark.py +2 -2
  30. mlrun/data_types/to_pandas.py +57 -25
  31. mlrun/datastore/__init__.py +1 -0
  32. mlrun/datastore/alibaba_oss.py +3 -2
  33. mlrun/datastore/azure_blob.py +125 -37
  34. mlrun/datastore/base.py +42 -21
  35. mlrun/datastore/datastore.py +4 -2
  36. mlrun/datastore/datastore_profile.py +1 -1
  37. mlrun/datastore/dbfs_store.py +3 -7
  38. mlrun/datastore/filestore.py +1 -3
  39. mlrun/datastore/google_cloud_storage.py +85 -29
  40. mlrun/datastore/inmem.py +4 -1
  41. mlrun/datastore/redis.py +1 -0
  42. mlrun/datastore/s3.py +25 -12
  43. mlrun/datastore/sources.py +76 -4
  44. mlrun/datastore/spark_utils.py +30 -0
  45. mlrun/datastore/storeytargets.py +151 -0
  46. mlrun/datastore/targets.py +102 -131
  47. mlrun/datastore/v3io.py +1 -0
  48. mlrun/db/base.py +15 -6
  49. mlrun/db/httpdb.py +57 -28
  50. mlrun/db/nopdb.py +29 -5
  51. mlrun/errors.py +20 -3
  52. mlrun/execution.py +46 -5
  53. mlrun/feature_store/api.py +25 -1
  54. mlrun/feature_store/common.py +6 -11
  55. mlrun/feature_store/feature_vector.py +3 -1
  56. mlrun/feature_store/retrieval/job.py +4 -1
  57. mlrun/feature_store/retrieval/spark_merger.py +10 -39
  58. mlrun/feature_store/steps.py +8 -0
  59. mlrun/frameworks/_common/plan.py +3 -3
  60. mlrun/frameworks/_ml_common/plan.py +1 -1
  61. mlrun/frameworks/parallel_coordinates.py +2 -3
  62. mlrun/frameworks/sklearn/mlrun_interface.py +13 -3
  63. mlrun/k8s_utils.py +48 -2
  64. mlrun/launcher/client.py +6 -6
  65. mlrun/launcher/local.py +2 -2
  66. mlrun/model.py +215 -34
  67. mlrun/model_monitoring/api.py +38 -24
  68. mlrun/model_monitoring/applications/__init__.py +1 -2
  69. mlrun/model_monitoring/applications/_application_steps.py +60 -29
  70. mlrun/model_monitoring/applications/base.py +2 -174
  71. mlrun/model_monitoring/applications/context.py +197 -70
  72. mlrun/model_monitoring/applications/evidently_base.py +11 -85
  73. mlrun/model_monitoring/applications/histogram_data_drift.py +21 -16
  74. mlrun/model_monitoring/applications/results.py +4 -4
  75. mlrun/model_monitoring/controller.py +110 -282
  76. mlrun/model_monitoring/db/stores/__init__.py +8 -3
  77. mlrun/model_monitoring/db/stores/base/store.py +3 -0
  78. mlrun/model_monitoring/db/stores/sqldb/models/base.py +9 -7
  79. mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +18 -3
  80. mlrun/model_monitoring/db/stores/sqldb/sql_store.py +43 -23
  81. mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +48 -35
  82. mlrun/model_monitoring/db/tsdb/__init__.py +7 -2
  83. mlrun/model_monitoring/db/tsdb/base.py +147 -15
  84. mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +94 -55
  85. mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +0 -3
  86. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +144 -38
  87. mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +44 -3
  88. mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +246 -57
  89. mlrun/model_monitoring/helpers.py +70 -50
  90. mlrun/model_monitoring/stream_processing.py +96 -195
  91. mlrun/model_monitoring/writer.py +13 -5
  92. mlrun/package/packagers/default_packager.py +2 -2
  93. mlrun/projects/operations.py +16 -8
  94. mlrun/projects/pipelines.py +126 -115
  95. mlrun/projects/project.py +286 -129
  96. mlrun/render.py +3 -3
  97. mlrun/run.py +38 -19
  98. mlrun/runtimes/__init__.py +19 -8
  99. mlrun/runtimes/base.py +4 -1
  100. mlrun/runtimes/daskjob.py +1 -1
  101. mlrun/runtimes/funcdoc.py +1 -1
  102. mlrun/runtimes/kubejob.py +6 -6
  103. mlrun/runtimes/local.py +12 -5
  104. mlrun/runtimes/nuclio/api_gateway.py +68 -8
  105. mlrun/runtimes/nuclio/application/application.py +307 -70
  106. mlrun/runtimes/nuclio/function.py +63 -14
  107. mlrun/runtimes/nuclio/serving.py +10 -10
  108. mlrun/runtimes/pod.py +25 -19
  109. mlrun/runtimes/remotesparkjob.py +2 -5
  110. mlrun/runtimes/sparkjob/spark3job.py +16 -17
  111. mlrun/runtimes/utils.py +34 -0
  112. mlrun/serving/routers.py +2 -5
  113. mlrun/serving/server.py +37 -19
  114. mlrun/serving/states.py +30 -3
  115. mlrun/serving/v2_serving.py +44 -35
  116. mlrun/track/trackers/mlflow_tracker.py +5 -0
  117. mlrun/utils/async_http.py +1 -1
  118. mlrun/utils/db.py +18 -0
  119. mlrun/utils/helpers.py +150 -36
  120. mlrun/utils/http.py +1 -1
  121. mlrun/utils/notifications/notification/__init__.py +0 -1
  122. mlrun/utils/notifications/notification/webhook.py +8 -1
  123. mlrun/utils/notifications/notification_pusher.py +1 -1
  124. mlrun/utils/v3io_clients.py +2 -2
  125. mlrun/utils/version/version.json +2 -2
  126. {mlrun-1.7.0rc28.dist-info → mlrun-1.7.0rc55.dist-info}/METADATA +153 -66
  127. {mlrun-1.7.0rc28.dist-info → mlrun-1.7.0rc55.dist-info}/RECORD +131 -134
  128. {mlrun-1.7.0rc28.dist-info → mlrun-1.7.0rc55.dist-info}/WHEEL +1 -1
  129. mlrun/feature_store/retrieval/conversion.py +0 -271
  130. mlrun/model_monitoring/controller_handler.py +0 -37
  131. mlrun/model_monitoring/evidently_application.py +0 -20
  132. mlrun/model_monitoring/prometheus.py +0 -216
  133. {mlrun-1.7.0rc28.dist-info → mlrun-1.7.0rc55.dist-info}/LICENSE +0 -0
  134. {mlrun-1.7.0rc28.dist-info → mlrun-1.7.0rc55.dist-info}/entry_points.txt +0 -0
  135. {mlrun-1.7.0rc28.dist-info → mlrun-1.7.0rc55.dist-info}/top_level.txt +0 -0
@@ -27,6 +27,8 @@ import mlrun_pipelines.utils
27
27
  import mlrun
28
28
  import mlrun.common.runtimes.constants
29
29
  import mlrun.common.schemas
30
+ import mlrun.common.schemas.function
31
+ import mlrun.common.schemas.workflow
30
32
  import mlrun.utils.notifications
31
33
  from mlrun.errors import err_to_str
32
34
  from mlrun.utils import (
@@ -44,21 +46,21 @@ from ..runtimes.pod import AutoMountType
44
46
 
45
47
  def get_workflow_engine(engine_kind, local=False):
46
48
  if pipeline_context.is_run_local(local):
47
- if engine_kind == "kfp":
49
+ if engine_kind == mlrun.common.schemas.workflow.EngineType.KFP:
48
50
  logger.warning(
49
51
  "Running kubeflow pipeline locally, note some ops may not run locally!"
50
52
  )
51
- elif engine_kind == "remote":
53
+ elif engine_kind == mlrun.common.schemas.workflow.EngineType.REMOTE:
52
54
  raise mlrun.errors.MLRunInvalidArgumentError(
53
55
  "Cannot run a remote pipeline locally using `kind='remote'` and `local=True`. "
54
56
  "in order to run a local pipeline remotely, please use `engine='remote:local'` instead"
55
57
  )
56
58
  return _LocalRunner
57
- if not engine_kind or engine_kind == "kfp":
59
+ if not engine_kind or engine_kind == mlrun.common.schemas.workflow.EngineType.KFP:
58
60
  return _KFPRunner
59
- if engine_kind == "local":
61
+ if engine_kind == mlrun.common.schemas.workflow.EngineType.LOCAL:
60
62
  return _LocalRunner
61
- if engine_kind == "remote":
63
+ if engine_kind == mlrun.common.schemas.workflow.EngineType.REMOTE:
62
64
  return _RemoteRunner
63
65
  raise mlrun.errors.MLRunInvalidArgumentError(
64
66
  f"Provided workflow engine is not supported. engine_kind={engine_kind}"
@@ -80,6 +82,7 @@ class WorkflowSpec(mlrun.model.ModelObj):
80
82
  schedule: typing.Union[str, mlrun.common.schemas.ScheduleCronTrigger] = None,
81
83
  cleanup_ttl: typing.Optional[int] = None,
82
84
  image: typing.Optional[str] = None,
85
+ workflow_runner_node_selector: typing.Optional[dict[str, str]] = None,
83
86
  ):
84
87
  self.engine = engine
85
88
  self.code = code
@@ -93,6 +96,7 @@ class WorkflowSpec(mlrun.model.ModelObj):
93
96
  self._tmp_path = None
94
97
  self.schedule = schedule
95
98
  self.image = image
99
+ self.workflow_runner_node_selector = workflow_runner_node_selector
96
100
 
97
101
  def get_source_file(self, context=""):
98
102
  if not self.code and not self.path:
@@ -311,7 +315,11 @@ def get_db_function(project, key) -> mlrun.runtimes.BaseRuntime:
311
315
 
312
316
 
313
317
  def enrich_function_object(
314
- project, function, decorator=None, copy_function=True, try_auto_mount=True
318
+ project: mlrun.common.schemas.Project,
319
+ function: mlrun.runtimes.BaseRuntime,
320
+ decorator: typing.Callable = None,
321
+ copy_function: bool = True,
322
+ try_auto_mount: bool = True,
315
323
  ) -> mlrun.runtimes.BaseRuntime:
316
324
  if hasattr(function, "_enriched"):
317
325
  return function
@@ -352,7 +360,6 @@ def enrich_function_object(
352
360
  f.enrich_runtime_spec(
353
361
  project.spec.default_function_node_selector,
354
362
  )
355
-
356
363
  if try_auto_mount:
357
364
  if (
358
365
  decorator and AutoMountType.is_auto_modifier(decorator)
@@ -404,12 +411,15 @@ class _PipelineRunStatus:
404
411
  return self._exc
405
412
 
406
413
  def wait_for_completion(self, timeout=None, expected_statuses=None):
407
- self._state = self._engine.wait_for_completion(
408
- self.run_id,
414
+ returned_state = self._engine.wait_for_completion(
415
+ self,
409
416
  project=self.project,
410
417
  timeout=timeout,
411
418
  expected_statuses=expected_statuses,
412
419
  )
420
+ # TODO: returning a state is optional until all runners implement wait_for_completion
421
+ if returned_state:
422
+ self._state = returned_state
413
423
  return self._state
414
424
 
415
425
  def __str__(self):
@@ -444,13 +454,17 @@ class _PipelineRunner(abc.ABC):
444
454
  namespace=None,
445
455
  source=None,
446
456
  notifications: list[mlrun.model.Notification] = None,
447
- send_start_notification: bool = True,
448
457
  ) -> _PipelineRunStatus:
449
458
  pass
450
459
 
451
460
  @staticmethod
452
461
  @abc.abstractmethod
453
- def wait_for_completion(run_id, project=None, timeout=None, expected_statuses=None):
462
+ def wait_for_completion(
463
+ run: "_PipelineRunStatus",
464
+ project: typing.Optional["mlrun.projects.MlrunProject"] = None,
465
+ timeout: typing.Optional[int] = None,
466
+ expected_statuses: list[str] = None,
467
+ ):
454
468
  pass
455
469
 
456
470
  @staticmethod
@@ -458,6 +472,48 @@ class _PipelineRunner(abc.ABC):
458
472
  def get_state(run_id, project=None):
459
473
  pass
460
474
 
475
+ @staticmethod
476
+ def get_run_status(
477
+ project,
478
+ run: _PipelineRunStatus,
479
+ timeout=None,
480
+ expected_statuses=None,
481
+ notifiers: mlrun.utils.notifications.CustomNotificationPusher = None,
482
+ **kwargs,
483
+ ):
484
+ timeout = timeout or 60 * 60
485
+ raise_error = None
486
+ state = ""
487
+ try:
488
+ if timeout:
489
+ state = run.wait_for_completion(
490
+ timeout=timeout, expected_statuses=expected_statuses
491
+ )
492
+ except RuntimeError as exc:
493
+ # push runs table also when we have errors
494
+ raise_error = exc
495
+
496
+ mldb = mlrun.db.get_run_db(secrets=project._secrets)
497
+ runs = mldb.list_runs(project=project.name, labels=f"workflow={run.run_id}")
498
+
499
+ # TODO: The below section duplicates notifiers.push_pipeline_run_results() logic. We should use it instead.
500
+ errors_counter = 0
501
+ for r in runs:
502
+ if r["status"].get("state", "") == "error":
503
+ errors_counter += 1
504
+
505
+ text = _PipelineRunner._generate_workflow_finished_message(
506
+ run.run_id, errors_counter, run._state
507
+ )
508
+
509
+ notifiers = notifiers or project.notifiers
510
+ if notifiers:
511
+ notifiers.push(text, "info", runs)
512
+
513
+ if raise_error:
514
+ raise raise_error
515
+ return state or run._state, errors_counter, text
516
+
461
517
  @staticmethod
462
518
  def _get_handler(workflow_handler, workflow_spec, project, secrets):
463
519
  if not (workflow_handler and callable(workflow_handler)):
@@ -474,16 +530,13 @@ class _PipelineRunner(abc.ABC):
474
530
  return workflow_handler
475
531
 
476
532
  @staticmethod
477
- @abc.abstractmethod
478
- def get_run_status(
479
- project,
480
- run,
481
- timeout=None,
482
- expected_statuses=None,
483
- notifiers: mlrun.utils.notifications.CustomNotificationPusher = None,
484
- **kwargs,
485
- ):
486
- pass
533
+ def _generate_workflow_finished_message(run_id, errors_counter, state):
534
+ text = f"Workflow {run_id} finished"
535
+ if errors_counter:
536
+ text += f" with {errors_counter} errors"
537
+ if state:
538
+ text += f", state={state}"
539
+ return text
487
540
 
488
541
 
489
542
  class _KFPRunner(_PipelineRunner):
@@ -525,7 +578,6 @@ class _KFPRunner(_PipelineRunner):
525
578
  namespace=None,
526
579
  source=None,
527
580
  notifications: list[mlrun.model.Notification] = None,
528
- send_start_notification: bool = True,
529
581
  ) -> _PipelineRunStatus:
530
582
  pipeline_context.set(project, workflow_spec)
531
583
  workflow_handler = _PipelineRunner._get_handler(
@@ -541,12 +593,13 @@ class _KFPRunner(_PipelineRunner):
541
593
  logger.warning(
542
594
  "Setting notifications on kfp pipeline runner uses old notification behavior. "
543
595
  "Notifications will only be sent if you wait for pipeline completion. "
544
- "To use the new notification behavior, use the remote pipeline runner."
596
+ "Some of the features (like setting message or severity level) are not supported."
545
597
  )
546
- for notification in notifications:
547
- project.notifiers.add_notification(
548
- notification.kind, notification.params
549
- )
598
+ # for start message, fallback to old notification behavior
599
+ for notification in notifications or []:
600
+ params = notification.params
601
+ params.update(notification.secret_params)
602
+ project.notifiers.add_notification(notification.kind, params)
550
603
 
551
604
  run_id = _run_pipeline(
552
605
  workflow_handler,
@@ -574,23 +627,31 @@ class _KFPRunner(_PipelineRunner):
574
627
  func_name=func.metadata.name,
575
628
  exc_info=err_to_str(exc),
576
629
  )
577
- if send_start_notification:
578
- project.notifiers.push_pipeline_start_message(
579
- project.metadata.name,
580
- project.get_param("commit_id", None),
581
- run_id,
582
- True,
583
- )
630
+ project.notifiers.push_pipeline_start_message(
631
+ project.metadata.name,
632
+ project.get_param("commit_id", None),
633
+ run_id,
634
+ True,
635
+ )
584
636
  pipeline_context.clear()
585
637
  return _PipelineRunStatus(run_id, cls, project=project, workflow=workflow_spec)
586
638
 
587
639
  @staticmethod
588
- def wait_for_completion(run_id, project=None, timeout=None, expected_statuses=None):
589
- if timeout is None:
590
- timeout = 60 * 60
640
+ def wait_for_completion(
641
+ run: "_PipelineRunStatus",
642
+ project: typing.Optional["mlrun.projects.MlrunProject"] = None,
643
+ timeout: typing.Optional[int] = None,
644
+ expected_statuses: list[str] = None,
645
+ ):
591
646
  project_name = project.metadata.name if project else ""
647
+ logger.info(
648
+ "Waiting for pipeline run completion",
649
+ run_id=run.run_id,
650
+ project=project_name,
651
+ )
652
+ timeout = timeout or 60 * 60
592
653
  run_info = wait_for_pipeline_completion(
593
- run_id,
654
+ run.run_id,
594
655
  timeout=timeout,
595
656
  expected_statuses=expected_statuses,
596
657
  project=project_name,
@@ -608,51 +669,6 @@ class _KFPRunner(_PipelineRunner):
608
669
  return resp["run"].get("status", "")
609
670
  return ""
610
671
 
611
- @staticmethod
612
- def get_run_status(
613
- project,
614
- run,
615
- timeout=None,
616
- expected_statuses=None,
617
- notifiers: mlrun.utils.notifications.CustomNotificationPusher = None,
618
- **kwargs,
619
- ):
620
- if timeout is None:
621
- timeout = 60 * 60
622
- state = ""
623
- raise_error = None
624
- try:
625
- if timeout:
626
- logger.info("Waiting for pipeline run completion")
627
- state = run.wait_for_completion(
628
- timeout=timeout, expected_statuses=expected_statuses
629
- )
630
- except RuntimeError as exc:
631
- # push runs table also when we have errors
632
- raise_error = exc
633
-
634
- mldb = mlrun.db.get_run_db(secrets=project._secrets)
635
- runs = mldb.list_runs(project=project.name, labels=f"workflow={run.run_id}")
636
-
637
- # TODO: The below section duplicates notifiers.push_pipeline_run_results() logic. We should use it instead.
638
- had_errors = 0
639
- for r in runs:
640
- if r["status"].get("state", "") == "error":
641
- had_errors += 1
642
-
643
- text = f"Workflow {run.run_id} finished"
644
- if had_errors:
645
- text += f" with {had_errors} errors"
646
- if state:
647
- text += f", state={state}"
648
-
649
- notifiers = notifiers or project.notifiers
650
- notifiers.push(text, "info", runs)
651
-
652
- if raise_error:
653
- raise raise_error
654
- return state, had_errors, text
655
-
656
672
 
657
673
  class _LocalRunner(_PipelineRunner):
658
674
  """local pipelines runner"""
@@ -671,7 +687,6 @@ class _LocalRunner(_PipelineRunner):
671
687
  namespace=None,
672
688
  source=None,
673
689
  notifications: list[mlrun.model.Notification] = None,
674
- send_start_notification: bool = True,
675
690
  ) -> _PipelineRunStatus:
676
691
  pipeline_context.set(project, workflow_spec)
677
692
  workflow_handler = _PipelineRunner._get_handler(
@@ -693,10 +708,9 @@ class _LocalRunner(_PipelineRunner):
693
708
  project.set_source(source=source)
694
709
  pipeline_context.workflow_artifact_path = artifact_path
695
710
 
696
- if send_start_notification:
697
- project.notifiers.push_pipeline_start_message(
698
- project.metadata.name, pipeline_id=workflow_id
699
- )
711
+ project.notifiers.push_pipeline_start_message(
712
+ project.metadata.name, pipeline_id=workflow_id
713
+ )
700
714
  err = None
701
715
  try:
702
716
  workflow_handler(**workflow_spec.args)
@@ -732,18 +746,10 @@ class _LocalRunner(_PipelineRunner):
732
746
  return ""
733
747
 
734
748
  @staticmethod
735
- def wait_for_completion(run_id, project=None, timeout=None, expected_statuses=None):
736
- pass
737
-
738
- @staticmethod
739
- def get_run_status(
740
- project,
741
- run,
742
- timeout=None,
743
- expected_statuses=None,
744
- notifiers: mlrun.utils.notifications.CustomNotificationPusher = None,
745
- **kwargs,
746
- ):
749
+ def wait_for_completion(run, project=None, timeout=None, expected_statuses=None):
750
+ # TODO: local runner blocks for the duration of the pipeline.
751
+ # Therefore usually there will be nothing to wait for.
752
+ # However, users may run functions with watch=False and then it can be useful to wait for the runs here.
747
753
  pass
748
754
 
749
755
 
@@ -764,22 +770,10 @@ class _RemoteRunner(_PipelineRunner):
764
770
  namespace: str = None,
765
771
  source: str = None,
766
772
  notifications: list[mlrun.model.Notification] = None,
767
- send_start_notification: bool = True,
768
773
  ) -> typing.Optional[_PipelineRunStatus]:
769
774
  workflow_name = normalize_workflow_name(name=name, project_name=project.name)
770
775
  workflow_id = None
771
776
 
772
- # for start message, fallback to old notification behavior
773
- if send_start_notification:
774
- for notification in notifications or []:
775
- project.notifiers.add_notification(
776
- notification.kind, notification.params
777
- )
778
- # if a notification with `when=running` is provided, it will be used explicitly and others
779
- # will be ignored
780
- if "running" in notification.when:
781
- break
782
-
783
777
  # The returned engine for this runner is the engine of the workflow.
784
778
  # In this way wait_for_completion/get_run_status would be executed by the correct pipeline runner.
785
779
  inner_engine = get_workflow_engine(workflow_spec.engine)
@@ -879,9 +873,6 @@ class _RemoteRunner(_PipelineRunner):
879
873
  state = mlrun_pipelines.common.models.RunStatuses.failed
880
874
  else:
881
875
  state = mlrun_pipelines.common.models.RunStatuses.running
882
- project.notifiers.push_pipeline_start_message(
883
- project.metadata.name,
884
- )
885
876
  pipeline_context.clear()
886
877
  return _PipelineRunStatus(
887
878
  run_id=workflow_id,
@@ -924,13 +915,25 @@ class _RemoteRunner(_PipelineRunner):
924
915
  elif inner_engine.engine == _LocalRunner.engine:
925
916
  mldb = mlrun.db.get_run_db(secrets=project._secrets)
926
917
  pipeline_runner_run = mldb.read_run(run.run_id, project=project.name)
918
+
927
919
  pipeline_runner_run = mlrun.run.RunObject.from_dict(pipeline_runner_run)
920
+
921
+ # here we are waiting for the pipeline run to complete and refreshing after that the pipeline run from the
922
+ # db
923
+ # TODO: do it with timeout
928
924
  pipeline_runner_run.logs(db=mldb)
929
925
  pipeline_runner_run.refresh()
930
926
  run._state = mlrun.common.runtimes.constants.RunStates.run_state_to_pipeline_run_status(
931
927
  pipeline_runner_run.status.state
932
928
  )
933
929
  run._exc = pipeline_runner_run.status.error
930
+ return _LocalRunner.get_run_status(
931
+ project,
932
+ run,
933
+ timeout,
934
+ expected_statuses,
935
+ notifiers=notifiers,
936
+ )
934
937
 
935
938
  else:
936
939
  raise mlrun.errors.MLRunInvalidArgumentError(
@@ -1075,6 +1078,13 @@ def load_and_run(
1075
1078
  if load_only:
1076
1079
  return
1077
1080
 
1081
+ # extract "start" notification if exists
1082
+ start_notifications = [
1083
+ notification
1084
+ for notification in context.get_notifications(unmask_secret_params=True)
1085
+ if "running" in notification.when
1086
+ ]
1087
+
1078
1088
  workflow_log_message = workflow_name or workflow_path
1079
1089
  context.logger.info(f"Running workflow {workflow_log_message} from remote")
1080
1090
  run = project.run(
@@ -1090,6 +1100,7 @@ def load_and_run(
1090
1100
  cleanup_ttl=cleanup_ttl,
1091
1101
  engine=engine,
1092
1102
  local=local,
1103
+ notifications=start_notifications,
1093
1104
  )
1094
1105
  context.log_result(key="workflow_id", value=run.run_id)
1095
1106
  context.log_result(key="engine", value=run._engine.engine, commit=True)