qwak-sdk 0.5.30__py3-none-any.whl → 0.5.32__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 qwak-sdk might be problematic. Click here for more details.
- qwak_sdk/__init__.py +1 -1
- qwak_sdk/commands/feature_store/backfill/__init__.py +0 -0
- qwak_sdk/commands/feature_store/backfill/_logic.py +140 -0
- qwak_sdk/commands/feature_store/backfill/ui.py +129 -0
- qwak_sdk/commands/feature_store/execution/__init__.py +0 -0
- qwak_sdk/commands/feature_store/execution/ui.py +19 -0
- qwak_sdk/commands/feature_store/feature_store_command_group.py +4 -0
- qwak_sdk/commands/models/build/_logic/client_logs/logger.py +2 -1
- qwak_sdk/commands/models/builds/status/_logic.py +2 -2
- qwak_sdk/commands/models/deployments/deploy/_logic/get_latest_successful_build.py +5 -8
- qwak_sdk/commands/models/deployments/undeploy/_logic/request_undeploy.py +103 -1
- qwak_sdk/commands/models/deployments/undeploy/ui.py +8 -5
- qwak_sdk/commands/models/describe/_logic.py +123 -108
- qwak_sdk/tools/log_handling.py +8 -6
- {qwak_sdk-0.5.30.dist-info → qwak_sdk-0.5.32.dist-info}/METADATA +3 -2
- {qwak_sdk-0.5.30.dist-info → qwak_sdk-0.5.32.dist-info}/RECORD +18 -13
- {qwak_sdk-0.5.30.dist-info → qwak_sdk-0.5.32.dist-info}/WHEEL +0 -0
- {qwak_sdk-0.5.30.dist-info → qwak_sdk-0.5.32.dist-info}/entry_points.txt +0 -0
qwak_sdk/__init__.py
CHANGED
|
File without changes
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
from datetime import datetime, timezone
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from _qwak_proto.qwak.feature_store.features.feature_set_pb2 import FeatureSet
|
|
5
|
+
from _qwak_proto.qwak.feature_store.features.feature_set_service_pb2 import (
|
|
6
|
+
GetFeatureSetByNameResponse,
|
|
7
|
+
)
|
|
8
|
+
from croniter import croniter
|
|
9
|
+
from qwak.clients.feature_store import FeatureRegistryClient
|
|
10
|
+
from qwak.feature_store.execution.backfill import Backfill
|
|
11
|
+
from qwak.feature_store.feature_sets.batch import BatchFeatureSetV1
|
|
12
|
+
|
|
13
|
+
from qwak_sdk.inner.tools.cli_tools import ask_yesno
|
|
14
|
+
from qwak_sdk.tools.colors import Color
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class _BackfillLogic:
|
|
18
|
+
featureset: FeatureSet
|
|
19
|
+
batch_featureset: BatchFeatureSetV1
|
|
20
|
+
|
|
21
|
+
def configure_window_backfill(
|
|
22
|
+
self,
|
|
23
|
+
featureset_name: str,
|
|
24
|
+
start_time: Optional[datetime] = None,
|
|
25
|
+
stop_time: Optional[datetime] = None,
|
|
26
|
+
comment: str = None,
|
|
27
|
+
cluster_template: str = None,
|
|
28
|
+
) -> Optional[Backfill]:
|
|
29
|
+
"""
|
|
30
|
+
Configures a window back-fill object. This type of backfill will not reset the entire featureset, but will be
|
|
31
|
+
performed from the requested start time to the requested stop time in ticks calculated according to the
|
|
32
|
+
feature sets scheduling policy.
|
|
33
|
+
@param featureset_name: feature set name to perform the backfill job for
|
|
34
|
+
@type featureset_name: str
|
|
35
|
+
@param start_time: backfill requested start date
|
|
36
|
+
@type start_time: optional[datetime]
|
|
37
|
+
@param stop_time:
|
|
38
|
+
@type stop_time: optional[datetime]
|
|
39
|
+
@param comment:
|
|
40
|
+
@type comment: str
|
|
41
|
+
@param cluster_template:
|
|
42
|
+
@type cluster_template: str
|
|
43
|
+
@return:
|
|
44
|
+
@rtype:
|
|
45
|
+
"""
|
|
46
|
+
print(
|
|
47
|
+
f"{Color.BLUE} Backfilling from {start_time} to {stop_time}, "
|
|
48
|
+
f"the following ticks are going to be performed {Color.END}"
|
|
49
|
+
)
|
|
50
|
+
zipped_tick_strs = Backfill.generate_expected_ticks_repr(
|
|
51
|
+
scheduling_policy=self.batch_featureset.scheduling_policy,
|
|
52
|
+
start_time=start_time,
|
|
53
|
+
stop_time=stop_time,
|
|
54
|
+
)
|
|
55
|
+
print("\n".join(zipped_tick_strs))
|
|
56
|
+
if ask_yesno("", force=False):
|
|
57
|
+
backfill_execution = Backfill(
|
|
58
|
+
featureset_name=featureset_name,
|
|
59
|
+
comment=comment,
|
|
60
|
+
cluster_template=cluster_template,
|
|
61
|
+
start_time=start_time,
|
|
62
|
+
stop_time=stop_time,
|
|
63
|
+
)
|
|
64
|
+
return backfill_execution
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def _get_featureset_by_name(featureset_name: str) -> GetFeatureSetByNameResponse:
|
|
69
|
+
registry_client = FeatureRegistryClient()
|
|
70
|
+
|
|
71
|
+
return registry_client.get_feature_set_by_name(feature_set_name=featureset_name)
|
|
72
|
+
|
|
73
|
+
def get_start_stop_times(
|
|
74
|
+
self,
|
|
75
|
+
requested_start_time: Optional[datetime] = None,
|
|
76
|
+
requested_stop_time: Optional[datetime] = None,
|
|
77
|
+
) -> tuple:
|
|
78
|
+
start_time: datetime = (
|
|
79
|
+
requested_start_time or self.batch_featureset.backfill.start_date
|
|
80
|
+
).replace(tzinfo=timezone.utc)
|
|
81
|
+
stop_time: datetime
|
|
82
|
+
|
|
83
|
+
if start_time and start_time > datetime.now().replace(tzinfo=timezone.utc):
|
|
84
|
+
stop_time = datetime.utcnow().replace(tzinfo=timezone.utc)
|
|
85
|
+
else:
|
|
86
|
+
stop_time = (
|
|
87
|
+
requested_stop_time or datetime.utcnow().replace(tzinfo=timezone.utc)
|
|
88
|
+
).replace(tzinfo=timezone.utc)
|
|
89
|
+
|
|
90
|
+
return start_time, stop_time
|
|
91
|
+
|
|
92
|
+
def is_valid_input(
|
|
93
|
+
self,
|
|
94
|
+
reset_backfill: bool,
|
|
95
|
+
start_time: Optional[datetime],
|
|
96
|
+
stop_time: Optional[datetime],
|
|
97
|
+
featureset_name: str,
|
|
98
|
+
) -> bool:
|
|
99
|
+
# Validate reset_backfill or start and stop times
|
|
100
|
+
if (not reset_backfill and not start_time and not stop_time) or (
|
|
101
|
+
reset_backfill and (start_time or stop_time)
|
|
102
|
+
):
|
|
103
|
+
print(
|
|
104
|
+
f"{Color.RED} please specify either --reset-backfill or one or more of --start-time/--stop-time"
|
|
105
|
+
)
|
|
106
|
+
return False
|
|
107
|
+
|
|
108
|
+
# Validate featureset exists
|
|
109
|
+
get_featureset_resp = self._get_featureset_by_name(
|
|
110
|
+
featureset_name=featureset_name
|
|
111
|
+
)
|
|
112
|
+
if not get_featureset_resp or not get_featureset_resp.feature_set:
|
|
113
|
+
print(f"{Color.RED} Failed to retrieve featureset {featureset_name}")
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
self.featureset = get_featureset_resp.feature_set
|
|
117
|
+
fs_type_attr: str = self.featureset.feature_set_definition.feature_set_spec.feature_set_type.WhichOneof(
|
|
118
|
+
"set_type"
|
|
119
|
+
)
|
|
120
|
+
# Validate featureset is batch v1
|
|
121
|
+
if fs_type_attr != "batch_feature_set_v1":
|
|
122
|
+
print(
|
|
123
|
+
f"{Color.RED} Feature set {featureset_name} is of type {fs_type_attr}. Only BatchV1 is supported."
|
|
124
|
+
)
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
self.batch_featureset = BatchFeatureSetV1.from_proto(
|
|
128
|
+
self.featureset.feature_set_definition.feature_set_spec
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# Validate scheduling policy
|
|
132
|
+
if not self.batch_featureset.scheduling_policy or not croniter.is_valid(
|
|
133
|
+
self.batch_featureset.scheduling_policy
|
|
134
|
+
):
|
|
135
|
+
print(
|
|
136
|
+
f"{Color.RED} Feature set {featureset_name} has an invalid scheduling policy {self.batch_featureset.scheduling_policy}."
|
|
137
|
+
)
|
|
138
|
+
return False
|
|
139
|
+
|
|
140
|
+
return True
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
from qwak.feature_store.execution.backfill import Backfill
|
|
6
|
+
|
|
7
|
+
from qwak_sdk.commands.feature_store.backfill._logic import _BackfillLogic
|
|
8
|
+
from qwak_sdk.inner.tools.cli_tools import QwakCommand, ask_yesno
|
|
9
|
+
from qwak_sdk.tools.colors import Color
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.command(
|
|
13
|
+
"backfill", cls=QwakCommand, help="Trigger a backfill process for a Feature Set"
|
|
14
|
+
)
|
|
15
|
+
@click.option(
|
|
16
|
+
"--feature-set",
|
|
17
|
+
"--name",
|
|
18
|
+
help="Feature Set name to perform the backfill process for",
|
|
19
|
+
required=True,
|
|
20
|
+
type=click.STRING,
|
|
21
|
+
)
|
|
22
|
+
@click.option(
|
|
23
|
+
"--reset-backfill",
|
|
24
|
+
"--reset",
|
|
25
|
+
is_flag=True,
|
|
26
|
+
metavar="FLAG",
|
|
27
|
+
default=False,
|
|
28
|
+
help="Perform a complete reset of the featuresets data. "
|
|
29
|
+
"This will result in the deletion of the current existing data.",
|
|
30
|
+
)
|
|
31
|
+
@click.option(
|
|
32
|
+
"--start-time",
|
|
33
|
+
type=click.DateTime(),
|
|
34
|
+
default=None,
|
|
35
|
+
required=False,
|
|
36
|
+
help="The time from which the featuresets data should be backfilled in UTC. "
|
|
37
|
+
"Defaults to the featuresets configured backfill start time.",
|
|
38
|
+
)
|
|
39
|
+
@click.option(
|
|
40
|
+
"--stop-time",
|
|
41
|
+
type=click.DateTime(),
|
|
42
|
+
default=None,
|
|
43
|
+
required=False,
|
|
44
|
+
help="The time up until the featuresets data should be backfilled in UTC. Defaults to the current timestamp. "
|
|
45
|
+
"If the time provided is in the future, stop time will be rounded down to the current time. ",
|
|
46
|
+
)
|
|
47
|
+
@click.option(
|
|
48
|
+
"--cluster-template",
|
|
49
|
+
type=str,
|
|
50
|
+
default=None,
|
|
51
|
+
required=False,
|
|
52
|
+
help="Backfill resource configuration, expects a ClusterType size. "
|
|
53
|
+
"Defaults to the featureset resource configuration",
|
|
54
|
+
)
|
|
55
|
+
@click.option(
|
|
56
|
+
"--comment",
|
|
57
|
+
type=str,
|
|
58
|
+
default=None,
|
|
59
|
+
required=False,
|
|
60
|
+
help="Backfill job optional comment tag line",
|
|
61
|
+
)
|
|
62
|
+
def backfill(
|
|
63
|
+
feature_set: str,
|
|
64
|
+
reset_backfill: bool,
|
|
65
|
+
start_time: Optional[datetime],
|
|
66
|
+
stop_time: Optional[datetime],
|
|
67
|
+
cluster_template: Optional[str],
|
|
68
|
+
comment: Optional[str],
|
|
69
|
+
**kwargs,
|
|
70
|
+
):
|
|
71
|
+
backill_logic = _BackfillLogic()
|
|
72
|
+
if not backill_logic.is_valid_input(
|
|
73
|
+
reset_backfill=reset_backfill,
|
|
74
|
+
start_time=start_time,
|
|
75
|
+
stop_time=stop_time,
|
|
76
|
+
featureset_name=feature_set,
|
|
77
|
+
):
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
if reset_backfill:
|
|
81
|
+
backfill_execution = _configure_backfill_triggered(
|
|
82
|
+
featureset_name=feature_set,
|
|
83
|
+
comment=comment,
|
|
84
|
+
cluster_template=cluster_template,
|
|
85
|
+
)
|
|
86
|
+
else:
|
|
87
|
+
start, stop = backill_logic.get_start_stop_times(
|
|
88
|
+
requested_start_time=start_time, requested_stop_time=stop_time
|
|
89
|
+
)
|
|
90
|
+
if start >= stop:
|
|
91
|
+
print(
|
|
92
|
+
f"{Color.RED} Stop time {stop.isoformat()} must be after start time {start.isoformat()}"
|
|
93
|
+
)
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
backfill_execution = backill_logic.configure_window_backfill(
|
|
97
|
+
featureset_name=feature_set,
|
|
98
|
+
start_time=start,
|
|
99
|
+
stop_time=stop,
|
|
100
|
+
comment=comment,
|
|
101
|
+
cluster_template=cluster_template,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
if backfill_execution:
|
|
105
|
+
execution_id: str = backfill_execution.trigger_batch_backfill()
|
|
106
|
+
print(
|
|
107
|
+
f"{Color.BLUE}✅ Triggered backfill execution for featureset {feature_set}, "
|
|
108
|
+
f"execution id for follow up on is {execution_id}."
|
|
109
|
+
)
|
|
110
|
+
print(
|
|
111
|
+
f"To inquire the status of the execution run "
|
|
112
|
+
f"'qwak features execution-status --execution-id {execution_id}'"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _configure_backfill_triggered(
|
|
117
|
+
featureset_name: str, comment: str, cluster_template: str
|
|
118
|
+
) -> Optional[Backfill]:
|
|
119
|
+
if ask_yesno(
|
|
120
|
+
f"You are about to remove and re-populate all data in {featureset_name}",
|
|
121
|
+
force=False,
|
|
122
|
+
):
|
|
123
|
+
print(f"{Color.RED} - A backfill reset was triggered")
|
|
124
|
+
return Backfill(
|
|
125
|
+
featureset_name=featureset_name,
|
|
126
|
+
comment=comment,
|
|
127
|
+
cluster_template=cluster_template,
|
|
128
|
+
)
|
|
129
|
+
return None
|
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from qwak.feature_store.execution.execution_query import ExecutionQuery
|
|
3
|
+
|
|
4
|
+
from qwak_sdk.inner.tools.cli_tools import QwakCommand
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@click.command(
|
|
8
|
+
"execution-status",
|
|
9
|
+
cls=QwakCommand,
|
|
10
|
+
help="Retrieve the current status of an execution (backfill/batch job ingestion)",
|
|
11
|
+
)
|
|
12
|
+
@click.option(
|
|
13
|
+
"--execution-id",
|
|
14
|
+
type=str,
|
|
15
|
+
required=True,
|
|
16
|
+
help="The id of the execution to retrieve the status for.",
|
|
17
|
+
)
|
|
18
|
+
def execution_status(execution_id: str, **kwargs):
|
|
19
|
+
print(ExecutionQuery.get_execution_status_message(execution_id=execution_id))
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import click
|
|
2
2
|
|
|
3
|
+
from qwak_sdk.commands.feature_store.backfill.ui import backfill
|
|
3
4
|
from qwak_sdk.commands.feature_store.delete.ui import delete_fs_object
|
|
5
|
+
from qwak_sdk.commands.feature_store.execution.ui import execution_status
|
|
4
6
|
from qwak_sdk.commands.feature_store.list.ui import list_feature_sets
|
|
5
7
|
from qwak_sdk.commands.feature_store.pause.ui import pause_feature_set
|
|
6
8
|
from qwak_sdk.commands.feature_store.register.ui import register_fs_objects
|
|
@@ -23,3 +25,5 @@ feature_store_commands_group.add_command(pause_feature_set)
|
|
|
23
25
|
feature_store_commands_group.add_command(resume_feature_set)
|
|
24
26
|
feature_store_commands_group.add_command(trigger_feature_set)
|
|
25
27
|
feature_store_commands_group.add_command(register_fs_objects)
|
|
28
|
+
feature_store_commands_group.add_command(backfill)
|
|
29
|
+
feature_store_commands_group.add_command(execution_status)
|
|
@@ -29,6 +29,7 @@ from qwak_sdk.inner.tools.logger.logger import (
|
|
|
29
29
|
BUILDS_LOGS = HOST_QWAK_HIDDEN_FOLDER / "logs" / "build"
|
|
30
30
|
BUILD_LOG_NAME = "build.log"
|
|
31
31
|
MAX_LOGS_NUMBER = 15
|
|
32
|
+
DEBUG_LEVEL = 2
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
@contextlib.contextmanager
|
|
@@ -76,7 +77,7 @@ def setup_logger(
|
|
|
76
77
|
set_handler_verbosity(
|
|
77
78
|
logger,
|
|
78
79
|
REMOTE_CONSOLE_HANDLER_NAME,
|
|
79
|
-
VERBOSITY_LEVEL_MAPPING[
|
|
80
|
+
VERBOSITY_LEVEL_MAPPING[DEBUG_LEVEL],
|
|
80
81
|
)
|
|
81
82
|
|
|
82
83
|
return logger
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from _qwak_proto.qwak.builds.builds_pb2 import BuildStatus
|
|
2
|
-
from qwak.clients.
|
|
2
|
+
from qwak.clients.build_orchestrator import BuildOrchestratorClient
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
def execute_get_build_status(build_id) -> BuildStatus:
|
|
6
|
-
return
|
|
6
|
+
return BuildOrchestratorClient().get_build(build_id).build.build_status
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
from copy import deepcopy
|
|
2
|
-
|
|
3
1
|
from _qwak_proto.qwak.builds.builds_pb2 import SUCCESSFUL
|
|
4
|
-
from qwak.clients.
|
|
2
|
+
from qwak.clients.build_orchestrator import BuildOrchestratorClient
|
|
5
3
|
from qwak.clients.model_management.client import ModelsManagementClient
|
|
6
4
|
from qwak.exceptions import QwakException
|
|
7
5
|
|
|
@@ -20,12 +18,11 @@ def get_latest_successful_build_from_model(model_id: str) -> str:
|
|
|
20
18
|
"""
|
|
21
19
|
model = ModelsManagementClient().get_model(model_id)
|
|
22
20
|
model_uuid = model.uuid
|
|
23
|
-
|
|
24
|
-
builds =
|
|
25
|
-
builds
|
|
26
|
-
builds.sort(key=lambda r: r.created_at.seconds)
|
|
21
|
+
all_builds = BuildOrchestratorClient().list_builds(model_uuid).build
|
|
22
|
+
builds = [build for build in all_builds if build.build_status == SUCCESSFUL]
|
|
23
|
+
builds.sort(key=lambda r: r.audit.created_at.seconds)
|
|
27
24
|
|
|
28
25
|
if not builds:
|
|
29
26
|
raise QwakException(f"Unable to find successful build for model {model_id}")
|
|
30
27
|
|
|
31
|
-
return builds[-1].
|
|
28
|
+
return builds[-1].buildId
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from time import sleep
|
|
1
2
|
from typing import Dict, List, Set
|
|
2
3
|
|
|
3
4
|
from _qwak_proto.qwak.audience.v1.audience_pb2 import AudienceRoutesEntry
|
|
@@ -24,13 +25,19 @@ from qwak_sdk.commands.models.deployments.deploy._logic.deploy_config import (
|
|
|
24
25
|
from qwak_sdk.commands.models.deployments.undeploy._logic.variations import (
|
|
25
26
|
validate_variations_for_undeploy,
|
|
26
27
|
)
|
|
28
|
+
from qwak_sdk.tools.utils import qwak_spinner
|
|
27
29
|
|
|
28
30
|
NO_DEPLOYED_VARIATIONS_ERROR_MSG = (
|
|
29
31
|
"There are currently no deployed variations for model {model_id} in {env_name}"
|
|
30
32
|
)
|
|
31
|
-
|
|
33
|
+
UNDEPLOY_ERROR_MSG = "Environment {environment_id} failed with status: {status}"
|
|
32
34
|
logger = get_qwak_logger()
|
|
33
35
|
|
|
36
|
+
FAILED_UNDEPLOYMENT_STATUS = ["FAILED_UNDEPLOYMENT"]
|
|
37
|
+
SUCCESSFUL_UNDEPLOYMENT_STATUS = ["SUCCESSFUL_UNDEPLOYMENT"]
|
|
38
|
+
END_UNDEPLOYMENT_STATUSES = SUCCESSFUL_UNDEPLOYMENT_STATUS + FAILED_UNDEPLOYMENT_STATUS
|
|
39
|
+
TIME_TO_WAIT_FOR_UNDEPLOYMENT_POLLING = 5
|
|
40
|
+
|
|
34
41
|
|
|
35
42
|
def get_deployed_variation_name(existing_variations_names: Set[str]) -> str:
|
|
36
43
|
return list(existing_variations_names)[0]
|
|
@@ -123,6 +130,7 @@ def undeploy(
|
|
|
123
130
|
model_id: str,
|
|
124
131
|
config: DeployConfig,
|
|
125
132
|
model_uuid: str = "",
|
|
133
|
+
sync: bool = False,
|
|
126
134
|
):
|
|
127
135
|
deployment_client = DeploymentManagementClient()
|
|
128
136
|
ecosystem_client = EcosystemClient()
|
|
@@ -161,14 +169,108 @@ def undeploy(
|
|
|
161
169
|
config.realtime.fallback_variation,
|
|
162
170
|
)
|
|
163
171
|
|
|
172
|
+
environment_to_deployment_id = {}
|
|
173
|
+
if sync:
|
|
174
|
+
environment_to_deployment_id = __get_environment_to_deployment_id(
|
|
175
|
+
deployment_client, model_id, model_uuid, env_undeployment_requests
|
|
176
|
+
)
|
|
177
|
+
|
|
164
178
|
undeployment_response = deployment_client.undeploy_model(
|
|
165
179
|
model_id=model_id,
|
|
166
180
|
model_uuid=model_uuid,
|
|
167
181
|
env_undeployment_requests=env_undeployment_requests,
|
|
168
182
|
)
|
|
169
183
|
|
|
184
|
+
if sync:
|
|
185
|
+
__sync_undeploy(
|
|
186
|
+
environment_to_deployment_id=environment_to_deployment_id,
|
|
187
|
+
model_id=model_id,
|
|
188
|
+
deployment_client=deployment_client,
|
|
189
|
+
)
|
|
170
190
|
logger.info(
|
|
171
191
|
f"Current status is {ModelDeploymentStatus.Name(undeployment_response.status)}."
|
|
172
192
|
)
|
|
173
193
|
|
|
174
194
|
return undeployment_response
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def __get_environment_to_deployment_id(
|
|
198
|
+
deployment_client: DeploymentManagementClient,
|
|
199
|
+
model_id: str,
|
|
200
|
+
model_uuid: str,
|
|
201
|
+
env_undeployment_requests: Dict,
|
|
202
|
+
) -> Dict:
|
|
203
|
+
deployment_details = deployment_client.get_deployment_details(
|
|
204
|
+
model_id, model_uuid
|
|
205
|
+
).environment_to_deployment_details
|
|
206
|
+
result = {}
|
|
207
|
+
for env_id, env_deployment_details in env_undeployment_requests.items():
|
|
208
|
+
deployment_detail_by_env = deployment_details.get(env_id)
|
|
209
|
+
if deployment_detail_by_env:
|
|
210
|
+
for deployment_detail in deployment_detail_by_env.deployments_details:
|
|
211
|
+
if (
|
|
212
|
+
env_id == deployment_detail.environment_id
|
|
213
|
+
and deployment_detail.variation.name
|
|
214
|
+
== env_deployment_details.traffic_config.selected_variation_name
|
|
215
|
+
):
|
|
216
|
+
result[env_id] = deployment_detail.deployment_id
|
|
217
|
+
return result
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def __sync_undeploy(
|
|
221
|
+
environment_to_deployment_id: Dict,
|
|
222
|
+
model_id: str,
|
|
223
|
+
deployment_client: DeploymentManagementClient,
|
|
224
|
+
):
|
|
225
|
+
with qwak_spinner(
|
|
226
|
+
begin_text=f"Undeploy - model: {model_id}",
|
|
227
|
+
end_text="Successful undeployment",
|
|
228
|
+
print_callback=print,
|
|
229
|
+
):
|
|
230
|
+
for environment_id, deployment_id in environment_to_deployment_id.items():
|
|
231
|
+
status_result = _poll_undeployment_status(
|
|
232
|
+
deployment_id=deployment_id,
|
|
233
|
+
deployment_client=deployment_client,
|
|
234
|
+
check_every_n_seconds=TIME_TO_WAIT_FOR_UNDEPLOYMENT_POLLING,
|
|
235
|
+
)
|
|
236
|
+
failed_to_undeployment = []
|
|
237
|
+
for _, status in status_result.items():
|
|
238
|
+
if status in FAILED_UNDEPLOYMENT_STATUS:
|
|
239
|
+
failed_to_undeployment.append(
|
|
240
|
+
UNDEPLOY_ERROR_MSG.format(
|
|
241
|
+
environment_id=environment_id, status=status
|
|
242
|
+
)
|
|
243
|
+
)
|
|
244
|
+
if failed_to_undeployment:
|
|
245
|
+
raise QwakException("\n".join(failed_to_undeployment))
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _poll_undeployment_status(
|
|
249
|
+
deployment_id: str,
|
|
250
|
+
deployment_client: DeploymentManagementClient,
|
|
251
|
+
check_every_n_seconds: int,
|
|
252
|
+
) -> Dict:
|
|
253
|
+
deployment_status = ""
|
|
254
|
+
|
|
255
|
+
while deployment_status not in END_UNDEPLOYMENT_STATUSES:
|
|
256
|
+
sleep(check_every_n_seconds)
|
|
257
|
+
deployment_status = __get_deployment_status(
|
|
258
|
+
deployment_id=deployment_id, deployment_client=deployment_client
|
|
259
|
+
)
|
|
260
|
+
return {deployment_id: deployment_status}
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def __get_deployment_status(
|
|
264
|
+
deployment_id: str, deployment_client: DeploymentManagementClient
|
|
265
|
+
):
|
|
266
|
+
try:
|
|
267
|
+
return ModelDeploymentStatus.Name(
|
|
268
|
+
deployment_client.get_deployment_status(
|
|
269
|
+
deployment_named_id=deployment_id,
|
|
270
|
+
).status
|
|
271
|
+
)
|
|
272
|
+
except QwakException as e:
|
|
273
|
+
logger.error(
|
|
274
|
+
f"Got error while trying to get deployment id: {deployment_id} status. Error is: {e.message}"
|
|
275
|
+
)
|
|
276
|
+
return ""
|
|
@@ -42,11 +42,18 @@ logger = get_qwak_logger()
|
|
|
42
42
|
required=False,
|
|
43
43
|
type=click.Path(exists=True, resolve_path=True, dir_okay=False),
|
|
44
44
|
)
|
|
45
|
+
@click.option(
|
|
46
|
+
"--sync",
|
|
47
|
+
is_flag=True,
|
|
48
|
+
default=False,
|
|
49
|
+
help="Waiting for deployments to be undeploy",
|
|
50
|
+
)
|
|
45
51
|
def models_undeploy(
|
|
46
52
|
model_id: str,
|
|
47
53
|
variation_name: str,
|
|
48
54
|
environment_name: List[str],
|
|
49
55
|
from_file: str = None,
|
|
56
|
+
sync: bool = False,
|
|
50
57
|
**kwargs,
|
|
51
58
|
):
|
|
52
59
|
logger.info(f"Initiating undeployment for model '{model_id}'")
|
|
@@ -62,8 +69,4 @@ def models_undeploy(
|
|
|
62
69
|
)
|
|
63
70
|
model_uuid = models_management.get_model_uuid(model_id)
|
|
64
71
|
|
|
65
|
-
undeploy(
|
|
66
|
-
model_id=model_id,
|
|
67
|
-
config=config,
|
|
68
|
-
model_uuid=model_uuid,
|
|
69
|
-
)
|
|
72
|
+
undeploy(model_id=model_id, config=config, model_uuid=model_uuid, sync=sync)
|
|
@@ -4,20 +4,27 @@ from datetime import datetime
|
|
|
4
4
|
from _qwak_proto.qwak.builds.builds_pb2 import BuildStatus, ValueType
|
|
5
5
|
from _qwak_proto.qwak.deployment.deployment_pb2 import ModelDeploymentStatus
|
|
6
6
|
from google.protobuf.json_format import MessageToDict
|
|
7
|
-
from qwak.clients.
|
|
7
|
+
from qwak.clients.build_orchestrator import BuildOrchestratorClient
|
|
8
8
|
from qwak.clients.deployment.client import DeploymentManagementClient
|
|
9
9
|
from qwak.clients.model_management import ModelsManagementClient
|
|
10
10
|
from tabulate import tabulate
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
def execute_model_describe(model_id, interface, show_list_builds,
|
|
13
|
+
def execute_model_describe(model_id, interface, show_list_builds, output_format):
|
|
14
14
|
model = _model_data(model_id)
|
|
15
|
+
deployment_data = _get_deployment_data(model)
|
|
15
16
|
list_builds = _builds_data(show_list_builds, model)
|
|
16
|
-
|
|
17
|
-
if
|
|
18
|
-
print_text_data(model, list_builds,
|
|
19
|
-
elif
|
|
20
|
-
print_json_data(model, list_builds,
|
|
17
|
+
schema_data = _schema_data(interface, deployment_data)
|
|
18
|
+
if output_format == "text":
|
|
19
|
+
print_text_data(model, list_builds, schema_data, deployment_data)
|
|
20
|
+
elif output_format == "json":
|
|
21
|
+
print_json_data(model, list_builds, schema_data)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _get_deployment_data(model):
|
|
25
|
+
return DeploymentManagementClient().get_deployment_details(
|
|
26
|
+
model_id=model.model_id, model_uuid=model.uuid
|
|
27
|
+
)
|
|
21
28
|
|
|
22
29
|
|
|
23
30
|
def _model_data(model_id):
|
|
@@ -27,128 +34,136 @@ def _model_data(model_id):
|
|
|
27
34
|
|
|
28
35
|
def _builds_data(list_builds, model):
|
|
29
36
|
if list_builds:
|
|
30
|
-
|
|
31
|
-
return
|
|
37
|
+
builds_orchestrator = BuildOrchestratorClient()
|
|
38
|
+
return builds_orchestrator.list_builds(model.uuid)
|
|
32
39
|
return None
|
|
33
40
|
|
|
34
41
|
|
|
35
|
-
def
|
|
36
|
-
if interface:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
model_id=model.model_id, model_uuid=model.uuid
|
|
42
|
+
def _schema_data(interface, deployment_data):
|
|
43
|
+
if interface and deployment_data.current_deployment_details.build_id:
|
|
44
|
+
build_response = BuildOrchestratorClient().get_build(
|
|
45
|
+
deployment_data.current_deployment_details.build_id
|
|
40
46
|
)
|
|
41
|
-
if
|
|
42
|
-
|
|
43
|
-
build_response = builds_management.get_build(
|
|
44
|
-
deployment_details.current_deployment_details.build_id
|
|
45
|
-
)
|
|
46
|
-
if build_response.build.HasField("model_schema"):
|
|
47
|
-
return build_response
|
|
47
|
+
if build_response.build.HasField("model_schema"):
|
|
48
|
+
return build_response.build.model_schema
|
|
48
49
|
return None
|
|
49
50
|
|
|
50
51
|
|
|
51
|
-
def print_json_data(model, list_builds,
|
|
52
|
+
def print_json_data(model, list_builds, schema_data):
|
|
52
53
|
output = MessageToDict(model)
|
|
53
54
|
if list_builds:
|
|
54
55
|
output["builds"] = MessageToDict(list_builds)
|
|
55
|
-
if
|
|
56
|
-
output["interface"] = MessageToDict(
|
|
56
|
+
if schema_data:
|
|
57
|
+
output["interface"] = MessageToDict(schema_data)
|
|
57
58
|
print(json.dumps(output, indent=4, sort_keys=True))
|
|
58
59
|
|
|
59
60
|
|
|
60
|
-
def print_text_data(model, list_builds,
|
|
61
|
+
def print_text_data(model, list_builds, schema_data, deployment_data):
|
|
61
62
|
print(
|
|
62
63
|
f"Model id: {model.model_id}\nDisplay name: {model.display_name}\nDescription: {model.model_description}\n"
|
|
63
64
|
+ f'Creation Date: {datetime.fromtimestamp(model.created_at.seconds + model.created_at.nanos / 1e9).strftime("%A, %B %d, %Y %I:%M:%S")}\n'
|
|
64
65
|
+ f'Last update: {datetime.fromtimestamp(model.created_at.seconds + model.last_modified_at.nanos / 1e9).strftime("%A, %B %d, %Y %I:%M:%S")}'
|
|
65
66
|
)
|
|
66
67
|
if list_builds:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
_print_text_builds(deployment_data, list_builds)
|
|
69
|
+
if schema_data:
|
|
70
|
+
_print_text_schema(schema_data)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _print_text_schema(schema_data):
|
|
74
|
+
columns = [
|
|
75
|
+
"Parameter name",
|
|
76
|
+
"Parameter type",
|
|
77
|
+
"Parameter source",
|
|
78
|
+
"Parameter category",
|
|
79
|
+
]
|
|
80
|
+
data = []
|
|
81
|
+
for entity in schema_data.entities:
|
|
82
|
+
data.append(
|
|
83
|
+
[
|
|
84
|
+
entity.name,
|
|
85
|
+
ValueType.Types.Name(entity.type.type),
|
|
86
|
+
None,
|
|
87
|
+
"Input",
|
|
88
|
+
]
|
|
89
|
+
)
|
|
90
|
+
for feature in schema_data.features:
|
|
91
|
+
parsed_feature = _parse_feature(feature)
|
|
92
|
+
if parsed_feature:
|
|
93
|
+
data.append(parsed_feature)
|
|
94
|
+
|
|
95
|
+
for prediction in schema_data.predictions:
|
|
96
|
+
data.append(
|
|
97
|
+
[
|
|
98
|
+
prediction.name,
|
|
99
|
+
ValueType.Types.Name(prediction.type.type),
|
|
100
|
+
None,
|
|
101
|
+
"Output",
|
|
102
|
+
]
|
|
103
|
+
)
|
|
104
|
+
print("\n" + tabulate(data, headers=columns))
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _parse_feature(feature):
|
|
108
|
+
if feature.HasField("explicit_feature"):
|
|
109
|
+
return [
|
|
110
|
+
feature.explicit_feature.name,
|
|
111
|
+
ValueType.Types.Name(feature.explicit_feature.type.type),
|
|
112
|
+
None,
|
|
113
|
+
"Input",
|
|
73
114
|
]
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
)
|
|
81
|
-
data.append(
|
|
82
|
-
[
|
|
83
|
-
build.build_spec.build_id,
|
|
84
|
-
build.build_spec.commit_id,
|
|
85
|
-
datetime.fromtimestamp(
|
|
86
|
-
build.last_modified_at.seconds
|
|
87
|
-
+ build.last_modified_at.nanos / 1e9
|
|
88
|
-
).strftime("%A, %B %d, %Y %I:%M:%S"),
|
|
89
|
-
BuildStatus.Name(number=build.build_status),
|
|
90
|
-
deployment_status,
|
|
91
|
-
]
|
|
92
|
-
)
|
|
93
|
-
print("\n" + tabulate(data, headers=columns))
|
|
94
|
-
if interface_build:
|
|
95
|
-
model_schema = interface_build.build.model_schema
|
|
96
|
-
columns = [
|
|
97
|
-
"Parameter name",
|
|
98
|
-
"Parameter type",
|
|
99
|
-
"Parameter source",
|
|
100
|
-
"Parameter category",
|
|
115
|
+
elif feature.HasField("batch_feature"):
|
|
116
|
+
return [
|
|
117
|
+
feature.batch_feature.name,
|
|
118
|
+
None,
|
|
119
|
+
feature.batch_feature.entity.name,
|
|
120
|
+
"Batch Feature",
|
|
101
121
|
]
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
ValueType.Types.Name(entity.type.type),
|
|
108
|
-
None,
|
|
109
|
-
"Input",
|
|
110
|
-
]
|
|
111
|
-
)
|
|
112
|
-
for feature in model_schema.features:
|
|
113
|
-
if feature.HasField("explicit_feature"):
|
|
114
|
-
data.append(
|
|
115
|
-
[
|
|
116
|
-
feature.explicit_feature.name,
|
|
117
|
-
ValueType.Types.Name(feature.explicit_feature.type.type),
|
|
118
|
-
None,
|
|
119
|
-
"Input",
|
|
120
|
-
]
|
|
121
|
-
)
|
|
122
|
-
elif feature.HasField("batch_feature"):
|
|
123
|
-
data.append(
|
|
124
|
-
[
|
|
125
|
-
feature.batch_feature.name,
|
|
126
|
-
None,
|
|
127
|
-
feature.batch_feature.entity.name,
|
|
128
|
-
"Batch Feature",
|
|
129
|
-
]
|
|
130
|
-
)
|
|
131
|
-
elif feature.HasField("on_the_fly_feature"):
|
|
132
|
-
data.append(
|
|
133
|
-
[
|
|
134
|
-
feature.on_the_fly_feature.name,
|
|
135
|
-
None,
|
|
136
|
-
str(
|
|
137
|
-
[
|
|
138
|
-
source.explicit_feature.name
|
|
139
|
-
for source in feature.on_the_fly_feature.source_features
|
|
140
|
-
]
|
|
141
|
-
),
|
|
142
|
-
"On-The-Fly Feature",
|
|
143
|
-
]
|
|
144
|
-
)
|
|
145
|
-
for prediction in model_schema.predictions:
|
|
146
|
-
data.append(
|
|
122
|
+
elif feature.HasField("on_the_fly_feature"):
|
|
123
|
+
return [
|
|
124
|
+
feature.on_the_fly_feature.name,
|
|
125
|
+
None,
|
|
126
|
+
str(
|
|
147
127
|
[
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
None,
|
|
151
|
-
"Output",
|
|
128
|
+
source.explicit_feature.name
|
|
129
|
+
for source in feature.on_the_fly_feature.source_features
|
|
152
130
|
]
|
|
131
|
+
),
|
|
132
|
+
"On-The-Fly Feature",
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _print_text_builds(deployment_data, list_builds):
|
|
137
|
+
columns = [
|
|
138
|
+
"Build id",
|
|
139
|
+
"Commit id",
|
|
140
|
+
"Last modified date",
|
|
141
|
+
"Build Status",
|
|
142
|
+
"Deployment build status",
|
|
143
|
+
]
|
|
144
|
+
data = []
|
|
145
|
+
for build in list_builds.build:
|
|
146
|
+
deployments = deployment_data.build_to_environment_deployment_status.get(
|
|
147
|
+
build.buildId
|
|
148
|
+
)
|
|
149
|
+
if deployments:
|
|
150
|
+
deployment_status = ModelDeploymentStatus.Name(
|
|
151
|
+
number=list(deployments.environment_to_deployment_brief.values())[
|
|
152
|
+
0
|
|
153
|
+
].status
|
|
153
154
|
)
|
|
154
|
-
|
|
155
|
+
else:
|
|
156
|
+
deployment_status = ""
|
|
157
|
+
data.append(
|
|
158
|
+
[
|
|
159
|
+
build.buildId,
|
|
160
|
+
build.commitId,
|
|
161
|
+
datetime.fromtimestamp(
|
|
162
|
+
build.audit.last_modified_at.seconds
|
|
163
|
+
+ build.audit.last_modified_at.nanos / 1e9
|
|
164
|
+
).strftime("%A, %B %d, %Y %I:%M:%S"),
|
|
165
|
+
BuildStatus.Name(number=build.build_status),
|
|
166
|
+
deployment_status,
|
|
167
|
+
]
|
|
168
|
+
)
|
|
169
|
+
print("\n" + tabulate(data, headers=columns))
|
qwak_sdk/tools/log_handling.py
CHANGED
|
@@ -2,7 +2,7 @@ import time
|
|
|
2
2
|
from datetime import datetime, timedelta, timezone
|
|
3
3
|
|
|
4
4
|
from _qwak_proto.qwak.builds.builds_pb2 import BuildStatus
|
|
5
|
-
from qwak.clients.
|
|
5
|
+
from qwak.clients.build_orchestrator import BuildOrchestratorClient
|
|
6
6
|
from qwak.clients.logging_client import LoggingClient
|
|
7
7
|
from qwak.exceptions import QwakException
|
|
8
8
|
|
|
@@ -31,6 +31,10 @@ class QwakLogHandling:
|
|
|
31
31
|
def __init__(self):
|
|
32
32
|
self.log_formatter = self.LogFormatting()
|
|
33
33
|
|
|
34
|
+
@staticmethod
|
|
35
|
+
def get_build_status_name(build_client, build_id: str):
|
|
36
|
+
return BuildStatus.Name(build_client.get_build(build_id).build.build_status)
|
|
37
|
+
|
|
34
38
|
def get_logs(
|
|
35
39
|
self, follow, since, number_of_results, grep, source_params, source_name
|
|
36
40
|
):
|
|
@@ -39,7 +43,7 @@ class QwakLogHandling:
|
|
|
39
43
|
|
|
40
44
|
logging_client = LoggingClient()
|
|
41
45
|
if source_name == "build":
|
|
42
|
-
build_client =
|
|
46
|
+
build_client = BuildOrchestratorClient()
|
|
43
47
|
while True:
|
|
44
48
|
params = {
|
|
45
49
|
**source_params,
|
|
@@ -65,10 +69,8 @@ class QwakLogHandling:
|
|
|
65
69
|
after = response.last_offset
|
|
66
70
|
elif follow:
|
|
67
71
|
if source_name == "build":
|
|
68
|
-
build_status =
|
|
69
|
-
build_client
|
|
70
|
-
source_params["build_id"]
|
|
71
|
-
).build.build_status
|
|
72
|
+
build_status = self.get_build_status_name(
|
|
73
|
+
build_client, source_params["build_id"]
|
|
72
74
|
)
|
|
73
75
|
if build_status in self.BUILD_FINISHED_STATUS:
|
|
74
76
|
break
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: qwak-sdk
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.32
|
|
4
4
|
Summary: Qwak SDK and CLI for qwak models
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Keywords: mlops,ml,deployment,serving,model
|
|
@@ -21,13 +21,14 @@ Provides-Extra: batch
|
|
|
21
21
|
Provides-Extra: feedback
|
|
22
22
|
Requires-Dist: boto3 (>=1.24.116,<2.0.0) ; extra == "batch" or extra == "feedback"
|
|
23
23
|
Requires-Dist: cookiecutter
|
|
24
|
+
Requires-Dist: croniter (==1.4.1)
|
|
24
25
|
Requires-Dist: gitpython (>=2.1.0)
|
|
25
26
|
Requires-Dist: joblib (>=1.1.0,<2.0.0) ; extra == "batch" or extra == "feedback"
|
|
26
27
|
Requires-Dist: pandas (<1.4) ; (python_full_version >= "3.7.1" and python_version < "3.8") and (extra == "batch" or extra == "feedback")
|
|
27
28
|
Requires-Dist: pandas (>=1.4.3,<2.0.0) ; (python_version >= "3.8" and python_version < "3.10") and (extra == "batch" or extra == "feedback")
|
|
28
29
|
Requires-Dist: pyarrow (>=6.0.0,<11.0.0) ; extra == "batch"
|
|
29
30
|
Requires-Dist: python-json-logger (>=2.0.2)
|
|
30
|
-
Requires-Dist: qwak-core (==0.1.
|
|
31
|
+
Requires-Dist: qwak-core (==0.1.36)
|
|
31
32
|
Requires-Dist: qwak-inference (==0.1.8)
|
|
32
33
|
Requires-Dist: tabulate (>=0.8.0)
|
|
33
34
|
Requires-Dist: yaspin (>=2.0.0)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
qwak_sdk/__init__.py,sha256=
|
|
1
|
+
qwak_sdk/__init__.py,sha256=huJCMSFUcwVASX5p43L2I3GPEx5O4EKNrTDBkQKeuI4,135
|
|
2
2
|
qwak_sdk/cli.py,sha256=FIK1dUNxR57ypb-CeD7fKSJnPJ02lrjR9G4aj2qMLPU,2458
|
|
3
3
|
qwak_sdk/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
qwak_sdk/commands/_logic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -79,10 +79,15 @@ qwak_sdk/commands/automations/register/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JC
|
|
|
79
79
|
qwak_sdk/commands/automations/register/_logic.py,sha256=C4ghMOKMNLlSQD1ZEb508VJHvuoSfy0mc2vjNQNFyz4,1624
|
|
80
80
|
qwak_sdk/commands/automations/register/ui.py,sha256=nRfLwJ6dxEjNjFd4guvkT2v_LBC2r0t0gsh6NqZUTH8,1331
|
|
81
81
|
qwak_sdk/commands/feature_store/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
82
|
+
qwak_sdk/commands/feature_store/backfill/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
83
|
+
qwak_sdk/commands/feature_store/backfill/_logic.py,sha256=wuCq7Qq2OAFxiz3XeODRmQStc1ZFgFLo1b0hPRfCDXU,5330
|
|
84
|
+
qwak_sdk/commands/feature_store/backfill/ui.py,sha256=tcTSWMJ9HiA16fWFtfTABfSUvTTFQLnwkpDJTHxddDY,3934
|
|
82
85
|
qwak_sdk/commands/feature_store/delete/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
83
86
|
qwak_sdk/commands/feature_store/delete/_logic.py,sha256=KVNoVCBuh2X67GlfeSYjK-NrbYSajtB-d0W46LnbPUg,1716
|
|
84
87
|
qwak_sdk/commands/feature_store/delete/ui.py,sha256=-LyDBnrHesFaXqUfuhnh_GAyWRdhsmDnqmkGv1DUESM,1039
|
|
85
|
-
qwak_sdk/commands/feature_store/
|
|
88
|
+
qwak_sdk/commands/feature_store/execution/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
89
|
+
qwak_sdk/commands/feature_store/execution/ui.py,sha256=7lZHUIjsLjxg0L9oIX4DqoVPKi0i0iYy7KHXCoZtLto,562
|
|
90
|
+
qwak_sdk/commands/feature_store/feature_store_command_group.py,sha256=Ooi6vvKZf5D4AwPY4b3gdd-njIuQYwlGjvt7LI-T6SM,1251
|
|
86
91
|
qwak_sdk/commands/feature_store/list/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
87
92
|
qwak_sdk/commands/feature_store/list/ui.py,sha256=NuXnQ3cdXSDCFAcC2jmgx4sAdjNuyIQa18h54Dflji8,4145
|
|
88
93
|
qwak_sdk/commands/feature_store/pause/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -103,7 +108,7 @@ qwak_sdk/commands/models/build/_logic/build_steps.py,sha256=K03FIlA67Cq8dpRf-u8J
|
|
|
103
108
|
qwak_sdk/commands/models/build/_logic/client_logs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
104
109
|
qwak_sdk/commands/models/build/_logic/client_logs/build_run_handlers.py,sha256=f7KQwdRNW8J_T9KMMaSbSpMRZFY-uqs82yxwJcg2_PE,6185
|
|
105
110
|
qwak_sdk/commands/models/build/_logic/client_logs/cli_ui.py,sha256=nyQ-L6Q_C_g4Tb0rnBy0UCXnhJt-cwiuxDyQ28RnIoE,4647
|
|
106
|
-
qwak_sdk/commands/models/build/_logic/client_logs/logger.py,sha256=
|
|
111
|
+
qwak_sdk/commands/models/build/_logic/client_logs/logger.py,sha256=ukah6zSZT9YJeSnlkNyTq1ZmLUl4zenUzm5e1XdVqRA,2920
|
|
107
112
|
qwak_sdk/commands/models/build/_logic/client_logs/messages.py,sha256=1zcvDzt7dcNKnMA1YyNfkDWFZs7MfLavflZN5RrBLlk,1369
|
|
108
113
|
qwak_sdk/commands/models/build/_logic/client_logs/notifier_impl.py,sha256=TLAvkmiMT-TQm0Tu_P0Cy-WQ6nH-NYpvNauT8DNo5uQ,1435
|
|
109
114
|
qwak_sdk/commands/models/build/_logic/client_logs/spinner.py,sha256=iph1eVC7QtSzNobNP2j_x0DC2k7bbka9eT8rcBpZGeQ,359
|
|
@@ -155,7 +160,7 @@ qwak_sdk/commands/models/builds/cancel/ui.py,sha256=wmtGv55ORwju-uXjNLYBb8vedAQ3
|
|
|
155
160
|
qwak_sdk/commands/models/builds/logs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
156
161
|
qwak_sdk/commands/models/builds/logs/ui.py,sha256=Z2gNoJjg7YQX3e2qhXq3DMu605tr-vkaYqzVro0W6ok,1054
|
|
157
162
|
qwak_sdk/commands/models/builds/status/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
158
|
-
qwak_sdk/commands/models/builds/status/_logic.py,sha256=
|
|
163
|
+
qwak_sdk/commands/models/builds/status/_logic.py,sha256=B3AUKqKY4hHYb6vlfJLZy90DP86inxPZsmj860YTFvs,260
|
|
159
164
|
qwak_sdk/commands/models/builds/status/ui.py,sha256=zR7TPiVRkWoN1NEoCbyuPCIyB2BMfjVbGMdBVXPBh70,1209
|
|
160
165
|
qwak_sdk/commands/models/create/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
161
166
|
qwak_sdk/commands/models/create/_logic.py,sha256=9t4OLQB8MKji7yIzHbvyPNAV7Ek7OaRt0AP3q0PUvlM,1335
|
|
@@ -173,7 +178,7 @@ qwak_sdk/commands/models/deployments/deploy/_logic/deployment.py,sha256=zBTjeeEV
|
|
|
173
178
|
qwak_sdk/commands/models/deployments/deploy/_logic/deployment_message_helpers.py,sha256=72T3QV_ZeVgvVJ9lnjoLBjW4Hs77NUlVbyoltTWbspk,4516
|
|
174
179
|
qwak_sdk/commands/models/deployments/deploy/_logic/deployment_response_handler.py,sha256=cHa2iF_2A8e1wx2a4UZuTjQ5IHgPvua1xziFJwzP_6s,5837
|
|
175
180
|
qwak_sdk/commands/models/deployments/deploy/_logic/deployment_size_mapper.py,sha256=OoRGjcBM6jZYPr4mHPcefJzBH0bCXfu5QPlqLAsx2Ws,2160
|
|
176
|
-
qwak_sdk/commands/models/deployments/deploy/_logic/get_latest_successful_build.py,sha256=
|
|
181
|
+
qwak_sdk/commands/models/deployments/deploy/_logic/get_latest_successful_build.py,sha256=fsahuSvgGzVjkZGaTH9YyNSOtRWBHYlyAU3wTriyCgI,978
|
|
177
182
|
qwak_sdk/commands/models/deployments/deploy/_logic/variations.py,sha256=n3OGHTYgjnmLYbafZbAOaudaGbeKjw8jbX4MhZX47fE,3256
|
|
178
183
|
qwak_sdk/commands/models/deployments/deploy/batch/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
179
184
|
qwak_sdk/commands/models/deployments/deploy/batch/_logic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -194,11 +199,11 @@ qwak_sdk/commands/models/deployments/deploy/streaming/_logic/serving_strategy_ma
|
|
|
194
199
|
qwak_sdk/commands/models/deployments/deploy/streaming/ui.py,sha256=9tPMnVnLNX3lYyBd4BAh-YThDFz99oyvrCRd_tDZufU,5796
|
|
195
200
|
qwak_sdk/commands/models/deployments/undeploy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
196
201
|
qwak_sdk/commands/models/deployments/undeploy/_logic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
197
|
-
qwak_sdk/commands/models/deployments/undeploy/_logic/request_undeploy.py,sha256=
|
|
202
|
+
qwak_sdk/commands/models/deployments/undeploy/_logic/request_undeploy.py,sha256=e-9lHQ2AbTyogUHxppPVli5pugl6ml4c1zAuFoLQTpw,9481
|
|
198
203
|
qwak_sdk/commands/models/deployments/undeploy/_logic/variations.py,sha256=qo4qUB78voUhtM1YIuTz1OSBaLRS3xw97bwxgqRuLgI,3036
|
|
199
|
-
qwak_sdk/commands/models/deployments/undeploy/ui.py,sha256=
|
|
204
|
+
qwak_sdk/commands/models/deployments/undeploy/ui.py,sha256=1XYBouPgbJWOJCNR1UU1UOVnQS10iKsUqeNkvIBhHY8,2008
|
|
200
205
|
qwak_sdk/commands/models/describe/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
201
|
-
qwak_sdk/commands/models/describe/_logic.py,sha256=
|
|
206
|
+
qwak_sdk/commands/models/describe/_logic.py,sha256=LN1r3zrXLHf83U5ate7ED7VdoZuWUqcHwLZPMMZXRfs,5589
|
|
202
207
|
qwak_sdk/commands/models/describe/ui.py,sha256=6QHt_EQRe5yRV9e2yYOAROfqU-_BfmS0VHXP4hfY6D8,968
|
|
203
208
|
qwak_sdk/commands/models/executions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
204
209
|
qwak_sdk/commands/models/executions/cancel/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -354,9 +359,9 @@ qwak_sdk/main.py,sha256=KmNxbBxJQtKllX9mmA1jeUXh9dXVzq4W-qfHAHatcvA,136
|
|
|
354
359
|
qwak_sdk/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
355
360
|
qwak_sdk/tools/colors.py,sha256=7pui_GGjC4uZKYFsIyXaJjYsjLxJVHb4OrfTgr93hqo,287
|
|
356
361
|
qwak_sdk/tools/files.py,sha256=AyKJTOy7NhvP3SrqwIw_lxYNCOy1CvLgMmSJpWZ0OKM,2257
|
|
357
|
-
qwak_sdk/tools/log_handling.py,sha256=
|
|
362
|
+
qwak_sdk/tools/log_handling.py,sha256=NIQ-5uCb2NHzB7KwIbqbR9EhmhL1bawXLD_oamB6Gok,5711
|
|
358
363
|
qwak_sdk/tools/utils.py,sha256=SHmU4r_m2ABZyFYMC03P17GvltPbYbmB39hvalIZEtI,1168
|
|
359
|
-
qwak_sdk-0.5.
|
|
360
|
-
qwak_sdk-0.5.
|
|
361
|
-
qwak_sdk-0.5.
|
|
362
|
-
qwak_sdk-0.5.
|
|
364
|
+
qwak_sdk-0.5.32.dist-info/entry_points.txt,sha256=vSl0ELYDyj640oMM57u0AjBP87wtLYxCcGOendhEx80,47
|
|
365
|
+
qwak_sdk-0.5.32.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
|
|
366
|
+
qwak_sdk-0.5.32.dist-info/METADATA,sha256=tpM7nE5JSP8lkm_GJuMeSvSd5YQt8VEs2Nsgse0jxbk,1885
|
|
367
|
+
qwak_sdk-0.5.32.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|