mlrun 1.8.0rc10__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.

Files changed (40) hide show
  1. mlrun/artifacts/document.py +32 -6
  2. mlrun/common/constants.py +1 -0
  3. mlrun/common/formatters/artifact.py +1 -1
  4. mlrun/common/schemas/__init__.py +2 -0
  5. mlrun/common/schemas/model_monitoring/__init__.py +1 -0
  6. mlrun/common/schemas/model_monitoring/constants.py +6 -0
  7. mlrun/common/schemas/model_monitoring/model_endpoints.py +35 -0
  8. mlrun/common/schemas/partition.py +23 -18
  9. mlrun/datastore/vectorstore.py +69 -26
  10. mlrun/db/base.py +14 -0
  11. mlrun/db/httpdb.py +48 -1
  12. mlrun/db/nopdb.py +13 -0
  13. mlrun/execution.py +43 -11
  14. mlrun/feature_store/steps.py +1 -1
  15. mlrun/model_monitoring/api.py +26 -19
  16. mlrun/model_monitoring/applications/_application_steps.py +1 -1
  17. mlrun/model_monitoring/applications/base.py +44 -7
  18. mlrun/model_monitoring/applications/context.py +94 -71
  19. mlrun/projects/pipelines.py +6 -3
  20. mlrun/projects/project.py +95 -17
  21. mlrun/runtimes/nuclio/function.py +2 -1
  22. mlrun/runtimes/nuclio/serving.py +33 -5
  23. mlrun/serving/__init__.py +8 -0
  24. mlrun/serving/merger.py +1 -1
  25. mlrun/serving/remote.py +17 -5
  26. mlrun/serving/routers.py +36 -87
  27. mlrun/serving/server.py +6 -2
  28. mlrun/serving/states.py +162 -13
  29. mlrun/serving/v2_serving.py +39 -82
  30. mlrun/utils/helpers.py +6 -0
  31. mlrun/utils/notifications/notification/base.py +1 -1
  32. mlrun/utils/notifications/notification/webhook.py +13 -12
  33. mlrun/utils/notifications/notification_pusher.py +18 -23
  34. mlrun/utils/version/version.json +2 -2
  35. {mlrun-1.8.0rc10.dist-info → mlrun-1.8.0rc13.dist-info}/METADATA +10 -10
  36. {mlrun-1.8.0rc10.dist-info → mlrun-1.8.0rc13.dist-info}/RECORD +40 -40
  37. {mlrun-1.8.0rc10.dist-info → mlrun-1.8.0rc13.dist-info}/LICENSE +0 -0
  38. {mlrun-1.8.0rc10.dist-info → mlrun-1.8.0rc13.dist-info}/WHEEL +0 -0
  39. {mlrun-1.8.0rc10.dist-info → mlrun-1.8.0rc13.dist-info}/entry_points.txt +0 -0
  40. {mlrun-1.8.0rc10.dist-info → mlrun-1.8.0rc13.dist-info}/top_level.txt +0 -0
@@ -89,12 +89,17 @@ class MLRunLoader:
89
89
  A factory class for creating instances of a dynamically defined document loader.
90
90
 
91
91
  Args:
92
- artifact_key (str): The key for the artifact to be logged.It can include '%%' which will be replaced
93
- by a hex-encoded version of the source path.
92
+ artifact_key (str, optional): The key for the artifact to be logged. Special characters and symbols
93
+ not valid in artifact names will be encoded as their hexadecimal representation. The '%%' pattern
94
+ in the key will be replaced by the hex-encoded version of the source path. Defaults to "doc%%".
94
95
  local_path (str): The source path of the document to be loaded.
95
96
  loader_spec (DocumentLoaderSpec): Specification for the document loader.
96
- producer (Optional[Union[MlrunProject, str, MLClientCtx]], optional): The producer of the document
97
+ producer (Optional[Union[MlrunProject, str, MLClientCtx]], optional): The producer of the document.
98
+ If not specified, will try to get the current MLRun context or project.
99
+ Defaults to None.
97
100
  upload (bool, optional): Flag indicating whether to upload the document.
101
+ labels (Optional[Dict[str, str]], optional): Key-value labels to attach to the artifact. Defaults to None.
102
+ tag (str, optional): Version tag for the artifact. Defaults to "".
98
103
 
99
104
  Returns:
100
105
  DynamicDocumentLoader: An instance of a dynamically defined subclass of BaseLoader.
@@ -146,6 +151,8 @@ class MLRunLoader:
146
151
  artifact_key="doc%%",
147
152
  producer: Optional[Union["MlrunProject", str, "MLClientCtx"]] = None, # noqa: F821
148
153
  upload: bool = False,
154
+ tag: str = "",
155
+ labels: Optional[dict[str, str]] = None,
149
156
  ):
150
157
  # Dynamically import BaseLoader
151
158
  from langchain_community.document_loaders.base import BaseLoader
@@ -158,6 +165,8 @@ class MLRunLoader:
158
165
  artifact_key,
159
166
  producer,
160
167
  upload,
168
+ tag,
169
+ labels,
161
170
  ):
162
171
  self.producer = producer
163
172
  self.artifact_key = (
@@ -168,6 +177,8 @@ class MLRunLoader:
168
177
  self.loader_spec = loader_spec
169
178
  self.local_path = local_path
170
179
  self.upload = upload
180
+ self.tag = tag
181
+ self.labels = labels
171
182
 
172
183
  # Resolve the producer
173
184
  if not self.producer:
@@ -181,9 +192,11 @@ class MLRunLoader:
181
192
  document_loader_spec=self.loader_spec,
182
193
  local_path=self.local_path,
183
194
  upload=self.upload,
195
+ labels=self.labels,
196
+ tag=self.tag,
184
197
  )
185
198
  res = artifact.to_langchain_documents()
186
- yield res[0]
199
+ return res
187
200
 
188
201
  # Return an instance of the dynamically defined subclass
189
202
  instance = DynamicDocumentLoader(
@@ -192,6 +205,8 @@ class MLRunLoader:
192
205
  loader_spec=loader_spec,
193
206
  producer=producer,
194
207
  upload=upload,
208
+ tag=tag,
209
+ labels=labels,
195
210
  )
196
211
  return instance
197
212
 
@@ -257,6 +272,9 @@ class DocumentArtifact(Artifact):
257
272
  METADATA_CHUNK_KEY = "mlrun_chunk"
258
273
  METADATA_ARTIFACT_URI_KEY = "mlrun_object_uri"
259
274
  METADATA_ARTIFACT_TARGET_PATH_KEY = "mlrun_target_path"
275
+ METADATA_ARTIFACT_TAG = "mlrun_tag"
276
+ METADATA_ARTIFACT_KEY = "mlrun_key"
277
+ METADATA_ARTIFACT_PROJECT = "mlrun_project"
260
278
 
261
279
  def __init__(
262
280
  self,
@@ -331,6 +349,10 @@ class DocumentArtifact(Artifact):
331
349
  metadata[self.METADATA_ORIGINAL_SOURCE_KEY] = self.spec.original_source
332
350
  metadata[self.METADATA_SOURCE_KEY] = self.get_source()
333
351
  metadata[self.METADATA_ARTIFACT_URI_KEY] = self.uri
352
+ metadata[self.METADATA_ARTIFACT_TAG] = self.tag or "latest"
353
+ metadata[self.METADATA_ARTIFACT_KEY] = self.key
354
+ metadata[self.METADATA_ARTIFACT_PROJECT] = self.metadata.project
355
+
334
356
  if self.get_target_path():
335
357
  metadata[self.METADATA_ARTIFACT_TARGET_PATH_KEY] = (
336
358
  self.get_target_path()
@@ -346,7 +368,7 @@ class DocumentArtifact(Artifact):
346
368
  idx = idx + 1
347
369
  return results
348
370
 
349
- def collection_add(self, collection_id: str) -> None:
371
+ def collection_add(self, collection_id: str) -> bool:
350
372
  """
351
373
  Add a collection ID to the artifact's collection list.
352
374
 
@@ -361,8 +383,10 @@ class DocumentArtifact(Artifact):
361
383
  """
362
384
  if collection_id not in self.spec.collections:
363
385
  self.spec.collections[collection_id] = "1"
386
+ return True
387
+ return False
364
388
 
365
- def collection_remove(self, collection_id: str) -> None:
389
+ def collection_remove(self, collection_id: str) -> bool:
366
390
  """
367
391
  Remove a collection ID from the artifact's collection list.
368
392
 
@@ -376,3 +400,5 @@ class DocumentArtifact(Artifact):
376
400
  """
377
401
  if collection_id in self.spec.collections:
378
402
  self.spec.collections.pop(collection_id)
403
+ return True
404
+ return False
mlrun/common/constants.py CHANGED
@@ -25,6 +25,7 @@ MYSQL_MEDIUMBLOB_SIZE_BYTES = 16 * 1024 * 1024
25
25
  MLRUN_LABEL_PREFIX = "mlrun/"
26
26
  DASK_LABEL_PREFIX = "dask.org/"
27
27
  NUCLIO_LABEL_PREFIX = "nuclio.io/"
28
+ RESERVED_TAG_NAME_LATEST = "latest"
28
29
 
29
30
 
30
31
  class MLRunInternalLabels:
@@ -32,7 +32,7 @@ class ArtifactFormat(ObjectFormat, mlrun.common.types.StrEnum):
32
32
  [
33
33
  "kind",
34
34
  "metadata",
35
- "status",
35
+ "status.state",
36
36
  "project",
37
37
  "spec.producer",
38
38
  "spec.db_key",
@@ -146,8 +146,10 @@ from .model_monitoring import (
146
146
  GrafanaTable,
147
147
  GrafanaTimeSeriesTarget,
148
148
  ModelEndpoint,
149
+ ModelEndpointCreationStrategy,
149
150
  ModelEndpointList,
150
151
  ModelEndpointMetadata,
152
+ ModelEndpointSchema,
151
153
  ModelEndpointSpec,
152
154
  ModelEndpointStatus,
153
155
  ModelMonitoringMode,
@@ -26,6 +26,7 @@ from .constants import (
26
26
  FileTargetKind,
27
27
  FunctionURI,
28
28
  MetricData,
29
+ ModelEndpointCreationStrategy,
29
30
  ModelEndpointMonitoringMetricType,
30
31
  ModelEndpointSchema,
31
32
  ModelEndpointTarget,
@@ -71,6 +71,12 @@ class ModelEndpointSchema(MonitoringStrEnum):
71
71
  DRIFT_MEASURES = "drift_measures"
72
72
 
73
73
 
74
+ class ModelEndpointCreationStrategy(MonitoringStrEnum):
75
+ INPLACE = "inplace"
76
+ ARCHIVE = "archive"
77
+ OVERWRITE = "overwrite"
78
+
79
+
74
80
  class EventFieldType:
75
81
  FUNCTION_URI = "function_uri"
76
82
  FUNCTION = "function"
@@ -117,6 +117,10 @@ class ModelEndpointMetadata(ObjectMetadata, ModelEndpointParser):
117
117
  endpoint_type: EndpointType = EndpointType.NODE_EP
118
118
  uid: Optional[constr(regex=MODEL_ENDPOINT_ID_PATTERN)]
119
119
 
120
+ @classmethod
121
+ def mutable_fields(cls):
122
+ return ["labels"]
123
+
120
124
 
121
125
  class ModelEndpointSpec(ObjectSpec, ModelEndpointParser):
122
126
  model_uid: Optional[str] = ""
@@ -136,6 +140,21 @@ class ModelEndpointSpec(ObjectSpec, ModelEndpointParser):
136
140
  children_uids: Optional[list[str]] = []
137
141
  monitoring_feature_set_uri: Optional[str] = ""
138
142
 
143
+ @classmethod
144
+ def mutable_fields(cls):
145
+ return [
146
+ "model_uid",
147
+ "model_name",
148
+ "model_db_key",
149
+ "model_tag",
150
+ "model_class",
151
+ "function_uid",
152
+ "feature_names",
153
+ "label_names",
154
+ "children",
155
+ "children_uids",
156
+ ]
157
+
139
158
 
140
159
  class ModelEndpointStatus(ObjectStatus, ModelEndpointParser):
141
160
  state: Optional[str] = "unknown" # will be updated according to the function state
@@ -152,6 +171,14 @@ class ModelEndpointStatus(ObjectStatus, ModelEndpointParser):
152
171
  drift_measures: Optional[dict] = {}
153
172
  drift_measures_timestamp: Optional[datetime] = None
154
173
 
174
+ @classmethod
175
+ def mutable_fields(cls):
176
+ return [
177
+ "monitoring_mode",
178
+ "first_request",
179
+ "last_request",
180
+ ]
181
+
155
182
 
156
183
  class ModelEndpoint(BaseModel):
157
184
  kind: ObjectKind = Field(ObjectKind.model_endpoint, const=True)
@@ -159,6 +186,14 @@ class ModelEndpoint(BaseModel):
159
186
  spec: ModelEndpointSpec
160
187
  status: ModelEndpointStatus
161
188
 
189
+ @classmethod
190
+ def mutable_fields(cls):
191
+ return (
192
+ ModelEndpointMetadata.mutable_fields()
193
+ + ModelEndpointSpec.mutable_fields()
194
+ + ModelEndpointStatus.mutable_fields()
195
+ )
196
+
162
197
  def flat_dict(self) -> dict[str, Any]:
163
198
  """Generate a flattened `ModelEndpoint` dictionary. The flattened dictionary result is important for storing
164
199
  the model endpoint object in the database.
@@ -46,24 +46,23 @@ class PartitionInterval(StrEnum):
46
46
  return timedelta(weeks=1)
47
47
 
48
48
  @classmethod
49
- def from_function(cls, partition_function: str):
49
+ def from_expression(cls, partition_expression: str):
50
50
  """
51
- Returns the corresponding PartitionInterval for a given partition function,
51
+ Returns the corresponding PartitionInterval for a given partition expression,
52
52
  or None if the function is not mapped.
53
53
 
54
- :param partition_function: The partition function to map to an interval.
55
- :return: PartitionInterval corresponding to the function, or None if no match is found.
54
+ :param partition_expression: The partition expression to map to an interval.
55
+ :return: PartitionInterval corresponding to the expression, or `month` if no match is found.
56
56
  """
57
- partition_function_to_partitions_interval = {
58
- "DAY": "DAY",
59
- "DAYOFMONTH": "DAY",
60
- "MONTH": "MONTH",
61
- "YEARWEEK": "YEARWEEK",
62
- }
63
- interval = partition_function_to_partitions_interval.get(partition_function)
64
- if interval and cls.is_valid(interval):
65
- return cls[interval]
66
- raise KeyError(f"Partition function: {partition_function} isn't supported")
57
+
58
+ # Match the provided function string to the correct interval
59
+ partition_expression = partition_expression.upper()
60
+ if "YEARWEEK" in partition_expression:
61
+ return cls.YEARWEEK
62
+ elif "DAYOFMONTH" in partition_expression:
63
+ return cls.DAY
64
+ else:
65
+ return cls.MONTH
67
66
 
68
67
  def get_partition_info(
69
68
  self,
@@ -120,11 +119,17 @@ class PartitionInterval(StrEnum):
120
119
  year, week, _ = current_datetime.isocalendar()
121
120
  return f"{year}{week:02d}"
122
121
 
123
- def get_partition_expression(self):
122
+ def get_partition_expression(self, column_name: str):
124
123
  if self == PartitionInterval.YEARWEEK:
125
- return "YEARWEEK(activation_time, 1)"
126
- else:
127
- return f"{self}(activation_time)"
124
+ return f"YEARWEEK({column_name}, 1)"
125
+ elif self == PartitionInterval.DAY:
126
+ # generates value in format %Y%m%d in mysql
127
+ # mysql query example: `select YEAR(NOW())*10000 + MONTH(NOW())*100 + DAY(NOW());`
128
+ return f"YEAR({column_name}) * 10000 + MONTH({column_name}) * 100 + DAY({column_name})"
129
+ elif self == PartitionInterval.MONTH:
130
+ # generates value in format %Y%m in mysql
131
+ # mysql query example: `select YEAR(NOW())*100 + MONTH(NOW());`
132
+ return f"YEAR({column_name}) * 100 + MONTH({column_name})"
128
133
 
129
134
  def get_number_of_partitions(self, days: int) -> int:
130
135
  # Calculate the number partitions based on given number of days
@@ -19,19 +19,42 @@ from typing import Optional, Union
19
19
  from mlrun.artifacts import DocumentArtifact
20
20
 
21
21
 
22
- def _extract_collection_name(vectorstore: "VectorStore") -> str: # noqa: F821
23
- # List of possible attribute names for collection name
24
- possible_attributes = ["collection_name", "_collection_name"]
22
+ def find_existing_attribute(obj, base_name="name", parent_name="collection"):
23
+ # Define all possible patterns
24
+
25
+ return None
26
+
25
27
 
26
- for attr in possible_attributes:
27
- if hasattr(vectorstore, attr):
28
- collection_name = getattr(vectorstore, attr)
29
- if collection_name:
30
- return collection_name
28
+ def _extract_collection_name(vectorstore: "VectorStore") -> str: # noqa: F821
29
+ patterns = [
30
+ "collection.name",
31
+ "collection._name",
32
+ "_collection.name",
33
+ "_collection._name",
34
+ "collection_name",
35
+ "_collection_name",
36
+ ]
37
+
38
+ def resolve_attribute(obj, pattern):
39
+ if "." in pattern:
40
+ parts = pattern.split(".")
41
+ current = vectorstore
42
+ for part in parts:
43
+ if hasattr(current, part):
44
+ current = getattr(current, part)
45
+ else:
46
+ return None
47
+ return current
48
+ else:
49
+ return getattr(obj, pattern, None)
31
50
 
32
- store_class = vectorstore.__class__.__name__.lower()
33
- if store_class == "mongodbatlasvectorsearch":
34
- return vectorstore.collection.name
51
+ for pattern in patterns:
52
+ try:
53
+ value = resolve_attribute(vectorstore, pattern)
54
+ if value is not None:
55
+ return value
56
+ except (AttributeError, TypeError):
57
+ continue
35
58
 
36
59
  # If we get here, we couldn't find a valid collection name
37
60
  raise ValueError(
@@ -82,6 +105,19 @@ class VectorStoreCollection:
82
105
  # Forward the attribute setting to _collection_impl
83
106
  setattr(self._collection_impl, name, value)
84
107
 
108
+ def _get_mlrun_project_name(self):
109
+ import mlrun
110
+
111
+ if self._mlrun_context and isinstance(
112
+ self._mlrun_context, mlrun.projects.MlrunProject
113
+ ):
114
+ return self._mlrun_context.name
115
+ if self._mlrun_context and isinstance(
116
+ self._mlrun_context, mlrun.execution.MLClientCtx
117
+ ):
118
+ return self._mlrun_context.get_project_object().name
119
+ return None
120
+
85
121
  def delete(self, *args, **kwargs):
86
122
  self._collection_impl.delete(*args, **kwargs)
87
123
 
@@ -106,13 +142,22 @@ class VectorStoreCollection:
106
142
  """
107
143
  if self._mlrun_context:
108
144
  for document in documents:
109
- mlrun_uri = document.metadata.get(
110
- DocumentArtifact.METADATA_ARTIFACT_URI_KEY
145
+ mlrun_key = document.metadata.get(
146
+ DocumentArtifact.METADATA_ARTIFACT_KEY, None
147
+ )
148
+ mlrun_project = document.metadata.get(
149
+ DocumentArtifact.METADATA_ARTIFACT_PROJECT, None
111
150
  )
112
- if mlrun_uri:
113
- artifact = self._mlrun_context.get_store_resource(mlrun_uri)
114
- artifact.collection_add(self.collection_name)
115
- self._mlrun_context.update_artifact(artifact)
151
+
152
+ if mlrun_key and mlrun_project == self._get_mlrun_project_name():
153
+ mlrun_tag = document.metadata.get(
154
+ DocumentArtifact.METADATA_ARTIFACT_TAG, None
155
+ )
156
+ artifact = self._mlrun_context.get_artifact(
157
+ key=mlrun_key, tag=mlrun_tag
158
+ )
159
+ if artifact.collection_add(self.collection_name):
160
+ self._mlrun_context.update_artifact(artifact)
116
161
 
117
162
  return self._collection_impl.add_documents(documents, **kwargs)
118
163
 
@@ -159,8 +204,7 @@ class VectorStoreCollection:
159
204
  )
160
205
  for index, artifact in enumerate(artifacts):
161
206
  documents = artifact.to_langchain_documents(splitter)
162
- artifact.collection_add(self.collection_name)
163
- if self._mlrun_context:
207
+ if artifact.collection_add(self.collection_name) and self._mlrun_context:
164
208
  self._mlrun_context.update_artifact(artifact)
165
209
  if user_ids:
166
210
  num_of_documents = len(documents)
@@ -182,8 +226,8 @@ class VectorStoreCollection:
182
226
  Args:
183
227
  artifact (DocumentArtifact): The artifact from which the current object should be removed.
184
228
  """
185
- artifact.collection_remove(self.collection_name)
186
- if self._mlrun_context:
229
+
230
+ if artifact.collection_remove(self.collection_name) and self._mlrun_context:
187
231
  self._mlrun_context.update_artifact(artifact)
188
232
 
189
233
  def delete_artifacts(self, artifacts: list[DocumentArtifact]):
@@ -201,16 +245,15 @@ class VectorStoreCollection:
201
245
  """
202
246
  store_class = self._collection_impl.__class__.__name__.lower()
203
247
  for artifact in artifacts:
204
- artifact.collection_remove(self.collection_name)
205
- if self._mlrun_context:
248
+ if artifact.collection_remove(self.collection_name) and self._mlrun_context:
206
249
  self._mlrun_context.update_artifact(artifact)
207
250
 
208
251
  if store_class == "milvus":
209
252
  expr = f"{DocumentArtifact.METADATA_SOURCE_KEY} == '{artifact.get_source()}'"
210
- return self._collection_impl.delete(expr=expr)
253
+ self._collection_impl.delete(expr=expr)
211
254
  elif store_class == "chroma":
212
255
  where = {DocumentArtifact.METADATA_SOURCE_KEY: artifact.get_source()}
213
- return self._collection_impl.delete(where=where)
256
+ self._collection_impl.delete(where=where)
214
257
 
215
258
  elif (
216
259
  hasattr(self._collection_impl, "delete")
@@ -222,7 +265,7 @@ class VectorStoreCollection:
222
265
  DocumentArtifact.METADATA_SOURCE_KEY: artifact.get_source()
223
266
  }
224
267
  }
225
- return self._collection_impl.delete(filter=filter)
268
+ self._collection_impl.delete(filter=filter)
226
269
  else:
227
270
  raise NotImplementedError(
228
271
  f"delete_artifacts() operation not supported for {store_class}"
mlrun/db/base.py CHANGED
@@ -23,6 +23,7 @@ import mlrun.common
23
23
  import mlrun.common.formatters
24
24
  import mlrun.common.runtimes.constants
25
25
  import mlrun.common.schemas
26
+ import mlrun.common.schemas.model_monitoring.constants as mm_constants
26
27
  import mlrun.common.schemas.model_monitoring.model_endpoints as mm_endpoints
27
28
  import mlrun.model_monitoring
28
29
 
@@ -58,6 +59,15 @@ class RunDBInterface(ABC):
58
59
  def abort_run(self, uid, project="", iter=0, timeout=45, status_text=""):
59
60
  pass
60
61
 
62
+ @abstractmethod
63
+ def push_run_notifications(
64
+ self,
65
+ uid,
66
+ project="",
67
+ timeout=45,
68
+ ):
69
+ pass
70
+
61
71
  @abstractmethod
62
72
  def read_run(
63
73
  self,
@@ -666,6 +676,9 @@ class RunDBInterface(ABC):
666
676
  def create_model_endpoint(
667
677
  self,
668
678
  model_endpoint: mlrun.common.schemas.ModelEndpoint,
679
+ creation_strategy: Optional[
680
+ mm_constants.ModelEndpointCreationStrategy
681
+ ] = mm_constants.ModelEndpointCreationStrategy.INPLACE,
669
682
  ) -> mlrun.common.schemas.ModelEndpoint:
670
683
  pass
671
684
 
@@ -688,6 +701,7 @@ class RunDBInterface(ABC):
688
701
  function_name: Optional[str] = None,
689
702
  function_tag: Optional[str] = None,
690
703
  model_name: Optional[str] = None,
704
+ model_tag: Optional[str] = None,
691
705
  labels: Optional[Union[str, dict[str, Optional[str]], list[str]]] = None,
692
706
  start: Optional[datetime.datetime] = None,
693
707
  end: Optional[datetime.datetime] = None,
mlrun/db/httpdb.py CHANGED
@@ -35,6 +35,7 @@ import mlrun.common.constants
35
35
  import mlrun.common.formatters
36
36
  import mlrun.common.runtimes
37
37
  import mlrun.common.schemas
38
+ import mlrun.common.schemas.model_monitoring.constants as mm_constants
38
39
  import mlrun.common.schemas.model_monitoring.model_endpoints as mm_endpoints
39
40
  import mlrun.common.types
40
41
  import mlrun.platforms
@@ -755,6 +756,34 @@ class HTTPRunDB(RunDBInterface):
755
756
  )
756
757
  return None
757
758
 
759
+ def push_run_notifications(
760
+ self,
761
+ uid,
762
+ project="",
763
+ timeout=45,
764
+ ):
765
+ """
766
+ Push notifications for a run.
767
+
768
+ :param uid: Unique ID of the run.
769
+ :param project: Project that the run belongs to.
770
+ :returns: :py:class:`~mlrun.common.schemas.BackgroundTask`.
771
+ """
772
+ project = project or config.default_project
773
+
774
+ response = self.api_call(
775
+ "POST",
776
+ path=f"projects/{project}/runs/{uid}/push_notifications",
777
+ error="Failed push notifications",
778
+ timeout=timeout,
779
+ )
780
+ if response.status_code == http.HTTPStatus.ACCEPTED:
781
+ background_task = mlrun.common.schemas.BackgroundTask(**response.json())
782
+ return self._wait_for_background_task_to_reach_terminal_state(
783
+ background_task.metadata.name, project=project
784
+ )
785
+ return None
786
+
758
787
  def read_run(
759
788
  self,
760
789
  uid,
@@ -3582,12 +3611,24 @@ class HTTPRunDB(RunDBInterface):
3582
3611
  def create_model_endpoint(
3583
3612
  self,
3584
3613
  model_endpoint: mlrun.common.schemas.ModelEndpoint,
3614
+ creation_strategy: Optional[
3615
+ mm_constants.ModelEndpointCreationStrategy
3616
+ ] = mm_constants.ModelEndpointCreationStrategy.INPLACE,
3585
3617
  ) -> mlrun.common.schemas.ModelEndpoint:
3586
3618
  """
3587
3619
  Creates a DB record with the given model_endpoint record.
3588
3620
 
3589
3621
  :param model_endpoint: An object representing the model endpoint.
3590
-
3622
+ :param creation_strategy: Strategy for creating or updating the model endpoint:
3623
+ * **overwrite**:
3624
+ 1. If model endpoints with the same name exist, delete the `latest` one.
3625
+ 2. Create a new model endpoint entry and set it as `latest`.
3626
+ * **inplace** (default):
3627
+ 1. If model endpoints with the same name exist, update the `latest` entry.
3628
+ 2. Otherwise, create a new entry.
3629
+ * **archive**:
3630
+ 1. If model endpoints with the same name exist, preserve them.
3631
+ 2. Create a new model endpoint with the same name and set it to `latest`.
3591
3632
  :return: The created model endpoint object.
3592
3633
  """
3593
3634
 
@@ -3596,6 +3637,9 @@ class HTTPRunDB(RunDBInterface):
3596
3637
  method=mlrun.common.types.HTTPMethod.POST,
3597
3638
  path=path,
3598
3639
  body=model_endpoint.json(),
3640
+ params={
3641
+ "creation_strategy": creation_strategy,
3642
+ },
3599
3643
  )
3600
3644
  return mlrun.common.schemas.ModelEndpoint(**response.json())
3601
3645
 
@@ -3637,6 +3681,7 @@ class HTTPRunDB(RunDBInterface):
3637
3681
  function_name: Optional[str] = None,
3638
3682
  function_tag: Optional[str] = None,
3639
3683
  model_name: Optional[str] = None,
3684
+ model_tag: Optional[str] = None,
3640
3685
  labels: Optional[Union[str, dict[str, Optional[str]], list[str]]] = None,
3641
3686
  start: Optional[datetime] = None,
3642
3687
  end: Optional[datetime] = None,
@@ -3653,6 +3698,7 @@ class HTTPRunDB(RunDBInterface):
3653
3698
  :param function_name: The name of the function
3654
3699
  :param function_tag: The tag of the function
3655
3700
  :param model_name: The name of the model
3701
+ :param model_tag: The tag of the model
3656
3702
  :param labels: A list of labels to filter by. (see mlrun.common.schemas.LabelsModel)
3657
3703
  :param start: The start time to filter by.Corresponding to the `created` field.
3658
3704
  :param end: The end time to filter by. Corresponding to the `created` field.
@@ -3671,6 +3717,7 @@ class HTTPRunDB(RunDBInterface):
3671
3717
  params={
3672
3718
  "name": name,
3673
3719
  "model_name": model_name,
3720
+ "model_tag": model_tag,
3674
3721
  "function_name": function_name,
3675
3722
  "function_tag": function_tag,
3676
3723
  "label": labels,
mlrun/db/nopdb.py CHANGED
@@ -20,6 +20,7 @@ import mlrun.alerts
20
20
  import mlrun.common.formatters
21
21
  import mlrun.common.runtimes.constants
22
22
  import mlrun.common.schemas
23
+ import mlrun.common.schemas.model_monitoring.constants as mm_constants
23
24
  import mlrun.errors
24
25
  import mlrun.lists
25
26
  import mlrun.model_monitoring
@@ -75,6 +76,14 @@ class NopDB(RunDBInterface):
75
76
  def abort_run(self, uid, project="", iter=0, timeout=45, status_text=""):
76
77
  pass
77
78
 
79
+ def push_run_notifications(
80
+ self,
81
+ uid,
82
+ project="",
83
+ timeout=45,
84
+ ):
85
+ pass
86
+
78
87
  def list_runtime_resources(
79
88
  self,
80
89
  project: Optional[str] = None,
@@ -575,6 +584,9 @@ class NopDB(RunDBInterface):
575
584
  def create_model_endpoint(
576
585
  self,
577
586
  model_endpoint: mlrun.common.schemas.ModelEndpoint,
587
+ creation_strategy: Optional[
588
+ mm_constants.ModelEndpointCreationStrategy
589
+ ] = mm_constants.ModelEndpointCreationStrategy.INPLACE,
578
590
  ) -> mlrun.common.schemas.ModelEndpoint:
579
591
  pass
580
592
 
@@ -595,6 +607,7 @@ class NopDB(RunDBInterface):
595
607
  function_name: Optional[str] = None,
596
608
  function_tag: Optional[str] = None,
597
609
  model_name: Optional[str] = None,
610
+ model_tag: Optional[str] = None,
598
611
  labels: Optional[Union[str, dict[str, Optional[str]], list[str]]] = None,
599
612
  start: Optional[datetime.datetime] = None,
600
613
  end: Optional[datetime.datetime] = None,