mlrun 1.8.0rc28__py3-none-any.whl → 1.8.0rc30__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 +2 -1
- mlrun/artifacts/document.py +3 -3
- mlrun/artifacts/manager.py +1 -0
- mlrun/artifacts/model.py +3 -3
- mlrun/common/model_monitoring/helpers.py +16 -7
- mlrun/common/runtimes/constants.py +1 -0
- mlrun/common/schemas/model_monitoring/constants.py +3 -1
- mlrun/datastore/datastore_profile.py +1 -1
- mlrun/datastore/sources.py +14 -13
- mlrun/db/httpdb.py +10 -34
- mlrun/k8s_utils.py +2 -5
- mlrun/launcher/base.py +16 -0
- mlrun/model_monitoring/api.py +1 -2
- mlrun/model_monitoring/applications/_application_steps.py +23 -37
- mlrun/model_monitoring/applications/context.py +0 -3
- mlrun/model_monitoring/applications/results.py +14 -14
- mlrun/model_monitoring/controller.py +31 -28
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +6 -2
- mlrun/model_monitoring/helpers.py +108 -1
- mlrun/model_monitoring/stream_processing.py +0 -8
- mlrun/projects/project.py +20 -8
- mlrun/run.py +2 -2
- mlrun/runtimes/nuclio/function.py +11 -7
- mlrun/serving/routers.py +3 -4
- mlrun/serving/server.py +10 -8
- mlrun/serving/states.py +11 -1
- mlrun/serving/v2_serving.py +5 -10
- mlrun/utils/async_http.py +32 -19
- mlrun/utils/helpers.py +4 -1
- mlrun/utils/logger.py +1 -0
- mlrun/utils/notifications/notification_pusher.py +1 -0
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.8.0rc28.dist-info → mlrun-1.8.0rc30.dist-info}/METADATA +4 -4
- {mlrun-1.8.0rc28.dist-info → mlrun-1.8.0rc30.dist-info}/RECORD +38 -38
- {mlrun-1.8.0rc28.dist-info → mlrun-1.8.0rc30.dist-info}/LICENSE +0 -0
- {mlrun-1.8.0rc28.dist-info → mlrun-1.8.0rc30.dist-info}/WHEEL +0 -0
- {mlrun-1.8.0rc28.dist-info → mlrun-1.8.0rc30.dist-info}/entry_points.txt +0 -0
- {mlrun-1.8.0rc28.dist-info → mlrun-1.8.0rc30.dist-info}/top_level.txt +0 -0
|
@@ -16,7 +16,7 @@ import datetime
|
|
|
16
16
|
import functools
|
|
17
17
|
import os
|
|
18
18
|
from fnmatch import fnmatchcase
|
|
19
|
-
from typing import TYPE_CHECKING, Callable, Optional, TypedDict, cast
|
|
19
|
+
from typing import TYPE_CHECKING, Callable, Optional, TypedDict, Union, cast
|
|
20
20
|
|
|
21
21
|
import numpy as np
|
|
22
22
|
import pandas as pd
|
|
@@ -28,6 +28,7 @@ import mlrun.common.schemas.model_monitoring.constants as mm_constants
|
|
|
28
28
|
import mlrun.data_types.infer
|
|
29
29
|
import mlrun.datastore.datastore_profile
|
|
30
30
|
import mlrun.model_monitoring
|
|
31
|
+
import mlrun.platforms.iguazio
|
|
31
32
|
import mlrun.utils.helpers
|
|
32
33
|
from mlrun.common.schemas import ModelEndpoint
|
|
33
34
|
from mlrun.common.schemas.model_monitoring.model_endpoints import (
|
|
@@ -294,6 +295,112 @@ _get_stream_profile = functools.partial(
|
|
|
294
295
|
)
|
|
295
296
|
|
|
296
297
|
|
|
298
|
+
def _get_v3io_output_stream(
|
|
299
|
+
*,
|
|
300
|
+
v3io_profile: mlrun.datastore.datastore_profile.DatastoreProfileV3io,
|
|
301
|
+
project: str,
|
|
302
|
+
function_name: str,
|
|
303
|
+
v3io_access_key: Optional[str],
|
|
304
|
+
mock: bool = False,
|
|
305
|
+
) -> mlrun.platforms.iguazio.OutputStream:
|
|
306
|
+
stream_uri = mlrun.mlconf.get_model_monitoring_file_target_path(
|
|
307
|
+
project=project,
|
|
308
|
+
kind=mm_constants.FileTargetKind.STREAM,
|
|
309
|
+
target="online",
|
|
310
|
+
function_name=function_name,
|
|
311
|
+
)
|
|
312
|
+
endpoint, stream_path = mlrun.platforms.iguazio.parse_path(stream_uri)
|
|
313
|
+
return mlrun.platforms.iguazio.OutputStream(
|
|
314
|
+
stream_path,
|
|
315
|
+
endpoint=endpoint,
|
|
316
|
+
access_key=v3io_access_key or v3io_profile.v3io_access_key,
|
|
317
|
+
mock=mock,
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _get_kafka_output_stream(
|
|
322
|
+
*,
|
|
323
|
+
kafka_profile: mlrun.datastore.datastore_profile.DatastoreProfileKafkaSource,
|
|
324
|
+
project: str,
|
|
325
|
+
function_name: str,
|
|
326
|
+
mock: bool = False,
|
|
327
|
+
) -> mlrun.platforms.iguazio.KafkaOutputStream:
|
|
328
|
+
topic = mlrun.common.model_monitoring.helpers.get_kafka_topic(
|
|
329
|
+
project=project, function_name=function_name
|
|
330
|
+
)
|
|
331
|
+
profile_attributes = kafka_profile.attributes()
|
|
332
|
+
producer_options = profile_attributes.get("producer_options", {})
|
|
333
|
+
if "sasl" in profile_attributes:
|
|
334
|
+
sasl = profile_attributes["sasl"]
|
|
335
|
+
producer_options.update(
|
|
336
|
+
{
|
|
337
|
+
"security_protocol": "SASL_PLAINTEXT",
|
|
338
|
+
"sasl_mechanism": sasl["mechanism"],
|
|
339
|
+
"sasl_plain_username": sasl["user"],
|
|
340
|
+
"sasl_plain_password": sasl["password"],
|
|
341
|
+
},
|
|
342
|
+
)
|
|
343
|
+
return mlrun.platforms.iguazio.KafkaOutputStream(
|
|
344
|
+
brokers=kafka_profile.brokers,
|
|
345
|
+
topic=topic,
|
|
346
|
+
producer_options=producer_options,
|
|
347
|
+
mock=mock,
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def get_output_stream(
|
|
352
|
+
project: str,
|
|
353
|
+
function_name: str = mm_constants.MonitoringFunctionNames.STREAM,
|
|
354
|
+
secret_provider: Optional[Callable[[str], str]] = None,
|
|
355
|
+
profile: Optional[mlrun.datastore.datastore_profile.DatastoreProfile] = None,
|
|
356
|
+
v3io_access_key: Optional[str] = None,
|
|
357
|
+
mock: bool = False,
|
|
358
|
+
) -> Union[
|
|
359
|
+
mlrun.platforms.iguazio.OutputStream, mlrun.platforms.iguazio.KafkaOutputStream
|
|
360
|
+
]:
|
|
361
|
+
"""
|
|
362
|
+
Get stream path from the project secret. If wasn't set, take it from the system configurations
|
|
363
|
+
|
|
364
|
+
:param project: Project name.
|
|
365
|
+
:param function_name: Application name. Default is model_monitoring_stream.
|
|
366
|
+
:param secret_provider: Optional secret provider to get the connection string secret.
|
|
367
|
+
If not set, the env vars are used.
|
|
368
|
+
:param profile: Optional datastore profile of the stream (V3IO/KafkaSource profile).
|
|
369
|
+
:param v3io_access_key: Optional V3IO access key.
|
|
370
|
+
:param mock: Should the output stream be mocked or not.
|
|
371
|
+
:return: Monitoring stream path to the relevant application.
|
|
372
|
+
"""
|
|
373
|
+
|
|
374
|
+
profile = profile or _get_stream_profile(
|
|
375
|
+
project=project, secret_provider=secret_provider
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
if isinstance(profile, mlrun.datastore.datastore_profile.DatastoreProfileV3io):
|
|
379
|
+
return _get_v3io_output_stream(
|
|
380
|
+
v3io_profile=profile,
|
|
381
|
+
project=project,
|
|
382
|
+
function_name=function_name,
|
|
383
|
+
v3io_access_key=v3io_access_key,
|
|
384
|
+
mock=mock,
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
elif isinstance(
|
|
388
|
+
profile, mlrun.datastore.datastore_profile.DatastoreProfileKafkaSource
|
|
389
|
+
):
|
|
390
|
+
return _get_kafka_output_stream(
|
|
391
|
+
kafka_profile=profile,
|
|
392
|
+
project=project,
|
|
393
|
+
function_name=function_name,
|
|
394
|
+
mock=mock,
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
else:
|
|
398
|
+
raise mlrun.errors.MLRunValueError(
|
|
399
|
+
f"Received an unexpected stream profile type: {type(profile)}\n"
|
|
400
|
+
"Expects `DatastoreProfileV3io` or `DatastoreProfileKafkaSource`."
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
|
|
297
404
|
def batch_dict2timedelta(batch_dict: _BatchDict) -> datetime.timedelta:
|
|
298
405
|
"""
|
|
299
406
|
Convert a batch dictionary to timedelta.
|
|
@@ -20,11 +20,8 @@ import storey
|
|
|
20
20
|
|
|
21
21
|
import mlrun
|
|
22
22
|
import mlrun.common.model_monitoring.helpers
|
|
23
|
-
import mlrun.config
|
|
24
|
-
import mlrun.datastore.targets
|
|
25
23
|
import mlrun.feature_store as fstore
|
|
26
24
|
import mlrun.feature_store.steps
|
|
27
|
-
import mlrun.model_monitoring.db
|
|
28
25
|
import mlrun.serving.states
|
|
29
26
|
import mlrun.utils
|
|
30
27
|
from mlrun.common.schemas.model_monitoring.constants import (
|
|
@@ -392,10 +389,6 @@ class ProcessEndpointEvent(mlrun.feature_store.steps.MapClass):
|
|
|
392
389
|
if not is_not_none(model, [EventFieldType.MODEL]):
|
|
393
390
|
return None
|
|
394
391
|
|
|
395
|
-
version = full_event.body.get(EventFieldType.VERSION)
|
|
396
|
-
versioned_model = f"{model}:{version}" if version else f"{model}:latest"
|
|
397
|
-
|
|
398
|
-
full_event.body[EventFieldType.VERSIONED_MODEL] = versioned_model
|
|
399
392
|
endpoint_id = event[EventFieldType.ENDPOINT_ID]
|
|
400
393
|
|
|
401
394
|
# In case this process fails, resume state from existing record
|
|
@@ -493,7 +486,6 @@ class ProcessEndpointEvent(mlrun.feature_store.steps.MapClass):
|
|
|
493
486
|
events.append(
|
|
494
487
|
{
|
|
495
488
|
EventFieldType.FUNCTION_URI: function_uri,
|
|
496
|
-
EventFieldType.MODEL: versioned_model,
|
|
497
489
|
EventFieldType.ENDPOINT_NAME: event.get(EventFieldType.MODEL),
|
|
498
490
|
EventFieldType.MODEL_CLASS: model_class,
|
|
499
491
|
EventFieldType.TIMESTAMP: timestamp,
|
mlrun/projects/project.py
CHANGED
|
@@ -1254,8 +1254,14 @@ class MlrunProject(ModelObj):
|
|
|
1254
1254
|
mlrun.utils.helpers.validate_builder_source(source, pull_at_runtime, workdir)
|
|
1255
1255
|
|
|
1256
1256
|
self.spec.load_source_on_run = pull_at_runtime
|
|
1257
|
+
|
|
1258
|
+
source_has_changed = source != self.spec.source
|
|
1257
1259
|
self.spec.source = source or self.spec.source
|
|
1258
1260
|
|
|
1261
|
+
# new source should not relay on old workdir
|
|
1262
|
+
if source_has_changed:
|
|
1263
|
+
self.spec.workdir = workdir
|
|
1264
|
+
|
|
1259
1265
|
if self.spec.source.startswith("git://"):
|
|
1260
1266
|
source, reference, branch = resolve_git_reference_from_source(source)
|
|
1261
1267
|
if not branch and not reference:
|
|
@@ -1264,7 +1270,6 @@ class MlrunProject(ModelObj):
|
|
|
1264
1270
|
"'git://<url>/org/repo.git#<branch-name or refs/heads/..>'"
|
|
1265
1271
|
)
|
|
1266
1272
|
|
|
1267
|
-
self.spec.workdir = workdir or self.spec.workdir
|
|
1268
1273
|
try:
|
|
1269
1274
|
# reset function objects (to recalculate build attributes)
|
|
1270
1275
|
self.sync_functions()
|
|
@@ -2217,6 +2222,12 @@ class MlrunProject(ModelObj):
|
|
|
2217
2222
|
reset_policy=reset_policy,
|
|
2218
2223
|
)
|
|
2219
2224
|
)
|
|
2225
|
+
if not alerts:
|
|
2226
|
+
warnings.warn(
|
|
2227
|
+
"No alert config has been created. "
|
|
2228
|
+
"Try specifying a result name explicitly or verifying that results are available"
|
|
2229
|
+
)
|
|
2230
|
+
|
|
2220
2231
|
return alerts
|
|
2221
2232
|
|
|
2222
2233
|
def set_model_monitoring_function(
|
|
@@ -3456,12 +3467,13 @@ class MlrunProject(ModelObj):
|
|
|
3456
3467
|
|
|
3457
3468
|
arguments = arguments or {}
|
|
3458
3469
|
need_repo = self.spec._need_repo()
|
|
3459
|
-
if
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3470
|
+
if not dirty:
|
|
3471
|
+
if self.spec.repo and self.spec.repo.is_dirty():
|
|
3472
|
+
msg = "You seem to have uncommitted git changes, use .push()"
|
|
3473
|
+
if not need_repo:
|
|
3474
|
+
logger.warning("WARNING!, " + msg)
|
|
3475
|
+
else:
|
|
3476
|
+
raise ProjectError(msg + " or dirty=True")
|
|
3465
3477
|
|
|
3466
3478
|
if need_repo and self.spec.repo and not self.spec.source:
|
|
3467
3479
|
raise ProjectError(
|
|
@@ -4120,7 +4132,7 @@ class MlrunProject(ModelObj):
|
|
|
4120
4132
|
|
|
4121
4133
|
:param image: target image name/path. If not specified the project's existing `default_image` name will be
|
|
4122
4134
|
used. If not set, the `mlconf.default_project_image_name` value will be used
|
|
4123
|
-
:param set_as_default: set `image` to be the project's default image (default
|
|
4135
|
+
:param set_as_default: set `image` to be the project's default image (default True)
|
|
4124
4136
|
:param with_mlrun: add the current mlrun package to the container build
|
|
4125
4137
|
:param base_image: base image name/path (commands and source code will be added to it) defaults to
|
|
4126
4138
|
mlrun.mlconf.default_base_image
|
mlrun/run.py
CHANGED
|
@@ -911,7 +911,7 @@ def _run_pipeline(
|
|
|
911
911
|
|
|
912
912
|
def retry_pipeline(
|
|
913
913
|
run_id: str,
|
|
914
|
-
project:
|
|
914
|
+
project: str,
|
|
915
915
|
namespace: Optional[str] = None,
|
|
916
916
|
) -> str:
|
|
917
917
|
"""Retry a pipeline run.
|
|
@@ -920,7 +920,7 @@ def retry_pipeline(
|
|
|
920
920
|
retryable state, a new run is created as a clone of the original run.
|
|
921
921
|
|
|
922
922
|
:param run_id: ID of the pipeline run to retry.
|
|
923
|
-
:param project:
|
|
923
|
+
:param project: name of the project associated with the pipeline run.
|
|
924
924
|
:param namespace: Optional; Kubernetes namespace to use if not the default.
|
|
925
925
|
|
|
926
926
|
:returns: ID of the retried pipeline run or the ID of a cloned run if the original run is not retryable.
|
|
@@ -369,8 +369,9 @@ class RemoteRuntime(KubeResource):
|
|
|
369
369
|
)
|
|
370
370
|
"""
|
|
371
371
|
self.spec.build.source = source
|
|
372
|
-
# update handler in function_handler
|
|
373
|
-
|
|
372
|
+
# update handler in function_handler if needed
|
|
373
|
+
if handler:
|
|
374
|
+
self.spec.function_handler = handler
|
|
374
375
|
if workdir:
|
|
375
376
|
self.spec.workdir = workdir
|
|
376
377
|
if runtime:
|
|
@@ -600,13 +601,15 @@ class RemoteRuntime(KubeResource):
|
|
|
600
601
|
# this also means that the function object will be updated with the function status
|
|
601
602
|
self._wait_for_function_deployment(db, verbose=verbose)
|
|
602
603
|
# check if there are any background tasks related to creating model endpoints
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
604
|
+
model_endpoints_creation_background_tasks = (
|
|
605
|
+
mlrun.common.schemas.BackgroundTaskList(
|
|
606
|
+
**data.pop("background_tasks", {"background_tasks": []})
|
|
607
|
+
).background_tasks
|
|
608
|
+
)
|
|
609
|
+
if model_endpoints_creation_background_tasks:
|
|
607
610
|
self._check_model_endpoint_task_state(
|
|
608
611
|
db=db,
|
|
609
|
-
background_task=
|
|
612
|
+
background_task=model_endpoints_creation_background_tasks[0],
|
|
610
613
|
wait_for_completion=False,
|
|
611
614
|
)
|
|
612
615
|
|
|
@@ -1014,6 +1017,7 @@ class RemoteRuntime(KubeResource):
|
|
|
1014
1017
|
):
|
|
1015
1018
|
"""
|
|
1016
1019
|
Add a sidecar container to the function pod
|
|
1020
|
+
|
|
1017
1021
|
:param name: Sidecar container name.
|
|
1018
1022
|
:param image: Sidecar container image.
|
|
1019
1023
|
:param ports: Sidecar container ports to expose. Can be a single port or a list of ports.
|
mlrun/serving/routers.py
CHANGED
|
@@ -596,7 +596,6 @@ class VotingEnsemble(ParallelRun):
|
|
|
596
596
|
self.vote_type = vote_type
|
|
597
597
|
self.vote_flag = True if self.vote_type is not None else False
|
|
598
598
|
self.weights = weights
|
|
599
|
-
self.version = kwargs.get("version", "v1")
|
|
600
599
|
self.log_router = True
|
|
601
600
|
self.prediction_col_name = prediction_col_name or "prediction"
|
|
602
601
|
self.format_response_with_col_name_flag = format_response_with_col_name_flag
|
|
@@ -927,14 +926,14 @@ class VotingEnsemble(ParallelRun):
|
|
|
927
926
|
"model_name": self.name,
|
|
928
927
|
"outputs": votes,
|
|
929
928
|
}
|
|
930
|
-
if self.
|
|
931
|
-
response_body["
|
|
929
|
+
if self.model_endpoint_uid:
|
|
930
|
+
response_body["model_endpoint_uid"] = self.model_endpoint_uid
|
|
932
931
|
response.body = response_body
|
|
933
932
|
elif name == self.name and event.method == "GET" and not subpath:
|
|
934
933
|
response = copy.copy(event)
|
|
935
934
|
response_body = {
|
|
936
935
|
"name": self.name,
|
|
937
|
-
"
|
|
936
|
+
"model_endpoint_uid": self.model_endpoint_uid or "",
|
|
938
937
|
"inputs": [],
|
|
939
938
|
"outputs": [],
|
|
940
939
|
}
|
mlrun/serving/server.py
CHANGED
|
@@ -65,7 +65,7 @@ class _StreamContext:
|
|
|
65
65
|
self.hostname = socket.gethostname()
|
|
66
66
|
self.function_uri = function_uri
|
|
67
67
|
self.output_stream = None
|
|
68
|
-
|
|
68
|
+
stream_uri = None
|
|
69
69
|
log_stream = parameters.get(FileTargetKind.LOG_STREAM, "")
|
|
70
70
|
|
|
71
71
|
if (enabled or log_stream) and function_uri:
|
|
@@ -78,17 +78,19 @@ class _StreamContext:
|
|
|
78
78
|
|
|
79
79
|
if log_stream == DUMMY_STREAM:
|
|
80
80
|
# Dummy stream used for testing, see tests/serving/test_serving.py
|
|
81
|
-
|
|
81
|
+
stream_uri = DUMMY_STREAM
|
|
82
82
|
elif not stream_args.get("mock"): # if not a mock: `context.is_mock = True`
|
|
83
|
-
|
|
84
|
-
project=project
|
|
85
|
-
)
|
|
83
|
+
stream_uri = mlrun.model_monitoring.get_stream_path(project=project)
|
|
86
84
|
|
|
87
85
|
if log_stream:
|
|
88
86
|
# Update the stream path to the log stream value
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
87
|
+
stream_uri = log_stream.format(project=project)
|
|
88
|
+
self.output_stream = get_stream_pusher(stream_uri, **stream_args)
|
|
89
|
+
else:
|
|
90
|
+
# Get the output stream from the profile
|
|
91
|
+
self.output_stream = mlrun.model_monitoring.helpers.get_output_stream(
|
|
92
|
+
project=project, mock=stream_args.get("mock", False)
|
|
93
|
+
)
|
|
92
94
|
|
|
93
95
|
|
|
94
96
|
class GraphServer(ModelObj):
|
mlrun/serving/states.py
CHANGED
|
@@ -652,6 +652,14 @@ class TaskStep(BaseStep):
|
|
|
652
652
|
if isinstance(self.endpoint_type, schemas.EndpointType)
|
|
653
653
|
else self.endpoint_type
|
|
654
654
|
)
|
|
655
|
+
self.model_endpoint_creation_strategy = (
|
|
656
|
+
self.model_endpoint_creation_strategy.value
|
|
657
|
+
if isinstance(
|
|
658
|
+
self.model_endpoint_creation_strategy,
|
|
659
|
+
schemas.ModelEndpointCreationStrategy,
|
|
660
|
+
)
|
|
661
|
+
else self.model_endpoint_creation_strategy
|
|
662
|
+
)
|
|
655
663
|
return super().to_dict(fields, exclude, strip)
|
|
656
664
|
|
|
657
665
|
|
|
@@ -755,9 +763,11 @@ class RouterStep(TaskStep):
|
|
|
755
763
|
self._routes: ObjectDict = None
|
|
756
764
|
self.routes = routes
|
|
757
765
|
self.endpoint_type = schemas.EndpointType.ROUTER
|
|
766
|
+
if isinstance(class_name, type):
|
|
767
|
+
class_name = class_name.__name__
|
|
758
768
|
self.model_endpoint_creation_strategy = (
|
|
759
769
|
schemas.ModelEndpointCreationStrategy.INPLACE
|
|
760
|
-
if class_name and "
|
|
770
|
+
if class_name and "VotingEnsemble" in class_name
|
|
761
771
|
else schemas.ModelEndpointCreationStrategy.SKIP
|
|
762
772
|
)
|
|
763
773
|
|
mlrun/serving/v2_serving.py
CHANGED
|
@@ -95,9 +95,6 @@ class V2ModelServer(StepToDict):
|
|
|
95
95
|
:param kwargs: extra arguments (can be accessed using self.get_param(key))
|
|
96
96
|
"""
|
|
97
97
|
self.name = name
|
|
98
|
-
self.version = ""
|
|
99
|
-
if name and ":" in name:
|
|
100
|
-
self.version = name.split(":", 1)[-1]
|
|
101
98
|
self.context = context
|
|
102
99
|
self.ready = False
|
|
103
100
|
self.error = ""
|
|
@@ -325,8 +322,8 @@ class V2ModelServer(StepToDict):
|
|
|
325
322
|
"outputs": outputs,
|
|
326
323
|
"timestamp": start.isoformat(sep=" ", timespec="microseconds"),
|
|
327
324
|
}
|
|
328
|
-
if self.
|
|
329
|
-
response["
|
|
325
|
+
if self.model_endpoint_uid:
|
|
326
|
+
response["model_endpoint_uid"] = self.model_endpoint_uid
|
|
330
327
|
elif op == "ready" and event.method == "GET":
|
|
331
328
|
# get model health operation
|
|
332
329
|
setattr(event, "terminated", True)
|
|
@@ -352,7 +349,7 @@ class V2ModelServer(StepToDict):
|
|
|
352
349
|
setattr(event, "terminated", True)
|
|
353
350
|
event_body = {
|
|
354
351
|
"name": self.name.split(":")[0],
|
|
355
|
-
"
|
|
352
|
+
"model_endpoint_uid": self.model_endpoint_uid or "",
|
|
356
353
|
"inputs": [],
|
|
357
354
|
"outputs": [],
|
|
358
355
|
}
|
|
@@ -386,8 +383,8 @@ class V2ModelServer(StepToDict):
|
|
|
386
383
|
"model_name": self.name,
|
|
387
384
|
"outputs": outputs,
|
|
388
385
|
}
|
|
389
|
-
if self.
|
|
390
|
-
response["
|
|
386
|
+
if self.model_endpoint_uid:
|
|
387
|
+
response["model_endpoint_uid"] = self.model_endpoint_uid
|
|
391
388
|
|
|
392
389
|
elif hasattr(self, "op_" + op):
|
|
393
390
|
# custom operation (child methods starting with "op_")
|
|
@@ -510,7 +507,6 @@ class _ModelLogPusher:
|
|
|
510
507
|
self.verbose = context.verbose
|
|
511
508
|
self.hostname = context.stream.hostname
|
|
512
509
|
self.function_uri = context.stream.function_uri
|
|
513
|
-
self.stream_path = context.stream.stream_uri
|
|
514
510
|
self.sampling_percentage = float(context.get_param("sampling_percentage", 100))
|
|
515
511
|
self.output_stream = output_stream or context.stream.output_stream
|
|
516
512
|
self._worker = context.worker_id
|
|
@@ -520,7 +516,6 @@ class _ModelLogPusher:
|
|
|
520
516
|
"class": self.model.__class__.__name__,
|
|
521
517
|
"worker": self._worker,
|
|
522
518
|
"model": self.model.name,
|
|
523
|
-
"version": self.model.version,
|
|
524
519
|
"host": self.hostname,
|
|
525
520
|
"function_uri": self.function_uri,
|
|
526
521
|
"endpoint_id": self.model.model_endpoint_uid,
|
mlrun/utils/async_http.py
CHANGED
|
@@ -24,10 +24,17 @@ from aiohttp_retry import ExponentialRetry, RequestParams, RetryClient, RetryOpt
|
|
|
24
24
|
from aiohttp_retry.client import _RequestContext
|
|
25
25
|
|
|
26
26
|
from mlrun.config import config
|
|
27
|
-
from mlrun.errors import err_to_str
|
|
27
|
+
from mlrun.errors import err_to_str
|
|
28
|
+
from mlrun.errors import raise_for_status as ml_raise_for_status
|
|
28
29
|
|
|
29
30
|
from .helpers import logger as mlrun_logger
|
|
30
31
|
|
|
32
|
+
DEFAULT_BLACKLISTED_METHODS = [
|
|
33
|
+
"POST",
|
|
34
|
+
"PUT",
|
|
35
|
+
"PATCH",
|
|
36
|
+
]
|
|
37
|
+
|
|
31
38
|
|
|
32
39
|
class AsyncClientWithRetry(RetryClient):
|
|
33
40
|
"""
|
|
@@ -46,16 +53,6 @@ class AsyncClientWithRetry(RetryClient):
|
|
|
46
53
|
*args,
|
|
47
54
|
**kwargs,
|
|
48
55
|
):
|
|
49
|
-
# do not retry on PUT / PATCH as they might have side effects (not truly idempotent)
|
|
50
|
-
blacklisted_methods = (
|
|
51
|
-
blacklisted_methods
|
|
52
|
-
if blacklisted_methods is not None
|
|
53
|
-
else [
|
|
54
|
-
"POST",
|
|
55
|
-
"PUT",
|
|
56
|
-
"PATCH",
|
|
57
|
-
]
|
|
58
|
-
)
|
|
59
56
|
super().__init__(
|
|
60
57
|
*args,
|
|
61
58
|
retry_options=ExponentialRetryOverride(
|
|
@@ -72,9 +69,13 @@ class AsyncClientWithRetry(RetryClient):
|
|
|
72
69
|
**kwargs,
|
|
73
70
|
)
|
|
74
71
|
|
|
72
|
+
@property
|
|
73
|
+
def retry_options(self) -> "ExponentialRetryOverride":
|
|
74
|
+
return typing.cast(ExponentialRetryOverride, self._retry_options)
|
|
75
|
+
|
|
75
76
|
def methods_blacklist_update_required(self, new_blacklist: str):
|
|
76
77
|
self._retry_options: ExponentialRetryOverride
|
|
77
|
-
return set(self.
|
|
78
|
+
return set(self.retry_options.blacklisted_methods).difference(
|
|
78
79
|
set(new_blacklist)
|
|
79
80
|
)
|
|
80
81
|
|
|
@@ -114,8 +115,8 @@ class ExponentialRetryOverride(ExponentialRetry):
|
|
|
114
115
|
|
|
115
116
|
def __init__(
|
|
116
117
|
self,
|
|
117
|
-
retry_on_exception: bool,
|
|
118
|
-
blacklisted_methods: list[str],
|
|
118
|
+
retry_on_exception: typing.Optional[bool] = True,
|
|
119
|
+
blacklisted_methods: typing.Optional[list[str]] = None,
|
|
119
120
|
*args,
|
|
120
121
|
**kwargs,
|
|
121
122
|
):
|
|
@@ -123,7 +124,12 @@ class ExponentialRetryOverride(ExponentialRetry):
|
|
|
123
124
|
self.retry_on_exception = retry_on_exception
|
|
124
125
|
|
|
125
126
|
# methods that should not be retried
|
|
126
|
-
|
|
127
|
+
# do not retry on PUT / PATCH as they might have side effects (not truly idempotent)
|
|
128
|
+
self.blacklisted_methods = (
|
|
129
|
+
blacklisted_methods
|
|
130
|
+
if blacklisted_methods is not None
|
|
131
|
+
else DEFAULT_BLACKLISTED_METHODS
|
|
132
|
+
)
|
|
127
133
|
|
|
128
134
|
# default exceptions that should be retried on (when retry_on_exception is True)
|
|
129
135
|
if "exceptions" not in kwargs:
|
|
@@ -136,6 +142,10 @@ class _CustomRequestContext(_RequestContext):
|
|
|
136
142
|
Extends by adding retry logic on both error statuses and certain exceptions.
|
|
137
143
|
"""
|
|
138
144
|
|
|
145
|
+
@property
|
|
146
|
+
def retry_options(self) -> ExponentialRetryOverride:
|
|
147
|
+
return typing.cast(ExponentialRetryOverride, self._retry_options)
|
|
148
|
+
|
|
139
149
|
async def _do_request(self) -> aiohttp.ClientResponse:
|
|
140
150
|
current_attempt = 0
|
|
141
151
|
while True:
|
|
@@ -186,9 +196,9 @@ class _CustomRequestContext(_RequestContext):
|
|
|
186
196
|
return response
|
|
187
197
|
|
|
188
198
|
last_attempt = current_attempt == self._retry_options.attempts
|
|
189
|
-
if self.
|
|
199
|
+
if not self._is_status_retryable(response.status) or last_attempt:
|
|
190
200
|
if self._raise_for_status:
|
|
191
|
-
|
|
201
|
+
ml_raise_for_status(response)
|
|
192
202
|
|
|
193
203
|
self._response = response
|
|
194
204
|
return response
|
|
@@ -204,7 +214,7 @@ class _CustomRequestContext(_RequestContext):
|
|
|
204
214
|
)
|
|
205
215
|
|
|
206
216
|
except Exception as exc:
|
|
207
|
-
if not self.
|
|
217
|
+
if not self.retry_options.retry_on_exception:
|
|
208
218
|
self._logger.warning(
|
|
209
219
|
"Request failed, retry on exception is disabled",
|
|
210
220
|
method=params.method,
|
|
@@ -250,7 +260,10 @@ class _CustomRequestContext(_RequestContext):
|
|
|
250
260
|
await asyncio.sleep(retry_wait)
|
|
251
261
|
|
|
252
262
|
def _is_method_retryable(self, method: str):
|
|
253
|
-
return method not in self.
|
|
263
|
+
return method not in self.retry_options.blacklisted_methods
|
|
264
|
+
|
|
265
|
+
def _is_status_retryable(self, status: int):
|
|
266
|
+
return status in self._retry_options.statuses
|
|
254
267
|
|
|
255
268
|
async def _check_response_callback(
|
|
256
269
|
self,
|
mlrun/utils/helpers.py
CHANGED
|
@@ -34,6 +34,7 @@ from importlib import import_module, reload
|
|
|
34
34
|
from os import path
|
|
35
35
|
from types import ModuleType
|
|
36
36
|
from typing import Any, Optional
|
|
37
|
+
from urllib.parse import urlparse
|
|
37
38
|
|
|
38
39
|
import git
|
|
39
40
|
import inflection
|
|
@@ -1736,7 +1737,9 @@ setting partitioned=False"""
|
|
|
1736
1737
|
|
|
1737
1738
|
def is_ecr_url(registry: str) -> bool:
|
|
1738
1739
|
# example URL: <aws_account_id>.dkr.ecr.<region>.amazonaws.com
|
|
1739
|
-
|
|
1740
|
+
parsed_url = urlparse(f"https://{registry}")
|
|
1741
|
+
hostname = parsed_url.hostname
|
|
1742
|
+
return hostname and ".ecr." in hostname and hostname.endswith(".amazonaws.com")
|
|
1740
1743
|
|
|
1741
1744
|
|
|
1742
1745
|
def get_local_file_schema() -> list:
|
mlrun/utils/logger.py
CHANGED
|
@@ -55,6 +55,7 @@ class _NotificationPusherBase:
|
|
|
55
55
|
event_loop = asyncio.get_event_loop()
|
|
56
56
|
except RuntimeError:
|
|
57
57
|
event_loop = asyncio.new_event_loop()
|
|
58
|
+
asyncio.set_event_loop(event_loop)
|
|
58
59
|
|
|
59
60
|
if not event_loop.is_running():
|
|
60
61
|
event_loop.run_until_complete(async_push_callback())
|
mlrun/utils/version/version.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: mlrun
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.0rc30
|
|
4
4
|
Summary: Tracking and config of machine learning runs
|
|
5
5
|
Home-page: https://github.com/mlrun/mlrun
|
|
6
6
|
Author: Yaron Haviv
|
|
@@ -23,8 +23,8 @@ Description-Content-Type: text/markdown
|
|
|
23
23
|
License-File: LICENSE
|
|
24
24
|
Requires-Dist: urllib3<1.27,>=1.26.9
|
|
25
25
|
Requires-Dist: GitPython>=3.1.41,~=3.1
|
|
26
|
-
Requires-Dist: aiohttp~=3.
|
|
27
|
-
Requires-Dist: aiohttp-retry~=2.
|
|
26
|
+
Requires-Dist: aiohttp~=3.11
|
|
27
|
+
Requires-Dist: aiohttp-retry~=2.9
|
|
28
28
|
Requires-Dist: click~=8.1
|
|
29
29
|
Requires-Dist: nest-asyncio~=1.0
|
|
30
30
|
Requires-Dist: ipython~=8.10
|
|
@@ -51,7 +51,7 @@ Requires-Dist: setuptools>=75.2
|
|
|
51
51
|
Requires-Dist: deprecated~=1.2
|
|
52
52
|
Requires-Dist: jinja2>=3.1.3,~=3.1
|
|
53
53
|
Requires-Dist: orjson<4,>=3.9.15
|
|
54
|
-
Requires-Dist: mlrun-pipelines-kfp-common~=0.3.
|
|
54
|
+
Requires-Dist: mlrun-pipelines-kfp-common~=0.3.7
|
|
55
55
|
Requires-Dist: mlrun-pipelines-kfp-v1-8~=0.3.5; python_version < "3.11"
|
|
56
56
|
Requires-Dist: docstring_parser~=0.16
|
|
57
57
|
Requires-Dist: aiosmtplib~=3.0
|