mlrun 1.7.0rc43__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 (68) hide show
  1. mlrun/__main__.py +4 -2
  2. mlrun/artifacts/manager.py +3 -1
  3. mlrun/common/formatters/__init__.py +1 -0
  4. mlrun/{model_monitoring/application.py → common/formatters/feature_set.py} +20 -6
  5. mlrun/common/formatters/run.py +3 -0
  6. mlrun/common/schemas/__init__.py +1 -0
  7. mlrun/common/schemas/alert.py +11 -11
  8. mlrun/common/schemas/auth.py +5 -0
  9. mlrun/common/schemas/client_spec.py +0 -1
  10. mlrun/common/schemas/model_monitoring/__init__.py +2 -1
  11. mlrun/common/schemas/model_monitoring/constants.py +23 -9
  12. mlrun/common/schemas/model_monitoring/model_endpoints.py +24 -47
  13. mlrun/common/schemas/notification.py +12 -2
  14. mlrun/common/schemas/workflow.py +10 -2
  15. mlrun/config.py +28 -21
  16. mlrun/data_types/data_types.py +6 -1
  17. mlrun/datastore/base.py +4 -4
  18. mlrun/datastore/s3.py +12 -9
  19. mlrun/datastore/storeytargets.py +9 -6
  20. mlrun/db/base.py +3 -0
  21. mlrun/db/httpdb.py +28 -16
  22. mlrun/db/nopdb.py +24 -4
  23. mlrun/errors.py +7 -1
  24. mlrun/execution.py +40 -7
  25. mlrun/feature_store/api.py +1 -0
  26. mlrun/feature_store/retrieval/spark_merger.py +7 -7
  27. mlrun/frameworks/_common/plan.py +3 -3
  28. mlrun/frameworks/_ml_common/plan.py +1 -1
  29. mlrun/frameworks/parallel_coordinates.py +2 -3
  30. mlrun/launcher/client.py +6 -6
  31. mlrun/model.py +29 -0
  32. mlrun/model_monitoring/api.py +1 -12
  33. mlrun/model_monitoring/applications/__init__.py +1 -2
  34. mlrun/model_monitoring/applications/_application_steps.py +5 -1
  35. mlrun/model_monitoring/applications/base.py +2 -182
  36. mlrun/model_monitoring/applications/context.py +2 -9
  37. mlrun/model_monitoring/applications/evidently_base.py +0 -74
  38. mlrun/model_monitoring/applications/histogram_data_drift.py +2 -2
  39. mlrun/model_monitoring/applications/results.py +4 -4
  40. mlrun/model_monitoring/controller.py +46 -209
  41. mlrun/model_monitoring/db/stores/base/store.py +1 -0
  42. mlrun/model_monitoring/db/stores/sqldb/sql_store.py +15 -1
  43. mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +12 -0
  44. mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +17 -16
  45. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +49 -39
  46. mlrun/model_monitoring/helpers.py +13 -15
  47. mlrun/model_monitoring/writer.py +3 -1
  48. mlrun/projects/operations.py +11 -8
  49. mlrun/projects/pipelines.py +35 -16
  50. mlrun/projects/project.py +52 -24
  51. mlrun/render.py +3 -3
  52. mlrun/runtimes/daskjob.py +1 -1
  53. mlrun/runtimes/kubejob.py +6 -6
  54. mlrun/runtimes/nuclio/api_gateway.py +12 -0
  55. mlrun/runtimes/nuclio/application/application.py +3 -3
  56. mlrun/runtimes/nuclio/function.py +41 -0
  57. mlrun/runtimes/nuclio/serving.py +2 -2
  58. mlrun/runtimes/pod.py +19 -13
  59. mlrun/serving/server.py +2 -0
  60. mlrun/utils/helpers.py +62 -16
  61. mlrun/utils/version/version.json +2 -2
  62. {mlrun-1.7.0rc43.dist-info → mlrun-1.7.0rc55.dist-info}/METADATA +126 -44
  63. {mlrun-1.7.0rc43.dist-info → mlrun-1.7.0rc55.dist-info}/RECORD +67 -68
  64. {mlrun-1.7.0rc43.dist-info → mlrun-1.7.0rc55.dist-info}/WHEEL +1 -1
  65. mlrun/model_monitoring/evidently_application.py +0 -20
  66. {mlrun-1.7.0rc43.dist-info → mlrun-1.7.0rc55.dist-info}/LICENSE +0 -0
  67. {mlrun-1.7.0rc43.dist-info → mlrun-1.7.0rc55.dist-info}/entry_points.txt +0 -0
  68. {mlrun-1.7.0rc43.dist-info → mlrun-1.7.0rc55.dist-info}/top_level.txt +0 -0
@@ -18,11 +18,16 @@ from typing import Union
18
18
 
19
19
  import pandas as pd
20
20
  import taosws
21
+ from taoswswrap.tdengine_connection import (
22
+ Statement,
23
+ TDEngineConnection,
24
+ )
21
25
 
22
26
  import mlrun.common.schemas.model_monitoring as mm_schemas
23
27
  import mlrun.model_monitoring.db.tsdb.tdengine.schemas as tdengine_schemas
24
28
  import mlrun.model_monitoring.db.tsdb.tdengine.stream_graph_steps
25
29
  from mlrun.model_monitoring.db import TSDBConnector
30
+ from mlrun.model_monitoring.db.tsdb.tdengine.schemas import TDEngineSchema
26
31
  from mlrun.model_monitoring.helpers import get_invocations_fqn
27
32
  from mlrun.utils import logger
28
33
 
@@ -52,25 +57,18 @@ class TDEngineConnector(TSDBConnector):
52
57
  self._init_super_tables()
53
58
 
54
59
  @property
55
- def connection(self) -> taosws.Connection:
60
+ def connection(self) -> TDEngineConnection:
56
61
  if not self._connection:
57
62
  self._connection = self._create_connection()
58
63
  return self._connection
59
64
 
60
- def _create_connection(self) -> taosws.Connection:
65
+ def _create_connection(self) -> TDEngineConnection:
61
66
  """Establish a connection to the TSDB server."""
62
- conn = taosws.connect(self._tdengine_connection_string)
63
- try:
64
- conn.execute(f"CREATE DATABASE {self.database}")
65
- except taosws.QueryError:
66
- # Database already exists
67
- pass
68
- try:
69
- conn.execute(f"USE {self.database}")
70
- except taosws.QueryError as e:
71
- raise mlrun.errors.MLRunTSDBConnectionFailureError(
72
- f"Failed to use TDEngine database {self.database}, {mlrun.errors.err_to_str(e)}"
73
- )
67
+ logger.debug("Creating a new connection to TDEngine", project=self.project)
68
+ conn = TDEngineConnection(self._tdengine_connection_string)
69
+ conn.run(statements=f"CREATE DATABASE IF NOT EXISTS {self.database}")
70
+ conn.prefix_statements = [f"USE {self.database}"]
71
+ logger.debug("Connected to TDEngine", project=self.project)
74
72
  return conn
75
73
 
76
74
  def _init_super_tables(self):
@@ -91,13 +89,13 @@ class TDEngineConnector(TSDBConnector):
91
89
  """Create TDEngine supertables."""
92
90
  for table in self.tables:
93
91
  create_table_query = self.tables[table]._create_super_table_query()
94
- self.connection.execute(create_table_query)
92
+ self.connection.run(statements=create_table_query)
95
93
 
96
94
  def write_application_event(
97
95
  self,
98
96
  event: dict,
99
97
  kind: mm_schemas.WriterEventKind = mm_schemas.WriterEventKind.RESULT,
100
- ):
98
+ ) -> None:
101
99
  """
102
100
  Write a single result or metric to TSDB.
103
101
  """
@@ -113,7 +111,7 @@ class TDEngineConnector(TSDBConnector):
113
111
  # Write a new result
114
112
  table = self.tables[mm_schemas.TDEngineSuperTables.APP_RESULTS]
115
113
  table_name = (
116
- f"{table_name}_" f"{event[mm_schemas.ResultData.RESULT_NAME]}"
114
+ f"{table_name}_{event[mm_schemas.ResultData.RESULT_NAME]}"
117
115
  ).replace("-", "_")
118
116
  event.pop(mm_schemas.ResultData.CURRENT_STATS, None)
119
117
 
@@ -121,9 +119,13 @@ class TDEngineConnector(TSDBConnector):
121
119
  # Write a new metric
122
120
  table = self.tables[mm_schemas.TDEngineSuperTables.METRICS]
123
121
  table_name = (
124
- f"{table_name}_" f"{event[mm_schemas.MetricData.METRIC_NAME]}"
122
+ f"{table_name}_{event[mm_schemas.MetricData.METRIC_NAME]}"
125
123
  ).replace("-", "_")
126
124
 
125
+ # Escape the table name for case-sensitivity (ML-7908)
126
+ # https://github.com/taosdata/taos-connector-python/issues/260
127
+ table_name = f"`{table_name}`"
128
+
127
129
  # Convert the datetime strings to datetime objects
128
130
  event[mm_schemas.WriterEvent.END_INFER_TIME] = self._convert_to_datetime(
129
131
  val=event[mm_schemas.WriterEvent.END_INFER_TIME]
@@ -132,18 +134,19 @@ class TDEngineConnector(TSDBConnector):
132
134
  val=event[mm_schemas.WriterEvent.START_INFER_TIME]
133
135
  )
134
136
 
135
- create_table_query = table._create_subtable_query(
136
- subtable=table_name, values=event
137
+ create_table_sql = table._create_subtable_sql(subtable=table_name, values=event)
138
+
139
+ insert_statement = Statement(
140
+ TDEngineSchema._insert_subtable_stmt,
141
+ dict(columns=table.columns, subtable=table_name, values=event),
137
142
  )
138
- self.connection.execute(create_table_query)
139
143
 
140
- insert_statement = table._insert_subtable_query(
141
- self.connection,
142
- subtable=table_name,
143
- values=event,
144
+ self.connection.run(
145
+ statements=[
146
+ create_table_sql,
147
+ insert_statement,
148
+ ]
144
149
  )
145
- insert_statement.add_batch()
146
- insert_statement.execute()
147
150
 
148
151
  @staticmethod
149
152
  def _convert_to_datetime(val: typing.Union[str, datetime]) -> datetime:
@@ -200,18 +203,24 @@ class TDEngineConnector(TSDBConnector):
200
203
  """
201
204
  Delete all project resources in the TSDB connector, such as model endpoints data and drift results.
202
205
  """
206
+ logger.debug(
207
+ "Deleting all project resources using the TDEngine connector",
208
+ project=self.project,
209
+ )
203
210
  for table in self.tables:
204
211
  get_subtable_names_query = self.tables[table]._get_subtables_query(
205
212
  values={mm_schemas.EventFieldType.PROJECT: self.project}
206
213
  )
207
- subtables = self.connection.query(get_subtable_names_query)
214
+ subtables = self.connection.run(query=get_subtable_names_query).data
215
+ drop_statements = []
208
216
  for subtable in subtables:
209
- drop_query = self.tables[table]._drop_subtable_query(
210
- subtable=subtable[0]
217
+ drop_statements.append(
218
+ self.tables[table]._drop_subtable_query(subtable=subtable[0])
211
219
  )
212
- self.connection.execute(drop_query)
213
- logger.info(
214
- f"Deleted all project resources in the TSDB connector for project {self.project}"
220
+ self.connection.run(statements=drop_statements)
221
+ logger.debug(
222
+ "Deleted all project resources using the TDEngine connector",
223
+ project=self.project,
215
224
  )
216
225
 
217
226
  def get_model_endpoint_real_time_metrics(
@@ -262,7 +271,7 @@ class TDEngineConnector(TSDBConnector):
262
271
 
263
272
  project_condition = f"project = '{self.project}'"
264
273
  filter_query = (
265
- f"{filter_query} AND {project_condition}"
274
+ f"({filter_query}) AND ({project_condition})"
266
275
  if filter_query
267
276
  else project_condition
268
277
  )
@@ -280,15 +289,16 @@ class TDEngineConnector(TSDBConnector):
280
289
  timestamp_column=timestamp_column,
281
290
  database=self.database,
282
291
  )
292
+ logger.debug("Querying TDEngine", query=full_query)
283
293
  try:
284
- query_result = self.connection.query(full_query)
294
+ query_result = self.connection.run(query=full_query)
285
295
  except taosws.QueryError as e:
286
296
  raise mlrun.errors.MLRunInvalidArgumentError(
287
297
  f"Failed to query table {table} in database {self.database}, {str(e)}"
288
298
  )
289
299
 
290
- df_columns = [field.name() for field in query_result.fields]
291
- return pd.DataFrame(query_result, columns=df_columns)
300
+ df_columns = [field.name for field in query_result.fields]
301
+ return pd.DataFrame(query_result.data, columns=df_columns)
292
302
 
293
303
  def read_metrics_data(
294
304
  self,
@@ -336,11 +346,11 @@ class TDEngineConnector(TSDBConnector):
336
346
 
337
347
  metrics_condition = " OR ".join(
338
348
  [
339
- f"({mm_schemas.WriterEvent.APPLICATION_NAME} = '{metric.app}' AND {name} = '{metric.name}')"
349
+ f"({mm_schemas.WriterEvent.APPLICATION_NAME}='{metric.app}' AND {name}='{metric.name}')"
340
350
  for metric in metrics
341
351
  ]
342
352
  )
343
- filter_query = f"endpoint_id='{endpoint_id}' AND ({metrics_condition})"
353
+ filter_query = f"(endpoint_id='{endpoint_id}') AND ({metrics_condition})"
344
354
 
345
355
  df = self._get_records(
346
356
  table=table,
@@ -18,6 +18,10 @@ import typing
18
18
  import numpy as np
19
19
  import pandas as pd
20
20
 
21
+ if typing.TYPE_CHECKING:
22
+ from mlrun.db.base import RunDBInterface
23
+ from mlrun.projects import MlrunProject
24
+
21
25
  import mlrun
22
26
  import mlrun.artifacts
23
27
  import mlrun.common.model_monitoring.helpers
@@ -26,16 +30,11 @@ import mlrun.data_types.infer
26
30
  import mlrun.model_monitoring
27
31
  from mlrun.common.schemas.model_monitoring.model_endpoints import (
28
32
  ModelEndpointMonitoringMetric,
29
- ModelEndpointMonitoringMetricType,
30
33
  _compose_full_name,
31
34
  )
32
35
  from mlrun.model_monitoring.model_endpoint import ModelEndpoint
33
36
  from mlrun.utils import logger
34
37
 
35
- if typing.TYPE_CHECKING:
36
- from mlrun.db.base import RunDBInterface
37
- from mlrun.projects import MlrunProject
38
-
39
38
 
40
39
  class _BatchDict(typing.TypedDict):
41
40
  minutes: int
@@ -63,7 +62,6 @@ def get_stream_path(
63
62
  )
64
63
 
65
64
  if not stream_uri or stream_uri == "v3io":
66
- # TODO : remove the first part of this condition in 1.9.0
67
65
  stream_uri = mlrun.mlconf.get_model_monitoring_file_target_path(
68
66
  project=project,
69
67
  kind=mm_constants.FileTargetKind.STREAM,
@@ -71,8 +69,6 @@ def get_stream_path(
71
69
  function_name=function_name,
72
70
  )
73
71
 
74
- if isinstance(stream_uri, list): # ML-6043 - user side gets only the new stream uri
75
- stream_uri = stream_uri[1] # get new stream path, under projects
76
72
  return mlrun.common.model_monitoring.helpers.parse_monitoring_stream_path(
77
73
  stream_uri=stream_uri, project=project, function_name=function_name
78
74
  )
@@ -179,7 +175,7 @@ def _get_monitoring_time_window_from_controller_run(
179
175
  def update_model_endpoint_last_request(
180
176
  project: str,
181
177
  model_endpoint: ModelEndpoint,
182
- current_request: datetime,
178
+ current_request: datetime.datetime,
183
179
  db: "RunDBInterface",
184
180
  ) -> None:
185
181
  """
@@ -190,7 +186,8 @@ def update_model_endpoint_last_request(
190
186
  :param current_request: current request time
191
187
  :param db: DB interface.
192
188
  """
193
- if model_endpoint.spec.stream_path != "":
189
+ is_model_server_endpoint = model_endpoint.spec.stream_path != ""
190
+ if is_model_server_endpoint:
194
191
  current_request = current_request.isoformat()
195
192
  logger.info(
196
193
  "Update model endpoint last request time (EP with serving)",
@@ -204,12 +201,13 @@ def update_model_endpoint_last_request(
204
201
  endpoint_id=model_endpoint.metadata.uid,
205
202
  attributes={mm_constants.EventFieldType.LAST_REQUEST: current_request},
206
203
  )
207
- else:
204
+ else: # model endpoint without any serving function - close the window "manually"
208
205
  try:
209
206
  time_window = _get_monitoring_time_window_from_controller_run(project, db)
210
207
  except mlrun.errors.MLRunNotFoundError:
211
- logger.debug(
212
- "Not bumping model endpoint last request time - the monitoring controller isn't deployed yet"
208
+ logger.warn(
209
+ "Not bumping model endpoint last request time - the monitoring controller isn't deployed yet.\n"
210
+ "Call `project.enable_model_monitoring()` first."
213
211
  )
214
212
  return
215
213
 
@@ -302,7 +300,7 @@ def get_invocations_fqn(project: str) -> str:
302
300
  project=project,
303
301
  app=mm_constants.SpecialApps.MLRUN_INFRA,
304
302
  name=mm_constants.PredictionsQueryConstants.INVOCATIONS,
305
- type=ModelEndpointMonitoringMetricType.METRIC,
303
+ type=mm_constants.ModelEndpointMonitoringMetricType.METRIC,
306
304
  )
307
305
 
308
306
 
@@ -316,7 +314,7 @@ def get_invocations_metric(project: str) -> ModelEndpointMonitoringMetric:
316
314
  return ModelEndpointMonitoringMetric(
317
315
  project=project,
318
316
  app=mm_constants.SpecialApps.MLRUN_INFRA,
319
- type=ModelEndpointMonitoringMetricType.METRIC,
317
+ type=mm_constants.ModelEndpointMonitoringMetricType.METRIC,
320
318
  name=mm_constants.PredictionsQueryConstants.INVOCATIONS,
321
319
  full_name=get_invocations_fqn(project),
322
320
  )
@@ -160,7 +160,9 @@ class ModelMonitoringWriter(StepToDict):
160
160
  event_kind = f"{event_kind}_detected"
161
161
  else:
162
162
  event_kind = f"{event_kind}_suspected"
163
- return alert_objects.EventKind(value=event_kind)
163
+ return alert_objects.EventKind(
164
+ value=mlrun.utils.helpers.normalize_name(event_kind)
165
+ )
164
166
 
165
167
  @staticmethod
166
168
  def _reconstruct_event(event: _RawEvent) -> tuple[_AppResultEvent, WriterEventKind]:
@@ -15,10 +15,13 @@
15
15
  import warnings
16
16
  from typing import Optional, Union
17
17
 
18
- from mlrun_pipelines.models import PipelineNodeWrapper
18
+ import mlrun_pipelines.common.models
19
+ import mlrun_pipelines.models
19
20
 
20
21
  import mlrun
21
22
  import mlrun.common.constants as mlrun_constants
23
+ import mlrun.common.schemas.function
24
+ import mlrun.common.schemas.workflow
22
25
  from mlrun.utils import hub_prefix
23
26
 
24
27
  from .pipelines import enrich_function_object, pipeline_context
@@ -49,7 +52,7 @@ def _get_engine_and_function(function, project=None):
49
52
  function = enrich_function_object(project, function, copy_function=False)
50
53
 
51
54
  if not pipeline_context.workflow:
52
- return "local", function
55
+ return mlrun.common.schemas.workflow.EngineType.LOCAL, function
53
56
 
54
57
  return pipeline_context.workflow.engine, function
55
58
 
@@ -78,7 +81,7 @@ def run_function(
78
81
  returns: Optional[list[Union[str, dict[str, str]]]] = None,
79
82
  builder_env: Optional[list] = None,
80
83
  reset_on_run: Optional[bool] = None,
81
- ) -> Union[mlrun.model.RunObject, PipelineNodeWrapper]:
84
+ ) -> Union[mlrun.model.RunObject, mlrun_pipelines.models.PipelineNodeWrapper]:
82
85
  """Run a local or remote task as part of a local/kubeflow pipeline
83
86
 
84
87
  run_function() allow you to execute a function locally, on a remote cluster, or as part of an automated workflow
@@ -186,7 +189,7 @@ def run_function(
186
189
  )
187
190
  task.spec.verbose = task.spec.verbose or verbose
188
191
 
189
- if engine == "kfp":
192
+ if engine == mlrun.common.schemas.workflow.EngineType.KFP:
190
193
  if schedule:
191
194
  raise mlrun.errors.MLRunInvalidArgumentError(
192
195
  "Scheduling jobs is not supported when running a workflow with the kfp engine."
@@ -266,7 +269,7 @@ def build_function(
266
269
  overwrite_build_params: bool = False,
267
270
  extra_args: str = None,
268
271
  force_build: bool = False,
269
- ) -> Union[BuildStatus, PipelineNodeWrapper]:
272
+ ) -> Union[BuildStatus, mlrun_pipelines.models.PipelineNodeWrapper]:
270
273
  """deploy ML function, build container with its dependencies
271
274
 
272
275
  :param function: Name of the function (in the project) or function object
@@ -302,7 +305,7 @@ def build_function(
302
305
  raise mlrun.errors.MLRunInvalidArgumentError(
303
306
  "Cannot build use deploy_function()"
304
307
  )
305
- if engine == "kfp":
308
+ if engine == mlrun.common.schemas.workflow.EngineType.KFP:
306
309
  if overwrite_build_params:
307
310
  function.spec.build.commands = None
308
311
  if requirements or requirements_file:
@@ -375,7 +378,7 @@ def deploy_function(
375
378
  builder_env: dict = None,
376
379
  project_object=None,
377
380
  mock: bool = None,
378
- ) -> Union[DeployStatus, PipelineNodeWrapper]:
381
+ ) -> Union[DeployStatus, mlrun_pipelines.models.PipelineNodeWrapper]:
379
382
  """deploy real-time (nuclio based) functions
380
383
 
381
384
  :param function: name of the function (in the project) or function object
@@ -392,7 +395,7 @@ def deploy_function(
392
395
  raise mlrun.errors.MLRunInvalidArgumentError(
393
396
  "deploy is used with real-time functions, for other kinds use build_function()"
394
397
  )
395
- if engine == "kfp":
398
+ if engine == mlrun.common.schemas.workflow.EngineType.KFP:
396
399
  return function.deploy_step(models=models, env=env, tag=tag, verbose=verbose)
397
400
  else:
398
401
  if env:
@@ -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)
@@ -452,7 +459,12 @@ class _PipelineRunner(abc.ABC):
452
459
 
453
460
  @staticmethod
454
461
  @abc.abstractmethod
455
- 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
+ ):
456
468
  pass
457
469
 
458
470
  @staticmethod
@@ -581,13 +593,13 @@ class _KFPRunner(_PipelineRunner):
581
593
  logger.warning(
582
594
  "Setting notifications on kfp pipeline runner uses old notification behavior. "
583
595
  "Notifications will only be sent if you wait for pipeline completion. "
584
- "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."
585
597
  )
586
598
  # for start message, fallback to old notification behavior
587
599
  for notification in notifications or []:
588
- project.notifiers.add_notification(
589
- notification.kind, notification.params
590
- )
600
+ params = notification.params
601
+ params.update(notification.secret_params)
602
+ project.notifiers.add_notification(notification.kind, params)
591
603
 
592
604
  run_id = _run_pipeline(
593
605
  workflow_handler,
@@ -625,12 +637,19 @@ class _KFPRunner(_PipelineRunner):
625
637
  return _PipelineRunStatus(run_id, cls, project=project, workflow=workflow_spec)
626
638
 
627
639
  @staticmethod
628
- def wait_for_completion(run, project=None, timeout=None, expected_statuses=None):
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
+ ):
646
+ project_name = project.metadata.name if project else ""
629
647
  logger.info(
630
- "Waiting for pipeline run completion", run_id=run.run_id, project=project
648
+ "Waiting for pipeline run completion",
649
+ run_id=run.run_id,
650
+ project=project_name,
631
651
  )
632
652
  timeout = timeout or 60 * 60
633
- project_name = project.metadata.name if project else ""
634
653
  run_info = wait_for_pipeline_completion(
635
654
  run.run_id,
636
655
  timeout=timeout,
@@ -1062,7 +1081,7 @@ def load_and_run(
1062
1081
  # extract "start" notification if exists
1063
1082
  start_notifications = [
1064
1083
  notification
1065
- for notification in context.get_notifications()
1084
+ for notification in context.get_notifications(unmask_secret_params=True)
1066
1085
  if "running" in notification.when
1067
1086
  ]
1068
1087