mlrun 1.7.0rc4__py3-none-any.whl → 1.7.0rc20__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 (200) hide show
  1. mlrun/__init__.py +11 -1
  2. mlrun/__main__.py +25 -111
  3. mlrun/{datastore/helpers.py → alerts/__init__.py} +2 -5
  4. mlrun/alerts/alert.py +144 -0
  5. mlrun/api/schemas/__init__.py +4 -3
  6. mlrun/artifacts/__init__.py +8 -3
  7. mlrun/artifacts/base.py +38 -254
  8. mlrun/artifacts/dataset.py +9 -190
  9. mlrun/artifacts/manager.py +41 -47
  10. mlrun/artifacts/model.py +30 -158
  11. mlrun/artifacts/plots.py +23 -380
  12. mlrun/common/constants.py +68 -0
  13. mlrun/common/formatters/__init__.py +19 -0
  14. mlrun/{model_monitoring/stores/models/sqlite.py → common/formatters/artifact.py} +6 -8
  15. mlrun/common/formatters/base.py +78 -0
  16. mlrun/common/formatters/function.py +41 -0
  17. mlrun/common/formatters/pipeline.py +53 -0
  18. mlrun/common/formatters/project.py +51 -0
  19. mlrun/{runtimes → common/runtimes}/constants.py +32 -4
  20. mlrun/common/schemas/__init__.py +25 -4
  21. mlrun/common/schemas/alert.py +203 -0
  22. mlrun/common/schemas/api_gateway.py +148 -0
  23. mlrun/common/schemas/artifact.py +15 -5
  24. mlrun/common/schemas/auth.py +8 -2
  25. mlrun/common/schemas/client_spec.py +2 -0
  26. mlrun/common/schemas/frontend_spec.py +1 -0
  27. mlrun/common/schemas/function.py +4 -0
  28. mlrun/common/schemas/hub.py +7 -9
  29. mlrun/common/schemas/model_monitoring/__init__.py +19 -3
  30. mlrun/common/schemas/model_monitoring/constants.py +96 -26
  31. mlrun/common/schemas/model_monitoring/grafana.py +9 -5
  32. mlrun/common/schemas/model_monitoring/model_endpoints.py +86 -2
  33. mlrun/{runtimes/mpijob/v1alpha1.py → common/schemas/pagination.py} +10 -13
  34. mlrun/common/schemas/pipeline.py +0 -9
  35. mlrun/common/schemas/project.py +22 -21
  36. mlrun/common/types.py +7 -1
  37. mlrun/config.py +87 -19
  38. mlrun/data_types/data_types.py +4 -0
  39. mlrun/data_types/to_pandas.py +9 -9
  40. mlrun/datastore/__init__.py +5 -8
  41. mlrun/datastore/alibaba_oss.py +130 -0
  42. mlrun/datastore/azure_blob.py +4 -5
  43. mlrun/datastore/base.py +69 -30
  44. mlrun/datastore/datastore.py +10 -2
  45. mlrun/datastore/datastore_profile.py +90 -6
  46. mlrun/datastore/google_cloud_storage.py +1 -1
  47. mlrun/datastore/hdfs.py +5 -0
  48. mlrun/datastore/inmem.py +2 -2
  49. mlrun/datastore/redis.py +2 -2
  50. mlrun/datastore/s3.py +5 -0
  51. mlrun/datastore/snowflake_utils.py +43 -0
  52. mlrun/datastore/sources.py +172 -44
  53. mlrun/datastore/store_resources.py +7 -7
  54. mlrun/datastore/targets.py +285 -41
  55. mlrun/datastore/utils.py +68 -5
  56. mlrun/datastore/v3io.py +27 -50
  57. mlrun/db/auth_utils.py +152 -0
  58. mlrun/db/base.py +149 -14
  59. mlrun/db/factory.py +1 -1
  60. mlrun/db/httpdb.py +608 -178
  61. mlrun/db/nopdb.py +191 -7
  62. mlrun/errors.py +11 -0
  63. mlrun/execution.py +37 -20
  64. mlrun/feature_store/__init__.py +0 -2
  65. mlrun/feature_store/api.py +21 -52
  66. mlrun/feature_store/feature_set.py +48 -23
  67. mlrun/feature_store/feature_vector.py +2 -1
  68. mlrun/feature_store/ingestion.py +7 -6
  69. mlrun/feature_store/retrieval/base.py +9 -4
  70. mlrun/feature_store/retrieval/conversion.py +9 -9
  71. mlrun/feature_store/retrieval/dask_merger.py +2 -0
  72. mlrun/feature_store/retrieval/job.py +9 -3
  73. mlrun/feature_store/retrieval/local_merger.py +2 -0
  74. mlrun/feature_store/retrieval/spark_merger.py +34 -24
  75. mlrun/feature_store/steps.py +30 -19
  76. mlrun/features.py +4 -13
  77. mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +7 -12
  78. mlrun/frameworks/auto_mlrun/auto_mlrun.py +2 -2
  79. mlrun/frameworks/lgbm/__init__.py +1 -1
  80. mlrun/frameworks/lgbm/callbacks/callback.py +2 -4
  81. mlrun/frameworks/lgbm/model_handler.py +1 -1
  82. mlrun/frameworks/parallel_coordinates.py +2 -1
  83. mlrun/frameworks/pytorch/__init__.py +2 -2
  84. mlrun/frameworks/sklearn/__init__.py +1 -1
  85. mlrun/frameworks/tf_keras/__init__.py +5 -2
  86. mlrun/frameworks/tf_keras/callbacks/logging_callback.py +1 -1
  87. mlrun/frameworks/tf_keras/mlrun_interface.py +2 -2
  88. mlrun/frameworks/xgboost/__init__.py +1 -1
  89. mlrun/k8s_utils.py +10 -11
  90. mlrun/launcher/__init__.py +1 -1
  91. mlrun/launcher/base.py +6 -5
  92. mlrun/launcher/client.py +8 -6
  93. mlrun/launcher/factory.py +1 -1
  94. mlrun/launcher/local.py +9 -3
  95. mlrun/launcher/remote.py +9 -3
  96. mlrun/lists.py +6 -2
  97. mlrun/model.py +58 -19
  98. mlrun/model_monitoring/__init__.py +1 -1
  99. mlrun/model_monitoring/api.py +127 -301
  100. mlrun/model_monitoring/application.py +5 -296
  101. mlrun/model_monitoring/applications/__init__.py +11 -0
  102. mlrun/model_monitoring/applications/_application_steps.py +157 -0
  103. mlrun/model_monitoring/applications/base.py +282 -0
  104. mlrun/model_monitoring/applications/context.py +214 -0
  105. mlrun/model_monitoring/applications/evidently_base.py +211 -0
  106. mlrun/model_monitoring/applications/histogram_data_drift.py +224 -93
  107. mlrun/model_monitoring/applications/results.py +99 -0
  108. mlrun/model_monitoring/controller.py +30 -36
  109. mlrun/model_monitoring/db/__init__.py +18 -0
  110. mlrun/model_monitoring/{stores → db/stores}/__init__.py +43 -36
  111. mlrun/model_monitoring/db/stores/base/__init__.py +15 -0
  112. mlrun/model_monitoring/{stores/model_endpoint_store.py → db/stores/base/store.py} +58 -32
  113. mlrun/model_monitoring/db/stores/sqldb/__init__.py +13 -0
  114. mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +71 -0
  115. mlrun/model_monitoring/{stores → db/stores/sqldb}/models/base.py +109 -5
  116. mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +88 -0
  117. mlrun/model_monitoring/{stores/models/mysql.py → db/stores/sqldb/models/sqlite.py} +19 -13
  118. mlrun/model_monitoring/db/stores/sqldb/sql_store.py +684 -0
  119. mlrun/model_monitoring/db/stores/v3io_kv/__init__.py +13 -0
  120. mlrun/model_monitoring/{stores/kv_model_endpoint_store.py → db/stores/v3io_kv/kv_store.py} +302 -155
  121. mlrun/model_monitoring/db/tsdb/__init__.py +100 -0
  122. mlrun/model_monitoring/db/tsdb/base.py +329 -0
  123. mlrun/model_monitoring/db/tsdb/helpers.py +30 -0
  124. mlrun/model_monitoring/db/tsdb/tdengine/__init__.py +15 -0
  125. mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +240 -0
  126. mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +45 -0
  127. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +397 -0
  128. mlrun/model_monitoring/db/tsdb/v3io/__init__.py +15 -0
  129. mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +117 -0
  130. mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +630 -0
  131. mlrun/model_monitoring/evidently_application.py +6 -118
  132. mlrun/model_monitoring/features_drift_table.py +34 -22
  133. mlrun/model_monitoring/helpers.py +100 -7
  134. mlrun/model_monitoring/model_endpoint.py +3 -2
  135. mlrun/model_monitoring/stream_processing.py +93 -228
  136. mlrun/model_monitoring/tracking_policy.py +7 -1
  137. mlrun/model_monitoring/writer.py +152 -124
  138. mlrun/package/packagers_manager.py +1 -0
  139. mlrun/package/utils/_formatter.py +2 -2
  140. mlrun/platforms/__init__.py +11 -10
  141. mlrun/platforms/iguazio.py +21 -202
  142. mlrun/projects/operations.py +30 -16
  143. mlrun/projects/pipelines.py +92 -99
  144. mlrun/projects/project.py +757 -268
  145. mlrun/render.py +15 -14
  146. mlrun/run.py +160 -162
  147. mlrun/runtimes/__init__.py +55 -3
  148. mlrun/runtimes/base.py +33 -19
  149. mlrun/runtimes/databricks_job/databricks_wrapper.py +1 -1
  150. mlrun/runtimes/funcdoc.py +0 -28
  151. mlrun/runtimes/kubejob.py +28 -122
  152. mlrun/runtimes/local.py +5 -2
  153. mlrun/runtimes/mpijob/__init__.py +0 -20
  154. mlrun/runtimes/mpijob/abstract.py +8 -8
  155. mlrun/runtimes/mpijob/v1.py +1 -1
  156. mlrun/runtimes/nuclio/__init__.py +1 -0
  157. mlrun/runtimes/nuclio/api_gateway.py +709 -0
  158. mlrun/runtimes/nuclio/application/__init__.py +15 -0
  159. mlrun/runtimes/nuclio/application/application.py +523 -0
  160. mlrun/runtimes/nuclio/application/reverse_proxy.go +95 -0
  161. mlrun/runtimes/nuclio/function.py +98 -58
  162. mlrun/runtimes/nuclio/serving.py +36 -42
  163. mlrun/runtimes/pod.py +196 -45
  164. mlrun/runtimes/remotesparkjob.py +1 -1
  165. mlrun/runtimes/sparkjob/spark3job.py +1 -1
  166. mlrun/runtimes/utils.py +6 -73
  167. mlrun/secrets.py +6 -2
  168. mlrun/serving/remote.py +2 -3
  169. mlrun/serving/routers.py +7 -4
  170. mlrun/serving/server.py +7 -8
  171. mlrun/serving/states.py +73 -43
  172. mlrun/serving/v2_serving.py +8 -7
  173. mlrun/track/tracker.py +2 -1
  174. mlrun/utils/async_http.py +25 -5
  175. mlrun/utils/helpers.py +141 -75
  176. mlrun/utils/http.py +1 -1
  177. mlrun/utils/logger.py +39 -7
  178. mlrun/utils/notifications/notification/__init__.py +14 -9
  179. mlrun/utils/notifications/notification/base.py +12 -0
  180. mlrun/utils/notifications/notification/console.py +2 -0
  181. mlrun/utils/notifications/notification/git.py +3 -1
  182. mlrun/utils/notifications/notification/ipython.py +2 -0
  183. mlrun/utils/notifications/notification/slack.py +101 -21
  184. mlrun/utils/notifications/notification/webhook.py +11 -1
  185. mlrun/utils/notifications/notification_pusher.py +147 -16
  186. mlrun/utils/retryer.py +3 -2
  187. mlrun/utils/v3io_clients.py +0 -1
  188. mlrun/utils/version/version.json +2 -2
  189. {mlrun-1.7.0rc4.dist-info → mlrun-1.7.0rc20.dist-info}/METADATA +33 -18
  190. mlrun-1.7.0rc20.dist-info/RECORD +353 -0
  191. {mlrun-1.7.0rc4.dist-info → mlrun-1.7.0rc20.dist-info}/WHEEL +1 -1
  192. mlrun/kfpops.py +0 -868
  193. mlrun/model_monitoring/batch.py +0 -974
  194. mlrun/model_monitoring/stores/models/__init__.py +0 -27
  195. mlrun/model_monitoring/stores/sql_model_endpoint_store.py +0 -382
  196. mlrun/platforms/other.py +0 -305
  197. mlrun-1.7.0rc4.dist-info/RECORD +0 -321
  198. {mlrun-1.7.0rc4.dist-info → mlrun-1.7.0rc20.dist-info}/LICENSE +0 -0
  199. {mlrun-1.7.0rc4.dist-info → mlrun-1.7.0rc20.dist-info}/entry_points.txt +0 -0
  200. {mlrun-1.7.0rc4.dist-info → mlrun-1.7.0rc20.dist-info}/top_level.txt +0 -0
mlrun/serving/states.py CHANGED
@@ -14,19 +14,21 @@
14
14
 
15
15
  __all__ = ["TaskStep", "RouterStep", "RootFlowStep", "ErrorStep"]
16
16
 
17
- import asyncio
18
17
  import os
19
18
  import pathlib
20
19
  import traceback
21
20
  from copy import copy, deepcopy
22
21
  from inspect import getfullargspec, signature
23
- from typing import Union
22
+ from typing import Any, Union
24
23
 
25
24
  import mlrun
26
25
 
27
26
  from ..config import config
28
27
  from ..datastore import get_stream_pusher
29
- from ..datastore.utils import parse_kafka_url
28
+ from ..datastore.utils import (
29
+ get_kafka_brokers_from_dict,
30
+ parse_kafka_url,
31
+ )
30
32
  from ..errors import MLRunInvalidArgumentError, err_to_str
31
33
  from ..model import ModelObj, ObjectDict
32
34
  from ..platforms.iguazio import parse_path
@@ -325,7 +327,7 @@ class BaseStep(ModelObj):
325
327
  parent = self._parent
326
328
  else:
327
329
  raise GraphError(
328
- f"step {self.name} parent is not set or its not part of a graph"
330
+ f"step {self.name} parent is not set or it's not part of a graph"
329
331
  )
330
332
 
331
333
  name, step = params_to_step(
@@ -347,6 +349,36 @@ class BaseStep(ModelObj):
347
349
  parent._last_added = step
348
350
  return step
349
351
 
352
+ def set_flow(
353
+ self,
354
+ steps: list[Union[str, StepToDict, dict[str, Any]]],
355
+ force: bool = False,
356
+ ):
357
+ """set list of steps as downstream from this step, in the order specified. This will overwrite any existing
358
+ downstream steps.
359
+
360
+ :param steps: list of steps to follow this one
361
+ :param force: whether to overwrite existing downstream steps. If False, this method will fail if any downstream
362
+ steps have already been defined. Defaults to False.
363
+ :return: the last step added to the flow
364
+
365
+ example:
366
+ The below code sets the downstream nodes of step1 by using a list of steps (provided to `set_flow()`) and a
367
+ single step (provided to `to()`), resulting in the graph (step1 -> step2 -> step3 -> step4).
368
+ Notice that using `force=True` is required in case step1 already had downstream nodes (e.g. if the existing
369
+ graph is step1 -> step2_old) and that following the execution of this code the existing downstream steps
370
+ are removed. If the intention is to split the graph (and not to overwrite), please use `to()`.
371
+
372
+ step1.set_flow(
373
+ [
374
+ dict(name="step2", handler="step2_handler"),
375
+ dict(name="step3", class_name="Step3Class"),
376
+ ],
377
+ force=True,
378
+ ).to(dict(name="step4", class_name="Step4Class"))
379
+ """
380
+ raise NotImplementedError("set_flow() can only be called on a FlowStep")
381
+
350
382
 
351
383
  class TaskStep(BaseStep):
352
384
  """task execution step, runs a class or handler"""
@@ -1161,19 +1193,11 @@ class FlowStep(BaseStep):
1161
1193
  if self._controller:
1162
1194
  # async flow (using storey)
1163
1195
  event._awaitable_result = None
1164
- if self.context.is_mock:
1165
- resp = self._controller.emit(
1166
- event, return_awaitable_result=self._wait_for_result
1167
- )
1168
- if self._wait_for_result and resp:
1169
- return resp.await_result()
1170
- else:
1171
- resp_awaitable = self._controller.emit(
1172
- event, await_result=self._wait_for_result
1173
- )
1174
- if self._wait_for_result:
1175
- return resp_awaitable
1176
- return self._await_and_return_id(resp_awaitable, event)
1196
+ resp = self._controller.emit(
1197
+ event, return_awaitable_result=self._wait_for_result
1198
+ )
1199
+ if self._wait_for_result and resp:
1200
+ return resp.await_result()
1177
1201
  event = copy(event)
1178
1202
  event.body = {"id": event.id}
1179
1203
  return event
@@ -1213,18 +1237,9 @@ class FlowStep(BaseStep):
1213
1237
  """wait for completion of run in async flows"""
1214
1238
 
1215
1239
  if self._controller:
1216
- if asyncio.iscoroutinefunction(self._controller.await_termination):
1217
-
1218
- async def terminate_and_await_termination():
1219
- if hasattr(self._controller, "terminate"):
1220
- await self._controller.terminate()
1221
- return await self._controller.await_termination()
1222
-
1223
- return terminate_and_await_termination()
1224
- else:
1225
- if hasattr(self._controller, "terminate"):
1226
- self._controller.terminate()
1227
- return self._controller.await_termination()
1240
+ if hasattr(self._controller, "terminate"):
1241
+ self._controller.terminate()
1242
+ return self._controller.await_termination()
1228
1243
 
1229
1244
  def plot(self, filename=None, format=None, source=None, targets=None, **kw):
1230
1245
  """plot/save graph using graphviz
@@ -1273,6 +1288,27 @@ class FlowStep(BaseStep):
1273
1288
  )
1274
1289
  self[step_name].after_step(name)
1275
1290
 
1291
+ def set_flow(
1292
+ self,
1293
+ steps: list[Union[str, StepToDict, dict[str, Any]]],
1294
+ force: bool = False,
1295
+ ):
1296
+ if not force and self.steps:
1297
+ raise mlrun.errors.MLRunInvalidArgumentError(
1298
+ "set_flow() called on a step that already has downstream steps. "
1299
+ "If you want to overwrite existing steps, set force=True."
1300
+ )
1301
+
1302
+ self.steps = None
1303
+ step = self
1304
+ for next_step in steps:
1305
+ if isinstance(next_step, dict):
1306
+ step = step.to(**next_step)
1307
+ else:
1308
+ step = step.to(next_step)
1309
+
1310
+ return step
1311
+
1276
1312
 
1277
1313
  class RootFlowStep(FlowStep):
1278
1314
  """root flow step"""
@@ -1512,13 +1548,11 @@ def _init_async_objects(context, steps):
1512
1548
  endpoint = None
1513
1549
  options = {}
1514
1550
  options.update(step.options)
1515
- kafka_bootstrap_servers = options.pop(
1516
- "kafka_bootstrap_servers", None
1517
- )
1518
- if stream_path.startswith("kafka://") or kafka_bootstrap_servers:
1519
- topic, bootstrap_servers = parse_kafka_url(
1520
- stream_path, kafka_bootstrap_servers
1521
- )
1551
+
1552
+ kafka_brokers = get_kafka_brokers_from_dict(options, pop=True)
1553
+
1554
+ if stream_path.startswith("kafka://") or kafka_brokers:
1555
+ topic, brokers = parse_kafka_url(stream_path, kafka_brokers)
1522
1556
 
1523
1557
  kafka_producer_options = options.pop(
1524
1558
  "kafka_producer_options", None
@@ -1526,7 +1560,7 @@ def _init_async_objects(context, steps):
1526
1560
 
1527
1561
  step._async_object = storey.KafkaTarget(
1528
1562
  topic=topic,
1529
- bootstrap_servers=bootstrap_servers,
1563
+ brokers=brokers,
1530
1564
  producer_options=kafka_producer_options,
1531
1565
  context=context,
1532
1566
  **options,
@@ -1568,12 +1602,8 @@ def _init_async_objects(context, steps):
1568
1602
  source_args = context.get_param("source_args", {})
1569
1603
  explicit_ack = is_explicit_ack_supported(context) and mlrun.mlconf.is_explicit_ack()
1570
1604
 
1571
- if context.is_mock:
1572
- source_class = storey.SyncEmitSource
1573
- else:
1574
- source_class = storey.AsyncEmitSource
1575
-
1576
- default_source = source_class(
1605
+ # TODO: Change to AsyncEmitSource once we can drop support for nuclio<1.12.10
1606
+ default_source = storey.SyncEmitSource(
1577
1607
  context=context,
1578
1608
  explicit_ack=explicit_ack,
1579
1609
  **source_args,
@@ -21,6 +21,7 @@ import mlrun.common.model_monitoring
21
21
  import mlrun.common.schemas.model_monitoring
22
22
  from mlrun.artifacts import ModelArtifact # noqa: F401
23
23
  from mlrun.config import config
24
+ from mlrun.errors import err_to_str
24
25
  from mlrun.utils import logger, now_date
25
26
 
26
27
  from ..common.helpers import parse_versioned_object_uri
@@ -62,11 +63,11 @@ class V2ModelServer(StepToDict):
62
63
  class MyClass(V2ModelServer):
63
64
  def load(self):
64
65
  # load and initialize the model and/or other elements
65
- model_file, extra_data = self.get_model(suffix='.pkl')
66
+ model_file, extra_data = self.get_model(suffix=".pkl")
66
67
  self.model = load(open(model_file, "rb"))
67
68
 
68
69
  def predict(self, request):
69
- events = np.array(request['inputs'])
70
+ events = np.array(request["inputs"])
70
71
  dmatrix = xgb.DMatrix(events)
71
72
  result: xgb.DMatrix = self.model.predict(dmatrix)
72
73
  return {"outputs": result.tolist()}
@@ -175,9 +176,9 @@ class V2ModelServer(StepToDict):
175
176
  ::
176
177
 
177
178
  def load(self):
178
- model_file, extra_data = self.get_model(suffix='.pkl')
179
+ model_file, extra_data = self.get_model(suffix=".pkl")
179
180
  self.model = load(open(model_file, "rb"))
180
- categories = extra_data['categories'].as_df()
181
+ categories = extra_data["categories"].as_df()
181
182
 
182
183
  Parameters
183
184
  ----------
@@ -523,7 +524,7 @@ def _init_endpoint_record(
523
524
  graph_server.function_uri
524
525
  )
525
526
  except Exception as e:
526
- logger.error("Failed to parse function URI", exc=e)
527
+ logger.error("Failed to parse function URI", exc=err_to_str(e))
527
528
  return None
528
529
 
529
530
  # Generating version model value based on the model name and model version
@@ -576,9 +577,9 @@ def _init_endpoint_record(
576
577
  )
577
578
 
578
579
  except Exception as e:
579
- logger.error("Failed to create endpoint record", exc=e)
580
+ logger.error("Failed to create endpoint record", exc=err_to_str(e))
580
581
 
581
582
  except Exception as e:
582
- logger.error("Failed to retrieve model endpoint object", exc=e)
583
+ logger.error("Failed to retrieve model endpoint object", exc=err_to_str(e))
583
584
 
584
585
  return uid
mlrun/track/tracker.py CHANGED
@@ -31,8 +31,9 @@ class Tracker(ABC):
31
31
  * Offline: Manually importing models and artifacts into an MLRun project using the `import_x` methods.
32
32
  """
33
33
 
34
+ @staticmethod
34
35
  @abstractmethod
35
- def is_enabled(self) -> bool:
36
+ def is_enabled() -> bool:
36
37
  """
37
38
  Checks if tracker is enabled.
38
39
 
mlrun/utils/async_http.py CHANGED
@@ -24,7 +24,7 @@ from aiohttp_retry import ExponentialRetry, RequestParams, RetryClient, RetryOpt
24
24
  from aiohttp_retry.client import _RequestContext
25
25
 
26
26
  from mlrun.config import config
27
- from mlrun.errors import err_to_str
27
+ from mlrun.errors import err_to_str, raise_for_status
28
28
 
29
29
  from .helpers import logger as mlrun_logger
30
30
 
@@ -46,12 +46,21 @@ class AsyncClientWithRetry(RetryClient):
46
46
  *args,
47
47
  **kwargs,
48
48
  ):
49
+ # do not retry on PUT / PATCH as they might have side effects (not truly idempotent)
50
+ blacklisted_methods = (
51
+ blacklisted_methods
52
+ if blacklisted_methods is not None
53
+ else [
54
+ "POST",
55
+ "PUT",
56
+ "PATCH",
57
+ ]
58
+ )
49
59
  super().__init__(
50
60
  *args,
51
61
  retry_options=ExponentialRetryOverride(
52
62
  retry_on_exception=retry_on_exception,
53
- # do not retry on PUT / PATCH as they might have side effects (not truly idempotent)
54
- blacklisted_methods=blacklisted_methods or ["POST", "PUT", "PATCH"],
63
+ blacklisted_methods=blacklisted_methods,
55
64
  attempts=max_retries,
56
65
  statuses=retry_on_status_codes,
57
66
  factor=retry_backoff_factor,
@@ -63,6 +72,12 @@ class AsyncClientWithRetry(RetryClient):
63
72
  **kwargs,
64
73
  )
65
74
 
75
+ def methods_blacklist_update_required(self, new_blacklist: str):
76
+ self._retry_options: ExponentialRetryOverride
77
+ return set(self._retry_options.blacklisted_methods).difference(
78
+ set(new_blacklist)
79
+ )
80
+
66
81
  def _make_requests(
67
82
  self,
68
83
  params_list: list[RequestParams],
@@ -173,7 +188,7 @@ class _CustomRequestContext(_RequestContext):
173
188
  last_attempt = current_attempt == self._retry_options.attempts
174
189
  if self._is_status_code_ok(response.status) or last_attempt:
175
190
  if self._raise_for_status:
176
- response.raise_for_status()
191
+ raise_for_status(response)
177
192
 
178
193
  self._response = response
179
194
  return response
@@ -275,6 +290,11 @@ class _CustomRequestContext(_RequestContext):
275
290
  if isinstance(exc.os_error, exc_type):
276
291
  return
277
292
  if exc.__cause__:
278
- return self.verify_exception_type(exc.__cause__)
293
+ # If the cause exception is retriable, return, otherwise, raise the original exception
294
+ try:
295
+ self.verify_exception_type(exc.__cause__)
296
+ except Exception:
297
+ raise exc
298
+ return
279
299
  else:
280
300
  raise exc
mlrun/utils/helpers.py CHANGED
@@ -39,7 +39,7 @@ import pandas
39
39
  import semver
40
40
  import yaml
41
41
  from dateutil import parser
42
- from deprecated import deprecated
42
+ from mlrun_pipelines.models import PipelineRun
43
43
  from pandas._libs.tslibs.timestamps import Timedelta, Timestamp
44
44
  from yaml.representer import RepresenterError
45
45
 
@@ -76,19 +76,6 @@ class OverwriteBuildParamsWarning(FutureWarning):
76
76
  pass
77
77
 
78
78
 
79
- # TODO: remove in 1.7.0
80
- @deprecated(
81
- version="1.5.0",
82
- reason="'parse_versioned_object_uri' will be removed from this file in 1.7.0, use "
83
- "'mlrun.common.helpers.parse_versioned_object_uri' instead",
84
- category=FutureWarning,
85
- )
86
- def parse_versioned_object_uri(uri: str, default_project: str = ""):
87
- return mlrun.common.helpers.parse_versioned_object_uri(
88
- uri=uri, default_project=default_project
89
- )
90
-
91
-
92
79
  class StorePrefix:
93
80
  """map mlrun store objects to prefixes"""
94
81
 
@@ -119,14 +106,9 @@ class StorePrefix:
119
106
 
120
107
 
121
108
  def get_artifact_target(item: dict, project=None):
122
- if is_legacy_artifact(item):
123
- db_key = item.get("db_key")
124
- project_str = project or item.get("project")
125
- tree = item.get("tree")
126
- else:
127
- db_key = item["spec"].get("db_key")
128
- project_str = project or item["metadata"].get("project")
129
- tree = item["metadata"].get("tree")
109
+ db_key = item["spec"].get("db_key")
110
+ project_str = project or item["metadata"].get("project")
111
+ tree = item["metadata"].get("tree")
130
112
 
131
113
  kind = item.get("kind")
132
114
  if kind in ["dataset", "model", "artifact"] and db_key:
@@ -135,11 +117,15 @@ def get_artifact_target(item: dict, project=None):
135
117
  target = f"{target}@{tree}"
136
118
  return target
137
119
 
138
- return (
139
- item.get("target_path")
140
- if is_legacy_artifact(item)
141
- else item["spec"].get("target_path")
142
- )
120
+ return item["spec"].get("target_path")
121
+
122
+
123
+ # TODO: left for migrations testing purposes. Remove in 1.8.0.
124
+ def is_legacy_artifact(artifact):
125
+ if isinstance(artifact, dict):
126
+ return "metadata" not in artifact
127
+ else:
128
+ return not hasattr(artifact, "metadata")
143
129
 
144
130
 
145
131
  logger = create_logger(config.log_level, config.log_formatter, "mlrun", sys.stdout)
@@ -195,8 +181,12 @@ def verify_field_regex(
195
181
  )
196
182
  if mode == mlrun.common.schemas.RegexMatchModes.all:
197
183
  if raise_on_failure:
184
+ if len(field_name) > max_chars:
185
+ field_name = field_name[:max_chars] + "...truncated"
186
+ if len(field_value) > max_chars:
187
+ field_value = field_value[:max_chars] + "...truncated"
198
188
  raise mlrun.errors.MLRunInvalidArgumentError(
199
- f"Field '{field_name[:max_chars]}' is malformed. '{field_value[:max_chars]}' "
189
+ f"Field '{field_name}' is malformed. '{field_value}' "
200
190
  f"does not match required pattern: {pattern}"
201
191
  )
202
192
  return False
@@ -437,7 +427,7 @@ class LogBatchWriter:
437
427
 
438
428
  def get_in(obj, keys, default=None):
439
429
  """
440
- >>> get_in({'a': {'b': 1}}, 'a.b')
430
+ >>> get_in({"a": {"b": 1}}, "a.b")
441
431
  1
442
432
  """
443
433
  if isinstance(keys, str):
@@ -801,34 +791,6 @@ def gen_html_table(header, rows=None):
801
791
  return style + '<table class="tg">\n' + out + "</table>\n\n"
802
792
 
803
793
 
804
- def new_pipe_metadata(
805
- artifact_path: str = None,
806
- cleanup_ttl: int = None,
807
- op_transformers: list[typing.Callable] = None,
808
- ):
809
- from kfp.dsl import PipelineConf
810
-
811
- def _set_artifact_path(task):
812
- from kubernetes import client as k8s_client
813
-
814
- task.add_env_variable(
815
- k8s_client.V1EnvVar(name="MLRUN_ARTIFACT_PATH", value=artifact_path)
816
- )
817
- return task
818
-
819
- conf = PipelineConf()
820
- cleanup_ttl = cleanup_ttl or int(config.kfp_ttl)
821
-
822
- if cleanup_ttl:
823
- conf.set_ttl_seconds_after_finished(cleanup_ttl)
824
- if artifact_path:
825
- conf.add_op_transformer(_set_artifact_path)
826
- if op_transformers:
827
- for op_transformer in op_transformers:
828
- conf.add_op_transformer(op_transformer)
829
- return conf
830
-
831
-
832
794
  def _convert_python_package_version_to_image_tag(version: typing.Optional[str]):
833
795
  return (
834
796
  version.replace("+", "-").replace("0.0.0-", "") if version is not None else None
@@ -1015,17 +977,27 @@ def get_ui_url(project, uid=None):
1015
977
  return url
1016
978
 
1017
979
 
980
+ def get_model_endpoint_url(project, model_name, model_endpoint_id):
981
+ url = ""
982
+ if mlrun.mlconf.resolve_ui_url():
983
+ url = f"{mlrun.mlconf.resolve_ui_url()}/{mlrun.mlconf.ui.projects_prefix}/{project}/models"
984
+ if model_name:
985
+ url += f"/model-endpoints/{model_name}/{model_endpoint_id}/overview"
986
+ return url
987
+
988
+
1018
989
  def get_workflow_url(project, id=None):
1019
990
  url = ""
1020
991
  if mlrun.mlconf.resolve_ui_url():
1021
- url = "{}/{}/{}/jobs/monitor-workflows/workflow/{}".format(
1022
- mlrun.mlconf.resolve_ui_url(), mlrun.mlconf.ui.projects_prefix, project, id
992
+ url = (
993
+ f"{mlrun.mlconf.resolve_ui_url()}/{mlrun.mlconf.ui.projects_prefix}"
994
+ f"/{project}/jobs/monitor-workflows/workflow/{id}"
1023
995
  )
1024
996
  return url
1025
997
 
1026
998
 
1027
999
  def are_strings_in_exception_chain_messages(
1028
- exception: Exception, strings_list=list[str]
1000
+ exception: Exception, strings_list: list[str]
1029
1001
  ) -> bool:
1030
1002
  while exception is not None:
1031
1003
  if any([string in str(exception) for string in strings_list]):
@@ -1138,7 +1110,7 @@ def get_function(function, namespace):
1138
1110
 
1139
1111
 
1140
1112
  def get_handler_extended(
1141
- handler_path: str, context=None, class_args: dict = {}, namespaces=None
1113
+ handler_path: str, context=None, class_args: dict = None, namespaces=None
1142
1114
  ):
1143
1115
  """get function handler from [class_name::]handler string
1144
1116
 
@@ -1148,6 +1120,7 @@ def get_handler_extended(
1148
1120
  :param namespaces: one or list of namespaces/modules to search the handler in
1149
1121
  :return: function handler (callable)
1150
1122
  """
1123
+ class_args = class_args or {}
1151
1124
  if "::" not in handler_path:
1152
1125
  return get_function(handler_path, namespaces)
1153
1126
 
@@ -1224,7 +1197,7 @@ def calculate_dataframe_hash(dataframe: pandas.DataFrame):
1224
1197
  return hashlib.sha1(pandas.util.hash_pandas_object(dataframe).values).hexdigest()
1225
1198
 
1226
1199
 
1227
- def template_artifact_path(artifact_path, project, run_uid="project"):
1200
+ def template_artifact_path(artifact_path, project, run_uid=None):
1228
1201
  """
1229
1202
  Replace {{run.uid}} with the run uid and {{project}} with the project name in the artifact path.
1230
1203
  If no run uid is provided, the word `project` will be used instead as it is assumed to be a project
@@ -1232,6 +1205,7 @@ def template_artifact_path(artifact_path, project, run_uid="project"):
1232
1205
  """
1233
1206
  if not artifact_path:
1234
1207
  return artifact_path
1208
+ run_uid = run_uid or "project"
1235
1209
  artifact_path = artifact_path.replace("{{run.uid}}", run_uid)
1236
1210
  artifact_path = _fill_project_path_template(artifact_path, project)
1237
1211
  return artifact_path
@@ -1291,13 +1265,6 @@ def str_to_timestamp(time_str: str, now_time: Timestamp = None):
1291
1265
  return Timestamp(time_str)
1292
1266
 
1293
1267
 
1294
- def is_legacy_artifact(artifact):
1295
- if isinstance(artifact, dict):
1296
- return "metadata" not in artifact
1297
- else:
1298
- return not hasattr(artifact, "metadata")
1299
-
1300
-
1301
1268
  def is_link_artifact(artifact):
1302
1269
  if isinstance(artifact, dict):
1303
1270
  return (
@@ -1307,7 +1274,7 @@ def is_link_artifact(artifact):
1307
1274
  return artifact.kind == mlrun.common.schemas.ArtifactCategories.link.value
1308
1275
 
1309
1276
 
1310
- def format_run(run: dict, with_project=False) -> dict:
1277
+ def format_run(run: PipelineRun, with_project=False) -> dict:
1311
1278
  fields = [
1312
1279
  "id",
1313
1280
  "name",
@@ -1344,17 +1311,17 @@ def format_run(run: dict, with_project=False) -> dict:
1344
1311
  # pipelines are yet to populate the status or workflow has failed
1345
1312
  # as observed https://jira.iguazeng.com/browse/ML-5195
1346
1313
  # set to unknown to ensure a status is returned
1347
- if run["status"] is None:
1348
- run["status"] = inflection.titleize(mlrun.runtimes.constants.RunStates.unknown)
1314
+ if run.get("status", None) is None:
1315
+ run["status"] = inflection.titleize(
1316
+ mlrun.common.runtimes.constants.RunStates.unknown
1317
+ )
1349
1318
 
1350
1319
  return run
1351
1320
 
1352
1321
 
1353
1322
  def get_in_artifact(artifact: dict, key, default=None, raise_on_missing=False):
1354
1323
  """artifact can be dict or Artifact object"""
1355
- if is_legacy_artifact(artifact):
1356
- return artifact.get(key, default)
1357
- elif key == "kind":
1324
+ if key == "kind":
1358
1325
  return artifact.get(key, default)
1359
1326
  else:
1360
1327
  for block in ["metadata", "spec", "status"]:
@@ -1405,6 +1372,18 @@ def as_number(field_name, field_value):
1405
1372
 
1406
1373
 
1407
1374
  def filter_warnings(action, category):
1375
+ """
1376
+ Decorator to filter warnings
1377
+
1378
+ Example::
1379
+ @filter_warnings("ignore", FutureWarning)
1380
+ def my_function():
1381
+ pass
1382
+
1383
+ :param action: one of "error", "ignore", "always", "default", "module", or "once"
1384
+ :param category: a class that the warning must be a subclass of
1385
+ """
1386
+
1408
1387
  def decorator(function):
1409
1388
  def wrapper(*args, **kwargs):
1410
1389
  # context manager that copies and, upon exit, restores the warnings filter and the showwarning() function.
@@ -1562,3 +1541,90 @@ def is_safe_path(base, filepath, is_symlink=False):
1562
1541
  os.path.abspath(filepath) if not is_symlink else os.path.realpath(filepath)
1563
1542
  )
1564
1543
  return base == os.path.commonpath((base, resolved_filepath))
1544
+
1545
+
1546
+ def get_serving_spec():
1547
+ data = None
1548
+
1549
+ # we will have the serving spec in either mounted config map
1550
+ # or env depending on the size of the spec and configuration
1551
+
1552
+ try:
1553
+ with open(mlrun.common.constants.MLRUN_SERVING_SPEC_PATH) as f:
1554
+ data = f.read()
1555
+ except FileNotFoundError:
1556
+ pass
1557
+
1558
+ if data is None:
1559
+ data = os.environ.get("SERVING_SPEC_ENV", "")
1560
+ if not data:
1561
+ raise mlrun.errors.MLRunInvalidArgumentError(
1562
+ "Failed to find serving spec in env var or config file"
1563
+ )
1564
+ spec = json.loads(data)
1565
+ return spec
1566
+
1567
+
1568
+ def additional_filters_warning(additional_filters, class_name):
1569
+ if additional_filters and any(additional_filters):
1570
+ mlrun.utils.logger.warn(
1571
+ f"additional_filters parameter is not supported in {class_name},"
1572
+ f" parameter has been ignored."
1573
+ )
1574
+
1575
+
1576
+ def validate_component_version_compatibility(
1577
+ component_name: typing.Literal["iguazio", "nuclio"], *min_versions: str
1578
+ ):
1579
+ """
1580
+ :param component_name: Name of the component to validate compatibility for.
1581
+ :param min_versions: Valid minimum version(s) required, assuming no 2 versions has equal major and minor.
1582
+ """
1583
+ parsed_min_versions = [
1584
+ semver.VersionInfo.parse(min_version) for min_version in min_versions
1585
+ ]
1586
+ parsed_current_version = None
1587
+ component_current_version = None
1588
+ try:
1589
+ if component_name == "iguazio":
1590
+ component_current_version = mlrun.mlconf.igz_version
1591
+ parsed_current_version = mlrun.mlconf.get_parsed_igz_version()
1592
+
1593
+ if parsed_current_version:
1594
+ # ignore pre-release and build metadata, as iguazio version always has them, and we only care about the
1595
+ # major, minor, and patch versions
1596
+ parsed_current_version = semver.VersionInfo.parse(
1597
+ f"{parsed_current_version.major}.{parsed_current_version.minor}.{parsed_current_version.patch}"
1598
+ )
1599
+ if component_name == "nuclio":
1600
+ component_current_version = mlrun.mlconf.nuclio_version
1601
+ parsed_current_version = semver.VersionInfo.parse(
1602
+ mlrun.mlconf.nuclio_version
1603
+ )
1604
+ if not parsed_current_version:
1605
+ return True
1606
+ except ValueError:
1607
+ # only log when version is set but invalid
1608
+ if component_current_version:
1609
+ logger.warning(
1610
+ "Unable to parse current version, assuming compatibility",
1611
+ component_name=component_name,
1612
+ current_version=component_current_version,
1613
+ min_versions=min_versions,
1614
+ )
1615
+ return True
1616
+
1617
+ parsed_min_versions.sort(reverse=True)
1618
+ for parsed_min_version in parsed_min_versions:
1619
+ if parsed_current_version < parsed_min_version:
1620
+ return False
1621
+ return True
1622
+
1623
+
1624
+ def format_alert_summary(
1625
+ alert: mlrun.common.schemas.AlertConfig, event_data: mlrun.common.schemas.Event
1626
+ ) -> str:
1627
+ result = alert.summary.replace("{{project}}", alert.project)
1628
+ result = result.replace("{{name}}", alert.name)
1629
+ result = result.replace("{{entity}}", event_data.entity.ids[0])
1630
+ return result
mlrun/utils/http.py CHANGED
@@ -122,7 +122,7 @@ class HTTPSessionWithRetry(requests.Session):
122
122
 
123
123
  self._logger.warning(
124
124
  "Error during request handling, retrying",
125
- exc=str(exc),
125
+ exc=err_to_str(exc),
126
126
  retry_count=retry_count,
127
127
  url=url,
128
128
  method=method,