flwr 1.13.1__py3-none-any.whl → 1.15.0__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.
- flwr/cli/app.py +5 -0
- flwr/cli/auth_plugin/__init__.py +31 -0
- flwr/cli/auth_plugin/oidc_cli_plugin.py +150 -0
- flwr/cli/build.py +1 -0
- flwr/cli/cli_user_auth_interceptor.py +90 -0
- flwr/cli/config_utils.py +43 -149
- flwr/cli/constant.py +27 -0
- flwr/cli/example.py +1 -0
- flwr/cli/install.py +2 -1
- flwr/cli/log.py +34 -37
- flwr/cli/login/__init__.py +22 -0
- flwr/cli/login/login.py +116 -0
- flwr/cli/ls.py +214 -106
- flwr/cli/new/__init__.py +1 -0
- flwr/cli/new/new.py +2 -1
- flwr/cli/new/templates/app/.gitignore.tpl +3 -0
- flwr/cli/new/templates/app/README.md.tpl +3 -2
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +4 -4
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +4 -4
- flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +4 -4
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +2 -2
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +3 -4
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +2 -2
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +4 -4
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +3 -3
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +2 -2
- flwr/cli/run/__init__.py +1 -0
- flwr/cli/run/run.py +103 -43
- flwr/cli/stop.py +139 -0
- flwr/cli/utils.py +186 -8
- flwr/client/app.py +49 -50
- flwr/client/client.py +1 -32
- flwr/client/clientapp/app.py +23 -26
- flwr/client/clientapp/utils.py +2 -1
- flwr/client/grpc_adapter_client/connection.py +1 -1
- flwr/client/grpc_client/connection.py +2 -13
- flwr/client/grpc_rere_client/client_interceptor.py +19 -119
- flwr/client/grpc_rere_client/connection.py +59 -43
- flwr/client/grpc_rere_client/grpc_adapter.py +12 -12
- flwr/client/message_handler/message_handler.py +1 -2
- flwr/client/message_handler/task_handler.py +0 -17
- flwr/client/mod/comms_mods.py +1 -0
- flwr/client/mod/localdp_mod.py +1 -1
- flwr/client/nodestate/__init__.py +1 -0
- flwr/client/nodestate/nodestate.py +1 -0
- flwr/client/nodestate/nodestate_factory.py +1 -0
- flwr/client/numpy_client.py +0 -44
- flwr/client/rest_client/connection.py +37 -29
- flwr/client/supernode/app.py +20 -74
- flwr/common/address.py +1 -0
- flwr/common/args.py +26 -47
- flwr/common/auth_plugin/__init__.py +24 -0
- flwr/common/auth_plugin/auth_plugin.py +122 -0
- flwr/common/config.py +169 -17
- flwr/common/constant.py +38 -9
- flwr/common/differential_privacy.py +2 -1
- flwr/common/exit/__init__.py +24 -0
- flwr/common/exit/exit.py +99 -0
- flwr/common/exit/exit_code.py +93 -0
- flwr/common/exit_handlers.py +24 -10
- flwr/common/grpc.py +167 -4
- flwr/common/logger.py +66 -7
- flwr/common/message.py +1 -0
- flwr/common/object_ref.py +57 -54
- flwr/common/pyproject.py +1 -0
- flwr/common/record/__init__.py +1 -0
- flwr/common/record/parametersrecord.py +1 -0
- flwr/common/record/recordset.py +1 -1
- flwr/common/retry_invoker.py +77 -0
- flwr/common/secure_aggregation/crypto/symmetric_encryption.py +45 -0
- flwr/common/secure_aggregation/secaggplus_utils.py +2 -2
- flwr/common/serde.py +6 -4
- flwr/common/telemetry.py +15 -4
- flwr/common/typing.py +32 -0
- flwr/common/version.py +1 -0
- flwr/proto/clientappio_pb2.py +1 -1
- flwr/proto/error_pb2.py +1 -1
- flwr/proto/exec_pb2.py +27 -15
- flwr/proto/exec_pb2.pyi +80 -2
- flwr/proto/exec_pb2_grpc.py +102 -0
- flwr/proto/exec_pb2_grpc.pyi +39 -0
- flwr/proto/fab_pb2.py +5 -5
- flwr/proto/fab_pb2.pyi +4 -1
- flwr/proto/fleet_pb2.py +31 -31
- flwr/proto/fleet_pb2.pyi +23 -23
- flwr/proto/fleet_pb2_grpc.py +30 -30
- flwr/proto/fleet_pb2_grpc.pyi +20 -20
- flwr/proto/grpcadapter_pb2.py +1 -1
- flwr/proto/log_pb2.py +1 -1
- flwr/proto/message_pb2.py +1 -1
- flwr/proto/node_pb2.py +3 -3
- flwr/proto/node_pb2.pyi +1 -4
- flwr/proto/recordset_pb2.py +1 -1
- flwr/proto/run_pb2.py +1 -1
- flwr/proto/serverappio_pb2.py +24 -25
- flwr/proto/serverappio_pb2.pyi +32 -32
- flwr/proto/serverappio_pb2_grpc.py +62 -28
- flwr/proto/serverappio_pb2_grpc.pyi +29 -16
- flwr/proto/simulationio_pb2.py +3 -3
- flwr/proto/simulationio_pb2_grpc.py +34 -0
- flwr/proto/simulationio_pb2_grpc.pyi +13 -0
- flwr/proto/task_pb2.py +1 -1
- flwr/proto/transport_pb2.py +1 -1
- flwr/server/app.py +152 -112
- flwr/server/compat/app_utils.py +7 -2
- flwr/server/compat/driver_client_proxy.py +1 -2
- flwr/server/driver/grpc_driver.py +38 -85
- flwr/server/driver/inmemory_driver.py +7 -2
- flwr/server/run_serverapp.py +8 -9
- flwr/server/serverapp/app.py +37 -13
- flwr/server/strategy/dpfedavg_fixed.py +1 -0
- flwr/server/superlink/driver/serverappio_grpc.py +2 -1
- flwr/server/superlink/driver/serverappio_servicer.py +148 -63
- flwr/server/superlink/ffs/disk_ffs.py +1 -0
- flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +20 -87
- flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -0
- flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +2 -165
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +56 -35
- flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +99 -169
- flwr/server/superlink/fleet/message_handler/message_handler.py +69 -29
- flwr/server/superlink/fleet/rest_rere/rest_api.py +20 -19
- flwr/server/superlink/fleet/vce/__init__.py +1 -0
- flwr/server/superlink/fleet/vce/backend/__init__.py +1 -0
- flwr/server/superlink/fleet/vce/backend/raybackend.py +1 -0
- flwr/server/superlink/fleet/vce/vce_api.py +2 -2
- flwr/server/superlink/linkstate/in_memory_linkstate.py +60 -99
- flwr/server/superlink/linkstate/linkstate.py +30 -36
- flwr/server/superlink/linkstate/sqlite_linkstate.py +105 -188
- flwr/server/superlink/linkstate/utils.py +18 -8
- flwr/server/superlink/simulation/simulationio_grpc.py +1 -1
- flwr/server/superlink/simulation/simulationio_servicer.py +33 -0
- flwr/server/superlink/utils.py +65 -0
- flwr/server/utils/validator.py +9 -34
- flwr/simulation/app.py +20 -10
- flwr/simulation/legacy_app.py +4 -2
- flwr/simulation/ray_transport/ray_actor.py +1 -0
- flwr/simulation/ray_transport/utils.py +1 -0
- flwr/simulation/run_simulation.py +36 -22
- flwr/simulation/simulationio_connection.py +5 -1
- flwr/superexec/app.py +1 -0
- flwr/superexec/deployment.py +1 -0
- flwr/superexec/exec_grpc.py +20 -2
- flwr/superexec/exec_servicer.py +97 -2
- flwr/superexec/exec_user_auth_interceptor.py +101 -0
- flwr/superexec/executor.py +1 -0
- {flwr-1.13.1.dist-info → flwr-1.15.0.dist-info}/METADATA +14 -13
- {flwr-1.13.1.dist-info → flwr-1.15.0.dist-info}/RECORD +150 -144
- flwr/proto/common_pb2.py +0 -36
- flwr/proto/common_pb2.pyi +0 -121
- flwr/proto/common_pb2_grpc.py +0 -4
- flwr/proto/common_pb2_grpc.pyi +0 -4
- flwr/proto/control_pb2.py +0 -27
- flwr/proto/control_pb2.pyi +0 -7
- flwr/proto/control_pb2_grpc.py +0 -135
- flwr/proto/control_pb2_grpc.pyi +0 -53
- {flwr-1.13.1.dist-info → flwr-1.15.0.dist-info}/LICENSE +0 -0
- {flwr-1.13.1.dist-info → flwr-1.15.0.dist-info}/WHEEL +0 -0
- {flwr-1.13.1.dist-info → flwr-1.15.0.dist-info}/entry_points.txt +0 -0
flwr/cli/log.py
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
# ==============================================================================
|
|
15
15
|
"""Flower command line interface `log` command."""
|
|
16
16
|
|
|
17
|
+
|
|
17
18
|
import time
|
|
18
19
|
from logging import DEBUG, ERROR, INFO
|
|
19
20
|
from pathlib import Path
|
|
@@ -23,17 +24,19 @@ import grpc
|
|
|
23
24
|
import typer
|
|
24
25
|
|
|
25
26
|
from flwr.cli.config_utils import (
|
|
27
|
+
exit_if_no_address,
|
|
26
28
|
load_and_validate,
|
|
27
|
-
|
|
29
|
+
process_loaded_project_config,
|
|
28
30
|
validate_federation_in_project_config,
|
|
29
|
-
validate_project_config,
|
|
30
31
|
)
|
|
32
|
+
from flwr.cli.constant import FEDERATION_CONFIG_HELP_MESSAGE
|
|
31
33
|
from flwr.common.constant import CONN_RECONNECT_INTERVAL, CONN_REFRESH_PERIOD
|
|
32
|
-
from flwr.common.grpc import GRPC_MAX_MESSAGE_LENGTH, create_channel
|
|
33
34
|
from flwr.common.logger import log as logger
|
|
34
35
|
from flwr.proto.exec_pb2 import StreamLogsRequest # pylint: disable=E0611
|
|
35
36
|
from flwr.proto.exec_pb2_grpc import ExecStub
|
|
36
37
|
|
|
38
|
+
from .utils import init_channel, try_obtain_cli_auth_plugin, unauthenticated_exc_handler
|
|
39
|
+
|
|
37
40
|
|
|
38
41
|
def start_stream(
|
|
39
42
|
run_id: int, channel: grpc.Channel, refresh_period: int = CONN_REFRESH_PERIOD
|
|
@@ -55,6 +58,8 @@ def start_stream(
|
|
|
55
58
|
logger(ERROR, "Invalid run_id `%s`, exiting", run_id)
|
|
56
59
|
if e.code() == grpc.StatusCode.CANCELLED:
|
|
57
60
|
pass
|
|
61
|
+
else:
|
|
62
|
+
raise e
|
|
58
63
|
finally:
|
|
59
64
|
channel.close()
|
|
60
65
|
|
|
@@ -86,8 +91,9 @@ def stream_logs(
|
|
|
86
91
|
latest_timestamp = 0.0
|
|
87
92
|
res = None
|
|
88
93
|
try:
|
|
89
|
-
|
|
90
|
-
|
|
94
|
+
with unauthenticated_exc_handler():
|
|
95
|
+
for res in stub.StreamLogs(req, timeout=duration):
|
|
96
|
+
print(res.log_output, end="")
|
|
91
97
|
except grpc.RpcError as e:
|
|
92
98
|
# pylint: disable=E1101
|
|
93
99
|
if e.code() != grpc.StatusCode.DEADLINE_EXCEEDED:
|
|
@@ -107,9 +113,10 @@ def print_logs(run_id: int, channel: grpc.Channel, timeout: int) -> None:
|
|
|
107
113
|
try:
|
|
108
114
|
while True:
|
|
109
115
|
try:
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
116
|
+
with unauthenticated_exc_handler():
|
|
117
|
+
# Enforce timeout for graceful exit
|
|
118
|
+
for res in stub.StreamLogs(req, timeout=timeout):
|
|
119
|
+
print(res.log_output)
|
|
113
120
|
except grpc.RpcError as e:
|
|
114
121
|
# pylint: disable=E1101
|
|
115
122
|
if e.code() == grpc.StatusCode.DEADLINE_EXCEEDED:
|
|
@@ -119,6 +126,7 @@ def print_logs(run_id: int, channel: grpc.Channel, timeout: int) -> None:
|
|
|
119
126
|
break
|
|
120
127
|
if e.code() == grpc.StatusCode.CANCELLED:
|
|
121
128
|
break
|
|
129
|
+
raise e
|
|
122
130
|
except KeyboardInterrupt:
|
|
123
131
|
logger(DEBUG, "Stream interrupted by user")
|
|
124
132
|
finally:
|
|
@@ -126,11 +134,6 @@ def print_logs(run_id: int, channel: grpc.Channel, timeout: int) -> None:
|
|
|
126
134
|
logger(DEBUG, "Channel closed")
|
|
127
135
|
|
|
128
136
|
|
|
129
|
-
def on_channel_state_change(channel_connectivity: str) -> None:
|
|
130
|
-
"""Log channel connectivity."""
|
|
131
|
-
logger(DEBUG, channel_connectivity)
|
|
132
|
-
|
|
133
|
-
|
|
134
137
|
def log(
|
|
135
138
|
run_id: Annotated[
|
|
136
139
|
int,
|
|
@@ -144,6 +147,13 @@ def log(
|
|
|
144
147
|
Optional[str],
|
|
145
148
|
typer.Argument(help="Name of the federation to run the app on"),
|
|
146
149
|
] = None,
|
|
150
|
+
federation_config_overrides: Annotated[
|
|
151
|
+
Optional[list[str]],
|
|
152
|
+
typer.Option(
|
|
153
|
+
"--federation-config",
|
|
154
|
+
help=FEDERATION_CONFIG_HELP_MESSAGE,
|
|
155
|
+
),
|
|
156
|
+
] = None,
|
|
147
157
|
stream: Annotated[
|
|
148
158
|
bool,
|
|
149
159
|
typer.Option(
|
|
@@ -157,41 +167,28 @@ def log(
|
|
|
157
167
|
|
|
158
168
|
pyproject_path = app / "pyproject.toml" if app else None
|
|
159
169
|
config, errors, warnings = load_and_validate(path=pyproject_path)
|
|
160
|
-
config =
|
|
170
|
+
config = process_loaded_project_config(config, errors, warnings)
|
|
161
171
|
federation, federation_config = validate_federation_in_project_config(
|
|
162
|
-
federation, config
|
|
172
|
+
federation, config, federation_config_overrides
|
|
163
173
|
)
|
|
174
|
+
exit_if_no_address(federation_config, "log")
|
|
164
175
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
bold=True,
|
|
171
|
-
)
|
|
172
|
-
raise typer.Exit(code=1)
|
|
173
|
-
|
|
174
|
-
_log_with_exec_api(app, federation_config, run_id, stream)
|
|
176
|
+
try:
|
|
177
|
+
_log_with_exec_api(app, federation, federation_config, run_id, stream)
|
|
178
|
+
except Exception as err: # pylint: disable=broad-except
|
|
179
|
+
typer.secho(str(err), fg=typer.colors.RED, bold=True)
|
|
180
|
+
raise typer.Exit(code=1) from None
|
|
175
181
|
|
|
176
182
|
|
|
177
183
|
def _log_with_exec_api(
|
|
178
184
|
app: Path,
|
|
185
|
+
federation: str,
|
|
179
186
|
federation_config: dict[str, Any],
|
|
180
187
|
run_id: int,
|
|
181
188
|
stream: bool,
|
|
182
189
|
) -> None:
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
app, federation_config
|
|
186
|
-
)
|
|
187
|
-
channel = create_channel(
|
|
188
|
-
server_address=federation_config["address"],
|
|
189
|
-
insecure=insecure,
|
|
190
|
-
root_certificates=root_certificates_bytes,
|
|
191
|
-
max_message_length=GRPC_MAX_MESSAGE_LENGTH,
|
|
192
|
-
interceptors=None,
|
|
193
|
-
)
|
|
194
|
-
channel.subscribe(on_channel_state_change)
|
|
190
|
+
auth_plugin = try_obtain_cli_auth_plugin(app, federation, federation_config)
|
|
191
|
+
channel = init_channel(app, federation_config, auth_plugin)
|
|
195
192
|
|
|
196
193
|
if stream:
|
|
197
194
|
start_stream(run_id, channel, CONN_REFRESH_PERIOD)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Copyright 2024 Flower Labs GmbH. All Rights Reserved.
|
|
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
|
+
"""Flower command line interface `login` command."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from .login import login as login
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"login",
|
|
22
|
+
]
|
flwr/cli/login/login.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Copyright 2024 Flower Labs GmbH. All Rights Reserved.
|
|
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
|
+
"""Flower command line interface `login` command."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Annotated, Optional
|
|
20
|
+
|
|
21
|
+
import typer
|
|
22
|
+
|
|
23
|
+
from flwr.cli.config_utils import (
|
|
24
|
+
exit_if_no_address,
|
|
25
|
+
load_and_validate,
|
|
26
|
+
process_loaded_project_config,
|
|
27
|
+
validate_federation_in_project_config,
|
|
28
|
+
)
|
|
29
|
+
from flwr.cli.constant import FEDERATION_CONFIG_HELP_MESSAGE
|
|
30
|
+
from flwr.common.typing import UserAuthLoginDetails
|
|
31
|
+
from flwr.proto.exec_pb2 import ( # pylint: disable=E0611
|
|
32
|
+
GetLoginDetailsRequest,
|
|
33
|
+
GetLoginDetailsResponse,
|
|
34
|
+
)
|
|
35
|
+
from flwr.proto.exec_pb2_grpc import ExecStub
|
|
36
|
+
|
|
37
|
+
from ..utils import (
|
|
38
|
+
init_channel,
|
|
39
|
+
try_obtain_cli_auth_plugin,
|
|
40
|
+
unauthenticated_exc_handler,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def login( # pylint: disable=R0914
|
|
45
|
+
app: Annotated[
|
|
46
|
+
Path,
|
|
47
|
+
typer.Argument(help="Path of the Flower App to run."),
|
|
48
|
+
] = Path("."),
|
|
49
|
+
federation: Annotated[
|
|
50
|
+
Optional[str],
|
|
51
|
+
typer.Argument(help="Name of the federation to login into."),
|
|
52
|
+
] = None,
|
|
53
|
+
federation_config_overrides: Annotated[
|
|
54
|
+
Optional[list[str]],
|
|
55
|
+
typer.Option(
|
|
56
|
+
"--federation-config",
|
|
57
|
+
help=FEDERATION_CONFIG_HELP_MESSAGE,
|
|
58
|
+
),
|
|
59
|
+
] = None,
|
|
60
|
+
) -> None:
|
|
61
|
+
"""Login to Flower SuperLink."""
|
|
62
|
+
typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
|
|
63
|
+
|
|
64
|
+
pyproject_path = app / "pyproject.toml" if app else None
|
|
65
|
+
config, errors, warnings = load_and_validate(path=pyproject_path)
|
|
66
|
+
|
|
67
|
+
config = process_loaded_project_config(config, errors, warnings)
|
|
68
|
+
federation, federation_config = validate_federation_in_project_config(
|
|
69
|
+
federation, config, federation_config_overrides
|
|
70
|
+
)
|
|
71
|
+
exit_if_no_address(federation_config, "login")
|
|
72
|
+
|
|
73
|
+
# Check if `enable-user-auth` is set to `true`
|
|
74
|
+
if not federation_config.get("enable-user-auth", False):
|
|
75
|
+
typer.secho(
|
|
76
|
+
f"❌ User authentication is not enabled for the federation '{federation}'. "
|
|
77
|
+
"To enable it, set `enable-user-auth = true` in the federation "
|
|
78
|
+
"configuration.",
|
|
79
|
+
fg=typer.colors.RED,
|
|
80
|
+
bold=True,
|
|
81
|
+
)
|
|
82
|
+
raise typer.Exit(code=1)
|
|
83
|
+
|
|
84
|
+
channel = init_channel(app, federation_config, None)
|
|
85
|
+
stub = ExecStub(channel)
|
|
86
|
+
|
|
87
|
+
login_request = GetLoginDetailsRequest()
|
|
88
|
+
with unauthenticated_exc_handler():
|
|
89
|
+
login_response: GetLoginDetailsResponse = stub.GetLoginDetails(login_request)
|
|
90
|
+
|
|
91
|
+
# Get the auth plugin
|
|
92
|
+
auth_type = login_response.auth_type
|
|
93
|
+
auth_plugin = try_obtain_cli_auth_plugin(
|
|
94
|
+
app, federation, federation_config, auth_type
|
|
95
|
+
)
|
|
96
|
+
if auth_plugin is None:
|
|
97
|
+
typer.secho(
|
|
98
|
+
f'❌ Authentication type "{auth_type}" not found',
|
|
99
|
+
fg=typer.colors.RED,
|
|
100
|
+
bold=True,
|
|
101
|
+
)
|
|
102
|
+
raise typer.Exit(code=1)
|
|
103
|
+
|
|
104
|
+
# Login
|
|
105
|
+
details = UserAuthLoginDetails(
|
|
106
|
+
auth_type=login_response.auth_type,
|
|
107
|
+
device_code=login_response.device_code,
|
|
108
|
+
verification_uri_complete=login_response.verification_uri_complete,
|
|
109
|
+
expires_in=login_response.expires_in,
|
|
110
|
+
interval=login_response.interval,
|
|
111
|
+
)
|
|
112
|
+
with unauthenticated_exc_handler():
|
|
113
|
+
credentials = auth_plugin.login(details, stub)
|
|
114
|
+
|
|
115
|
+
# Store the tokens
|
|
116
|
+
auth_plugin.store_tokens(credentials)
|