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.
- mlrun/__init__.py +24 -3
- mlrun/__main__.py +0 -4
- mlrun/artifacts/dataset.py +2 -2
- mlrun/artifacts/document.py +6 -1
- mlrun/artifacts/llm_prompt.py +21 -15
- mlrun/artifacts/model.py +3 -3
- mlrun/artifacts/plots.py +1 -1
- mlrun/{model_monitoring/db/tsdb/tdengine → auth}/__init__.py +2 -3
- mlrun/auth/nuclio.py +89 -0
- mlrun/auth/providers.py +429 -0
- mlrun/auth/utils.py +415 -0
- mlrun/common/constants.py +14 -0
- mlrun/common/model_monitoring/helpers.py +123 -0
- mlrun/common/runtimes/constants.py +28 -0
- mlrun/common/schemas/__init__.py +14 -3
- mlrun/common/schemas/alert.py +2 -2
- mlrun/common/schemas/api_gateway.py +3 -0
- mlrun/common/schemas/auth.py +12 -10
- mlrun/common/schemas/client_spec.py +4 -0
- mlrun/common/schemas/constants.py +25 -0
- mlrun/common/schemas/frontend_spec.py +1 -8
- mlrun/common/schemas/function.py +34 -0
- mlrun/common/schemas/hub.py +33 -20
- mlrun/common/schemas/model_monitoring/__init__.py +2 -1
- mlrun/common/schemas/model_monitoring/constants.py +12 -15
- mlrun/common/schemas/model_monitoring/functions.py +13 -4
- mlrun/common/schemas/model_monitoring/model_endpoints.py +11 -0
- mlrun/common/schemas/pipeline.py +1 -1
- mlrun/common/schemas/secret.py +17 -2
- mlrun/common/secrets.py +95 -1
- mlrun/common/types.py +10 -10
- mlrun/config.py +69 -19
- mlrun/data_types/infer.py +2 -2
- mlrun/datastore/__init__.py +12 -5
- mlrun/datastore/azure_blob.py +162 -47
- mlrun/datastore/base.py +274 -10
- mlrun/datastore/datastore.py +7 -2
- mlrun/datastore/datastore_profile.py +84 -22
- mlrun/datastore/model_provider/huggingface_provider.py +225 -41
- mlrun/datastore/model_provider/mock_model_provider.py +87 -0
- mlrun/datastore/model_provider/model_provider.py +206 -74
- mlrun/datastore/model_provider/openai_provider.py +226 -66
- mlrun/datastore/s3.py +39 -18
- mlrun/datastore/sources.py +1 -1
- mlrun/datastore/store_resources.py +4 -4
- mlrun/datastore/storeytargets.py +17 -12
- mlrun/datastore/targets.py +1 -1
- mlrun/datastore/utils.py +25 -6
- mlrun/datastore/v3io.py +1 -1
- mlrun/db/base.py +63 -32
- mlrun/db/httpdb.py +373 -153
- mlrun/db/nopdb.py +54 -21
- mlrun/errors.py +4 -2
- mlrun/execution.py +66 -25
- mlrun/feature_store/api.py +1 -1
- mlrun/feature_store/common.py +1 -1
- mlrun/feature_store/feature_vector_utils.py +1 -1
- mlrun/feature_store/steps.py +8 -6
- mlrun/frameworks/_common/utils.py +3 -3
- mlrun/frameworks/_dl_common/loggers/logger.py +1 -1
- mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +2 -1
- mlrun/frameworks/_ml_common/loggers/mlrun_logger.py +1 -1
- mlrun/frameworks/_ml_common/utils.py +2 -1
- mlrun/frameworks/auto_mlrun/auto_mlrun.py +4 -3
- mlrun/frameworks/lgbm/mlrun_interfaces/mlrun_interface.py +2 -1
- mlrun/frameworks/onnx/dataset.py +2 -1
- mlrun/frameworks/onnx/mlrun_interface.py +2 -1
- mlrun/frameworks/pytorch/callbacks/logging_callback.py +5 -4
- mlrun/frameworks/pytorch/callbacks/mlrun_logging_callback.py +2 -1
- mlrun/frameworks/pytorch/callbacks/tensorboard_logging_callback.py +2 -1
- mlrun/frameworks/pytorch/utils.py +2 -1
- mlrun/frameworks/sklearn/metric.py +2 -1
- mlrun/frameworks/tf_keras/callbacks/logging_callback.py +5 -4
- mlrun/frameworks/tf_keras/callbacks/mlrun_logging_callback.py +2 -1
- mlrun/frameworks/tf_keras/callbacks/tensorboard_logging_callback.py +2 -1
- mlrun/hub/__init__.py +52 -0
- mlrun/hub/base.py +142 -0
- mlrun/hub/module.py +172 -0
- mlrun/hub/step.py +113 -0
- mlrun/k8s_utils.py +105 -16
- mlrun/launcher/base.py +15 -7
- mlrun/launcher/local.py +4 -1
- mlrun/model.py +14 -4
- mlrun/model_monitoring/__init__.py +0 -1
- mlrun/model_monitoring/api.py +65 -28
- mlrun/model_monitoring/applications/__init__.py +1 -1
- mlrun/model_monitoring/applications/base.py +299 -128
- mlrun/model_monitoring/applications/context.py +2 -4
- mlrun/model_monitoring/controller.py +132 -58
- mlrun/model_monitoring/db/_schedules.py +38 -29
- mlrun/model_monitoring/db/_stats.py +6 -16
- mlrun/model_monitoring/db/tsdb/__init__.py +9 -7
- mlrun/model_monitoring/db/tsdb/base.py +29 -9
- mlrun/model_monitoring/db/tsdb/preaggregate.py +234 -0
- mlrun/model_monitoring/db/tsdb/stream_graph_steps.py +63 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/queries/timescaledb_metrics_queries.py +414 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/queries/timescaledb_predictions_queries.py +376 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/queries/timescaledb_results_queries.py +590 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_connection.py +434 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_connector.py +541 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_operations.py +808 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_schema.py +502 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_stream.py +163 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_stream_graph_steps.py +60 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/utils/timescaledb_dataframe_processor.py +141 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/utils/timescaledb_query_builder.py +585 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/writer_graph_steps.py +73 -0
- mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +20 -9
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +235 -51
- mlrun/model_monitoring/features_drift_table.py +2 -1
- mlrun/model_monitoring/helpers.py +30 -6
- mlrun/model_monitoring/stream_processing.py +34 -28
- mlrun/model_monitoring/writer.py +224 -4
- mlrun/package/__init__.py +2 -1
- mlrun/platforms/__init__.py +0 -43
- mlrun/platforms/iguazio.py +8 -4
- mlrun/projects/operations.py +17 -11
- mlrun/projects/pipelines.py +2 -2
- mlrun/projects/project.py +187 -123
- mlrun/run.py +95 -21
- mlrun/runtimes/__init__.py +2 -186
- mlrun/runtimes/base.py +103 -25
- mlrun/runtimes/constants.py +225 -0
- mlrun/runtimes/daskjob.py +5 -2
- mlrun/runtimes/databricks_job/databricks_runtime.py +2 -1
- mlrun/runtimes/local.py +5 -2
- mlrun/runtimes/mounts.py +20 -2
- mlrun/runtimes/nuclio/__init__.py +12 -7
- mlrun/runtimes/nuclio/api_gateway.py +36 -6
- mlrun/runtimes/nuclio/application/application.py +339 -40
- mlrun/runtimes/nuclio/function.py +222 -72
- mlrun/runtimes/nuclio/serving.py +132 -42
- mlrun/runtimes/pod.py +213 -21
- mlrun/runtimes/utils.py +49 -9
- mlrun/secrets.py +99 -14
- mlrun/serving/__init__.py +2 -0
- mlrun/serving/remote.py +84 -11
- mlrun/serving/routers.py +26 -44
- mlrun/serving/server.py +138 -51
- mlrun/serving/serving_wrapper.py +6 -2
- mlrun/serving/states.py +997 -283
- mlrun/serving/steps.py +62 -0
- mlrun/serving/system_steps.py +149 -95
- mlrun/serving/v2_serving.py +9 -10
- mlrun/track/trackers/mlflow_tracker.py +29 -31
- mlrun/utils/helpers.py +292 -94
- mlrun/utils/http.py +9 -2
- mlrun/utils/notifications/notification/base.py +18 -0
- mlrun/utils/notifications/notification/git.py +3 -5
- mlrun/utils/notifications/notification/mail.py +39 -16
- mlrun/utils/notifications/notification/slack.py +2 -4
- mlrun/utils/notifications/notification/webhook.py +2 -5
- mlrun/utils/notifications/notification_pusher.py +3 -3
- mlrun/utils/version/version.json +2 -2
- mlrun/utils/version/version.py +3 -4
- {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/METADATA +63 -74
- {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/RECORD +161 -143
- mlrun/api/schemas/__init__.py +0 -259
- mlrun/db/auth_utils.py +0 -152
- mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +0 -344
- mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +0 -75
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connection.py +0 -281
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +0 -1266
- {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/WHEEL +0 -0
- {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/entry_points.txt +0 -0
- {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/licenses/LICENSE +0 -0
- {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
|
|
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 =
|
|
273
|
-
enriched_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
|
|
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
|