mlrun 1.10.0rc18__py3-none-any.whl → 1.11.0rc16__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 (167) hide show
  1. mlrun/__init__.py +24 -3
  2. mlrun/__main__.py +0 -4
  3. mlrun/artifacts/dataset.py +2 -2
  4. mlrun/artifacts/document.py +6 -1
  5. mlrun/artifacts/llm_prompt.py +21 -15
  6. mlrun/artifacts/model.py +3 -3
  7. mlrun/artifacts/plots.py +1 -1
  8. mlrun/{model_monitoring/db/tsdb/tdengine → auth}/__init__.py +2 -3
  9. mlrun/auth/nuclio.py +89 -0
  10. mlrun/auth/providers.py +429 -0
  11. mlrun/auth/utils.py +415 -0
  12. mlrun/common/constants.py +14 -0
  13. mlrun/common/model_monitoring/helpers.py +123 -0
  14. mlrun/common/runtimes/constants.py +28 -0
  15. mlrun/common/schemas/__init__.py +14 -3
  16. mlrun/common/schemas/alert.py +2 -2
  17. mlrun/common/schemas/api_gateway.py +3 -0
  18. mlrun/common/schemas/auth.py +12 -10
  19. mlrun/common/schemas/client_spec.py +4 -0
  20. mlrun/common/schemas/constants.py +25 -0
  21. mlrun/common/schemas/frontend_spec.py +1 -8
  22. mlrun/common/schemas/function.py +34 -0
  23. mlrun/common/schemas/hub.py +33 -20
  24. mlrun/common/schemas/model_monitoring/__init__.py +2 -1
  25. mlrun/common/schemas/model_monitoring/constants.py +12 -15
  26. mlrun/common/schemas/model_monitoring/functions.py +13 -4
  27. mlrun/common/schemas/model_monitoring/model_endpoints.py +11 -0
  28. mlrun/common/schemas/pipeline.py +1 -1
  29. mlrun/common/schemas/secret.py +17 -2
  30. mlrun/common/secrets.py +95 -1
  31. mlrun/common/types.py +10 -10
  32. mlrun/config.py +69 -19
  33. mlrun/data_types/infer.py +2 -2
  34. mlrun/datastore/__init__.py +12 -5
  35. mlrun/datastore/azure_blob.py +162 -47
  36. mlrun/datastore/base.py +274 -10
  37. mlrun/datastore/datastore.py +7 -2
  38. mlrun/datastore/datastore_profile.py +84 -22
  39. mlrun/datastore/model_provider/huggingface_provider.py +225 -41
  40. mlrun/datastore/model_provider/mock_model_provider.py +87 -0
  41. mlrun/datastore/model_provider/model_provider.py +206 -74
  42. mlrun/datastore/model_provider/openai_provider.py +226 -66
  43. mlrun/datastore/s3.py +39 -18
  44. mlrun/datastore/sources.py +1 -1
  45. mlrun/datastore/store_resources.py +4 -4
  46. mlrun/datastore/storeytargets.py +17 -12
  47. mlrun/datastore/targets.py +1 -1
  48. mlrun/datastore/utils.py +25 -6
  49. mlrun/datastore/v3io.py +1 -1
  50. mlrun/db/base.py +63 -32
  51. mlrun/db/httpdb.py +373 -153
  52. mlrun/db/nopdb.py +54 -21
  53. mlrun/errors.py +4 -2
  54. mlrun/execution.py +66 -25
  55. mlrun/feature_store/api.py +1 -1
  56. mlrun/feature_store/common.py +1 -1
  57. mlrun/feature_store/feature_vector_utils.py +1 -1
  58. mlrun/feature_store/steps.py +8 -6
  59. mlrun/frameworks/_common/utils.py +3 -3
  60. mlrun/frameworks/_dl_common/loggers/logger.py +1 -1
  61. mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +2 -1
  62. mlrun/frameworks/_ml_common/loggers/mlrun_logger.py +1 -1
  63. mlrun/frameworks/_ml_common/utils.py +2 -1
  64. mlrun/frameworks/auto_mlrun/auto_mlrun.py +4 -3
  65. mlrun/frameworks/lgbm/mlrun_interfaces/mlrun_interface.py +2 -1
  66. mlrun/frameworks/onnx/dataset.py +2 -1
  67. mlrun/frameworks/onnx/mlrun_interface.py +2 -1
  68. mlrun/frameworks/pytorch/callbacks/logging_callback.py +5 -4
  69. mlrun/frameworks/pytorch/callbacks/mlrun_logging_callback.py +2 -1
  70. mlrun/frameworks/pytorch/callbacks/tensorboard_logging_callback.py +2 -1
  71. mlrun/frameworks/pytorch/utils.py +2 -1
  72. mlrun/frameworks/sklearn/metric.py +2 -1
  73. mlrun/frameworks/tf_keras/callbacks/logging_callback.py +5 -4
  74. mlrun/frameworks/tf_keras/callbacks/mlrun_logging_callback.py +2 -1
  75. mlrun/frameworks/tf_keras/callbacks/tensorboard_logging_callback.py +2 -1
  76. mlrun/hub/__init__.py +52 -0
  77. mlrun/hub/base.py +142 -0
  78. mlrun/hub/module.py +172 -0
  79. mlrun/hub/step.py +113 -0
  80. mlrun/k8s_utils.py +105 -16
  81. mlrun/launcher/base.py +15 -7
  82. mlrun/launcher/local.py +4 -1
  83. mlrun/model.py +14 -4
  84. mlrun/model_monitoring/__init__.py +0 -1
  85. mlrun/model_monitoring/api.py +65 -28
  86. mlrun/model_monitoring/applications/__init__.py +1 -1
  87. mlrun/model_monitoring/applications/base.py +299 -128
  88. mlrun/model_monitoring/applications/context.py +2 -4
  89. mlrun/model_monitoring/controller.py +132 -58
  90. mlrun/model_monitoring/db/_schedules.py +38 -29
  91. mlrun/model_monitoring/db/_stats.py +6 -16
  92. mlrun/model_monitoring/db/tsdb/__init__.py +9 -7
  93. mlrun/model_monitoring/db/tsdb/base.py +29 -9
  94. mlrun/model_monitoring/db/tsdb/preaggregate.py +234 -0
  95. mlrun/model_monitoring/db/tsdb/stream_graph_steps.py +63 -0
  96. mlrun/model_monitoring/db/tsdb/timescaledb/queries/timescaledb_metrics_queries.py +414 -0
  97. mlrun/model_monitoring/db/tsdb/timescaledb/queries/timescaledb_predictions_queries.py +376 -0
  98. mlrun/model_monitoring/db/tsdb/timescaledb/queries/timescaledb_results_queries.py +590 -0
  99. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_connection.py +434 -0
  100. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_connector.py +541 -0
  101. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_operations.py +808 -0
  102. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_schema.py +502 -0
  103. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_stream.py +163 -0
  104. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_stream_graph_steps.py +60 -0
  105. mlrun/model_monitoring/db/tsdb/timescaledb/utils/timescaledb_dataframe_processor.py +141 -0
  106. mlrun/model_monitoring/db/tsdb/timescaledb/utils/timescaledb_query_builder.py +585 -0
  107. mlrun/model_monitoring/db/tsdb/timescaledb/writer_graph_steps.py +73 -0
  108. mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +20 -9
  109. mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +235 -51
  110. mlrun/model_monitoring/features_drift_table.py +2 -1
  111. mlrun/model_monitoring/helpers.py +30 -6
  112. mlrun/model_monitoring/stream_processing.py +34 -28
  113. mlrun/model_monitoring/writer.py +224 -4
  114. mlrun/package/__init__.py +2 -1
  115. mlrun/platforms/__init__.py +0 -43
  116. mlrun/platforms/iguazio.py +8 -4
  117. mlrun/projects/operations.py +17 -11
  118. mlrun/projects/pipelines.py +2 -2
  119. mlrun/projects/project.py +187 -123
  120. mlrun/run.py +95 -21
  121. mlrun/runtimes/__init__.py +2 -186
  122. mlrun/runtimes/base.py +103 -25
  123. mlrun/runtimes/constants.py +225 -0
  124. mlrun/runtimes/daskjob.py +5 -2
  125. mlrun/runtimes/databricks_job/databricks_runtime.py +2 -1
  126. mlrun/runtimes/local.py +5 -2
  127. mlrun/runtimes/mounts.py +20 -2
  128. mlrun/runtimes/nuclio/__init__.py +12 -7
  129. mlrun/runtimes/nuclio/api_gateway.py +36 -6
  130. mlrun/runtimes/nuclio/application/application.py +339 -40
  131. mlrun/runtimes/nuclio/function.py +222 -72
  132. mlrun/runtimes/nuclio/serving.py +132 -42
  133. mlrun/runtimes/pod.py +213 -21
  134. mlrun/runtimes/utils.py +49 -9
  135. mlrun/secrets.py +99 -14
  136. mlrun/serving/__init__.py +2 -0
  137. mlrun/serving/remote.py +84 -11
  138. mlrun/serving/routers.py +26 -44
  139. mlrun/serving/server.py +138 -51
  140. mlrun/serving/serving_wrapper.py +6 -2
  141. mlrun/serving/states.py +997 -283
  142. mlrun/serving/steps.py +62 -0
  143. mlrun/serving/system_steps.py +149 -95
  144. mlrun/serving/v2_serving.py +9 -10
  145. mlrun/track/trackers/mlflow_tracker.py +29 -31
  146. mlrun/utils/helpers.py +292 -94
  147. mlrun/utils/http.py +9 -2
  148. mlrun/utils/notifications/notification/base.py +18 -0
  149. mlrun/utils/notifications/notification/git.py +3 -5
  150. mlrun/utils/notifications/notification/mail.py +39 -16
  151. mlrun/utils/notifications/notification/slack.py +2 -4
  152. mlrun/utils/notifications/notification/webhook.py +2 -5
  153. mlrun/utils/notifications/notification_pusher.py +3 -3
  154. mlrun/utils/version/version.json +2 -2
  155. mlrun/utils/version/version.py +3 -4
  156. {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/METADATA +63 -74
  157. {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/RECORD +161 -143
  158. mlrun/api/schemas/__init__.py +0 -259
  159. mlrun/db/auth_utils.py +0 -152
  160. mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +0 -344
  161. mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +0 -75
  162. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connection.py +0 -281
  163. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +0 -1266
  164. {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/WHEEL +0 -0
  165. {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/entry_points.txt +0 -0
  166. {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/licenses/LICENSE +0 -0
  167. {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/top_level.txt +0 -0
@@ -12,8 +12,9 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ from collections.abc import Callable
15
16
  from datetime import datetime
16
- from typing import Callable, Optional, Union
17
+ from typing import Optional, Union
17
18
 
18
19
  import tensorflow as tf
19
20
  from packaging import version
mlrun/hub/__init__.py ADDED
@@ -0,0 +1,52 @@
1
+ # Copyright 2025 Iguazio
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from typing import Optional
15
+
16
+ import mlrun
17
+ from mlrun.common.schemas.hub import HubSourceType
18
+
19
+ from .module import get_hub_module, import_module
20
+ from .step import get_hub_step
21
+
22
+
23
+ def get_hub_item(
24
+ source_name: str,
25
+ item_name: str,
26
+ version: Optional[str] = None,
27
+ tag: Optional[str] = "latest",
28
+ force_refresh: bool = False,
29
+ item_type: HubSourceType = HubSourceType.functions,
30
+ ) -> mlrun.common.schemas.hub.HubItem:
31
+ """
32
+ Retrieve a specific hub item.
33
+
34
+ :param source_name: Name of source.
35
+ :param item_name: Name of the item to retrieve, as it appears in the hub catalog.
36
+ :param version: Get a specific version of the item. Default is ``None``.
37
+ :param tag: Get a specific version of the item identified by tag. Default is ``latest``.
38
+ :param force_refresh: Make the server fetch the information from the actual hub
39
+ source, rather than
40
+ rely on cached information. Default is ``False``.
41
+ :param item_type: The type of item to retrieve from the hub source (e.g: functions, modules).
42
+ :returns: :py:class:`~mlrun.common.schemas.hub.HubItem`.
43
+ """
44
+ db = mlrun.get_run_db()
45
+ return db.get_hub_item(
46
+ source_name=source_name,
47
+ item_name=item_name,
48
+ version=version,
49
+ tag=tag,
50
+ force_refresh=force_refresh,
51
+ item_type=item_type,
52
+ )
mlrun/hub/base.py ADDED
@@ -0,0 +1,142 @@
1
+ # Copyright 2025 Iguazio
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import os
16
+ import subprocess
17
+ import sys
18
+ from pathlib import Path
19
+ from typing import ClassVar, Optional
20
+
21
+ from mlrun.common.schemas.hub import HubSourceType
22
+ from mlrun.run import function_to_module, get_object
23
+ from mlrun.utils import logger
24
+
25
+ from ..errors import MLRunBadRequestError
26
+ from ..model import ModelObj
27
+ from ..utils import extend_hub_uri_if_needed
28
+
29
+
30
+ class HubAsset(ModelObj):
31
+ ASSET_TYPE: ClassVar[HubSourceType]
32
+
33
+ def __init__(
34
+ self,
35
+ name: str,
36
+ version: str,
37
+ description: Optional[str] = None,
38
+ categories: Optional[list] = None,
39
+ requirements: Optional[list] = None,
40
+ local_path: Optional[Path] = None,
41
+ filename: Optional[str] = None,
42
+ example: Optional[str] = None,
43
+ url: Optional[str] = None,
44
+ **kwargs,
45
+ ):
46
+ self.name: str = name
47
+ self.version: str = version
48
+ self.description: str = description or ""
49
+ self.categories: list = categories or []
50
+ self.requirements: list = requirements or []
51
+ self.local_path: Optional[Path] = local_path
52
+ self.filename: str = filename or name
53
+ self.example: str = example or ""
54
+ self.url: str = url or ""
55
+
56
+ def module(self):
57
+ """Import the code of the asset as a module."""
58
+ try:
59
+ return function_to_module(code=self.filename, workdir=self.local_path)
60
+ except Exception as e:
61
+ raise MLRunBadRequestError(
62
+ f"Failed to import module from {self.get_src_file_path()}: {e}"
63
+ )
64
+
65
+ def install_requirements(self) -> None:
66
+ """
67
+ Install pip-style requirements of the asset (e.g., ["pandas>=2.0.0", "requests==2.31.0"]).
68
+ """
69
+ if not self.requirements or len(self.requirements) == 0:
70
+ logger.info("No requirements to install.")
71
+ return
72
+ for req in self.requirements:
73
+ logger.info(f"Installing {req} ...")
74
+ try:
75
+ subprocess.run(
76
+ [sys.executable, "-m", "pip", "install", req], check=True, text=True
77
+ )
78
+ logger.info(f"Installed {req}")
79
+ except subprocess.CalledProcessError as e:
80
+ logger.error(f"Failed to install {req} (exit code {e.returncode})")
81
+
82
+ def download_files(
83
+ self,
84
+ local_path: Optional[str] = None,
85
+ download_example: bool = True,
86
+ ):
87
+ """
88
+ Download this hub asset’s files (code file and, if available and requested, an example notebook) to the target
89
+ directory specified by `local_path` (defaults to the current working directory).
90
+ This path will be used later to locate the code file when calling module().
91
+ """
92
+ self.local_path = self.verify_directory(path=local_path)
93
+ source_url, _ = extend_hub_uri_if_needed(
94
+ uri=self.url, asset_type=self.ASSET_TYPE, file=self.filename
95
+ )
96
+ self._download_object(obj_url=source_url, target_name=self.filename)
97
+ if download_example and self.example:
98
+ example_url, _ = extend_hub_uri_if_needed(
99
+ uri=self.url, asset_type=self.ASSET_TYPE, file=self.example
100
+ )
101
+ self._download_object(obj_url=example_url, target_name=self.example)
102
+
103
+ def _download_object(self, obj_url, target_name, secrets=None):
104
+ data = get_object(url=obj_url, secrets=secrets)
105
+ target_filepath = os.path.join(self.local_path, target_name)
106
+ with open(target_filepath, "wb") as f:
107
+ f.write(data)
108
+
109
+ @staticmethod
110
+ def verify_directory(path: Optional[str] = None) -> Path:
111
+ """
112
+ Validate that the given path is an existing directory.
113
+ If no path has been provided, returns current working directory.
114
+ """
115
+ if path:
116
+ path = Path(path)
117
+ if not path.exists():
118
+ raise ValueError(f"Path does not exist: {path}")
119
+ if not path.is_dir():
120
+ raise ValueError(f"Path is not a directory: {path}")
121
+ return path
122
+ return Path(os.getcwd())
123
+
124
+ def get_src_file_path(self) -> str:
125
+ """Get the full path to the asset's code file."""
126
+ if not self.local_path:
127
+ raise MLRunBadRequestError(
128
+ f"Local path not set. Call download_files() first to download the asset files, or "
129
+ f"set_local_path() with the directory containing {self.filename}"
130
+ )
131
+ src_path = Path(self.local_path) / self.filename
132
+ if not src_path.exists():
133
+ raise FileNotFoundError(
134
+ f"File {self.filename} not found in {self.local_path}. Call download_files() first to download the "
135
+ f"asset files, or set_local_path() with the directory containing {self.filename}"
136
+ )
137
+
138
+ return str(src_path)
139
+
140
+ def set_local_path(self, path: str):
141
+ """Set the local path where the asset's files are stored."""
142
+ self.local_path = self.verify_directory(path=path)
mlrun/hub/module.py ADDED
@@ -0,0 +1,172 @@
1
+ # Copyright 2025 Iguazio
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import warnings
16
+ from pathlib import Path
17
+ from typing import Optional, Union
18
+
19
+ import yaml
20
+ from deprecated import deprecated
21
+
22
+ import mlrun.common.types
23
+ import mlrun.utils
24
+ from mlrun.common.schemas.hub import HubModuleType, HubSourceType
25
+ from mlrun.run import get_object
26
+
27
+ from ..utils import extend_hub_uri_if_needed
28
+ from .base import HubAsset
29
+
30
+
31
+ class HubModule(HubAsset):
32
+ ASSET_TYPE = HubSourceType.modules
33
+
34
+ def __init__(
35
+ self,
36
+ name: str,
37
+ version: str,
38
+ kind: Union[HubModuleType, str],
39
+ description: Optional[str] = None,
40
+ categories: Optional[list] = None,
41
+ requirements: Optional[list] = None,
42
+ local_path: Optional[Path] = None,
43
+ filename: Optional[str] = None,
44
+ example: Optional[str] = None,
45
+ url: Optional[str] = None,
46
+ **kwargs, # catch all for unused args
47
+ ):
48
+ super().__init__(
49
+ name=name,
50
+ version=version,
51
+ description=description,
52
+ categories=categories,
53
+ requirements=requirements,
54
+ local_path=local_path,
55
+ filename=filename,
56
+ example=example,
57
+ url=url,
58
+ )
59
+ self.kind = kind
60
+
61
+ # TODO: Remove this in 1.13.0
62
+ @deprecated(
63
+ version="1.11.0",
64
+ reason="This function is deprecated and will be removed in 1.13. You can download module files by calling "
65
+ "download_files() instead.",
66
+ category=FutureWarning,
67
+ )
68
+ def download_module_files(
69
+ self, local_path: Optional[str] = None, secrets: Optional[dict] = None
70
+ ):
71
+ """
72
+ Download this hub module’s files (code file and, if available, an example notebook) to the target directory
73
+ specified by `local_path` (defaults to the current working directory).
74
+ This path will be used later to locate the code file when importing the module.
75
+ """
76
+ self.local_path = self.verify_directory(path=local_path)
77
+ source_url, _ = extend_hub_uri_if_needed(
78
+ uri=self.url, asset_type=self.ASSET_TYPE, file=self.filename
79
+ )
80
+ self._download_object(
81
+ obj_url=source_url, target_name=self.filename, secrets=secrets
82
+ )
83
+ if self.example:
84
+ example_url, _ = extend_hub_uri_if_needed(
85
+ uri=self.url, asset_type=self.ASSET_TYPE, file=self.example
86
+ )
87
+ self._download_object(
88
+ obj_url=example_url, target_name=self.example, secrets=secrets
89
+ )
90
+
91
+ def download_files(
92
+ self,
93
+ local_path: Optional[str] = None,
94
+ download_example: bool = True,
95
+ ):
96
+ """
97
+ Download this hub module’s code file.
98
+ :param local_path: Target directory to download the module files to. Defaults to the current working directory.
99
+ This path will be used to locate the code file when importing it as a module.
100
+ :param download_example: Whether to download the example notebook if available. Defaults to True.
101
+ """
102
+ super().download_files(
103
+ local_path=local_path,
104
+ download_example=download_example,
105
+ )
106
+
107
+ # TODO: Remove this in 1.13.0
108
+ @deprecated(
109
+ version="1.11.0",
110
+ reason="This function is deprecated and will be removed in 1.13. You can get the module source file path by"
111
+ " calling get_src_file_path() instead.",
112
+ category=FutureWarning,
113
+ )
114
+ def get_module_file_path(self):
115
+ """Get the full path to the module's code file."""
116
+ return super().get_src_file_path()
117
+
118
+
119
+ def get_hub_module(
120
+ url: str,
121
+ download_files: bool = True,
122
+ secrets: Optional[dict] = None,
123
+ local_path: Optional[str] = None,
124
+ ) -> HubModule:
125
+ """
126
+ Get a hub-module object containing metadata of the requested module.
127
+ :param url: Hub module url in the format "hub://[<source>/]<item-name>[:<tag>]"
128
+ :param download_files: When set to True, the module files (code file and example notebook) are downloaded
129
+ :param secrets: Optional, credentials dict for DB or URL (s3, v3io, ...)
130
+ :param local_path: Path to target directory for the module files. Ignored when download_files is set to False.
131
+ Defaults to the current working directory.
132
+
133
+ :return: HubModule object
134
+ """
135
+ item_yaml_url, is_hub_uri = extend_hub_uri_if_needed(
136
+ uri=url, asset_type=HubSourceType.modules, file="item.yaml"
137
+ )
138
+ if not is_hub_uri:
139
+ raise mlrun.errors.MLRunInvalidArgumentError("Not a valid hub URL")
140
+ yaml_obj = get_object(url=item_yaml_url, secrets=secrets)
141
+ item_yaml = yaml.safe_load(yaml_obj)
142
+ spec = item_yaml.pop("spec", {})
143
+ hub_module = HubModule(**item_yaml, **spec, url=url)
144
+ if download_files:
145
+ with warnings.catch_warnings():
146
+ warnings.filterwarnings("ignore", category=FutureWarning)
147
+ hub_module.download_module_files(local_path=local_path, secrets=secrets)
148
+ return hub_module
149
+
150
+
151
+ def import_module(
152
+ url: str,
153
+ install_requirements: bool = False,
154
+ secrets: Optional[dict] = None,
155
+ local_path: Optional[str] = None,
156
+ ):
157
+ """
158
+ Import a module from the hub to use directly.
159
+ :param url: hub module url in the format "hub://[<source>/]<item-name>[:<tag>]"
160
+ :param install_requirements: when set to True, the module's requirements are installed.
161
+ :param secrets: optional, credentials dict for DB or URL (s3, v3io, ...)
162
+ :param local_path: Path to target directory for the module files (code and example notebook).
163
+ Defaults to the current working directory.
164
+
165
+ :return: the module
166
+ """
167
+ hub_module: HubModule = get_hub_module(
168
+ url=url, download_files=True, secrets=secrets, local_path=local_path
169
+ )
170
+ if install_requirements:
171
+ hub_module.install_requirements()
172
+ return hub_module.module()
mlrun/hub/step.py ADDED
@@ -0,0 +1,113 @@
1
+ # Copyright 2025 Iguazio
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from pathlib import Path
16
+ from typing import Optional
17
+
18
+ import yaml
19
+
20
+ from mlrun.common.schemas.hub import HubSourceType
21
+ from mlrun.run import get_object
22
+
23
+ from ..errors import MLRunInvalidArgumentError
24
+ from ..utils import extend_hub_uri_if_needed
25
+ from .base import HubAsset
26
+
27
+
28
+ class HubStep(HubAsset):
29
+ ASSET_TYPE = HubSourceType.steps
30
+
31
+ def __init__(
32
+ self,
33
+ name: str,
34
+ version: str,
35
+ class_name: str,
36
+ default_handler: str,
37
+ description: Optional[str] = None,
38
+ categories: Optional[list] = None,
39
+ requirements: Optional[list] = None,
40
+ local_path: Optional[Path] = None,
41
+ filename: Optional[str] = None,
42
+ example: Optional[str] = None,
43
+ url: Optional[str] = None,
44
+ **kwargs, # catch all for unused args
45
+ ):
46
+ super().__init__(
47
+ name=name,
48
+ version=version,
49
+ description=description,
50
+ categories=categories,
51
+ requirements=requirements,
52
+ local_path=local_path,
53
+ filename=filename,
54
+ example=example,
55
+ url=url,
56
+ )
57
+ self.class_name = class_name
58
+ self.default_handler = default_handler
59
+
60
+ def download_files(
61
+ self,
62
+ local_path: Optional[str] = None,
63
+ download_example: bool = False,
64
+ ):
65
+ """
66
+ Download this step's code file.
67
+ :param local_path: Target directory to download the step files to. Defaults to the current working directory.
68
+ This path will be used to locate the code file when importing it as a python module.
69
+ :param download_example: Whether to download the example notebook if available. Defaults to False.
70
+ """
71
+ super().download_files(
72
+ local_path=local_path,
73
+ download_example=download_example,
74
+ )
75
+
76
+
77
+ def get_hub_step(
78
+ url: str,
79
+ local_path: Optional[str] = None,
80
+ download_files: bool = True,
81
+ include_example: bool = False,
82
+ ) -> HubStep:
83
+ """
84
+ Get a hub-step object containing metadata of the requested step.
85
+ :param url: Hub step url in the format "hub://[<source>/]<item-name>[:<tag>]"
86
+ :param local_path: Path to target directory for the step files. Ignored when download_files is set to False.
87
+ Defaults to the current working directory.
88
+ :param download_files: When set to True, the step code files are downloaded
89
+ :param include_example: When set to True, the example notebook will also be downloaded (ignored if download_files is
90
+ False)
91
+
92
+ :return: HubStep object
93
+ """
94
+ item_yaml_url, is_hub_uri = extend_hub_uri_if_needed(
95
+ uri=url, asset_type=HubSourceType.steps, file="item.yaml"
96
+ )
97
+ if not is_hub_uri:
98
+ raise MLRunInvalidArgumentError("Not a valid hub URL")
99
+ yaml_obj = get_object(url=item_yaml_url)
100
+ item_yaml = yaml.safe_load(yaml_obj)
101
+ spec = item_yaml.pop("spec", {})
102
+ class_name = item_yaml.pop("className", "")
103
+ default_handler = item_yaml.pop("defaultHandler", "")
104
+ hub_step = HubStep(
105
+ **item_yaml,
106
+ **spec,
107
+ class_name=class_name,
108
+ default_handler=default_handler,
109
+ url=url,
110
+ )
111
+ if download_files:
112
+ hub_step.download_files(local_path=local_path, download_example=include_example)
113
+ return hub_step
mlrun/k8s_utils.py CHANGED
@@ -26,6 +26,10 @@ from .config import config as mlconfig
26
26
 
27
27
  _running_inside_kubernetes_cluster = None
28
28
 
29
+ K8sObj = typing.Union[kubernetes.client.V1Affinity, kubernetes.client.V1Toleration]
30
+ SanitizedK8sObj = dict[str, typing.Any]
31
+ K8sObjList = typing.Union[list[K8sObj], list[SanitizedK8sObj]]
32
+
29
33
 
30
34
  def is_running_inside_kubernetes_cluster():
31
35
  global _running_inside_kubernetes_cluster
@@ -232,6 +236,54 @@ def validate_node_selectors(
232
236
  return True
233
237
 
234
238
 
239
+ def sanitize_k8s_objects(
240
+ k8s_objects: typing.Union[None, K8sObjList, SanitizedK8sObj, K8sObj],
241
+ ) -> typing.Union[list[SanitizedK8sObj], SanitizedK8sObj]:
242
+ """Convert K8s objects to dicts. Handles single objects or lists."""
243
+ api_client = kubernetes.client.ApiClient()
244
+ if not k8s_objects:
245
+ return k8s_objects
246
+
247
+ def _sanitize_k8s_object(k8s_obj):
248
+ return (
249
+ api_client.sanitize_for_serialization(k8s_obj)
250
+ if hasattr(k8s_obj, "to_dict")
251
+ else k8s_obj
252
+ )
253
+
254
+ return (
255
+ [_sanitize_k8s_object(k8s_obj) for k8s_obj in k8s_objects]
256
+ if isinstance(k8s_objects, list)
257
+ else _sanitize_k8s_object(k8s_objects)
258
+ )
259
+
260
+
261
+ def sanitize_scheduling_configuration(
262
+ tolerations: typing.Optional[list[kubernetes.client.V1Toleration]] = None,
263
+ affinity: typing.Optional[kubernetes.client.V1Affinity] = None,
264
+ ) -> tuple[
265
+ typing.Optional[list[dict]],
266
+ typing.Optional[dict],
267
+ ]:
268
+ """
269
+ Sanitizes pod scheduling configuration for serialization.
270
+
271
+ Takes affinity and tolerations and converts them to
272
+ JSON-serializable dictionaries using the Kubernetes API client's
273
+ sanitization method.
274
+
275
+ Args:
276
+ affinity: Pod affinity/anti-affinity rules
277
+ tolerations: List of toleration rules
278
+
279
+ Returns:
280
+ Tuple of (sanitized_affinity, sanitized_tolerations)
281
+ - affinity: Sanitized dict representation or None
282
+ - tolerations: List of sanitized dict representations or None
283
+ """
284
+ return sanitize_k8s_objects(tolerations), sanitize_k8s_objects(affinity)
285
+
286
+
235
287
  def enrich_preemption_mode(
236
288
  preemption_mode: typing.Optional[str],
237
289
  node_selector: dict[str, str],
@@ -269,8 +321,8 @@ def enrich_preemption_mode(
269
321
  )
270
322
 
271
323
  enriched_node_selector = copy.deepcopy(node_selector or {})
272
- enriched_tolerations = copy.deepcopy(tolerations or [])
273
- enriched_affinity = copy.deepcopy(affinity)
324
+ enriched_tolerations = _safe_copy_tolerations(tolerations or [])
325
+ enriched_affinity = _safe_copy_affinity(affinity)
274
326
  preemptible_tolerations = generate_preemptible_tolerations()
275
327
 
276
328
  if handler := _get_mode_handler(preemption_mode):
@@ -288,6 +340,57 @@ def enrich_preemption_mode(
288
340
  )
289
341
 
290
342
 
343
+ def _safe_copy_tolerations(
344
+ tolerations: list[kubernetes.client.V1Toleration],
345
+ ) -> list[kubernetes.client.V1Toleration]:
346
+ """
347
+ Safely copy a list of V1Toleration objects without mutating the originals.
348
+
349
+ Explicitly reconstructs V1Toleration objects instead of using deepcopy() to avoid
350
+ serialization errors with K8s client objects that contain threading primitives
351
+ and non-copyable elements like RLock objects.
352
+
353
+ Args:
354
+ tolerations: List of V1Toleration objects to copy
355
+
356
+ Returns:
357
+ New list containing copied V1Toleration objects with identical field values"""
358
+ return [
359
+ kubernetes.client.V1Toleration(
360
+ effect=toleration.effect,
361
+ key=toleration.key,
362
+ value=toleration.value,
363
+ operator=toleration.operator,
364
+ toleration_seconds=toleration.toleration_seconds,
365
+ )
366
+ for toleration in tolerations
367
+ ]
368
+
369
+
370
+ def _safe_copy_affinity(
371
+ affinity: kubernetes.client.V1Affinity,
372
+ ) -> kubernetes.client.V1Affinity:
373
+ """
374
+ Safely create a deep copy of a V1Affinity object.
375
+
376
+ Uses K8s API client serialization/deserialization instead of deepcopy() to avoid
377
+ errors with threading primitives and complex internal structures in K8s objects.
378
+ Serializes to dict then deserializes back to a clean V1Affinity object.
379
+
380
+ Args:
381
+ affinity: V1Affinity object to copy, or None
382
+
383
+ Returns:
384
+ New V1Affinity object with identical field values, or None if input was None
385
+ """
386
+ if not affinity:
387
+ return None
388
+ api_client = kubernetes.client.ApiClient()
389
+ # Convert to dict then back to object properly
390
+ affinity_dict = api_client.sanitize_for_serialization(affinity)
391
+ return api_client._ApiClient__deserialize(affinity_dict, "V1Affinity")
392
+
393
+
291
394
  def _get_mode_handler(mode: str):
292
395
  return {
293
396
  mlrun.common.schemas.PreemptionModes.prevent: _handle_prevent_mode,
@@ -367,20 +470,6 @@ def _handle_allow_mode(
367
470
  list[kubernetes.client.V1Toleration],
368
471
  typing.Optional[kubernetes.client.V1Affinity],
369
472
  ]:
370
- for op in [
371
- mlrun.common.schemas.NodeSelectorOperator.node_selector_op_not_in.value,
372
- mlrun.common.schemas.NodeSelectorOperator.node_selector_op_in.value,
373
- ]:
374
- affinity = _prune_affinity_node_selector_requirement(
375
- generate_preemptible_node_selector_requirements(op),
376
- affinity=affinity,
377
- )
378
-
379
- node_selector = _prune_node_selector(
380
- mlconfig.get_preemptible_node_selector(),
381
- enriched_node_selector=node_selector,
382
- )
383
-
384
473
  tolerations = _merge_tolerations(tolerations, preemptible_tolerations)
385
474
  return node_selector, tolerations, affinity
386
475
 
mlrun/launcher/base.py CHANGED
@@ -16,7 +16,8 @@ import ast
16
16
  import copy
17
17
  import os
18
18
  import uuid
19
- from typing import Any, Callable, Optional, Union
19
+ from collections.abc import Callable
20
+ from typing import Any, Optional, Union
20
21
 
21
22
  import mlrun.common.constants
22
23
  import mlrun.common.runtimes.constants
@@ -157,6 +158,19 @@ class BaseLauncher(abc.ABC):
157
158
  ]:
158
159
  mlrun.utils.helpers.warn_on_deprecated_image(image)
159
160
 
161
+ # Raise an error if retry is configured for a runtime that doesn't support retries.
162
+ # For local runs, we intentionally skip this validation and allow the run to proceed, since they are typically
163
+ # used for debugging purposes, and in such cases we avoid blocking their execution.
164
+ if (
165
+ not mlrun.runtimes.RuntimeKinds.is_local_runtime(runtime.kind)
166
+ and run.spec.retry.count
167
+ and runtime.kind not in mlrun.runtimes.RuntimeKinds.retriable_runtimes()
168
+ ):
169
+ raise mlrun.errors.MLRunInvalidArgumentError(
170
+ f"Retry is not supported for {runtime.kind} runtime, supported runtimes are: "
171
+ f"{mlrun.runtimes.RuntimeKinds.retriable_runtimes()}"
172
+ )
173
+
160
174
  @staticmethod
161
175
  def _validate_output_path(
162
176
  runtime: "mlrun.runtimes.BaseRuntime",
@@ -268,12 +282,6 @@ class BaseLauncher(abc.ABC):
268
282
 
269
283
  run.metadata.name = mlrun.utils.normalize_name(
270
284
  name=name or run.metadata.name or def_name,
271
- # if name or runspec.metadata.name are set then it means that is user defined name and we want to warn the
272
- # user that the passed name needs to be set without underscore, if its not user defined but rather enriched
273
- # from the handler(function) name then we replace the underscore without warning the user.
274
- # most of the time handlers will have `_` in the handler name (python convention is to separate function
275
- # words with `_`), therefore we don't want to be noisy when normalizing the run name
276
- verbose=bool(name or run.metadata.name),
277
285
  )
278
286
  mlrun.utils.verify_field_regex(
279
287
  "run.metadata.name", run.metadata.name, mlrun.utils.regex.run_name