mlrun 1.6.4rc2__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 (291) hide show
  1. mlrun/__init__.py +11 -1
  2. mlrun/__main__.py +26 -112
  3. mlrun/alerts/__init__.py +15 -0
  4. mlrun/alerts/alert.py +144 -0
  5. mlrun/api/schemas/__init__.py +5 -4
  6. mlrun/artifacts/__init__.py +8 -3
  7. mlrun/artifacts/base.py +46 -257
  8. mlrun/artifacts/dataset.py +11 -192
  9. mlrun/artifacts/manager.py +47 -48
  10. mlrun/artifacts/model.py +31 -159
  11. mlrun/artifacts/plots.py +23 -380
  12. mlrun/common/constants.py +69 -0
  13. mlrun/common/db/sql_session.py +2 -3
  14. mlrun/common/formatters/__init__.py +19 -0
  15. mlrun/common/formatters/artifact.py +21 -0
  16. mlrun/common/formatters/base.py +78 -0
  17. mlrun/common/formatters/function.py +41 -0
  18. mlrun/common/formatters/pipeline.py +53 -0
  19. mlrun/common/formatters/project.py +51 -0
  20. mlrun/common/helpers.py +1 -2
  21. mlrun/common/model_monitoring/helpers.py +9 -5
  22. mlrun/{runtimes → common/runtimes}/constants.py +37 -9
  23. mlrun/common/schemas/__init__.py +24 -4
  24. mlrun/common/schemas/alert.py +203 -0
  25. mlrun/common/schemas/api_gateway.py +148 -0
  26. mlrun/common/schemas/artifact.py +18 -8
  27. mlrun/common/schemas/auth.py +11 -5
  28. mlrun/common/schemas/background_task.py +1 -1
  29. mlrun/common/schemas/client_spec.py +4 -1
  30. mlrun/common/schemas/feature_store.py +16 -16
  31. mlrun/common/schemas/frontend_spec.py +8 -7
  32. mlrun/common/schemas/function.py +5 -1
  33. mlrun/common/schemas/hub.py +11 -18
  34. mlrun/common/schemas/memory_reports.py +2 -2
  35. mlrun/common/schemas/model_monitoring/__init__.py +18 -3
  36. mlrun/common/schemas/model_monitoring/constants.py +83 -26
  37. mlrun/common/schemas/model_monitoring/grafana.py +13 -9
  38. mlrun/common/schemas/model_monitoring/model_endpoints.py +99 -16
  39. mlrun/common/schemas/notification.py +4 -4
  40. mlrun/common/schemas/object.py +2 -2
  41. mlrun/{runtimes/mpijob/v1alpha1.py → common/schemas/pagination.py} +10 -13
  42. mlrun/common/schemas/pipeline.py +1 -10
  43. mlrun/common/schemas/project.py +24 -23
  44. mlrun/common/schemas/runtime_resource.py +8 -12
  45. mlrun/common/schemas/schedule.py +3 -3
  46. mlrun/common/schemas/tag.py +1 -2
  47. mlrun/common/schemas/workflow.py +2 -2
  48. mlrun/common/types.py +7 -1
  49. mlrun/config.py +54 -17
  50. mlrun/data_types/to_pandas.py +10 -12
  51. mlrun/datastore/__init__.py +5 -8
  52. mlrun/datastore/alibaba_oss.py +130 -0
  53. mlrun/datastore/azure_blob.py +17 -5
  54. mlrun/datastore/base.py +62 -39
  55. mlrun/datastore/datastore.py +28 -9
  56. mlrun/datastore/datastore_profile.py +146 -20
  57. mlrun/datastore/filestore.py +0 -1
  58. mlrun/datastore/google_cloud_storage.py +6 -2
  59. mlrun/datastore/hdfs.py +56 -0
  60. mlrun/datastore/inmem.py +2 -2
  61. mlrun/datastore/redis.py +6 -2
  62. mlrun/datastore/s3.py +9 -0
  63. mlrun/datastore/snowflake_utils.py +43 -0
  64. mlrun/datastore/sources.py +201 -96
  65. mlrun/datastore/spark_utils.py +1 -2
  66. mlrun/datastore/store_resources.py +7 -7
  67. mlrun/datastore/targets.py +358 -104
  68. mlrun/datastore/utils.py +72 -58
  69. mlrun/datastore/v3io.py +5 -1
  70. mlrun/db/base.py +185 -35
  71. mlrun/db/factory.py +1 -1
  72. mlrun/db/httpdb.py +614 -179
  73. mlrun/db/nopdb.py +210 -26
  74. mlrun/errors.py +12 -1
  75. mlrun/execution.py +41 -24
  76. mlrun/feature_store/__init__.py +0 -2
  77. mlrun/feature_store/api.py +40 -72
  78. mlrun/feature_store/common.py +1 -1
  79. mlrun/feature_store/feature_set.py +76 -55
  80. mlrun/feature_store/feature_vector.py +28 -30
  81. mlrun/feature_store/ingestion.py +7 -6
  82. mlrun/feature_store/retrieval/base.py +16 -11
  83. mlrun/feature_store/retrieval/conversion.py +11 -13
  84. mlrun/feature_store/retrieval/dask_merger.py +2 -0
  85. mlrun/feature_store/retrieval/job.py +9 -3
  86. mlrun/feature_store/retrieval/local_merger.py +2 -0
  87. mlrun/feature_store/retrieval/spark_merger.py +34 -24
  88. mlrun/feature_store/steps.py +37 -34
  89. mlrun/features.py +9 -20
  90. mlrun/frameworks/_common/artifacts_library.py +9 -9
  91. mlrun/frameworks/_common/mlrun_interface.py +5 -5
  92. mlrun/frameworks/_common/model_handler.py +48 -48
  93. mlrun/frameworks/_common/plan.py +2 -3
  94. mlrun/frameworks/_common/producer.py +3 -4
  95. mlrun/frameworks/_common/utils.py +5 -5
  96. mlrun/frameworks/_dl_common/loggers/logger.py +6 -7
  97. mlrun/frameworks/_dl_common/loggers/mlrun_logger.py +9 -9
  98. mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +23 -47
  99. mlrun/frameworks/_ml_common/artifacts_library.py +1 -2
  100. mlrun/frameworks/_ml_common/loggers/logger.py +3 -4
  101. mlrun/frameworks/_ml_common/loggers/mlrun_logger.py +4 -5
  102. mlrun/frameworks/_ml_common/model_handler.py +24 -24
  103. mlrun/frameworks/_ml_common/pkl_model_server.py +2 -2
  104. mlrun/frameworks/_ml_common/plan.py +1 -1
  105. mlrun/frameworks/_ml_common/plans/calibration_curve_plan.py +2 -3
  106. mlrun/frameworks/_ml_common/plans/confusion_matrix_plan.py +2 -3
  107. mlrun/frameworks/_ml_common/plans/dataset_plan.py +3 -3
  108. mlrun/frameworks/_ml_common/plans/feature_importance_plan.py +3 -3
  109. mlrun/frameworks/_ml_common/plans/roc_curve_plan.py +4 -4
  110. mlrun/frameworks/_ml_common/utils.py +4 -4
  111. mlrun/frameworks/auto_mlrun/auto_mlrun.py +9 -9
  112. mlrun/frameworks/huggingface/model_server.py +4 -4
  113. mlrun/frameworks/lgbm/__init__.py +33 -33
  114. mlrun/frameworks/lgbm/callbacks/callback.py +2 -4
  115. mlrun/frameworks/lgbm/callbacks/logging_callback.py +4 -5
  116. mlrun/frameworks/lgbm/callbacks/mlrun_logging_callback.py +4 -5
  117. mlrun/frameworks/lgbm/mlrun_interfaces/booster_mlrun_interface.py +1 -3
  118. mlrun/frameworks/lgbm/mlrun_interfaces/mlrun_interface.py +6 -6
  119. mlrun/frameworks/lgbm/model_handler.py +10 -10
  120. mlrun/frameworks/lgbm/model_server.py +6 -6
  121. mlrun/frameworks/lgbm/utils.py +5 -5
  122. mlrun/frameworks/onnx/dataset.py +8 -8
  123. mlrun/frameworks/onnx/mlrun_interface.py +3 -3
  124. mlrun/frameworks/onnx/model_handler.py +6 -6
  125. mlrun/frameworks/onnx/model_server.py +7 -7
  126. mlrun/frameworks/parallel_coordinates.py +4 -3
  127. mlrun/frameworks/pytorch/__init__.py +18 -18
  128. mlrun/frameworks/pytorch/callbacks/callback.py +4 -5
  129. mlrun/frameworks/pytorch/callbacks/logging_callback.py +17 -17
  130. mlrun/frameworks/pytorch/callbacks/mlrun_logging_callback.py +11 -11
  131. mlrun/frameworks/pytorch/callbacks/tensorboard_logging_callback.py +23 -29
  132. mlrun/frameworks/pytorch/callbacks_handler.py +38 -38
  133. mlrun/frameworks/pytorch/mlrun_interface.py +20 -20
  134. mlrun/frameworks/pytorch/model_handler.py +17 -17
  135. mlrun/frameworks/pytorch/model_server.py +7 -7
  136. mlrun/frameworks/sklearn/__init__.py +13 -13
  137. mlrun/frameworks/sklearn/estimator.py +4 -4
  138. mlrun/frameworks/sklearn/metrics_library.py +14 -14
  139. mlrun/frameworks/sklearn/mlrun_interface.py +3 -6
  140. mlrun/frameworks/sklearn/model_handler.py +2 -2
  141. mlrun/frameworks/tf_keras/__init__.py +10 -7
  142. mlrun/frameworks/tf_keras/callbacks/logging_callback.py +15 -15
  143. mlrun/frameworks/tf_keras/callbacks/mlrun_logging_callback.py +11 -11
  144. mlrun/frameworks/tf_keras/callbacks/tensorboard_logging_callback.py +19 -23
  145. mlrun/frameworks/tf_keras/mlrun_interface.py +9 -11
  146. mlrun/frameworks/tf_keras/model_handler.py +14 -14
  147. mlrun/frameworks/tf_keras/model_server.py +6 -6
  148. mlrun/frameworks/xgboost/__init__.py +13 -13
  149. mlrun/frameworks/xgboost/model_handler.py +6 -6
  150. mlrun/k8s_utils.py +14 -16
  151. mlrun/launcher/__init__.py +1 -1
  152. mlrun/launcher/base.py +16 -15
  153. mlrun/launcher/client.py +8 -6
  154. mlrun/launcher/factory.py +1 -1
  155. mlrun/launcher/local.py +17 -11
  156. mlrun/launcher/remote.py +16 -10
  157. mlrun/lists.py +7 -6
  158. mlrun/model.py +238 -73
  159. mlrun/model_monitoring/__init__.py +1 -1
  160. mlrun/model_monitoring/api.py +138 -315
  161. mlrun/model_monitoring/application.py +5 -296
  162. mlrun/model_monitoring/applications/__init__.py +24 -0
  163. mlrun/model_monitoring/applications/_application_steps.py +157 -0
  164. mlrun/model_monitoring/applications/base.py +282 -0
  165. mlrun/model_monitoring/applications/context.py +214 -0
  166. mlrun/model_monitoring/applications/evidently_base.py +211 -0
  167. mlrun/model_monitoring/applications/histogram_data_drift.py +349 -0
  168. mlrun/model_monitoring/applications/results.py +99 -0
  169. mlrun/model_monitoring/controller.py +104 -84
  170. mlrun/model_monitoring/controller_handler.py +13 -5
  171. mlrun/model_monitoring/db/__init__.py +18 -0
  172. mlrun/model_monitoring/{stores → db/stores}/__init__.py +43 -36
  173. mlrun/model_monitoring/db/stores/base/__init__.py +15 -0
  174. mlrun/model_monitoring/{stores/model_endpoint_store.py → db/stores/base/store.py} +64 -40
  175. mlrun/model_monitoring/db/stores/sqldb/__init__.py +13 -0
  176. mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +71 -0
  177. mlrun/model_monitoring/{stores → db/stores/sqldb}/models/base.py +109 -5
  178. mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +88 -0
  179. mlrun/model_monitoring/{stores/models/mysql.py → db/stores/sqldb/models/sqlite.py} +19 -13
  180. mlrun/model_monitoring/db/stores/sqldb/sql_store.py +684 -0
  181. mlrun/model_monitoring/db/stores/v3io_kv/__init__.py +13 -0
  182. mlrun/model_monitoring/{stores/kv_model_endpoint_store.py → db/stores/v3io_kv/kv_store.py} +310 -165
  183. mlrun/model_monitoring/db/tsdb/__init__.py +100 -0
  184. mlrun/model_monitoring/db/tsdb/base.py +329 -0
  185. mlrun/model_monitoring/db/tsdb/helpers.py +30 -0
  186. mlrun/model_monitoring/db/tsdb/tdengine/__init__.py +15 -0
  187. mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +240 -0
  188. mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +45 -0
  189. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +397 -0
  190. mlrun/model_monitoring/db/tsdb/v3io/__init__.py +15 -0
  191. mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +117 -0
  192. mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +630 -0
  193. mlrun/model_monitoring/evidently_application.py +6 -118
  194. mlrun/model_monitoring/features_drift_table.py +134 -106
  195. mlrun/model_monitoring/helpers.py +127 -28
  196. mlrun/model_monitoring/metrics/__init__.py +13 -0
  197. mlrun/model_monitoring/metrics/histogram_distance.py +127 -0
  198. mlrun/model_monitoring/model_endpoint.py +3 -2
  199. mlrun/model_monitoring/prometheus.py +1 -4
  200. mlrun/model_monitoring/stream_processing.py +62 -231
  201. mlrun/model_monitoring/tracking_policy.py +9 -2
  202. mlrun/model_monitoring/writer.py +152 -124
  203. mlrun/package/__init__.py +6 -6
  204. mlrun/package/context_handler.py +5 -5
  205. mlrun/package/packager.py +7 -7
  206. mlrun/package/packagers/default_packager.py +6 -6
  207. mlrun/package/packagers/numpy_packagers.py +15 -15
  208. mlrun/package/packagers/pandas_packagers.py +5 -5
  209. mlrun/package/packagers/python_standard_library_packagers.py +10 -10
  210. mlrun/package/packagers_manager.py +19 -23
  211. mlrun/package/utils/_formatter.py +6 -6
  212. mlrun/package/utils/_pickler.py +2 -2
  213. mlrun/package/utils/_supported_format.py +4 -4
  214. mlrun/package/utils/log_hint_utils.py +2 -2
  215. mlrun/package/utils/type_hint_utils.py +4 -9
  216. mlrun/platforms/__init__.py +11 -10
  217. mlrun/platforms/iguazio.py +24 -203
  218. mlrun/projects/operations.py +35 -21
  219. mlrun/projects/pipelines.py +68 -99
  220. mlrun/projects/project.py +830 -266
  221. mlrun/render.py +3 -11
  222. mlrun/run.py +162 -166
  223. mlrun/runtimes/__init__.py +62 -7
  224. mlrun/runtimes/base.py +39 -32
  225. mlrun/runtimes/daskjob.py +8 -8
  226. mlrun/runtimes/databricks_job/databricks_cancel_task.py +1 -1
  227. mlrun/runtimes/databricks_job/databricks_runtime.py +7 -7
  228. mlrun/runtimes/databricks_job/databricks_wrapper.py +1 -1
  229. mlrun/runtimes/funcdoc.py +0 -28
  230. mlrun/runtimes/function_reference.py +1 -1
  231. mlrun/runtimes/kubejob.py +28 -122
  232. mlrun/runtimes/local.py +6 -3
  233. mlrun/runtimes/mpijob/__init__.py +0 -20
  234. mlrun/runtimes/mpijob/abstract.py +9 -10
  235. mlrun/runtimes/mpijob/v1.py +1 -1
  236. mlrun/{model_monitoring/stores/models/sqlite.py → runtimes/nuclio/__init__.py} +7 -9
  237. mlrun/runtimes/nuclio/api_gateway.py +709 -0
  238. mlrun/runtimes/nuclio/application/__init__.py +15 -0
  239. mlrun/runtimes/nuclio/application/application.py +523 -0
  240. mlrun/runtimes/nuclio/application/reverse_proxy.go +95 -0
  241. mlrun/runtimes/{function.py → nuclio/function.py} +112 -73
  242. mlrun/runtimes/{nuclio.py → nuclio/nuclio.py} +6 -6
  243. mlrun/runtimes/{serving.py → nuclio/serving.py} +45 -51
  244. mlrun/runtimes/pod.py +286 -88
  245. mlrun/runtimes/remotesparkjob.py +2 -2
  246. mlrun/runtimes/sparkjob/spark3job.py +51 -34
  247. mlrun/runtimes/utils.py +7 -75
  248. mlrun/secrets.py +9 -5
  249. mlrun/serving/remote.py +2 -7
  250. mlrun/serving/routers.py +13 -10
  251. mlrun/serving/server.py +22 -26
  252. mlrun/serving/states.py +99 -25
  253. mlrun/serving/utils.py +3 -3
  254. mlrun/serving/v1_serving.py +6 -7
  255. mlrun/serving/v2_serving.py +59 -20
  256. mlrun/track/tracker.py +2 -1
  257. mlrun/track/tracker_manager.py +3 -3
  258. mlrun/track/trackers/mlflow_tracker.py +1 -2
  259. mlrun/utils/async_http.py +5 -7
  260. mlrun/utils/azure_vault.py +1 -1
  261. mlrun/utils/clones.py +1 -2
  262. mlrun/utils/condition_evaluator.py +3 -3
  263. mlrun/utils/db.py +3 -3
  264. mlrun/utils/helpers.py +183 -197
  265. mlrun/utils/http.py +2 -5
  266. mlrun/utils/logger.py +76 -14
  267. mlrun/utils/notifications/notification/__init__.py +17 -12
  268. mlrun/utils/notifications/notification/base.py +14 -2
  269. mlrun/utils/notifications/notification/console.py +2 -0
  270. mlrun/utils/notifications/notification/git.py +3 -1
  271. mlrun/utils/notifications/notification/ipython.py +3 -1
  272. mlrun/utils/notifications/notification/slack.py +101 -21
  273. mlrun/utils/notifications/notification/webhook.py +11 -1
  274. mlrun/utils/notifications/notification_pusher.py +155 -30
  275. mlrun/utils/retryer.py +208 -0
  276. mlrun/utils/singleton.py +1 -1
  277. mlrun/utils/v3io_clients.py +2 -4
  278. mlrun/utils/version/version.json +2 -2
  279. mlrun/utils/version/version.py +2 -6
  280. {mlrun-1.6.4rc2.dist-info → mlrun-1.7.0rc20.dist-info}/METADATA +31 -19
  281. mlrun-1.7.0rc20.dist-info/RECORD +353 -0
  282. mlrun/kfpops.py +0 -868
  283. mlrun/model_monitoring/batch.py +0 -1095
  284. mlrun/model_monitoring/stores/models/__init__.py +0 -27
  285. mlrun/model_monitoring/stores/sql_model_endpoint_store.py +0 -384
  286. mlrun/platforms/other.py +0 -306
  287. mlrun-1.6.4rc2.dist-info/RECORD +0 -314
  288. {mlrun-1.6.4rc2.dist-info → mlrun-1.7.0rc20.dist-info}/LICENSE +0 -0
  289. {mlrun-1.6.4rc2.dist-info → mlrun-1.7.0rc20.dist-info}/WHEEL +0 -0
  290. {mlrun-1.6.4rc2.dist-info → mlrun-1.7.0rc20.dist-info}/entry_points.txt +0 -0
  291. {mlrun-1.6.4rc2.dist-info → mlrun-1.7.0rc20.dist-info}/top_level.txt +0 -0
mlrun/utils/helpers.py CHANGED
@@ -12,6 +12,7 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ import asyncio
15
16
  import enum
16
17
  import functools
17
18
  import hashlib
@@ -22,16 +23,14 @@ import os
22
23
  import re
23
24
  import string
24
25
  import sys
25
- import time
26
26
  import typing
27
27
  import warnings
28
28
  from datetime import datetime, timezone
29
29
  from importlib import import_module
30
30
  from os import path
31
31
  from types import ModuleType
32
- from typing import Any, List, Optional, Tuple
32
+ from typing import Any, Optional
33
33
 
34
- import anyio
35
34
  import git
36
35
  import inflection
37
36
  import numpy as np
@@ -40,7 +39,7 @@ import pandas
40
39
  import semver
41
40
  import yaml
42
41
  from dateutil import parser
43
- from deprecated import deprecated
42
+ from mlrun_pipelines.models import PipelineRun
44
43
  from pandas._libs.tslibs.timestamps import Timedelta, Timestamp
45
44
  from yaml.representer import RepresenterError
46
45
 
@@ -50,10 +49,17 @@ import mlrun.common.schemas
50
49
  import mlrun.errors
51
50
  import mlrun.utils.regex
52
51
  import mlrun.utils.version.version
52
+ from mlrun.common.constants import MYSQL_MEDIUMBLOB_SIZE_BYTES
53
53
  from mlrun.config import config
54
- from mlrun.errors import err_to_str
55
54
 
56
55
  from .logger import create_logger
56
+ from .retryer import ( # noqa: F401
57
+ AsyncRetryer,
58
+ Retryer,
59
+ create_exponential_backoff,
60
+ create_linear_backoff,
61
+ create_step_backoff,
62
+ )
57
63
 
58
64
  yaml.Dumper.ignore_aliases = lambda *args: True
59
65
  _missing = object()
@@ -70,19 +76,6 @@ class OverwriteBuildParamsWarning(FutureWarning):
70
76
  pass
71
77
 
72
78
 
73
- # TODO: remove in 1.7.0
74
- @deprecated(
75
- version="1.5.0",
76
- reason="'parse_versioned_object_uri' will be removed from this file in 1.7.0, use "
77
- "'mlrun.common.helpers.parse_versioned_object_uri' instead",
78
- category=FutureWarning,
79
- )
80
- def parse_versioned_object_uri(uri: str, default_project: str = ""):
81
- return mlrun.common.helpers.parse_versioned_object_uri(
82
- uri=uri, default_project=default_project
83
- )
84
-
85
-
86
79
  class StorePrefix:
87
80
  """map mlrun store objects to prefixes"""
88
81
 
@@ -113,14 +106,9 @@ class StorePrefix:
113
106
 
114
107
 
115
108
  def get_artifact_target(item: dict, project=None):
116
- if is_legacy_artifact(item):
117
- db_key = item.get("db_key")
118
- project_str = project or item.get("project")
119
- tree = item.get("tree")
120
- else:
121
- db_key = item["spec"].get("db_key")
122
- project_str = project or item["metadata"].get("project")
123
- 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")
124
112
 
125
113
  kind = item.get("kind")
126
114
  if kind in ["dataset", "model", "artifact"] and db_key:
@@ -129,11 +117,15 @@ def get_artifact_target(item: dict, project=None):
129
117
  target = f"{target}@{tree}"
130
118
  return target
131
119
 
132
- return (
133
- item.get("target_path")
134
- if is_legacy_artifact(item)
135
- else item["spec"].get("target_path")
136
- )
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")
137
129
 
138
130
 
139
131
  logger = create_logger(config.log_level, config.log_formatter, "mlrun", sys.stdout)
@@ -189,8 +181,12 @@ def verify_field_regex(
189
181
  )
190
182
  if mode == mlrun.common.schemas.RegexMatchModes.all:
191
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"
192
188
  raise mlrun.errors.MLRunInvalidArgumentError(
193
- f"Field '{field_name[:max_chars]}' is malformed. '{field_value[:max_chars]}' "
189
+ f"Field '{field_name}' is malformed. '{field_value}' "
194
190
  f"does not match required pattern: {pattern}"
195
191
  )
196
192
  return False
@@ -265,6 +261,17 @@ def validate_artifact_key_name(
265
261
  )
266
262
 
267
263
 
264
+ def validate_inline_artifact_body_size(body: typing.Union[str, bytes, None]) -> None:
265
+ if body and len(body) > MYSQL_MEDIUMBLOB_SIZE_BYTES:
266
+ raise mlrun.errors.MLRunBadRequestError(
267
+ "The body of the artifact exceeds the maximum allowed size. "
268
+ "Avoid embedding the artifact body. "
269
+ "This increases the size of the project yaml file and could affect the project during loading and saving. "
270
+ "More information is available at"
271
+ "https://docs.mlrun.org/en/latest/projects/automate-project-git-source.html#setting-and-registering-the-project-artifacts"
272
+ )
273
+
274
+
268
275
  def validate_v3io_stream_consumer_group(
269
276
  value: str, raise_on_failure: bool = True
270
277
  ) -> bool:
@@ -276,12 +283,12 @@ def validate_v3io_stream_consumer_group(
276
283
  )
277
284
 
278
285
 
279
- def get_regex_list_as_string(regex_list: List) -> str:
286
+ def get_regex_list_as_string(regex_list: list) -> str:
280
287
  """
281
288
  This function is used to combine a list of regex strings into a single regex,
282
289
  with and condition between them.
283
290
  """
284
- return "".join(["(?={regex})".format(regex=regex) for regex in regex_list]) + ".*$"
291
+ return "".join([f"(?={regex})" for regex in regex_list]) + ".*$"
285
292
 
286
293
 
287
294
  def tag_name_regex_as_string() -> str:
@@ -420,7 +427,7 @@ class LogBatchWriter:
420
427
 
421
428
  def get_in(obj, keys, default=None):
422
429
  """
423
- >>> get_in({'a': {'b': 1}}, 'a.b')
430
+ >>> get_in({"a": {"b": 1}}, "a.b")
424
431
  1
425
432
  """
426
433
  if isinstance(keys, str):
@@ -698,7 +705,7 @@ def generate_artifact_uri(project, key, tag=None, iter=None, tree=None):
698
705
  return artifact_uri
699
706
 
700
707
 
701
- def extend_hub_uri_if_needed(uri) -> Tuple[str, bool]:
708
+ def extend_hub_uri_if_needed(uri) -> tuple[str, bool]:
702
709
  """
703
710
  Retrieve the full uri of the item's yaml in the hub.
704
711
 
@@ -784,34 +791,6 @@ def gen_html_table(header, rows=None):
784
791
  return style + '<table class="tg">\n' + out + "</table>\n\n"
785
792
 
786
793
 
787
- def new_pipe_metadata(
788
- artifact_path: str = None,
789
- cleanup_ttl: int = None,
790
- op_transformers: typing.List[typing.Callable] = None,
791
- ):
792
- from kfp.dsl import PipelineConf
793
-
794
- def _set_artifact_path(task):
795
- from kubernetes import client as k8s_client
796
-
797
- task.add_env_variable(
798
- k8s_client.V1EnvVar(name="MLRUN_ARTIFACT_PATH", value=artifact_path)
799
- )
800
- return task
801
-
802
- conf = PipelineConf()
803
- cleanup_ttl = cleanup_ttl or int(config.kfp_ttl)
804
-
805
- if cleanup_ttl:
806
- conf.set_ttl_seconds_after_finished(cleanup_ttl)
807
- if artifact_path:
808
- conf.add_op_transformer(_set_artifact_path)
809
- if op_transformers:
810
- for op_transformer in op_transformers:
811
- conf.add_op_transformer(op_transformer)
812
- return conf
813
-
814
-
815
794
  def _convert_python_package_version_to_image_tag(version: typing.Optional[str]):
816
795
  return (
817
796
  version.replace("+", "-").replace("0.0.0-", "") if version is not None else None
@@ -893,7 +872,7 @@ def get_docker_repository_or_default(repository: str) -> str:
893
872
  return repository
894
873
 
895
874
 
896
- def get_parsed_docker_registry() -> Tuple[Optional[str], Optional[str]]:
875
+ def get_parsed_docker_registry() -> tuple[Optional[str], Optional[str]]:
897
876
  # according to https://stackoverflow.com/questions/37861791/how-are-docker-image-names-parsed
898
877
  docker_registry = config.httpdb.builder.docker_registry or ""
899
878
  first_slash_index = docker_registry.find("/")
@@ -947,65 +926,27 @@ def fill_function_hash(function_dict, tag=""):
947
926
  return fill_object_hash(function_dict, "hash", tag)
948
927
 
949
928
 
950
- def create_linear_backoff(base=2, coefficient=2, stop_value=120):
951
- """
952
- Create a generator of linear backoff. Check out usage example in test_helpers.py
953
- """
954
- x = 0
955
- comparison = min if coefficient >= 0 else max
956
-
957
- while True:
958
- next_value = comparison(base + x * coefficient, stop_value)
959
- yield next_value
960
- x += 1
961
-
962
-
963
- def create_step_backoff(steps=None):
964
- """
965
- Create a generator of steps backoff.
966
- Example: steps = [[2, 5], [20, 10], [120, None]] will produce a generator in which the first 5
967
- values will be 2, the next 10 values will be 20 and the rest will be 120.
968
- :param steps: a list of lists [step_value, number_of_iteration_in_this_step]
969
- """
970
- steps = steps if steps is not None else [[2, 10], [10, 10], [120, None]]
971
- steps = iter(steps)
972
-
973
- # Get first step
974
- step = next(steps)
975
- while True:
976
- current_step_value, current_step_remain = step
977
- if current_step_remain == 0:
978
- # No more in this step, moving on
979
- step = next(steps)
980
- elif current_step_remain is None:
981
- # We are in the last step, staying here forever
982
- yield current_step_value
983
- elif current_step_remain > 0:
984
- # Still more remains in this step, just reduce the remaining number
985
- step[1] -= 1
986
- yield current_step_value
987
-
988
-
989
- def create_exponential_backoff(base=2, max_value=120, scale_factor=1):
929
+ def retry_until_successful(
930
+ backoff: int, timeout: int, logger, verbose: bool, _function, *args, **kwargs
931
+ ):
990
932
  """
991
- Create a generator of exponential backoff. Check out usage example in test_helpers.py
992
- :param base: exponent base
993
- :param max_value: max limit on the result
994
- :param scale_factor: factor to be used as linear scaling coefficient
933
+ Runs function with given *args and **kwargs.
934
+ Tries to run it until success or timeout reached (timeout is optional)
935
+ :param backoff: can either be a:
936
+ - number (int / float) that will be used as interval.
937
+ - generator of waiting intervals. (support next())
938
+ :param timeout: pass None if timeout is not wanted, number of seconds if it is
939
+ :param logger: a logger so we can log the failures
940
+ :param verbose: whether to log the failure on each retry
941
+ :param _function: function to run
942
+ :param args: functions args
943
+ :param kwargs: functions kwargs
944
+ :return: function result
995
945
  """
996
- exponent = 1
997
- while True:
998
- # This "complex" implementation (unlike the one in linear backoff) is to avoid exponent growing too fast and
999
- # risking going behind max_int
1000
- next_value = scale_factor * (base**exponent)
1001
- if next_value < max_value:
1002
- exponent += 1
1003
- yield next_value
1004
- else:
1005
- yield max_value
946
+ return Retryer(backoff, timeout, logger, verbose, _function, *args, **kwargs).run()
1006
947
 
1007
948
 
1008
- def retry_until_successful(
949
+ async def retry_until_successful_async(
1009
950
  backoff: int, timeout: int, logger, verbose: bool, _function, *args, **kwargs
1010
951
  ):
1011
952
  """
@@ -1022,80 +963,41 @@ def retry_until_successful(
1022
963
  :param kwargs: functions kwargs
1023
964
  :return: function result
1024
965
  """
1025
- start_time = time.time()
1026
- last_exception = None
1027
-
1028
- # Check if backoff is just a simple interval
1029
- if isinstance(backoff, int) or isinstance(backoff, float):
1030
- backoff = create_linear_backoff(base=backoff, coefficient=0)
1031
-
1032
- first_interval = next(backoff)
1033
- if timeout and timeout <= first_interval:
1034
- logger.warning(
1035
- f"Timeout ({timeout}) must be higher than backoff ({first_interval})."
1036
- f" Set timeout to be higher than backoff."
1037
- )
1038
-
1039
- # If deadline was not provided or deadline not reached
1040
- while timeout is None or time.time() < start_time + timeout:
1041
- next_interval = first_interval or next(backoff)
1042
- first_interval = None
1043
- try:
1044
- result = _function(*args, **kwargs)
1045
- return result
1046
-
1047
- except mlrun.errors.MLRunFatalFailureError as exc:
1048
- raise exc.original_exception
1049
- except Exception as exc:
1050
- last_exception = exc
1051
-
1052
- # If next interval is within allowed time period - wait on interval, abort otherwise
1053
- if timeout is None or time.time() + next_interval < start_time + timeout:
1054
- if logger is not None and verbose:
1055
- logger.debug(
1056
- f"Operation not yet successful, Retrying in {next_interval} seconds."
1057
- f" exc: {err_to_str(exc)}"
1058
- )
1059
-
1060
- time.sleep(next_interval)
1061
- else:
1062
- break
1063
-
1064
- if logger is not None:
1065
- logger.warning(
1066
- f"Operation did not complete on time. last exception: {last_exception}"
1067
- )
1068
-
1069
- raise mlrun.errors.MLRunRetryExhaustedError(
1070
- f"Failed to execute command by the given deadline."
1071
- f" last_exception: {last_exception},"
1072
- f" function_name: {_function.__name__},"
1073
- f" timeout: {timeout}"
1074
- ) from last_exception
966
+ return await AsyncRetryer(
967
+ backoff, timeout, logger, verbose, _function, *args, **kwargs
968
+ ).run()
1075
969
 
1076
970
 
1077
971
  def get_ui_url(project, uid=None):
1078
972
  url = ""
1079
973
  if mlrun.mlconf.resolve_ui_url():
1080
- url = "{}/{}/{}/jobs".format(
1081
- mlrun.mlconf.resolve_ui_url(), mlrun.mlconf.ui.projects_prefix, project
1082
- )
974
+ url = f"{mlrun.mlconf.resolve_ui_url()}/{mlrun.mlconf.ui.projects_prefix}/{project}/jobs"
1083
975
  if uid:
1084
976
  url += f"/monitor/{uid}/overview"
1085
977
  return url
1086
978
 
1087
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
+
1088
989
  def get_workflow_url(project, id=None):
1089
990
  url = ""
1090
991
  if mlrun.mlconf.resolve_ui_url():
1091
- url = "{}/{}/{}/jobs/monitor-workflows/workflow/{}".format(
1092
- 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}"
1093
995
  )
1094
996
  return url
1095
997
 
1096
998
 
1097
999
  def are_strings_in_exception_chain_messages(
1098
- exception: Exception, strings_list=typing.List[str]
1000
+ exception: Exception, strings_list: list[str]
1099
1001
  ) -> bool:
1100
1002
  while exception is not None:
1101
1003
  if any([string in str(exception) for string in strings_list]):
@@ -1208,7 +1110,7 @@ def get_function(function, namespace):
1208
1110
 
1209
1111
 
1210
1112
  def get_handler_extended(
1211
- handler_path: str, context=None, class_args: dict = {}, namespaces=None
1113
+ handler_path: str, context=None, class_args: dict = None, namespaces=None
1212
1114
  ):
1213
1115
  """get function handler from [class_name::]handler string
1214
1116
 
@@ -1218,6 +1120,7 @@ def get_handler_extended(
1218
1120
  :param namespaces: one or list of namespaces/modules to search the handler in
1219
1121
  :return: function handler (callable)
1220
1122
  """
1123
+ class_args = class_args or {}
1221
1124
  if "::" not in handler_path:
1222
1125
  return get_function(handler_path, namespaces)
1223
1126
 
@@ -1275,7 +1178,7 @@ def has_timezone(timestamp):
1275
1178
  return False
1276
1179
 
1277
1180
 
1278
- def as_list(element: Any) -> List[Any]:
1181
+ def as_list(element: Any) -> list[Any]:
1279
1182
  return element if isinstance(element, list) else [element]
1280
1183
 
1281
1184
 
@@ -1294,7 +1197,7 @@ def calculate_dataframe_hash(dataframe: pandas.DataFrame):
1294
1197
  return hashlib.sha1(pandas.util.hash_pandas_object(dataframe).values).hexdigest()
1295
1198
 
1296
1199
 
1297
- def template_artifact_path(artifact_path, project, run_uid="project"):
1200
+ def template_artifact_path(artifact_path, project, run_uid=None):
1298
1201
  """
1299
1202
  Replace {{run.uid}} with the run uid and {{project}} with the project name in the artifact path.
1300
1203
  If no run uid is provided, the word `project` will be used instead as it is assumed to be a project
@@ -1302,6 +1205,7 @@ def template_artifact_path(artifact_path, project, run_uid="project"):
1302
1205
  """
1303
1206
  if not artifact_path:
1304
1207
  return artifact_path
1208
+ run_uid = run_uid or "project"
1305
1209
  artifact_path = artifact_path.replace("{{run.uid}}", run_uid)
1306
1210
  artifact_path = _fill_project_path_template(artifact_path, project)
1307
1211
  return artifact_path
@@ -1361,13 +1265,6 @@ def str_to_timestamp(time_str: str, now_time: Timestamp = None):
1361
1265
  return Timestamp(time_str)
1362
1266
 
1363
1267
 
1364
- def is_legacy_artifact(artifact):
1365
- if isinstance(artifact, dict):
1366
- return "metadata" not in artifact
1367
- else:
1368
- return not hasattr(artifact, "metadata")
1369
-
1370
-
1371
1268
  def is_link_artifact(artifact):
1372
1269
  if isinstance(artifact, dict):
1373
1270
  return (
@@ -1377,7 +1274,7 @@ def is_link_artifact(artifact):
1377
1274
  return artifact.kind == mlrun.common.schemas.ArtifactCategories.link.value
1378
1275
 
1379
1276
 
1380
- def format_run(run: dict, with_project=False) -> dict:
1277
+ def format_run(run: PipelineRun, with_project=False) -> dict:
1381
1278
  fields = [
1382
1279
  "id",
1383
1280
  "name",
@@ -1414,17 +1311,17 @@ def format_run(run: dict, with_project=False) -> dict:
1414
1311
  # pipelines are yet to populate the status or workflow has failed
1415
1312
  # as observed https://jira.iguazeng.com/browse/ML-5195
1416
1313
  # set to unknown to ensure a status is returned
1417
- if run["status"] is None:
1418
- 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
+ )
1419
1318
 
1420
1319
  return run
1421
1320
 
1422
1321
 
1423
1322
  def get_in_artifact(artifact: dict, key, default=None, raise_on_missing=False):
1424
1323
  """artifact can be dict or Artifact object"""
1425
- if is_legacy_artifact(artifact):
1426
- return artifact.get(key, default)
1427
- elif key == "kind":
1324
+ if key == "kind":
1428
1325
  return artifact.get(key, default)
1429
1326
  else:
1430
1327
  for block in ["metadata", "spec", "status"]:
@@ -1558,13 +1455,15 @@ def normalize_project_username(username: str):
1558
1455
  return username
1559
1456
 
1560
1457
 
1561
- # run_in threadpool is taken from fastapi to allow us to run sync functions in a threadpool
1562
- # without importing fastapi in the client
1563
1458
  async def run_in_threadpool(func, *args, **kwargs):
1459
+ """
1460
+ Run a sync-function in the loop default thread pool executor pool and await its result.
1461
+ Note that this function is not suitable for CPU-bound tasks, as it will block the event loop.
1462
+ """
1463
+ loop = asyncio.get_running_loop()
1564
1464
  if kwargs:
1565
- # run_sync doesn't accept 'kwargs', so bind them in here
1566
1465
  func = functools.partial(func, **kwargs)
1567
- return await anyio.to_thread.run_sync(func, *args)
1466
+ return await loop.run_in_executor(None, func, *args)
1568
1467
 
1569
1468
 
1570
1469
  def is_explicit_ack_supported(context):
@@ -1630,7 +1529,7 @@ def is_ecr_url(registry: str) -> bool:
1630
1529
  return ".ecr." in registry and ".amazonaws.com" in registry
1631
1530
 
1632
1531
 
1633
- def get_local_file_schema() -> List:
1532
+ def get_local_file_schema() -> list:
1634
1533
  # The expression `list(string.ascii_lowercase)` generates a list of lowercase alphabets,
1635
1534
  # which corresponds to drive letters in Windows file paths such as `C:/Windows/path`.
1636
1535
  return ["file"] + list(string.ascii_lowercase)
@@ -1642,3 +1541,90 @@ def is_safe_path(base, filepath, is_symlink=False):
1642
1541
  os.path.abspath(filepath) if not is_symlink else os.path.realpath(filepath)
1643
1542
  )
1644
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
@@ -14,7 +14,6 @@
14
14
  #
15
15
 
16
16
  import time
17
- import typing
18
17
 
19
18
  import requests
20
19
  import requests.adapters
@@ -123,7 +122,7 @@ class HTTPSessionWithRetry(requests.Session):
123
122
 
124
123
  self._logger.warning(
125
124
  "Error during request handling, retrying",
126
- exc=str(exc),
125
+ exc=err_to_str(exc),
127
126
  retry_count=retry_count,
128
127
  url=url,
129
128
  method=method,
@@ -202,9 +201,7 @@ class HTTPSessionWithRetry(requests.Session):
202
201
  def _method_retryable(self, method: str):
203
202
  return method in self._retry_methods
204
203
 
205
- def _resolve_retry_methods(
206
- self, retry_on_post: bool = False
207
- ) -> typing.FrozenSet[str]:
204
+ def _resolve_retry_methods(self, retry_on_post: bool = False) -> frozenset[str]:
208
205
  methods = urllib3.util.retry.Retry.DEFAULT_ALLOWED_METHODS
209
206
  methods = methods.union({"PATCH"})
210
207
  if retry_on_post: