mlrun 1.8.0rc12__py3-none-any.whl → 1.8.0rc13__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.

@@ -45,32 +45,6 @@ class _ArtifactsLogger(Protocol):
45
45
 
46
46
 
47
47
  class MonitoringApplicationContext:
48
- """
49
- The monitoring context holds all the relevant information for the monitoring application,
50
- and also it can be used for logging artifacts and results.
51
- The monitoring context has the following attributes:
52
-
53
- :param application_name: (str) The model monitoring application name.
54
- :param project_name: (str) The project name.
55
- :param project: (MlrunProject) The project object.
56
- :param logger: (mlrun.utils.Logger) MLRun logger.
57
- :param nuclio_logger: (nuclio.request.Logger) Nuclio logger.
58
- :param sample_df_stats: (FeatureStats) The new sample distribution dictionary.
59
- :param feature_stats: (FeatureStats) The train sample distribution dictionary.
60
- :param sample_df: (pd.DataFrame) The new sample DataFrame.
61
- :param start_infer_time: (pd.Timestamp) Start time of the monitoring schedule.
62
- :param end_infer_time: (pd.Timestamp) End time of the monitoring schedule.
63
- :param latest_request: (pd.Timestamp) Timestamp of the latest request on this endpoint_id.
64
- :param endpoint_id: (str) ID of the monitored model endpoint
65
- :param endpoint_name: (str) Name of the monitored model endpoint
66
- :param output_stream_uri: (str) URI of the output stream for results
67
- :param model_endpoint: (ModelEndpoint) The model endpoint object.
68
- :param feature_names: (list[str]) List of models feature names.
69
- :param label_names: (list[str]) List of models label names.
70
- :param model: (tuple[str, ModelArtifact, dict]) The model file, model spec object,
71
- and a list of extra data items.
72
- """
73
-
74
48
  _logger_name = "monitoring-application"
75
49
 
76
50
  def __init__(
@@ -78,64 +52,51 @@ class MonitoringApplicationContext:
78
52
  *,
79
53
  application_name: str,
80
54
  event: dict[str, Any],
55
+ project: "mlrun.MlrunProject",
56
+ artifacts_logger: _ArtifactsLogger,
57
+ logger: mlrun.utils.Logger,
58
+ nuclio_logger: nuclio.request.Logger,
81
59
  model_endpoint_dict: Optional[dict[str, ModelEndpoint]] = None,
82
- logger: Optional[mlrun.utils.Logger] = None,
83
- graph_context: Optional[mlrun.serving.GraphContext] = None,
84
- context: Optional["mlrun.MLClientCtx"] = None,
85
- artifacts_logger: Optional[_ArtifactsLogger] = None,
86
60
  sample_df: Optional[pd.DataFrame] = None,
87
61
  feature_stats: Optional[FeatureStats] = None,
88
62
  ) -> None:
89
63
  """
90
- The :code:`__init__` method initializes a :code:`MonitoringApplicationContext` object
91
- and has the following attributes.
92
- Note: this object should not be instantiated manually.
93
-
94
- :param application_name: The application name.
95
- :param event: The instance data dictionary.
96
- :param model_endpoint_dict: Optional - dictionary of model endpoints.
97
- :param logger: Optional - MLRun logger instance.
98
- :param graph_context: Optional - GraphContext instance.
99
- :param context: Optional - MLClientCtx instance.
100
- :param artifacts_logger: Optional - an object that can log artifacts,
101
- typically :py:class:`~mlrun.projects.MlrunProject` or
102
- :py:class:`~mlrun.execution.MLClientCtx`.
103
- :param sample_df: Optional - pandas data-frame as the current dataset.
104
- When set, it replaces the data read from the offline source.
105
- :param feature_stats: Optional - statistics dictionary of the reference data.
106
- When set, it overrides the model endpoint's feature stats.
64
+ The :code:`MonitoringApplicationContext` object holds all the relevant information for the
65
+ model monitoring application, and can be used for logging artifacts and messages.
66
+ The monitoring context has the following attributes:
67
+
68
+ :param application_name: (str) The model monitoring application name.
69
+ :param project: (:py:class:`~mlrun.projects.MlrunProject`) The current MLRun project object.
70
+ :param project_name: (str) The project name.
71
+ :param logger: (:py:class:`~mlrun.utils.Logger`) MLRun logger.
72
+ :param nuclio_logger: (nuclio.request.Logger) Nuclio logger.
73
+ :param sample_df_stats: (FeatureStats) The new sample distribution dictionary.
74
+ :param feature_stats: (FeatureStats) The train sample distribution dictionary.
75
+ :param sample_df: (pd.DataFrame) The new sample DataFrame.
76
+ :param start_infer_time: (pd.Timestamp) Start time of the monitoring schedule.
77
+ :param end_infer_time: (pd.Timestamp) End time of the monitoring schedule.
78
+ :param latest_request: (pd.Timestamp) Timestamp of the latest request on this endpoint_id.
79
+ :param endpoint_id: (str) ID of the monitored model endpoint
80
+ :param endpoint_name: (str) Name of the monitored model endpoint
81
+ :param output_stream_uri: (str) URI of the output stream for results
82
+ :param model_endpoint: (ModelEndpoint) The model endpoint object.
83
+ :param feature_names: (list[str]) List of models feature names.
84
+ :param label_names: (list[str]) List of models label names.
85
+ :param model: (tuple[str, ModelArtifact, dict]) The model file, model spec object,
86
+ and a list of extra data items.
107
87
  """
108
88
  self.application_name = application_name
109
89
 
110
- if graph_context:
111
- self.project_name = graph_context.project
112
- self.project = mlrun.load_project(url=self.project_name)
113
- elif context:
114
- potential_project = context.get_project_object()
115
- if not potential_project:
116
- raise mlrun.errors.MLRunValueError(
117
- "Could not load project from context"
118
- )
119
- self.project = potential_project
120
- self.project_name = self.project.name
90
+ self.project = project
91
+ self.project_name = project.name
121
92
 
122
- self._artifacts_logger: _ArtifactsLogger = artifacts_logger or self.project
93
+ self._artifacts_logger = artifacts_logger
123
94
 
124
95
  # MLRun Logger
125
- self.logger = logger or mlrun.utils.create_logger(
126
- level=mlrun.mlconf.log_level,
127
- formatter_kind=mlrun.mlconf.log_formatter,
128
- name=self._logger_name,
129
- )
96
+ self.logger = logger
130
97
  # Nuclio logger - `nuclio.request.Logger`.
131
98
  # Note: this logger accepts keyword arguments only in its `_with` methods, e.g. `info_with`.
132
- self.nuclio_logger = (
133
- graph_context.logger
134
- if graph_context
135
- else nuclio.request.Logger(
136
- level=mlrun.mlconf.log_level, name=self._logger_name
137
- )
138
- )
99
+ self.nuclio_logger = nuclio_logger
139
100
 
140
101
  # event data
141
102
  self.start_infer_time = pd.Timestamp(
@@ -166,6 +127,68 @@ class MonitoringApplicationContext:
166
127
  model_endpoint_dict.get(self.endpoint_id) if model_endpoint_dict else None
167
128
  )
168
129
 
130
+ @classmethod
131
+ def _from_ml_ctx(
132
+ cls,
133
+ context: "mlrun.MLClientCtx",
134
+ *,
135
+ application_name: str,
136
+ event: dict[str, Any],
137
+ model_endpoint_dict: Optional[dict[str, ModelEndpoint]] = None,
138
+ sample_df: Optional[pd.DataFrame] = None,
139
+ feature_stats: Optional[FeatureStats] = None,
140
+ ) -> "MonitoringApplicationContext":
141
+ project = context.get_project_object()
142
+ if not project:
143
+ raise mlrun.errors.MLRunValueError("Could not load project from context")
144
+ logger = context.logger
145
+ artifacts_logger = context
146
+ nuclio_logger = nuclio.request.Logger(
147
+ level=mlrun.mlconf.log_level, name=cls._logger_name
148
+ )
149
+ return cls(
150
+ application_name=application_name,
151
+ event=event,
152
+ model_endpoint_dict=model_endpoint_dict,
153
+ project=project,
154
+ logger=logger,
155
+ nuclio_logger=nuclio_logger,
156
+ artifacts_logger=artifacts_logger,
157
+ sample_df=sample_df,
158
+ feature_stats=feature_stats,
159
+ )
160
+
161
+ @classmethod
162
+ def _from_graph_ctx(
163
+ cls,
164
+ graph_context: mlrun.serving.GraphContext,
165
+ *,
166
+ application_name: str,
167
+ event: dict[str, Any],
168
+ model_endpoint_dict: Optional[dict[str, ModelEndpoint]] = None,
169
+ sample_df: Optional[pd.DataFrame] = None,
170
+ feature_stats: Optional[FeatureStats] = None,
171
+ ) -> "MonitoringApplicationContext":
172
+ project = mlrun.load_project(url=graph_context.project)
173
+ nuclio_logger = graph_context.logger
174
+ artifacts_logger = project
175
+ logger = mlrun.utils.create_logger(
176
+ level=mlrun.mlconf.log_level,
177
+ formatter_kind=mlrun.mlconf.log_formatter,
178
+ name=cls._logger_name,
179
+ )
180
+ return cls(
181
+ application_name=application_name,
182
+ event=event,
183
+ project=project,
184
+ model_endpoint_dict=model_endpoint_dict,
185
+ logger=logger,
186
+ nuclio_logger=nuclio_logger,
187
+ artifacts_logger=artifacts_logger,
188
+ sample_df=sample_df,
189
+ feature_stats=feature_stats,
190
+ )
191
+
169
192
  def _get_default_labels(self) -> dict[str, str]:
170
193
  labels = {
171
194
  mlrun_constants.MLRunInternalLabels.runner_pod: socket.gethostname(),
@@ -471,6 +471,7 @@ class _PipelineRunner(abc.ABC):
471
471
  namespace=None,
472
472
  source=None,
473
473
  notifications: typing.Optional[list[mlrun.model.Notification]] = None,
474
+ context: typing.Optional[mlrun.execution.MLClientCtx] = None,
474
475
  ) -> _PipelineRunStatus:
475
476
  pass
476
477
 
@@ -595,6 +596,7 @@ class _KFPRunner(_PipelineRunner):
595
596
  namespace=None,
596
597
  source=None,
597
598
  notifications: typing.Optional[list[mlrun.model.Notification]] = None,
599
+ context: typing.Optional[mlrun.execution.MLClientCtx] = None,
598
600
  ) -> _PipelineRunStatus:
599
601
  pipeline_context.set(project, workflow_spec)
600
602
  workflow_handler = _PipelineRunner._get_handler(
@@ -646,9 +648,7 @@ class _KFPRunner(_PipelineRunner):
646
648
  )
647
649
  project.notifiers.push_pipeline_start_message(
648
650
  project.metadata.name,
649
- project.get_param("commit_id", None),
650
- run_id,
651
- True,
651
+ context.uid,
652
652
  )
653
653
  pipeline_context.clear()
654
654
  return _PipelineRunStatus(run_id, cls, project=project, workflow=workflow_spec)
@@ -722,6 +722,7 @@ class _LocalRunner(_PipelineRunner):
722
722
  namespace=None,
723
723
  source=None,
724
724
  notifications: typing.Optional[list[mlrun.model.Notification]] = None,
725
+ context: typing.Optional[mlrun.execution.MLClientCtx] = None,
725
726
  ) -> _PipelineRunStatus:
726
727
  pipeline_context.set(project, workflow_spec)
727
728
  workflow_handler = _PipelineRunner._get_handler(
@@ -805,6 +806,7 @@ class _RemoteRunner(_PipelineRunner):
805
806
  namespace: typing.Optional[str] = None,
806
807
  source: typing.Optional[str] = None,
807
808
  notifications: typing.Optional[list[mlrun.model.Notification]] = None,
809
+ context: typing.Optional[mlrun.execution.MLClientCtx] = None,
808
810
  ) -> typing.Optional[_PipelineRunStatus]:
809
811
  workflow_name = normalize_workflow_name(name=name, project_name=project.name)
810
812
  workflow_id = None
@@ -1127,6 +1129,7 @@ def load_and_run_workflow(
1127
1129
  engine=engine,
1128
1130
  local=local,
1129
1131
  notifications=start_notifications,
1132
+ context=context,
1130
1133
  )
1131
1134
  context.log_result(key="workflow_id", value=run.run_id)
1132
1135
  context.log_result(key="engine", value=run._engine.engine, commit=True)
mlrun/projects/project.py CHANGED
@@ -1873,6 +1873,34 @@ class MlrunProject(ModelObj):
1873
1873
  vector_store: "VectorStore", # noqa: F821
1874
1874
  collection_name: Optional[str] = None,
1875
1875
  ) -> VectorStoreCollection:
1876
+ """
1877
+ Create a VectorStoreCollection wrapper for a given vector store instance.
1878
+
1879
+ This method wraps a vector store implementation (like Milvus, Chroma) with MLRun
1880
+ integration capabilities. The wrapper provides access to the underlying vector
1881
+ store's functionality while adding MLRun-specific features like document and
1882
+ artifact management.
1883
+
1884
+ Args:
1885
+ vector_store: The vector store instance to wrap (e.g., Milvus, Chroma).
1886
+ This is the underlying implementation that will handle
1887
+ vector storage and retrieval.
1888
+ collection_name: Optional name for the collection. If not provided,
1889
+ will attempt to extract it from the vector_store object
1890
+ by looking for 'collection_name', '_collection_name',
1891
+ 'index_name', or '_index_name' attributes.
1892
+
1893
+ Returns:
1894
+ VectorStoreCollection: A wrapped vector store instance with MLRun integration.
1895
+ This wrapper provides both access to the original vector
1896
+ store's capabilities and additional MLRun functionality.
1897
+
1898
+ Example:
1899
+ >>> vector_store = Chroma(embedding_function=embeddings)
1900
+ >>> collection = project.get_vector_store_collection(
1901
+ ... vector_store, collection_name="my_collection"
1902
+ ... )
1903
+ """
1876
1904
  return VectorStoreCollection(
1877
1905
  self,
1878
1906
  vector_store,
@@ -1899,12 +1927,39 @@ class MlrunProject(ModelObj):
1899
1927
  :param local_path: path to the local file we upload, will also be use
1900
1928
  as the destination subpath (under "artifact_path")
1901
1929
  :param artifact_path: Target path for artifact storage
1902
- :param document_loader_spec: Spec to use to load the artifact as langchain document
1930
+ :param document_loader_spec: Spec to use to load the artifact as langchain document.
1931
+
1932
+ By default, uses DocumentLoaderSpec() which initializes with:
1933
+
1934
+ * loader_class_name="langchain_community.document_loaders.TextLoader"
1935
+ * src_name="file_path"
1936
+ * kwargs=None
1937
+
1938
+ Can be customized for different document types, e.g.::
1939
+
1940
+ DocumentLoaderSpec(
1941
+ loader_class_name="langchain_community.document_loaders.PDFLoader",
1942
+ src_name="file_path",
1943
+ kwargs={"extract_images": True}
1944
+ )
1903
1945
  :param upload: Whether to upload the artifact
1904
1946
  :param labels: Key-value labels
1905
1947
  :param target_path: Target file path
1906
1948
  :param kwargs: Additional keyword arguments
1907
1949
  :return: DocumentArtifact object
1950
+
1951
+ Example:
1952
+ >>> # Log a PDF document with custom loader
1953
+ >>> project.log_document(
1954
+ ... key="my_doc",
1955
+ ... local_path="path/to/doc.pdf",
1956
+ ... document_loader=DocumentLoaderSpec(
1957
+ ... loader_class_name="langchain_community.document_loaders.PDFLoader",
1958
+ ... src_name="file_path",
1959
+ ... kwargs={"extract_images": True},
1960
+ ... ),
1961
+ ... )
1962
+
1908
1963
  """
1909
1964
  doc_artifact = DocumentArtifact(
1910
1965
  key=key,
@@ -2586,6 +2641,24 @@ class MlrunProject(ModelObj):
2586
2641
  self._set_function(resolved_function_name, tag, function_object, func)
2587
2642
  return function_object
2588
2643
 
2644
+ def push_run_notifications(
2645
+ self,
2646
+ uid,
2647
+ timeout=45,
2648
+ ):
2649
+ """
2650
+ Push notifications for a run.
2651
+
2652
+ :param uid: Unique ID of the run.
2653
+ :returns: :py:class:`~mlrun.common.schemas.BackgroundTask`.
2654
+ """
2655
+ db = mlrun.db.get_run_db(secrets=self._secrets)
2656
+ return db.push_run_notifications(
2657
+ project=self.name,
2658
+ uid=uid,
2659
+ timeout=timeout,
2660
+ )
2661
+
2589
2662
  def _instantiate_function(
2590
2663
  self,
2591
2664
  func: typing.Union[str, mlrun.runtimes.BaseRuntime] = None,
@@ -3239,6 +3312,7 @@ class MlrunProject(ModelObj):
3239
3312
  cleanup_ttl: Optional[int] = None,
3240
3313
  notifications: Optional[list[mlrun.model.Notification]] = None,
3241
3314
  workflow_runner_node_selector: typing.Optional[dict[str, str]] = None,
3315
+ context: typing.Optional[mlrun.execution.MLClientCtx] = None,
3242
3316
  ) -> _PipelineRunStatus:
3243
3317
  """Run a workflow using kubeflow pipelines
3244
3318
 
@@ -3281,6 +3355,7 @@ class MlrunProject(ModelObj):
3281
3355
  This allows you to control and specify where the workflow runner pod will be scheduled.
3282
3356
  This setting is only relevant when the engine is set to 'remote' or for scheduled workflows,
3283
3357
  and it will be ignored if the workflow is not run on a remote engine.
3358
+ :param context: mlrun context.
3284
3359
  :returns: ~py:class:`~mlrun.projects.pipelines._PipelineRunStatus` instance
3285
3360
  """
3286
3361
 
@@ -3367,6 +3442,7 @@ class MlrunProject(ModelObj):
3367
3442
  namespace=namespace,
3368
3443
  source=source,
3369
3444
  notifications=notifications,
3445
+ context=context,
3370
3446
  )
3371
3447
  # run is None when scheduling
3372
3448
  if run and run.state == mlrun_pipelines.common.models.RunStatuses.failed:
@@ -1036,9 +1036,10 @@ class RemoteRuntime(KubeResource):
1036
1036
  if args and sidecar.get("command"):
1037
1037
  sidecar["args"] = mlrun.utils.helpers.as_list(args)
1038
1038
 
1039
- # populate the sidecar resources from the function spec
1039
+ # put the configured resources on the sidecar container instead of the reverse proxy container
1040
1040
  if self.spec.resources:
1041
1041
  sidecar["resources"] = self.spec.resources
1042
+ self.spec.resources = None
1042
1043
 
1043
1044
  def _set_sidecar(self, name: str) -> dict:
1044
1045
  self.spec.config.setdefault("spec.sidecars", [])
@@ -387,11 +387,16 @@ class ServingRuntime(RemoteRuntime):
387
387
  :param router_step: router step name (to determine which router we add the model to in graphs
388
388
  with multiple router steps)
389
389
  :param child_function: child function name, when the model runs in a child function
390
- :param creation_strategy: model endpoint creation strategy :
391
- * overwrite - Create a new model endpoint and delete the last old one if it exists.
392
- * inplace - Use the existing model endpoint if it already exists (default).
393
- * archive - Preserve the old model endpoint and create a new one,
394
- tagging it as the latest.
390
+ :param creation_strategy: Strategy for creating or updating the model endpoint:
391
+ * **overwrite**:
392
+ 1. If model endpoints with the same name exist, delete the `latest` one.
393
+ 2. Create a new model endpoint entry and set it as `latest`.
394
+ * **inplace** (default):
395
+ 1. If model endpoints with the same name exist, update the `latest` entry.
396
+ 2. Otherwise, create a new entry.
397
+ * **archive**:
398
+ 1. If model endpoints with the same name exist, preserve them.
399
+ 2. Create a new model endpoint with the same name and set it to `latest`.
395
400
  :param class_args: extra kwargs to pass to the model serving class __init__
396
401
  (can be read in the model using .get_param(key) method)
397
402
  """
mlrun/serving/routers.py CHANGED
@@ -619,7 +619,10 @@ class VotingEnsemble(ParallelRun):
619
619
 
620
620
  if not self.context.is_mock or self.context.monitoring_mock:
621
621
  self.model_endpoint_uid = _init_endpoint_record(
622
- server, self, creation_strategy=kwargs.get("creation_strategy")
622
+ server,
623
+ self,
624
+ creation_strategy=kwargs.get("creation_strategy"),
625
+ endpoint_type=kwargs.get("endpoint_type"),
623
626
  )
624
627
 
625
628
  self._update_weights(self.weights)
@@ -1004,7 +1007,7 @@ class VotingEnsemble(ParallelRun):
1004
1007
  def _init_endpoint_record(
1005
1008
  graph_server: GraphServer,
1006
1009
  voting_ensemble: VotingEnsemble,
1007
- creation_strategy: str,
1010
+ creation_strategy: mlrun.common.schemas.ModelEndpointCreationStrategy,
1008
1011
  endpoint_type: mlrun.common.schemas.EndpointType,
1009
1012
  ) -> Union[str, None]:
1010
1013
  """
@@ -1015,11 +1018,17 @@ def _init_endpoint_record(
1015
1018
  :param graph_server: A GraphServer object which will be used for getting the function uri.
1016
1019
  :param voting_ensemble: Voting ensemble serving class. It contains important details for the model endpoint record
1017
1020
  such as model name, model path, model version, and the ids of the children model endpoints.
1018
- :param creation_strategy: model endpoint creation strategy :
1019
- * overwrite - Create a new model endpoint and delete the last old one if it exists.
1020
- * inplace - Use the existing model endpoint if it already exists (default).
1021
- * archive - Preserve the old model endpoint and create a new one,
1022
- tagging it as the latest.
1021
+ :param creation_strategy: Strategy for creating or updating the model endpoint:
1022
+ * **overwrite**:
1023
+ 1. If model endpoints with the same name exist, delete the `latest` one.
1024
+ 2. Create a new model endpoint entry and set it as `latest`.
1025
+ * **inplace** (default):
1026
+ 1. If model endpoints with the same name exist, update the `latest` entry.
1027
+ 2. Otherwise, create a new entry.
1028
+ * **archive**:
1029
+ 1. If model endpoints with the same name exist, preserve them.
1030
+ 2. Create a new model endpoint with the same name and set it to `latest`.
1031
+
1023
1032
  :param endpoint_type: model endpoint type
1024
1033
  :return: Model endpoint unique ID.
1025
1034
  """
mlrun/serving/states.py CHANGED
@@ -755,11 +755,17 @@ class RouterStep(TaskStep):
755
755
  :param class_args: class init arguments
756
756
  :param handler: class handler to invoke on run/event
757
757
  :param function: function this step should run in
758
- :param creation_strategy: model endpoint creation strategy :
759
- * overwrite - Create a new model endpoint and delete the last old one if it exists.
760
- * inplace - Use the existing model endpoint if it already exists (default).
761
- * archive - Preserve the old model endpoint and create a new one,
762
- tagging it as the latest.
758
+ :param creation_strategy: Strategy for creating or updating the model endpoint:
759
+ * **overwrite**:
760
+ 1. If model endpoints with the same name exist, delete the `latest` one.
761
+ 2. Create a new model endpoint entry and set it as `latest`.
762
+ * **inplace** (default):
763
+ 1. If model endpoints with the same name exist, update the `latest` entry.
764
+ 2. Otherwise, create a new entry.
765
+ * **archive**:
766
+ 1. If model endpoints with the same name exist, preserve them.
767
+ 2. Create a new model endpoint with the same name and set it to `latest`.
768
+
763
769
  """
764
770
 
765
771
  if not route and not class_name and not handler:
@@ -770,7 +776,9 @@ class RouterStep(TaskStep):
770
776
  class_args,
771
777
  handler=handler,
772
778
  model_endpoint_creation_strategy=creation_strategy,
773
- endpoint_type=schemas.EndpointType.NODE_EP,
779
+ endpoint_type=schemas.EndpointType.LEAF_EP
780
+ if self.class_name and "serving.VotingEnsemble" in self.class_name
781
+ else schemas.EndpointType.NODE_EP,
774
782
  )
775
783
  route.function = function or route.function
776
784
 
@@ -558,7 +558,7 @@ class _ModelLogPusher:
558
558
  def _init_endpoint_record(
559
559
  graph_server: GraphServer,
560
560
  model: V2ModelServer,
561
- creation_strategy: str,
561
+ creation_strategy: mlrun.common.schemas.ModelEndpointCreationStrategy,
562
562
  endpoint_type: mlrun.common.schemas.EndpointType,
563
563
  ) -> Union[str, None]:
564
564
  """
@@ -569,11 +569,16 @@ def _init_endpoint_record(
569
569
  :param graph_server: A GraphServer object which will be used for getting the function uri.
570
570
  :param model: Base model serving class (v2). It contains important details for the model endpoint record
571
571
  such as model name, model path, and model version.
572
- :param creation_strategy: model endpoint creation strategy :
573
- * overwrite - Create a new model endpoint and delete the last old one if it exists.
574
- * inplace - Use the existing model endpoint if it already exists (default).
575
- * archive - Preserve the old model endpoint and create a new one,
576
- tagging it as the latest.
572
+ :param creation_strategy: Strategy for creating or updating the model endpoint:
573
+ * **overwrite**:
574
+ 1. If model endpoints with the same name exist, delete the `latest` one.
575
+ 2. Create a new model endpoint entry and set it as `latest`.
576
+ * **inplace** (default):
577
+ 1. If model endpoints with the same name exist, update the `latest` entry.
578
+ 2. Otherwise, create a new entry.
579
+ * **archive**:
580
+ 1. If model endpoints with the same name exist, preserve them.
581
+ 2. Create a new model endpoint with the same name and set it to `latest`.
577
582
  :param endpoint_type model endpoint type
578
583
 
579
584
  :return: Model endpoint unique ID.
@@ -57,7 +57,7 @@ class NotificationBase:
57
57
  typing.Union[mlrun.common.schemas.NotificationSeverity, str]
58
58
  ] = mlrun.common.schemas.NotificationSeverity.INFO,
59
59
  runs: typing.Optional[typing.Union[mlrun.lists.RunList, list]] = None,
60
- custom_html: typing.Optional[typing.Optional[str]] = None,
60
+ custom_html: typing.Optional[str] = None,
61
61
  alert: typing.Optional[mlrun.common.schemas.AlertConfig] = None,
62
62
  event_data: typing.Optional[mlrun.common.schemas.Event] = None,
63
63
  ):
@@ -12,6 +12,7 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ import re
15
16
  import typing
16
17
 
17
18
  import aiohttp
@@ -93,7 +94,6 @@ class WebhookNotification(NotificationBase):
93
94
 
94
95
  @staticmethod
95
96
  def _serialize_runs_in_request_body(override_body, runs):
96
- str_parsed_runs = ""
97
97
  runs = runs or []
98
98
 
99
99
  def parse_runs():
@@ -105,22 +105,23 @@ class WebhookNotification(NotificationBase):
105
105
  parsed_run = {
106
106
  "project": run["metadata"]["project"],
107
107
  "name": run["metadata"]["name"],
108
- "host": run["metadata"]["labels"]["host"],
109
108
  "status": {"state": run["status"]["state"]},
110
109
  }
111
- if run["status"].get("error", None):
112
- parsed_run["status"]["error"] = run["status"]["error"]
113
- elif run["status"].get("results", None):
114
- parsed_run["status"]["results"] = run["status"]["results"]
110
+ if host := run["metadata"].get("labels", {}).get("host", ""):
111
+ parsed_run["host"] = host
112
+ if error := run["status"].get("error"):
113
+ parsed_run["status"]["error"] = error
114
+ elif results := run["status"].get("results"):
115
+ parsed_run["status"]["results"] = results
115
116
  parsed_runs.append(parsed_run)
116
117
  return str(parsed_runs)
117
118
 
118
119
  if isinstance(override_body, dict):
119
120
  for key, value in override_body.items():
120
- if "{{ runs }}" or "{{runs}}" in value:
121
- if not str_parsed_runs:
122
- str_parsed_runs = parse_runs()
123
- override_body[key] = value.replace(
124
- "{{ runs }}", str_parsed_runs
125
- ).replace("{{runs}}", str_parsed_runs)
121
+ if re.search(r"{{\s*runs\s*}}", value):
122
+ str_parsed_runs = parse_runs()
123
+ override_body[key] = re.sub(
124
+ r"{{\s*runs\s*}}", str_parsed_runs, value
125
+ )
126
+
126
127
  return override_body