mlrun 1.10.0rc6__py3-none-any.whl → 1.10.0rc8__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 (52) hide show
  1. mlrun/__init__.py +3 -1
  2. mlrun/__main__.py +47 -4
  3. mlrun/artifacts/base.py +0 -27
  4. mlrun/artifacts/dataset.py +0 -8
  5. mlrun/artifacts/model.py +0 -7
  6. mlrun/artifacts/plots.py +0 -13
  7. mlrun/common/schemas/background_task.py +5 -0
  8. mlrun/common/schemas/model_monitoring/__init__.py +2 -0
  9. mlrun/common/schemas/model_monitoring/constants.py +16 -0
  10. mlrun/common/schemas/project.py +4 -0
  11. mlrun/common/schemas/serving.py +2 -0
  12. mlrun/config.py +11 -22
  13. mlrun/datastore/utils.py +3 -1
  14. mlrun/db/base.py +0 -19
  15. mlrun/db/httpdb.py +73 -65
  16. mlrun/db/nopdb.py +0 -12
  17. mlrun/frameworks/tf_keras/__init__.py +4 -4
  18. mlrun/frameworks/tf_keras/callbacks/logging_callback.py +23 -20
  19. mlrun/frameworks/tf_keras/model_handler.py +69 -9
  20. mlrun/frameworks/tf_keras/utils.py +12 -1
  21. mlrun/launcher/base.py +7 -0
  22. mlrun/launcher/client.py +2 -21
  23. mlrun/launcher/local.py +4 -0
  24. mlrun/model_monitoring/applications/_application_steps.py +23 -39
  25. mlrun/model_monitoring/applications/base.py +167 -32
  26. mlrun/model_monitoring/helpers.py +0 -3
  27. mlrun/projects/operations.py +11 -24
  28. mlrun/projects/pipelines.py +33 -3
  29. mlrun/projects/project.py +45 -89
  30. mlrun/run.py +37 -5
  31. mlrun/runtimes/daskjob.py +2 -0
  32. mlrun/runtimes/kubejob.py +5 -8
  33. mlrun/runtimes/mpijob/abstract.py +2 -0
  34. mlrun/runtimes/mpijob/v1.py +2 -0
  35. mlrun/runtimes/nuclio/function.py +2 -0
  36. mlrun/runtimes/nuclio/serving.py +60 -5
  37. mlrun/runtimes/pod.py +3 -0
  38. mlrun/runtimes/remotesparkjob.py +2 -0
  39. mlrun/runtimes/sparkjob/spark3job.py +2 -0
  40. mlrun/serving/__init__.py +2 -0
  41. mlrun/serving/server.py +253 -29
  42. mlrun/serving/states.py +215 -18
  43. mlrun/serving/system_steps.py +391 -0
  44. mlrun/serving/v2_serving.py +9 -8
  45. mlrun/utils/helpers.py +18 -4
  46. mlrun/utils/version/version.json +2 -2
  47. {mlrun-1.10.0rc6.dist-info → mlrun-1.10.0rc8.dist-info}/METADATA +9 -9
  48. {mlrun-1.10.0rc6.dist-info → mlrun-1.10.0rc8.dist-info}/RECORD +52 -51
  49. {mlrun-1.10.0rc6.dist-info → mlrun-1.10.0rc8.dist-info}/WHEEL +0 -0
  50. {mlrun-1.10.0rc6.dist-info → mlrun-1.10.0rc8.dist-info}/entry_points.txt +0 -0
  51. {mlrun-1.10.0rc6.dist-info → mlrun-1.10.0rc8.dist-info}/licenses/LICENSE +0 -0
  52. {mlrun-1.10.0rc6.dist-info → mlrun-1.10.0rc8.dist-info}/top_level.txt +0 -0
@@ -13,7 +13,6 @@
13
13
  # limitations under the License.
14
14
 
15
15
  import collections
16
- import json
17
16
  import traceback
18
17
  from collections import OrderedDict
19
18
  from datetime import datetime
@@ -23,10 +22,12 @@ import mlrun.common.schemas
23
22
  import mlrun.common.schemas.alert as alert_objects
24
23
  import mlrun.common.schemas.model_monitoring.constants as mm_constants
25
24
  import mlrun.model_monitoring.helpers
25
+ import mlrun.platforms.iguazio
26
26
  from mlrun.serving import GraphContext
27
27
  from mlrun.serving.utils import StepToDict
28
28
  from mlrun.utils import logger
29
29
 
30
+ from .base import _serialize_context_and_result
30
31
  from .context import MonitoringApplicationContext
31
32
  from .results import (
32
33
  ModelMonitoringApplicationMetric,
@@ -45,7 +46,7 @@ class _PushToMonitoringWriter(StepToDict):
45
46
  :param project: Project name.
46
47
  """
47
48
  self.project = project
48
- self.output_stream = None
49
+ self._output_stream = None
49
50
 
50
51
  def do(
51
52
  self,
@@ -65,48 +66,31 @@ class _PushToMonitoringWriter(StepToDict):
65
66
 
66
67
  :param event: Monitoring result(s) to push and the original event from the controller.
67
68
  """
68
- self._lazy_init()
69
69
  application_results, application_context = event
70
- writer_event = {
71
- mm_constants.WriterEvent.ENDPOINT_NAME: application_context.endpoint_name,
72
- mm_constants.WriterEvent.APPLICATION_NAME: application_context.application_name,
73
- mm_constants.WriterEvent.ENDPOINT_ID: application_context.endpoint_id,
74
- mm_constants.WriterEvent.START_INFER_TIME: application_context.start_infer_time.isoformat(
75
- sep=" ", timespec="microseconds"
76
- ),
77
- mm_constants.WriterEvent.END_INFER_TIME: application_context.end_infer_time.isoformat(
78
- sep=" ", timespec="microseconds"
79
- ),
80
- }
81
- for result in application_results:
82
- data = result.to_dict()
83
- if isinstance(result, ModelMonitoringApplicationResult):
84
- writer_event[mm_constants.WriterEvent.EVENT_KIND] = (
85
- mm_constants.WriterEventKind.RESULT
86
- )
87
- elif isinstance(result, _ModelMonitoringApplicationStats):
88
- writer_event[mm_constants.WriterEvent.EVENT_KIND] = (
89
- mm_constants.WriterEventKind.STATS
90
- )
91
- else:
92
- writer_event[mm_constants.WriterEvent.EVENT_KIND] = (
93
- mm_constants.WriterEventKind.METRIC
94
- )
95
- writer_event[mm_constants.WriterEvent.DATA] = json.dumps(data)
96
- logger.debug(
97
- "Pushing data to output stream", writer_event=str(writer_event)
98
- )
99
- self.output_stream.push(
100
- [writer_event], partition_key=application_context.endpoint_id
101
- )
102
- logger.debug("Pushed data to output stream successfully")
103
70
 
104
- def _lazy_init(self):
105
- if self.output_stream is None:
106
- self.output_stream = mlrun.model_monitoring.helpers.get_output_stream(
71
+ writer_events = [
72
+ _serialize_context_and_result(context=application_context, result=result)
73
+ for result in application_results
74
+ ]
75
+
76
+ logger.debug("Pushing data to output stream", writer_events=str(writer_events))
77
+ self.output_stream.push(
78
+ writer_events, partition_key=application_context.endpoint_id
79
+ )
80
+ logger.debug("Pushed data to output stream successfully")
81
+
82
+ @property
83
+ def output_stream(
84
+ self,
85
+ ) -> Union[
86
+ mlrun.platforms.iguazio.OutputStream, mlrun.platforms.iguazio.KafkaOutputStream
87
+ ]:
88
+ if self._output_stream is None:
89
+ self._output_stream = mlrun.model_monitoring.helpers.get_output_stream(
107
90
  project=self.project,
108
91
  function_name=mm_constants.MonitoringFunctionNames.WRITER,
109
92
  )
93
+ return self._output_stream
110
94
 
111
95
 
112
96
  class _PrepareMonitoringEvent(StepToDict):
@@ -12,9 +12,12 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ import json
15
16
  import socket
16
17
  from abc import ABC, abstractmethod
18
+ from collections import defaultdict
17
19
  from collections.abc import Iterator
20
+ from contextlib import contextmanager
18
21
  from datetime import datetime, timedelta
19
22
  from typing import Any, Optional, Union, cast
20
23
 
@@ -23,14 +26,58 @@ import pandas as pd
23
26
  import mlrun
24
27
  import mlrun.common.constants as mlrun_constants
25
28
  import mlrun.common.schemas.model_monitoring.constants as mm_constants
29
+ import mlrun.datastore.datastore_profile as ds_profile
26
30
  import mlrun.errors
27
31
  import mlrun.model_monitoring.api as mm_api
28
32
  import mlrun.model_monitoring.applications.context as mm_context
29
33
  import mlrun.model_monitoring.applications.results as mm_results
34
+ import mlrun.model_monitoring.helpers as mm_helpers
30
35
  from mlrun.serving.utils import MonitoringApplicationToDict
31
36
  from mlrun.utils import logger
32
37
 
33
38
 
39
+ def _serialize_context_and_result(
40
+ *,
41
+ context: mm_context.MonitoringApplicationContext,
42
+ result: Union[
43
+ mm_results.ModelMonitoringApplicationResult,
44
+ mm_results.ModelMonitoringApplicationMetric,
45
+ mm_results._ModelMonitoringApplicationStats,
46
+ ],
47
+ ) -> dict[mm_constants.WriterEvent, str]:
48
+ """
49
+ Serialize the returned result from a model monitoring application and its context
50
+ for the writer.
51
+ """
52
+ writer_event = {
53
+ mm_constants.WriterEvent.ENDPOINT_NAME: context.endpoint_name,
54
+ mm_constants.WriterEvent.APPLICATION_NAME: context.application_name,
55
+ mm_constants.WriterEvent.ENDPOINT_ID: context.endpoint_id,
56
+ mm_constants.WriterEvent.START_INFER_TIME: context.start_infer_time.isoformat(
57
+ sep=" ", timespec="microseconds"
58
+ ),
59
+ mm_constants.WriterEvent.END_INFER_TIME: context.end_infer_time.isoformat(
60
+ sep=" ", timespec="microseconds"
61
+ ),
62
+ }
63
+
64
+ if isinstance(result, mm_results.ModelMonitoringApplicationResult):
65
+ writer_event[mm_constants.WriterEvent.EVENT_KIND] = (
66
+ mm_constants.WriterEventKind.RESULT
67
+ )
68
+ elif isinstance(result, mm_results._ModelMonitoringApplicationStats):
69
+ writer_event[mm_constants.WriterEvent.EVENT_KIND] = (
70
+ mm_constants.WriterEventKind.STATS
71
+ )
72
+ else:
73
+ writer_event[mm_constants.WriterEvent.EVENT_KIND] = (
74
+ mm_constants.WriterEventKind.METRIC
75
+ )
76
+ writer_event[mm_constants.WriterEvent.DATA] = json.dumps(result.to_dict())
77
+
78
+ return writer_event
79
+
80
+
34
81
  class ModelMonitoringApplicationBase(MonitoringApplicationToDict, ABC):
35
82
  """
36
83
  The base class for a model monitoring application.
@@ -118,6 +165,43 @@ class ModelMonitoringApplicationBase(MonitoringApplicationToDict, ABC):
118
165
  ]
119
166
  return result
120
167
 
168
+ @staticmethod
169
+ @contextmanager
170
+ def _push_to_writer(
171
+ *,
172
+ write_output: bool,
173
+ stream_profile: Optional[ds_profile.DatastoreProfile],
174
+ ) -> Iterator[dict[str, list[tuple]]]:
175
+ endpoints_output: dict[str, list[tuple]] = defaultdict(list)
176
+ try:
177
+ yield endpoints_output
178
+ finally:
179
+ if write_output:
180
+ logger.debug(
181
+ "Pushing model monitoring application job data to the writer stream",
182
+ passed_stream_profile=str(stream_profile),
183
+ )
184
+ project_name = (
185
+ mlrun.mlconf.active_project or mlrun.get_current_project().name
186
+ )
187
+ writer_stream = mm_helpers.get_output_stream(
188
+ project=project_name,
189
+ function_name=mm_constants.MonitoringFunctionNames.WRITER,
190
+ profile=stream_profile,
191
+ )
192
+ for endpoint_id, outputs in endpoints_output.items():
193
+ writer_stream.push(
194
+ [
195
+ _serialize_context_and_result(context=ctx, result=res)
196
+ for ctx, res in outputs
197
+ ],
198
+ partition_key=endpoint_id,
199
+ )
200
+ logger.debug(
201
+ "Pushed the data to all the relevant model endpoints successfully",
202
+ endpoints_output=endpoints_output,
203
+ )
204
+
121
205
  def _handler(
122
206
  self,
123
207
  context: "mlrun.MLClientCtx",
@@ -127,6 +211,8 @@ class ModelMonitoringApplicationBase(MonitoringApplicationToDict, ABC):
127
211
  start: Optional[str] = None,
128
212
  end: Optional[str] = None,
129
213
  base_period: Optional[int] = None,
214
+ write_output: bool = False,
215
+ stream_profile: Optional[ds_profile.DatastoreProfile] = None,
130
216
  ):
131
217
  """
132
218
  A custom handler that wraps the application's logic implemented in
@@ -134,46 +220,69 @@ class ModelMonitoringApplicationBase(MonitoringApplicationToDict, ABC):
134
220
  for an MLRun job.
135
221
  This method should not be called directly.
136
222
  """
223
+
224
+ if write_output and (
225
+ not endpoints or sample_data is not None or reference_data is not None
226
+ ):
227
+ raise mlrun.errors.MLRunValueError(
228
+ "Writing the results of an application to the TSDB is possible only when "
229
+ "working with endpoints, without any custom data-frame input"
230
+ )
231
+
137
232
  feature_stats = (
138
233
  mm_api.get_sample_set_statistics(reference_data)
139
234
  if reference_data is not None
140
235
  else None
141
236
  )
142
237
 
143
- def call_do_tracking(event: Optional[dict] = None):
144
- if event is None:
145
- event = {}
146
- monitoring_context = mm_context.MonitoringApplicationContext._from_ml_ctx(
147
- event=event,
148
- application_name=self.__class__.__name__,
149
- context=context,
150
- sample_df=sample_data,
151
- feature_stats=feature_stats,
152
- )
153
- return self.do_tracking(monitoring_context)
154
-
155
- if endpoints is not None:
156
- for window_start, window_end in self._window_generator(
157
- start, end, base_period
158
- ):
159
- for endpoint_name, endpoint_id in endpoints:
160
- result = call_do_tracking(
161
- event={
162
- mm_constants.ApplicationEvent.ENDPOINT_NAME: endpoint_name,
163
- mm_constants.ApplicationEvent.ENDPOINT_ID: endpoint_id,
164
- mm_constants.ApplicationEvent.START_INFER_TIME: window_start,
165
- mm_constants.ApplicationEvent.END_INFER_TIME: window_end,
166
- }
167
- )
168
- result_key = (
169
- f"{endpoint_name}-{endpoint_id}_{window_start.isoformat()}_{window_end.isoformat()}"
170
- if window_start and window_end
171
- else f"{endpoint_name}-{endpoint_id}"
238
+ with self._push_to_writer(
239
+ write_output=write_output, stream_profile=stream_profile
240
+ ) as endpoints_output:
241
+
242
+ def call_do_tracking(event: Optional[dict] = None):
243
+ nonlocal endpoints_output
244
+
245
+ if event is None:
246
+ event = {}
247
+ monitoring_context = (
248
+ mm_context.MonitoringApplicationContext._from_ml_ctx(
249
+ event=event,
250
+ application_name=self.__class__.__name__,
251
+ context=context,
252
+ sample_df=sample_data,
253
+ feature_stats=feature_stats,
172
254
  )
255
+ )
256
+ result = self.do_tracking(monitoring_context)
257
+ endpoints_output[monitoring_context.endpoint_id].append(
258
+ (monitoring_context, result)
259
+ )
260
+ return result
261
+
262
+ if endpoints is not None:
263
+ for window_start, window_end in self._window_generator(
264
+ start, end, base_period
265
+ ):
266
+ for endpoint_name, endpoint_id in endpoints:
267
+ result = call_do_tracking(
268
+ event={
269
+ mm_constants.ApplicationEvent.ENDPOINT_NAME: endpoint_name,
270
+ mm_constants.ApplicationEvent.ENDPOINT_ID: endpoint_id,
271
+ mm_constants.ApplicationEvent.START_INFER_TIME: window_start,
272
+ mm_constants.ApplicationEvent.END_INFER_TIME: window_end,
273
+ }
274
+ )
275
+ result_key = (
276
+ f"{endpoint_name}-{endpoint_id}_{window_start.isoformat()}_{window_end.isoformat()}"
277
+ if window_start and window_end
278
+ else f"{endpoint_name}-{endpoint_id}"
279
+ )
173
280
 
174
- context.log_result(result_key, self._flatten_data_result(result))
175
- else:
176
- return self._flatten_data_result(call_do_tracking())
281
+ context.log_result(
282
+ result_key, self._flatten_data_result(result)
283
+ )
284
+ else:
285
+ return self._flatten_data_result(call_do_tracking())
177
286
 
178
287
  @staticmethod
179
288
  def _handle_endpoints_type_evaluate(
@@ -338,6 +447,7 @@ class ModelMonitoringApplicationBase(MonitoringApplicationToDict, ABC):
338
447
  * ``start``, ``datetime``
339
448
  * ``end``, ``datetime``
340
449
  * ``base_period``, ``int``
450
+ * ``write_output``, ``bool``
341
451
 
342
452
  For Git sources, add the source archive to the returned job and change the handler:
343
453
 
@@ -420,6 +530,8 @@ class ModelMonitoringApplicationBase(MonitoringApplicationToDict, ABC):
420
530
  start: Optional[datetime] = None,
421
531
  end: Optional[datetime] = None,
422
532
  base_period: Optional[int] = None,
533
+ write_output: bool = False,
534
+ stream_profile: Optional[ds_profile.DatastoreProfile] = None,
423
535
  ) -> "mlrun.RunObject":
424
536
  """
425
537
  Call this function to run the application's
@@ -470,6 +582,14 @@ class ModelMonitoringApplicationBase(MonitoringApplicationToDict, ABC):
470
582
  ..., (\\operatorname{start} +
471
583
  m\\cdot\\operatorname{base\\_period}, \\operatorname{end}]`,
472
584
  where :math:`m` is some positive integer.
585
+ :param write_output: Whether to write the results and metrics to the time-series DB. Can be ``True`` only
586
+ if ``endpoints`` are passed.
587
+ Note: the model monitoring infrastructure must be up for the writing to work.
588
+ :param stream_profile: The stream datastore profile. It should be provided only when running locally and
589
+ writing the outputs to the database (i.e., when both ``run_local`` and
590
+ ``write_output`` are set to ``True``).
591
+ For more details on configuring the stream profile, see
592
+ :py:meth:`~mlrun.projects.MlrunProject.set_model_monitoring_credentials`.
473
593
 
474
594
  :returns: The output of the
475
595
  :py:meth:`~mlrun.model_monitoring.applications.ModelMonitoringApplicationBase.do_tracking`
@@ -507,10 +627,25 @@ class ModelMonitoringApplicationBase(MonitoringApplicationToDict, ABC):
507
627
  )
508
628
  params["end"] = end.isoformat() if isinstance(end, datetime) else end
509
629
  params["base_period"] = base_period
630
+ params["write_output"] = write_output
631
+ if stream_profile:
632
+ if not run_local:
633
+ raise mlrun.errors.MLRunValueError(
634
+ "Passing a `stream_profile` is relevant only when running locally"
635
+ )
636
+ if not write_output:
637
+ raise mlrun.errors.MLRunValueError(
638
+ "Passing a `stream_profile` is relevant only when writing the outputs"
639
+ )
640
+ params["stream_profile"] = stream_profile
510
641
  elif start or end or base_period:
511
642
  raise mlrun.errors.MLRunValueError(
512
643
  "Custom `start` and `end` times or base_period are supported only with endpoints data"
513
644
  )
645
+ elif write_output or stream_profile:
646
+ raise mlrun.errors.MLRunValueError(
647
+ "Writing the application output or passing `stream_profile` are supported only with endpoints data"
648
+ )
514
649
 
515
650
  inputs: dict[str, str] = {}
516
651
  for data, identifier in [
@@ -22,14 +22,11 @@ import numpy as np
22
22
  import pandas as pd
23
23
 
24
24
  import mlrun
25
- import mlrun.artifacts
26
25
  import mlrun.common.model_monitoring.helpers
27
26
  import mlrun.common.schemas.model_monitoring.constants as mm_constants
28
27
  import mlrun.data_types.infer
29
28
  import mlrun.datastore.datastore_profile
30
- import mlrun.model_monitoring
31
29
  import mlrun.platforms.iguazio
32
- import mlrun.utils.helpers
33
30
  from mlrun.common.schemas import ModelEndpoint
34
31
  from mlrun.common.schemas.model_monitoring.model_endpoints import (
35
32
  ModelEndpointMonitoringMetric,
@@ -281,7 +281,7 @@ def build_function(
281
281
  mlrun_version_specifier=None,
282
282
  builder_env: Optional[dict] = None,
283
283
  project_object=None,
284
- overwrite_build_params: bool = False,
284
+ overwrite_build_params: bool = True,
285
285
  extra_args: Optional[str] = None,
286
286
  force_build: bool = False,
287
287
  ) -> Union[BuildStatus, mlrun_pipelines.models.PipelineNodeWrapper]:
@@ -308,13 +308,6 @@ def build_function(
308
308
  e.g. extra_args="--skip-tls-verify --build-arg A=val"
309
309
  :param force_build: Force building the image, even when no changes were made
310
310
  """
311
- if not overwrite_build_params:
312
- # TODO: change overwrite_build_params default to True in 1.10.0
313
- warnings.warn(
314
- "The `overwrite_build_params` parameter default will change from 'False' to 'True' in 1.10.0.",
315
- mlrun.utils.OverwriteBuildParamsWarning,
316
- )
317
-
318
311
  engine, function = _get_engine_and_function(function, project_object)
319
312
  if function.kind in mlrun.runtimes.RuntimeKinds.nuclio_runtimes():
320
313
  raise mlrun.errors.MLRunInvalidArgumentError(
@@ -340,22 +333,16 @@ def build_function(
340
333
  skip_deployed=skip_deployed,
341
334
  )
342
335
  else:
343
- # TODO: remove filter once overwrite_build_params default is changed to True in 1.10.0
344
- with warnings.catch_warnings():
345
- warnings.simplefilter(
346
- "ignore", category=mlrun.utils.OverwriteBuildParamsWarning
347
- )
348
-
349
- function.build_config(
350
- image=image,
351
- base_image=base_image,
352
- commands=commands,
353
- secret=secret_name,
354
- requirements=requirements,
355
- requirements_file=requirements_file,
356
- overwrite=overwrite_build_params,
357
- extra_args=extra_args,
358
- )
336
+ function.build_config(
337
+ image=image,
338
+ base_image=base_image,
339
+ commands=commands,
340
+ secret=secret_name,
341
+ requirements=requirements,
342
+ requirements_file=requirements_file,
343
+ overwrite=overwrite_build_params,
344
+ extra_args=extra_args,
345
+ )
359
346
  ready = function.deploy(
360
347
  watch=True,
361
348
  with_mlrun=with_mlrun,
@@ -39,7 +39,12 @@ from mlrun.utils import (
39
39
 
40
40
  from ..common.helpers import parse_versioned_object_uri
41
41
  from ..config import config
42
- from ..run import _run_pipeline, retry_pipeline, wait_for_pipeline_completion
42
+ from ..run import (
43
+ _run_pipeline,
44
+ retry_pipeline,
45
+ terminate_pipeline,
46
+ wait_for_pipeline_completion,
47
+ )
43
48
  from ..runtimes.pod import AutoMountType
44
49
 
45
50
 
@@ -696,6 +701,24 @@ class _KFPRunner(_PipelineRunner):
696
701
  )
697
702
  return run_id
698
703
 
704
+ @classmethod
705
+ def terminate(
706
+ cls,
707
+ run: "_PipelineRunStatus",
708
+ project: typing.Optional["mlrun.projects.MlrunProject"] = None,
709
+ ) -> str:
710
+ project_name = project.metadata.name if project else ""
711
+ logger.info(
712
+ "Terminating pipeline",
713
+ run_id=run.run_id,
714
+ project=project_name,
715
+ )
716
+ run_id = terminate_pipeline(
717
+ run.run_id,
718
+ project=project_name,
719
+ )
720
+ return run_id
721
+
699
722
  @staticmethod
700
723
  def wait_for_completion(
701
724
  run: "_PipelineRunStatus",
@@ -1145,7 +1168,9 @@ def load_and_run_workflow(
1145
1168
  notification.when = ["running"]
1146
1169
 
1147
1170
  workflow_log_message = workflow_name or workflow_path
1148
- context.logger.info(f"Running workflow {workflow_log_message} from remote")
1171
+ context.logger.info(
1172
+ "Running workflow from remote", workflow_log_message=workflow_log_message
1173
+ )
1149
1174
  run = project.run(
1150
1175
  name=workflow_name,
1151
1176
  workflow_path=workflow_path,
@@ -1162,6 +1187,11 @@ def load_and_run_workflow(
1162
1187
  notifications=start_notifications,
1163
1188
  context=context,
1164
1189
  )
1190
+ # Patch the current run object (the workflow-runner) with the workflow-id label
1191
+ context.logger.info(
1192
+ "Associating workflow-runner with workflow ID", run_id=run.run_id
1193
+ )
1194
+ context.set_label("workflow-id", run.run_id)
1165
1195
  context.log_result(key="workflow_id", value=run.run_id)
1166
1196
  context.log_result(key="engine", value=run._engine.engine, commit=True)
1167
1197
 
@@ -1321,4 +1351,4 @@ def import_remote_project(
1321
1351
  sync_functions=True,
1322
1352
  )
1323
1353
 
1324
- context.logger.info(f"Loaded project {project.name} successfully")
1354
+ context.logger.info("Loaded project successfully", project_name=project.name)