mlrun 1.7.0rc25__py3-none-any.whl → 1.7.0rc27__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/__main__.py +7 -7
- mlrun/alerts/alert.py +13 -1
- mlrun/artifacts/manager.py +5 -0
- mlrun/common/constants.py +2 -2
- mlrun/common/formatters/base.py +9 -9
- mlrun/common/schemas/alert.py +4 -8
- mlrun/common/schemas/api_gateway.py +7 -0
- mlrun/common/schemas/constants.py +3 -0
- mlrun/common/schemas/model_monitoring/constants.py +20 -9
- mlrun/config.py +6 -11
- mlrun/datastore/datastore.py +3 -3
- mlrun/datastore/google_cloud_storage.py +6 -2
- mlrun/datastore/snowflake_utils.py +3 -1
- mlrun/datastore/sources.py +23 -9
- mlrun/datastore/targets.py +27 -13
- mlrun/db/base.py +10 -0
- mlrun/db/httpdb.py +45 -33
- mlrun/db/nopdb.py +10 -1
- mlrun/execution.py +18 -10
- mlrun/feature_store/retrieval/spark_merger.py +2 -1
- mlrun/model.py +21 -0
- mlrun/model_monitoring/db/stores/__init__.py +5 -3
- mlrun/model_monitoring/db/stores/base/store.py +36 -1
- mlrun/model_monitoring/db/stores/sqldb/sql_store.py +4 -38
- mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +19 -27
- mlrun/model_monitoring/db/tsdb/__init__.py +4 -7
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +4 -1
- mlrun/model_monitoring/helpers.py +9 -5
- mlrun/projects/project.py +68 -69
- mlrun/render.py +10 -5
- mlrun/run.py +2 -2
- mlrun/runtimes/nuclio/function.py +20 -0
- mlrun/runtimes/pod.py +5 -29
- mlrun/serving/routers.py +75 -59
- mlrun/serving/server.py +1 -0
- mlrun/serving/v2_serving.py +8 -1
- mlrun/utils/helpers.py +33 -1
- mlrun/utils/notifications/notification/base.py +4 -0
- mlrun/utils/notifications/notification/git.py +21 -0
- mlrun/utils/notifications/notification/slack.py +8 -0
- mlrun/utils/notifications/notification/webhook.py +29 -0
- mlrun/utils/notifications/notification_pusher.py +1 -1
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.7.0rc25.dist-info → mlrun-1.7.0rc27.dist-info}/METADATA +6 -6
- {mlrun-1.7.0rc25.dist-info → mlrun-1.7.0rc27.dist-info}/RECORD +49 -49
- {mlrun-1.7.0rc25.dist-info → mlrun-1.7.0rc27.dist-info}/WHEEL +1 -1
- {mlrun-1.7.0rc25.dist-info → mlrun-1.7.0rc27.dist-info}/LICENSE +0 -0
- {mlrun-1.7.0rc25.dist-info → mlrun-1.7.0rc27.dist-info}/entry_points.txt +0 -0
- {mlrun-1.7.0rc25.dist-info → mlrun-1.7.0rc27.dist-info}/top_level.txt +0 -0
mlrun/projects/project.py
CHANGED
|
@@ -1007,8 +1007,13 @@ class ProjectSpec(ModelObj):
|
|
|
1007
1007
|
key = artifact.key
|
|
1008
1008
|
artifact = artifact.to_dict()
|
|
1009
1009
|
else: # artifact is a dict
|
|
1010
|
-
# imported artifacts don't have metadata,spec,status fields
|
|
1011
|
-
key_field =
|
|
1010
|
+
# imported/legacy artifacts don't have metadata,spec,status fields
|
|
1011
|
+
key_field = (
|
|
1012
|
+
"key"
|
|
1013
|
+
if _is_imported_artifact(artifact)
|
|
1014
|
+
or mlrun.utils.is_legacy_artifact(artifact)
|
|
1015
|
+
else "metadata.key"
|
|
1016
|
+
)
|
|
1012
1017
|
key = mlrun.utils.get_in(artifact, key_field, "")
|
|
1013
1018
|
if not key:
|
|
1014
1019
|
raise ValueError(f'artifacts "{key_field}" must be specified')
|
|
@@ -2127,6 +2132,7 @@ class MlrunProject(ModelObj):
|
|
|
2127
2132
|
deploy_histogram_data_drift_app: bool = True,
|
|
2128
2133
|
wait_for_deployment: bool = False,
|
|
2129
2134
|
rebuild_images: bool = False,
|
|
2135
|
+
fetch_credentials_from_sys_config: bool = False,
|
|
2130
2136
|
) -> None:
|
|
2131
2137
|
"""
|
|
2132
2138
|
Deploy model monitoring application controller, writer and stream functions.
|
|
@@ -2136,17 +2142,18 @@ class MlrunProject(ModelObj):
|
|
|
2136
2142
|
The stream function goal is to monitor the log of the data stream. It is triggered when a new log entry
|
|
2137
2143
|
is detected. It processes the new events into statistics that are then written to statistics databases.
|
|
2138
2144
|
|
|
2139
|
-
:param default_controller_image:
|
|
2140
|
-
:param base_period:
|
|
2141
|
-
|
|
2142
|
-
:param image:
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
:param deploy_histogram_data_drift_app:
|
|
2146
|
-
:param wait_for_deployment:
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
:param rebuild_images:
|
|
2145
|
+
:param default_controller_image: Deprecated.
|
|
2146
|
+
:param base_period: The time period in minutes in which the model monitoring controller
|
|
2147
|
+
function is triggered. By default, the base period is 10 minutes.
|
|
2148
|
+
:param image: The image of the model monitoring controller, writer, monitoring
|
|
2149
|
+
stream & histogram data drift functions, which are real time nuclio
|
|
2150
|
+
functions. By default, the image is mlrun/mlrun.
|
|
2151
|
+
:param deploy_histogram_data_drift_app: If true, deploy the default histogram-based data drift application.
|
|
2152
|
+
:param wait_for_deployment: If true, return only after the deployment is done on the backend.
|
|
2153
|
+
Otherwise, deploy the model monitoring infrastructure on the
|
|
2154
|
+
background, including the histogram data drift app if selected.
|
|
2155
|
+
:param rebuild_images: If true, force rebuild of model monitoring infrastructure images.
|
|
2156
|
+
:param fetch_credentials_from_sys_config: If true, fetch the credentials from the system configuration.
|
|
2150
2157
|
"""
|
|
2151
2158
|
if default_controller_image != "mlrun/mlrun":
|
|
2152
2159
|
# TODO: Remove this in 1.9.0
|
|
@@ -2163,6 +2170,7 @@ class MlrunProject(ModelObj):
|
|
|
2163
2170
|
base_period=base_period,
|
|
2164
2171
|
deploy_histogram_data_drift_app=deploy_histogram_data_drift_app,
|
|
2165
2172
|
rebuild_images=rebuild_images,
|
|
2173
|
+
fetch_credentials_from_sys_config=fetch_credentials_from_sys_config,
|
|
2166
2174
|
)
|
|
2167
2175
|
|
|
2168
2176
|
if wait_for_deployment:
|
|
@@ -2337,7 +2345,8 @@ class MlrunProject(ModelObj):
|
|
|
2337
2345
|
Default: job
|
|
2338
2346
|
:param image: Docker image to be used, can also be specified in the function object/yaml
|
|
2339
2347
|
:param handler: Default function handler to invoke (can only be set with .py/.ipynb files)
|
|
2340
|
-
:param with_repo: Add (clone) the current repo to the build source
|
|
2348
|
+
:param with_repo: Add (clone) the current repo to the build source - use when the function code is in
|
|
2349
|
+
the project repo (project.spec.source).
|
|
2341
2350
|
:param tag: Function version tag to set (none for current or 'latest')
|
|
2342
2351
|
Specifying a tag as a parameter will update the project's tagged function
|
|
2343
2352
|
(myfunc:v1) and the untagged function (myfunc)
|
|
@@ -2484,25 +2493,17 @@ class MlrunProject(ModelObj):
|
|
|
2484
2493
|
self.spec.remove_function(name)
|
|
2485
2494
|
|
|
2486
2495
|
def remove_model_monitoring_function(self, name: Union[str, list[str]]):
|
|
2487
|
-
"""
|
|
2496
|
+
"""delete the specified model-monitoring-app function/s
|
|
2488
2497
|
|
|
2489
2498
|
:param name: name of the model-monitoring-function/s (under the project)
|
|
2490
2499
|
"""
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
self.remove_function(name=func_name)
|
|
2499
|
-
logger.info(
|
|
2500
|
-
f"{func_name} function has been removed from {self.name} project"
|
|
2501
|
-
)
|
|
2502
|
-
else:
|
|
2503
|
-
raise logger.warn(
|
|
2504
|
-
f"There is no model monitoring function with {func_name} name"
|
|
2505
|
-
)
|
|
2500
|
+
# TODO: Remove this in 1.9.0
|
|
2501
|
+
warnings.warn(
|
|
2502
|
+
"'remove_model_monitoring_function' is deprecated and will be removed in 1.9.0. "
|
|
2503
|
+
"Please use `delete_model_monitoring_function` instead.",
|
|
2504
|
+
FutureWarning,
|
|
2505
|
+
)
|
|
2506
|
+
self.delete_model_monitoring_function(name)
|
|
2506
2507
|
|
|
2507
2508
|
def delete_model_monitoring_function(self, name: Union[str, list[str]]):
|
|
2508
2509
|
"""delete the specified model-monitoring-app function/s
|
|
@@ -3204,49 +3205,44 @@ class MlrunProject(ModelObj):
|
|
|
3204
3205
|
stream_path: Optional[str] = None,
|
|
3205
3206
|
tsdb_connection: Optional[str] = None,
|
|
3206
3207
|
):
|
|
3207
|
-
"""
|
|
3208
|
+
"""
|
|
3209
|
+
Set the credentials that will be used by the project's model monitoring
|
|
3208
3210
|
infrastructure functions. Important to note that you have to set the credentials before deploying any
|
|
3209
3211
|
model monitoring or serving function.
|
|
3210
3212
|
|
|
3211
|
-
:param access_key: Model Monitoring access key for managing user permissions
|
|
3212
|
-
:param endpoint_store_connection: Endpoint store connection string
|
|
3213
|
-
|
|
3214
|
-
|
|
3213
|
+
:param access_key: Model Monitoring access key for managing user permissions.
|
|
3214
|
+
:param endpoint_store_connection: Endpoint store connection string. By default, None.
|
|
3215
|
+
Options:
|
|
3216
|
+
1. None, will be set from the system configuration.
|
|
3217
|
+
2. v3io - for v3io endpoint store,
|
|
3218
|
+
pass `v3io` and the system will generate the exact path.
|
|
3219
|
+
3. MySQL/SQLite - for SQL endpoint store, please provide full
|
|
3220
|
+
connection string, for example
|
|
3221
|
+
mysql+pymysql://<username>:<password>@<host>:<port>/<db_name>
|
|
3222
|
+
:param stream_path: Path to the model monitoring stream. By default, None.
|
|
3223
|
+
Options:
|
|
3224
|
+
1. None, will be set from the system configuration.
|
|
3225
|
+
2. v3io - for v3io stream,
|
|
3226
|
+
pass `v3io` and the system will generate the exact path.
|
|
3227
|
+
3. Kafka - for Kafka stream, please provide full connection string without
|
|
3228
|
+
custom topic, for example kafka://<some_kafka_broker>:<port>.
|
|
3229
|
+
:param tsdb_connection: Connection string to the time series database. By default, None.
|
|
3230
|
+
Options:
|
|
3231
|
+
1. None, will be set from the system configuration.
|
|
3232
|
+
2. v3io - for v3io stream,
|
|
3233
|
+
pass `v3io` and the system will generate the exact path.
|
|
3234
|
+
3. TDEngine - for TDEngine tsdb, please provide full websocket connection URL,
|
|
3235
|
+
for example taosws://<username>:<password>@<host>:<port>.
|
|
3215
3236
|
"""
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
mlrun.common.schemas.model_monitoring.ProjectSecretKeys.ENDPOINT_STORE_CONNECTION
|
|
3226
|
-
] = endpoint_store_connection
|
|
3227
|
-
|
|
3228
|
-
if stream_path:
|
|
3229
|
-
if stream_path.startswith("kafka://") and "?topic" in stream_path:
|
|
3230
|
-
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
3231
|
-
"Custom kafka topic is not allowed"
|
|
3232
|
-
)
|
|
3233
|
-
secrets_dict[
|
|
3234
|
-
mlrun.common.schemas.model_monitoring.ProjectSecretKeys.STREAM_PATH
|
|
3235
|
-
] = stream_path
|
|
3236
|
-
|
|
3237
|
-
if tsdb_connection:
|
|
3238
|
-
if not tsdb_connection.startswith("taosws://"):
|
|
3239
|
-
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
3240
|
-
"Currently only TDEngine websocket connection is supported for non-v3io TSDB,"
|
|
3241
|
-
"please provide a full URL (e.g. taosws://user:password@host:port)"
|
|
3242
|
-
)
|
|
3243
|
-
secrets_dict[
|
|
3244
|
-
mlrun.common.schemas.model_monitoring.ProjectSecretKeys.TSDB_CONNECTION
|
|
3245
|
-
] = tsdb_connection
|
|
3246
|
-
|
|
3247
|
-
self.set_secrets(
|
|
3248
|
-
secrets=secrets_dict,
|
|
3249
|
-
provider=mlrun.common.schemas.SecretProviderName.kubernetes,
|
|
3237
|
+
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
3238
|
+
db.set_model_monitoring_credentials(
|
|
3239
|
+
project=self.name,
|
|
3240
|
+
credentials={
|
|
3241
|
+
"access_key": access_key,
|
|
3242
|
+
"endpoint_store_connection": endpoint_store_connection,
|
|
3243
|
+
"stream_path": stream_path,
|
|
3244
|
+
"tsdb_connection": tsdb_connection,
|
|
3245
|
+
},
|
|
3250
3246
|
)
|
|
3251
3247
|
|
|
3252
3248
|
def run_function(
|
|
@@ -3663,6 +3659,7 @@ class MlrunProject(ModelObj):
|
|
|
3663
3659
|
kind: str = None,
|
|
3664
3660
|
category: typing.Union[str, mlrun.common.schemas.ArtifactCategories] = None,
|
|
3665
3661
|
tree: str = None,
|
|
3662
|
+
limit: int = None,
|
|
3666
3663
|
) -> mlrun.lists.ArtifactList:
|
|
3667
3664
|
"""List artifacts filtered by various parameters.
|
|
3668
3665
|
|
|
@@ -3692,6 +3689,7 @@ class MlrunProject(ModelObj):
|
|
|
3692
3689
|
:param kind: Return artifacts of the requested kind.
|
|
3693
3690
|
:param category: Return artifacts of the requested category.
|
|
3694
3691
|
:param tree: Return artifacts of the requested tree.
|
|
3692
|
+
:param limit: Maximum number of artifacts to return.
|
|
3695
3693
|
"""
|
|
3696
3694
|
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
3697
3695
|
return db.list_artifacts(
|
|
@@ -3706,6 +3704,7 @@ class MlrunProject(ModelObj):
|
|
|
3706
3704
|
kind=kind,
|
|
3707
3705
|
category=category,
|
|
3708
3706
|
tree=tree,
|
|
3707
|
+
limit=limit,
|
|
3709
3708
|
)
|
|
3710
3709
|
|
|
3711
3710
|
def list_models(
|
mlrun/render.py
CHANGED
|
@@ -283,9 +283,14 @@ function copyToClipboard(fld) {
|
|
|
283
283
|
}
|
|
284
284
|
function expandPanel(el) {
|
|
285
285
|
const panelName = "#" + el.getAttribute('paneName');
|
|
286
|
-
console.log(el.title);
|
|
287
286
|
|
|
288
|
-
|
|
287
|
+
// Get the base URL of the current notebook
|
|
288
|
+
var baseUrl = window.location.origin;
|
|
289
|
+
|
|
290
|
+
// Construct the full URL
|
|
291
|
+
var fullUrl = new URL(el.title, baseUrl).href;
|
|
292
|
+
|
|
293
|
+
document.querySelector(panelName + "-title").innerHTML = fullUrl
|
|
289
294
|
iframe = document.querySelector(panelName + "-body");
|
|
290
295
|
|
|
291
296
|
const tblcss = `<style> body { font-family: Arial, Helvetica, sans-serif;}
|
|
@@ -299,7 +304,7 @@ function expandPanel(el) {
|
|
|
299
304
|
}
|
|
300
305
|
|
|
301
306
|
function reqListener () {
|
|
302
|
-
if (
|
|
307
|
+
if (fullUrl.endsWith(".csv")) {
|
|
303
308
|
iframe.setAttribute("srcdoc", tblcss + csvToHtmlTable(this.responseText));
|
|
304
309
|
} else {
|
|
305
310
|
iframe.setAttribute("srcdoc", this.responseText);
|
|
@@ -309,11 +314,11 @@ function expandPanel(el) {
|
|
|
309
314
|
|
|
310
315
|
const oReq = new XMLHttpRequest();
|
|
311
316
|
oReq.addEventListener("load", reqListener);
|
|
312
|
-
oReq.open("GET",
|
|
317
|
+
oReq.open("GET", fullUrl);
|
|
313
318
|
oReq.send();
|
|
314
319
|
|
|
315
320
|
|
|
316
|
-
//iframe.src =
|
|
321
|
+
//iframe.src = fullUrl;
|
|
317
322
|
const resultPane = document.querySelector(panelName + "-pane");
|
|
318
323
|
if (resultPane.classList.contains("hidden")) {
|
|
319
324
|
resultPane.classList.remove("hidden");
|
mlrun/run.py
CHANGED
|
@@ -63,11 +63,11 @@ from .runtimes.funcdoc import update_function_entry_points
|
|
|
63
63
|
from .runtimes.nuclio.application import ApplicationRuntime
|
|
64
64
|
from .runtimes.utils import add_code_metadata, global_context
|
|
65
65
|
from .utils import (
|
|
66
|
+
RunKeys,
|
|
66
67
|
extend_hub_uri_if_needed,
|
|
67
68
|
get_in,
|
|
68
69
|
logger,
|
|
69
70
|
retry_until_successful,
|
|
70
|
-
run_keys,
|
|
71
71
|
update_in,
|
|
72
72
|
)
|
|
73
73
|
|
|
@@ -280,7 +280,7 @@ def get_or_create_ctx(
|
|
|
280
280
|
artifact_path = mlrun.utils.helpers.template_artifact_path(
|
|
281
281
|
mlconf.artifact_path, project or mlconf.default_project
|
|
282
282
|
)
|
|
283
|
-
update_in(newspec, ["spec",
|
|
283
|
+
update_in(newspec, ["spec", RunKeys.output_path], artifact_path)
|
|
284
284
|
|
|
285
285
|
newspec.setdefault("metadata", {})
|
|
286
286
|
update_in(newspec, "metadata.name", name, replace=False)
|
|
@@ -1327,3 +1327,23 @@ def get_nuclio_deploy_status(
|
|
|
1327
1327
|
else:
|
|
1328
1328
|
text = "\n".join(outputs) if outputs else ""
|
|
1329
1329
|
return state, address, name, last_log_timestamp, text, function_status
|
|
1330
|
+
|
|
1331
|
+
|
|
1332
|
+
def enrich_nuclio_function_from_headers(
|
|
1333
|
+
func: RemoteRuntime,
|
|
1334
|
+
headers: dict,
|
|
1335
|
+
):
|
|
1336
|
+
func.status.state = headers.get("x-mlrun-function-status", "")
|
|
1337
|
+
func.status.address = headers.get("x-mlrun-address", "")
|
|
1338
|
+
func.status.nuclio_name = headers.get("x-mlrun-name", "")
|
|
1339
|
+
func.status.internal_invocation_urls = (
|
|
1340
|
+
headers.get("x-mlrun-internal-invocation-urls", "").split(",")
|
|
1341
|
+
if headers.get("x-mlrun-internal-invocation-urls")
|
|
1342
|
+
else []
|
|
1343
|
+
)
|
|
1344
|
+
func.status.external_invocation_urls = (
|
|
1345
|
+
headers.get("x-mlrun-external-invocation-urls", "").split(",")
|
|
1346
|
+
if headers.get("x-mlrun-external-invocation-urls")
|
|
1347
|
+
else []
|
|
1348
|
+
)
|
|
1349
|
+
func.status.container_image = headers.get("x-mlrun-container-image", "")
|
mlrun/runtimes/pod.py
CHANGED
|
@@ -532,7 +532,9 @@ class KubeResourceSpec(FunctionSpec):
|
|
|
532
532
|
return
|
|
533
533
|
|
|
534
534
|
# merge node selectors - precedence to existing node selector
|
|
535
|
-
self.node_selector =
|
|
535
|
+
self.node_selector = mlrun.utils.helpers.merge_with_precedence(
|
|
536
|
+
node_selector, self.node_selector
|
|
537
|
+
)
|
|
536
538
|
|
|
537
539
|
def _merge_tolerations(
|
|
538
540
|
self,
|
|
@@ -1038,32 +1040,6 @@ class KubeResource(BaseRuntime, KfpAdapterMixin):
|
|
|
1038
1040
|
return True
|
|
1039
1041
|
return False
|
|
1040
1042
|
|
|
1041
|
-
def enrich_runtime_spec(
|
|
1042
|
-
self,
|
|
1043
|
-
project_node_selector: dict[str, str],
|
|
1044
|
-
):
|
|
1045
|
-
"""
|
|
1046
|
-
Enriches the runtime spec with the project-level node selector.
|
|
1047
|
-
|
|
1048
|
-
This method merges the project-level node selector with the existing function node_selector.
|
|
1049
|
-
The merge logic used here combines the two dictionaries, giving precedence to
|
|
1050
|
-
the keys in the runtime node_selector. If there are conflicting keys between the
|
|
1051
|
-
two dictionaries, the values from self.spec.node_selector will overwrite the
|
|
1052
|
-
values from project_node_selector.
|
|
1053
|
-
|
|
1054
|
-
Example:
|
|
1055
|
-
Suppose self.spec.node_selector = {"type": "gpu", "zone": "us-east-1"}
|
|
1056
|
-
and project_node_selector = {"type": "cpu", "environment": "production"}.
|
|
1057
|
-
After the merge, the resulting node_selector will be:
|
|
1058
|
-
{"type": "gpu", "zone": "us-east-1", "environment": "production"}
|
|
1059
|
-
|
|
1060
|
-
Note:
|
|
1061
|
-
- The merge uses the ** operator, also known as the "unpacking" operator in Python,
|
|
1062
|
-
combining key-value pairs from each dictionary. Later dictionaries take precedence
|
|
1063
|
-
when there are conflicting keys.
|
|
1064
|
-
"""
|
|
1065
|
-
self.spec.node_selector = {**project_node_selector, **self.spec.node_selector}
|
|
1066
|
-
|
|
1067
1043
|
def _set_env(self, name, value=None, value_from=None):
|
|
1068
1044
|
new_var = k8s_client.V1EnvVar(name=name, value=value, value_from=value_from)
|
|
1069
1045
|
|
|
@@ -1542,7 +1518,7 @@ def get_sanitized_attribute(spec, attribute_name: str):
|
|
|
1542
1518
|
|
|
1543
1519
|
# check if attribute of type dict, and then check if type is sanitized
|
|
1544
1520
|
if isinstance(attribute, dict):
|
|
1545
|
-
if attribute_config["not_sanitized_class"]
|
|
1521
|
+
if not isinstance(attribute_config["not_sanitized_class"], dict):
|
|
1546
1522
|
raise mlrun.errors.MLRunInvalidArgumentTypeError(
|
|
1547
1523
|
f"expected to be of type {attribute_config.get('not_sanitized_class')} but got dict"
|
|
1548
1524
|
)
|
|
@@ -1552,7 +1528,7 @@ def get_sanitized_attribute(spec, attribute_name: str):
|
|
|
1552
1528
|
elif isinstance(attribute, list) and not isinstance(
|
|
1553
1529
|
attribute[0], attribute_config["sub_attribute_type"]
|
|
1554
1530
|
):
|
|
1555
|
-
if attribute_config["not_sanitized_class"]
|
|
1531
|
+
if not isinstance(attribute_config["not_sanitized_class"], list):
|
|
1556
1532
|
raise mlrun.errors.MLRunInvalidArgumentTypeError(
|
|
1557
1533
|
f"expected to be of type {attribute_config.get('not_sanitized_class')} but got list"
|
|
1558
1534
|
)
|
mlrun/serving/routers.py
CHANGED
|
@@ -1030,74 +1030,90 @@ def _init_endpoint_record(
|
|
|
1030
1030
|
function_uri=graph_server.function_uri, versioned_model=versioned_model_name
|
|
1031
1031
|
).uid
|
|
1032
1032
|
|
|
1033
|
-
# If model endpoint object was found in DB, skip the creation process.
|
|
1034
1033
|
try:
|
|
1035
|
-
mlrun.get_run_db().get_model_endpoint(
|
|
1036
|
-
|
|
1034
|
+
model_ep = mlrun.get_run_db().get_model_endpoint(
|
|
1035
|
+
project=project, endpoint_id=endpoint_uid
|
|
1036
|
+
)
|
|
1037
1037
|
except mlrun.errors.MLRunNotFoundError:
|
|
1038
|
-
|
|
1038
|
+
model_ep = None
|
|
1039
|
+
except mlrun.errors.MLRunBadRequestError as err:
|
|
1040
|
+
logger.debug(
|
|
1041
|
+
f"Cant reach to model endpoints store, due to : {err}",
|
|
1042
|
+
)
|
|
1043
|
+
return
|
|
1039
1044
|
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
),
|
|
1058
|
-
active=True,
|
|
1059
|
-
monitoring_mode=mlrun.common.schemas.model_monitoring.ModelMonitoringMode.enabled
|
|
1060
|
-
if voting_ensemble.context.server.track_models
|
|
1061
|
-
else mlrun.common.schemas.model_monitoring.ModelMonitoringMode.disabled,
|
|
1062
|
-
),
|
|
1063
|
-
status=mlrun.common.schemas.ModelEndpointStatus(
|
|
1064
|
-
children=list(voting_ensemble.routes.keys()),
|
|
1065
|
-
endpoint_type=mlrun.common.schemas.model_monitoring.EndpointType.ROUTER,
|
|
1066
|
-
children_uids=children_uids,
|
|
1045
|
+
if voting_ensemble.context.server.track_models and not model_ep:
|
|
1046
|
+
logger.info("Creating a new model endpoint record", endpoint_id=endpoint_uid)
|
|
1047
|
+
# Get the children model endpoints ids
|
|
1048
|
+
children_uids = []
|
|
1049
|
+
for _, c in voting_ensemble.routes.items():
|
|
1050
|
+
if hasattr(c, "endpoint_uid"):
|
|
1051
|
+
children_uids.append(c.endpoint_uid)
|
|
1052
|
+
model_endpoint = mlrun.common.schemas.ModelEndpoint(
|
|
1053
|
+
metadata=mlrun.common.schemas.ModelEndpointMetadata(
|
|
1054
|
+
project=project, uid=endpoint_uid
|
|
1055
|
+
),
|
|
1056
|
+
spec=mlrun.common.schemas.ModelEndpointSpec(
|
|
1057
|
+
function_uri=graph_server.function_uri,
|
|
1058
|
+
model=versioned_model_name,
|
|
1059
|
+
model_class=voting_ensemble.__class__.__name__,
|
|
1060
|
+
stream_path=config.model_endpoint_monitoring.store_prefixes.default.format(
|
|
1061
|
+
project=project, kind="stream"
|
|
1067
1062
|
),
|
|
1068
|
-
|
|
1063
|
+
active=True,
|
|
1064
|
+
monitoring_mode=mlrun.common.schemas.model_monitoring.ModelMonitoringMode.enabled,
|
|
1065
|
+
),
|
|
1066
|
+
status=mlrun.common.schemas.ModelEndpointStatus(
|
|
1067
|
+
children=list(voting_ensemble.routes.keys()),
|
|
1068
|
+
endpoint_type=mlrun.common.schemas.model_monitoring.EndpointType.ROUTER,
|
|
1069
|
+
children_uids=children_uids,
|
|
1070
|
+
),
|
|
1071
|
+
)
|
|
1069
1072
|
|
|
1070
|
-
|
|
1073
|
+
db = mlrun.get_run_db()
|
|
1074
|
+
|
|
1075
|
+
db.create_model_endpoint(
|
|
1076
|
+
project=project,
|
|
1077
|
+
endpoint_id=model_endpoint.metadata.uid,
|
|
1078
|
+
model_endpoint=model_endpoint.dict(),
|
|
1079
|
+
)
|
|
1071
1080
|
|
|
1081
|
+
# Update model endpoint children type
|
|
1082
|
+
for model_endpoint in children_uids:
|
|
1083
|
+
current_endpoint = db.get_model_endpoint(
|
|
1084
|
+
project=project, endpoint_id=model_endpoint
|
|
1085
|
+
)
|
|
1086
|
+
current_endpoint.status.endpoint_type = (
|
|
1087
|
+
mlrun.common.schemas.model_monitoring.EndpointType.LEAF_EP
|
|
1088
|
+
)
|
|
1072
1089
|
db.create_model_endpoint(
|
|
1073
1090
|
project=project,
|
|
1074
|
-
endpoint_id=model_endpoint
|
|
1075
|
-
model_endpoint=
|
|
1076
|
-
)
|
|
1077
|
-
|
|
1078
|
-
# Update model endpoint children type
|
|
1079
|
-
for model_endpoint in children_uids:
|
|
1080
|
-
current_endpoint = db.get_model_endpoint(
|
|
1081
|
-
project=project, endpoint_id=model_endpoint
|
|
1082
|
-
)
|
|
1083
|
-
current_endpoint.status.endpoint_type = (
|
|
1084
|
-
mlrun.common.schemas.model_monitoring.EndpointType.LEAF_EP
|
|
1085
|
-
)
|
|
1086
|
-
db.create_model_endpoint(
|
|
1087
|
-
project=project,
|
|
1088
|
-
endpoint_id=model_endpoint,
|
|
1089
|
-
model_endpoint=current_endpoint,
|
|
1090
|
-
)
|
|
1091
|
-
|
|
1092
|
-
except Exception as exc:
|
|
1093
|
-
logger.warning(
|
|
1094
|
-
"Failed creating model endpoint record",
|
|
1095
|
-
exc=err_to_str(exc),
|
|
1096
|
-
traceback=traceback.format_exc(),
|
|
1091
|
+
endpoint_id=model_endpoint,
|
|
1092
|
+
model_endpoint=current_endpoint,
|
|
1097
1093
|
)
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1094
|
+
elif (
|
|
1095
|
+
model_ep
|
|
1096
|
+
and (
|
|
1097
|
+
model_ep.spec.monitoring_mode
|
|
1098
|
+
== mlrun.common.schemas.model_monitoring.ModelMonitoringMode.enabled
|
|
1099
|
+
)
|
|
1100
|
+
!= voting_ensemble.context.server.track_models
|
|
1101
|
+
):
|
|
1102
|
+
monitoring_mode = (
|
|
1103
|
+
mlrun.common.schemas.model_monitoring.ModelMonitoringMode.enabled
|
|
1104
|
+
if voting_ensemble.context.server.track_models
|
|
1105
|
+
else mlrun.common.schemas.model_monitoring.ModelMonitoringMode.disabled
|
|
1106
|
+
)
|
|
1107
|
+
db = mlrun.get_run_db()
|
|
1108
|
+
db.patch_model_endpoint(
|
|
1109
|
+
project=project,
|
|
1110
|
+
endpoint_id=endpoint_uid,
|
|
1111
|
+
attributes={"monitoring_mode": monitoring_mode},
|
|
1112
|
+
)
|
|
1113
|
+
logger.debug(
|
|
1114
|
+
f"Updating model endpoint monitoring_mode to {monitoring_mode}",
|
|
1115
|
+
endpoint_id=endpoint_uid,
|
|
1116
|
+
)
|
|
1101
1117
|
|
|
1102
1118
|
return endpoint_uid
|
|
1103
1119
|
|
mlrun/serving/server.py
CHANGED
mlrun/serving/v2_serving.py
CHANGED
|
@@ -531,7 +531,9 @@ def _init_endpoint_record(
|
|
|
531
531
|
if model.model_path and model.model_path.startswith("store://"):
|
|
532
532
|
# Enrich the model server with the model artifact metadata
|
|
533
533
|
model.get_model()
|
|
534
|
-
|
|
534
|
+
if not model.version:
|
|
535
|
+
# Enrich the model version with the model artifact tag
|
|
536
|
+
model.version = model.model_spec.tag
|
|
535
537
|
model.labels = model.model_spec.labels
|
|
536
538
|
versioned_model_name = f"{model.name}:{model.version}"
|
|
537
539
|
else:
|
|
@@ -548,6 +550,11 @@ def _init_endpoint_record(
|
|
|
548
550
|
)
|
|
549
551
|
except mlrun.errors.MLRunNotFoundError:
|
|
550
552
|
model_ep = None
|
|
553
|
+
except mlrun.errors.MLRunBadRequestError as err:
|
|
554
|
+
logger.debug(
|
|
555
|
+
f"Cant reach to model endpoints store, due to : {err}",
|
|
556
|
+
)
|
|
557
|
+
return
|
|
551
558
|
|
|
552
559
|
if model.context.server.track_models and not model_ep:
|
|
553
560
|
logger.debug("Creating a new model endpoint record", endpoint_id=uid)
|
mlrun/utils/helpers.py
CHANGED
|
@@ -149,7 +149,7 @@ if is_ipython and config.nest_asyncio_enabled in ["1", "True"]:
|
|
|
149
149
|
nest_asyncio.apply()
|
|
150
150
|
|
|
151
151
|
|
|
152
|
-
class
|
|
152
|
+
class RunKeys:
|
|
153
153
|
input_path = "input_path"
|
|
154
154
|
output_path = "output_path"
|
|
155
155
|
inputs = "inputs"
|
|
@@ -160,6 +160,10 @@ class run_keys:
|
|
|
160
160
|
secrets = "secret_sources"
|
|
161
161
|
|
|
162
162
|
|
|
163
|
+
# for Backward compatibility
|
|
164
|
+
run_keys = RunKeys
|
|
165
|
+
|
|
166
|
+
|
|
163
167
|
def verify_field_regex(
|
|
164
168
|
field_name,
|
|
165
169
|
field_value,
|
|
@@ -1259,6 +1263,10 @@ def _fill_project_path_template(artifact_path, project):
|
|
|
1259
1263
|
return artifact_path
|
|
1260
1264
|
|
|
1261
1265
|
|
|
1266
|
+
def to_non_empty_values_dict(input_dict: dict) -> dict:
|
|
1267
|
+
return {key: value for key, value in input_dict.items() if value}
|
|
1268
|
+
|
|
1269
|
+
|
|
1262
1270
|
def str_to_timestamp(time_str: str, now_time: Timestamp = None):
|
|
1263
1271
|
"""convert fixed/relative time string to Pandas Timestamp
|
|
1264
1272
|
|
|
@@ -1606,6 +1614,30 @@ def additional_filters_warning(additional_filters, class_name):
|
|
|
1606
1614
|
)
|
|
1607
1615
|
|
|
1608
1616
|
|
|
1617
|
+
def merge_with_precedence(first_dict: dict, second_dict: dict) -> dict:
|
|
1618
|
+
"""
|
|
1619
|
+
Merge two dictionaries with precedence given to keys from the second dictionary.
|
|
1620
|
+
|
|
1621
|
+
This function merges two dictionaries, `first_dict` and `second_dict`, where keys from `second_dict`
|
|
1622
|
+
take precedence in case of conflicts. If both dictionaries contain the same key,
|
|
1623
|
+
the value from `second_dict` will overwrite the value from `first_dict`.
|
|
1624
|
+
|
|
1625
|
+
Example:
|
|
1626
|
+
>>> first_dict = {"key1": "value1", "key2": "value2"}
|
|
1627
|
+
>>> second_dict = {"key2": "new_value2", "key3": "value3"}
|
|
1628
|
+
>>> merge_with_precedence(first_dict, second_dict)
|
|
1629
|
+
{'key1': 'value1', 'key2': 'new_value2', 'key3': 'value3'}
|
|
1630
|
+
|
|
1631
|
+
Note:
|
|
1632
|
+
- The merge operation uses the ** operator in Python, which combines key-value pairs
|
|
1633
|
+
from each dictionary. Later dictionaries take precedence when there are conflicting keys.
|
|
1634
|
+
"""
|
|
1635
|
+
return {
|
|
1636
|
+
**(first_dict or {}),
|
|
1637
|
+
**(second_dict or {}),
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
|
|
1609
1641
|
def validate_component_version_compatibility(
|
|
1610
1642
|
component_name: typing.Literal["iguazio", "nuclio"], *min_versions: str
|
|
1611
1643
|
):
|
|
@@ -30,6 +30,27 @@ class GitNotification(NotificationBase):
|
|
|
30
30
|
API/Client notification for setting a rich run statuses git issue comment (github/gitlab)
|
|
31
31
|
"""
|
|
32
32
|
|
|
33
|
+
@classmethod
|
|
34
|
+
def validate_params(cls, params):
|
|
35
|
+
git_repo = params.get("repo", None)
|
|
36
|
+
git_issue = params.get("issue", None)
|
|
37
|
+
git_merge_request = params.get("merge_request", None)
|
|
38
|
+
token = (
|
|
39
|
+
params.get("token", None)
|
|
40
|
+
or params.get("GIT_TOKEN", None)
|
|
41
|
+
or params.get("GITHUB_TOKEN", None)
|
|
42
|
+
)
|
|
43
|
+
if not git_repo:
|
|
44
|
+
raise ValueError("Parameter 'repo' is required for GitNotification")
|
|
45
|
+
|
|
46
|
+
if not token:
|
|
47
|
+
raise ValueError("Parameter 'token' is required for GitNotification")
|
|
48
|
+
|
|
49
|
+
if not git_issue and not git_merge_request:
|
|
50
|
+
raise ValueError(
|
|
51
|
+
"At least one of 'issue' or 'merge_request' is required for GitNotification"
|
|
52
|
+
)
|
|
53
|
+
|
|
33
54
|
async def push(
|
|
34
55
|
self,
|
|
35
56
|
message: str,
|
|
@@ -35,6 +35,14 @@ class SlackNotification(NotificationBase):
|
|
|
35
35
|
"skipped": ":zzz:",
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
@classmethod
|
|
39
|
+
def validate_params(cls, params):
|
|
40
|
+
webhook = params.get("webhook", None) or mlrun.get_secret_or_env(
|
|
41
|
+
"SLACK_WEBHOOK"
|
|
42
|
+
)
|
|
43
|
+
if not webhook:
|
|
44
|
+
raise ValueError("Parameter 'webhook' is required for SlackNotification")
|
|
45
|
+
|
|
38
46
|
async def push(
|
|
39
47
|
self,
|
|
40
48
|
message: str,
|