mlrun 1.7.0rc22__py3-none-any.whl → 1.7.0rc28__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 +10 -8
- mlrun/alerts/alert.py +13 -1
- mlrun/artifacts/manager.py +5 -0
- mlrun/common/constants.py +2 -2
- mlrun/common/formatters/__init__.py +1 -0
- mlrun/common/formatters/artifact.py +26 -3
- mlrun/common/formatters/base.py +9 -9
- mlrun/common/formatters/run.py +26 -0
- mlrun/common/helpers.py +11 -0
- mlrun/common/schemas/__init__.py +4 -0
- mlrun/common/schemas/alert.py +5 -9
- mlrun/common/schemas/api_gateway.py +64 -16
- mlrun/common/schemas/artifact.py +11 -0
- mlrun/common/schemas/constants.py +3 -0
- mlrun/common/schemas/feature_store.py +58 -28
- mlrun/common/schemas/model_monitoring/constants.py +21 -12
- mlrun/common/schemas/model_monitoring/model_endpoints.py +0 -12
- mlrun/common/schemas/pipeline.py +16 -0
- mlrun/common/schemas/project.py +17 -0
- mlrun/common/schemas/runs.py +17 -0
- mlrun/common/schemas/schedule.py +1 -1
- mlrun/common/types.py +5 -0
- mlrun/config.py +10 -25
- mlrun/datastore/azure_blob.py +2 -1
- 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 +26 -11
- mlrun/datastore/store_resources.py +2 -0
- mlrun/datastore/targets.py +68 -16
- mlrun/db/base.py +64 -2
- mlrun/db/httpdb.py +129 -41
- mlrun/db/nopdb.py +44 -3
- mlrun/errors.py +5 -3
- mlrun/execution.py +18 -10
- mlrun/feature_store/retrieval/spark_merger.py +2 -1
- mlrun/frameworks/__init__.py +0 -6
- mlrun/model.py +23 -0
- mlrun/model_monitoring/api.py +6 -52
- mlrun/model_monitoring/applications/histogram_data_drift.py +1 -1
- mlrun/model_monitoring/db/stores/__init__.py +37 -24
- mlrun/model_monitoring/db/stores/base/store.py +40 -1
- mlrun/model_monitoring/db/stores/sqldb/sql_store.py +42 -87
- mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +27 -35
- mlrun/model_monitoring/db/tsdb/__init__.py +15 -15
- mlrun/model_monitoring/db/tsdb/base.py +1 -1
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +6 -4
- mlrun/model_monitoring/helpers.py +17 -9
- mlrun/model_monitoring/stream_processing.py +9 -11
- mlrun/model_monitoring/writer.py +11 -11
- mlrun/package/__init__.py +1 -13
- mlrun/package/packagers/__init__.py +1 -6
- mlrun/projects/pipelines.py +10 -9
- mlrun/projects/project.py +95 -81
- mlrun/render.py +10 -5
- mlrun/run.py +13 -8
- mlrun/runtimes/base.py +11 -4
- mlrun/runtimes/daskjob.py +7 -1
- mlrun/runtimes/local.py +16 -3
- mlrun/runtimes/nuclio/application/application.py +0 -2
- mlrun/runtimes/nuclio/function.py +20 -0
- mlrun/runtimes/nuclio/serving.py +9 -6
- mlrun/runtimes/pod.py +5 -29
- mlrun/serving/routers.py +75 -59
- mlrun/serving/server.py +11 -0
- mlrun/serving/states.py +29 -0
- mlrun/serving/v2_serving.py +62 -39
- mlrun/utils/helpers.py +39 -1
- mlrun/utils/logger.py +36 -2
- mlrun/utils/notifications/notification/base.py +43 -7
- mlrun/utils/notifications/notification/git.py +21 -0
- mlrun/utils/notifications/notification/slack.py +9 -14
- mlrun/utils/notifications/notification/webhook.py +41 -1
- mlrun/utils/notifications/notification_pusher.py +3 -9
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.7.0rc22.dist-info → mlrun-1.7.0rc28.dist-info}/METADATA +12 -7
- {mlrun-1.7.0rc22.dist-info → mlrun-1.7.0rc28.dist-info}/RECORD +81 -80
- {mlrun-1.7.0rc22.dist-info → mlrun-1.7.0rc28.dist-info}/WHEEL +1 -1
- {mlrun-1.7.0rc22.dist-info → mlrun-1.7.0rc28.dist-info}/LICENSE +0 -0
- {mlrun-1.7.0rc22.dist-info → mlrun-1.7.0rc28.dist-info}/entry_points.txt +0 -0
- {mlrun-1.7.0rc22.dist-info → mlrun-1.7.0rc28.dist-info}/top_level.txt +0 -0
mlrun/runtimes/daskjob.py
CHANGED
|
@@ -548,7 +548,13 @@ class DaskCluster(KubejobRuntime):
|
|
|
548
548
|
"specified handler (string) without command "
|
|
549
549
|
"(py file path), specify command or use handler pointer"
|
|
550
550
|
)
|
|
551
|
-
|
|
551
|
+
# Do not embed the module in system as it is not persistent with the dask cluster
|
|
552
|
+
handler = load_module(
|
|
553
|
+
self.spec.command,
|
|
554
|
+
handler,
|
|
555
|
+
context=context,
|
|
556
|
+
embed_in_sys=False,
|
|
557
|
+
)
|
|
552
558
|
client = self.client
|
|
553
559
|
setattr(context, "dask_client", client)
|
|
554
560
|
sout, serr = exec_from_params(handler, runobj, context)
|
mlrun/runtimes/local.py
CHANGED
|
@@ -372,8 +372,20 @@ class LocalRuntime(BaseRuntime, ParallelRunner):
|
|
|
372
372
|
return run_obj_dict
|
|
373
373
|
|
|
374
374
|
|
|
375
|
-
def load_module(
|
|
376
|
-
|
|
375
|
+
def load_module(
|
|
376
|
+
file_name: str,
|
|
377
|
+
handler: str,
|
|
378
|
+
context: MLClientCtx,
|
|
379
|
+
embed_in_sys: bool = True,
|
|
380
|
+
):
|
|
381
|
+
"""
|
|
382
|
+
Load module from filename
|
|
383
|
+
:param file_name: The module path to load
|
|
384
|
+
:param handler: The callable to load
|
|
385
|
+
:param context: Execution context
|
|
386
|
+
:param embed_in_sys: Embed the file-named module in sys.modules. This is not persistent with remote
|
|
387
|
+
environments and therefore can effect pickling.
|
|
388
|
+
"""
|
|
377
389
|
module = None
|
|
378
390
|
if file_name:
|
|
379
391
|
path = Path(file_name)
|
|
@@ -384,7 +396,8 @@ def load_module(file_name, handler, context):
|
|
|
384
396
|
if spec is None:
|
|
385
397
|
raise RunError(f"Cannot import from {file_name!r}")
|
|
386
398
|
module = imputil.module_from_spec(spec)
|
|
387
|
-
|
|
399
|
+
if embed_in_sys:
|
|
400
|
+
sys.modules[mod_name] = module
|
|
388
401
|
spec.loader.exec_module(module)
|
|
389
402
|
|
|
390
403
|
class_args = {}
|
|
@@ -263,7 +263,6 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
263
263
|
is_kfp=False,
|
|
264
264
|
mlrun_version_specifier=None,
|
|
265
265
|
show_on_failure: bool = False,
|
|
266
|
-
skip_access_key_auth: bool = False,
|
|
267
266
|
direct_port_access: bool = False,
|
|
268
267
|
authentication_mode: schemas.APIGatewayAuthenticationMode = None,
|
|
269
268
|
authentication_creds: tuple[str] = None,
|
|
@@ -283,7 +282,6 @@ class ApplicationRuntime(RemoteRuntime):
|
|
|
283
282
|
:param is_kfp: Deploy as part of a kfp pipeline
|
|
284
283
|
:param mlrun_version_specifier: Which mlrun package version to include (if not current)
|
|
285
284
|
:param show_on_failure: Show logs only in case of build failure
|
|
286
|
-
:param skip_access_key_auth: Skip adding access key auth to the API Gateway
|
|
287
285
|
:param direct_port_access: Set True to allow direct port access to the application sidecar
|
|
288
286
|
:param authentication_mode: API Gateway authentication mode
|
|
289
287
|
:param authentication_creds: API Gateway authentication credentials as a tuple (username, password)
|
|
@@ -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/nuclio/serving.py
CHANGED
|
@@ -312,15 +312,18 @@ class ServingRuntime(RemoteRuntime):
|
|
|
312
312
|
sample: Optional[int] = None,
|
|
313
313
|
stream_args: Optional[dict] = None,
|
|
314
314
|
tracking_policy: Optional[Union["TrackingPolicy", dict]] = None,
|
|
315
|
+
enable_tracking: bool = True,
|
|
315
316
|
) -> None:
|
|
316
317
|
"""apply on your serving function to monitor a deployed model, including real-time dashboards to detect drift
|
|
317
318
|
and analyze performance.
|
|
318
319
|
|
|
319
|
-
:param stream_path:
|
|
320
|
-
|
|
321
|
-
:param batch:
|
|
322
|
-
:param sample:
|
|
323
|
-
:param stream_args:
|
|
320
|
+
:param stream_path: Path/url of the tracking stream e.g. v3io:///users/mike/mystream
|
|
321
|
+
you can use the "dummy://" path for test/simulation.
|
|
322
|
+
:param batch: Micro batch size (send micro batches of N records at a time).
|
|
323
|
+
:param sample: Sample size (send only one of N records).
|
|
324
|
+
:param stream_args: Stream initialization parameters, e.g. shards, retention_in_hours, ..
|
|
325
|
+
:param enable_tracking: Enabled/Disable model-monitoring tracking.
|
|
326
|
+
Default True (tracking enabled).
|
|
324
327
|
|
|
325
328
|
example::
|
|
326
329
|
|
|
@@ -331,7 +334,7 @@ class ServingRuntime(RemoteRuntime):
|
|
|
331
334
|
|
|
332
335
|
"""
|
|
333
336
|
# Applying model monitoring configurations
|
|
334
|
-
self.spec.track_models =
|
|
337
|
+
self.spec.track_models = enable_tracking
|
|
335
338
|
|
|
336
339
|
if stream_path:
|
|
337
340
|
self.spec.parameters["log_stream"] = stream_path
|
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
|
@@ -383,6 +383,17 @@ def v2_serving_handler(context, event, get_body=False):
|
|
|
383
383
|
if event.body == b"":
|
|
384
384
|
event.body = None
|
|
385
385
|
|
|
386
|
+
# original path is saved in stream_path so it can be used by explicit ack, but path is reset to / as a
|
|
387
|
+
# workaround for NUC-178
|
|
388
|
+
event.stream_path = event.path
|
|
389
|
+
if hasattr(event, "trigger") and event.trigger.kind in (
|
|
390
|
+
"kafka",
|
|
391
|
+
"kafka-cluster",
|
|
392
|
+
"v3ioStream",
|
|
393
|
+
"v3io-stream",
|
|
394
|
+
):
|
|
395
|
+
event.path = "/"
|
|
396
|
+
|
|
386
397
|
return context._server.run(event, context, get_body)
|
|
387
398
|
|
|
388
399
|
|
mlrun/serving/states.py
CHANGED
|
@@ -832,6 +832,35 @@ class QueueStep(BaseStep):
|
|
|
832
832
|
def async_object(self):
|
|
833
833
|
return self._async_object
|
|
834
834
|
|
|
835
|
+
def to(
|
|
836
|
+
self,
|
|
837
|
+
class_name: Union[str, StepToDict] = None,
|
|
838
|
+
name: str = None,
|
|
839
|
+
handler: str = None,
|
|
840
|
+
graph_shape: str = None,
|
|
841
|
+
function: str = None,
|
|
842
|
+
full_event: bool = None,
|
|
843
|
+
input_path: str = None,
|
|
844
|
+
result_path: str = None,
|
|
845
|
+
**class_args,
|
|
846
|
+
):
|
|
847
|
+
if not function:
|
|
848
|
+
name = get_name(name, class_name)
|
|
849
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
850
|
+
f"step '{name}' must specify a function, because it follows a queue step"
|
|
851
|
+
)
|
|
852
|
+
return super().to(
|
|
853
|
+
class_name,
|
|
854
|
+
name,
|
|
855
|
+
handler,
|
|
856
|
+
graph_shape,
|
|
857
|
+
function,
|
|
858
|
+
full_event,
|
|
859
|
+
input_path,
|
|
860
|
+
result_path,
|
|
861
|
+
**class_args,
|
|
862
|
+
)
|
|
863
|
+
|
|
835
864
|
def run(self, event, *args, **kwargs):
|
|
836
865
|
data = event.body
|
|
837
866
|
if not data:
|
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:
|
|
@@ -542,48 +544,69 @@ def _init_endpoint_record(
|
|
|
542
544
|
function_uri=graph_server.function_uri, versioned_model=versioned_model_name
|
|
543
545
|
).uid
|
|
544
546
|
|
|
545
|
-
# If model endpoint object was found in DB, skip the creation process.
|
|
546
547
|
try:
|
|
547
|
-
mlrun.get_run_db().get_model_endpoint(
|
|
548
|
-
|
|
548
|
+
model_ep = mlrun.get_run_db().get_model_endpoint(
|
|
549
|
+
project=project, endpoint_id=uid
|
|
550
|
+
)
|
|
549
551
|
except mlrun.errors.MLRunNotFoundError:
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
status=mlrun.common.schemas.ModelEndpointStatus(
|
|
571
|
-
endpoint_type=mlrun.common.schemas.model_monitoring.EndpointType.NODE_EP
|
|
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
|
|
558
|
+
|
|
559
|
+
if model.context.server.track_models and not model_ep:
|
|
560
|
+
logger.debug("Creating a new model endpoint record", endpoint_id=uid)
|
|
561
|
+
model_endpoint = mlrun.common.schemas.ModelEndpoint(
|
|
562
|
+
metadata=mlrun.common.schemas.ModelEndpointMetadata(
|
|
563
|
+
project=project, labels=model.labels, uid=uid
|
|
564
|
+
),
|
|
565
|
+
spec=mlrun.common.schemas.ModelEndpointSpec(
|
|
566
|
+
function_uri=graph_server.function_uri,
|
|
567
|
+
model=versioned_model_name,
|
|
568
|
+
model_class=model.__class__.__name__,
|
|
569
|
+
model_uri=model.model_path,
|
|
570
|
+
stream_path=config.model_endpoint_monitoring.store_prefixes.default.format(
|
|
571
|
+
project=project, kind="stream"
|
|
572
572
|
),
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
model_endpoint=model_endpoint.dict(),
|
|
581
|
-
)
|
|
573
|
+
active=True,
|
|
574
|
+
monitoring_mode=mlrun.common.schemas.model_monitoring.ModelMonitoringMode.enabled,
|
|
575
|
+
),
|
|
576
|
+
status=mlrun.common.schemas.ModelEndpointStatus(
|
|
577
|
+
endpoint_type=mlrun.common.schemas.model_monitoring.EndpointType.NODE_EP
|
|
578
|
+
),
|
|
579
|
+
)
|
|
582
580
|
|
|
583
|
-
|
|
584
|
-
|
|
581
|
+
db = mlrun.get_run_db()
|
|
582
|
+
db.create_model_endpoint(
|
|
583
|
+
project=project,
|
|
584
|
+
endpoint_id=uid,
|
|
585
|
+
model_endpoint=model_endpoint.dict(),
|
|
586
|
+
)
|
|
585
587
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
+
elif (
|
|
589
|
+
model_ep
|
|
590
|
+
and (
|
|
591
|
+
model_ep.spec.monitoring_mode
|
|
592
|
+
== mlrun.common.schemas.model_monitoring.ModelMonitoringMode.enabled
|
|
593
|
+
)
|
|
594
|
+
!= model.context.server.track_models
|
|
595
|
+
):
|
|
596
|
+
monitoring_mode = (
|
|
597
|
+
mlrun.common.schemas.model_monitoring.ModelMonitoringMode.enabled
|
|
598
|
+
if model.context.server.track_models
|
|
599
|
+
else mlrun.common.schemas.model_monitoring.ModelMonitoringMode.disabled
|
|
600
|
+
)
|
|
601
|
+
db = mlrun.get_run_db()
|
|
602
|
+
db.patch_model_endpoint(
|
|
603
|
+
project=project,
|
|
604
|
+
endpoint_id=uid,
|
|
605
|
+
attributes={"monitoring_mode": monitoring_mode},
|
|
606
|
+
)
|
|
607
|
+
logger.debug(
|
|
608
|
+
f"Updating model endpoint monitoring_mode to {monitoring_mode}",
|
|
609
|
+
endpoint_id=uid,
|
|
610
|
+
)
|
|
588
611
|
|
|
589
612
|
return uid
|
mlrun/utils/helpers.py
CHANGED
|
@@ -109,10 +109,13 @@ def get_artifact_target(item: dict, project=None):
|
|
|
109
109
|
db_key = item["spec"].get("db_key")
|
|
110
110
|
project_str = project or item["metadata"].get("project")
|
|
111
111
|
tree = item["metadata"].get("tree")
|
|
112
|
+
tag = item["metadata"].get("tag")
|
|
112
113
|
|
|
113
114
|
kind = item.get("kind")
|
|
114
115
|
if kind in ["dataset", "model", "artifact"] and db_key:
|
|
115
116
|
target = f"{DB_SCHEMA}://{StorePrefix.Artifact}/{project_str}/{db_key}"
|
|
117
|
+
if tag:
|
|
118
|
+
target = f"{target}:{tag}"
|
|
116
119
|
if tree:
|
|
117
120
|
target = f"{target}@{tree}"
|
|
118
121
|
return target
|
|
@@ -149,7 +152,7 @@ if is_ipython and config.nest_asyncio_enabled in ["1", "True"]:
|
|
|
149
152
|
nest_asyncio.apply()
|
|
150
153
|
|
|
151
154
|
|
|
152
|
-
class
|
|
155
|
+
class RunKeys:
|
|
153
156
|
input_path = "input_path"
|
|
154
157
|
output_path = "output_path"
|
|
155
158
|
inputs = "inputs"
|
|
@@ -160,6 +163,10 @@ class run_keys:
|
|
|
160
163
|
secrets = "secret_sources"
|
|
161
164
|
|
|
162
165
|
|
|
166
|
+
# for Backward compatibility
|
|
167
|
+
run_keys = RunKeys
|
|
168
|
+
|
|
169
|
+
|
|
163
170
|
def verify_field_regex(
|
|
164
171
|
field_name,
|
|
165
172
|
field_value,
|
|
@@ -674,6 +681,8 @@ def parse_artifact_uri(uri, default_project=""):
|
|
|
674
681
|
raise ValueError(
|
|
675
682
|
f"illegal store path '{uri}', iteration must be integer value"
|
|
676
683
|
)
|
|
684
|
+
else:
|
|
685
|
+
iteration = 0
|
|
677
686
|
return (
|
|
678
687
|
group_dict["project"] or default_project,
|
|
679
688
|
group_dict["key"],
|
|
@@ -1257,6 +1266,10 @@ def _fill_project_path_template(artifact_path, project):
|
|
|
1257
1266
|
return artifact_path
|
|
1258
1267
|
|
|
1259
1268
|
|
|
1269
|
+
def to_non_empty_values_dict(input_dict: dict) -> dict:
|
|
1270
|
+
return {key: value for key, value in input_dict.items() if value}
|
|
1271
|
+
|
|
1272
|
+
|
|
1260
1273
|
def str_to_timestamp(time_str: str, now_time: Timestamp = None):
|
|
1261
1274
|
"""convert fixed/relative time string to Pandas Timestamp
|
|
1262
1275
|
|
|
@@ -1314,6 +1327,7 @@ def format_run(run: PipelineRun, with_project=False) -> dict:
|
|
|
1314
1327
|
"scheduled_at",
|
|
1315
1328
|
"finished_at",
|
|
1316
1329
|
"description",
|
|
1330
|
+
"experiment_id",
|
|
1317
1331
|
]
|
|
1318
1332
|
|
|
1319
1333
|
if with_project:
|
|
@@ -1603,6 +1617,30 @@ def additional_filters_warning(additional_filters, class_name):
|
|
|
1603
1617
|
)
|
|
1604
1618
|
|
|
1605
1619
|
|
|
1620
|
+
def merge_with_precedence(first_dict: dict, second_dict: dict) -> dict:
|
|
1621
|
+
"""
|
|
1622
|
+
Merge two dictionaries with precedence given to keys from the second dictionary.
|
|
1623
|
+
|
|
1624
|
+
This function merges two dictionaries, `first_dict` and `second_dict`, where keys from `second_dict`
|
|
1625
|
+
take precedence in case of conflicts. If both dictionaries contain the same key,
|
|
1626
|
+
the value from `second_dict` will overwrite the value from `first_dict`.
|
|
1627
|
+
|
|
1628
|
+
Example:
|
|
1629
|
+
>>> first_dict = {"key1": "value1", "key2": "value2"}
|
|
1630
|
+
>>> second_dict = {"key2": "new_value2", "key3": "value3"}
|
|
1631
|
+
>>> merge_with_precedence(first_dict, second_dict)
|
|
1632
|
+
{'key1': 'value1', 'key2': 'new_value2', 'key3': 'value3'}
|
|
1633
|
+
|
|
1634
|
+
Note:
|
|
1635
|
+
- The merge operation uses the ** operator in Python, which combines key-value pairs
|
|
1636
|
+
from each dictionary. Later dictionaries take precedence when there are conflicting keys.
|
|
1637
|
+
"""
|
|
1638
|
+
return {
|
|
1639
|
+
**(first_dict or {}),
|
|
1640
|
+
**(second_dict or {}),
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
|
|
1606
1644
|
def validate_component_version_compatibility(
|
|
1607
1645
|
component_name: typing.Literal["iguazio", "nuclio"], *min_versions: str
|
|
1608
1646
|
):
|
mlrun/utils/logger.py
CHANGED
|
@@ -13,8 +13,10 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
import logging
|
|
16
|
+
import os
|
|
16
17
|
import typing
|
|
17
18
|
from enum import Enum
|
|
19
|
+
from functools import cached_property
|
|
18
20
|
from sys import stdout
|
|
19
21
|
from traceback import format_exception
|
|
20
22
|
from typing import IO, Optional, Union
|
|
@@ -92,6 +94,16 @@ class HumanReadableFormatter(_BaseFormatter):
|
|
|
92
94
|
|
|
93
95
|
|
|
94
96
|
class HumanReadableExtendedFormatter(HumanReadableFormatter):
|
|
97
|
+
_colors = {
|
|
98
|
+
logging.NOTSET: "",
|
|
99
|
+
logging.DEBUG: "\x1b[34m",
|
|
100
|
+
logging.INFO: "\x1b[36m",
|
|
101
|
+
logging.WARNING: "\x1b[33m",
|
|
102
|
+
logging.ERROR: "\x1b[0;31m",
|
|
103
|
+
logging.CRITICAL: "\x1b[1;31m",
|
|
104
|
+
}
|
|
105
|
+
_color_reset = "\x1b[0m"
|
|
106
|
+
|
|
95
107
|
def format(self, record) -> str:
|
|
96
108
|
more = ""
|
|
97
109
|
record_with = self._record_with(record)
|
|
@@ -113,12 +125,34 @@ class HumanReadableExtendedFormatter(HumanReadableFormatter):
|
|
|
113
125
|
[f"{key}: {_format_value(val)}" for key, val in record_with.items()]
|
|
114
126
|
)
|
|
115
127
|
return (
|
|
116
|
-
"> "
|
|
128
|
+
f"{self._get_message_color(record.levelno)}> "
|
|
117
129
|
f"{self.formatTime(record, self.datefmt)} "
|
|
118
130
|
f"[{record.name}:{record.levelname.lower()}] "
|
|
119
|
-
f"{record.getMessage()}{more}"
|
|
131
|
+
f"{record.getMessage()}{more}{self._get_color_reset()}"
|
|
120
132
|
)
|
|
121
133
|
|
|
134
|
+
def _get_color_reset(self):
|
|
135
|
+
if not self._have_color_support:
|
|
136
|
+
return ""
|
|
137
|
+
|
|
138
|
+
return self._color_reset
|
|
139
|
+
|
|
140
|
+
def _get_message_color(self, levelno):
|
|
141
|
+
if not self._have_color_support:
|
|
142
|
+
return ""
|
|
143
|
+
|
|
144
|
+
return self._colors[levelno]
|
|
145
|
+
|
|
146
|
+
@cached_property
|
|
147
|
+
def _have_color_support(self):
|
|
148
|
+
if os.environ.get("PYCHARM_HOSTED"):
|
|
149
|
+
return True
|
|
150
|
+
if os.environ.get("NO_COLOR"):
|
|
151
|
+
return False
|
|
152
|
+
if os.environ.get("CLICOLOR_FORCE"):
|
|
153
|
+
return True
|
|
154
|
+
return stdout.isatty()
|
|
155
|
+
|
|
122
156
|
|
|
123
157
|
class Logger:
|
|
124
158
|
def __init__(
|