mlrun 1.7.0rc25__py3-none-any.whl → 1.7.0rc29__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/__init__.py +1 -0
- mlrun/common/schemas/model_monitoring/constants.py +27 -12
- mlrun/common/schemas/model_monitoring/model_endpoints.py +0 -12
- mlrun/common/schemas/schedule.py +1 -1
- mlrun/config.py +16 -9
- mlrun/datastore/azure_blob.py +2 -1
- mlrun/datastore/base.py +1 -5
- mlrun/datastore/datastore.py +3 -3
- mlrun/datastore/google_cloud_storage.py +6 -2
- mlrun/datastore/inmem.py +1 -1
- mlrun/datastore/snowflake_utils.py +3 -1
- mlrun/datastore/sources.py +26 -11
- mlrun/datastore/store_resources.py +2 -0
- mlrun/datastore/targets.py +60 -25
- mlrun/db/base.py +11 -0
- mlrun/db/httpdb.py +47 -33
- mlrun/db/nopdb.py +11 -1
- mlrun/errors.py +4 -0
- mlrun/execution.py +18 -10
- mlrun/feature_store/retrieval/spark_merger.py +2 -1
- mlrun/launcher/local.py +2 -2
- mlrun/model.py +30 -0
- mlrun/model_monitoring/api.py +6 -52
- mlrun/model_monitoring/applications/histogram_data_drift.py +4 -1
- mlrun/model_monitoring/db/stores/__init__.py +21 -9
- mlrun/model_monitoring/db/stores/base/store.py +39 -1
- mlrun/model_monitoring/db/stores/sqldb/models/base.py +9 -7
- mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +4 -2
- mlrun/model_monitoring/db/stores/sqldb/sql_store.py +34 -79
- mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +19 -27
- mlrun/model_monitoring/db/tsdb/__init__.py +19 -14
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +4 -2
- mlrun/model_monitoring/helpers.py +9 -5
- mlrun/model_monitoring/writer.py +1 -5
- mlrun/projects/operations.py +1 -0
- mlrun/projects/project.py +76 -76
- mlrun/render.py +10 -5
- mlrun/run.py +2 -2
- mlrun/runtimes/daskjob.py +7 -1
- mlrun/runtimes/local.py +24 -7
- 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 +46 -2
- mlrun/utils/logger.py +36 -2
- 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 +41 -1
- mlrun/utils/notifications/notification_pusher.py +2 -2
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.7.0rc25.dist-info → mlrun-1.7.0rc29.dist-info}/METADATA +11 -6
- {mlrun-1.7.0rc25.dist-info → mlrun-1.7.0rc29.dist-info}/RECORD +67 -67
- {mlrun-1.7.0rc25.dist-info → mlrun-1.7.0rc29.dist-info}/WHEEL +1 -1
- {mlrun-1.7.0rc25.dist-info → mlrun-1.7.0rc29.dist-info}/LICENSE +0 -0
- {mlrun-1.7.0rc25.dist-info → mlrun-1.7.0rc29.dist-info}/entry_points.txt +0 -0
- {mlrun-1.7.0rc25.dist-info → mlrun-1.7.0rc29.dist-info}/top_level.txt +0 -0
mlrun/datastore/targets.py
CHANGED
|
@@ -29,7 +29,10 @@ from mergedeep import merge
|
|
|
29
29
|
import mlrun
|
|
30
30
|
import mlrun.utils.helpers
|
|
31
31
|
from mlrun.config import config
|
|
32
|
-
from mlrun.datastore.snowflake_utils import
|
|
32
|
+
from mlrun.datastore.snowflake_utils import (
|
|
33
|
+
get_snowflake_password,
|
|
34
|
+
get_snowflake_spark_options,
|
|
35
|
+
)
|
|
33
36
|
from mlrun.datastore.utils import transform_list_filters_to_tuple
|
|
34
37
|
from mlrun.model import DataSource, DataTarget, DataTargetBase, TargetPathObject
|
|
35
38
|
from mlrun.utils import logger, now_date
|
|
@@ -546,9 +549,7 @@ class BaseStoreTarget(DataTargetBase):
|
|
|
546
549
|
os.makedirs(dir, exist_ok=True)
|
|
547
550
|
target_df = df
|
|
548
551
|
partition_cols = None # single parquet file
|
|
549
|
-
if not
|
|
550
|
-
".pq"
|
|
551
|
-
): # directory
|
|
552
|
+
if not mlrun.utils.helpers.is_parquet_file(target_path): # directory
|
|
552
553
|
partition_cols = []
|
|
553
554
|
if timestamp_key and (
|
|
554
555
|
self.partitioned or self.time_partitioning_granularity
|
|
@@ -775,6 +776,10 @@ class BaseStoreTarget(DataTargetBase):
|
|
|
775
776
|
def get_dask_options(self):
|
|
776
777
|
raise NotImplementedError()
|
|
777
778
|
|
|
779
|
+
@property
|
|
780
|
+
def source_spark_attributes(self) -> dict:
|
|
781
|
+
return {}
|
|
782
|
+
|
|
778
783
|
|
|
779
784
|
class ParquetTarget(BaseStoreTarget):
|
|
780
785
|
"""Parquet target storage driver, used to materialize feature set/vector data into parquet files.
|
|
@@ -911,10 +916,8 @@ class ParquetTarget(BaseStoreTarget):
|
|
|
911
916
|
if time_unit == time_partitioning_granularity:
|
|
912
917
|
break
|
|
913
918
|
|
|
914
|
-
if (
|
|
915
|
-
|
|
916
|
-
and not self.get_target_path().endswith(".parquet")
|
|
917
|
-
and not self.get_target_path().endswith(".pq")
|
|
919
|
+
if not self.partitioned and not mlrun.utils.helpers.is_parquet_file(
|
|
920
|
+
self.get_target_path()
|
|
918
921
|
):
|
|
919
922
|
partition_cols = []
|
|
920
923
|
|
|
@@ -1033,9 +1036,7 @@ class ParquetTarget(BaseStoreTarget):
|
|
|
1033
1036
|
return result
|
|
1034
1037
|
|
|
1035
1038
|
def is_single_file(self):
|
|
1036
|
-
|
|
1037
|
-
return self.path.endswith(".parquet") or self.path.endswith(".pq")
|
|
1038
|
-
return False
|
|
1039
|
+
return mlrun.utils.helpers.is_parquet_file(self.path)
|
|
1039
1040
|
|
|
1040
1041
|
def prepare_spark_df(self, df, key_columns, timestamp_key=None, spark_options=None):
|
|
1041
1042
|
# If partitioning by time, add the necessary columns
|
|
@@ -1208,19 +1209,20 @@ class SnowflakeTarget(BaseStoreTarget):
|
|
|
1208
1209
|
warehouse: str = None,
|
|
1209
1210
|
table_name: str = None,
|
|
1210
1211
|
):
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
"
|
|
1214
|
-
|
|
1215
|
-
"
|
|
1216
|
-
|
|
1217
|
-
"
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1212
|
+
attributes = attributes or {}
|
|
1213
|
+
if url:
|
|
1214
|
+
attributes["url"] = url
|
|
1215
|
+
if user:
|
|
1216
|
+
attributes["user"] = user
|
|
1217
|
+
if database:
|
|
1218
|
+
attributes["database"] = database
|
|
1219
|
+
if db_schema:
|
|
1220
|
+
attributes["db_schema"] = db_schema
|
|
1221
|
+
if warehouse:
|
|
1222
|
+
attributes["warehouse"] = warehouse
|
|
1223
|
+
if table_name:
|
|
1224
|
+
attributes["table"] = table_name
|
|
1225
|
+
|
|
1224
1226
|
super().__init__(
|
|
1225
1227
|
name,
|
|
1226
1228
|
path,
|
|
@@ -1244,7 +1246,31 @@ class SnowflakeTarget(BaseStoreTarget):
|
|
|
1244
1246
|
return spark_options
|
|
1245
1247
|
|
|
1246
1248
|
def purge(self):
|
|
1247
|
-
|
|
1249
|
+
import snowflake.connector
|
|
1250
|
+
|
|
1251
|
+
missing = [
|
|
1252
|
+
key
|
|
1253
|
+
for key in ["database", "db_schema", "table", "url", "user", "warehouse"]
|
|
1254
|
+
if self.attributes.get(key) is None
|
|
1255
|
+
]
|
|
1256
|
+
if missing:
|
|
1257
|
+
raise mlrun.errors.MLRunRuntimeError(
|
|
1258
|
+
f"Can't purge Snowflake target, "
|
|
1259
|
+
f"some attributes are missing: {', '.join(missing)}"
|
|
1260
|
+
)
|
|
1261
|
+
account = self.attributes["url"].replace(".snowflakecomputing.com", "")
|
|
1262
|
+
|
|
1263
|
+
with snowflake.connector.connect(
|
|
1264
|
+
account=account,
|
|
1265
|
+
user=self.attributes["user"],
|
|
1266
|
+
password=get_snowflake_password(),
|
|
1267
|
+
warehouse=self.attributes["warehouse"],
|
|
1268
|
+
) as snowflake_connector:
|
|
1269
|
+
drop_statement = (
|
|
1270
|
+
f"DROP TABLE IF EXISTS {self.attributes['database']}.{self.attributes['db_schema']}"
|
|
1271
|
+
f".{self.attributes['table']}"
|
|
1272
|
+
)
|
|
1273
|
+
snowflake_connector.execute_string(drop_statement)
|
|
1248
1274
|
|
|
1249
1275
|
def as_df(
|
|
1250
1276
|
self,
|
|
@@ -1259,6 +1285,15 @@ class SnowflakeTarget(BaseStoreTarget):
|
|
|
1259
1285
|
):
|
|
1260
1286
|
raise NotImplementedError()
|
|
1261
1287
|
|
|
1288
|
+
@property
|
|
1289
|
+
def source_spark_attributes(self) -> dict:
|
|
1290
|
+
keys = ["url", "user", "database", "db_schema", "warehouse"]
|
|
1291
|
+
attributes = self.attributes or {}
|
|
1292
|
+
snowflake_dict = {key: attributes.get(key) for key in keys}
|
|
1293
|
+
table = attributes.get("table")
|
|
1294
|
+
snowflake_dict["query"] = f"SELECT * from {table}" if table else None
|
|
1295
|
+
return snowflake_dict
|
|
1296
|
+
|
|
1262
1297
|
|
|
1263
1298
|
class NoSqlBaseTarget(BaseStoreTarget):
|
|
1264
1299
|
is_table = True
|
mlrun/db/base.py
CHANGED
|
@@ -138,6 +138,7 @@ class RunDBInterface(ABC):
|
|
|
138
138
|
category: Union[str, mlrun.common.schemas.ArtifactCategories] = None,
|
|
139
139
|
tree: str = None,
|
|
140
140
|
format_: mlrun.common.formatters.ArtifactFormat = mlrun.common.formatters.ArtifactFormat.full,
|
|
141
|
+
limit: int = None,
|
|
141
142
|
):
|
|
142
143
|
pass
|
|
143
144
|
|
|
@@ -153,6 +154,7 @@ class RunDBInterface(ABC):
|
|
|
153
154
|
mlrun.common.schemas.artifact.ArtifactsDeletionStrategies.metadata_only
|
|
154
155
|
),
|
|
155
156
|
secrets: dict = None,
|
|
157
|
+
iter=None,
|
|
156
158
|
):
|
|
157
159
|
pass
|
|
158
160
|
|
|
@@ -890,6 +892,7 @@ class RunDBInterface(ABC):
|
|
|
890
892
|
image: str = "mlrun/mlrun",
|
|
891
893
|
deploy_histogram_data_drift_app: bool = True,
|
|
892
894
|
rebuild_images: bool = False,
|
|
895
|
+
fetch_credentials_from_sys_config: bool = False,
|
|
893
896
|
) -> None:
|
|
894
897
|
pass
|
|
895
898
|
|
|
@@ -916,3 +919,11 @@ class RunDBInterface(ABC):
|
|
|
916
919
|
self, project: str, image: str = "mlrun/mlrun"
|
|
917
920
|
) -> None:
|
|
918
921
|
pass
|
|
922
|
+
|
|
923
|
+
@abstractmethod
|
|
924
|
+
def set_model_monitoring_credentials(
|
|
925
|
+
self,
|
|
926
|
+
project: str,
|
|
927
|
+
credentials: dict[str, str],
|
|
928
|
+
) -> None:
|
|
929
|
+
pass
|
mlrun/db/httpdb.py
CHANGED
|
@@ -38,6 +38,7 @@ import mlrun.model_monitoring.model_endpoint
|
|
|
38
38
|
import mlrun.platforms
|
|
39
39
|
import mlrun.projects
|
|
40
40
|
import mlrun.runtimes.nuclio.api_gateway
|
|
41
|
+
import mlrun.runtimes.nuclio.function
|
|
41
42
|
import mlrun.utils
|
|
42
43
|
from mlrun.alerts.alert import AlertConfig
|
|
43
44
|
from mlrun.db.auth_utils import OAuthClientIDTokenProvider, StaticTokenProvider
|
|
@@ -536,6 +537,10 @@ class HTTPRunDB(RunDBInterface):
|
|
|
536
537
|
server_cfg.get("model_monitoring_tsdb_connection")
|
|
537
538
|
or config.model_endpoint_monitoring.tsdb_connection
|
|
538
539
|
)
|
|
540
|
+
config.model_endpoint_monitoring.stream_connection = (
|
|
541
|
+
server_cfg.get("stream_connection")
|
|
542
|
+
or config.model_endpoint_monitoring.stream_connection
|
|
543
|
+
)
|
|
539
544
|
config.packagers = server_cfg.get("packagers") or config.packagers
|
|
540
545
|
server_data_prefixes = server_cfg.get("feature_store_data_prefixes") or {}
|
|
541
546
|
for prefix in ["default", "nosql", "redisnosql"]:
|
|
@@ -870,7 +875,7 @@ class HTTPRunDB(RunDBInterface):
|
|
|
870
875
|
):
|
|
871
876
|
# default to last week on no filter
|
|
872
877
|
start_time_from = datetime.now() - timedelta(days=7)
|
|
873
|
-
partition_by = mlrun.common.schemas.RunPartitionByField.
|
|
878
|
+
partition_by = mlrun.common.schemas.RunPartitionByField.project_and_name
|
|
874
879
|
partition_sort_by = mlrun.common.schemas.SortField.updated
|
|
875
880
|
|
|
876
881
|
params = {
|
|
@@ -963,7 +968,7 @@ class HTTPRunDB(RunDBInterface):
|
|
|
963
968
|
|
|
964
969
|
# we do this because previously the 'uid' name was used for the 'tree' parameter
|
|
965
970
|
tree = tree or uid
|
|
966
|
-
|
|
971
|
+
project = project or mlrun.mlconf.default_project
|
|
967
972
|
endpoint_path = f"projects/{project}/artifacts/{key}"
|
|
968
973
|
|
|
969
974
|
error = f"store artifact {project}/{key}"
|
|
@@ -1002,7 +1007,7 @@ class HTTPRunDB(RunDBInterface):
|
|
|
1002
1007
|
:param format_: The format in which to return the artifact. Default is 'full'.
|
|
1003
1008
|
"""
|
|
1004
1009
|
|
|
1005
|
-
project = project or
|
|
1010
|
+
project = project or mlrun.mlconf.default_project
|
|
1006
1011
|
tag = tag or "latest"
|
|
1007
1012
|
endpoint_path = f"projects/{project}/artifacts/{key}"
|
|
1008
1013
|
error = f"read artifact {project}/{key}"
|
|
@@ -1028,6 +1033,7 @@ class HTTPRunDB(RunDBInterface):
|
|
|
1028
1033
|
mlrun.common.schemas.artifact.ArtifactsDeletionStrategies.metadata_only
|
|
1029
1034
|
),
|
|
1030
1035
|
secrets: dict = None,
|
|
1036
|
+
iter=None,
|
|
1031
1037
|
):
|
|
1032
1038
|
"""Delete an artifact.
|
|
1033
1039
|
|
|
@@ -1039,13 +1045,14 @@ class HTTPRunDB(RunDBInterface):
|
|
|
1039
1045
|
:param deletion_strategy: The artifact deletion strategy types.
|
|
1040
1046
|
:param secrets: Credentials needed to access the artifact data.
|
|
1041
1047
|
"""
|
|
1042
|
-
|
|
1048
|
+
project = project or mlrun.mlconf.default_project
|
|
1043
1049
|
endpoint_path = f"projects/{project}/artifacts/{key}"
|
|
1044
1050
|
params = {
|
|
1045
1051
|
"key": key,
|
|
1046
1052
|
"tag": tag,
|
|
1047
1053
|
"tree": tree,
|
|
1048
1054
|
"uid": uid,
|
|
1055
|
+
"iter": iter,
|
|
1049
1056
|
"deletion_strategy": deletion_strategy,
|
|
1050
1057
|
}
|
|
1051
1058
|
error = f"del artifact {project}/{key}"
|
|
@@ -1073,6 +1080,7 @@ class HTTPRunDB(RunDBInterface):
|
|
|
1073
1080
|
tree: str = None,
|
|
1074
1081
|
producer_uri: str = None,
|
|
1075
1082
|
format_: mlrun.common.formatters.ArtifactFormat = mlrun.common.formatters.ArtifactFormat.full,
|
|
1083
|
+
limit: int = None,
|
|
1076
1084
|
) -> ArtifactList:
|
|
1077
1085
|
"""List artifacts filtered by various parameters.
|
|
1078
1086
|
|
|
@@ -1108,6 +1116,7 @@ class HTTPRunDB(RunDBInterface):
|
|
|
1108
1116
|
points to a run and is used to filter artifacts by the run that produced them when the artifact producer id
|
|
1109
1117
|
is a workflow id (artifact was created as part of a workflow).
|
|
1110
1118
|
:param format_: The format in which to return the artifacts. Default is 'full'.
|
|
1119
|
+
:param limit: Maximum number of artifacts to return.
|
|
1111
1120
|
"""
|
|
1112
1121
|
|
|
1113
1122
|
project = project or config.default_project
|
|
@@ -1127,6 +1136,7 @@ class HTTPRunDB(RunDBInterface):
|
|
|
1127
1136
|
"tree": tree,
|
|
1128
1137
|
"format": format_,
|
|
1129
1138
|
"producer_uri": producer_uri,
|
|
1139
|
+
"limit": limit,
|
|
1130
1140
|
}
|
|
1131
1141
|
error = "list artifacts"
|
|
1132
1142
|
endpoint_path = f"projects/{project}/artifacts"
|
|
@@ -1607,20 +1617,11 @@ class HTTPRunDB(RunDBInterface):
|
|
|
1607
1617
|
raise RunDBError("bad function build response")
|
|
1608
1618
|
|
|
1609
1619
|
if resp.headers:
|
|
1610
|
-
func.status.state = resp.headers.get("x-mlrun-function-status", "")
|
|
1611
1620
|
last_log_timestamp = float(
|
|
1612
1621
|
resp.headers.get("x-mlrun-last-timestamp", "0.0")
|
|
1613
1622
|
)
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
func.status.internal_invocation_urls = resp.headers.get(
|
|
1617
|
-
"x-mlrun-internal-invocation-urls", ""
|
|
1618
|
-
).split(",")
|
|
1619
|
-
func.status.external_invocation_urls = resp.headers.get(
|
|
1620
|
-
"x-mlrun-external-invocation-urls", ""
|
|
1621
|
-
).split(",")
|
|
1622
|
-
func.status.container_image = resp.headers.get(
|
|
1623
|
-
"x-mlrun-container-image", ""
|
|
1623
|
+
mlrun.runtimes.nuclio.function.enrich_nuclio_function_from_headers(
|
|
1624
|
+
func, resp.headers
|
|
1624
1625
|
)
|
|
1625
1626
|
|
|
1626
1627
|
text = ""
|
|
@@ -1678,16 +1679,8 @@ class HTTPRunDB(RunDBInterface):
|
|
|
1678
1679
|
resp.headers.get("x-mlrun-last-timestamp", "0.0")
|
|
1679
1680
|
)
|
|
1680
1681
|
if func.kind in mlrun.runtimes.RuntimeKinds.nuclio_runtimes():
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
func.status.internal_invocation_urls = resp.headers.get(
|
|
1684
|
-
"x-mlrun-internal-invocation-urls", ""
|
|
1685
|
-
).split(",")
|
|
1686
|
-
func.status.external_invocation_urls = resp.headers.get(
|
|
1687
|
-
"x-mlrun-external-invocation-urls", ""
|
|
1688
|
-
).split(",")
|
|
1689
|
-
func.status.container_image = resp.headers.get(
|
|
1690
|
-
"x-mlrun-container-image", ""
|
|
1682
|
+
mlrun.runtimes.nuclio.function.enrich_nuclio_function_from_headers(
|
|
1683
|
+
func, resp.headers
|
|
1691
1684
|
)
|
|
1692
1685
|
|
|
1693
1686
|
builder_pod = resp.headers.get("builder_pod", "")
|
|
@@ -3394,6 +3387,7 @@ class HTTPRunDB(RunDBInterface):
|
|
|
3394
3387
|
image: str = "mlrun/mlrun",
|
|
3395
3388
|
deploy_histogram_data_drift_app: bool = True,
|
|
3396
3389
|
rebuild_images: bool = False,
|
|
3390
|
+
fetch_credentials_from_sys_config: bool = False,
|
|
3397
3391
|
) -> None:
|
|
3398
3392
|
"""
|
|
3399
3393
|
Deploy model monitoring application controller, writer and stream functions.
|
|
@@ -3403,14 +3397,16 @@ class HTTPRunDB(RunDBInterface):
|
|
|
3403
3397
|
The stream function goal is to monitor the log of the data stream. It is triggered when a new log entry
|
|
3404
3398
|
is detected. It processes the new events into statistics that are then written to statistics databases.
|
|
3405
3399
|
|
|
3406
|
-
:param project:
|
|
3407
|
-
:param base_period:
|
|
3408
|
-
|
|
3409
|
-
:param image:
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
:param deploy_histogram_data_drift_app:
|
|
3413
|
-
:param rebuild_images:
|
|
3400
|
+
:param project: Project name.
|
|
3401
|
+
:param base_period: The time period in minutes in which the model monitoring controller
|
|
3402
|
+
function triggers. By default, the base period is 10 minutes.
|
|
3403
|
+
:param image: The image of the model monitoring controller, writer & monitoring
|
|
3404
|
+
stream functions, which are real time nuclio functions.
|
|
3405
|
+
By default, the image is mlrun/mlrun.
|
|
3406
|
+
:param deploy_histogram_data_drift_app: If true, deploy the default histogram-based data drift application.
|
|
3407
|
+
:param rebuild_images: If true, force rebuild of model monitoring infrastructure images.
|
|
3408
|
+
:param fetch_credentials_from_sys_config: If true, fetch the credentials from the system configuration.
|
|
3409
|
+
|
|
3414
3410
|
"""
|
|
3415
3411
|
self.api_call(
|
|
3416
3412
|
method=mlrun.common.types.HTTPMethod.POST,
|
|
@@ -3420,6 +3416,7 @@ class HTTPRunDB(RunDBInterface):
|
|
|
3420
3416
|
"image": image,
|
|
3421
3417
|
"deploy_histogram_data_drift_app": deploy_histogram_data_drift_app,
|
|
3422
3418
|
"rebuild_images": rebuild_images,
|
|
3419
|
+
"fetch_credentials_from_sys_config": fetch_credentials_from_sys_config,
|
|
3423
3420
|
},
|
|
3424
3421
|
)
|
|
3425
3422
|
|
|
@@ -3545,6 +3542,23 @@ class HTTPRunDB(RunDBInterface):
|
|
|
3545
3542
|
params={"image": image},
|
|
3546
3543
|
)
|
|
3547
3544
|
|
|
3545
|
+
def set_model_monitoring_credentials(
|
|
3546
|
+
self,
|
|
3547
|
+
project: str,
|
|
3548
|
+
credentials: dict[str, str],
|
|
3549
|
+
) -> None:
|
|
3550
|
+
"""
|
|
3551
|
+
Set the credentials for the model monitoring application.
|
|
3552
|
+
|
|
3553
|
+
:param project: Project name.
|
|
3554
|
+
:param credentials: Credentials to set.
|
|
3555
|
+
"""
|
|
3556
|
+
self.api_call(
|
|
3557
|
+
method=mlrun.common.types.HTTPMethod.POST,
|
|
3558
|
+
path=f"projects/{project}/model-monitoring/set-model-monitoring-credentials",
|
|
3559
|
+
params={**credentials},
|
|
3560
|
+
)
|
|
3561
|
+
|
|
3548
3562
|
def create_hub_source(
|
|
3549
3563
|
self, source: Union[dict, mlrun.common.schemas.IndexedHubSource]
|
|
3550
3564
|
):
|
mlrun/db/nopdb.py
CHANGED
|
@@ -147,6 +147,7 @@ class NopDB(RunDBInterface):
|
|
|
147
147
|
category: Union[str, mlrun.common.schemas.ArtifactCategories] = None,
|
|
148
148
|
tree: str = None,
|
|
149
149
|
format_: mlrun.common.formatters.ArtifactFormat = mlrun.common.formatters.ArtifactFormat.full,
|
|
150
|
+
limit: int = None,
|
|
150
151
|
):
|
|
151
152
|
pass
|
|
152
153
|
|
|
@@ -161,6 +162,7 @@ class NopDB(RunDBInterface):
|
|
|
161
162
|
mlrun.common.schemas.artifact.ArtifactsDeletionStrategies.metadata_only
|
|
162
163
|
),
|
|
163
164
|
secrets: dict = None,
|
|
165
|
+
iter=None,
|
|
164
166
|
):
|
|
165
167
|
pass
|
|
166
168
|
|
|
@@ -707,6 +709,7 @@ class NopDB(RunDBInterface):
|
|
|
707
709
|
image: str = "mlrun/mlrun",
|
|
708
710
|
deploy_histogram_data_drift_app: bool = True,
|
|
709
711
|
rebuild_images: bool = False,
|
|
712
|
+
fetch_credentials_from_sys_config: bool = False,
|
|
710
713
|
) -> None:
|
|
711
714
|
pass
|
|
712
715
|
|
|
@@ -729,7 +732,14 @@ class NopDB(RunDBInterface):
|
|
|
729
732
|
def deploy_histogram_data_drift_app(
|
|
730
733
|
self, project: str, image: str = "mlrun/mlrun"
|
|
731
734
|
) -> None:
|
|
732
|
-
|
|
735
|
+
pass
|
|
736
|
+
|
|
737
|
+
def set_model_monitoring_credentials(
|
|
738
|
+
self,
|
|
739
|
+
project: str,
|
|
740
|
+
credentials: dict[str, str],
|
|
741
|
+
) -> None:
|
|
742
|
+
pass
|
|
733
743
|
|
|
734
744
|
def generate_event(
|
|
735
745
|
self, name: str, event_data: Union[dict, mlrun.common.schemas.Event], project=""
|
mlrun/errors.py
CHANGED
|
@@ -205,6 +205,10 @@ class MLRunTimeoutError(MLRunHTTPStatusError, TimeoutError):
|
|
|
205
205
|
error_status_code = HTTPStatus.GATEWAY_TIMEOUT.value
|
|
206
206
|
|
|
207
207
|
|
|
208
|
+
class MLRunInvalidMMStoreType(MLRunHTTPStatusError, ValueError):
|
|
209
|
+
error_status_code = HTTPStatus.BAD_REQUEST.value
|
|
210
|
+
|
|
211
|
+
|
|
208
212
|
class MLRunRetryExhaustedError(Exception):
|
|
209
213
|
pass
|
|
210
214
|
|
mlrun/execution.py
CHANGED
|
@@ -34,13 +34,13 @@ from .features import Feature
|
|
|
34
34
|
from .model import HyperParamOptions
|
|
35
35
|
from .secrets import SecretsStore
|
|
36
36
|
from .utils import (
|
|
37
|
+
RunKeys,
|
|
37
38
|
dict_to_json,
|
|
38
39
|
dict_to_yaml,
|
|
39
40
|
get_in,
|
|
40
41
|
is_relative_path,
|
|
41
42
|
logger,
|
|
42
43
|
now_date,
|
|
43
|
-
run_keys,
|
|
44
44
|
to_date_str,
|
|
45
45
|
update_in,
|
|
46
46
|
)
|
|
@@ -85,6 +85,7 @@ class MLClientCtx:
|
|
|
85
85
|
|
|
86
86
|
self._labels = {}
|
|
87
87
|
self._annotations = {}
|
|
88
|
+
self._node_selector = {}
|
|
88
89
|
|
|
89
90
|
self._function = ""
|
|
90
91
|
self._parameters = {}
|
|
@@ -207,6 +208,11 @@ class MLClientCtx:
|
|
|
207
208
|
"""Dictionary with labels (read-only)"""
|
|
208
209
|
return deepcopy(self._labels)
|
|
209
210
|
|
|
211
|
+
@property
|
|
212
|
+
def node_selector(self):
|
|
213
|
+
"""Dictionary with node selectors (read-only)"""
|
|
214
|
+
return deepcopy(self._node_selector)
|
|
215
|
+
|
|
210
216
|
@property
|
|
211
217
|
def annotations(self):
|
|
212
218
|
"""Dictionary with annotations (read-only)"""
|
|
@@ -365,7 +371,7 @@ class MLClientCtx:
|
|
|
365
371
|
self._labels = meta.get("labels", self._labels)
|
|
366
372
|
spec = attrs.get("spec")
|
|
367
373
|
if spec:
|
|
368
|
-
self._secrets_manager = SecretsStore.from_list(spec.get(
|
|
374
|
+
self._secrets_manager = SecretsStore.from_list(spec.get(RunKeys.secrets))
|
|
369
375
|
self._log_level = spec.get("log_level", self._log_level)
|
|
370
376
|
self._function = spec.get("function", self._function)
|
|
371
377
|
self._parameters = spec.get("parameters", self._parameters)
|
|
@@ -383,13 +389,14 @@ class MLClientCtx:
|
|
|
383
389
|
self._allow_empty_resources = spec.get(
|
|
384
390
|
"allow_empty_resources", self._allow_empty_resources
|
|
385
391
|
)
|
|
386
|
-
self.artifact_path = spec.get(
|
|
387
|
-
self._in_path = spec.get(
|
|
388
|
-
inputs = spec.get(
|
|
392
|
+
self.artifact_path = spec.get(RunKeys.output_path, self.artifact_path)
|
|
393
|
+
self._in_path = spec.get(RunKeys.input_path, self._in_path)
|
|
394
|
+
inputs = spec.get(RunKeys.inputs)
|
|
389
395
|
self._notifications = spec.get("notifications", self._notifications)
|
|
390
396
|
self._state_thresholds = spec.get(
|
|
391
397
|
"state_thresholds", self._state_thresholds
|
|
392
398
|
)
|
|
399
|
+
self._node_selector = spec.get("node_selector", self._node_selector)
|
|
393
400
|
self._reset_on_run = spec.get("reset_on_run", self._reset_on_run)
|
|
394
401
|
|
|
395
402
|
self._init_dbs(rundb)
|
|
@@ -567,7 +574,7 @@ class MLClientCtx:
|
|
|
567
574
|
self._results["best_iteration"] = best
|
|
568
575
|
for k, v in get_in(task, ["status", "results"], {}).items():
|
|
569
576
|
self._results[k] = v
|
|
570
|
-
for artifact in get_in(task, ["status",
|
|
577
|
+
for artifact in get_in(task, ["status", RunKeys.artifacts], []):
|
|
571
578
|
self._artifacts_manager.artifacts[artifact["metadata"]["key"]] = (
|
|
572
579
|
artifact
|
|
573
580
|
)
|
|
@@ -939,10 +946,11 @@ class MLClientCtx:
|
|
|
939
946
|
"parameters": self._parameters,
|
|
940
947
|
"handler": self._handler,
|
|
941
948
|
"outputs": self._outputs,
|
|
942
|
-
|
|
943
|
-
|
|
949
|
+
RunKeys.output_path: self.artifact_path,
|
|
950
|
+
RunKeys.inputs: self._inputs,
|
|
944
951
|
"notifications": self._notifications,
|
|
945
952
|
"state_thresholds": self._state_thresholds,
|
|
953
|
+
"node_selector": self._node_selector,
|
|
946
954
|
},
|
|
947
955
|
"status": {
|
|
948
956
|
"results": self._results,
|
|
@@ -964,7 +972,7 @@ class MLClientCtx:
|
|
|
964
972
|
set_if_not_none(struct["status"], "commit", self._commit)
|
|
965
973
|
set_if_not_none(struct["status"], "iterations", self._iteration_results)
|
|
966
974
|
|
|
967
|
-
struct["status"][
|
|
975
|
+
struct["status"][RunKeys.artifacts] = self._artifacts_manager.artifact_list()
|
|
968
976
|
self._data_stores.to_dict(struct["spec"])
|
|
969
977
|
return struct
|
|
970
978
|
|
|
@@ -1058,7 +1066,7 @@ class MLClientCtx:
|
|
|
1058
1066
|
set_if_not_none(struct, "status.commit", self._commit)
|
|
1059
1067
|
set_if_not_none(struct, "status.iterations", self._iteration_results)
|
|
1060
1068
|
|
|
1061
|
-
struct[f"status.{
|
|
1069
|
+
struct[f"status.{RunKeys.artifacts}"] = self._artifacts_manager.artifact_list()
|
|
1062
1070
|
return struct
|
|
1063
1071
|
|
|
1064
1072
|
def _init_dbs(self, rundb):
|
|
@@ -45,6 +45,7 @@ def spark_df_to_pandas(spark_df):
|
|
|
45
45
|
),
|
|
46
46
|
)
|
|
47
47
|
type_conversion_dict[field.name] = "datetime64[ns]"
|
|
48
|
+
|
|
48
49
|
df = PandasConversionMixin.toPandas(spark_df)
|
|
49
50
|
if type_conversion_dict:
|
|
50
51
|
df = df.astype(type_conversion_dict)
|
|
@@ -252,7 +253,7 @@ class SparkFeatureMerger(BaseMerger):
|
|
|
252
253
|
)
|
|
253
254
|
source_kind = target.kind
|
|
254
255
|
source_path = target.get_target_path()
|
|
255
|
-
|
|
256
|
+
source_kwargs = target.source_spark_attributes
|
|
256
257
|
# handling case where there are multiple feature sets and user creates vector where
|
|
257
258
|
# entity_timestamp_column is from a specific feature set (can't be entity timestamp)
|
|
258
259
|
source_driver = mlrun.datastore.sources.source_kind_to_driver[source_kind]
|
mlrun/launcher/local.py
CHANGED
|
@@ -72,9 +72,9 @@ class ClientLocalLauncher(launcher.ClientBaseLauncher):
|
|
|
72
72
|
reset_on_run: Optional[bool] = None,
|
|
73
73
|
) -> "mlrun.run.RunObject":
|
|
74
74
|
# do not allow local function to be scheduled
|
|
75
|
-
if
|
|
75
|
+
if schedule is not None:
|
|
76
76
|
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
77
|
-
"
|
|
77
|
+
f"Unexpected {schedule=} parameter for local function execution"
|
|
78
78
|
)
|
|
79
79
|
|
|
80
80
|
self.enrich_runtime(runtime, project)
|
mlrun/model.py
CHANGED
|
@@ -732,6 +732,34 @@ class Notification(ModelObj):
|
|
|
732
732
|
"Notification params size exceeds max size of 1 MB"
|
|
733
733
|
)
|
|
734
734
|
|
|
735
|
+
def validate_notification_params(self):
|
|
736
|
+
notification_class = mlrun.utils.notifications.NotificationTypes(
|
|
737
|
+
self.kind
|
|
738
|
+
).get_notification()
|
|
739
|
+
|
|
740
|
+
secret_params = self.secret_params or {}
|
|
741
|
+
params = self.params or {}
|
|
742
|
+
|
|
743
|
+
# if the secret_params are already masked - no need to validate
|
|
744
|
+
params_secret = secret_params.get("secret", "")
|
|
745
|
+
if params_secret:
|
|
746
|
+
if len(secret_params) > 1:
|
|
747
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
748
|
+
"When the 'secret' key is present, 'secret_params' should not contain any other keys."
|
|
749
|
+
)
|
|
750
|
+
return
|
|
751
|
+
|
|
752
|
+
if not secret_params and not params:
|
|
753
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
754
|
+
"Both 'secret_params' and 'params' are empty, at least one must be defined."
|
|
755
|
+
)
|
|
756
|
+
if secret_params and params and secret_params != params:
|
|
757
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
758
|
+
"Both 'secret_params' and 'params' are defined but they contain different values"
|
|
759
|
+
)
|
|
760
|
+
|
|
761
|
+
notification_class.validate_params(secret_params or params)
|
|
762
|
+
|
|
735
763
|
@staticmethod
|
|
736
764
|
def validate_notification_uniqueness(notifications: list["Notification"]):
|
|
737
765
|
"""Validate that all notifications in the list are unique by name"""
|
|
@@ -873,6 +901,7 @@ class RunSpec(ModelObj):
|
|
|
873
901
|
notifications=None,
|
|
874
902
|
state_thresholds=None,
|
|
875
903
|
reset_on_run=None,
|
|
904
|
+
node_selector=None,
|
|
876
905
|
):
|
|
877
906
|
# A dictionary of parsing configurations that will be read from the inputs the user set. The keys are the inputs
|
|
878
907
|
# keys (parameter names) and the values are the type hint given in the input keys after the colon.
|
|
@@ -910,6 +939,7 @@ class RunSpec(ModelObj):
|
|
|
910
939
|
self._notifications = notifications or []
|
|
911
940
|
self.state_thresholds = state_thresholds or {}
|
|
912
941
|
self.reset_on_run = reset_on_run
|
|
942
|
+
self.node_selector = node_selector or {}
|
|
913
943
|
|
|
914
944
|
def _serialize_field(
|
|
915
945
|
self, struct: dict, field_name: str = None, strip: bool = False
|