mlrun 1.7.2rc3__py3-none-any.whl → 1.8.0rc1__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 +14 -12
- mlrun/__main__.py +3 -3
- mlrun/alerts/alert.py +19 -12
- mlrun/artifacts/__init__.py +0 -2
- mlrun/artifacts/base.py +34 -11
- mlrun/artifacts/dataset.py +16 -16
- mlrun/artifacts/manager.py +13 -13
- mlrun/artifacts/model.py +66 -53
- mlrun/common/constants.py +6 -0
- mlrun/common/formatters/__init__.py +1 -0
- mlrun/common/formatters/feature_set.py +1 -0
- mlrun/common/formatters/function.py +1 -0
- mlrun/common/formatters/model_endpoint.py +30 -0
- mlrun/common/formatters/pipeline.py +1 -2
- mlrun/common/model_monitoring/__init__.py +0 -3
- mlrun/common/model_monitoring/helpers.py +1 -1
- mlrun/common/runtimes/constants.py +1 -2
- mlrun/common/schemas/__init__.py +4 -2
- mlrun/common/schemas/artifact.py +0 -6
- mlrun/common/schemas/common.py +50 -0
- mlrun/common/schemas/model_monitoring/__init__.py +8 -1
- mlrun/common/schemas/model_monitoring/constants.py +62 -12
- mlrun/common/schemas/model_monitoring/model_endpoint_v2.py +149 -0
- mlrun/common/schemas/model_monitoring/model_endpoints.py +21 -5
- mlrun/common/schemas/partition.py +122 -0
- mlrun/config.py +43 -15
- mlrun/data_types/__init__.py +0 -2
- mlrun/data_types/data_types.py +0 -1
- mlrun/data_types/infer.py +3 -1
- mlrun/data_types/spark.py +4 -4
- mlrun/data_types/to_pandas.py +2 -11
- mlrun/datastore/__init__.py +0 -2
- mlrun/datastore/alibaba_oss.py +4 -1
- mlrun/datastore/azure_blob.py +4 -1
- mlrun/datastore/base.py +12 -4
- mlrun/datastore/datastore.py +9 -3
- mlrun/datastore/datastore_profile.py +1 -1
- mlrun/datastore/dbfs_store.py +4 -1
- mlrun/datastore/filestore.py +4 -1
- mlrun/datastore/google_cloud_storage.py +4 -1
- mlrun/datastore/hdfs.py +4 -1
- mlrun/datastore/inmem.py +4 -1
- mlrun/datastore/redis.py +4 -1
- mlrun/datastore/s3.py +4 -1
- mlrun/datastore/sources.py +51 -49
- mlrun/datastore/store_resources.py +0 -2
- mlrun/datastore/targets.py +22 -23
- mlrun/datastore/utils.py +2 -2
- mlrun/datastore/v3io.py +4 -1
- mlrun/datastore/wasbfs/fs.py +13 -12
- mlrun/db/base.py +126 -62
- mlrun/db/factory.py +3 -0
- mlrun/db/httpdb.py +767 -231
- mlrun/db/nopdb.py +126 -57
- mlrun/errors.py +2 -2
- mlrun/execution.py +55 -29
- mlrun/feature_store/__init__.py +0 -2
- mlrun/feature_store/api.py +40 -40
- mlrun/feature_store/common.py +9 -9
- mlrun/feature_store/feature_set.py +20 -18
- mlrun/feature_store/feature_vector.py +27 -24
- mlrun/feature_store/retrieval/base.py +14 -9
- mlrun/feature_store/retrieval/job.py +2 -1
- mlrun/feature_store/steps.py +2 -2
- mlrun/features.py +30 -13
- mlrun/frameworks/__init__.py +1 -2
- mlrun/frameworks/_common/__init__.py +1 -2
- mlrun/frameworks/_common/artifacts_library.py +2 -2
- mlrun/frameworks/_common/mlrun_interface.py +10 -6
- mlrun/frameworks/_common/model_handler.py +29 -27
- mlrun/frameworks/_common/producer.py +3 -1
- mlrun/frameworks/_dl_common/__init__.py +1 -2
- mlrun/frameworks/_dl_common/loggers/__init__.py +1 -2
- mlrun/frameworks/_dl_common/loggers/mlrun_logger.py +4 -4
- mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +3 -3
- mlrun/frameworks/_ml_common/__init__.py +1 -2
- mlrun/frameworks/_ml_common/loggers/__init__.py +1 -2
- mlrun/frameworks/_ml_common/model_handler.py +21 -21
- mlrun/frameworks/_ml_common/plans/__init__.py +1 -2
- mlrun/frameworks/_ml_common/plans/confusion_matrix_plan.py +3 -1
- mlrun/frameworks/_ml_common/plans/dataset_plan.py +3 -3
- mlrun/frameworks/_ml_common/plans/roc_curve_plan.py +4 -4
- mlrun/frameworks/auto_mlrun/__init__.py +1 -2
- mlrun/frameworks/auto_mlrun/auto_mlrun.py +22 -15
- mlrun/frameworks/huggingface/__init__.py +1 -2
- mlrun/frameworks/huggingface/model_server.py +9 -9
- mlrun/frameworks/lgbm/__init__.py +47 -44
- mlrun/frameworks/lgbm/callbacks/__init__.py +1 -2
- mlrun/frameworks/lgbm/callbacks/logging_callback.py +4 -2
- mlrun/frameworks/lgbm/callbacks/mlrun_logging_callback.py +4 -2
- mlrun/frameworks/lgbm/mlrun_interfaces/__init__.py +1 -2
- mlrun/frameworks/lgbm/mlrun_interfaces/mlrun_interface.py +5 -5
- mlrun/frameworks/lgbm/model_handler.py +15 -11
- mlrun/frameworks/lgbm/model_server.py +11 -7
- mlrun/frameworks/lgbm/utils.py +2 -2
- mlrun/frameworks/onnx/__init__.py +1 -2
- mlrun/frameworks/onnx/dataset.py +3 -3
- mlrun/frameworks/onnx/mlrun_interface.py +2 -2
- mlrun/frameworks/onnx/model_handler.py +7 -5
- mlrun/frameworks/onnx/model_server.py +8 -6
- mlrun/frameworks/parallel_coordinates.py +11 -11
- mlrun/frameworks/pytorch/__init__.py +22 -23
- mlrun/frameworks/pytorch/callbacks/__init__.py +1 -2
- mlrun/frameworks/pytorch/callbacks/callback.py +2 -1
- mlrun/frameworks/pytorch/callbacks/logging_callback.py +15 -8
- mlrun/frameworks/pytorch/callbacks/mlrun_logging_callback.py +19 -12
- mlrun/frameworks/pytorch/callbacks/tensorboard_logging_callback.py +22 -15
- mlrun/frameworks/pytorch/callbacks_handler.py +36 -30
- mlrun/frameworks/pytorch/mlrun_interface.py +17 -17
- mlrun/frameworks/pytorch/model_handler.py +21 -17
- mlrun/frameworks/pytorch/model_server.py +13 -9
- mlrun/frameworks/sklearn/__init__.py +19 -18
- mlrun/frameworks/sklearn/estimator.py +2 -2
- mlrun/frameworks/sklearn/metric.py +3 -3
- mlrun/frameworks/sklearn/metrics_library.py +8 -6
- mlrun/frameworks/sklearn/mlrun_interface.py +3 -2
- mlrun/frameworks/sklearn/model_handler.py +4 -3
- mlrun/frameworks/tf_keras/__init__.py +11 -12
- mlrun/frameworks/tf_keras/callbacks/__init__.py +1 -2
- mlrun/frameworks/tf_keras/callbacks/logging_callback.py +17 -14
- mlrun/frameworks/tf_keras/callbacks/mlrun_logging_callback.py +15 -12
- mlrun/frameworks/tf_keras/callbacks/tensorboard_logging_callback.py +21 -18
- mlrun/frameworks/tf_keras/model_handler.py +17 -13
- mlrun/frameworks/tf_keras/model_server.py +12 -8
- mlrun/frameworks/xgboost/__init__.py +19 -18
- mlrun/frameworks/xgboost/model_handler.py +13 -9
- mlrun/launcher/base.py +3 -4
- mlrun/launcher/local.py +1 -1
- mlrun/launcher/remote.py +1 -1
- mlrun/lists.py +4 -3
- mlrun/model.py +108 -44
- mlrun/model_monitoring/__init__.py +1 -2
- mlrun/model_monitoring/api.py +6 -6
- mlrun/model_monitoring/applications/_application_steps.py +13 -15
- mlrun/model_monitoring/applications/histogram_data_drift.py +41 -15
- mlrun/model_monitoring/applications/results.py +55 -3
- mlrun/model_monitoring/controller.py +185 -223
- mlrun/model_monitoring/db/_schedules.py +156 -0
- mlrun/model_monitoring/db/_stats.py +189 -0
- mlrun/model_monitoring/db/stores/__init__.py +1 -1
- mlrun/model_monitoring/db/stores/base/store.py +6 -65
- mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +0 -25
- mlrun/model_monitoring/db/stores/sqldb/models/base.py +0 -97
- mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +2 -58
- mlrun/model_monitoring/db/stores/sqldb/models/sqlite.py +0 -15
- mlrun/model_monitoring/db/stores/sqldb/sql_store.py +6 -257
- mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +9 -271
- mlrun/model_monitoring/db/tsdb/base.py +74 -22
- mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +66 -35
- mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +33 -0
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +284 -51
- mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +1 -0
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +35 -17
- mlrun/model_monitoring/helpers.py +97 -1
- mlrun/model_monitoring/model_endpoint.py +4 -2
- mlrun/model_monitoring/stream_processing.py +2 -2
- mlrun/model_monitoring/tracking_policy.py +10 -3
- mlrun/model_monitoring/writer.py +47 -26
- mlrun/package/__init__.py +3 -6
- mlrun/package/context_handler.py +1 -1
- mlrun/package/packager.py +12 -9
- mlrun/package/packagers/__init__.py +0 -2
- mlrun/package/packagers/default_packager.py +14 -11
- mlrun/package/packagers/numpy_packagers.py +16 -7
- mlrun/package/packagers/pandas_packagers.py +18 -18
- mlrun/package/packagers/python_standard_library_packagers.py +25 -11
- mlrun/package/packagers_manager.py +31 -14
- mlrun/package/utils/__init__.py +0 -3
- mlrun/package/utils/_pickler.py +6 -6
- mlrun/platforms/__init__.py +3 -3
- mlrun/platforms/iguazio.py +4 -1
- mlrun/projects/__init__.py +1 -6
- mlrun/projects/operations.py +27 -27
- mlrun/projects/pipelines.py +85 -215
- mlrun/projects/project.py +444 -158
- mlrun/run.py +9 -9
- mlrun/runtimes/__init__.py +1 -3
- mlrun/runtimes/base.py +13 -10
- mlrun/runtimes/daskjob.py +9 -9
- mlrun/runtimes/generators.py +2 -1
- mlrun/runtimes/kubejob.py +4 -5
- mlrun/runtimes/mpijob/__init__.py +0 -2
- mlrun/runtimes/mpijob/abstract.py +7 -6
- mlrun/runtimes/nuclio/api_gateway.py +7 -7
- mlrun/runtimes/nuclio/application/application.py +11 -11
- mlrun/runtimes/nuclio/function.py +14 -13
- mlrun/runtimes/nuclio/serving.py +9 -9
- mlrun/runtimes/pod.py +74 -29
- mlrun/runtimes/remotesparkjob.py +3 -2
- mlrun/runtimes/sparkjob/__init__.py +0 -2
- mlrun/runtimes/sparkjob/spark3job.py +21 -11
- mlrun/runtimes/utils.py +6 -5
- mlrun/serving/merger.py +6 -4
- mlrun/serving/remote.py +18 -17
- mlrun/serving/routers.py +27 -27
- mlrun/serving/server.py +1 -1
- mlrun/serving/states.py +76 -71
- mlrun/serving/utils.py +13 -2
- mlrun/serving/v1_serving.py +3 -2
- mlrun/serving/v2_serving.py +4 -4
- mlrun/track/__init__.py +1 -1
- mlrun/track/tracker.py +2 -2
- mlrun/track/trackers/mlflow_tracker.py +6 -5
- mlrun/utils/async_http.py +1 -1
- mlrun/utils/helpers.py +72 -28
- mlrun/utils/logger.py +104 -2
- mlrun/utils/notifications/notification/base.py +23 -4
- mlrun/utils/notifications/notification/console.py +1 -1
- mlrun/utils/notifications/notification/git.py +6 -6
- mlrun/utils/notifications/notification/ipython.py +5 -4
- mlrun/utils/notifications/notification/slack.py +1 -1
- mlrun/utils/notifications/notification/webhook.py +13 -17
- mlrun/utils/notifications/notification_pusher.py +23 -19
- mlrun/utils/regex.py +1 -1
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.7.2rc3.dist-info → mlrun-1.8.0rc1.dist-info}/METADATA +186 -186
- mlrun-1.8.0rc1.dist-info/RECORD +356 -0
- {mlrun-1.7.2rc3.dist-info → mlrun-1.8.0rc1.dist-info}/WHEEL +1 -1
- mlrun-1.7.2rc3.dist-info/RECORD +0 -351
- {mlrun-1.7.2rc3.dist-info → mlrun-1.8.0rc1.dist-info}/LICENSE +0 -0
- {mlrun-1.7.2rc3.dist-info → mlrun-1.8.0rc1.dist-info}/entry_points.txt +0 -0
- {mlrun-1.7.2rc3.dist-info → mlrun-1.8.0rc1.dist-info}/top_level.txt +0 -0
|
@@ -11,8 +11,5 @@
|
|
|
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
|
-
# flake8: noqa - this is until we take care of the F401 violations with respect to __all__ & sphinx
|
|
17
14
|
|
|
18
15
|
from .helpers import create_model_endpoint_uid
|
|
@@ -65,7 +65,7 @@ def parse_model_endpoint_store_prefix(store_prefix: str):
|
|
|
65
65
|
|
|
66
66
|
|
|
67
67
|
def parse_monitoring_stream_path(
|
|
68
|
-
stream_uri: str, project: str, function_name: str = None
|
|
68
|
+
stream_uri: str, project: str, function_name: typing.Optional[str] = None
|
|
69
69
|
):
|
|
70
70
|
if stream_uri.startswith("kafka://"):
|
|
71
71
|
if "?topic" in stream_uri:
|
mlrun/common/schemas/__init__.py
CHANGED
|
@@ -11,8 +11,6 @@
|
|
|
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
|
-
# flake8: noqa - this is until we take care of the F401 violations with respect to __all__ & sphinx
|
|
16
14
|
|
|
17
15
|
from .alert import (
|
|
18
16
|
AlertActiveState,
|
|
@@ -149,6 +147,10 @@ from .model_monitoring import (
|
|
|
149
147
|
ModelEndpointMetadata,
|
|
150
148
|
ModelEndpointSpec,
|
|
151
149
|
ModelEndpointStatus,
|
|
150
|
+
ModelEndpointV2,
|
|
151
|
+
ModelEndpointV2Metadata,
|
|
152
|
+
ModelEndpointV2Spec,
|
|
153
|
+
ModelEndpointV2Status,
|
|
152
154
|
ModelMonitoringMode,
|
|
153
155
|
ModelMonitoringStoreKinds,
|
|
154
156
|
MonitoringFunctionNames,
|
mlrun/common/schemas/artifact.py
CHANGED
|
@@ -47,12 +47,6 @@ class ArtifactCategories(mlrun.common.types.StrEnum):
|
|
|
47
47
|
True,
|
|
48
48
|
)
|
|
49
49
|
|
|
50
|
-
@classmethod
|
|
51
|
-
def from_kind(cls, kind: str) -> "ArtifactCategories":
|
|
52
|
-
if kind in [cls.model.value, cls.dataset.value]:
|
|
53
|
-
return cls(kind)
|
|
54
|
-
return cls.other
|
|
55
|
-
|
|
56
50
|
|
|
57
51
|
class ArtifactIdentifier(pydantic.BaseModel):
|
|
58
52
|
# artifact kind
|
mlrun/common/schemas/common.py
CHANGED
|
@@ -16,6 +16,8 @@ import typing
|
|
|
16
16
|
|
|
17
17
|
import pydantic
|
|
18
18
|
|
|
19
|
+
import mlrun.errors
|
|
20
|
+
|
|
19
21
|
|
|
20
22
|
class ImageBuilder(pydantic.BaseModel):
|
|
21
23
|
functionSourceCode: typing.Optional[str] = None # noqa: N815
|
|
@@ -41,3 +43,51 @@ class ImageBuilder(pydantic.BaseModel):
|
|
|
41
43
|
|
|
42
44
|
class Config:
|
|
43
45
|
extra = pydantic.Extra.allow
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class LabelsModel(pydantic.BaseModel):
|
|
49
|
+
"""
|
|
50
|
+
This class accepts either a dictionary, a list, or a string for filtering by labels.
|
|
51
|
+
|
|
52
|
+
:param labels:
|
|
53
|
+
- If a dictionary is provided, it should be in the format {'label_name': 'value'}.
|
|
54
|
+
The values can also be `None`, which will result in the format 'label_name' (without a value).
|
|
55
|
+
This will be converted to a list of strings in the format 'label_name=value'.
|
|
56
|
+
- If a list is provided, all items must be strings. Each string can either
|
|
57
|
+
be a simple label name (e.g., 'label1') or a key-value pair in the format
|
|
58
|
+
'label=value'.
|
|
59
|
+
- If a string is provided, it should be a comma-separated list of labels
|
|
60
|
+
(e.g., 'label1,label2').
|
|
61
|
+
- If no labels are specified, the default is an empty list.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
labels: typing.Optional[
|
|
65
|
+
typing.Union[str, dict[str, typing.Optional[str]], list[str]]
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
@pydantic.validator("labels")
|
|
69
|
+
@classmethod
|
|
70
|
+
def validate(cls, labels) -> list[str]:
|
|
71
|
+
if labels is None:
|
|
72
|
+
return []
|
|
73
|
+
|
|
74
|
+
# If labels is a string, split it by commas
|
|
75
|
+
if isinstance(labels, str):
|
|
76
|
+
return [label.strip() for label in labels.split(",") if label.strip()]
|
|
77
|
+
|
|
78
|
+
if isinstance(labels, list):
|
|
79
|
+
if not all(isinstance(item, str) for item in labels):
|
|
80
|
+
raise mlrun.errors.MLRunValueError(
|
|
81
|
+
"All items in the list must be strings."
|
|
82
|
+
)
|
|
83
|
+
return labels
|
|
84
|
+
|
|
85
|
+
if isinstance(labels, dict):
|
|
86
|
+
return [
|
|
87
|
+
f"{key}={value}" if value is not None else key
|
|
88
|
+
for key, value in labels.items()
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
raise mlrun.errors.MLRunValueError(
|
|
92
|
+
"Invalid labels format. Must be a string, dictionary of strings, or a list of strings."
|
|
93
|
+
)
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
from .constants import (
|
|
16
16
|
V3IO_MODEL_MONITORING_DB,
|
|
17
|
+
ApplicationEvent,
|
|
17
18
|
ControllerPolicy,
|
|
18
19
|
DriftStatus,
|
|
19
20
|
EndpointType,
|
|
@@ -26,6 +27,7 @@ from .constants import (
|
|
|
26
27
|
FunctionURI,
|
|
27
28
|
MetricData,
|
|
28
29
|
ModelEndpointMonitoringMetricType,
|
|
30
|
+
ModelEndpointSchema,
|
|
29
31
|
ModelEndpointTarget,
|
|
30
32
|
ModelEndpointTargetSchemas,
|
|
31
33
|
ModelMonitoringMode,
|
|
@@ -36,7 +38,6 @@ from .constants import (
|
|
|
36
38
|
ResultData,
|
|
37
39
|
ResultKindApp,
|
|
38
40
|
ResultStatusApp,
|
|
39
|
-
SchedulingKeys,
|
|
40
41
|
SpecialApps,
|
|
41
42
|
TDEngineSuperTables,
|
|
42
43
|
TSDBTarget,
|
|
@@ -54,6 +55,12 @@ from .grafana import (
|
|
|
54
55
|
GrafanaTable,
|
|
55
56
|
GrafanaTimeSeriesTarget,
|
|
56
57
|
)
|
|
58
|
+
from .model_endpoint_v2 import (
|
|
59
|
+
ModelEndpointV2,
|
|
60
|
+
ModelEndpointV2Metadata,
|
|
61
|
+
ModelEndpointV2Spec,
|
|
62
|
+
ModelEndpointV2Status,
|
|
63
|
+
)
|
|
57
64
|
from .model_endpoints import (
|
|
58
65
|
Features,
|
|
59
66
|
FeatureValues,
|
|
@@ -29,6 +29,46 @@ class MonitoringStrEnum(StrEnum):
|
|
|
29
29
|
return list(map(lambda c: c.value, cls))
|
|
30
30
|
|
|
31
31
|
|
|
32
|
+
class ModelEndpointSchema(MonitoringStrEnum):
|
|
33
|
+
# metadata
|
|
34
|
+
UID = "uid"
|
|
35
|
+
PROJECT = "project"
|
|
36
|
+
ENDPOINT_TYPE = "endpoint_type"
|
|
37
|
+
NAME = "name"
|
|
38
|
+
CREATED = "created"
|
|
39
|
+
UPDATED = "updated"
|
|
40
|
+
LABELS = "labels"
|
|
41
|
+
|
|
42
|
+
# spec
|
|
43
|
+
FUNCTION_NAME = "function_name"
|
|
44
|
+
FUNCTION_UID = "function_uid"
|
|
45
|
+
MODEL_NAME = "model_name"
|
|
46
|
+
MODEL_TAG = "model_tag"
|
|
47
|
+
MODEL_CLASS = "model_class"
|
|
48
|
+
MODEL_UID = "model_uid"
|
|
49
|
+
FEATURE_NAMES = "feature_names"
|
|
50
|
+
LABEL_NAMES = "label_names"
|
|
51
|
+
|
|
52
|
+
# status
|
|
53
|
+
STATE = "state"
|
|
54
|
+
MONITORING_MODE = "monitoring_mode"
|
|
55
|
+
MONITORING_FEATURE_SET_URI = "monitoring_feature_set_uri"
|
|
56
|
+
CHILDREN = "children"
|
|
57
|
+
CHILDREN_UIDS = "children_uids"
|
|
58
|
+
FIRST_REQUEST = "first_request"
|
|
59
|
+
FUNCTION_URI = "function_uri"
|
|
60
|
+
MODEL_URI = "model_uri"
|
|
61
|
+
|
|
62
|
+
# status - operative
|
|
63
|
+
LAST_REQUEST = "last_request"
|
|
64
|
+
DRIFT_STATUS = "drift_status"
|
|
65
|
+
AVG_LATENCY = "avg_latency"
|
|
66
|
+
ERROR_COUNT = "error_count"
|
|
67
|
+
FEATURE_STATS = "feature_stats"
|
|
68
|
+
CURRENT_STATS = "current_stats"
|
|
69
|
+
DRIFT_MEASURES = "drift_measures"
|
|
70
|
+
|
|
71
|
+
|
|
32
72
|
class EventFieldType:
|
|
33
73
|
FUNCTION_URI = "function_uri"
|
|
34
74
|
FUNCTION = "function"
|
|
@@ -55,6 +95,8 @@ class EventFieldType:
|
|
|
55
95
|
NAMED_PREDICTIONS = "named_predictions"
|
|
56
96
|
ERROR_COUNT = "error_count"
|
|
57
97
|
MODEL_ERROR = "model_error"
|
|
98
|
+
ERROR_TYPE = "error_type"
|
|
99
|
+
INFER_ERROR = "infer_error"
|
|
58
100
|
ENTITIES = "entities"
|
|
59
101
|
FIRST_REQUEST = "first_request"
|
|
60
102
|
LAST_REQUEST = "last_request"
|
|
@@ -114,13 +156,14 @@ class WriterEvent(MonitoringStrEnum):
|
|
|
114
156
|
ENDPOINT_ID = "endpoint_id"
|
|
115
157
|
START_INFER_TIME = "start_infer_time"
|
|
116
158
|
END_INFER_TIME = "end_infer_time"
|
|
117
|
-
EVENT_KIND = "event_kind" # metric or result
|
|
159
|
+
EVENT_KIND = "event_kind" # metric or result or stats
|
|
118
160
|
DATA = "data"
|
|
119
161
|
|
|
120
162
|
|
|
121
163
|
class WriterEventKind(MonitoringStrEnum):
|
|
122
164
|
METRIC = "metric"
|
|
123
165
|
RESULT = "result"
|
|
166
|
+
STATS = "stats"
|
|
124
167
|
|
|
125
168
|
|
|
126
169
|
class MetricData(MonitoringStrEnum):
|
|
@@ -134,7 +177,17 @@ class ResultData(MonitoringStrEnum):
|
|
|
134
177
|
RESULT_KIND = "result_kind"
|
|
135
178
|
RESULT_STATUS = "result_status"
|
|
136
179
|
RESULT_EXTRA_DATA = "result_extra_data"
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class StatsData(MonitoringStrEnum):
|
|
183
|
+
STATS_NAME = "stats_name"
|
|
184
|
+
STATS = "stats"
|
|
185
|
+
TIMESTAMP = "timestamp"
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class StatsKind(MonitoringStrEnum):
|
|
137
189
|
CURRENT_STATS = "current_stats"
|
|
190
|
+
DRIFT_MEASURES = "drift_measures"
|
|
138
191
|
|
|
139
192
|
|
|
140
193
|
class EventLiveStats:
|
|
@@ -194,13 +247,6 @@ class ModelMonitoringStoreKinds:
|
|
|
194
247
|
EVENTS = "events"
|
|
195
248
|
|
|
196
249
|
|
|
197
|
-
class SchedulingKeys:
|
|
198
|
-
LAST_ANALYZED = "last_analyzed"
|
|
199
|
-
ENDPOINT_ID = "endpoint_id"
|
|
200
|
-
APPLICATION_NAME = "application_name"
|
|
201
|
-
UID = "uid"
|
|
202
|
-
|
|
203
|
-
|
|
204
250
|
class FileTargetKind:
|
|
205
251
|
ENDPOINTS = "endpoints"
|
|
206
252
|
EVENTS = "events"
|
|
@@ -209,14 +255,13 @@ class FileTargetKind:
|
|
|
209
255
|
PARQUET = "parquet"
|
|
210
256
|
APPS_PARQUET = "apps_parquet"
|
|
211
257
|
LOG_STREAM = "log_stream"
|
|
212
|
-
APP_RESULTS = "app_results"
|
|
213
|
-
APP_METRICS = "app_metrics"
|
|
214
258
|
MONITORING_SCHEDULES = "monitoring_schedules"
|
|
215
259
|
MONITORING_APPLICATION = "monitoring_application"
|
|
216
260
|
ERRORS = "errors"
|
|
261
|
+
STATS = "stats"
|
|
217
262
|
|
|
218
263
|
|
|
219
|
-
class ModelMonitoringMode(
|
|
264
|
+
class ModelMonitoringMode(StrEnum):
|
|
220
265
|
enabled = "enabled"
|
|
221
266
|
disabled = "disabled"
|
|
222
267
|
|
|
@@ -225,6 +270,11 @@ class EndpointType(IntEnum):
|
|
|
225
270
|
NODE_EP = 1 # end point that is not a child of a router
|
|
226
271
|
ROUTER = 2 # endpoint that is router
|
|
227
272
|
LEAF_EP = 3 # end point that is a child of a router
|
|
273
|
+
BATCH_EP = 4 # endpoint that is representing an offline batch endpoint
|
|
274
|
+
|
|
275
|
+
@classmethod
|
|
276
|
+
def top_level_list(cls):
|
|
277
|
+
return [cls.NODE_EP, cls.ROUTER, cls.BATCH_EP]
|
|
228
278
|
|
|
229
279
|
|
|
230
280
|
class MonitoringFunctionNames(MonitoringStrEnum):
|
|
@@ -244,6 +294,7 @@ class TDEngineSuperTables(MonitoringStrEnum):
|
|
|
244
294
|
APP_RESULTS = "app_results"
|
|
245
295
|
METRICS = "metrics"
|
|
246
296
|
PREDICTIONS = "predictions"
|
|
297
|
+
ERRORS = "errors"
|
|
247
298
|
|
|
248
299
|
|
|
249
300
|
@dataclass
|
|
@@ -364,7 +415,6 @@ class SpecialApps:
|
|
|
364
415
|
|
|
365
416
|
_RESERVED_FUNCTION_NAMES = MonitoringFunctionNames.list() + [SpecialApps.MLRUN_INFRA]
|
|
366
417
|
|
|
367
|
-
|
|
368
418
|
V3IO_MODEL_MONITORING_DB = "v3io"
|
|
369
419
|
|
|
370
420
|
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Copyright 2023 Iguazio
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
#
|
|
15
|
+
import abc
|
|
16
|
+
from typing import Optional
|
|
17
|
+
|
|
18
|
+
from pydantic import BaseModel, Field, constr
|
|
19
|
+
|
|
20
|
+
from ..object import ObjectKind, ObjectMetadata, ObjectSpec, ObjectStatus
|
|
21
|
+
from .constants import (
|
|
22
|
+
PROJECT_PATTERN,
|
|
23
|
+
EndpointType,
|
|
24
|
+
ModelMonitoringMode,
|
|
25
|
+
)
|
|
26
|
+
from .model_endpoints import _mapping_attributes
|
|
27
|
+
|
|
28
|
+
# TODO : replace ModelEndpoint
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ModelEndpointParser(abc.ABC, BaseModel):
|
|
32
|
+
@classmethod
|
|
33
|
+
def json_parse_values(cls) -> list[str]:
|
|
34
|
+
return []
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def from_flat_dict(
|
|
38
|
+
cls, endpoint_dict: dict, json_parse_values: Optional[list] = None
|
|
39
|
+
):
|
|
40
|
+
"""Create a `ModelEndpointMetadata` object from an endpoint dictionary
|
|
41
|
+
|
|
42
|
+
:param endpoint_dict: Model endpoint dictionary.
|
|
43
|
+
:param json_parse_values: List of dictionary keys with a JSON string value that will be parsed into a
|
|
44
|
+
dictionary using json.loads().
|
|
45
|
+
"""
|
|
46
|
+
if json_parse_values is None:
|
|
47
|
+
json_parse_values = cls.json_parse_values()
|
|
48
|
+
|
|
49
|
+
return _mapping_attributes(
|
|
50
|
+
model_class=cls,
|
|
51
|
+
flattened_dictionary=endpoint_dict,
|
|
52
|
+
json_parse_values=json_parse_values,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class ModelEndpointV2Metadata(ObjectMetadata, ModelEndpointParser):
|
|
57
|
+
project: constr(regex=PROJECT_PATTERN)
|
|
58
|
+
endpoint_type: Optional[EndpointType] = EndpointType.NODE_EP.value
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ModelEndpointV2Spec(ObjectSpec, ModelEndpointParser):
|
|
62
|
+
model_uid: Optional[str] = ""
|
|
63
|
+
model_name: Optional[str] = ""
|
|
64
|
+
model_tag: Optional[str] = ""
|
|
65
|
+
model_class: Optional[str] = ""
|
|
66
|
+
function_name: Optional[str] = ""
|
|
67
|
+
function_uid: Optional[str] = ""
|
|
68
|
+
feature_names: Optional[list[str]] = []
|
|
69
|
+
label_names: Optional[list[str]] = []
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class ModelEndpointV2Status(ObjectStatus, ModelEndpointParser):
|
|
73
|
+
state: Optional[str] = "unknown" # will be updated according to the function state
|
|
74
|
+
first_request: Optional[str] = ""
|
|
75
|
+
children: Optional[list[str]] = []
|
|
76
|
+
children_uids: Optional[list[str]] = []
|
|
77
|
+
monitoring_feature_set_uri: Optional[str] = ""
|
|
78
|
+
monitoring_mode: Optional[ModelMonitoringMode] = ModelMonitoringMode.disabled.value
|
|
79
|
+
function_uri: Optional[str] = "" # <project_name>/<function_name>:<tag>
|
|
80
|
+
model_uri: Optional[str] = ""
|
|
81
|
+
|
|
82
|
+
# operative
|
|
83
|
+
last_request: Optional[str] = ""
|
|
84
|
+
drift_status: Optional[str] = ""
|
|
85
|
+
avg_latency: Optional[float] = None
|
|
86
|
+
error_count: Optional[int] = 0
|
|
87
|
+
feature_stats: Optional[dict] = {}
|
|
88
|
+
current_stats: Optional[dict] = {}
|
|
89
|
+
drift_measures: Optional[dict] = {}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class ModelEndpointV2(BaseModel):
|
|
93
|
+
kind: ObjectKind = Field(ObjectKind.model_endpoint, const=True)
|
|
94
|
+
metadata: ModelEndpointV2Metadata
|
|
95
|
+
spec: ModelEndpointV2Spec
|
|
96
|
+
status: ModelEndpointV2Status
|
|
97
|
+
|
|
98
|
+
def flat_dict(self, exclude: Optional[set] = None):
|
|
99
|
+
"""Generate a flattened `ModelEndpoint` dictionary. The flattened dictionary result is important for storing
|
|
100
|
+
the model endpoint object in the database.
|
|
101
|
+
|
|
102
|
+
:return: Flattened `ModelEndpoint` dictionary.
|
|
103
|
+
"""
|
|
104
|
+
# Convert the ModelEndpoint object into a dictionary using BaseModel dict() function
|
|
105
|
+
# In addition, remove the BaseModel kind as it is not required by the DB schema
|
|
106
|
+
if exclude:
|
|
107
|
+
exclude = exclude | {"kind", "tag"}
|
|
108
|
+
else:
|
|
109
|
+
exclude = {"kind", "tag"}
|
|
110
|
+
model_endpoint_dictionary = self.dict(exclude=exclude)
|
|
111
|
+
|
|
112
|
+
# Initialize a flattened dictionary that will be filled with the model endpoint dictionary attributes
|
|
113
|
+
flatten_dict = {}
|
|
114
|
+
for k_object in model_endpoint_dictionary:
|
|
115
|
+
for key in model_endpoint_dictionary[k_object]:
|
|
116
|
+
# Extract the value of the current field
|
|
117
|
+
flatten_dict[key] = model_endpoint_dictionary[k_object][key]
|
|
118
|
+
|
|
119
|
+
return flatten_dict
|
|
120
|
+
|
|
121
|
+
@classmethod
|
|
122
|
+
def from_flat_dict(cls, endpoint_dict: dict) -> "ModelEndpointV2":
|
|
123
|
+
"""Create a `ModelEndpoint` object from an endpoint flattened dictionary. Because the provided dictionary
|
|
124
|
+
is flattened, we pass it as is to the subclasses without splitting the keys into spec, metadata, and status.
|
|
125
|
+
|
|
126
|
+
:param endpoint_dict: Model endpoint dictionary.
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
return cls(
|
|
130
|
+
metadata=ModelEndpointV2Metadata.from_flat_dict(
|
|
131
|
+
endpoint_dict=endpoint_dict
|
|
132
|
+
),
|
|
133
|
+
spec=ModelEndpointV2Spec.from_flat_dict(endpoint_dict=endpoint_dict),
|
|
134
|
+
status=ModelEndpointV2Status.from_flat_dict(endpoint_dict=endpoint_dict),
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
@classmethod
|
|
138
|
+
def _operative_data(cls) -> set:
|
|
139
|
+
return {
|
|
140
|
+
"last_request",
|
|
141
|
+
"drift_status",
|
|
142
|
+
"avg_latency",
|
|
143
|
+
"error_count",
|
|
144
|
+
"feature_stats",
|
|
145
|
+
"current_stats",
|
|
146
|
+
"drift_measures",
|
|
147
|
+
"function_uri",
|
|
148
|
+
"model_uri",
|
|
149
|
+
}
|
|
@@ -56,7 +56,9 @@ class ModelEndpointMetadata(BaseModel):
|
|
|
56
56
|
extra = Extra.allow
|
|
57
57
|
|
|
58
58
|
@classmethod
|
|
59
|
-
def from_flat_dict(
|
|
59
|
+
def from_flat_dict(
|
|
60
|
+
cls, endpoint_dict: dict, json_parse_values: Optional[list] = None
|
|
61
|
+
):
|
|
60
62
|
"""Create a `ModelEndpointMetadata` object from an endpoint dictionary
|
|
61
63
|
|
|
62
64
|
:param endpoint_dict: Model endpoint dictionary.
|
|
@@ -87,7 +89,9 @@ class ModelEndpointSpec(ObjectSpec):
|
|
|
87
89
|
monitoring_mode: Optional[ModelMonitoringMode] = ModelMonitoringMode.disabled.value
|
|
88
90
|
|
|
89
91
|
@classmethod
|
|
90
|
-
def from_flat_dict(
|
|
92
|
+
def from_flat_dict(
|
|
93
|
+
cls, endpoint_dict: dict, json_parse_values: Optional[list] = None
|
|
94
|
+
):
|
|
91
95
|
"""Create a `ModelEndpointSpec` object from an endpoint dictionary
|
|
92
96
|
|
|
93
97
|
:param endpoint_dict: Model endpoint dictionary.
|
|
@@ -188,7 +192,9 @@ class ModelEndpointStatus(ObjectStatus):
|
|
|
188
192
|
extra = Extra.allow
|
|
189
193
|
|
|
190
194
|
@classmethod
|
|
191
|
-
def from_flat_dict(
|
|
195
|
+
def from_flat_dict(
|
|
196
|
+
cls, endpoint_dict: dict, json_parse_values: Optional[list] = None
|
|
197
|
+
):
|
|
192
198
|
"""Create a `ModelEndpointStatus` object from an endpoint dictionary
|
|
193
199
|
|
|
194
200
|
:param endpoint_dict: Model endpoint dictionary.
|
|
@@ -284,7 +290,14 @@ class ModelEndpointMonitoringMetric(BaseModel):
|
|
|
284
290
|
app: str
|
|
285
291
|
type: ModelEndpointMonitoringMetricType
|
|
286
292
|
name: str
|
|
287
|
-
full_name: str
|
|
293
|
+
full_name: Optional[str] = None
|
|
294
|
+
kind: Optional[ResultKindApp] = None
|
|
295
|
+
|
|
296
|
+
def __init__(self, **kwargs):
|
|
297
|
+
super().__init__(**kwargs)
|
|
298
|
+
self.full_name = _compose_full_name(
|
|
299
|
+
project=self.project, app=self.app, name=self.name, type=self.type
|
|
300
|
+
)
|
|
288
301
|
|
|
289
302
|
|
|
290
303
|
def _compose_full_name(
|
|
@@ -315,6 +328,7 @@ class _ResultPoint(NamedTuple):
|
|
|
315
328
|
timestamp: datetime
|
|
316
329
|
value: float
|
|
317
330
|
status: ResultStatusApp
|
|
331
|
+
extra_data: Optional[str] = ""
|
|
318
332
|
|
|
319
333
|
|
|
320
334
|
class _ModelEndpointMonitoringMetricValuesBase(BaseModel):
|
|
@@ -365,8 +379,10 @@ def _mapping_attributes(
|
|
|
365
379
|
dict_to_parse[field_key] = _json_loads_if_not_none(
|
|
366
380
|
flattened_dictionary[field_key]
|
|
367
381
|
)
|
|
368
|
-
|
|
382
|
+
elif flattened_dictionary[field_key] != "null":
|
|
369
383
|
dict_to_parse[field_key] = flattened_dictionary[field_key]
|
|
384
|
+
else:
|
|
385
|
+
dict_to_parse[field_key] = None
|
|
370
386
|
|
|
371
387
|
return model_class.parse_obj(dict_to_parse)
|
|
372
388
|
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Copyright 2024 Iguazio
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
#
|
|
15
|
+
from datetime import datetime, timedelta
|
|
16
|
+
|
|
17
|
+
from mlrun.common.types import StrEnum
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class PartitionInterval(StrEnum):
|
|
21
|
+
DAY = "DAY"
|
|
22
|
+
MONTH = "MONTH"
|
|
23
|
+
YEARWEEK = "YEARWEEK"
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def is_valid(cls, value: str) -> bool:
|
|
27
|
+
return value in cls._value2member_map_
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def valid_intervals(cls) -> list:
|
|
31
|
+
return list(cls._value2member_map_.keys())
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def from_function(cls, partition_function: str):
|
|
35
|
+
"""
|
|
36
|
+
Returns the corresponding PartitionInterval for a given partition function,
|
|
37
|
+
or None if the function is not mapped.
|
|
38
|
+
|
|
39
|
+
:param partition_function: The partition function to map to an interval.
|
|
40
|
+
:return: PartitionInterval corresponding to the function, or None if no match is found.
|
|
41
|
+
"""
|
|
42
|
+
partition_function_to_partitions_interval = {
|
|
43
|
+
"DAY": "DAY",
|
|
44
|
+
"DAYOFMONTH": "DAY",
|
|
45
|
+
"MONTH": "MONTH",
|
|
46
|
+
"YEARWEEK": "YEARWEEK",
|
|
47
|
+
}
|
|
48
|
+
interval = partition_function_to_partitions_interval.get(partition_function)
|
|
49
|
+
if interval and cls.is_valid(interval):
|
|
50
|
+
return cls[interval]
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
def get_partition_info(
|
|
54
|
+
self,
|
|
55
|
+
start_datetime: datetime,
|
|
56
|
+
partition_number: int = 1,
|
|
57
|
+
) -> list[tuple[str, str]]:
|
|
58
|
+
"""
|
|
59
|
+
Generates partition details for a specified number of partitions starting from a given datetime.
|
|
60
|
+
|
|
61
|
+
:param start_datetime: The starting datetime used for generating partition details.
|
|
62
|
+
:param partition_number: The number of partitions to generate details for.
|
|
63
|
+
|
|
64
|
+
:return: A list of tuples:
|
|
65
|
+
- partition_name: The name for the partition.
|
|
66
|
+
- partition_value: The "LESS THAN" value for the next partition boundary.
|
|
67
|
+
"""
|
|
68
|
+
partitioning_information_list = []
|
|
69
|
+
current_datetime = start_datetime
|
|
70
|
+
|
|
71
|
+
for _ in range(partition_number):
|
|
72
|
+
partition_name = self.get_partition_name(current_datetime)
|
|
73
|
+
partition_boundary_date = self.get_next_partition_time(current_datetime)
|
|
74
|
+
partition_value = self.get_partition_name(partition_boundary_date)
|
|
75
|
+
partitioning_information_list.append((partition_name, partition_value))
|
|
76
|
+
|
|
77
|
+
# Move to the next interval
|
|
78
|
+
current_datetime = partition_boundary_date
|
|
79
|
+
|
|
80
|
+
return partitioning_information_list
|
|
81
|
+
|
|
82
|
+
def get_next_partition_time(self, current_datetime: datetime) -> datetime:
|
|
83
|
+
"""
|
|
84
|
+
Calculates the next partition boundary time based on the specified partition interval.
|
|
85
|
+
:param current_datetime: The current datetime from which the next interval is calculated.
|
|
86
|
+
|
|
87
|
+
:return: A datetime object representing the start of the next partition interval.
|
|
88
|
+
- If the interval is DAY, it advances by one day.
|
|
89
|
+
- If the interval is MONTH, it advances to the first day of the next month.
|
|
90
|
+
- If the interval is YEARWEEK, it advances by one week.
|
|
91
|
+
"""
|
|
92
|
+
if self == PartitionInterval.DAY:
|
|
93
|
+
return current_datetime + timedelta(days=1)
|
|
94
|
+
elif self == PartitionInterval.MONTH:
|
|
95
|
+
return (current_datetime.replace(day=1) + timedelta(days=32)).replace(day=1)
|
|
96
|
+
elif self == PartitionInterval.YEARWEEK:
|
|
97
|
+
return current_datetime + timedelta(weeks=1)
|
|
98
|
+
|
|
99
|
+
def get_partition_name(self, current_datetime: datetime) -> str:
|
|
100
|
+
if self == PartitionInterval.DAY:
|
|
101
|
+
return current_datetime.strftime("%Y%m%d")
|
|
102
|
+
elif self == PartitionInterval.MONTH:
|
|
103
|
+
return current_datetime.strftime("%Y%m")
|
|
104
|
+
elif self == PartitionInterval.YEARWEEK:
|
|
105
|
+
year, week, _ = current_datetime.isocalendar()
|
|
106
|
+
return f"{year}{week:02d}"
|
|
107
|
+
|
|
108
|
+
def get_partition_expression(self):
|
|
109
|
+
if self == PartitionInterval.YEARWEEK:
|
|
110
|
+
return "YEARWEEK(activation_time, 1)"
|
|
111
|
+
else:
|
|
112
|
+
return f"{self}(activation_time)"
|
|
113
|
+
|
|
114
|
+
def get_number_of_partitions(self, days: int) -> int:
|
|
115
|
+
# Calculate the number partitions based on given number of days
|
|
116
|
+
if self == PartitionInterval.DAY:
|
|
117
|
+
return days
|
|
118
|
+
elif self == PartitionInterval.MONTH:
|
|
119
|
+
# Average number days in a month is 30.44
|
|
120
|
+
return int(days / 30.44)
|
|
121
|
+
elif self == PartitionInterval.YEARWEEK:
|
|
122
|
+
return int(days / 7)
|