fiddler-client 2.4.0.dev2__py3-none-any.whl → 2.5.0.dev2__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.
- fiddler/__init__.py +2 -0
- fiddler/_version.py +1 -1
- fiddler/api/alert_mixin.py +51 -1
- fiddler/api/webhooks_mixin.py +6 -8
- fiddler/core_objects.py +76 -26
- fiddler/schema/alert.py +9 -2
- fiddler/schemas/custom_features.py +44 -1
- fiddler3/__init__.py +121 -38
- fiddler3/configs.py +3 -0
- fiddler3/connection.py +68 -1
- fiddler3/constants/alert_rule.py +30 -0
- fiddler3/constants/events.py +7 -0
- fiddler3/constants/model.py +9 -0
- fiddler3/entities/__init__.py +0 -7
- fiddler3/entities/alert_record.py +106 -0
- fiddler3/entities/alert_rule.py +323 -0
- fiddler3/entities/base.py +2 -16
- fiddler3/entities/baseline.py +21 -15
- fiddler3/entities/dataset.py +1 -1
- fiddler3/entities/events.py +136 -0
- fiddler3/entities/file.py +162 -0
- fiddler3/entities/model.py +141 -24
- fiddler3/entities/model_artifact.py +52 -224
- fiddler3/entities/model_deployment.py +12 -1
- fiddler3/entities/project.py +2 -2
- fiddler3/entities/{model_surrogate.py → surrogate.py} +27 -29
- fiddler3/entities/webhook.py +128 -0
- fiddler3/entities/xai.py +271 -5
- fiddler3/libs/http_client.py +1 -1
- fiddler3/libs/json_encoder.py +1 -1
- fiddler3/schemas/__init__.py +0 -9
- fiddler3/schemas/alert_record.py +27 -0
- fiddler3/schemas/alert_rule.py +39 -0
- fiddler3/schemas/custom_features.py +68 -12
- fiddler3/schemas/events.py +15 -0
- fiddler3/schemas/file.py +23 -0
- fiddler3/schemas/model.py +1 -6
- fiddler3/schemas/model_deployment.py +5 -4
- fiddler3/schemas/webhook.py +23 -0
- fiddler3/tests/apis/test_alert_record.py +99 -0
- fiddler3/tests/apis/test_alert_rule.py +393 -0
- fiddler3/tests/apis/test_events.py +169 -0
- fiddler3/tests/apis/test_files.py +164 -0
- fiddler3/tests/apis/test_model.py +60 -9
- fiddler3/tests/apis/test_model_artifact.py +21 -0
- fiddler3/tests/apis/test_project.py +3 -3
- fiddler3/tests/apis/test_webhook.py +257 -0
- fiddler3/tests/apis/test_xai.py +233 -1
- fiddler3/tests/conftest.py +2 -2
- fiddler3/tests/constants.py +5 -0
- fiddler3/tests/test_connection.py +24 -1
- fiddler3/tests/test_json_encoder.py +2 -2
- fiddler3/utils/helpers.py +20 -0
- {fiddler_client-2.4.0.dev2.dist-info → fiddler_client-2.5.0.dev2.dist-info}/METADATA +15 -5
- {fiddler_client-2.4.0.dev2.dist-info → fiddler_client-2.5.0.dev2.dist-info}/RECORD +59 -44
- tests/fiddler/test_alert.py +95 -5
- fiddler3/entities/helpers.py +0 -21
- fiddler3/schemas/model_artifact.py +0 -6
- {fiddler_client-2.4.0.dev2.dist-info → fiddler_client-2.5.0.dev2.dist-info}/LICENSE.txt +0 -0
- {fiddler_client-2.4.0.dev2.dist-info → fiddler_client-2.5.0.dev2.dist-info}/WHEEL +0 -0
- {fiddler_client-2.4.0.dev2.dist-info → fiddler_client-2.5.0.dev2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, Iterator
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
7
|
+
import pandas as pd
|
|
8
|
+
from requests import Response
|
|
9
|
+
|
|
10
|
+
from fiddler3.configs import STREAM_EVENT_LIMIT
|
|
11
|
+
from fiddler3.connection import ConnectionMixin
|
|
12
|
+
from fiddler3.decorators import handle_api_error
|
|
13
|
+
from fiddler3.entities.file import File
|
|
14
|
+
from fiddler3.entities.job import Job
|
|
15
|
+
from fiddler3.schemas.dataset import EnvType
|
|
16
|
+
from fiddler3.schemas.events import EventsSource, FileSource
|
|
17
|
+
from fiddler3.schemas.job import JobCompactResp
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class EventPublisher(ConnectionMixin):
|
|
21
|
+
STREAM_LIMIT = STREAM_EVENT_LIMIT
|
|
22
|
+
|
|
23
|
+
def __init__(self, model_id: UUID) -> None:
|
|
24
|
+
"""
|
|
25
|
+
Event publishing methods
|
|
26
|
+
|
|
27
|
+
:param model_id: Model identifier
|
|
28
|
+
"""
|
|
29
|
+
self.model_id = model_id
|
|
30
|
+
|
|
31
|
+
def _get_stream_chunks(
|
|
32
|
+
self, source: pd.DataFrame | list[dict[str, Any]]
|
|
33
|
+
) -> Iterator[list[dict[str, Any]]]:
|
|
34
|
+
"""Chunk the source based on stream limit"""
|
|
35
|
+
|
|
36
|
+
for i in range(0, len(source), self.STREAM_LIMIT):
|
|
37
|
+
chunk = source[i : i + self.STREAM_LIMIT]
|
|
38
|
+
|
|
39
|
+
if isinstance(chunk, pd.DataFrame):
|
|
40
|
+
events = chunk.to_dict('records')
|
|
41
|
+
else:
|
|
42
|
+
events = chunk
|
|
43
|
+
|
|
44
|
+
yield events
|
|
45
|
+
|
|
46
|
+
@handle_api_error
|
|
47
|
+
def publish(
|
|
48
|
+
self,
|
|
49
|
+
source: list[dict[str, Any]] | str | Path | pd.DataFrame,
|
|
50
|
+
environment: EnvType = EnvType.PRODUCTION,
|
|
51
|
+
dataset_name: str | None = None,
|
|
52
|
+
update: bool = False,
|
|
53
|
+
) -> list[UUID] | Job:
|
|
54
|
+
"""
|
|
55
|
+
Publish Pre-production or Production data
|
|
56
|
+
|
|
57
|
+
:param source: one of:
|
|
58
|
+
Path or str path: path for data file.
|
|
59
|
+
list[dict]: list of event dicts (max 1000) EnvType.PRE_PRODUCTION is not supported
|
|
60
|
+
dataframe: events dataframe. EnvType.PRE_PRODUCTION not supported.
|
|
61
|
+
:param environment: Either EnvType.PRE_PRODUCTION or EnvType.PRODUCTION
|
|
62
|
+
:param dataset_name: Name of the dataset. Not supported for EnvType.PRODUCTION
|
|
63
|
+
:param update: flag indicating if the events are updates to previously published rows
|
|
64
|
+
|
|
65
|
+
:return: list[UUID] for list of dicts or dataframe source and Job object for file path source.
|
|
66
|
+
"""
|
|
67
|
+
if isinstance(source, (str, Path)):
|
|
68
|
+
return self._publish_file(
|
|
69
|
+
source=source,
|
|
70
|
+
environment=environment,
|
|
71
|
+
dataset_name=dataset_name,
|
|
72
|
+
update=update,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if isinstance(source, (list, pd.DataFrame)):
|
|
76
|
+
return self._publish_stream(source=source, update=update)
|
|
77
|
+
|
|
78
|
+
raise ValueError(f'Unsupported source - {type(source)}')
|
|
79
|
+
|
|
80
|
+
def _publish_stream(
|
|
81
|
+
self,
|
|
82
|
+
source: list[dict[str, Any]] | pd.DataFrame,
|
|
83
|
+
update: bool = False,
|
|
84
|
+
) -> list[UUID]:
|
|
85
|
+
fiddler_ids = []
|
|
86
|
+
for events in self._get_stream_chunks(source=source):
|
|
87
|
+
response = self._publish_call(
|
|
88
|
+
source=EventsSource(events=events),
|
|
89
|
+
environment=EnvType.PRODUCTION,
|
|
90
|
+
dataset_name=None,
|
|
91
|
+
update=update,
|
|
92
|
+
)
|
|
93
|
+
fiddler_ids.extend(response.json()['data']['fiddler_ids'])
|
|
94
|
+
|
|
95
|
+
return fiddler_ids
|
|
96
|
+
|
|
97
|
+
def _publish_file(
|
|
98
|
+
self,
|
|
99
|
+
source: Path | str,
|
|
100
|
+
environment: EnvType,
|
|
101
|
+
dataset_name: str | None = None,
|
|
102
|
+
update: bool = False,
|
|
103
|
+
) -> Job:
|
|
104
|
+
file = File(path=Path(source)).upload()
|
|
105
|
+
|
|
106
|
+
assert file.id is not None
|
|
107
|
+
|
|
108
|
+
response = self._publish_call(
|
|
109
|
+
source=FileSource(file_id=file.id),
|
|
110
|
+
environment=environment,
|
|
111
|
+
dataset_name=dataset_name,
|
|
112
|
+
update=update,
|
|
113
|
+
)
|
|
114
|
+
job_compact = JobCompactResp(**response.json()['data']['job'])
|
|
115
|
+
return Job.get(id_=job_compact.id)
|
|
116
|
+
|
|
117
|
+
def _publish_call(
|
|
118
|
+
self,
|
|
119
|
+
source: FileSource | EventsSource,
|
|
120
|
+
environment: EnvType,
|
|
121
|
+
dataset_name: str | None = None,
|
|
122
|
+
update: bool = False,
|
|
123
|
+
) -> Response:
|
|
124
|
+
if update:
|
|
125
|
+
method = self._client().patch
|
|
126
|
+
else:
|
|
127
|
+
method = self._client().post
|
|
128
|
+
return method(
|
|
129
|
+
url='/v3/events',
|
|
130
|
+
data={
|
|
131
|
+
'source': source.dict(),
|
|
132
|
+
'model_id': self.model_id,
|
|
133
|
+
'env_type': environment,
|
|
134
|
+
'env_name': dataset_name,
|
|
135
|
+
},
|
|
136
|
+
)
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
import os
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
from uuid import UUID
|
|
9
|
+
|
|
10
|
+
from requests import Response
|
|
11
|
+
from requests_toolbelt import MultipartEncoder
|
|
12
|
+
|
|
13
|
+
from fiddler3.constants.common import MULTI_PART_CHUNK_SIZE
|
|
14
|
+
from fiddler3.decorators import handle_api_error
|
|
15
|
+
from fiddler3.entities.base import BaseEntity
|
|
16
|
+
from fiddler3.entities.user import CreatedByMixin, UpdatedByMixin
|
|
17
|
+
from fiddler3.schemas.file import FileResp
|
|
18
|
+
from fiddler3.utils.logger import get_logger
|
|
19
|
+
|
|
20
|
+
logger = get_logger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class File(BaseEntity, CreatedByMixin, UpdatedByMixin):
|
|
24
|
+
def __init__(self, path: str | Path, name: str | None = None) -> None:
|
|
25
|
+
"""Construct a files instance."""
|
|
26
|
+
self.path = path
|
|
27
|
+
self.name = name or os.path.basename(self.path)
|
|
28
|
+
self.id: UUID | None = None
|
|
29
|
+
self.status: str | None = None
|
|
30
|
+
self.type: str | None = None
|
|
31
|
+
|
|
32
|
+
self.created_at: datetime | None = None
|
|
33
|
+
self.updated_at: datetime | None = None
|
|
34
|
+
|
|
35
|
+
# Deserialized response object
|
|
36
|
+
self._resp: FileResp | None = None
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def _from_dict(cls, data: dict) -> File:
|
|
40
|
+
"""Build entity object from the given dictionary"""
|
|
41
|
+
raise NotImplementedError()
|
|
42
|
+
|
|
43
|
+
def _refresh(self, data: dict) -> None:
|
|
44
|
+
"""Refresh the fields of this instance from the given response dictionary"""
|
|
45
|
+
resp_obj = FileResp(**data)
|
|
46
|
+
|
|
47
|
+
# Reset fields
|
|
48
|
+
self.name = resp_obj.name
|
|
49
|
+
|
|
50
|
+
# Add remaining fields
|
|
51
|
+
fields = [
|
|
52
|
+
'id',
|
|
53
|
+
'status',
|
|
54
|
+
'type',
|
|
55
|
+
'created_at',
|
|
56
|
+
'updated_at',
|
|
57
|
+
]
|
|
58
|
+
for field in fields:
|
|
59
|
+
setattr(self, field, getattr(resp_obj, field, None))
|
|
60
|
+
|
|
61
|
+
self._resp = resp_obj
|
|
62
|
+
|
|
63
|
+
@handle_api_error
|
|
64
|
+
def upload(
|
|
65
|
+
self,
|
|
66
|
+
chunk_size: int = MULTI_PART_CHUNK_SIZE,
|
|
67
|
+
) -> File:
|
|
68
|
+
"""Upload file. Single or multipart upload depends on file size"""
|
|
69
|
+
if os.path.getsize(self.path) < chunk_size:
|
|
70
|
+
return self.single_upload()
|
|
71
|
+
|
|
72
|
+
return self.multipart_upload(chunk_size=chunk_size)
|
|
73
|
+
|
|
74
|
+
@handle_api_error
|
|
75
|
+
def single_upload(self) -> File:
|
|
76
|
+
"""Single part file upload"""
|
|
77
|
+
with open(self.path, 'rb') as f:
|
|
78
|
+
multipart_data = MultipartEncoder(fields={'file': (self.name, f)})
|
|
79
|
+
|
|
80
|
+
response = self._client().post(
|
|
81
|
+
url='/v3/files/upload',
|
|
82
|
+
data=multipart_data,
|
|
83
|
+
headers={'Content-Type': multipart_data.content_type},
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
self._refresh_from_response(response)
|
|
87
|
+
return self
|
|
88
|
+
|
|
89
|
+
@handle_api_error
|
|
90
|
+
def multipart_upload(
|
|
91
|
+
self,
|
|
92
|
+
chunk_size: int = MULTI_PART_CHUNK_SIZE,
|
|
93
|
+
) -> File:
|
|
94
|
+
"""Multi part file upload"""
|
|
95
|
+
|
|
96
|
+
# init multipart upload
|
|
97
|
+
self._init_multipart_upload()
|
|
98
|
+
|
|
99
|
+
assert self.id is not None
|
|
100
|
+
# multipart parts upload
|
|
101
|
+
parts = self._multipart_upload(
|
|
102
|
+
chunk_size=chunk_size,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# multipart upload complete
|
|
106
|
+
response = self._multipart_upload_complete(parts=parts)
|
|
107
|
+
|
|
108
|
+
self._refresh_from_response(response)
|
|
109
|
+
return self
|
|
110
|
+
|
|
111
|
+
def _init_multipart_upload(self) -> File:
|
|
112
|
+
logger.info('Multi-part upload initializing')
|
|
113
|
+
response = self._client().post(
|
|
114
|
+
url='/v3/files/multipart-init',
|
|
115
|
+
data={'filename': self.name},
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
self._refresh_from_response(response)
|
|
119
|
+
return self
|
|
120
|
+
|
|
121
|
+
def _multipart_upload(self, chunk_size: int) -> list[dict[str, Any]]:
|
|
122
|
+
parts: list[dict[str, Any]] = []
|
|
123
|
+
part_number = 1
|
|
124
|
+
|
|
125
|
+
with open(self.path, 'rb') as f:
|
|
126
|
+
while True:
|
|
127
|
+
data = f.read(chunk_size)
|
|
128
|
+
if not data:
|
|
129
|
+
break
|
|
130
|
+
|
|
131
|
+
logger.info('Uploading part %d', part_number)
|
|
132
|
+
|
|
133
|
+
response = self._client().post(
|
|
134
|
+
url='/v3/files/multipart-upload',
|
|
135
|
+
files={
|
|
136
|
+
'file': (f'part_{part_number}_{self.name}', io.BytesIO(data)),
|
|
137
|
+
'file_id': (None, str(self.id)),
|
|
138
|
+
'part_number': (None, part_number),
|
|
139
|
+
},
|
|
140
|
+
headers={'Content-Type': None},
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
parts.append(
|
|
144
|
+
{'number': part_number, 'id': response.json()['data']['part_id']}
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
part_number += 1
|
|
148
|
+
|
|
149
|
+
return parts
|
|
150
|
+
|
|
151
|
+
def _multipart_upload_complete(self, parts: list[dict[str, Any]]) -> Response:
|
|
152
|
+
|
|
153
|
+
response = self._client().post(
|
|
154
|
+
url='/v3/files/multipart-complete',
|
|
155
|
+
json={
|
|
156
|
+
'file_id': str(self.id),
|
|
157
|
+
'parts': parts,
|
|
158
|
+
},
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
logger.info('Multi-part upload complete')
|
|
162
|
+
return response
|
fiddler3/entities/model.py
CHANGED
|
@@ -4,29 +4,34 @@ import builtins
|
|
|
4
4
|
import typing
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from datetime import datetime
|
|
7
|
+
from functools import cached_property
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
from typing import Any, Iterator
|
|
9
10
|
from uuid import UUID
|
|
10
11
|
|
|
11
12
|
import pandas as pd
|
|
12
13
|
|
|
14
|
+
from fiddler3.constants.dataset import EnvType
|
|
13
15
|
from fiddler3.constants.model import ModelInputType, ModelTask
|
|
14
16
|
from fiddler3.decorators import handle_api_error
|
|
15
17
|
from fiddler3.entities.base import BaseEntity
|
|
16
|
-
from fiddler3.entities.
|
|
18
|
+
from fiddler3.entities.events import EventPublisher
|
|
17
19
|
from fiddler3.entities.job import Job
|
|
18
|
-
from fiddler3.entities.model_artifact import
|
|
19
|
-
from fiddler3.entities.
|
|
20
|
+
from fiddler3.entities.model_artifact import ModelArtifact
|
|
21
|
+
from fiddler3.entities.model_deployment import ModelDeployment
|
|
20
22
|
from fiddler3.entities.project import ProjectCompactMixin
|
|
23
|
+
from fiddler3.entities.surrogate import Surrogate
|
|
21
24
|
from fiddler3.entities.user import CreatedByMixin, UpdatedByMixin
|
|
22
25
|
from fiddler3.entities.xai import XaiMixin
|
|
26
|
+
from fiddler3.schemas.deployment_params import DeploymentParams
|
|
23
27
|
from fiddler3.schemas.filter_query import OperatorType, QueryCondition, QueryRule
|
|
24
28
|
from fiddler3.schemas.job import JobCompactResp
|
|
25
29
|
from fiddler3.schemas.model import ModelResp
|
|
26
|
-
from fiddler3.schemas.model_schema import
|
|
30
|
+
from fiddler3.schemas.model_schema import ModelSchema
|
|
27
31
|
from fiddler3.schemas.model_spec import ModelSpec
|
|
28
32
|
from fiddler3.schemas.model_task_params import ModelTaskParams
|
|
29
33
|
from fiddler3.schemas.xai_params import XaiParams
|
|
34
|
+
from fiddler3.utils.helpers import raise_not_found
|
|
30
35
|
from fiddler3.utils.model_schema_generator import SchemaGeneratorFactory
|
|
31
36
|
|
|
32
37
|
if typing.TYPE_CHECKING:
|
|
@@ -35,11 +40,9 @@ if typing.TYPE_CHECKING:
|
|
|
35
40
|
|
|
36
41
|
class Model(
|
|
37
42
|
BaseEntity,
|
|
38
|
-
ArtifactMixin,
|
|
39
43
|
CreatedByMixin,
|
|
40
44
|
ProjectCompactMixin,
|
|
41
45
|
UpdatedByMixin,
|
|
42
|
-
SurrogateMixin,
|
|
43
46
|
XaiMixin,
|
|
44
47
|
): # pylint: disable=too-many-ancestors
|
|
45
48
|
def __init__( # pylint: disable=too-many-arguments
|
|
@@ -47,7 +50,7 @@ class Model(
|
|
|
47
50
|
name: str,
|
|
48
51
|
project_id: UUID,
|
|
49
52
|
schema: ModelSchema,
|
|
50
|
-
spec: ModelSpec
|
|
53
|
+
spec: ModelSpec,
|
|
51
54
|
input_type: str = ModelInputType.TABULAR,
|
|
52
55
|
task: str = ModelTask.NOT_SET,
|
|
53
56
|
task_params: ModelTaskParams | None = None,
|
|
@@ -67,18 +70,13 @@ class Model(
|
|
|
67
70
|
self.event_id_col = event_id_col
|
|
68
71
|
self.event_ts_col = event_ts_col
|
|
69
72
|
self.event_ts_format = event_ts_format
|
|
70
|
-
self.spec = spec
|
|
73
|
+
self.spec = spec
|
|
71
74
|
self.task_params = task_params or ModelTaskParams()
|
|
72
75
|
self.xai_params = xai_params or XaiParams()
|
|
73
76
|
|
|
74
77
|
self.id: UUID | None = None
|
|
75
78
|
self.artifact_status: str | None = None
|
|
76
79
|
self.artifact_files: list[dict] | None = None
|
|
77
|
-
self.input_cols: list[Column] | None = None
|
|
78
|
-
self.output_cols: list[Column] | None = None
|
|
79
|
-
self.target_cols: list[Column] | None = None
|
|
80
|
-
self.metadata_cols: list[Column] | None = None
|
|
81
|
-
self.decision_cols: list[Column] | None = None
|
|
82
80
|
self.is_binary_ranking_model: bool | None = None
|
|
83
81
|
self.created_at: datetime | None = None
|
|
84
82
|
self.updated_at: datetime | None = None
|
|
@@ -122,11 +120,6 @@ class Model(
|
|
|
122
120
|
'updated_at',
|
|
123
121
|
'artifact_status',
|
|
124
122
|
'artifact_files',
|
|
125
|
-
'input_cols',
|
|
126
|
-
'output_cols',
|
|
127
|
-
'target_cols',
|
|
128
|
-
'metadata_cols',
|
|
129
|
-
'decision_cols',
|
|
130
123
|
'is_binary_ranking_model',
|
|
131
124
|
]
|
|
132
125
|
for field in fields:
|
|
@@ -160,11 +153,6 @@ class Model(
|
|
|
160
153
|
'updated_at',
|
|
161
154
|
'artifact_status',
|
|
162
155
|
'artifact_files',
|
|
163
|
-
'input_cols',
|
|
164
|
-
'output_cols',
|
|
165
|
-
'target_cols',
|
|
166
|
-
'metadata_cols',
|
|
167
|
-
'decision_cols',
|
|
168
156
|
'is_binary_ranking_model',
|
|
169
157
|
]
|
|
170
158
|
for field in fields:
|
|
@@ -172,6 +160,24 @@ class Model(
|
|
|
172
160
|
|
|
173
161
|
self._resp = resp_obj
|
|
174
162
|
|
|
163
|
+
@cached_property
|
|
164
|
+
def _artifact(self) -> ModelArtifact:
|
|
165
|
+
"""Model artifact instance"""
|
|
166
|
+
assert self.id is not None
|
|
167
|
+
return ModelArtifact(model_id=self.id)
|
|
168
|
+
|
|
169
|
+
@cached_property
|
|
170
|
+
def _surrogate(self) -> Surrogate:
|
|
171
|
+
"""Model artifact instance"""
|
|
172
|
+
assert self.id is not None
|
|
173
|
+
return Surrogate(model_id=self.id)
|
|
174
|
+
|
|
175
|
+
@cached_property
|
|
176
|
+
def _event_publisher(self) -> EventPublisher:
|
|
177
|
+
"""Event publisher instance"""
|
|
178
|
+
assert self.id is not None
|
|
179
|
+
return EventPublisher(model_id=self.id)
|
|
180
|
+
|
|
175
181
|
@classmethod
|
|
176
182
|
@handle_api_error
|
|
177
183
|
def get(cls, id_: UUID | str) -> Model:
|
|
@@ -201,7 +207,7 @@ class Model(
|
|
|
201
207
|
if response.json()['data']['total'] == 0:
|
|
202
208
|
raise_not_found('Model not found for the given identifier')
|
|
203
209
|
|
|
204
|
-
return cls.
|
|
210
|
+
return cls.get(id_=response.json()['data']['items'][0]['id'])
|
|
205
211
|
|
|
206
212
|
@handle_api_error
|
|
207
213
|
def create(self) -> Model:
|
|
@@ -268,6 +274,14 @@ class Model(
|
|
|
268
274
|
|
|
269
275
|
yield from Dataset.list(model_id=self.id)
|
|
270
276
|
|
|
277
|
+
@property
|
|
278
|
+
@handle_api_error
|
|
279
|
+
def model_deployment(self) -> ModelDeployment:
|
|
280
|
+
"""Fetch all the deployments for this model"""
|
|
281
|
+
assert self.id is not None
|
|
282
|
+
|
|
283
|
+
return ModelDeployment.of(model_id=self.id)
|
|
284
|
+
|
|
271
285
|
@classmethod
|
|
272
286
|
@handle_api_error
|
|
273
287
|
def generate_schema(
|
|
@@ -304,6 +318,109 @@ class Model(
|
|
|
304
318
|
job_compact = JobCompactResp(**response.json()['data']['job'])
|
|
305
319
|
return Job.get(id_=job_compact.id)
|
|
306
320
|
|
|
321
|
+
@handle_api_error
|
|
322
|
+
def publish(
|
|
323
|
+
self,
|
|
324
|
+
source: builtins.list[dict[str, Any]] | str | Path | pd.DataFrame,
|
|
325
|
+
environment: EnvType = EnvType.PRODUCTION,
|
|
326
|
+
dataset_name: str | None = None,
|
|
327
|
+
update: bool = False,
|
|
328
|
+
) -> builtins.list[UUID] | Job:
|
|
329
|
+
"""
|
|
330
|
+
Publish Pre-production or Production data
|
|
331
|
+
|
|
332
|
+
:param source: one of:
|
|
333
|
+
Path or str path: path for data file.
|
|
334
|
+
list[dict]: list of events (max 1000) PRE_PRODUCTION environment is not
|
|
335
|
+
supported
|
|
336
|
+
dataframe: events dataframe. EnvType.PRE_PRODUCTION not supported.
|
|
337
|
+
:param environment: Either EnvType.PRE_PRODUCTION or EnvType.PRODUCTION
|
|
338
|
+
:param dataset_name: Name of the dataset. Not supported for EnvType.PRODUCTION
|
|
339
|
+
:param update: flag indicating if the events are updates to previously
|
|
340
|
+
published rows
|
|
341
|
+
|
|
342
|
+
:return: list[UUID] for list of dicts or dataframe source and Job object for
|
|
343
|
+
file path source.
|
|
344
|
+
"""
|
|
345
|
+
|
|
346
|
+
return self._event_publisher.publish(
|
|
347
|
+
source=source,
|
|
348
|
+
environment=environment,
|
|
349
|
+
dataset_name=dataset_name,
|
|
350
|
+
update=update,
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
def add_artifact(
|
|
354
|
+
self,
|
|
355
|
+
model_dir: str | Path,
|
|
356
|
+
deployment_params: DeploymentParams | None = None,
|
|
357
|
+
) -> Job:
|
|
358
|
+
"""
|
|
359
|
+
Upload and deploy model artifact.
|
|
360
|
+
|
|
361
|
+
:param model_dir: Path to model artifact tar file
|
|
362
|
+
:param deployment_params: Model deployment parameters
|
|
363
|
+
:return: Async job instance
|
|
364
|
+
"""
|
|
365
|
+
return self._artifact.add(
|
|
366
|
+
model_dir=model_dir, deployment_params=deployment_params
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
def update_artifact(
|
|
370
|
+
self,
|
|
371
|
+
model_dir: str | Path,
|
|
372
|
+
deployment_params: DeploymentParams | None = None,
|
|
373
|
+
) -> Job:
|
|
374
|
+
"""
|
|
375
|
+
Update existing model artifact.
|
|
376
|
+
|
|
377
|
+
:param model_dir: Path to model artifact tar file
|
|
378
|
+
:param deployment_params: Model deployment parameters
|
|
379
|
+
:return: Async job instance
|
|
380
|
+
"""
|
|
381
|
+
return self._artifact.update(
|
|
382
|
+
model_dir=model_dir, deployment_params=deployment_params
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
def download_artifact(
|
|
386
|
+
self,
|
|
387
|
+
output_dir: str | Path,
|
|
388
|
+
) -> None:
|
|
389
|
+
"""
|
|
390
|
+
Download existing model artifact.
|
|
391
|
+
|
|
392
|
+
:param output_dir: Path to download model artifact tar file
|
|
393
|
+
"""
|
|
394
|
+
|
|
395
|
+
self._artifact.download(output_dir=output_dir)
|
|
396
|
+
|
|
397
|
+
def add_surrogate(
|
|
398
|
+
self,
|
|
399
|
+
deployment_params: DeploymentParams | None = None,
|
|
400
|
+
) -> Job:
|
|
401
|
+
"""
|
|
402
|
+
Add a new surrogate model
|
|
403
|
+
|
|
404
|
+
:param deployment_params: Model deployment parameters
|
|
405
|
+
:return: Async job
|
|
406
|
+
"""
|
|
407
|
+
job = self._surrogate.add(deployment_params=deployment_params)
|
|
408
|
+
return job
|
|
409
|
+
|
|
410
|
+
@handle_api_error
|
|
411
|
+
def update_surrogate(
|
|
412
|
+
self,
|
|
413
|
+
deployment_params: DeploymentParams | None = None,
|
|
414
|
+
) -> Job:
|
|
415
|
+
"""
|
|
416
|
+
Update an existing surrogate model
|
|
417
|
+
|
|
418
|
+
:param deployment_params: Model deployment parameters
|
|
419
|
+
:return: Async job
|
|
420
|
+
"""
|
|
421
|
+
job = self._surrogate.update(deployment_params=deployment_params)
|
|
422
|
+
return job
|
|
423
|
+
|
|
307
424
|
|
|
308
425
|
@dataclass
|
|
309
426
|
class ModelCompact:
|