mlrun 1.7.0rc43__py3-none-any.whl → 1.7.0rc55__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 +4 -2
- mlrun/artifacts/manager.py +3 -1
- mlrun/common/formatters/__init__.py +1 -0
- mlrun/{model_monitoring/application.py → common/formatters/feature_set.py} +20 -6
- mlrun/common/formatters/run.py +3 -0
- mlrun/common/schemas/__init__.py +1 -0
- mlrun/common/schemas/alert.py +11 -11
- mlrun/common/schemas/auth.py +5 -0
- mlrun/common/schemas/client_spec.py +0 -1
- mlrun/common/schemas/model_monitoring/__init__.py +2 -1
- mlrun/common/schemas/model_monitoring/constants.py +23 -9
- mlrun/common/schemas/model_monitoring/model_endpoints.py +24 -47
- mlrun/common/schemas/notification.py +12 -2
- mlrun/common/schemas/workflow.py +10 -2
- mlrun/config.py +28 -21
- mlrun/data_types/data_types.py +6 -1
- mlrun/datastore/base.py +4 -4
- mlrun/datastore/s3.py +12 -9
- mlrun/datastore/storeytargets.py +9 -6
- mlrun/db/base.py +3 -0
- mlrun/db/httpdb.py +28 -16
- mlrun/db/nopdb.py +24 -4
- mlrun/errors.py +7 -1
- mlrun/execution.py +40 -7
- mlrun/feature_store/api.py +1 -0
- mlrun/feature_store/retrieval/spark_merger.py +7 -7
- mlrun/frameworks/_common/plan.py +3 -3
- mlrun/frameworks/_ml_common/plan.py +1 -1
- mlrun/frameworks/parallel_coordinates.py +2 -3
- mlrun/launcher/client.py +6 -6
- mlrun/model.py +29 -0
- mlrun/model_monitoring/api.py +1 -12
- mlrun/model_monitoring/applications/__init__.py +1 -2
- mlrun/model_monitoring/applications/_application_steps.py +5 -1
- mlrun/model_monitoring/applications/base.py +2 -182
- mlrun/model_monitoring/applications/context.py +2 -9
- mlrun/model_monitoring/applications/evidently_base.py +0 -74
- mlrun/model_monitoring/applications/histogram_data_drift.py +2 -2
- mlrun/model_monitoring/applications/results.py +4 -4
- mlrun/model_monitoring/controller.py +46 -209
- mlrun/model_monitoring/db/stores/base/store.py +1 -0
- mlrun/model_monitoring/db/stores/sqldb/sql_store.py +15 -1
- mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +12 -0
- mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +17 -16
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +49 -39
- mlrun/model_monitoring/helpers.py +13 -15
- mlrun/model_monitoring/writer.py +3 -1
- mlrun/projects/operations.py +11 -8
- mlrun/projects/pipelines.py +35 -16
- mlrun/projects/project.py +52 -24
- mlrun/render.py +3 -3
- mlrun/runtimes/daskjob.py +1 -1
- mlrun/runtimes/kubejob.py +6 -6
- mlrun/runtimes/nuclio/api_gateway.py +12 -0
- mlrun/runtimes/nuclio/application/application.py +3 -3
- mlrun/runtimes/nuclio/function.py +41 -0
- mlrun/runtimes/nuclio/serving.py +2 -2
- mlrun/runtimes/pod.py +19 -13
- mlrun/serving/server.py +2 -0
- mlrun/utils/helpers.py +62 -16
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.7.0rc43.dist-info → mlrun-1.7.0rc55.dist-info}/METADATA +126 -44
- {mlrun-1.7.0rc43.dist-info → mlrun-1.7.0rc55.dist-info}/RECORD +67 -68
- {mlrun-1.7.0rc43.dist-info → mlrun-1.7.0rc55.dist-info}/WHEEL +1 -1
- mlrun/model_monitoring/evidently_application.py +0 -20
- {mlrun-1.7.0rc43.dist-info → mlrun-1.7.0rc55.dist-info}/LICENSE +0 -0
- {mlrun-1.7.0rc43.dist-info → mlrun-1.7.0rc55.dist-info}/entry_points.txt +0 -0
- {mlrun-1.7.0rc43.dist-info → mlrun-1.7.0rc55.dist-info}/top_level.txt +0 -0
mlrun/__main__.py
CHANGED
|
@@ -734,9 +734,11 @@ def get(kind, name, selector, namespace, uid, project, tag, db, extra_args):
|
|
|
734
734
|
if db:
|
|
735
735
|
mlconf.dbpath = db
|
|
736
736
|
if not project:
|
|
737
|
-
|
|
737
|
+
logger.warning(
|
|
738
|
+
"Project parameter was not specified. Defaulting to 'default' project"
|
|
739
|
+
)
|
|
738
740
|
if kind.startswith("po"):
|
|
739
|
-
|
|
741
|
+
logger.warning("Unsupported, use 'get runtimes' instead")
|
|
740
742
|
return
|
|
741
743
|
|
|
742
744
|
elif kind.startswith("runtime"):
|
mlrun/artifacts/manager.py
CHANGED
|
@@ -200,7 +200,9 @@ class ArtifactManager:
|
|
|
200
200
|
:param artifact_path: The path to store the artifact.
|
|
201
201
|
If not provided, the artifact will be stored in the default artifact path.
|
|
202
202
|
:param format: The format of the artifact. (e.g. csv, json, html, etc.)
|
|
203
|
-
:param upload: Whether to upload the artifact
|
|
203
|
+
:param upload: Whether to upload the artifact to the datastore. If not provided, and the
|
|
204
|
+
`local_path` is not a directory, upload occurs by default. Directories are uploaded only when this
|
|
205
|
+
flag is explicitly set to `True`.
|
|
204
206
|
:param labels: Labels to add to the artifact.
|
|
205
207
|
:param db_key: The key to use when logging the artifact to the DB.
|
|
206
208
|
If not provided, will generate a key based on the producer name and the artifact key.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright
|
|
1
|
+
# Copyright 2024 Iguazio
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -11,9 +11,23 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
+
#
|
|
15
|
+
|
|
16
|
+
import typing
|
|
17
|
+
|
|
18
|
+
import mlrun.common.types
|
|
19
|
+
|
|
20
|
+
from .base import ObjectFormat
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class FeatureSetFormat(ObjectFormat, mlrun.common.types.StrEnum):
|
|
24
|
+
minimal = "minimal"
|
|
14
25
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
26
|
+
@staticmethod
|
|
27
|
+
def format_method(_format: str) -> typing.Optional[typing.Callable]:
|
|
28
|
+
return {
|
|
29
|
+
FeatureSetFormat.full: None,
|
|
30
|
+
FeatureSetFormat.minimal: FeatureSetFormat.filter_obj_method(
|
|
31
|
+
["kind", "metadata", "spec", "status.state"]
|
|
32
|
+
),
|
|
33
|
+
}[_format]
|
mlrun/common/formatters/run.py
CHANGED
|
@@ -22,5 +22,8 @@ class RunFormat(ObjectFormat, mlrun.common.types.StrEnum):
|
|
|
22
22
|
# No enrichment, data is pulled as-is from the database.
|
|
23
23
|
standard = "standard"
|
|
24
24
|
|
|
25
|
+
# Enrich run with full notifications since the notification params are subtracted from the run body.
|
|
26
|
+
notifications = "notifications"
|
|
27
|
+
|
|
25
28
|
# Performs run enrichment, including the run's artifacts. Only available for the `get` run API.
|
|
26
29
|
full = "full"
|
mlrun/common/schemas/__init__.py
CHANGED
mlrun/common/schemas/alert.py
CHANGED
|
@@ -34,17 +34,17 @@ class EventEntities(pydantic.BaseModel):
|
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
class EventKind(StrEnum):
|
|
37
|
-
DATA_DRIFT_DETECTED = "
|
|
38
|
-
DATA_DRIFT_SUSPECTED = "
|
|
39
|
-
CONCEPT_DRIFT_DETECTED = "
|
|
40
|
-
CONCEPT_DRIFT_SUSPECTED = "
|
|
41
|
-
MODEL_PERFORMANCE_DETECTED = "
|
|
42
|
-
MODEL_PERFORMANCE_SUSPECTED = "
|
|
43
|
-
SYSTEM_PERFORMANCE_DETECTED = "
|
|
44
|
-
SYSTEM_PERFORMANCE_SUSPECTED = "
|
|
45
|
-
MM_APP_ANOMALY_DETECTED = "
|
|
46
|
-
MM_APP_ANOMALY_SUSPECTED = "
|
|
47
|
-
MM_APP_FAILED = "
|
|
37
|
+
DATA_DRIFT_DETECTED = "data-drift-detected"
|
|
38
|
+
DATA_DRIFT_SUSPECTED = "data-drift-suspected"
|
|
39
|
+
CONCEPT_DRIFT_DETECTED = "concept-drift-detected"
|
|
40
|
+
CONCEPT_DRIFT_SUSPECTED = "concept-drift-suspected"
|
|
41
|
+
MODEL_PERFORMANCE_DETECTED = "model-performance-detected"
|
|
42
|
+
MODEL_PERFORMANCE_SUSPECTED = "model-performance-suspected"
|
|
43
|
+
SYSTEM_PERFORMANCE_DETECTED = "system-performance-detected"
|
|
44
|
+
SYSTEM_PERFORMANCE_SUSPECTED = "system-performance-suspected"
|
|
45
|
+
MM_APP_ANOMALY_DETECTED = "mm-app-anomaly-detected"
|
|
46
|
+
MM_APP_ANOMALY_SUSPECTED = "mm-app-anomaly-suspected"
|
|
47
|
+
MM_APP_FAILED = "mm-app-failed"
|
|
48
48
|
FAILED = "failed"
|
|
49
49
|
|
|
50
50
|
|
mlrun/common/schemas/auth.py
CHANGED
|
@@ -63,6 +63,7 @@ class AuthorizationResourceTypes(mlrun.common.types.StrEnum):
|
|
|
63
63
|
event = "event"
|
|
64
64
|
datastore_profile = "datastore-profile"
|
|
65
65
|
api_gateway = "api-gateway"
|
|
66
|
+
project_summaries = "project-summaries"
|
|
66
67
|
|
|
67
68
|
def to_resource_string(
|
|
68
69
|
self,
|
|
@@ -72,6 +73,7 @@ class AuthorizationResourceTypes(mlrun.common.types.StrEnum):
|
|
|
72
73
|
return {
|
|
73
74
|
# project is the resource itself, so no need for both resource_name and project_name
|
|
74
75
|
AuthorizationResourceTypes.project: "/projects/{project_name}",
|
|
76
|
+
AuthorizationResourceTypes.project_summaries: "/projects/{project_name}/project-summaries/{resource_name}",
|
|
75
77
|
AuthorizationResourceTypes.function: "/projects/{project_name}/functions/{resource_name}",
|
|
76
78
|
AuthorizationResourceTypes.artifact: "/projects/{project_name}/artifacts/{resource_name}",
|
|
77
79
|
AuthorizationResourceTypes.project_background_task: (
|
|
@@ -139,6 +141,9 @@ class AuthInfo(pydantic.BaseModel):
|
|
|
139
141
|
member_ids.extend(self.user_group_ids)
|
|
140
142
|
return member_ids
|
|
141
143
|
|
|
144
|
+
def get_session(self) -> str:
|
|
145
|
+
return self.data_session or self.session
|
|
146
|
+
|
|
142
147
|
|
|
143
148
|
class Credentials(pydantic.BaseModel):
|
|
144
149
|
access_key: typing.Optional[str]
|
|
@@ -57,7 +57,6 @@ class ClientSpec(pydantic.BaseModel):
|
|
|
57
57
|
redis_url: typing.Optional[str]
|
|
58
58
|
redis_type: typing.Optional[str]
|
|
59
59
|
sql_url: typing.Optional[str]
|
|
60
|
-
model_endpoint_monitoring_store_type: typing.Optional[str]
|
|
61
60
|
model_endpoint_monitoring_endpoint_store_connection: typing.Optional[str]
|
|
62
61
|
model_monitoring_tsdb_connection: typing.Optional[str]
|
|
63
62
|
ce: typing.Optional[dict]
|
|
@@ -25,6 +25,7 @@ from .constants import (
|
|
|
25
25
|
FileTargetKind,
|
|
26
26
|
FunctionURI,
|
|
27
27
|
MetricData,
|
|
28
|
+
ModelEndpointMonitoringMetricType,
|
|
28
29
|
ModelEndpointTarget,
|
|
29
30
|
ModelEndpointTargetSchemas,
|
|
30
31
|
ModelMonitoringMode,
|
|
@@ -34,6 +35,7 @@ from .constants import (
|
|
|
34
35
|
ProjectSecretKeys,
|
|
35
36
|
ResultData,
|
|
36
37
|
ResultKindApp,
|
|
38
|
+
ResultStatusApp,
|
|
37
39
|
SchedulingKeys,
|
|
38
40
|
SpecialApps,
|
|
39
41
|
TDEngineSuperTables,
|
|
@@ -60,7 +62,6 @@ from .model_endpoints import (
|
|
|
60
62
|
ModelEndpointMetadata,
|
|
61
63
|
ModelEndpointMonitoringMetric,
|
|
62
64
|
ModelEndpointMonitoringMetricNoData,
|
|
63
|
-
ModelEndpointMonitoringMetricType,
|
|
64
65
|
ModelEndpointMonitoringMetricValues,
|
|
65
66
|
ModelEndpointMonitoringResultValues,
|
|
66
67
|
ModelEndpointSpec,
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
import hashlib
|
|
16
|
-
|
|
16
|
+
import re
|
|
17
|
+
from dataclasses import dataclass, field
|
|
17
18
|
from enum import Enum, IntEnum
|
|
18
19
|
from typing import Optional
|
|
19
20
|
|
|
@@ -104,15 +105,8 @@ class ApplicationEvent:
|
|
|
104
105
|
APPLICATION_NAME = "application_name"
|
|
105
106
|
START_INFER_TIME = "start_infer_time"
|
|
106
107
|
END_INFER_TIME = "end_infer_time"
|
|
107
|
-
LAST_REQUEST = "last_request"
|
|
108
108
|
ENDPOINT_ID = "endpoint_id"
|
|
109
109
|
OUTPUT_STREAM_URI = "output_stream_uri"
|
|
110
|
-
MLRUN_CONTEXT = "mlrun_context"
|
|
111
|
-
|
|
112
|
-
# Deprecated fields - TODO : delete in 1.9.0 (V1 app deprecation)
|
|
113
|
-
SAMPLE_PARQUET_PATH = "sample_parquet_path"
|
|
114
|
-
CURRENT_STATS = "current_stats"
|
|
115
|
-
FEATURE_STATS = "feature_stats"
|
|
116
110
|
|
|
117
111
|
|
|
118
112
|
class WriterEvent(MonitoringStrEnum):
|
|
@@ -295,7 +289,7 @@ class EndpointUID:
|
|
|
295
289
|
function_hash_key: str
|
|
296
290
|
model: str
|
|
297
291
|
model_version: str
|
|
298
|
-
uid:
|
|
292
|
+
uid: str = field(init=False)
|
|
299
293
|
|
|
300
294
|
def __post_init__(self):
|
|
301
295
|
function_ref = (
|
|
@@ -372,3 +366,23 @@ _RESERVED_FUNCTION_NAMES = MonitoringFunctionNames.list() + [SpecialApps.MLRUN_I
|
|
|
372
366
|
|
|
373
367
|
|
|
374
368
|
V3IO_MODEL_MONITORING_DB = "v3io"
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
class ModelEndpointMonitoringMetricType(StrEnum):
|
|
372
|
+
RESULT = "result"
|
|
373
|
+
METRIC = "metric"
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
_FQN_PART_PATTERN = r"[a-zA-Z0-9_-]+"
|
|
377
|
+
FQN_PATTERN = (
|
|
378
|
+
rf"^(?P<project>{_FQN_PART_PATTERN})\."
|
|
379
|
+
rf"(?P<app>{_FQN_PART_PATTERN})\."
|
|
380
|
+
rf"(?P<type>{ModelEndpointMonitoringMetricType.RESULT}|{ModelEndpointMonitoringMetricType.METRIC})\."
|
|
381
|
+
rf"(?P<name>{_FQN_PART_PATTERN})$"
|
|
382
|
+
)
|
|
383
|
+
FQN_REGEX = re.compile(FQN_PATTERN)
|
|
384
|
+
|
|
385
|
+
# refer to `mlrun.utils.regex.project_name`
|
|
386
|
+
PROJECT_PATTERN = r"^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$"
|
|
387
|
+
|
|
388
|
+
MODEL_ENDPOINT_ID_PATTERN = r"^[a-zA-Z0-9_-]+$"
|
|
@@ -14,26 +14,32 @@
|
|
|
14
14
|
|
|
15
15
|
import enum
|
|
16
16
|
import json
|
|
17
|
-
import re
|
|
18
17
|
from datetime import datetime
|
|
19
|
-
from typing import Any, NamedTuple, Optional
|
|
18
|
+
from typing import Any, NamedTuple, Optional, TypeVar
|
|
20
19
|
|
|
21
|
-
from pydantic import BaseModel, Field, validator
|
|
22
|
-
from pydantic.main import Extra
|
|
20
|
+
from pydantic import BaseModel, Extra, Field, constr, validator
|
|
23
21
|
|
|
24
|
-
import mlrun.
|
|
22
|
+
# TODO: remove the unused import below after `mlrun.datastore` and `mlrun.utils` usage is removed.
|
|
23
|
+
# At the moment `make lint` fails if this is removed.
|
|
24
|
+
import mlrun.common.model_monitoring
|
|
25
25
|
|
|
26
26
|
from ..object import ObjectKind, ObjectSpec, ObjectStatus
|
|
27
27
|
from .constants import (
|
|
28
|
+
FQN_REGEX,
|
|
29
|
+
MODEL_ENDPOINT_ID_PATTERN,
|
|
30
|
+
PROJECT_PATTERN,
|
|
28
31
|
EndpointType,
|
|
29
32
|
EventFieldType,
|
|
30
33
|
EventKeyMetrics,
|
|
31
34
|
EventLiveStats,
|
|
35
|
+
ModelEndpointMonitoringMetricType,
|
|
32
36
|
ModelMonitoringMode,
|
|
33
37
|
ResultKindApp,
|
|
34
38
|
ResultStatusApp,
|
|
35
39
|
)
|
|
36
40
|
|
|
41
|
+
Model = TypeVar("Model", bound=BaseModel)
|
|
42
|
+
|
|
37
43
|
|
|
38
44
|
class ModelMonitoringStoreKinds:
|
|
39
45
|
# TODO: do changes in examples & demos In 1.5.0 remove
|
|
@@ -42,9 +48,9 @@ class ModelMonitoringStoreKinds:
|
|
|
42
48
|
|
|
43
49
|
|
|
44
50
|
class ModelEndpointMetadata(BaseModel):
|
|
45
|
-
project:
|
|
51
|
+
project: constr(regex=PROJECT_PATTERN)
|
|
52
|
+
uid: constr(regex=MODEL_ENDPOINT_ID_PATTERN)
|
|
46
53
|
labels: Optional[dict] = {}
|
|
47
|
-
uid: Optional[str] = ""
|
|
48
54
|
|
|
49
55
|
class Config:
|
|
50
56
|
extra = Extra.allow
|
|
@@ -57,12 +63,11 @@ class ModelEndpointMetadata(BaseModel):
|
|
|
57
63
|
:param json_parse_values: List of dictionary keys with a JSON string value that will be parsed into a
|
|
58
64
|
dictionary using json.loads().
|
|
59
65
|
"""
|
|
60
|
-
new_object = cls()
|
|
61
66
|
if json_parse_values is None:
|
|
62
67
|
json_parse_values = [EventFieldType.LABELS]
|
|
63
68
|
|
|
64
69
|
return _mapping_attributes(
|
|
65
|
-
|
|
70
|
+
model_class=cls,
|
|
66
71
|
flattened_dictionary=endpoint_dict,
|
|
67
72
|
json_parse_values=json_parse_values,
|
|
68
73
|
)
|
|
@@ -89,7 +94,6 @@ class ModelEndpointSpec(ObjectSpec):
|
|
|
89
94
|
:param json_parse_values: List of dictionary keys with a JSON string value that will be parsed into a
|
|
90
95
|
dictionary using json.loads().
|
|
91
96
|
"""
|
|
92
|
-
new_object = cls()
|
|
93
97
|
if json_parse_values is None:
|
|
94
98
|
json_parse_values = [
|
|
95
99
|
EventFieldType.FEATURE_NAMES,
|
|
@@ -97,7 +101,7 @@ class ModelEndpointSpec(ObjectSpec):
|
|
|
97
101
|
EventFieldType.MONITOR_CONFIGURATION,
|
|
98
102
|
]
|
|
99
103
|
return _mapping_attributes(
|
|
100
|
-
|
|
104
|
+
model_class=cls,
|
|
101
105
|
flattened_dictionary=endpoint_dict,
|
|
102
106
|
json_parse_values=json_parse_values,
|
|
103
107
|
)
|
|
@@ -191,7 +195,6 @@ class ModelEndpointStatus(ObjectStatus):
|
|
|
191
195
|
:param json_parse_values: List of dictionary keys with a JSON string value that will be parsed into a
|
|
192
196
|
dictionary using json.loads().
|
|
193
197
|
"""
|
|
194
|
-
new_object = cls()
|
|
195
198
|
if json_parse_values is None:
|
|
196
199
|
json_parse_values = [
|
|
197
200
|
EventFieldType.FEATURE_STATS,
|
|
@@ -203,7 +206,7 @@ class ModelEndpointStatus(ObjectStatus):
|
|
|
203
206
|
EventFieldType.ENDPOINT_TYPE,
|
|
204
207
|
]
|
|
205
208
|
return _mapping_attributes(
|
|
206
|
-
|
|
209
|
+
model_class=cls,
|
|
207
210
|
flattened_dictionary=endpoint_dict,
|
|
208
211
|
json_parse_values=json_parse_values,
|
|
209
212
|
)
|
|
@@ -211,22 +214,13 @@ class ModelEndpointStatus(ObjectStatus):
|
|
|
211
214
|
|
|
212
215
|
class ModelEndpoint(BaseModel):
|
|
213
216
|
kind: ObjectKind = Field(ObjectKind.model_endpoint, const=True)
|
|
214
|
-
metadata: ModelEndpointMetadata
|
|
217
|
+
metadata: ModelEndpointMetadata
|
|
215
218
|
spec: ModelEndpointSpec = ModelEndpointSpec()
|
|
216
219
|
status: ModelEndpointStatus = ModelEndpointStatus()
|
|
217
220
|
|
|
218
221
|
class Config:
|
|
219
222
|
extra = Extra.allow
|
|
220
223
|
|
|
221
|
-
def __init__(self, **data: Any):
|
|
222
|
-
super().__init__(**data)
|
|
223
|
-
if self.metadata.uid is None:
|
|
224
|
-
uid = mlrun.common.model_monitoring.create_model_endpoint_uid(
|
|
225
|
-
function_uri=self.spec.function_uri,
|
|
226
|
-
versioned_model=self.spec.model,
|
|
227
|
-
)
|
|
228
|
-
self.metadata.uid = str(uid)
|
|
229
|
-
|
|
230
224
|
def flat_dict(self):
|
|
231
225
|
"""Generate a flattened `ModelEndpoint` dictionary. The flattened dictionary result is important for storing
|
|
232
226
|
the model endpoint object in the database.
|
|
@@ -267,7 +261,7 @@ class ModelEndpoint(BaseModel):
|
|
|
267
261
|
return flatten_dict
|
|
268
262
|
|
|
269
263
|
@classmethod
|
|
270
|
-
def from_flat_dict(cls, endpoint_dict: dict):
|
|
264
|
+
def from_flat_dict(cls, endpoint_dict: dict) -> "ModelEndpoint":
|
|
271
265
|
"""Create a `ModelEndpoint` object from an endpoint flattened dictionary. Because the provided dictionary
|
|
272
266
|
is flattened, we pass it as is to the subclasses without splitting the keys into spec, metadata, and status.
|
|
273
267
|
|
|
@@ -285,11 +279,6 @@ class ModelEndpointList(BaseModel):
|
|
|
285
279
|
endpoints: list[ModelEndpoint] = []
|
|
286
280
|
|
|
287
281
|
|
|
288
|
-
class ModelEndpointMonitoringMetricType(mlrun.common.types.StrEnum):
|
|
289
|
-
RESULT = "result"
|
|
290
|
-
METRIC = "metric"
|
|
291
|
-
|
|
292
|
-
|
|
293
282
|
class ModelEndpointMonitoringMetric(BaseModel):
|
|
294
283
|
project: str
|
|
295
284
|
app: str
|
|
@@ -308,18 +297,8 @@ def _compose_full_name(
|
|
|
308
297
|
return ".".join([project, app, type, name])
|
|
309
298
|
|
|
310
299
|
|
|
311
|
-
_FQN_PART_PATTERN = r"[a-zA-Z0-9_-]+"
|
|
312
|
-
_FQN_PATTERN = (
|
|
313
|
-
rf"^(?P<project>{_FQN_PART_PATTERN})\."
|
|
314
|
-
rf"(?P<app>{_FQN_PART_PATTERN})\."
|
|
315
|
-
rf"(?P<type>{ModelEndpointMonitoringMetricType.RESULT}|{ModelEndpointMonitoringMetricType.METRIC})\."
|
|
316
|
-
rf"(?P<name>{_FQN_PART_PATTERN})$"
|
|
317
|
-
)
|
|
318
|
-
_FQN_REGEX = re.compile(_FQN_PATTERN)
|
|
319
|
-
|
|
320
|
-
|
|
321
300
|
def _parse_metric_fqn_to_monitoring_metric(fqn: str) -> ModelEndpointMonitoringMetric:
|
|
322
|
-
match =
|
|
301
|
+
match = FQN_REGEX.fullmatch(fqn)
|
|
323
302
|
if match is None:
|
|
324
303
|
raise ValueError("The fully qualified name is not in the expected format")
|
|
325
304
|
return ModelEndpointMonitoringMetric.parse_obj(
|
|
@@ -364,20 +343,18 @@ class ModelEndpointMonitoringMetricNoData(_ModelEndpointMonitoringMetricValuesBa
|
|
|
364
343
|
|
|
365
344
|
|
|
366
345
|
def _mapping_attributes(
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
json_parse_values: list = None,
|
|
370
|
-
):
|
|
346
|
+
model_class: type[Model], flattened_dictionary: dict, json_parse_values: list
|
|
347
|
+
) -> Model:
|
|
371
348
|
"""Generate a `BaseModel` object with the provided dictionary attributes.
|
|
372
349
|
|
|
373
|
-
:param
|
|
350
|
+
:param model_class: `BaseModel` class (e.g. `ModelEndpointMetadata`).
|
|
374
351
|
:param flattened_dictionary: Flattened dictionary that contains the model endpoint attributes.
|
|
375
352
|
:param json_parse_values: List of dictionary keys with a JSON string value that will be parsed into a
|
|
376
353
|
dictionary using json.loads().
|
|
377
354
|
"""
|
|
378
355
|
# Get the fields of the provided base model object. These fields will be used to filter to relevent keys
|
|
379
356
|
# from the flattened dictionary.
|
|
380
|
-
wanted_keys =
|
|
357
|
+
wanted_keys = model_class.__fields__.keys()
|
|
381
358
|
|
|
382
359
|
# Generate a filtered flattened dictionary that will be parsed into the BaseModel object
|
|
383
360
|
dict_to_parse = {}
|
|
@@ -391,7 +368,7 @@ def _mapping_attributes(
|
|
|
391
368
|
else:
|
|
392
369
|
dict_to_parse[field_key] = flattened_dictionary[field_key]
|
|
393
370
|
|
|
394
|
-
return
|
|
371
|
+
return model_class.parse_obj(dict_to_parse)
|
|
395
372
|
|
|
396
373
|
|
|
397
374
|
def _json_loads_if_not_none(field: Any) -> Any:
|
|
@@ -50,9 +50,19 @@ class NotificationKind(mlrun.common.types.StrEnum):
|
|
|
50
50
|
**url** - The webhook url to which to send the notification.\n
|
|
51
51
|
**method** - The http method to use when sending the notification (GET, POST, PUT, etc…).\n
|
|
52
52
|
**headers** - (dict) The http headers to send with the notification.\n
|
|
53
|
-
**override_body** -
|
|
53
|
+
**override_body** -
|
|
54
|
+
(dict) The body to send with the notification. If not specified, the
|
|
55
|
+
default body will be a dictionary containing `name`, `message`, `severity`, and a `runs` list of the
|
|
56
|
+
completed runs. You can also add the run's details.\n
|
|
57
|
+
Example::
|
|
58
|
+
|
|
59
|
+
"override_body": {"message":"Run Completed {{ runs }}"
|
|
60
|
+
# Results would look like:
|
|
61
|
+
"message": "Run Completed [{'project': 'my-project', 'name': 'my-function', 'host': <run-host>,
|
|
62
|
+
'status': {'state': 'completed', 'results': <run-results>}}]"
|
|
54
63
|
**verify_ssl** -
|
|
55
|
-
(bool) Whether SSL certificates are validated during HTTP requests or not
|
|
64
|
+
(bool) Whether SSL certificates are validated during HTTP requests or not.
|
|
65
|
+
The default is set to True.\n
|
|
56
66
|
"""
|
|
57
67
|
|
|
58
68
|
|
mlrun/common/schemas/workflow.py
CHANGED
|
@@ -16,8 +16,9 @@ import typing
|
|
|
16
16
|
|
|
17
17
|
import pydantic
|
|
18
18
|
|
|
19
|
-
from .notification import Notification
|
|
20
|
-
from .schedule import ScheduleCronTrigger
|
|
19
|
+
from mlrun.common.schemas.notification import Notification
|
|
20
|
+
from mlrun.common.schemas.schedule import ScheduleCronTrigger
|
|
21
|
+
from mlrun.common.types import StrEnum
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
class WorkflowSpec(pydantic.BaseModel):
|
|
@@ -32,6 +33,7 @@ class WorkflowSpec(pydantic.BaseModel):
|
|
|
32
33
|
schedule: typing.Union[str, ScheduleCronTrigger] = None
|
|
33
34
|
run_local: typing.Optional[bool] = None
|
|
34
35
|
image: typing.Optional[str] = None
|
|
36
|
+
workflow_runner_node_selector: typing.Optional[dict[str, str]] = None
|
|
35
37
|
|
|
36
38
|
|
|
37
39
|
class WorkflowRequest(pydantic.BaseModel):
|
|
@@ -54,3 +56,9 @@ class WorkflowResponse(pydantic.BaseModel):
|
|
|
54
56
|
|
|
55
57
|
class GetWorkflowResponse(pydantic.BaseModel):
|
|
56
58
|
workflow_id: str = None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class EngineType(StrEnum):
|
|
62
|
+
LOCAL = "local"
|
|
63
|
+
REMOTE = "remote"
|
|
64
|
+
KFP = "kfp"
|
mlrun/config.py
CHANGED
|
@@ -539,7 +539,6 @@ default_config = {
|
|
|
539
539
|
"store_prefixes": {
|
|
540
540
|
"default": "v3io:///users/pipelines/{project}/model-endpoints/{kind}",
|
|
541
541
|
"user_space": "v3io:///projects/{project}/model-endpoints/{kind}",
|
|
542
|
-
"stream": "", # TODO: Delete in 1.9.0
|
|
543
542
|
"monitoring_application": "v3io:///users/pipelines/{project}/monitoring-apps/",
|
|
544
543
|
},
|
|
545
544
|
# Offline storage path can be either relative or a full path. This path is used for general offline data
|
|
@@ -552,7 +551,6 @@ default_config = {
|
|
|
552
551
|
"parquet_batching_max_events": 10_000,
|
|
553
552
|
"parquet_batching_timeout_secs": timedelta(minutes=1).total_seconds(),
|
|
554
553
|
# See mlrun.model_monitoring.db.stores.ObjectStoreFactory for available options
|
|
555
|
-
"store_type": "v3io-nosql", # TODO: Delete in 1.9.0
|
|
556
554
|
"endpoint_store_connection": "",
|
|
557
555
|
# See mlrun.model_monitoring.db.tsdb.ObjectTSDBFactory for available options
|
|
558
556
|
"tsdb_connection": "",
|
|
@@ -736,7 +734,7 @@ default_config = {
|
|
|
736
734
|
"grafana_url": "",
|
|
737
735
|
"alerts": {
|
|
738
736
|
# supported modes: "enabled", "disabled".
|
|
739
|
-
"mode": "
|
|
737
|
+
"mode": "disabled",
|
|
740
738
|
# maximum number of alerts we allow to be configured.
|
|
741
739
|
# user will get an error when exceeding this
|
|
742
740
|
"max_allowed": 10000,
|
|
@@ -798,7 +796,21 @@ class Config:
|
|
|
798
796
|
for key, value in cfg.items():
|
|
799
797
|
if hasattr(self, key):
|
|
800
798
|
if isinstance(value, dict):
|
|
801
|
-
|
|
799
|
+
# ignore the `skip_errors` flag here
|
|
800
|
+
# if the key does not align with what mlrun config expects it is a user
|
|
801
|
+
# input error that can lead to unexpected behavior.
|
|
802
|
+
# raise the exception to ensure configuration is loaded correctly and do not
|
|
803
|
+
# ignore any errors.
|
|
804
|
+
config_value = getattr(self, key)
|
|
805
|
+
try:
|
|
806
|
+
config_value.update(value)
|
|
807
|
+
except AttributeError as exc:
|
|
808
|
+
if not isinstance(config_value, (dict, Config)):
|
|
809
|
+
raise ValueError(
|
|
810
|
+
f"Can not update `{key}` config. "
|
|
811
|
+
f"Expected a configuration but received {type(value)}"
|
|
812
|
+
) from exc
|
|
813
|
+
raise exc
|
|
802
814
|
else:
|
|
803
815
|
try:
|
|
804
816
|
setattr(self, key, value)
|
|
@@ -1102,6 +1114,9 @@ class Config:
|
|
|
1102
1114
|
# importing here to avoid circular dependency
|
|
1103
1115
|
import mlrun.db
|
|
1104
1116
|
|
|
1117
|
+
# It ensures that SSL verification is set before establishing a connection
|
|
1118
|
+
_configure_ssl_verification(self.httpdb.http.verify)
|
|
1119
|
+
|
|
1105
1120
|
# when dbpath is set we want to connect to it which will sync configuration from it to the client
|
|
1106
1121
|
mlrun.db.get_run_db(value, force_reconnect=True)
|
|
1107
1122
|
|
|
@@ -1130,10 +1145,10 @@ class Config:
|
|
|
1130
1145
|
project: str = "",
|
|
1131
1146
|
kind: str = "",
|
|
1132
1147
|
target: str = "online",
|
|
1133
|
-
artifact_path: str = None,
|
|
1134
|
-
function_name: str = None,
|
|
1148
|
+
artifact_path: typing.Optional[str] = None,
|
|
1149
|
+
function_name: typing.Optional[str] = None,
|
|
1135
1150
|
**kwargs,
|
|
1136
|
-
) ->
|
|
1151
|
+
) -> str:
|
|
1137
1152
|
"""Get the full path from the configuration based on the provided project and kind.
|
|
1138
1153
|
|
|
1139
1154
|
:param project: Project name.
|
|
@@ -1149,8 +1164,7 @@ class Config:
|
|
|
1149
1164
|
relative artifact path will be taken from the global MLRun artifact path.
|
|
1150
1165
|
:param function_name: Application name, None for model_monitoring_stream.
|
|
1151
1166
|
|
|
1152
|
-
:return: Full configured path for the provided kind.
|
|
1153
|
-
or a list of paths in the case of the online model monitoring stream path.
|
|
1167
|
+
:return: Full configured path for the provided kind.
|
|
1154
1168
|
"""
|
|
1155
1169
|
|
|
1156
1170
|
if target != "offline":
|
|
@@ -1171,18 +1185,11 @@ class Config:
|
|
|
1171
1185
|
if function_name is None
|
|
1172
1186
|
else f"{kind}-{function_name.lower()}",
|
|
1173
1187
|
)
|
|
1174
|
-
elif kind == "stream":
|
|
1175
|
-
return
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
kind=kind,
|
|
1180
|
-
), # old stream uri (pipelines) for BC ML-6043
|
|
1181
|
-
mlrun.mlconf.model_endpoint_monitoring.store_prefixes.user_space.format(
|
|
1182
|
-
project=project,
|
|
1183
|
-
kind=kind,
|
|
1184
|
-
), # new stream uri (projects)
|
|
1185
|
-
]
|
|
1188
|
+
elif kind == "stream":
|
|
1189
|
+
return mlrun.mlconf.model_endpoint_monitoring.store_prefixes.user_space.format(
|
|
1190
|
+
project=project,
|
|
1191
|
+
kind=kind,
|
|
1192
|
+
)
|
|
1186
1193
|
else:
|
|
1187
1194
|
return mlrun.mlconf.model_endpoint_monitoring.store_prefixes.default.format(
|
|
1188
1195
|
project=project,
|
mlrun/data_types/data_types.py
CHANGED
|
@@ -70,6 +70,11 @@ def pa_type_to_value_type(type_):
|
|
|
70
70
|
if isinstance(type_, TimestampType):
|
|
71
71
|
return ValueType.DATETIME
|
|
72
72
|
|
|
73
|
+
# pandas category type translates to pyarrow DictionaryType
|
|
74
|
+
# we need to unpack the value type (ML-7868)
|
|
75
|
+
if isinstance(type_, pyarrow.DictionaryType):
|
|
76
|
+
type_ = type_.value_type
|
|
77
|
+
|
|
73
78
|
type_map = {
|
|
74
79
|
pyarrow.bool_(): ValueType.BOOL,
|
|
75
80
|
pyarrow.int64(): ValueType.INT64,
|
|
@@ -139,7 +144,7 @@ def gbq_to_pandas_dtype(gbq_type):
|
|
|
139
144
|
"BOOL": "bool",
|
|
140
145
|
"FLOAT": "float64",
|
|
141
146
|
"INTEGER": pd.Int64Dtype(),
|
|
142
|
-
"TIMESTAMP": "datetime64[ns]",
|
|
147
|
+
"TIMESTAMP": "datetime64[ns, UTC]",
|
|
143
148
|
}
|
|
144
149
|
return type_map.get(gbq_type, "object")
|
|
145
150
|
|
mlrun/datastore/base.py
CHANGED
|
@@ -29,7 +29,7 @@ from deprecated import deprecated
|
|
|
29
29
|
import mlrun.config
|
|
30
30
|
import mlrun.errors
|
|
31
31
|
from mlrun.errors import err_to_str
|
|
32
|
-
from mlrun.utils import StorePrefix,
|
|
32
|
+
from mlrun.utils import StorePrefix, is_jupyter, logger
|
|
33
33
|
|
|
34
34
|
from .store_resources import is_store_uri, parse_store_uri
|
|
35
35
|
from .utils import filter_df_start_end_time, select_columns_from_df
|
|
@@ -619,14 +619,14 @@ class DataItem:
|
|
|
619
619
|
)
|
|
620
620
|
return df
|
|
621
621
|
|
|
622
|
-
def show(self, format=None):
|
|
622
|
+
def show(self, format: Optional[str] = None) -> None:
|
|
623
623
|
"""show the data object content in Jupyter
|
|
624
624
|
|
|
625
625
|
:param format: format to use (when there is no/wrong suffix), e.g. 'png'
|
|
626
626
|
"""
|
|
627
|
-
if not
|
|
627
|
+
if not is_jupyter:
|
|
628
628
|
logger.warning(
|
|
629
|
-
"Jupyter
|
|
629
|
+
"Jupyter was not detected. `.show()` displays only inside Jupyter."
|
|
630
630
|
)
|
|
631
631
|
return
|
|
632
632
|
|