mlrun 1.8.0rc18__py3-none-any.whl → 1.8.0rc19__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.

@@ -51,6 +51,12 @@ class ArtifactCategories(mlrun.common.types.StrEnum):
51
51
  True,
52
52
  )
53
53
 
54
+ @classmethod
55
+ def from_kind(cls, kind: str) -> "ArtifactCategories":
56
+ if kind in [cls.model.value, cls.dataset.value, cls.document.value]:
57
+ return cls(kind)
58
+ return cls.other
59
+
54
60
 
55
61
  class ArtifactIdentifier(pydantic.v1.BaseModel):
56
62
  # artifact kind
@@ -13,6 +13,7 @@
13
13
  # limitations under the License.
14
14
 
15
15
  from .constants import (
16
+ INTERSECT_DICT_KEYS,
16
17
  V3IO_MODEL_MONITORING_DB,
17
18
  ApplicationEvent,
18
19
  ControllerPolicy,
@@ -248,6 +248,12 @@ class ProjectSecretKeys:
248
248
  ]
249
249
 
250
250
 
251
+ class GetEventsFormat(MonitoringStrEnum):
252
+ SINGLE = "single"
253
+ SEPARATION = "separation"
254
+ INTERSECTION = "intersection"
255
+
256
+
251
257
  class ModelEndpointTargetSchemas(MonitoringStrEnum):
252
258
  V3IO = "v3io"
253
259
  MYSQL = "mysql"
@@ -448,3 +454,8 @@ FQN_REGEX = re.compile(FQN_PATTERN)
448
454
  PROJECT_PATTERN = r"^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$"
449
455
 
450
456
  MODEL_ENDPOINT_ID_PATTERN = r"^[a-zA-Z0-9_-]+$"
457
+
458
+ INTERSECT_DICT_KEYS = {
459
+ ModelEndpointMonitoringMetricType.METRIC: "intersect_metrics",
460
+ ModelEndpointMonitoringMetricType.RESULT: "intersect_results",
461
+ }
@@ -259,12 +259,12 @@ class ModelEndpointMonitoringMetric(BaseModel):
259
259
 
260
260
  def __init__(self, **kwargs):
261
261
  super().__init__(**kwargs)
262
- self.full_name = _compose_full_name(
262
+ self.full_name = compose_full_name(
263
263
  project=self.project, app=self.app, name=self.name, type=self.type
264
264
  )
265
265
 
266
266
 
267
- def _compose_full_name(
267
+ def compose_full_name(
268
268
  *,
269
269
  project: str,
270
270
  app: str,
mlrun/config.py CHANGED
@@ -83,8 +83,8 @@ default_config = {
83
83
  "images_to_enrich_registry": "^mlrun/*,python:3.9",
84
84
  "kfp_url": "",
85
85
  "kfp_ttl": "14400", # KFP ttl in sec, after that completed PODs will be deleted
86
- "kfp_image": "mlrun/mlrun-kfp", # image to use for KFP runner (defaults to mlrun/mlrun-kfp)
87
- "dask_kfp_image": "mlrun/ml-base", # image to use for dask KFP runner (defaults to mlrun/ml-base)
86
+ "kfp_image": "mlrun/mlrun-kfp", # image to use for KFP runner
87
+ "dask_kfp_image": "mlrun/ml-base", # image to use for dask KFP runner
88
88
  "igz_version": "", # the version of the iguazio system the API is running on
89
89
  "iguazio_api_url": "", # the url to iguazio api
90
90
  "spark_app_image": "", # image to use for spark operator app runtime
mlrun/db/base.py CHANGED
@@ -337,6 +337,15 @@ class RunDBInterface(ABC):
337
337
  ) -> list[mm_endpoints.ModelEndpointMonitoringMetric]:
338
338
  pass
339
339
 
340
+ def get_metrics_by_multiple_endpoints(
341
+ self,
342
+ project: str,
343
+ endpoint_ids: Union[str, list[str]],
344
+ type: Literal["results", "metrics", "all"] = "all",
345
+ events_format: mm_constants.GetEventsFormat = mm_constants.GetEventsFormat.SEPARATION,
346
+ ) -> dict[str, list[mm_endpoints.ModelEndpointMonitoringMetric]]:
347
+ pass
348
+
340
349
  @abstractmethod
341
350
  def delete_project(
342
351
  self,
mlrun/db/httpdb.py CHANGED
@@ -3524,6 +3524,48 @@ class HTTPRunDB(RunDBInterface):
3524
3524
  list[mm_endpoints.ModelEndpointMonitoringMetric], monitoring_metrics
3525
3525
  )
3526
3526
 
3527
+ def get_metrics_by_multiple_endpoints(
3528
+ self,
3529
+ project: str,
3530
+ endpoint_ids: Union[str, list[str]],
3531
+ type: Literal["results", "metrics", "all"] = "all",
3532
+ events_format: mm_constants.GetEventsFormat = mm_constants.GetEventsFormat.SEPARATION,
3533
+ ) -> dict[str, list[mm_endpoints.ModelEndpointMonitoringMetric]]:
3534
+ """Get application metrics/results by endpoint id and project.
3535
+
3536
+ :param project: The name of the project.
3537
+ :param endpoint_ids: The unique id of the model endpoint. Can be a single id or a list of ids.
3538
+ :param type: The type of the metrics to return. "all" means "results" and "metrics".
3539
+ :param events_format: response format:
3540
+
3541
+ separation: {"mep_id1":[...], "mep_id2":[...]}
3542
+ intersection {"intersect_metrics":[], "intersect_results":[]}
3543
+ :return: A dictionary of application metrics and/or results for the model endpoints formatted by events_format.
3544
+ """
3545
+ path = f"projects/{project}/model-endpoints/metrics"
3546
+ params = {
3547
+ "type": type,
3548
+ "endpoint-id": endpoint_ids,
3549
+ "events_format": events_format,
3550
+ }
3551
+ error_message = (
3552
+ f"Failed to get model monitoring metrics,"
3553
+ f" endpoint_ids: {endpoint_ids}, project: {project}"
3554
+ )
3555
+ response = self.api_call(
3556
+ mlrun.common.types.HTTPMethod.GET,
3557
+ path,
3558
+ error_message,
3559
+ params=params,
3560
+ )
3561
+ monitoring_metrics_by_endpoint = response.json()
3562
+ parsed_metrics_by_endpoint = {}
3563
+ for endpoint, metrics in monitoring_metrics_by_endpoint.items():
3564
+ parsed_metrics_by_endpoint[endpoint] = parse_obj_as(
3565
+ list[mm_endpoints.ModelEndpointMonitoringMetric], metrics
3566
+ )
3567
+ return parsed_metrics_by_endpoint
3568
+
3527
3569
  def create_user_secrets(
3528
3570
  self,
3529
3571
  user: str,
@@ -234,14 +234,14 @@ class TSDBConnector(ABC):
234
234
  @abstractmethod
235
235
  def get_metrics_metadata(
236
236
  self,
237
- endpoint_id: str,
237
+ endpoint_id: typing.Union[str, list[str]],
238
238
  start: typing.Optional[datetime] = None,
239
239
  end: typing.Optional[datetime] = None,
240
240
  ) -> pd.DataFrame:
241
241
  """
242
- Fetches distinct metrics metadata from the metrics TSDB table for a specified model endpoint.
242
+ Fetches distinct metrics metadata from the metrics TSDB table for a specified model endpoints.
243
243
 
244
- :param endpoint_id: The model endpoint identifier.
244
+ :param endpoint_id: The model endpoint identifier. Can be a single id or a list of ids.
245
245
  :param start: The start time of the query.
246
246
  :param end: The end time of the query.
247
247
 
@@ -252,14 +252,14 @@ class TSDBConnector(ABC):
252
252
  @abstractmethod
253
253
  def get_results_metadata(
254
254
  self,
255
- endpoint_id: str,
255
+ endpoint_id: typing.Union[str, list[str]],
256
256
  start: typing.Optional[datetime] = None,
257
257
  end: typing.Optional[datetime] = None,
258
258
  ) -> pd.DataFrame:
259
259
  """
260
- Fetches distinct results metadata from the app-results TSDB table for a specified model endpoint.
260
+ Fetches distinct results metadata from the app-results TSDB table for a specified model endpoints.
261
261
 
262
- :param endpoint_id: The model endpoint identifier.
262
+ :param endpoint_id: The model endpoint identifier. Can be a single id or a list of ids.
263
263
  :param start: The start time of the query.
264
264
  :param end: The end time of the query.
265
265
 
@@ -341,7 +341,7 @@ class TSDBConnector(ABC):
341
341
  logger.debug("No metrics", missing_metrics=metrics_without_data.keys())
342
342
  grouped = []
343
343
  for (app_name, name), sub_df in grouped:
344
- full_name = mlrun.model_monitoring.helpers._compose_full_name(
344
+ full_name = mm_schemas.model_endpoints.compose_full_name(
345
345
  project=project,
346
346
  app=app_name,
347
347
  name=name,
@@ -410,7 +410,7 @@ class TSDBConnector(ABC):
410
410
  result_kind = mlrun.model_monitoring.db.tsdb.helpers._get_result_kind(
411
411
  sub_df
412
412
  )
413
- full_name = mlrun.model_monitoring.helpers._compose_full_name(
413
+ full_name = mm_schemas.model_endpoints.compose_full_name(
414
414
  project=project, app=app_name, name=name
415
415
  )
416
416
  try:
@@ -467,6 +467,7 @@ class TSDBConnector(ABC):
467
467
 
468
468
  :return: A list of mm metrics objects.
469
469
  """
470
+
470
471
  return list(
471
472
  map(
472
473
  lambda record: mm_schemas.ModelEndpointMonitoringMetric(
@@ -481,6 +482,113 @@ class TSDBConnector(ABC):
481
482
  )
482
483
  )
483
484
 
485
+ @staticmethod
486
+ def df_to_metrics_grouped_dict(
487
+ *,
488
+ df: pd.DataFrame,
489
+ project: str,
490
+ type: str,
491
+ ) -> dict[str, list[mm_schemas.ModelEndpointMonitoringMetric]]:
492
+ """
493
+ Parse a DataFrame of metrics from the TSDB into a grouped mm metrics objects by endpoint_id.
494
+
495
+ :param df: The DataFrame to parse.
496
+ :param project: The project name.
497
+ :param type: The type of the metrics (either "result" or "metric").
498
+
499
+ :return: A grouped dict of mm metrics/results, using model_endpoints_ids as keys.
500
+ """
501
+
502
+ if df.empty:
503
+ return {}
504
+
505
+ grouped_by_fields = [mm_schemas.WriterEvent.APPLICATION_NAME]
506
+ if type == "result":
507
+ name_column = mm_schemas.ResultData.RESULT_NAME
508
+ grouped_by_fields.append(mm_schemas.ResultData.RESULT_KIND)
509
+ else:
510
+ name_column = mm_schemas.MetricData.METRIC_NAME
511
+
512
+ grouped_by_fields.append(name_column)
513
+ # groupby has different behavior for category columns
514
+ df["endpoint_id"] = df["endpoint_id"].astype(str)
515
+ grouped_by_df = df.groupby("endpoint_id")
516
+ grouped_dict = grouped_by_df.apply(
517
+ lambda group: list(
518
+ map(
519
+ lambda record: mm_schemas.ModelEndpointMonitoringMetric(
520
+ project=project,
521
+ type=type,
522
+ app=record.get(mm_schemas.WriterEvent.APPLICATION_NAME),
523
+ name=record.get(name_column),
524
+ **{"kind": record.get(mm_schemas.ResultData.RESULT_KIND)}
525
+ if type == "result"
526
+ else {},
527
+ ),
528
+ group[grouped_by_fields].to_dict(orient="records"),
529
+ )
530
+ )
531
+ ).to_dict()
532
+ return grouped_dict
533
+
534
+ @staticmethod
535
+ def df_to_events_intersection_dict(
536
+ *,
537
+ df: pd.DataFrame,
538
+ project: str,
539
+ type: typing.Union[str, mm_schemas.ModelEndpointMonitoringMetricType],
540
+ ) -> dict[str, list[mm_schemas.ModelEndpointMonitoringMetric]]:
541
+ """
542
+ Parse a DataFrame of metrics from the TSDB into a dict of intersection metrics/results by name and application
543
+ (and kind in results).
544
+
545
+ :param df: The DataFrame to parse.
546
+ :param project: The project name.
547
+ :param type: The type of the metrics (either "result" or "metric").
548
+
549
+ :return: A dictionary where the key is event type (as defined by `INTERSECT_DICT_KEYS`),
550
+ and the value is a list containing the intersect metrics or results across all endpoint IDs.
551
+
552
+ For example:
553
+ {
554
+ "intersect_metrics": [...]
555
+ }
556
+ """
557
+ dict_key = mm_schemas.INTERSECT_DICT_KEYS[type]
558
+ metrics = []
559
+ if df.empty:
560
+ return {dict_key: []}
561
+
562
+ columns_to_zip = [mm_schemas.WriterEvent.APPLICATION_NAME]
563
+
564
+ if type == "result":
565
+ name_column = mm_schemas.ResultData.RESULT_NAME
566
+ columns_to_zip.append(mm_schemas.ResultData.RESULT_KIND)
567
+ else:
568
+ name_column = mm_schemas.MetricData.METRIC_NAME
569
+ columns_to_zip.insert(1, name_column)
570
+
571
+ # groupby has different behavior for category columns
572
+ df["endpoint_id"] = df["endpoint_id"].astype(str)
573
+ df["event_values"] = list(zip(*[df[col] for col in columns_to_zip]))
574
+ grouped_by_event_values = df.groupby("endpoint_id")["event_values"].apply(set)
575
+ common_event_values_combinations = set.intersection(*grouped_by_event_values)
576
+ result_kind = None
577
+ for data in common_event_values_combinations:
578
+ application_name, event_name = data[0], data[1]
579
+ if len(data) > 2: # in result case
580
+ result_kind = data[2]
581
+ metrics.append(
582
+ mm_schemas.ModelEndpointMonitoringMetric(
583
+ project=project,
584
+ type=type,
585
+ app=application_name,
586
+ name=event_name,
587
+ kind=result_kind,
588
+ )
589
+ )
590
+ return {dict_key: metrics}
591
+
484
592
  @staticmethod
485
593
  def _get_start_end(
486
594
  start: typing.Union[datetime, None],
@@ -13,7 +13,7 @@
13
13
  # limitations under the License.
14
14
 
15
15
  import typing
16
- from datetime import datetime, timedelta, timezone
16
+ from datetime import datetime, timedelta
17
17
 
18
18
  import pandas as pd
19
19
  import taosws
@@ -164,6 +164,17 @@ class TDEngineConnector(TSDBConnector):
164
164
  def _convert_to_datetime(val: typing.Union[str, datetime]) -> datetime:
165
165
  return datetime.fromisoformat(val) if isinstance(val, str) else val
166
166
 
167
+ @staticmethod
168
+ def _get_endpoint_filter(endpoint_id: typing.Union[str, list[str]]):
169
+ if isinstance(endpoint_id, str):
170
+ return f"endpoint_id='{endpoint_id}'"
171
+ elif isinstance(endpoint_id, list):
172
+ return f"endpoint_id IN({str(endpoint_id)[1:-1]}) "
173
+ else:
174
+ raise mlrun.errors.MLRunInvalidArgumentError(
175
+ "Invalid 'endpoint_id' filter: must be a string or a list."
176
+ )
177
+
167
178
  def apply_monitoring_stream_steps(self, graph, **kwarg):
168
179
  """
169
180
  Apply TSDB steps on the provided monitoring graph. Throughout these steps, the graph stores live data of
@@ -542,12 +553,11 @@ class TDEngineConnector(TSDBConnector):
542
553
  },
543
554
  inplace=True,
544
555
  )
545
- df[mm_schemas.EventFieldType.LAST_REQUEST] = df[
546
- mm_schemas.EventFieldType.LAST_REQUEST
547
- ].map(
548
- lambda last_request: datetime.strptime(
549
- last_request, "%Y-%m-%d %H:%M:%S.%f %z"
550
- ).astimezone(tz=timezone.utc)
556
+ df[mm_schemas.EventFieldType.LAST_REQUEST] = pd.to_datetime(
557
+ df[mm_schemas.EventFieldType.LAST_REQUEST],
558
+ errors="coerce",
559
+ format="ISO8601",
560
+ utc=True,
551
561
  )
552
562
  return df
553
563
 
@@ -588,7 +598,7 @@ class TDEngineConnector(TSDBConnector):
588
598
 
589
599
  def get_metrics_metadata(
590
600
  self,
591
- endpoint_id: str,
601
+ endpoint_id: typing.Union[str, list[str]],
592
602
  start: typing.Optional[datetime] = None,
593
603
  end: typing.Optional[datetime] = None,
594
604
  ) -> pd.DataFrame:
@@ -602,11 +612,12 @@ class TDEngineConnector(TSDBConnector):
602
612
  mm_schemas.MetricData.METRIC_NAME,
603
613
  mm_schemas.EventFieldType.ENDPOINT_ID,
604
614
  ],
605
- filter_query=f"endpoint_id='{endpoint_id}'",
615
+ filter_query=self._get_endpoint_filter(endpoint_id=endpoint_id),
606
616
  timestamp_column=mm_schemas.WriterEvent.END_INFER_TIME,
607
617
  group_by=[
608
618
  mm_schemas.WriterEvent.APPLICATION_NAME,
609
619
  mm_schemas.MetricData.METRIC_NAME,
620
+ mm_schemas.EventFieldType.ENDPOINT_ID,
610
621
  ],
611
622
  agg_funcs=["last"],
612
623
  )
@@ -624,7 +635,7 @@ class TDEngineConnector(TSDBConnector):
624
635
 
625
636
  def get_results_metadata(
626
637
  self,
627
- endpoint_id: str,
638
+ endpoint_id: typing.Union[str, list[str]],
628
639
  start: typing.Optional[datetime] = None,
629
640
  end: typing.Optional[datetime] = None,
630
641
  ) -> pd.DataFrame:
@@ -639,11 +650,12 @@ class TDEngineConnector(TSDBConnector):
639
650
  mm_schemas.ResultData.RESULT_KIND,
640
651
  mm_schemas.EventFieldType.ENDPOINT_ID,
641
652
  ],
642
- filter_query=f"endpoint_id='{endpoint_id}'",
653
+ filter_query=self._get_endpoint_filter(endpoint_id=endpoint_id),
643
654
  timestamp_column=mm_schemas.WriterEvent.END_INFER_TIME,
644
655
  group_by=[
645
656
  mm_schemas.WriterEvent.APPLICATION_NAME,
646
657
  mm_schemas.ResultData.RESULT_NAME,
658
+ mm_schemas.EventFieldType.ENDPOINT_ID,
647
659
  ],
648
660
  agg_funcs=["last"],
649
661
  )
@@ -33,6 +33,8 @@ _TSDB_BE = "tsdb"
33
33
  _TSDB_RATE = "1/s"
34
34
  _CONTAINER = "users"
35
35
 
36
+ V3IO_MEPS_LIMIT = 50 # TODO remove limitation after fixing ML-8886
37
+
36
38
 
37
39
  def _is_no_schema_error(exc: v3io_frames.Error) -> bool:
38
40
  """
@@ -577,6 +579,21 @@ class V3IOTSDBConnector(TSDBConnector):
577
579
  token=v3io_access_key,
578
580
  )
579
581
 
582
+ @staticmethod
583
+ def _get_endpoint_filter(endpoint_id: Union[str, list[str]]):
584
+ if isinstance(endpoint_id, str):
585
+ return f"endpoint_id=='{endpoint_id}'"
586
+ elif isinstance(endpoint_id, list):
587
+ if len(endpoint_id) > V3IO_MEPS_LIMIT:
588
+ raise mlrun.errors.MLRunInvalidArgumentError(
589
+ f"Filtering more than {V3IO_MEPS_LIMIT} model endpoints in the V3IO connector is not supported."
590
+ )
591
+ return f"endpoint_id IN({str(endpoint_id)[1:-1]}) "
592
+ else:
593
+ raise mlrun.errors.MLRunInvalidArgumentError(
594
+ f"Invalid 'endpoint_id' filter: must be a string or a list, endpoint_id: {endpoint_id}"
595
+ )
596
+
580
597
  def read_metrics_data(
581
598
  self,
582
599
  *,
@@ -813,17 +830,18 @@ class V3IOTSDBConnector(TSDBConnector):
813
830
 
814
831
  def get_metrics_metadata(
815
832
  self,
816
- endpoint_id: str,
833
+ endpoint_id: Union[str, list[str]],
817
834
  start: Optional[datetime] = None,
818
835
  end: Optional[datetime] = None,
819
836
  ) -> pd.DataFrame:
820
837
  start, end = self._get_start_end(start, end)
838
+ filter_query = self._get_endpoint_filter(endpoint_id=endpoint_id)
821
839
  df = self._get_records(
822
840
  table=mm_schemas.V3IOTSDBTables.METRICS,
823
841
  start=start,
824
842
  end=end,
825
843
  columns=[mm_schemas.MetricData.METRIC_VALUE],
826
- filter_query=f"endpoint_id=='{endpoint_id}'",
844
+ filter_query=filter_query,
827
845
  agg_funcs=["last"],
828
846
  )
829
847
  if not df.empty:
@@ -834,11 +852,12 @@ class V3IOTSDBConnector(TSDBConnector):
834
852
 
835
853
  def get_results_metadata(
836
854
  self,
837
- endpoint_id: str,
855
+ endpoint_id: Union[str, list[str]],
838
856
  start: Optional[datetime] = None,
839
857
  end: Optional[datetime] = None,
840
858
  ) -> pd.DataFrame:
841
859
  start, end = self._get_start_end(start, end)
860
+ filter_query = self._get_endpoint_filter(endpoint_id=endpoint_id)
842
861
  df = self._get_records(
843
862
  table=mm_schemas.V3IOTSDBTables.APP_RESULTS,
844
863
  start=start,
@@ -846,7 +865,7 @@ class V3IOTSDBConnector(TSDBConnector):
846
865
  columns=[
847
866
  mm_schemas.ResultData.RESULT_KIND,
848
867
  ],
849
- filter_query=f"endpoint_id=='{endpoint_id}'",
868
+ filter_query=filter_query,
850
869
  agg_funcs=["last"],
851
870
  )
852
871
  if not df.empty:
@@ -32,7 +32,7 @@ import mlrun.utils.helpers
32
32
  from mlrun.common.schemas import ModelEndpoint
33
33
  from mlrun.common.schemas.model_monitoring.model_endpoints import (
34
34
  ModelEndpointMonitoringMetric,
35
- _compose_full_name,
35
+ compose_full_name,
36
36
  )
37
37
  from mlrun.utils import logger
38
38
 
@@ -450,7 +450,7 @@ def get_default_result_instance_fqn(model_endpoint_id: str) -> str:
450
450
 
451
451
 
452
452
  def get_invocations_fqn(project: str) -> str:
453
- return _compose_full_name(
453
+ return compose_full_name(
454
454
  project=project,
455
455
  app=mm_constants.SpecialApps.MLRUN_INFRA,
456
456
  name=mm_constants.PredictionsQueryConstants.INVOCATIONS,
mlrun/projects/project.py CHANGED
@@ -2137,18 +2137,23 @@ class MlrunProject(ModelObj):
2137
2137
  db = mlrun.db.get_run_db(secrets=self._secrets)
2138
2138
  matching_results = []
2139
2139
  alerts = []
2140
- # TODO: Refactor to use a single request to improve performance at scale, ML-8473
2141
- for endpoint in endpoints.endpoints:
2142
- results_by_endpoint = db.get_model_endpoint_monitoring_metrics(
2143
- project=self.name, endpoint_id=endpoint.metadata.uid, type="results"
2144
- )
2140
+ endpoint_ids = [endpoint.metadata.uid for endpoint in endpoints.endpoints]
2141
+ # using separation to group by endpoint IDs:
2142
+ # {"mep_id1": [...], "mep_id2": [...]}
2143
+ results_by_endpoint = db.get_metrics_by_multiple_endpoints(
2144
+ project=self.name,
2145
+ endpoint_ids=endpoint_ids,
2146
+ type="results",
2147
+ events_format=mm_constants.GetEventsFormat.SEPARATION,
2148
+ )
2149
+ for endpoint_uid, results in results_by_endpoint.items():
2145
2150
  results_fqn_by_endpoint = [
2146
2151
  get_result_instance_fqn(
2147
- model_endpoint_id=endpoint.metadata.uid,
2152
+ model_endpoint_id=endpoint_uid,
2148
2153
  app_name=result.app,
2149
2154
  result_name=result.name,
2150
2155
  )
2151
- for result in results_by_endpoint
2156
+ for result in results
2152
2157
  ]
2153
2158
  matching_results += filter_results_by_regex(
2154
2159
  existing_result_names=results_fqn_by_endpoint,
mlrun/utils/helpers.py CHANGED
@@ -23,6 +23,7 @@ import os
23
23
  import re
24
24
  import string
25
25
  import sys
26
+ import traceback
26
27
  import typing
27
28
  import uuid
28
29
  import warnings
@@ -44,11 +45,16 @@ from pandas import Timedelta, Timestamp
44
45
  from yaml.representer import RepresenterError
45
46
 
46
47
  import mlrun
48
+ import mlrun.common.constants as mlrun_constants
47
49
  import mlrun.common.helpers
50
+ import mlrun.common.runtimes.constants as runtimes_constants
48
51
  import mlrun.common.schemas
49
52
  import mlrun.errors
50
53
  import mlrun.utils.regex
51
54
  import mlrun.utils.version.version
55
+ import mlrun_pipelines.common.constants
56
+ import mlrun_pipelines.models
57
+ import mlrun_pipelines.utils
52
58
  from mlrun.common.constants import MYSQL_MEDIUMBLOB_SIZE_BYTES
53
59
  from mlrun.config import config
54
60
  from mlrun_pipelines.models import PipelineRun
@@ -1904,3 +1910,131 @@ def join_urls(base_url: Optional[str], path: Optional[str]) -> str:
1904
1910
  if base_url is None:
1905
1911
  base_url = ""
1906
1912
  return f"{base_url.rstrip('/')}/{path.lstrip('/')}" if path else base_url
1913
+
1914
+
1915
+ class Workflow:
1916
+ @staticmethod
1917
+ def get_workflow_steps(workflow_id: str, project: str) -> list:
1918
+ steps = []
1919
+ db = mlrun.get_run_db()
1920
+
1921
+ def _add_run_step(_step: mlrun_pipelines.models.PipelineStep):
1922
+ try:
1923
+ _run = db.list_runs(
1924
+ project=project,
1925
+ labels=f"{mlrun_constants.MLRunInternalLabels.runner_pod}={_step.node_name}",
1926
+ )[0]
1927
+ except IndexError:
1928
+ _run = {
1929
+ "metadata": {
1930
+ "name": _step.display_name,
1931
+ "project": project,
1932
+ },
1933
+ }
1934
+ _run["step_kind"] = _step.step_type
1935
+ if _step.skipped:
1936
+ _run.setdefault("status", {})["state"] = (
1937
+ runtimes_constants.RunStates.skipped
1938
+ )
1939
+ steps.append(_run)
1940
+
1941
+ def _add_deploy_function_step(_step: mlrun_pipelines.models.PipelineStep):
1942
+ project, name, hash_key = Workflow._extract_function_uri(
1943
+ _step.get_annotation("mlrun/function-uri")
1944
+ )
1945
+ if name:
1946
+ try:
1947
+ function = db.get_function(
1948
+ project=project, name=name, hash_key=hash_key
1949
+ )
1950
+ except mlrun.errors.MLRunNotFoundError:
1951
+ # If the function is not found (if build failed for example), we will create a dummy
1952
+ # function object for the notification to display the function name
1953
+ function = {
1954
+ "metadata": {
1955
+ "name": name,
1956
+ "project": project,
1957
+ "hash_key": hash_key,
1958
+ },
1959
+ }
1960
+ pod_phase = _step.phase
1961
+ if _step.skipped:
1962
+ state = mlrun.common.schemas.FunctionState.skipped
1963
+ else:
1964
+ state = runtimes_constants.PodPhases.pod_phase_to_run_state(
1965
+ pod_phase
1966
+ )
1967
+ function["status"] = {"state": state}
1968
+ if isinstance(function["metadata"].get("updated"), datetime.datetime):
1969
+ function["metadata"]["updated"] = function["metadata"][
1970
+ "updated"
1971
+ ].isoformat()
1972
+ function["step_kind"] = _step.step_type
1973
+ steps.append(function)
1974
+
1975
+ step_methods = {
1976
+ mlrun_pipelines.common.constants.PipelineRunType.run: _add_run_step,
1977
+ mlrun_pipelines.common.constants.PipelineRunType.build: _add_deploy_function_step,
1978
+ mlrun_pipelines.common.constants.PipelineRunType.deploy: _add_deploy_function_step,
1979
+ }
1980
+
1981
+ if not workflow_id:
1982
+ return steps
1983
+
1984
+ workflow_manifest = Workflow._get_workflow_manifest(workflow_id)
1985
+ if not workflow_manifest:
1986
+ return steps
1987
+
1988
+ try:
1989
+ for step in workflow_manifest.get_steps():
1990
+ step_method = step_methods.get(step.step_type)
1991
+ if step_method:
1992
+ step_method(step)
1993
+ return steps
1994
+ except Exception:
1995
+ # If we fail to read the pipeline steps, we will return the list of runs that have the same workflow id
1996
+ logger.warning(
1997
+ "Failed to extract workflow steps from workflow manifest, "
1998
+ "returning all runs with the workflow id label",
1999
+ workflow_id=workflow_id,
2000
+ traceback=traceback.format_exc(),
2001
+ )
2002
+ return db.list_runs(
2003
+ project=project,
2004
+ labels=f"workflow={workflow_id}",
2005
+ )
2006
+
2007
+ @staticmethod
2008
+ def _extract_function_uri(function_uri: str) -> tuple[str, str, str]:
2009
+ """
2010
+ Extract the project, name, and hash key from a function uri.
2011
+ Examples:
2012
+ - "project/name@hash_key" returns project, name, hash_key
2013
+ - "project/name returns" project, name, ""
2014
+ """
2015
+ project, name, hash_key = None, None, None
2016
+ hashed_pattern = r"^(.+)/(.+)@(.+)$"
2017
+ pattern = r"^(.+)/(.+)$"
2018
+ match = re.match(hashed_pattern, function_uri)
2019
+ if match:
2020
+ project, name, hash_key = match.groups()
2021
+ else:
2022
+ match = re.match(pattern, function_uri)
2023
+ if match:
2024
+ project, name = match.groups()
2025
+ hash_key = ""
2026
+ return project, name, hash_key
2027
+
2028
+ @staticmethod
2029
+ def _get_workflow_manifest(
2030
+ workflow_id: str,
2031
+ ) -> typing.Optional[mlrun_pipelines.models.PipelineManifest]:
2032
+ kfp_client = mlrun_pipelines.utils.get_client(mlrun.mlconf.kfp_url)
2033
+
2034
+ # arbitrary timeout of 5 seconds, the workflow should be done by now
2035
+ kfp_run = kfp_client.wait_for_run_completion(workflow_id, 5)
2036
+ if not kfp_run:
2037
+ return None
2038
+
2039
+ kfp_run = mlrun_pipelines.models.PipelineRun(kfp_run)
2040
+ return kfp_run.workflow_manifest()
@@ -118,6 +118,9 @@ class WebhookNotification(NotificationBase):
118
118
 
119
119
  if isinstance(override_body, dict):
120
120
  for key, value in override_body.items():
121
+ if not isinstance(value, str):
122
+ # If the value is not a string, we don't want to parse it
123
+ continue
121
124
  if re.search(r"{{\s*runs\s*}}", value):
122
125
  str_parsed_runs = parse_runs()
123
126
  override_body[key] = re.sub(
@@ -15,7 +15,6 @@
15
15
  import asyncio
16
16
  import datetime
17
17
  import os
18
- import re
19
18
  import traceback
20
19
  import typing
21
20
  from concurrent.futures import ThreadPoolExecutor
@@ -31,11 +30,7 @@ import mlrun.model
31
30
  import mlrun.utils.helpers
32
31
  import mlrun.utils.notifications.notification as notification_module
33
32
  import mlrun.utils.notifications.notification.base as base
34
- import mlrun_pipelines.common.constants
35
- import mlrun_pipelines.common.ops
36
- import mlrun_pipelines.models
37
- import mlrun_pipelines.utils
38
- from mlrun.utils import logger
33
+ from mlrun.utils import Workflow, logger
39
34
  from mlrun.utils.condition_evaluator import evaluate_condition_in_separate_process
40
35
 
41
36
 
@@ -283,7 +278,9 @@ class NotificationPusher(_NotificationPusherBase):
283
278
  custom_message = (
284
279
  f" (workflow: {run.metadata.labels['workflow']}){custom_message}"
285
280
  )
286
- runs.extend(self.get_workflow_steps(run))
281
+ project = run.metadata.project
282
+ workflow_id = run.status.results.get("workflow_id", None)
283
+ runs.extend(Workflow.get_workflow_steps(workflow_id, project))
287
284
 
288
285
  message = (
289
286
  self.messages.get(run.state(), "").format(resource=resource)
@@ -442,131 +439,6 @@ class NotificationPusher(_NotificationPusherBase):
442
439
  mask_params=False,
443
440
  )
444
441
 
445
- def get_workflow_steps(self, run: mlrun.model.RunObject) -> list:
446
- steps = []
447
- db = mlrun.get_run_db()
448
-
449
- def _add_run_step(_step: mlrun_pipelines.models.PipelineStep):
450
- try:
451
- _run = db.list_runs(
452
- project=run.metadata.project,
453
- labels=f"{mlrun_constants.MLRunInternalLabels.runner_pod}={_step.node_name}",
454
- )[0]
455
- except IndexError:
456
- _run = {
457
- "metadata": {
458
- "name": _step.display_name,
459
- "project": run.metadata.project,
460
- },
461
- }
462
- _run["step_kind"] = _step.step_type
463
- if _step.skipped:
464
- _run.setdefault("status", {})["state"] = (
465
- runtimes_constants.RunStates.skipped
466
- )
467
- steps.append(_run)
468
-
469
- def _add_deploy_function_step(_step: mlrun_pipelines.models.PipelineStep):
470
- project, name, hash_key = self._extract_function_uri(
471
- _step.get_annotation("mlrun/function-uri")
472
- )
473
- if name:
474
- try:
475
- function = db.get_function(
476
- project=project, name=name, hash_key=hash_key
477
- )
478
- except mlrun.errors.MLRunNotFoundError:
479
- # If the function is not found (if build failed for example), we will create a dummy
480
- # function object for the notification to display the function name
481
- function = {
482
- "metadata": {
483
- "name": name,
484
- "project": project,
485
- "hash_key": hash_key,
486
- },
487
- }
488
- pod_phase = _step.phase
489
- if _step.skipped:
490
- state = mlrun.common.schemas.FunctionState.skipped
491
- else:
492
- state = runtimes_constants.PodPhases.pod_phase_to_run_state(
493
- pod_phase
494
- )
495
- function["status"] = {"state": state}
496
- if isinstance(function["metadata"].get("updated"), datetime.datetime):
497
- function["metadata"]["updated"] = function["metadata"][
498
- "updated"
499
- ].isoformat()
500
- function["step_kind"] = _step.step_type
501
- steps.append(function)
502
-
503
- step_methods = {
504
- mlrun_pipelines.common.constants.PipelineRunType.run: _add_run_step,
505
- mlrun_pipelines.common.constants.PipelineRunType.build: _add_deploy_function_step,
506
- mlrun_pipelines.common.constants.PipelineRunType.deploy: _add_deploy_function_step,
507
- }
508
-
509
- workflow_id = run.status.results.get("workflow_id", None)
510
- if not workflow_id:
511
- return steps
512
-
513
- workflow_manifest = self._get_workflow_manifest(workflow_id)
514
- if not workflow_manifest:
515
- return steps
516
-
517
- try:
518
- for step in workflow_manifest.get_steps():
519
- step_method = step_methods.get(step.step_type)
520
- if step_method:
521
- step_method(step)
522
- return steps
523
- except Exception:
524
- # If we fail to read the pipeline steps, we will return the list of runs that have the same workflow id
525
- logger.warning(
526
- "Failed to extract workflow steps from workflow manifest, "
527
- "returning all runs with the workflow id label",
528
- workflow_id=workflow_id,
529
- traceback=traceback.format_exc(),
530
- )
531
- return db.list_runs(
532
- project=run.metadata.project,
533
- labels=f"workflow={workflow_id}",
534
- )
535
-
536
- @staticmethod
537
- def _get_workflow_manifest(
538
- workflow_id: str,
539
- ) -> typing.Optional[mlrun_pipelines.models.PipelineManifest]:
540
- kfp_client = mlrun_pipelines.utils.get_client(mlrun.mlconf.kfp_url)
541
-
542
- # arbitrary timeout of 5 seconds, the workflow should be done by now
543
- kfp_run = kfp_client.wait_for_run_completion(workflow_id, 5)
544
- if not kfp_run:
545
- return None
546
-
547
- kfp_run = mlrun_pipelines.models.PipelineRun(kfp_run)
548
- return kfp_run.workflow_manifest()
549
-
550
- def _extract_function_uri(self, function_uri: str) -> tuple[str, str, str]:
551
- """
552
- Extract the project, name, and hash key from a function uri.
553
- Examples:
554
- - "project/name@hash_key" returns project, name, hash_key
555
- - "project/name returns" project, name, ""
556
- """
557
- project, name, hash_key = None, None, None
558
- hashed_pattern = r"^(.+)/(.+)@(.+)$"
559
- pattern = r"^(.+)/(.+)$"
560
- match = re.match(hashed_pattern, function_uri)
561
- if match:
562
- project, name, hash_key = match.groups()
563
- else:
564
- match = re.match(pattern, function_uri)
565
- if match:
566
- project, name = match.groups()
567
- hash_key = ""
568
- return project, name, hash_key
569
-
570
442
 
571
443
  class CustomNotificationPusher(_NotificationPusherBase):
572
444
  def __init__(self, notification_types: typing.Optional[list[str]] = None):
@@ -1,4 +1,4 @@
1
1
  {
2
- "git_commit": "2d5cc0ee3dacf5d31a3b6a2158cd2e2b4a378b56",
3
- "version": "1.8.0-rc18"
2
+ "git_commit": "6671b915fe08ba2b9afa054585f86b955b48b671",
3
+ "version": "1.8.0-rc19"
4
4
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mlrun
3
- Version: 1.8.0rc18
3
+ Version: 1.8.0rc19
4
4
  Summary: Tracking and config of machine learning runs
5
5
  Home-page: https://github.com/mlrun/mlrun
6
6
  Author: Yaron Haviv
@@ -1,6 +1,6 @@
1
1
  mlrun/__init__.py,sha256=7vuMpUiigXXDrghLRq680LKWy1faC0kQyGCZb_7cwyE,7473
2
2
  mlrun/__main__.py,sha256=o65gXHhmFA9GV_n2mqmAO80nW3MAwo_s7j80IKgCzRE,45949
3
- mlrun/config.py,sha256=jjjpcnTjmYPc5ptvetYMJ-BPWaOiQW1b9Phdh9A8wo0,70605
3
+ mlrun/config.py,sha256=zHp7wtbaewFbHYsA_SLxwuwSDYwrcL6A3knWSPcRtAA,70547
4
4
  mlrun/errors.py,sha256=5raKb1PXQpTcIvWQ4sr1qn2IS7P_GT_FydBJ0dXkVuc,8097
5
5
  mlrun/execution.py,sha256=Up9U6xonTElRIaesF9Vej2JK1Isk2AZNK9ke0XcF5Dg,49030
6
6
  mlrun/features.py,sha256=ReBaNGsBYXqcbgI012n-SO_j6oHIbk_Vpv0CGPXbUmo,15842
@@ -42,7 +42,7 @@ mlrun/common/runtimes/constants.py,sha256=Mok3m9Rv182TTMp7uYNfWalm9Xcz86yva-4fTx
42
42
  mlrun/common/schemas/__init__.py,sha256=PBuIAhXSkVEVxxKcv5hR_xvTwNAUqxOXHVPugOoWTyM,5386
43
43
  mlrun/common/schemas/alert.py,sha256=G9lFTXFYDor-RVLpJxMorIPlLWr_-GYCFKRN9DkKwXs,10124
44
44
  mlrun/common/schemas/api_gateway.py,sha256=3a0QxECLmoDkD5IiOKtXJL-uiWB26Hg55WMA3nULYuI,7127
45
- mlrun/common/schemas/artifact.py,sha256=i29BeZ4MoOLMST3WlApX1Nli0vy-M7Zj_oTm8jySlw4,3805
45
+ mlrun/common/schemas/artifact.py,sha256=f0NPsoZmA-WD9RtN-dcKFW6KuV0PPQB25A2psF7LbP8,4013
46
46
  mlrun/common/schemas/auth.py,sha256=AGbBNvQq_vcvhX_NLqbT-QPHL4BAJMB3xwBXW7cFvpo,6761
47
47
  mlrun/common/schemas/background_task.py,sha256=ofWRAQGGEkXEu79Dbw7tT_5GPolR09Lc3Ebg2r0fT24,1728
48
48
  mlrun/common/schemas/client_spec.py,sha256=CCdAMwRS2DFKddxNSulWcRDwp3mrE7dDdKeAD6djxLo,2856
@@ -71,10 +71,10 @@ mlrun/common/schemas/schedule.py,sha256=LTWdZ4FvKDGkmmfyqKoBQ36VFqnnyIYLnq1I6qrT
71
71
  mlrun/common/schemas/secret.py,sha256=CCxFYiPwJtDxwg2VVJH9nUG9cAZ2a34IjeuaWv-BYlc,1487
72
72
  mlrun/common/schemas/tag.py,sha256=HRZi5QZ4vVGaCr2AMk9eJgcNiAIXmH4YDc8a4fvF770,893
73
73
  mlrun/common/schemas/workflow.py,sha256=rwYzDJYxpE9k4kC88j_eUCmqK4ZsWV_h-_nli7Fs7Ow,2078
74
- mlrun/common/schemas/model_monitoring/__init__.py,sha256=noj7DPPia59PBWORsQZO20Ym0yhUwyoNJ3EeG8gVxlQ,1875
75
- mlrun/common/schemas/model_monitoring/constants.py,sha256=VA1fEETbiyTKGiAG4WBSzo69Revd1_sgr8to6N0rp6U,11543
74
+ mlrun/common/schemas/model_monitoring/__init__.py,sha256=jz0fvdn8BEecgUCKhiSNH6QtFhSW4O19Ql9KXo0AxOg,1900
75
+ mlrun/common/schemas/model_monitoring/constants.py,sha256=KHpZiTruqr1iQ4XfEz8Ptj9ytCBuvwOXp30YTt5YQws,11834
76
76
  mlrun/common/schemas/model_monitoring/grafana.py,sha256=Rq10KKOyyUYr7qOQFZfwGZtUim0LY9O0LQ5uc9jmIVQ,1562
77
- mlrun/common/schemas/model_monitoring/model_endpoints.py,sha256=GVWePSdaKyJm7-QdBiAQdMymlp2QgZIJwXO9EA6Q7AI,11727
77
+ mlrun/common/schemas/model_monitoring/model_endpoints.py,sha256=j60-_puybc4yLlmoWkZ04m6PuY4p5yfzVJpPo0_n1sY,11725
78
78
  mlrun/data_types/__init__.py,sha256=unRo9GGwCmj0hBKBRsXJ2P4BzpQaddlQTvIrVQaKluI,984
79
79
  mlrun/data_types/data_types.py,sha256=0_oKLC6-sXL2_nnaDMP_HSXB3fD1nJAG4J2Jq6sGNNw,4998
80
80
  mlrun/data_types/infer.py,sha256=KdaRgWcqvLkuLjXrMuDr3ik6WY7JP5wJO0Yii_Vl5kw,6173
@@ -107,9 +107,9 @@ mlrun/datastore/wasbfs/__init__.py,sha256=s5Ul-0kAhYqFjKDR2X0O2vDGDbLQQduElb32Ev
107
107
  mlrun/datastore/wasbfs/fs.py,sha256=ge8NK__5vTcFT-krI155_8RDUywQw4SIRX6BWATXy9Q,6299
108
108
  mlrun/db/__init__.py,sha256=WqJ4x8lqJ7ZoKbhEyFqkYADd9P6E3citckx9e9ZLcIU,1163
109
109
  mlrun/db/auth_utils.py,sha256=hpg8D2r82oN0BWabuWN04BTNZ7jYMAF242YSUpK7LFM,5211
110
- mlrun/db/base.py,sha256=ZCvjAN68L4kRZsytkuqqz4JTLROxczmbXjJwU1vaK8c,29855
110
+ mlrun/db/base.py,sha256=aFtDl4J_yeEovso7uvnnn9KLYMnIRiS52qM1uemdG8k,30218
111
111
  mlrun/db/factory.py,sha256=yP2vVmveUE7LYTCHbS6lQIxP9rW--zdISWuPd_I3d_4,2111
112
- mlrun/db/httpdb.py,sha256=eGuQ05kwU8h_DbtuzlPo1aBMU4ZbfIsrRPWxiqb4v_Y,226141
112
+ mlrun/db/httpdb.py,sha256=VXp3ETu5fl_-6lEF_bsUerKESuRXbAFxLYvduH6DlKs,228070
113
113
  mlrun/db/nopdb.py,sha256=v285LHP_Onfuo8KRF078IAPHIXTeEhQsU58QoY7x-b0,26673
114
114
  mlrun/feature_store/__init__.py,sha256=AVnY2AFUNc2dKxLLUMx2K3Wo1eGviv0brDcYlDnmtf4,1506
115
115
  mlrun/feature_store/api.py,sha256=qkojZpzqGAn3r9ww0ynBRKOs8ji8URaK4DSYD4SE-CE,50395
@@ -219,7 +219,7 @@ mlrun/model_monitoring/__init__.py,sha256=ELy7njEtZnz09Dc6PGZSFFEGtnwI15bJNWM3Pj
219
219
  mlrun/model_monitoring/api.py,sha256=nH5aEUkmUEJF0CurrWJxmxVv1tQed2yzCLhQByG1L00,28561
220
220
  mlrun/model_monitoring/controller.py,sha256=dBfZQswF67vqeUFnmgsm9jU_5sOs9dLwMPEiYHG-Kk8,19786
221
221
  mlrun/model_monitoring/features_drift_table.py,sha256=c6GpKtpOJbuT1u5uMWDL_S-6N4YPOmlktWMqPme3KFY,25308
222
- mlrun/model_monitoring/helpers.py,sha256=Ul7-EHqzbf1GprB53Tf4WcbOHdD65sK3BgF2mpPYakg,17938
222
+ mlrun/model_monitoring/helpers.py,sha256=6L-IO4EUAYoAf74snGNiGDln_p77OIdxVdrf392VBzY,17936
223
223
  mlrun/model_monitoring/stream_processing.py,sha256=ltCVgo_b3yay16CUbqeGkRfzCHZSn14lVeBng5m9keY,31738
224
224
  mlrun/model_monitoring/tracking_policy.py,sha256=PBIGrUYWrwcE5gwXupBIVzOb0QRRwPJsgQm_yLGQxB4,5595
225
225
  mlrun/model_monitoring/writer.py,sha256=vbL7bqTyNu8q4bNcebX72sUMybVDAoTWg-CXq4fov3Y,8429
@@ -234,15 +234,15 @@ mlrun/model_monitoring/db/__init__.py,sha256=r47xPGZpIfMuv8J3PQCZTSqVPMhUta4sSJC
234
234
  mlrun/model_monitoring/db/_schedules.py,sha256=NTO1rbSyhW1JidpBDSN39ZBD0ctp5pbJFYQwxKRIRrs,5821
235
235
  mlrun/model_monitoring/db/_stats.py,sha256=VVMWLMqG3Us3ozBkLaokJF22Ewv8WKmVE1-OvS_g9vA,6943
236
236
  mlrun/model_monitoring/db/tsdb/__init__.py,sha256=_ejhGA-bi5-6kEMEVYUN5TLxND8hTj4gMG7WrYIkpxM,4585
237
- mlrun/model_monitoring/db/tsdb/base.py,sha256=EWm-56AzuLSH1JV6DAhSRu9sUel5YD6Ch1n5tgUHODo,20983
237
+ mlrun/model_monitoring/db/tsdb/base.py,sha256=JjLBzZXE4ZxtBmihVXjUYZ2HKmgqX03ZhUynXp4948o,25372
238
238
  mlrun/model_monitoring/db/tsdb/helpers.py,sha256=0oUXc4aUkYtP2SGP6jTb3uPPKImIUsVsrb9otX9a7O4,1189
239
239
  mlrun/model_monitoring/db/tsdb/tdengine/__init__.py,sha256=vgBdsKaXUURKqIf3M0y4sRatmSVA4CQiJs7J5dcVBkQ,620
240
240
  mlrun/model_monitoring/db/tsdb/tdengine/schemas.py,sha256=6de8P0CJMqe7PGttoZNt9UtrbBcJnpIp82hk_MbtepA,12477
241
241
  mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py,sha256=Uadj0UvAmln2MxDWod-kAzau1uNlqZh981rPhbUH_5M,2857
242
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py,sha256=Vbx5u4Y2ZbzbRQDwMIyz-9S0OgkGKw_gZTvWvF8Jp7I,29599
242
+ mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py,sha256=IEpJknjqx_LYcZjIccPuujOfEruXsRm8c8YzYaWWNEQ,30175
243
243
  mlrun/model_monitoring/db/tsdb/v3io/__init__.py,sha256=aL3bfmQsUQ-sbvKGdNihFj8gLCK3mSys0qDcXtYOwgc,616
244
244
  mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py,sha256=_-zo9relCDtjGgievxAcAP9gVN9nDWs8BzGtFwTjb9M,6284
245
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py,sha256=rZGakT22t3vNO7K9Ez1Au1UM5-E8c2Fuw4B6wuIIWPM,35896
245
+ mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py,sha256=9j_TWS_3OXTwjiYw9jb92z8URHUXDW7hCgwZnpb2P8c,36834
246
246
  mlrun/model_monitoring/metrics/__init__.py,sha256=6CsTXAxeLbbf8yfCADTaxmiavqwrLEdYFJ-qc5kgDAY,569
247
247
  mlrun/model_monitoring/metrics/histogram_distance.py,sha256=E9_WIl2vd6qNvoHVHoFcnuQk3ekbFWOdi8aU7sHrfk4,4724
248
248
  mlrun/package/__init__.py,sha256=v7VDyK9kDOOuDvFo4oiGV2fx-vM1KL7fdN9pGLakhUQ,7008
@@ -267,7 +267,7 @@ mlrun/platforms/iguazio.py,sha256=6VBTq8eQ3mzT96tzjYhAtcMQ2VjF4x8LpIPW5DAcX2Q,13
267
267
  mlrun/projects/__init__.py,sha256=0Krf0WIKfnZa71WthYOg0SoaTodGg3sV_hK3f_OlTPI,1220
268
268
  mlrun/projects/operations.py,sha256=VXUlMrouFTls-I-bMhdN5pPfQ34TR7bFQ-NUSWNvl84,20029
269
269
  mlrun/projects/pipelines.py,sha256=vZpyiERUzwPMS7NCC5ghI0KB_DItIddr7MMWGTwLawY,47437
270
- mlrun/projects/project.py,sha256=fApkLLMJKPQi-Q69O-pZP5sh6eVsLmBt1KqZUFwcZoM,228225
270
+ mlrun/projects/project.py,sha256=WKIgBy1nqHxQ4d2AHId6wptnGeYzfZTY61K8pOQUrvI,228386
271
271
  mlrun/runtimes/__init__.py,sha256=J9Sy2HiyMlztNv6VUurMzF5H2XzttNil8nRsWDsqLyg,8923
272
272
  mlrun/runtimes/base.py,sha256=Yt2l7srrXjK783cunBEKH0yQxQZRH8lkedXNOXuLbbo,37841
273
273
  mlrun/runtimes/daskjob.py,sha256=JwuGvOiPsxEDHHMMUS4Oie4hLlYYIZwihAl6DjroTY0,19521
@@ -318,7 +318,7 @@ mlrun/utils/azure_vault.py,sha256=IEFizrDGDbAaoWwDr1WoA88S_EZ0T--vjYtY-i0cvYQ,34
318
318
  mlrun/utils/clones.py,sha256=y3zC9QS7z5mLuvyQ6vFd6sJnikbgtDwrBvieQq0sovY,7359
319
319
  mlrun/utils/condition_evaluator.py,sha256=-nGfRmZzivn01rHTroiGY4rqEv8T1irMyhzxEei-sKc,1897
320
320
  mlrun/utils/db.py,sha256=blQgkWMfFH9lcN4sgJQcPQgEETz2Dl_zwbVA0SslpFg,2186
321
- mlrun/utils/helpers.py,sha256=3E7cQFcvcKdXmMnjsFY5r6cY6S0LxoFOrAkN6_jYw1s,64237
321
+ mlrun/utils/helpers.py,sha256=aPMmd5dEm7PIbx7maXtNGhNKzEHp8JK8uSmOgTtoDbc,69549
322
322
  mlrun/utils/http.py,sha256=t6FrXQstZm9xVVjxqIGiLzrwZNCR4CSienSOuVgNIcI,8706
323
323
  mlrun/utils/logger.py,sha256=_v4UTv1-STzC2c6aAWAa0NNl9STQoBYbR3OHgAiL41s,14606
324
324
  mlrun/utils/regex.py,sha256=IQqwPna6Z8J31xkTUduYbGk48GkQBUJFZSuxAWm1pzU,5162
@@ -327,7 +327,7 @@ mlrun/utils/singleton.py,sha256=p1Y-X0mPSs_At092GS-pZCA8CTR62HOqPU07_ZH6-To,869
327
327
  mlrun/utils/v3io_clients.py,sha256=0aCFiQFBmgdSeLzJr_nEP6SG-zyieSgH8RdtcUq4dc0,1294
328
328
  mlrun/utils/vault.py,sha256=xUiKL17dCXjwQJ33YRzQj0oadUXATlFWPzKKYAESoQk,10447
329
329
  mlrun/utils/notifications/__init__.py,sha256=eUzQDBxSQmMZASRY-YAnYS6tL5801P0wEjycp3Dvoe0,990
330
- mlrun/utils/notifications/notification_pusher.py,sha256=Y30ZG8MDk0oxPPHPjqd9kDxQaOewTuE5uwZhxlM5V-s,29269
330
+ mlrun/utils/notifications/notification_pusher.py,sha256=WN7RMfaZtCoG3bNfMjub1phbvJ2Xw8lKFiq2GStnKGw,24132
331
331
  mlrun/utils/notifications/notification/__init__.py,sha256=9Rfy6Jm8n0LaEDO1VAQb6kIbr7_uVuQhK1pS_abELIY,2581
332
332
  mlrun/utils/notifications/notification/base.py,sha256=VOgrzRakRfjYYBqvkc0cgEC5pl7KMidP7u-TL4HpGCY,5280
333
333
  mlrun/utils/notifications/notification/console.py,sha256=ICbIhOf9fEBJky_3j9TFiKAewDGyDHJr9l4VeT7G2sc,2745
@@ -335,13 +335,13 @@ mlrun/utils/notifications/notification/git.py,sha256=t2lqRrPRBO4awf_uhxJreH9Cpcb
335
335
  mlrun/utils/notifications/notification/ipython.py,sha256=9uZvI1uOLFaNuAsfJPXmL3l6dOzFoWdBK5GYNYFAfks,2282
336
336
  mlrun/utils/notifications/notification/mail.py,sha256=ZyJ3eqd8simxffQmXzqd3bgbAqp1vij7C6aRJ9h2mgs,6012
337
337
  mlrun/utils/notifications/notification/slack.py,sha256=NKV4RFiY3gLsS8uPppgniPLyag8zJ9O1VhixoXkM7kw,7108
338
- mlrun/utils/notifications/notification/webhook.py,sha256=M-pSBM2VTKVUPRERocjORlH6mKqo1K9ihVL5Qrn2GyM,4789
338
+ mlrun/utils/notifications/notification/webhook.py,sha256=NeyIMSBojjjTJaUHmPbxMByp34GxYkl1-16NqzU27fU,4943
339
339
  mlrun/utils/version/__init__.py,sha256=7kkrB7hEZ3cLXoWj1kPoDwo4MaswsI2JVOBpbKgPAgc,614
340
- mlrun/utils/version/version.json,sha256=fuKq-DQzj4VU91pAfeQ6y9--Wzwya8hmUG1brhepAzQ,89
340
+ mlrun/utils/version/version.json,sha256=9e_fs0UZTGdzgD6yjggI41O7Hv5n_tbuYDMtJsSNspY,89
341
341
  mlrun/utils/version/version.py,sha256=eEW0tqIAkU9Xifxv8Z9_qsYnNhn3YH7NRAfM-pPLt1g,1878
342
- mlrun-1.8.0rc18.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
343
- mlrun-1.8.0rc18.dist-info/METADATA,sha256=LmjjU6uSheHxPOGP4V2giVzBleaHV45fl9bCB_xqyBo,24885
344
- mlrun-1.8.0rc18.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
345
- mlrun-1.8.0rc18.dist-info/entry_points.txt,sha256=1Owd16eAclD5pfRCoJpYC2ZJSyGNTtUr0nCELMioMmU,46
346
- mlrun-1.8.0rc18.dist-info/top_level.txt,sha256=NObLzw3maSF9wVrgSeYBv-fgnHkAJ1kEkh12DLdd5KM,6
347
- mlrun-1.8.0rc18.dist-info/RECORD,,
342
+ mlrun-1.8.0rc19.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
343
+ mlrun-1.8.0rc19.dist-info/METADATA,sha256=A2YOQIMx1P8pckWVn0bKsr8oEpcWxVbhkXhPxTplJMM,24885
344
+ mlrun-1.8.0rc19.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
345
+ mlrun-1.8.0rc19.dist-info/entry_points.txt,sha256=1Owd16eAclD5pfRCoJpYC2ZJSyGNTtUr0nCELMioMmU,46
346
+ mlrun-1.8.0rc19.dist-info/top_level.txt,sha256=NObLzw3maSF9wVrgSeYBv-fgnHkAJ1kEkh12DLdd5KM,6
347
+ mlrun-1.8.0rc19.dist-info/RECORD,,