mlrun 1.7.2rc3__py3-none-any.whl → 1.8.0rc2__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 (250) hide show
  1. mlrun/__init__.py +18 -18
  2. mlrun/__main__.py +3 -3
  3. mlrun/alerts/alert.py +19 -12
  4. mlrun/artifacts/__init__.py +0 -2
  5. mlrun/artifacts/base.py +34 -11
  6. mlrun/artifacts/dataset.py +16 -16
  7. mlrun/artifacts/manager.py +13 -13
  8. mlrun/artifacts/model.py +66 -53
  9. mlrun/common/constants.py +6 -0
  10. mlrun/common/formatters/__init__.py +1 -0
  11. mlrun/common/formatters/feature_set.py +1 -0
  12. mlrun/common/formatters/function.py +1 -0
  13. mlrun/common/formatters/model_endpoint.py +30 -0
  14. mlrun/common/formatters/pipeline.py +1 -2
  15. mlrun/common/formatters/project.py +9 -0
  16. mlrun/common/model_monitoring/__init__.py +0 -3
  17. mlrun/common/model_monitoring/helpers.py +1 -1
  18. mlrun/common/runtimes/constants.py +1 -2
  19. mlrun/common/schemas/__init__.py +7 -2
  20. mlrun/common/schemas/alert.py +31 -18
  21. mlrun/common/schemas/api_gateway.py +3 -3
  22. mlrun/common/schemas/artifact.py +7 -13
  23. mlrun/common/schemas/auth.py +6 -4
  24. mlrun/common/schemas/background_task.py +7 -7
  25. mlrun/common/schemas/client_spec.py +2 -2
  26. mlrun/common/schemas/clusterization_spec.py +2 -2
  27. mlrun/common/schemas/common.py +53 -3
  28. mlrun/common/schemas/datastore_profile.py +1 -1
  29. mlrun/common/schemas/feature_store.py +9 -9
  30. mlrun/common/schemas/frontend_spec.py +4 -4
  31. mlrun/common/schemas/function.py +10 -10
  32. mlrun/common/schemas/hub.py +1 -1
  33. mlrun/common/schemas/k8s.py +3 -3
  34. mlrun/common/schemas/memory_reports.py +3 -3
  35. mlrun/common/schemas/model_monitoring/__init__.py +8 -1
  36. mlrun/common/schemas/model_monitoring/constants.py +62 -12
  37. mlrun/common/schemas/model_monitoring/grafana.py +1 -1
  38. mlrun/common/schemas/model_monitoring/model_endpoint_v2.py +149 -0
  39. mlrun/common/schemas/model_monitoring/model_endpoints.py +22 -6
  40. mlrun/common/schemas/notification.py +18 -3
  41. mlrun/common/schemas/object.py +1 -1
  42. mlrun/common/schemas/pagination.py +4 -4
  43. mlrun/common/schemas/partition.py +137 -0
  44. mlrun/common/schemas/pipeline.py +2 -2
  45. mlrun/common/schemas/project.py +22 -17
  46. mlrun/common/schemas/runs.py +2 -2
  47. mlrun/common/schemas/runtime_resource.py +5 -5
  48. mlrun/common/schemas/schedule.py +1 -1
  49. mlrun/common/schemas/secret.py +1 -1
  50. mlrun/common/schemas/tag.py +3 -3
  51. mlrun/common/schemas/workflow.py +5 -5
  52. mlrun/config.py +65 -15
  53. mlrun/data_types/__init__.py +0 -2
  54. mlrun/data_types/data_types.py +0 -1
  55. mlrun/data_types/infer.py +3 -1
  56. mlrun/data_types/spark.py +4 -4
  57. mlrun/data_types/to_pandas.py +2 -11
  58. mlrun/datastore/__init__.py +0 -2
  59. mlrun/datastore/alibaba_oss.py +4 -1
  60. mlrun/datastore/azure_blob.py +4 -1
  61. mlrun/datastore/base.py +12 -4
  62. mlrun/datastore/datastore.py +9 -3
  63. mlrun/datastore/datastore_profile.py +20 -20
  64. mlrun/datastore/dbfs_store.py +4 -1
  65. mlrun/datastore/filestore.py +4 -1
  66. mlrun/datastore/google_cloud_storage.py +4 -1
  67. mlrun/datastore/hdfs.py +4 -1
  68. mlrun/datastore/inmem.py +4 -1
  69. mlrun/datastore/redis.py +4 -1
  70. mlrun/datastore/s3.py +4 -1
  71. mlrun/datastore/sources.py +51 -49
  72. mlrun/datastore/store_resources.py +0 -2
  73. mlrun/datastore/targets.py +22 -23
  74. mlrun/datastore/utils.py +2 -2
  75. mlrun/datastore/v3io.py +4 -1
  76. mlrun/datastore/wasbfs/fs.py +13 -12
  77. mlrun/db/base.py +170 -64
  78. mlrun/db/factory.py +3 -0
  79. mlrun/db/httpdb.py +986 -238
  80. mlrun/db/nopdb.py +155 -57
  81. mlrun/errors.py +2 -2
  82. mlrun/execution.py +55 -29
  83. mlrun/feature_store/__init__.py +0 -2
  84. mlrun/feature_store/api.py +40 -40
  85. mlrun/feature_store/common.py +9 -9
  86. mlrun/feature_store/feature_set.py +20 -18
  87. mlrun/feature_store/feature_vector.py +27 -24
  88. mlrun/feature_store/retrieval/base.py +14 -9
  89. mlrun/feature_store/retrieval/job.py +2 -1
  90. mlrun/feature_store/steps.py +2 -2
  91. mlrun/features.py +30 -13
  92. mlrun/frameworks/__init__.py +1 -2
  93. mlrun/frameworks/_common/__init__.py +1 -2
  94. mlrun/frameworks/_common/artifacts_library.py +2 -2
  95. mlrun/frameworks/_common/mlrun_interface.py +10 -6
  96. mlrun/frameworks/_common/model_handler.py +29 -27
  97. mlrun/frameworks/_common/producer.py +3 -1
  98. mlrun/frameworks/_dl_common/__init__.py +1 -2
  99. mlrun/frameworks/_dl_common/loggers/__init__.py +1 -2
  100. mlrun/frameworks/_dl_common/loggers/mlrun_logger.py +4 -4
  101. mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +3 -3
  102. mlrun/frameworks/_ml_common/__init__.py +1 -2
  103. mlrun/frameworks/_ml_common/loggers/__init__.py +1 -2
  104. mlrun/frameworks/_ml_common/model_handler.py +21 -21
  105. mlrun/frameworks/_ml_common/plans/__init__.py +1 -2
  106. mlrun/frameworks/_ml_common/plans/confusion_matrix_plan.py +3 -1
  107. mlrun/frameworks/_ml_common/plans/dataset_plan.py +3 -3
  108. mlrun/frameworks/_ml_common/plans/roc_curve_plan.py +4 -4
  109. mlrun/frameworks/auto_mlrun/__init__.py +1 -2
  110. mlrun/frameworks/auto_mlrun/auto_mlrun.py +22 -15
  111. mlrun/frameworks/huggingface/__init__.py +1 -2
  112. mlrun/frameworks/huggingface/model_server.py +9 -9
  113. mlrun/frameworks/lgbm/__init__.py +47 -44
  114. mlrun/frameworks/lgbm/callbacks/__init__.py +1 -2
  115. mlrun/frameworks/lgbm/callbacks/logging_callback.py +4 -2
  116. mlrun/frameworks/lgbm/callbacks/mlrun_logging_callback.py +4 -2
  117. mlrun/frameworks/lgbm/mlrun_interfaces/__init__.py +1 -2
  118. mlrun/frameworks/lgbm/mlrun_interfaces/mlrun_interface.py +5 -5
  119. mlrun/frameworks/lgbm/model_handler.py +15 -11
  120. mlrun/frameworks/lgbm/model_server.py +11 -7
  121. mlrun/frameworks/lgbm/utils.py +2 -2
  122. mlrun/frameworks/onnx/__init__.py +1 -2
  123. mlrun/frameworks/onnx/dataset.py +3 -3
  124. mlrun/frameworks/onnx/mlrun_interface.py +2 -2
  125. mlrun/frameworks/onnx/model_handler.py +7 -5
  126. mlrun/frameworks/onnx/model_server.py +8 -6
  127. mlrun/frameworks/parallel_coordinates.py +11 -11
  128. mlrun/frameworks/pytorch/__init__.py +22 -23
  129. mlrun/frameworks/pytorch/callbacks/__init__.py +1 -2
  130. mlrun/frameworks/pytorch/callbacks/callback.py +2 -1
  131. mlrun/frameworks/pytorch/callbacks/logging_callback.py +15 -8
  132. mlrun/frameworks/pytorch/callbacks/mlrun_logging_callback.py +19 -12
  133. mlrun/frameworks/pytorch/callbacks/tensorboard_logging_callback.py +22 -15
  134. mlrun/frameworks/pytorch/callbacks_handler.py +36 -30
  135. mlrun/frameworks/pytorch/mlrun_interface.py +17 -17
  136. mlrun/frameworks/pytorch/model_handler.py +21 -17
  137. mlrun/frameworks/pytorch/model_server.py +13 -9
  138. mlrun/frameworks/sklearn/__init__.py +19 -18
  139. mlrun/frameworks/sklearn/estimator.py +2 -2
  140. mlrun/frameworks/sklearn/metric.py +3 -3
  141. mlrun/frameworks/sklearn/metrics_library.py +8 -6
  142. mlrun/frameworks/sklearn/mlrun_interface.py +3 -2
  143. mlrun/frameworks/sklearn/model_handler.py +4 -3
  144. mlrun/frameworks/tf_keras/__init__.py +11 -12
  145. mlrun/frameworks/tf_keras/callbacks/__init__.py +1 -2
  146. mlrun/frameworks/tf_keras/callbacks/logging_callback.py +17 -14
  147. mlrun/frameworks/tf_keras/callbacks/mlrun_logging_callback.py +15 -12
  148. mlrun/frameworks/tf_keras/callbacks/tensorboard_logging_callback.py +21 -18
  149. mlrun/frameworks/tf_keras/model_handler.py +17 -13
  150. mlrun/frameworks/tf_keras/model_server.py +12 -8
  151. mlrun/frameworks/xgboost/__init__.py +19 -18
  152. mlrun/frameworks/xgboost/model_handler.py +13 -9
  153. mlrun/launcher/base.py +3 -4
  154. mlrun/launcher/local.py +1 -1
  155. mlrun/launcher/remote.py +1 -1
  156. mlrun/lists.py +4 -3
  157. mlrun/model.py +110 -46
  158. mlrun/model_monitoring/__init__.py +1 -2
  159. mlrun/model_monitoring/api.py +6 -6
  160. mlrun/model_monitoring/applications/_application_steps.py +13 -15
  161. mlrun/model_monitoring/applications/histogram_data_drift.py +41 -15
  162. mlrun/model_monitoring/applications/results.py +55 -3
  163. mlrun/model_monitoring/controller.py +185 -223
  164. mlrun/model_monitoring/db/_schedules.py +156 -0
  165. mlrun/model_monitoring/db/_stats.py +189 -0
  166. mlrun/model_monitoring/db/stores/__init__.py +1 -1
  167. mlrun/model_monitoring/db/stores/base/store.py +6 -65
  168. mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +0 -25
  169. mlrun/model_monitoring/db/stores/sqldb/models/base.py +0 -97
  170. mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +2 -58
  171. mlrun/model_monitoring/db/stores/sqldb/models/sqlite.py +0 -15
  172. mlrun/model_monitoring/db/stores/sqldb/sql_store.py +6 -257
  173. mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +9 -271
  174. mlrun/model_monitoring/db/tsdb/base.py +76 -24
  175. mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +61 -6
  176. mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +33 -0
  177. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +253 -28
  178. mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +1 -0
  179. mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +35 -17
  180. mlrun/model_monitoring/helpers.py +91 -1
  181. mlrun/model_monitoring/model_endpoint.py +4 -2
  182. mlrun/model_monitoring/stream_processing.py +16 -13
  183. mlrun/model_monitoring/tracking_policy.py +10 -3
  184. mlrun/model_monitoring/writer.py +47 -26
  185. mlrun/package/__init__.py +3 -6
  186. mlrun/package/context_handler.py +1 -1
  187. mlrun/package/packager.py +12 -9
  188. mlrun/package/packagers/__init__.py +0 -2
  189. mlrun/package/packagers/default_packager.py +14 -11
  190. mlrun/package/packagers/numpy_packagers.py +16 -7
  191. mlrun/package/packagers/pandas_packagers.py +18 -18
  192. mlrun/package/packagers/python_standard_library_packagers.py +25 -11
  193. mlrun/package/packagers_manager.py +31 -14
  194. mlrun/package/utils/__init__.py +0 -3
  195. mlrun/package/utils/_pickler.py +6 -6
  196. mlrun/platforms/__init__.py +3 -16
  197. mlrun/platforms/iguazio.py +4 -1
  198. mlrun/projects/operations.py +27 -27
  199. mlrun/projects/pipelines.py +34 -35
  200. mlrun/projects/project.py +535 -182
  201. mlrun/run.py +13 -10
  202. mlrun/runtimes/__init__.py +1 -3
  203. mlrun/runtimes/base.py +15 -11
  204. mlrun/runtimes/daskjob.py +9 -9
  205. mlrun/runtimes/generators.py +2 -1
  206. mlrun/runtimes/kubejob.py +4 -5
  207. mlrun/runtimes/mounts.py +572 -0
  208. mlrun/runtimes/mpijob/__init__.py +0 -2
  209. mlrun/runtimes/mpijob/abstract.py +7 -6
  210. mlrun/runtimes/nuclio/api_gateway.py +7 -7
  211. mlrun/runtimes/nuclio/application/application.py +11 -11
  212. mlrun/runtimes/nuclio/function.py +13 -13
  213. mlrun/runtimes/nuclio/serving.py +9 -9
  214. mlrun/runtimes/pod.py +154 -45
  215. mlrun/runtimes/remotesparkjob.py +3 -2
  216. mlrun/runtimes/sparkjob/__init__.py +0 -2
  217. mlrun/runtimes/sparkjob/spark3job.py +21 -11
  218. mlrun/runtimes/utils.py +6 -5
  219. mlrun/serving/merger.py +6 -4
  220. mlrun/serving/remote.py +18 -17
  221. mlrun/serving/routers.py +27 -27
  222. mlrun/serving/server.py +1 -1
  223. mlrun/serving/states.py +76 -71
  224. mlrun/serving/utils.py +13 -2
  225. mlrun/serving/v1_serving.py +3 -2
  226. mlrun/serving/v2_serving.py +4 -4
  227. mlrun/track/__init__.py +1 -1
  228. mlrun/track/tracker.py +2 -2
  229. mlrun/track/trackers/mlflow_tracker.py +6 -5
  230. mlrun/utils/async_http.py +1 -1
  231. mlrun/utils/helpers.py +70 -16
  232. mlrun/utils/logger.py +106 -4
  233. mlrun/utils/notifications/notification/__init__.py +22 -19
  234. mlrun/utils/notifications/notification/base.py +33 -14
  235. mlrun/utils/notifications/notification/console.py +6 -6
  236. mlrun/utils/notifications/notification/git.py +11 -11
  237. mlrun/utils/notifications/notification/ipython.py +10 -9
  238. mlrun/utils/notifications/notification/mail.py +149 -0
  239. mlrun/utils/notifications/notification/slack.py +6 -6
  240. mlrun/utils/notifications/notification/webhook.py +18 -22
  241. mlrun/utils/notifications/notification_pusher.py +43 -31
  242. mlrun/utils/regex.py +3 -1
  243. mlrun/utils/version/version.json +2 -2
  244. {mlrun-1.7.2rc3.dist-info → mlrun-1.8.0rc2.dist-info}/METADATA +18 -14
  245. mlrun-1.8.0rc2.dist-info/RECORD +358 -0
  246. {mlrun-1.7.2rc3.dist-info → mlrun-1.8.0rc2.dist-info}/WHEEL +1 -1
  247. mlrun-1.7.2rc3.dist-info/RECORD +0 -351
  248. {mlrun-1.7.2rc3.dist-info → mlrun-1.8.0rc2.dist-info}/LICENSE +0 -0
  249. {mlrun-1.7.2rc3.dist-info → mlrun-1.8.0rc2.dist-info}/entry_points.txt +0 -0
  250. {mlrun-1.7.2rc3.dist-info → mlrun-1.8.0rc2.dist-info}/top_level.txt +0 -0
mlrun/utils/helpers.py CHANGED
@@ -40,7 +40,6 @@ import pandas
40
40
  import semver
41
41
  import yaml
42
42
  from dateutil import parser
43
- from mlrun_pipelines.models import PipelineRun
44
43
  from pandas import Timedelta, Timestamp
45
44
  from yaml.representer import RepresenterError
46
45
 
@@ -52,6 +51,7 @@ import mlrun.utils.regex
52
51
  import mlrun.utils.version.version
53
52
  from mlrun.common.constants import MYSQL_MEDIUMBLOB_SIZE_BYTES
54
53
  from mlrun.config import config
54
+ from mlrun_pipelines.models import PipelineRun
55
55
 
56
56
  from .logger import create_logger
57
57
  from .retryer import ( # noqa: F401
@@ -167,6 +167,7 @@ class RunKeys:
167
167
  inputs = "inputs"
168
168
  returns = "returns"
169
169
  artifacts = "artifacts"
170
+ artifact_uris = "artifact_uris"
170
171
  outputs = "outputs"
171
172
  data_stores = "data_stores"
172
173
  secrets = "secret_sources"
@@ -220,7 +221,7 @@ def verify_field_regex(
220
221
 
221
222
 
222
223
  def validate_builder_source(
223
- source: str, pull_at_runtime: bool = False, workdir: str = None
224
+ source: str, pull_at_runtime: bool = False, workdir: Optional[str] = None
224
225
  ):
225
226
  if pull_at_runtime or not source:
226
227
  return
@@ -268,12 +269,14 @@ def validate_tag_name(
268
269
  def validate_artifact_key_name(
269
270
  artifact_key: str, field_name: str, raise_on_failure: bool = True
270
271
  ) -> bool:
272
+ field_type = "key" if field_name == "artifact.key" else "db_key"
271
273
  return mlrun.utils.helpers.verify_field_regex(
272
274
  field_name,
273
275
  artifact_key,
274
276
  mlrun.utils.regex.artifact_key,
275
277
  raise_on_failure=raise_on_failure,
276
- log_message="Slashes are not permitted in the artifact key (both \\ and /)",
278
+ log_message=f"Artifact {field_type} must start and end with an alphanumeric character, and may only contain "
279
+ "letters, numbers, hyphens, underscores, and dots.",
277
280
  )
278
281
 
279
282
 
@@ -354,8 +357,8 @@ def verify_field_list_of_type(
354
357
  def verify_dict_items_type(
355
358
  name: str,
356
359
  dictionary: dict,
357
- expected_keys_types: list = None,
358
- expected_values_types: list = None,
360
+ expected_keys_types: Optional[list] = None,
361
+ expected_values_types: Optional[list] = None,
359
362
  ):
360
363
  if dictionary:
361
364
  if not isinstance(dictionary, dict):
@@ -372,7 +375,7 @@ def verify_dict_items_type(
372
375
  ) from exc
373
376
 
374
377
 
375
- def verify_list_items_type(list_, expected_types: list = None):
378
+ def verify_list_items_type(list_, expected_types: Optional[list] = None):
376
379
  if list_ and expected_types:
377
380
  list_items_types = set(map(type, list_))
378
381
  expected_types = set(expected_types)
@@ -396,6 +399,10 @@ def now_date(tz: timezone = timezone.utc) -> datetime:
396
399
  return datetime.now(tz=tz)
397
400
 
398
401
 
402
+ def datetime_min(tz: timezone = timezone.utc) -> datetime:
403
+ return datetime(1970, 1, 1, tzinfo=tz)
404
+
405
+
399
406
  datetime_now = now_date
400
407
 
401
408
 
@@ -816,7 +823,9 @@ def _convert_python_package_version_to_image_tag(version: typing.Optional[str]):
816
823
 
817
824
 
818
825
  def enrich_image_url(
819
- image_url: str, client_version: str = None, client_python_version: str = None
826
+ image_url: str,
827
+ client_version: Optional[str] = None,
828
+ client_python_version: Optional[str] = None,
820
829
  ) -> str:
821
830
  client_version = _convert_python_package_version_to_image_tag(client_version)
822
831
  server_version = _convert_python_package_version_to_image_tag(
@@ -856,7 +865,7 @@ def enrich_image_url(
856
865
 
857
866
 
858
867
  def resolve_image_tag_suffix(
859
- mlrun_version: str = None, python_version: str = None
868
+ mlrun_version: Optional[str] = None, python_version: Optional[str] = None
860
869
  ) -> str:
861
870
  """
862
871
  resolves what suffix should be appended to the image tag
@@ -947,6 +956,36 @@ def fill_function_hash(function_dict, tag=""):
947
956
  return fill_object_hash(function_dict, "hash", tag)
948
957
 
949
958
 
959
+ def fill_model_endpoint_hash(
960
+ model_endpoint: mlrun.common.schemas.ModelEndpointV2,
961
+ created_time: typing.Union[str, datetime],
962
+ ) -> str:
963
+ """
964
+ Fill the model endpoint uid field in the model endpoint object and returns it.
965
+ The uid is generated by hashing the following model endpoint fields:
966
+ - name
967
+ - model_tag
968
+ - function_name
969
+ - project
970
+ - created_time
971
+ """
972
+
973
+ name = model_endpoint.metadata.name
974
+ model_tag = model_endpoint.spec.model_tag
975
+ function_name = model_endpoint.spec.function_name
976
+ project = model_endpoint.metadata.project
977
+ created_time = (
978
+ created_time
979
+ if isinstance(created_time, str)
980
+ else created_time.isoformat(sep=" ", timespec="microseconds")
981
+ )
982
+
983
+ unique_string = f"{name}_{model_tag}_{function_name}_{project}_{created_time}"
984
+ uid = hashlib.sha1(unique_string.encode("utf-8")).hexdigest()
985
+ model_endpoint.metadata.uid = uid
986
+ return uid
987
+
988
+
950
989
  def retry_until_successful(
951
990
  backoff: int, timeout: int, logger, verbose: bool, _function, *args, **kwargs
952
991
  ):
@@ -1175,7 +1214,7 @@ def get_function(function, namespaces, reload_modules: bool = False):
1175
1214
  def get_handler_extended(
1176
1215
  handler_path: str,
1177
1216
  context=None,
1178
- class_args: dict = None,
1217
+ class_args: Optional[dict] = None,
1179
1218
  namespaces=None,
1180
1219
  reload_modules: bool = False,
1181
1220
  ):
@@ -1694,17 +1733,22 @@ def merge_dicts_with_precedence(*dicts: dict) -> dict:
1694
1733
 
1695
1734
 
1696
1735
  def validate_component_version_compatibility(
1697
- component_name: typing.Literal["iguazio", "nuclio"], *min_versions: str
1736
+ component_name: typing.Literal["iguazio", "nuclio", "mlrun-client"],
1737
+ *min_versions: str,
1738
+ mlrun_client_version: Optional[str] = None,
1698
1739
  ):
1699
1740
  """
1700
1741
  :param component_name: Name of the component to validate compatibility for.
1701
1742
  :param min_versions: Valid minimum version(s) required, assuming no 2 versions has equal major and minor.
1743
+ :param mlrun_client_version: Client version to validate when component_name is "mlrun-client".
1702
1744
  """
1703
1745
  parsed_min_versions = [
1704
1746
  semver.VersionInfo.parse(min_version) for min_version in min_versions
1705
1747
  ]
1706
1748
  parsed_current_version = None
1707
1749
  component_current_version = None
1750
+ # For mlrun client we don't assume compatability if we fail to parse the client version
1751
+ assume_compatible = component_name not in ["mlrun-client"]
1708
1752
  try:
1709
1753
  if component_name == "iguazio":
1710
1754
  component_current_version = mlrun.mlconf.igz_version
@@ -1721,18 +1765,29 @@ def validate_component_version_compatibility(
1721
1765
  parsed_current_version = semver.VersionInfo.parse(
1722
1766
  mlrun.mlconf.nuclio_version
1723
1767
  )
1768
+ if component_name == "mlrun-client":
1769
+ # dev version, assume compatible
1770
+ if mlrun_client_version and (
1771
+ mlrun_client_version.startswith("0.0.0+")
1772
+ or "unstable" in mlrun_client_version
1773
+ ):
1774
+ return True
1775
+
1776
+ component_current_version = mlrun_client_version
1777
+ parsed_current_version = semver.Version.parse(mlrun_client_version)
1724
1778
  if not parsed_current_version:
1725
- return True
1779
+ return assume_compatible
1726
1780
  except ValueError:
1727
1781
  # only log when version is set but invalid
1728
1782
  if component_current_version:
1729
1783
  logger.warning(
1730
- "Unable to parse current version, assuming compatibility",
1784
+ "Unable to parse current version",
1731
1785
  component_name=component_name,
1732
1786
  current_version=component_current_version,
1733
1787
  min_versions=min_versions,
1788
+ assume_compatible=assume_compatible,
1734
1789
  )
1735
- return True
1790
+ return assume_compatible
1736
1791
 
1737
1792
  # Feature might have been back-ported e.g. nuclio node selection is supported from
1738
1793
  # 1.5.20 and 1.6.10 but not in 1.6.9 - therefore we reverse sort to validate against 1.6.x 1st and
@@ -1797,9 +1852,8 @@ def _reload(module, max_recursion_depth):
1797
1852
  def run_with_retry(
1798
1853
  retry_count: int,
1799
1854
  func: typing.Callable,
1800
- retry_on_exceptions: typing.Union[
1801
- type[Exception],
1802
- tuple[type[Exception]],
1855
+ retry_on_exceptions: Optional[
1856
+ typing.Union[type[Exception], tuple[type[Exception]]]
1803
1857
  ] = None,
1804
1858
  *args,
1805
1859
  **kwargs,
mlrun/utils/logger.py CHANGED
@@ -11,9 +11,11 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
-
14
+ import datetime
15
15
  import logging
16
16
  import os
17
+ import string
18
+ import sys
17
19
  import typing
18
20
  from enum import Enum
19
21
  from functools import cached_property
@@ -22,15 +24,16 @@ from traceback import format_exception
22
24
  from typing import IO, Optional, Union
23
25
 
24
26
  import orjson
25
- import pydantic
27
+ import pydantic.v1
26
28
 
29
+ from mlrun import errors
27
30
  from mlrun.config import config
28
31
 
29
32
 
30
33
  class _BaseFormatter(logging.Formatter):
31
34
  def _json_dump(self, json_object):
32
35
  def default(obj):
33
- if isinstance(obj, pydantic.BaseModel):
36
+ if isinstance(obj, pydantic.v1.BaseModel):
34
37
  return obj.dict()
35
38
 
36
39
  # EAFP all the way.
@@ -93,6 +96,98 @@ class HumanReadableFormatter(_BaseFormatter):
93
96
  return record_with
94
97
 
95
98
 
99
+ class CustomFormatter(HumanReadableFormatter):
100
+ """
101
+ To enable custom logger formatter, configure MLRun with the following env variables:
102
+ 1. "MLRUN_LOG_FORMATTER" = "custom" - change the default log formatter.
103
+ 2. "MLRUN_LOG_FORMAT_OVERRIDE" = "> {timestamp} [{level}] Running module: {module} {message} {more}" - logger format
104
+ * Note that your custom format must include those 4 fields - timestamp, level, message and more
105
+ If the custom format is not configured properly , MLRun will use the default logger (human format).
106
+ """
107
+
108
+ # This attribute is used to solve an issue
109
+ # that causes the warning to be written numerous times(for any log generation).
110
+ # We want to print the errors just once, not for each logger generation.
111
+ fail_on_format_configuration = False # for issues that relates to unrecognized keys
112
+ fail_on_missing_default_keys_key = (
113
+ False # for issues that relates to missing default keys
114
+ )
115
+
116
+ def format(self, record) -> str:
117
+ more = self._resolve_more(record)
118
+ custom_format = config.log_format_override
119
+ _custom_format = None
120
+ current_time = datetime.datetime.now()
121
+ formatted_time = current_time.strftime("%Y-%m-%d %H:%M:%S,%f")[:-3]
122
+ try:
123
+ if custom_format:
124
+ default_keys = ["timestamp", "level", "message", "more"]
125
+ formatter = string.Formatter()
126
+ custom_format_keys = [
127
+ key
128
+ for _, key, _, _ in formatter.parse(custom_format)
129
+ if key is not None
130
+ ]
131
+ missing_default_flags = list(
132
+ set(default_keys) - set(custom_format_keys)
133
+ )
134
+
135
+ if (
136
+ missing_default_flags
137
+ and not CustomFormatter.fail_on_missing_default_keys_key
138
+ ):
139
+ print(
140
+ f'> {formatted_time} [warning] Custom loggers must '
141
+ f'include those keys within the logger format, {", ".join(default_keys)} '
142
+ f'your format is missing: {", ".join(missing_default_flags)}',
143
+ file=sys.stderr,
144
+ )
145
+ CustomFormatter.fail_on_missing_default_keys_key = True
146
+ record_dict = record.__dict__
147
+ missing_format_configuraiton_keys = list(
148
+ set(custom_format_keys)
149
+ - set(default_keys)
150
+ - set(record_dict.keys())
151
+ )
152
+ if missing_format_configuraiton_keys:
153
+ if not CustomFormatter.fail_on_format_configuration:
154
+ print(
155
+ f"> {formatted_time} [warning] Failed to create custom logger due "
156
+ f'to missing format key in the log record: {", ".join(missing_format_configuraiton_keys)}',
157
+ file=sys.stderr,
158
+ )
159
+ CustomFormatter.fail_on_format_configuration = True
160
+ _format = (
161
+ f"> {self.formatTime(record, self.datefmt)} "
162
+ f"[{record.levelname.lower()}] "
163
+ f"{record.getMessage().rstrip()}"
164
+ f"{more}"
165
+ )
166
+ _custom_format = custom_format.format(
167
+ timestamp=self.formatTime(record, self.datefmt),
168
+ level=record.levelname.lower(),
169
+ message=record.getMessage().rstrip(),
170
+ more=more or "",
171
+ **record_dict,
172
+ )
173
+ CustomFormatter.fail_on_format_configuration = True
174
+ except Exception as e:
175
+ if not CustomFormatter.fail_on_format_configuration:
176
+ print(
177
+ f"> {formatted_time} [warning] Failed to create custom logger, "
178
+ f"see Exception: {errors.err_to_str(e)}",
179
+ file=sys.stderr,
180
+ )
181
+ CustomFormatter.fail_on_format_configuration = True
182
+ _format = _custom_format or (
183
+ f"> {self.formatTime(record, self.datefmt)} "
184
+ f"[{record.levelname.lower()}] "
185
+ f"{record.getMessage().rstrip()}"
186
+ f"{more}"
187
+ )
188
+ return _format
189
+
190
+
96
191
  class HumanReadableExtendedFormatter(HumanReadableFormatter):
97
192
  _colors = {
98
193
  logging.NOTSET: "",
@@ -272,17 +367,24 @@ class FormatterKinds(Enum):
272
367
  HUMAN = "human"
273
368
  HUMAN_EXTENDED = "human_extended"
274
369
  JSON = "json"
370
+ CUSTOM = "custom"
275
371
 
276
372
 
277
373
  def resolve_formatter_by_kind(
278
374
  formatter_kind: FormatterKinds,
279
375
  ) -> type[
280
- typing.Union[HumanReadableFormatter, HumanReadableExtendedFormatter, JSONFormatter]
376
+ typing.Union[
377
+ HumanReadableFormatter,
378
+ HumanReadableExtendedFormatter,
379
+ JSONFormatter,
380
+ CustomFormatter,
381
+ ]
281
382
  ]:
282
383
  return {
283
384
  FormatterKinds.HUMAN: HumanReadableFormatter,
284
385
  FormatterKinds.HUMAN_EXTENDED: HumanReadableExtendedFormatter,
285
386
  FormatterKinds.JSON: JSONFormatter,
387
+ FormatterKinds.CUSTOM: CustomFormatter,
286
388
  }[formatter_kind]
287
389
 
288
390
 
@@ -14,30 +14,32 @@
14
14
 
15
15
  import enum
16
16
 
17
- from mlrun.common.schemas.notification import NotificationKind
18
-
19
- from .base import NotificationBase
20
- from .console import ConsoleNotification
21
- from .git import GitNotification
22
- from .ipython import IPythonNotification
23
- from .slack import SlackNotification
24
- from .webhook import WebhookNotification
17
+ import mlrun.common.schemas.notification as notifications
18
+ import mlrun.utils.notifications.notification.base as base
19
+ import mlrun.utils.notifications.notification.console as console
20
+ import mlrun.utils.notifications.notification.git as git
21
+ import mlrun.utils.notifications.notification.ipython as ipython
22
+ import mlrun.utils.notifications.notification.mail as mail
23
+ import mlrun.utils.notifications.notification.slack as slack
24
+ import mlrun.utils.notifications.notification.webhook as webhook
25
25
 
26
26
 
27
27
  class NotificationTypes(str, enum.Enum):
28
- console = NotificationKind.console.value
29
- git = NotificationKind.git.value
30
- ipython = NotificationKind.ipython.value
31
- slack = NotificationKind.slack.value
32
- webhook = NotificationKind.webhook.value
28
+ console = notifications.NotificationKind.console.value
29
+ git = notifications.NotificationKind.git.value
30
+ ipython = notifications.NotificationKind.ipython.value
31
+ slack = notifications.NotificationKind.slack.value
32
+ mail = notifications.NotificationKind.mail.value
33
+ webhook = notifications.NotificationKind.webhook.value
33
34
 
34
- def get_notification(self) -> type[NotificationBase]:
35
+ def get_notification(self) -> type[base.NotificationBase]:
35
36
  return {
36
- self.console: ConsoleNotification,
37
- self.git: GitNotification,
38
- self.ipython: IPythonNotification,
39
- self.slack: SlackNotification,
40
- self.webhook: WebhookNotification,
37
+ self.console: console.ConsoleNotification,
38
+ self.git: git.GitNotification,
39
+ self.ipython: ipython.IPythonNotification,
40
+ self.slack: slack.SlackNotification,
41
+ self.mail: mail.MailNotification,
42
+ self.webhook: webhook.WebhookNotification,
41
43
  }.get(self)
42
44
 
43
45
  def inverse_dependencies(self) -> list[str]:
@@ -64,5 +66,6 @@ class NotificationTypes(str, enum.Enum):
64
66
  cls.git,
65
67
  cls.ipython,
66
68
  cls.slack,
69
+ cls.mail,
67
70
  cls.webhook,
68
71
  ]
@@ -14,6 +14,7 @@
14
14
 
15
15
  import asyncio
16
16
  import typing
17
+ from copy import deepcopy
17
18
 
18
19
  import mlrun.common.schemas
19
20
  import mlrun.lists
@@ -22,11 +23,20 @@ import mlrun.lists
22
23
  class NotificationBase:
23
24
  def __init__(
24
25
  self,
25
- name: str = None,
26
- params: dict[str, str] = None,
26
+ name: typing.Optional[str] = None,
27
+ params: typing.Optional[dict[str, str]] = None,
28
+ default_params: typing.Optional[dict[str, str]] = None,
27
29
  ):
30
+ """
31
+ NotificationBase is the base class for all notification types.
32
+
33
+ :param name: The name of the notification.
34
+ :param params: The parameters of the notification.
35
+ :param default_params: The default parameters of the notification. Used for server-side enrichment purposes.
36
+ """
28
37
  self.name = name
29
38
  self.params = params or {}
39
+ self.params = self.enrich_default_params(self.params, default_params)
30
40
 
31
41
  @classmethod
32
42
  def validate_params(cls, params):
@@ -43,13 +53,13 @@ class NotificationBase:
43
53
  def push(
44
54
  self,
45
55
  message: str,
46
- severity: typing.Union[
47
- mlrun.common.schemas.NotificationSeverity, str
56
+ severity: typing.Optional[
57
+ typing.Union[mlrun.common.schemas.NotificationSeverity, str]
48
58
  ] = mlrun.common.schemas.NotificationSeverity.INFO,
49
- runs: typing.Union[mlrun.lists.RunList, list] = None,
50
- custom_html: str = None,
51
- alert: mlrun.common.schemas.AlertConfig = None,
52
- event_data: mlrun.common.schemas.Event = None,
59
+ runs: typing.Optional[typing.Union[mlrun.lists.RunList, list]] = None,
60
+ custom_html: typing.Optional[typing.Optional[str]] = None,
61
+ alert: typing.Optional[mlrun.common.schemas.AlertConfig] = None,
62
+ event_data: typing.Optional[mlrun.common.schemas.Event] = None,
53
63
  ):
54
64
  raise NotImplementedError()
55
65
 
@@ -59,16 +69,25 @@ class NotificationBase:
59
69
  ) -> None:
60
70
  self.params = params or {}
61
71
 
72
+ @classmethod
73
+ def enrich_default_params(
74
+ cls, params: dict, default_params: typing.Optional[dict] = None
75
+ ) -> dict:
76
+ default_params = default_params or {}
77
+ returned_params = deepcopy(default_params)
78
+ returned_params.update(params)
79
+ return returned_params
80
+
62
81
  def _get_html(
63
82
  self,
64
83
  message: str,
65
- severity: typing.Union[
66
- mlrun.common.schemas.NotificationSeverity, str
84
+ severity: typing.Optional[
85
+ typing.Union[mlrun.common.schemas.NotificationSeverity, str]
67
86
  ] = mlrun.common.schemas.NotificationSeverity.INFO,
68
- runs: typing.Union[mlrun.lists.RunList, list] = None,
69
- custom_html: str = None,
70
- alert: mlrun.common.schemas.AlertConfig = None,
71
- event_data: mlrun.common.schemas.Event = None,
87
+ runs: typing.Optional[typing.Union[mlrun.lists.RunList, list]] = None,
88
+ custom_html: typing.Optional[typing.Optional[str]] = None,
89
+ alert: typing.Optional[mlrun.common.schemas.AlertConfig] = None,
90
+ event_data: typing.Optional[mlrun.common.schemas.Event] = None,
72
91
  ) -> str:
73
92
  if custom_html:
74
93
  return custom_html
@@ -31,13 +31,13 @@ class ConsoleNotification(NotificationBase):
31
31
  def push(
32
32
  self,
33
33
  message: str,
34
- severity: typing.Union[
35
- mlrun.common.schemas.NotificationSeverity, str
34
+ severity: typing.Optional[
35
+ typing.Union[mlrun.common.schemas.NotificationSeverity, str]
36
36
  ] = mlrun.common.schemas.NotificationSeverity.INFO,
37
- runs: typing.Union[mlrun.lists.RunList, list] = None,
38
- custom_html: str = None,
39
- alert: mlrun.common.schemas.AlertConfig = None,
40
- event_data: mlrun.common.schemas.Event = None,
37
+ runs: typing.Optional[typing.Union[mlrun.lists.RunList, list]] = None,
38
+ custom_html: typing.Optional[typing.Optional[str]] = None,
39
+ alert: typing.Optional[mlrun.common.schemas.AlertConfig] = None,
40
+ event_data: typing.Optional[mlrun.common.schemas.Event] = None,
41
41
  ):
42
42
  severity = self._resolve_severity(severity)
43
43
  print(f"[{severity}] {message}")
@@ -54,13 +54,13 @@ class GitNotification(NotificationBase):
54
54
  async def push(
55
55
  self,
56
56
  message: str,
57
- severity: typing.Union[
58
- mlrun.common.schemas.NotificationSeverity, str
57
+ severity: typing.Optional[
58
+ typing.Union[mlrun.common.schemas.NotificationSeverity, str]
59
59
  ] = mlrun.common.schemas.NotificationSeverity.INFO,
60
- runs: typing.Union[mlrun.lists.RunList, list] = None,
61
- custom_html: str = None,
62
- alert: mlrun.common.schemas.AlertConfig = None,
63
- event_data: mlrun.common.schemas.Event = None,
60
+ runs: typing.Optional[typing.Union[mlrun.lists.RunList, list]] = None,
61
+ custom_html: typing.Optional[typing.Optional[str]] = None,
62
+ alert: typing.Optional[mlrun.common.schemas.AlertConfig] = None,
63
+ event_data: typing.Optional[mlrun.common.schemas.Event] = None,
64
64
  ):
65
65
  git_repo = self.params.get("repo", None)
66
66
  git_issue = self.params.get("issue", None)
@@ -85,11 +85,11 @@ class GitNotification(NotificationBase):
85
85
  @staticmethod
86
86
  async def _pr_comment(
87
87
  message: str,
88
- repo: str = None,
89
- issue: int = None,
90
- merge_request: int = None,
91
- token: str = None,
92
- server: str = None,
88
+ repo: typing.Optional[str] = None,
89
+ issue: typing.Optional[int] = None,
90
+ merge_request: typing.Optional[int] = None,
91
+ token: typing.Optional[str] = None,
92
+ server: typing.Optional[str] = None,
93
93
  gitlab: bool = False,
94
94
  ) -> str:
95
95
  """push comment message to Git system PR/issue
@@ -28,10 +28,11 @@ class IPythonNotification(NotificationBase):
28
28
 
29
29
  def __init__(
30
30
  self,
31
- name: str = None,
32
- params: dict[str, str] = None,
31
+ name: typing.Optional[str] = None,
32
+ params: typing.Optional[dict[str, str]] = None,
33
+ default_params: typing.Optional[dict[str, str]] = None,
33
34
  ):
34
- super().__init__(name, params)
35
+ super().__init__(name, params, default_params)
35
36
  self._ipython = None
36
37
  try:
37
38
  import IPython
@@ -48,13 +49,13 @@ class IPythonNotification(NotificationBase):
48
49
  def push(
49
50
  self,
50
51
  message: str,
51
- severity: typing.Union[
52
- mlrun.common.schemas.NotificationSeverity, str
52
+ severity: typing.Optional[
53
+ typing.Union[mlrun.common.schemas.NotificationSeverity, str]
53
54
  ] = mlrun.common.schemas.NotificationSeverity.INFO,
54
- runs: typing.Union[mlrun.lists.RunList, list] = None,
55
- custom_html: str = None,
56
- alert: mlrun.common.schemas.AlertConfig = None,
57
- event_data: mlrun.common.schemas.Event = None,
55
+ runs: typing.Optional[typing.Union[mlrun.lists.RunList, list]] = None,
56
+ custom_html: typing.Optional[typing.Optional[str]] = None,
57
+ alert: typing.Optional[mlrun.common.schemas.AlertConfig] = None,
58
+ event_data: typing.Optional[mlrun.common.schemas.Event] = None,
58
59
  ):
59
60
  if not self._ipython:
60
61
  mlrun.utils.helpers.logger.debug(