mlrun 1.7.0rc26__py3-none-any.whl → 1.7.0rc29__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 (66) hide show
  1. mlrun/__main__.py +7 -7
  2. mlrun/alerts/alert.py +13 -1
  3. mlrun/artifacts/manager.py +5 -0
  4. mlrun/common/constants.py +2 -2
  5. mlrun/common/formatters/base.py +9 -9
  6. mlrun/common/schemas/alert.py +4 -8
  7. mlrun/common/schemas/api_gateway.py +7 -0
  8. mlrun/common/schemas/constants.py +3 -0
  9. mlrun/common/schemas/model_monitoring/__init__.py +1 -0
  10. mlrun/common/schemas/model_monitoring/constants.py +27 -12
  11. mlrun/common/schemas/model_monitoring/model_endpoints.py +0 -12
  12. mlrun/common/schemas/schedule.py +1 -1
  13. mlrun/config.py +16 -9
  14. mlrun/datastore/azure_blob.py +2 -1
  15. mlrun/datastore/base.py +1 -5
  16. mlrun/datastore/datastore.py +3 -3
  17. mlrun/datastore/inmem.py +1 -1
  18. mlrun/datastore/snowflake_utils.py +3 -1
  19. mlrun/datastore/sources.py +26 -11
  20. mlrun/datastore/store_resources.py +2 -0
  21. mlrun/datastore/targets.py +60 -25
  22. mlrun/db/base.py +10 -0
  23. mlrun/db/httpdb.py +41 -30
  24. mlrun/db/nopdb.py +10 -1
  25. mlrun/errors.py +4 -0
  26. mlrun/execution.py +18 -10
  27. mlrun/feature_store/retrieval/spark_merger.py +2 -1
  28. mlrun/launcher/local.py +2 -2
  29. mlrun/model.py +30 -0
  30. mlrun/model_monitoring/api.py +6 -52
  31. mlrun/model_monitoring/applications/histogram_data_drift.py +4 -1
  32. mlrun/model_monitoring/db/stores/__init__.py +21 -9
  33. mlrun/model_monitoring/db/stores/base/store.py +39 -1
  34. mlrun/model_monitoring/db/stores/sqldb/models/base.py +9 -7
  35. mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +4 -2
  36. mlrun/model_monitoring/db/stores/sqldb/sql_store.py +34 -79
  37. mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +19 -27
  38. mlrun/model_monitoring/db/tsdb/__init__.py +19 -14
  39. mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +4 -2
  40. mlrun/model_monitoring/helpers.py +9 -5
  41. mlrun/model_monitoring/writer.py +1 -5
  42. mlrun/projects/operations.py +1 -0
  43. mlrun/projects/project.py +71 -75
  44. mlrun/render.py +10 -5
  45. mlrun/run.py +2 -2
  46. mlrun/runtimes/daskjob.py +7 -1
  47. mlrun/runtimes/local.py +24 -7
  48. mlrun/runtimes/nuclio/function.py +20 -0
  49. mlrun/runtimes/pod.py +5 -29
  50. mlrun/serving/routers.py +75 -59
  51. mlrun/serving/server.py +1 -0
  52. mlrun/serving/v2_serving.py +8 -1
  53. mlrun/utils/helpers.py +46 -2
  54. mlrun/utils/logger.py +36 -2
  55. mlrun/utils/notifications/notification/base.py +4 -0
  56. mlrun/utils/notifications/notification/git.py +21 -0
  57. mlrun/utils/notifications/notification/slack.py +8 -0
  58. mlrun/utils/notifications/notification/webhook.py +41 -1
  59. mlrun/utils/notifications/notification_pusher.py +2 -2
  60. mlrun/utils/version/version.json +2 -2
  61. {mlrun-1.7.0rc26.dist-info → mlrun-1.7.0rc29.dist-info}/METADATA +9 -4
  62. {mlrun-1.7.0rc26.dist-info → mlrun-1.7.0rc29.dist-info}/RECORD +66 -66
  63. {mlrun-1.7.0rc26.dist-info → mlrun-1.7.0rc29.dist-info}/WHEEL +1 -1
  64. {mlrun-1.7.0rc26.dist-info → mlrun-1.7.0rc29.dist-info}/LICENSE +0 -0
  65. {mlrun-1.7.0rc26.dist-info → mlrun-1.7.0rc29.dist-info}/entry_points.txt +0 -0
  66. {mlrun-1.7.0rc26.dist-info → mlrun-1.7.0rc29.dist-info}/top_level.txt +0 -0
@@ -59,13 +59,17 @@ def get_stream_path(
59
59
 
60
60
  stream_uri = mlrun.get_secret_or_env(
61
61
  mlrun.common.schemas.model_monitoring.ProjectSecretKeys.STREAM_PATH
62
- ) or mlrun.mlconf.get_model_monitoring_file_target_path(
63
- project=project,
64
- kind=mlrun.common.schemas.model_monitoring.FileTargetKind.STREAM,
65
- target="online",
66
- function_name=function_name,
67
62
  )
68
63
 
64
+ if not stream_uri or stream_uri == "v3io":
65
+ # TODO : remove the first part of this condition in 1.9.0
66
+ stream_uri = mlrun.mlconf.get_model_monitoring_file_target_path(
67
+ project=project,
68
+ kind=mlrun.common.schemas.model_monitoring.FileTargetKind.STREAM,
69
+ target="online",
70
+ function_name=function_name,
71
+ )
72
+
69
73
  if isinstance(stream_uri, list): # ML-6043 - user side gets only the new stream uri
70
74
  stream_uri = stream_uri[1] # get new stream path, under projects
71
75
  return mlrun.common.model_monitoring.helpers.parse_monitoring_stream_path(
@@ -153,11 +153,7 @@ class ModelMonitoringWriter(StepToDict):
153
153
  result_kind: int, result_status: int
154
154
  ) -> alert_objects.EventKind:
155
155
  """Generate the required Event Kind format for the alerting system"""
156
- if result_kind == ResultKindApp.custom.value:
157
- # Custom kind is represented as an anomaly detection
158
- event_kind = "mm_app_anomaly"
159
- else:
160
- event_kind = ResultKindApp(value=result_kind).name
156
+ event_kind = ResultKindApp(value=result_kind).name
161
157
 
162
158
  if result_status == ResultStatusApp.detected.value:
163
159
  event_kind = f"{event_kind}_detected"
@@ -330,6 +330,7 @@ def build_function(
330
330
  commands=commands,
331
331
  secret=secret_name,
332
332
  requirements=requirements,
333
+ requirements_file=requirements_file,
333
334
  overwrite=overwrite_build_params,
334
335
  extra_args=extra_args,
335
336
  )
mlrun/projects/project.py CHANGED
@@ -714,7 +714,8 @@ def _project_instance_from_struct(struct, name, allow_cross_project):
714
714
  name_from_struct = struct.get("metadata", {}).get("name", "")
715
715
  if name and name_from_struct and name_from_struct != name:
716
716
  error_message = (
717
- f"project name mismatch, {name_from_struct} != {name}, please do one of the following:\n"
717
+ f"Project name mismatch, {name_from_struct} != {name}, project is loaded from {name_from_struct} "
718
+ f"project yaml. To prevent/allow this, you can take one of the following actions:\n"
718
719
  "1. Set the `allow_cross_project=True` when loading the project.\n"
719
720
  f"2. Delete the existing project yaml, or ensure its name is equal to {name}.\n"
720
721
  "3. Use different project context dir."
@@ -722,14 +723,14 @@ def _project_instance_from_struct(struct, name, allow_cross_project):
722
723
 
723
724
  if allow_cross_project is None:
724
725
  # TODO: Remove this warning in version 1.9.0 and also fix cli to support allow_cross_project
725
- logger.warn(
726
- "Project name is different than specified on its project yaml."
727
- "You should fix it until version 1.9.0",
728
- description=error_message,
726
+ warnings.warn(
727
+ f"Project {name=} is different than specified on the context's project yaml. "
728
+ "This behavior is deprecated and will not be supported in version 1.9.0."
729
729
  )
730
+ logger.warn(error_message)
730
731
  elif allow_cross_project:
731
- logger.warn(
732
- "Project name is different than specified on its project yaml. Overriding.",
732
+ logger.debug(
733
+ "Project name is different than specified on the context's project yaml. Overriding.",
733
734
  existing_name=name_from_struct,
734
735
  overriding_name=name,
735
736
  )
@@ -1007,8 +1008,13 @@ class ProjectSpec(ModelObj):
1007
1008
  key = artifact.key
1008
1009
  artifact = artifact.to_dict()
1009
1010
  else: # artifact is a dict
1010
- # imported artifacts don't have metadata,spec,status fields
1011
- key_field = "key" if _is_imported_artifact(artifact) else "metadata.key"
1011
+ # imported/legacy artifacts don't have metadata,spec,status fields
1012
+ key_field = (
1013
+ "key"
1014
+ if _is_imported_artifact(artifact)
1015
+ or mlrun.utils.is_legacy_artifact(artifact)
1016
+ else "metadata.key"
1017
+ )
1012
1018
  key = mlrun.utils.get_in(artifact, key_field, "")
1013
1019
  if not key:
1014
1020
  raise ValueError(f'artifacts "{key_field}" must be specified')
@@ -2127,6 +2133,7 @@ class MlrunProject(ModelObj):
2127
2133
  deploy_histogram_data_drift_app: bool = True,
2128
2134
  wait_for_deployment: bool = False,
2129
2135
  rebuild_images: bool = False,
2136
+ fetch_credentials_from_sys_config: bool = False,
2130
2137
  ) -> None:
2131
2138
  """
2132
2139
  Deploy model monitoring application controller, writer and stream functions.
@@ -2136,17 +2143,18 @@ class MlrunProject(ModelObj):
2136
2143
  The stream function goal is to monitor the log of the data stream. It is triggered when a new log entry
2137
2144
  is detected. It processes the new events into statistics that are then written to statistics databases.
2138
2145
 
2139
- :param default_controller_image: Deprecated.
2140
- :param base_period: The time period in minutes in which the model monitoring controller
2141
- function is triggered. By default, the base period is 10 minutes.
2142
- :param image: The image of the model monitoring controller, writer, monitoring
2143
- stream & histogram data drift functions, which are real time nuclio
2144
- functions. By default, the image is mlrun/mlrun.
2145
- :param deploy_histogram_data_drift_app: If true, deploy the default histogram-based data drift application.
2146
- :param wait_for_deployment: If true, return only after the deployment is done on the backend.
2147
- Otherwise, deploy the model monitoring infrastructure on the
2148
- background, including the histogram data drift app if selected.
2149
- :param rebuild_images: If true, force rebuild of model monitoring infrastructure images.
2146
+ :param default_controller_image: Deprecated.
2147
+ :param base_period: The time period in minutes in which the model monitoring controller
2148
+ function is triggered. By default, the base period is 10 minutes.
2149
+ :param image: The image of the model monitoring controller, writer, monitoring
2150
+ stream & histogram data drift functions, which are real time nuclio
2151
+ functions. By default, the image is mlrun/mlrun.
2152
+ :param deploy_histogram_data_drift_app: If true, deploy the default histogram-based data drift application.
2153
+ :param wait_for_deployment: If true, return only after the deployment is done on the backend.
2154
+ Otherwise, deploy the model monitoring infrastructure on the
2155
+ background, including the histogram data drift app if selected.
2156
+ :param rebuild_images: If true, force rebuild of model monitoring infrastructure images.
2157
+ :param fetch_credentials_from_sys_config: If true, fetch the credentials from the system configuration.
2150
2158
  """
2151
2159
  if default_controller_image != "mlrun/mlrun":
2152
2160
  # TODO: Remove this in 1.9.0
@@ -2163,6 +2171,7 @@ class MlrunProject(ModelObj):
2163
2171
  base_period=base_period,
2164
2172
  deploy_histogram_data_drift_app=deploy_histogram_data_drift_app,
2165
2173
  rebuild_images=rebuild_images,
2174
+ fetch_credentials_from_sys_config=fetch_credentials_from_sys_config,
2166
2175
  )
2167
2176
 
2168
2177
  if wait_for_deployment:
@@ -2485,25 +2494,17 @@ class MlrunProject(ModelObj):
2485
2494
  self.spec.remove_function(name)
2486
2495
 
2487
2496
  def remove_model_monitoring_function(self, name: Union[str, list[str]]):
2488
- """remove the specified model-monitoring-app function/s from the project spec
2497
+ """delete the specified model-monitoring-app function/s
2489
2498
 
2490
2499
  :param name: name of the model-monitoring-function/s (under the project)
2491
2500
  """
2492
- names = name if isinstance(name, list) else [name]
2493
- for func_name in names:
2494
- function = self.get_function(key=func_name)
2495
- if (
2496
- function.metadata.labels.get(mm_constants.ModelMonitoringAppLabel.KEY)
2497
- == mm_constants.ModelMonitoringAppLabel.VAL
2498
- ):
2499
- self.remove_function(name=func_name)
2500
- logger.info(
2501
- f"{func_name} function has been removed from {self.name} project"
2502
- )
2503
- else:
2504
- raise logger.warn(
2505
- f"There is no model monitoring function with {func_name} name"
2506
- )
2501
+ # TODO: Remove this in 1.9.0
2502
+ warnings.warn(
2503
+ "'remove_model_monitoring_function' is deprecated and will be removed in 1.9.0. "
2504
+ "Please use `delete_model_monitoring_function` instead.",
2505
+ FutureWarning,
2506
+ )
2507
+ self.delete_model_monitoring_function(name)
2507
2508
 
2508
2509
  def delete_model_monitoring_function(self, name: Union[str, list[str]]):
2509
2510
  """delete the specified model-monitoring-app function/s
@@ -3205,49 +3206,44 @@ class MlrunProject(ModelObj):
3205
3206
  stream_path: Optional[str] = None,
3206
3207
  tsdb_connection: Optional[str] = None,
3207
3208
  ):
3208
- """Set the credentials that will be used by the project's model monitoring
3209
+ """
3210
+ Set the credentials that will be used by the project's model monitoring
3209
3211
  infrastructure functions. Important to note that you have to set the credentials before deploying any
3210
3212
  model monitoring or serving function.
3211
3213
 
3212
- :param access_key: Model Monitoring access key for managing user permissions
3213
- :param endpoint_store_connection: Endpoint store connection string
3214
- :param stream_path: Path to the model monitoring stream
3215
- :param tsdb_connection: Connection string to the time series database
3214
+ :param access_key: Model Monitoring access key for managing user permissions.
3215
+ :param endpoint_store_connection: Endpoint store connection string. By default, None.
3216
+ Options:
3217
+ 1. None, will be set from the system configuration.
3218
+ 2. v3io - for v3io endpoint store,
3219
+ pass `v3io` and the system will generate the exact path.
3220
+ 3. MySQL/SQLite - for SQL endpoint store, please provide full
3221
+ connection string, for example
3222
+ mysql+pymysql://<username>:<password>@<host>:<port>/<db_name>
3223
+ :param stream_path: Path to the model monitoring stream. By default, None.
3224
+ Options:
3225
+ 1. None, will be set from the system configuration.
3226
+ 2. v3io - for v3io stream,
3227
+ pass `v3io` and the system will generate the exact path.
3228
+ 3. Kafka - for Kafka stream, please provide full connection string without
3229
+ custom topic, for example kafka://<some_kafka_broker>:<port>.
3230
+ :param tsdb_connection: Connection string to the time series database. By default, None.
3231
+ Options:
3232
+ 1. None, will be set from the system configuration.
3233
+ 2. v3io - for v3io stream,
3234
+ pass `v3io` and the system will generate the exact path.
3235
+ 3. TDEngine - for TDEngine tsdb, please provide full websocket connection URL,
3236
+ for example taosws://<username>:<password>@<host>:<port>.
3216
3237
  """
3217
-
3218
- secrets_dict = {}
3219
- if access_key:
3220
- secrets_dict[
3221
- mlrun.common.schemas.model_monitoring.ProjectSecretKeys.ACCESS_KEY
3222
- ] = access_key
3223
-
3224
- if endpoint_store_connection:
3225
- secrets_dict[
3226
- mlrun.common.schemas.model_monitoring.ProjectSecretKeys.ENDPOINT_STORE_CONNECTION
3227
- ] = endpoint_store_connection
3228
-
3229
- if stream_path:
3230
- if stream_path.startswith("kafka://") and "?topic" in stream_path:
3231
- raise mlrun.errors.MLRunInvalidArgumentError(
3232
- "Custom kafka topic is not allowed"
3233
- )
3234
- secrets_dict[
3235
- mlrun.common.schemas.model_monitoring.ProjectSecretKeys.STREAM_PATH
3236
- ] = stream_path
3237
-
3238
- if tsdb_connection:
3239
- if not tsdb_connection.startswith("taosws://"):
3240
- raise mlrun.errors.MLRunInvalidArgumentError(
3241
- "Currently only TDEngine websocket connection is supported for non-v3io TSDB,"
3242
- "please provide a full URL (e.g. taosws://user:password@host:port)"
3243
- )
3244
- secrets_dict[
3245
- mlrun.common.schemas.model_monitoring.ProjectSecretKeys.TSDB_CONNECTION
3246
- ] = tsdb_connection
3247
-
3248
- self.set_secrets(
3249
- secrets=secrets_dict,
3250
- provider=mlrun.common.schemas.SecretProviderName.kubernetes,
3238
+ db = mlrun.db.get_run_db(secrets=self._secrets)
3239
+ db.set_model_monitoring_credentials(
3240
+ project=self.name,
3241
+ credentials={
3242
+ "access_key": access_key,
3243
+ "endpoint_store_connection": endpoint_store_connection,
3244
+ "stream_path": stream_path,
3245
+ "tsdb_connection": tsdb_connection,
3246
+ },
3251
3247
  )
3252
3248
 
3253
3249
  def run_function(
mlrun/render.py CHANGED
@@ -283,9 +283,14 @@ function copyToClipboard(fld) {
283
283
  }
284
284
  function expandPanel(el) {
285
285
  const panelName = "#" + el.getAttribute('paneName');
286
- console.log(el.title);
287
286
 
288
- document.querySelector(panelName + "-title").innerHTML = el.title
287
+ // Get the base URL of the current notebook
288
+ var baseUrl = window.location.origin;
289
+
290
+ // Construct the full URL
291
+ var fullUrl = new URL(el.title, baseUrl).href;
292
+
293
+ document.querySelector(panelName + "-title").innerHTML = fullUrl
289
294
  iframe = document.querySelector(panelName + "-body");
290
295
 
291
296
  const tblcss = `<style> body { font-family: Arial, Helvetica, sans-serif;}
@@ -299,7 +304,7 @@ function expandPanel(el) {
299
304
  }
300
305
 
301
306
  function reqListener () {
302
- if (el.title.endsWith(".csv")) {
307
+ if (fullUrl.endsWith(".csv")) {
303
308
  iframe.setAttribute("srcdoc", tblcss + csvToHtmlTable(this.responseText));
304
309
  } else {
305
310
  iframe.setAttribute("srcdoc", this.responseText);
@@ -309,11 +314,11 @@ function expandPanel(el) {
309
314
 
310
315
  const oReq = new XMLHttpRequest();
311
316
  oReq.addEventListener("load", reqListener);
312
- oReq.open("GET", el.title);
317
+ oReq.open("GET", fullUrl);
313
318
  oReq.send();
314
319
 
315
320
 
316
- //iframe.src = el.title;
321
+ //iframe.src = fullUrl;
317
322
  const resultPane = document.querySelector(panelName + "-pane");
318
323
  if (resultPane.classList.contains("hidden")) {
319
324
  resultPane.classList.remove("hidden");
mlrun/run.py CHANGED
@@ -63,11 +63,11 @@ from .runtimes.funcdoc import update_function_entry_points
63
63
  from .runtimes.nuclio.application import ApplicationRuntime
64
64
  from .runtimes.utils import add_code_metadata, global_context
65
65
  from .utils import (
66
+ RunKeys,
66
67
  extend_hub_uri_if_needed,
67
68
  get_in,
68
69
  logger,
69
70
  retry_until_successful,
70
- run_keys,
71
71
  update_in,
72
72
  )
73
73
 
@@ -280,7 +280,7 @@ def get_or_create_ctx(
280
280
  artifact_path = mlrun.utils.helpers.template_artifact_path(
281
281
  mlconf.artifact_path, project or mlconf.default_project
282
282
  )
283
- update_in(newspec, ["spec", run_keys.output_path], artifact_path)
283
+ update_in(newspec, ["spec", RunKeys.output_path], artifact_path)
284
284
 
285
285
  newspec.setdefault("metadata", {})
286
286
  update_in(newspec, "metadata.name", name, replace=False)
mlrun/runtimes/daskjob.py CHANGED
@@ -548,7 +548,13 @@ class DaskCluster(KubejobRuntime):
548
548
  "specified handler (string) without command "
549
549
  "(py file path), specify command or use handler pointer"
550
550
  )
551
- handler = load_module(self.spec.command, handler, context=context)
551
+ # Do not embed the module in system as it is not persistent with the dask cluster
552
+ handler = load_module(
553
+ self.spec.command,
554
+ handler,
555
+ context=context,
556
+ embed_in_sys=False,
557
+ )
552
558
  client = self.client
553
559
  setattr(context, "dask_client", client)
554
560
  sout, serr = exec_from_params(handler, runobj, context)
mlrun/runtimes/local.py CHANGED
@@ -58,7 +58,9 @@ class ParallelRunner:
58
58
 
59
59
  return TrackerManager()
60
60
 
61
- def _get_handler(self, handler, context):
61
+ def _get_handler(
62
+ self, handler: str, context: MLClientCtx, embed_in_sys: bool = True
63
+ ):
62
64
  return handler
63
65
 
64
66
  def _get_dask_client(self, options):
@@ -86,7 +88,7 @@ class ParallelRunner:
86
88
  handler = runobj.spec.handler
87
89
  self._force_handler(handler)
88
90
  set_paths(self.spec.pythonpath)
89
- handler = self._get_handler(handler, execution)
91
+ handler = self._get_handler(handler, execution, embed_in_sys=False)
90
92
 
91
93
  client, function_name = self._get_dask_client(generator.options)
92
94
  parallel_runs = generator.options.parallel_runs or 4
@@ -224,12 +226,14 @@ class LocalRuntime(BaseRuntime, ParallelRunner):
224
226
  def is_deployed(self):
225
227
  return True
226
228
 
227
- def _get_handler(self, handler, context):
229
+ def _get_handler(
230
+ self, handler: str, context: MLClientCtx, embed_in_sys: bool = True
231
+ ):
228
232
  command = self.spec.command
229
233
  if not command and self.spec.build.functionSourceCode:
230
234
  # if the code is embedded in the function object extract or find it
231
235
  command, _ = mlrun.run.load_func_code(self)
232
- return load_module(command, handler, context)
236
+ return load_module(command, handler, context, embed_in_sys=embed_in_sys)
233
237
 
234
238
  def _pre_run(self, runobj: RunObject, execution: MLClientCtx):
235
239
  workdir = self.spec.workdir
@@ -372,8 +376,20 @@ class LocalRuntime(BaseRuntime, ParallelRunner):
372
376
  return run_obj_dict
373
377
 
374
378
 
375
- def load_module(file_name, handler, context):
376
- """Load module from file name"""
379
+ def load_module(
380
+ file_name: str,
381
+ handler: str,
382
+ context: MLClientCtx,
383
+ embed_in_sys: bool = True,
384
+ ):
385
+ """
386
+ Load module from filename
387
+ :param file_name: The module path to load
388
+ :param handler: The callable to load
389
+ :param context: Execution context
390
+ :param embed_in_sys: Embed the file-named module in sys.modules. This is not persistent with remote
391
+ environments and therefore can effect pickling.
392
+ """
377
393
  module = None
378
394
  if file_name:
379
395
  path = Path(file_name)
@@ -384,7 +400,8 @@ def load_module(file_name, handler, context):
384
400
  if spec is None:
385
401
  raise RunError(f"Cannot import from {file_name!r}")
386
402
  module = imputil.module_from_spec(spec)
387
- sys.modules[mod_name] = module
403
+ if embed_in_sys:
404
+ sys.modules[mod_name] = module
388
405
  spec.loader.exec_module(module)
389
406
 
390
407
  class_args = {}
@@ -1327,3 +1327,23 @@ def get_nuclio_deploy_status(
1327
1327
  else:
1328
1328
  text = "\n".join(outputs) if outputs else ""
1329
1329
  return state, address, name, last_log_timestamp, text, function_status
1330
+
1331
+
1332
+ def enrich_nuclio_function_from_headers(
1333
+ func: RemoteRuntime,
1334
+ headers: dict,
1335
+ ):
1336
+ func.status.state = headers.get("x-mlrun-function-status", "")
1337
+ func.status.address = headers.get("x-mlrun-address", "")
1338
+ func.status.nuclio_name = headers.get("x-mlrun-name", "")
1339
+ func.status.internal_invocation_urls = (
1340
+ headers.get("x-mlrun-internal-invocation-urls", "").split(",")
1341
+ if headers.get("x-mlrun-internal-invocation-urls")
1342
+ else []
1343
+ )
1344
+ func.status.external_invocation_urls = (
1345
+ headers.get("x-mlrun-external-invocation-urls", "").split(",")
1346
+ if headers.get("x-mlrun-external-invocation-urls")
1347
+ else []
1348
+ )
1349
+ func.status.container_image = headers.get("x-mlrun-container-image", "")
mlrun/runtimes/pod.py CHANGED
@@ -532,7 +532,9 @@ class KubeResourceSpec(FunctionSpec):
532
532
  return
533
533
 
534
534
  # merge node selectors - precedence to existing node selector
535
- self.node_selector = {**node_selector, **self.node_selector}
535
+ self.node_selector = mlrun.utils.helpers.merge_with_precedence(
536
+ node_selector, self.node_selector
537
+ )
536
538
 
537
539
  def _merge_tolerations(
538
540
  self,
@@ -1038,32 +1040,6 @@ class KubeResource(BaseRuntime, KfpAdapterMixin):
1038
1040
  return True
1039
1041
  return False
1040
1042
 
1041
- def enrich_runtime_spec(
1042
- self,
1043
- project_node_selector: dict[str, str],
1044
- ):
1045
- """
1046
- Enriches the runtime spec with the project-level node selector.
1047
-
1048
- This method merges the project-level node selector with the existing function node_selector.
1049
- The merge logic used here combines the two dictionaries, giving precedence to
1050
- the keys in the runtime node_selector. If there are conflicting keys between the
1051
- two dictionaries, the values from self.spec.node_selector will overwrite the
1052
- values from project_node_selector.
1053
-
1054
- Example:
1055
- Suppose self.spec.node_selector = {"type": "gpu", "zone": "us-east-1"}
1056
- and project_node_selector = {"type": "cpu", "environment": "production"}.
1057
- After the merge, the resulting node_selector will be:
1058
- {"type": "gpu", "zone": "us-east-1", "environment": "production"}
1059
-
1060
- Note:
1061
- - The merge uses the ** operator, also known as the "unpacking" operator in Python,
1062
- combining key-value pairs from each dictionary. Later dictionaries take precedence
1063
- when there are conflicting keys.
1064
- """
1065
- self.spec.node_selector = {**project_node_selector, **self.spec.node_selector}
1066
-
1067
1043
  def _set_env(self, name, value=None, value_from=None):
1068
1044
  new_var = k8s_client.V1EnvVar(name=name, value=value, value_from=value_from)
1069
1045
 
@@ -1542,7 +1518,7 @@ def get_sanitized_attribute(spec, attribute_name: str):
1542
1518
 
1543
1519
  # check if attribute of type dict, and then check if type is sanitized
1544
1520
  if isinstance(attribute, dict):
1545
- if attribute_config["not_sanitized_class"] != dict:
1521
+ if not isinstance(attribute_config["not_sanitized_class"], dict):
1546
1522
  raise mlrun.errors.MLRunInvalidArgumentTypeError(
1547
1523
  f"expected to be of type {attribute_config.get('not_sanitized_class')} but got dict"
1548
1524
  )
@@ -1552,7 +1528,7 @@ def get_sanitized_attribute(spec, attribute_name: str):
1552
1528
  elif isinstance(attribute, list) and not isinstance(
1553
1529
  attribute[0], attribute_config["sub_attribute_type"]
1554
1530
  ):
1555
- if attribute_config["not_sanitized_class"] != list:
1531
+ if not isinstance(attribute_config["not_sanitized_class"], list):
1556
1532
  raise mlrun.errors.MLRunInvalidArgumentTypeError(
1557
1533
  f"expected to be of type {attribute_config.get('not_sanitized_class')} but got list"
1558
1534
  )
mlrun/serving/routers.py CHANGED
@@ -1030,74 +1030,90 @@ def _init_endpoint_record(
1030
1030
  function_uri=graph_server.function_uri, versioned_model=versioned_model_name
1031
1031
  ).uid
1032
1032
 
1033
- # If model endpoint object was found in DB, skip the creation process.
1034
1033
  try:
1035
- mlrun.get_run_db().get_model_endpoint(project=project, endpoint_id=endpoint_uid)
1036
-
1034
+ model_ep = mlrun.get_run_db().get_model_endpoint(
1035
+ project=project, endpoint_id=endpoint_uid
1036
+ )
1037
1037
  except mlrun.errors.MLRunNotFoundError:
1038
- logger.info("Creating a new model endpoint record", endpoint_id=endpoint_uid)
1038
+ model_ep = None
1039
+ except mlrun.errors.MLRunBadRequestError as err:
1040
+ logger.debug(
1041
+ f"Cant reach to model endpoints store, due to : {err}",
1042
+ )
1043
+ return
1039
1044
 
1040
- try:
1041
- # Get the children model endpoints ids
1042
- children_uids = []
1043
- for _, c in voting_ensemble.routes.items():
1044
- if hasattr(c, "endpoint_uid"):
1045
- children_uids.append(c.endpoint_uid)
1046
-
1047
- model_endpoint = mlrun.common.schemas.ModelEndpoint(
1048
- metadata=mlrun.common.schemas.ModelEndpointMetadata(
1049
- project=project, uid=endpoint_uid
1050
- ),
1051
- spec=mlrun.common.schemas.ModelEndpointSpec(
1052
- function_uri=graph_server.function_uri,
1053
- model=versioned_model_name,
1054
- model_class=voting_ensemble.__class__.__name__,
1055
- stream_path=config.model_endpoint_monitoring.store_prefixes.default.format(
1056
- project=project, kind="stream"
1057
- ),
1058
- active=True,
1059
- monitoring_mode=mlrun.common.schemas.model_monitoring.ModelMonitoringMode.enabled
1060
- if voting_ensemble.context.server.track_models
1061
- else mlrun.common.schemas.model_monitoring.ModelMonitoringMode.disabled,
1062
- ),
1063
- status=mlrun.common.schemas.ModelEndpointStatus(
1064
- children=list(voting_ensemble.routes.keys()),
1065
- endpoint_type=mlrun.common.schemas.model_monitoring.EndpointType.ROUTER,
1066
- children_uids=children_uids,
1045
+ if voting_ensemble.context.server.track_models and not model_ep:
1046
+ logger.info("Creating a new model endpoint record", endpoint_id=endpoint_uid)
1047
+ # Get the children model endpoints ids
1048
+ children_uids = []
1049
+ for _, c in voting_ensemble.routes.items():
1050
+ if hasattr(c, "endpoint_uid"):
1051
+ children_uids.append(c.endpoint_uid)
1052
+ model_endpoint = mlrun.common.schemas.ModelEndpoint(
1053
+ metadata=mlrun.common.schemas.ModelEndpointMetadata(
1054
+ project=project, uid=endpoint_uid
1055
+ ),
1056
+ spec=mlrun.common.schemas.ModelEndpointSpec(
1057
+ function_uri=graph_server.function_uri,
1058
+ model=versioned_model_name,
1059
+ model_class=voting_ensemble.__class__.__name__,
1060
+ stream_path=config.model_endpoint_monitoring.store_prefixes.default.format(
1061
+ project=project, kind="stream"
1067
1062
  ),
1068
- )
1063
+ active=True,
1064
+ monitoring_mode=mlrun.common.schemas.model_monitoring.ModelMonitoringMode.enabled,
1065
+ ),
1066
+ status=mlrun.common.schemas.ModelEndpointStatus(
1067
+ children=list(voting_ensemble.routes.keys()),
1068
+ endpoint_type=mlrun.common.schemas.model_monitoring.EndpointType.ROUTER,
1069
+ children_uids=children_uids,
1070
+ ),
1071
+ )
1069
1072
 
1070
- db = mlrun.get_run_db()
1073
+ db = mlrun.get_run_db()
1074
+
1075
+ db.create_model_endpoint(
1076
+ project=project,
1077
+ endpoint_id=model_endpoint.metadata.uid,
1078
+ model_endpoint=model_endpoint.dict(),
1079
+ )
1071
1080
 
1081
+ # Update model endpoint children type
1082
+ for model_endpoint in children_uids:
1083
+ current_endpoint = db.get_model_endpoint(
1084
+ project=project, endpoint_id=model_endpoint
1085
+ )
1086
+ current_endpoint.status.endpoint_type = (
1087
+ mlrun.common.schemas.model_monitoring.EndpointType.LEAF_EP
1088
+ )
1072
1089
  db.create_model_endpoint(
1073
1090
  project=project,
1074
- endpoint_id=model_endpoint.metadata.uid,
1075
- model_endpoint=model_endpoint.dict(),
1076
- )
1077
-
1078
- # Update model endpoint children type
1079
- for model_endpoint in children_uids:
1080
- current_endpoint = db.get_model_endpoint(
1081
- project=project, endpoint_id=model_endpoint
1082
- )
1083
- current_endpoint.status.endpoint_type = (
1084
- mlrun.common.schemas.model_monitoring.EndpointType.LEAF_EP
1085
- )
1086
- db.create_model_endpoint(
1087
- project=project,
1088
- endpoint_id=model_endpoint,
1089
- model_endpoint=current_endpoint,
1090
- )
1091
-
1092
- except Exception as exc:
1093
- logger.warning(
1094
- "Failed creating model endpoint record",
1095
- exc=err_to_str(exc),
1096
- traceback=traceback.format_exc(),
1091
+ endpoint_id=model_endpoint,
1092
+ model_endpoint=current_endpoint,
1097
1093
  )
1098
-
1099
- except Exception as e:
1100
- logger.error("Failed to retrieve model endpoint object", exc=err_to_str(e))
1094
+ elif (
1095
+ model_ep
1096
+ and (
1097
+ model_ep.spec.monitoring_mode
1098
+ == mlrun.common.schemas.model_monitoring.ModelMonitoringMode.enabled
1099
+ )
1100
+ != voting_ensemble.context.server.track_models
1101
+ ):
1102
+ monitoring_mode = (
1103
+ mlrun.common.schemas.model_monitoring.ModelMonitoringMode.enabled
1104
+ if voting_ensemble.context.server.track_models
1105
+ else mlrun.common.schemas.model_monitoring.ModelMonitoringMode.disabled
1106
+ )
1107
+ db = mlrun.get_run_db()
1108
+ db.patch_model_endpoint(
1109
+ project=project,
1110
+ endpoint_id=endpoint_uid,
1111
+ attributes={"monitoring_mode": monitoring_mode},
1112
+ )
1113
+ logger.debug(
1114
+ f"Updating model endpoint monitoring_mode to {monitoring_mode}",
1115
+ endpoint_id=endpoint_uid,
1116
+ )
1101
1117
 
1102
1118
  return endpoint_uid
1103
1119
 
mlrun/serving/server.py CHANGED
@@ -390,6 +390,7 @@ def v2_serving_handler(context, event, get_body=False):
390
390
  "kafka",
391
391
  "kafka-cluster",
392
392
  "v3ioStream",
393
+ "v3io-stream",
393
394
  ):
394
395
  event.path = "/"
395
396