flwr-nightly 1.23.0.dev20251001__py3-none-any.whl → 1.23.0.dev20251003__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 +13 -0
- flwr/cli/auth_plugin/__init__.py +7 -4
- flwr/cli/auth_plugin/auth_plugin.py +23 -11
- flwr/cli/auth_plugin/noop_auth_plugin.py +58 -0
- flwr/cli/auth_plugin/oidc_cli_plugin.py +15 -25
- flwr/cli/{cli_user_auth_interceptor.py → cli_account_auth_interceptor.py} +4 -4
- flwr/cli/login/login.py +34 -14
- flwr/{client/clientapp → cli/supernode}/__init__.py +11 -1
- flwr/cli/supernode/create.py +58 -0
- flwr/cli/supernode/delete.py +58 -0
- flwr/cli/supernode/ls.py +51 -0
- flwr/cli/utils.py +36 -22
- flwr/client/__init__.py +2 -1
- flwr/clientapp/__init__.py +1 -2
- flwr/{client/clientapp → clientapp}/utils.py +1 -1
- flwr/common/constant.py +14 -8
- flwr/common/typing.py +6 -6
- flwr/compat/client/app.py +1 -1
- flwr/proto/control_pb2.py +48 -35
- flwr/proto/control_pb2.pyi +67 -4
- flwr/proto/control_pb2_grpc.py +102 -0
- flwr/proto/control_pb2_grpc.pyi +39 -0
- flwr/proto/node_pb2.py +3 -1
- flwr/proto/node_pb2.pyi +33 -0
- flwr/server/app.py +34 -16
- flwr/server/superlink/fleet/vce/backend/backend.py +1 -1
- flwr/server/superlink/fleet/vce/backend/raybackend.py +1 -1
- flwr/server/superlink/fleet/vce/vce_api.py +2 -2
- flwr/simulation/ray_transport/ray_actor.py +1 -1
- flwr/simulation/ray_transport/ray_client_proxy.py +1 -1
- flwr/simulation/run_simulation.py +1 -1
- flwr/superlink/auth_plugin/__init__.py +5 -2
- flwr/superlink/auth_plugin/auth_plugin.py +16 -12
- flwr/superlink/auth_plugin/noop_auth_plugin.py +87 -0
- flwr/superlink/servicer/control/{control_user_auth_interceptor.py → control_account_auth_interceptor.py} +19 -19
- flwr/superlink/servicer/control/control_event_log_interceptor.py +1 -1
- flwr/superlink/servicer/control/control_grpc.py +9 -9
- flwr/superlink/servicer/control/control_servicer.py +57 -29
- flwr/supernode/runtime/run_clientapp.py +2 -2
- {flwr_nightly-1.23.0.dev20251001.dist-info → flwr_nightly-1.23.0.dev20251003.dist-info}/METADATA +1 -1
- {flwr_nightly-1.23.0.dev20251001.dist-info → flwr_nightly-1.23.0.dev20251003.dist-info}/RECORD +44 -39
- /flwr/{client → clientapp}/client_app.py +0 -0
- {flwr_nightly-1.23.0.dev20251001.dist-info → flwr_nightly-1.23.0.dev20251003.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.23.0.dev20251001.dist-info → flwr_nightly-1.23.0.dev20251003.dist-info}/entry_points.txt +0 -0
flwr/cli/app.py
CHANGED
@@ -28,6 +28,9 @@ from .new import new
|
|
28
28
|
from .pull import pull
|
29
29
|
from .run import run
|
30
30
|
from .stop import stop
|
31
|
+
from .supernode import create as supernode_create
|
32
|
+
from .supernode import delete as supernode_delete
|
33
|
+
from .supernode import ls as supernode_list
|
31
34
|
|
32
35
|
app = typer.Typer(
|
33
36
|
help=typer.style(
|
@@ -49,6 +52,16 @@ app.command()(stop)
|
|
49
52
|
app.command()(login)
|
50
53
|
app.command()(pull)
|
51
54
|
|
55
|
+
# Create supernode command group
|
56
|
+
supernode_app = typer.Typer(help="Manage SuperNodes")
|
57
|
+
supernode_app.command()(supernode_create)
|
58
|
+
supernode_app.command()(supernode_delete)
|
59
|
+
# Make it appear as "list"
|
60
|
+
supernode_app.command("list")(supernode_list)
|
61
|
+
# Hide "ls" command (left as alias)
|
62
|
+
supernode_app.command(hidden=True)(supernode_list)
|
63
|
+
app.add_typer(supernode_app, name="supernode")
|
64
|
+
|
52
65
|
typer_click_object = get_command(app)
|
53
66
|
|
54
67
|
|
flwr/cli/auth_plugin/__init__.py
CHANGED
@@ -12,22 +12,25 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
# ==============================================================================
|
15
|
-
"""Flower
|
15
|
+
"""Flower account auth plugins."""
|
16
16
|
|
17
17
|
|
18
|
-
from flwr.common.constant import
|
18
|
+
from flwr.common.constant import AuthnType
|
19
19
|
|
20
|
-
from .auth_plugin import CliAuthPlugin
|
20
|
+
from .auth_plugin import CliAuthPlugin, LoginError
|
21
|
+
from .noop_auth_plugin import NoOpCliAuthPlugin
|
21
22
|
from .oidc_cli_plugin import OidcCliPlugin
|
22
23
|
|
23
24
|
|
24
25
|
def get_cli_auth_plugins() -> dict[str, type[CliAuthPlugin]]:
|
25
26
|
"""Return all CLI authentication plugins."""
|
26
|
-
return {
|
27
|
+
return {AuthnType.NOOP: NoOpCliAuthPlugin, AuthnType.OIDC: OidcCliPlugin}
|
27
28
|
|
28
29
|
|
29
30
|
__all__ = [
|
30
31
|
"CliAuthPlugin",
|
32
|
+
"LoginError",
|
33
|
+
"NoOpCliAuthPlugin",
|
31
34
|
"OidcCliPlugin",
|
32
35
|
"get_cli_auth_plugins",
|
33
36
|
]
|
@@ -12,7 +12,7 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
# ==============================================================================
|
15
|
-
"""Abstract classes for Flower
|
15
|
+
"""Abstract classes for Flower account auth plugin."""
|
16
16
|
|
17
17
|
|
18
18
|
from abc import ABC, abstractmethod
|
@@ -20,38 +20,50 @@ from collections.abc import Sequence
|
|
20
20
|
from pathlib import Path
|
21
21
|
from typing import Optional, Union
|
22
22
|
|
23
|
-
from flwr.common.typing import
|
23
|
+
from flwr.common.typing import AccountAuthCredentials, AccountAuthLoginDetails
|
24
24
|
from flwr.proto.control_pb2_grpc import ControlStub
|
25
25
|
|
26
26
|
|
27
|
+
class LoginError(Exception):
|
28
|
+
"""Login error exception."""
|
29
|
+
|
30
|
+
def __init__(self, message: str):
|
31
|
+
self.message = message
|
32
|
+
|
33
|
+
|
27
34
|
class CliAuthPlugin(ABC):
|
28
35
|
"""Abstract Flower Auth Plugin class for CLI.
|
29
36
|
|
30
37
|
Parameters
|
31
38
|
----------
|
32
39
|
credentials_path : Path
|
33
|
-
Path to the
|
40
|
+
Path to the Flower account's authentication credentials file.
|
34
41
|
"""
|
35
42
|
|
36
43
|
@staticmethod
|
37
44
|
@abstractmethod
|
38
45
|
def login(
|
39
|
-
login_details:
|
46
|
+
login_details: AccountAuthLoginDetails,
|
40
47
|
control_stub: ControlStub,
|
41
|
-
) ->
|
42
|
-
"""Authenticate the
|
48
|
+
) -> AccountAuthCredentials:
|
49
|
+
"""Authenticate the account and retrieve authentication credentials.
|
43
50
|
|
44
51
|
Parameters
|
45
52
|
----------
|
46
|
-
login_details :
|
47
|
-
An object containing the
|
53
|
+
login_details : AccountAuthLoginDetails
|
54
|
+
An object containing the account's login details.
|
48
55
|
control_stub : ControlStub
|
49
56
|
A stub for executing RPC calls to the server.
|
50
57
|
|
51
58
|
Returns
|
52
59
|
-------
|
53
|
-
|
60
|
+
AccountAuthCredentials
|
54
61
|
The authentication credentials obtained after login.
|
62
|
+
|
63
|
+
Raises
|
64
|
+
------
|
65
|
+
LoginError
|
66
|
+
If the login process fails.
|
55
67
|
"""
|
56
68
|
|
57
69
|
@abstractmethod
|
@@ -59,7 +71,7 @@ class CliAuthPlugin(ABC):
|
|
59
71
|
"""Abstract constructor."""
|
60
72
|
|
61
73
|
@abstractmethod
|
62
|
-
def store_tokens(self, credentials:
|
74
|
+
def store_tokens(self, credentials: AccountAuthCredentials) -> None:
|
63
75
|
"""Store authentication tokens to the `credentials_path`.
|
64
76
|
|
65
77
|
The credentials, including tokens, will be saved as a JSON file
|
@@ -79,5 +91,5 @@ class CliAuthPlugin(ABC):
|
|
79
91
|
@abstractmethod
|
80
92
|
def read_tokens_from_metadata(
|
81
93
|
self, metadata: Sequence[tuple[str, Union[str, bytes]]]
|
82
|
-
) -> Optional[
|
94
|
+
) -> Optional[AccountAuthCredentials]:
|
83
95
|
"""Read authentication tokens from the provided metadata."""
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# Copyright 2025 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
|
+
"""Concrete NoOp implementation for CLI-side account authentication plugin."""
|
16
|
+
|
17
|
+
|
18
|
+
from collections.abc import Sequence
|
19
|
+
from pathlib import Path
|
20
|
+
from typing import Optional, Union
|
21
|
+
|
22
|
+
from flwr.common.typing import AccountAuthCredentials, AccountAuthLoginDetails
|
23
|
+
from flwr.proto.control_pb2_grpc import ControlStub
|
24
|
+
|
25
|
+
from .auth_plugin import CliAuthPlugin, LoginError
|
26
|
+
|
27
|
+
|
28
|
+
class NoOpCliAuthPlugin(CliAuthPlugin):
|
29
|
+
"""No-operation implementation of the CliAuthPlugin."""
|
30
|
+
|
31
|
+
@staticmethod
|
32
|
+
def login(
|
33
|
+
login_details: AccountAuthLoginDetails,
|
34
|
+
control_stub: ControlStub,
|
35
|
+
) -> AccountAuthCredentials:
|
36
|
+
"""Raise LoginError as no-op plugin does not support login."""
|
37
|
+
raise LoginError("Account authentication is not enabled on this SuperLink.")
|
38
|
+
|
39
|
+
def __init__(self, credentials_path: Path) -> None:
|
40
|
+
pass
|
41
|
+
|
42
|
+
def store_tokens(self, credentials: AccountAuthCredentials) -> None:
|
43
|
+
"""Do nothing."""
|
44
|
+
|
45
|
+
def load_tokens(self) -> None:
|
46
|
+
"""Do nothing."""
|
47
|
+
|
48
|
+
def write_tokens_to_metadata(
|
49
|
+
self, metadata: Sequence[tuple[str, Union[str, bytes]]]
|
50
|
+
) -> Sequence[tuple[str, Union[str, bytes]]]:
|
51
|
+
"""Return the metadata unchanged."""
|
52
|
+
return metadata
|
53
|
+
|
54
|
+
def read_tokens_from_metadata(
|
55
|
+
self, metadata: Sequence[tuple[str, Union[str, bytes]]]
|
56
|
+
) -> Optional[AccountAuthCredentials]:
|
57
|
+
"""Return None."""
|
58
|
+
return None
|
@@ -12,7 +12,7 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
# ==============================================================================
|
15
|
-
"""Flower CLI
|
15
|
+
"""Flower CLI account auth plugin for OIDC."""
|
16
16
|
|
17
17
|
|
18
18
|
import json
|
@@ -25,18 +25,18 @@ import typer
|
|
25
25
|
|
26
26
|
from flwr.common.constant import (
|
27
27
|
ACCESS_TOKEN_KEY,
|
28
|
-
|
28
|
+
AUTHN_TYPE_JSON_KEY,
|
29
29
|
REFRESH_TOKEN_KEY,
|
30
|
-
|
30
|
+
AuthnType,
|
31
31
|
)
|
32
|
-
from flwr.common.typing import
|
32
|
+
from flwr.common.typing import AccountAuthCredentials, AccountAuthLoginDetails
|
33
33
|
from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
34
34
|
GetAuthTokensRequest,
|
35
35
|
GetAuthTokensResponse,
|
36
36
|
)
|
37
37
|
from flwr.proto.control_pb2_grpc import ControlStub
|
38
38
|
|
39
|
-
from .auth_plugin import CliAuthPlugin
|
39
|
+
from .auth_plugin import CliAuthPlugin, LoginError
|
40
40
|
|
41
41
|
|
42
42
|
class OidcCliPlugin(CliAuthPlugin):
|
@@ -49,12 +49,12 @@ class OidcCliPlugin(CliAuthPlugin):
|
|
49
49
|
|
50
50
|
@staticmethod
|
51
51
|
def login(
|
52
|
-
login_details:
|
52
|
+
login_details: AccountAuthLoginDetails,
|
53
53
|
control_stub: ControlStub,
|
54
|
-
) ->
|
55
|
-
"""Authenticate the
|
54
|
+
) -> AccountAuthCredentials:
|
55
|
+
"""Authenticate the account and retrieve authentication credentials."""
|
56
56
|
typer.secho(
|
57
|
-
"Please
|
57
|
+
"Please log into your Flower account here: "
|
58
58
|
f"{login_details.verification_uri_complete}",
|
59
59
|
fg=typer.colors.BLUE,
|
60
60
|
)
|
@@ -70,26 +70,16 @@ class OidcCliPlugin(CliAuthPlugin):
|
|
70
70
|
refresh_token = res.refresh_token
|
71
71
|
|
72
72
|
if access_token and refresh_token:
|
73
|
-
|
74
|
-
"✅ Login successful.",
|
75
|
-
fg=typer.colors.GREEN,
|
76
|
-
bold=False,
|
77
|
-
)
|
78
|
-
return UserAuthCredentials(
|
73
|
+
return AccountAuthCredentials(
|
79
74
|
access_token=access_token,
|
80
75
|
refresh_token=refresh_token,
|
81
76
|
)
|
82
77
|
|
83
78
|
time.sleep(login_details.interval)
|
84
79
|
|
85
|
-
|
86
|
-
"❌ Timeout, failed to sign in.",
|
87
|
-
fg=typer.colors.RED,
|
88
|
-
bold=True,
|
89
|
-
)
|
90
|
-
raise typer.Exit(code=1)
|
80
|
+
raise LoginError("Process timed out.")
|
91
81
|
|
92
|
-
def store_tokens(self, credentials:
|
82
|
+
def store_tokens(self, credentials: AccountAuthCredentials) -> None:
|
93
83
|
"""Store authentication tokens to the `credentials_path`.
|
94
84
|
|
95
85
|
The credentials, including tokens, will be saved as a JSON file
|
@@ -98,7 +88,7 @@ class OidcCliPlugin(CliAuthPlugin):
|
|
98
88
|
self.access_token = credentials.access_token
|
99
89
|
self.refresh_token = credentials.refresh_token
|
100
90
|
json_dict = {
|
101
|
-
|
91
|
+
AUTHN_TYPE_JSON_KEY: AuthnType.OIDC,
|
102
92
|
ACCESS_TOKEN_KEY: credentials.access_token,
|
103
93
|
REFRESH_TOKEN_KEY: credentials.refresh_token,
|
104
94
|
}
|
@@ -136,14 +126,14 @@ class OidcCliPlugin(CliAuthPlugin):
|
|
136
126
|
|
137
127
|
def read_tokens_from_metadata(
|
138
128
|
self, metadata: Sequence[tuple[str, Union[str, bytes]]]
|
139
|
-
) -> Optional[
|
129
|
+
) -> Optional[AccountAuthCredentials]:
|
140
130
|
"""Read authentication tokens from the provided metadata."""
|
141
131
|
metadata_dict = dict(metadata)
|
142
132
|
access_token = metadata_dict.get(ACCESS_TOKEN_KEY)
|
143
133
|
refresh_token = metadata_dict.get(REFRESH_TOKEN_KEY)
|
144
134
|
|
145
135
|
if isinstance(access_token, str) and isinstance(refresh_token, str):
|
146
|
-
return
|
136
|
+
return AccountAuthCredentials(
|
147
137
|
access_token=access_token,
|
148
138
|
refresh_token=refresh_token,
|
149
139
|
)
|
@@ -32,10 +32,10 @@ Request = Union[
|
|
32
32
|
]
|
33
33
|
|
34
34
|
|
35
|
-
class
|
35
|
+
class CliAccountAuthInterceptor(
|
36
36
|
grpc.UnaryUnaryClientInterceptor, grpc.UnaryStreamClientInterceptor # type: ignore
|
37
37
|
):
|
38
|
-
"""CLI interceptor for
|
38
|
+
"""CLI interceptor for account authentication."""
|
39
39
|
|
40
40
|
def __init__(self, auth_plugin: CliAuthPlugin):
|
41
41
|
self.auth_plugin = auth_plugin
|
@@ -70,7 +70,7 @@ class CliUserAuthInterceptor(
|
|
70
70
|
client_call_details: grpc.ClientCallDetails,
|
71
71
|
request: Request,
|
72
72
|
) -> grpc.Call:
|
73
|
-
"""Intercept a unary-unary call for
|
73
|
+
"""Intercept a unary-unary call for account authentication.
|
74
74
|
|
75
75
|
This method intercepts a unary-unary RPC call initiated from the CLI and adds
|
76
76
|
the required authentication tokens to the RPC metadata.
|
@@ -83,7 +83,7 @@ class CliUserAuthInterceptor(
|
|
83
83
|
client_call_details: grpc.ClientCallDetails,
|
84
84
|
request: Request,
|
85
85
|
) -> grpc.Call:
|
86
|
-
"""Intercept a unary-stream call for
|
86
|
+
"""Intercept a unary-stream call for account authentication.
|
87
87
|
|
88
88
|
This method intercepts a unary-stream RPC call initiated from the CLI and adds
|
89
89
|
the required authentication tokens to the RPC metadata.
|
flwr/cli/login/login.py
CHANGED
@@ -20,6 +20,7 @@ from typing import Annotated, Optional
|
|
20
20
|
|
21
21
|
import typer
|
22
22
|
|
23
|
+
from flwr.cli.auth_plugin import LoginError
|
23
24
|
from flwr.cli.config_utils import (
|
24
25
|
exit_if_no_address,
|
25
26
|
get_insecure_flag,
|
@@ -28,14 +29,19 @@ from flwr.cli.config_utils import (
|
|
28
29
|
validate_federation_in_project_config,
|
29
30
|
)
|
30
31
|
from flwr.cli.constant import FEDERATION_CONFIG_HELP_MESSAGE
|
31
|
-
from flwr.common.typing import
|
32
|
+
from flwr.common.typing import AccountAuthLoginDetails
|
32
33
|
from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
33
34
|
GetLoginDetailsRequest,
|
34
35
|
GetLoginDetailsResponse,
|
35
36
|
)
|
36
37
|
from flwr.proto.control_pb2_grpc import ControlStub
|
37
38
|
|
38
|
-
from ..utils import
|
39
|
+
from ..utils import (
|
40
|
+
account_auth_enabled,
|
41
|
+
flwr_cli_grpc_exc_handler,
|
42
|
+
init_channel,
|
43
|
+
try_obtain_cli_auth_plugin,
|
44
|
+
)
|
39
45
|
|
40
46
|
|
41
47
|
def login( # pylint: disable=R0914
|
@@ -67,12 +73,13 @@ def login( # pylint: disable=R0914
|
|
67
73
|
)
|
68
74
|
exit_if_no_address(federation_config, "login")
|
69
75
|
|
70
|
-
# Check if `enable-
|
71
|
-
|
76
|
+
# Check if `enable-account-auth` is set to `true`
|
77
|
+
|
78
|
+
if not account_auth_enabled(federation_config):
|
72
79
|
typer.secho(
|
73
|
-
|
74
|
-
"To enable it, set `enable-
|
75
|
-
"configuration.",
|
80
|
+
"❌ Account authentication is not enabled for the federation "
|
81
|
+
f"'{federation}'. To enable it, set `enable-account-auth = true` "
|
82
|
+
"in the federation configuration.",
|
76
83
|
fg=typer.colors.RED,
|
77
84
|
bold=True,
|
78
85
|
)
|
@@ -96,28 +103,41 @@ def login( # pylint: disable=R0914
|
|
96
103
|
login_response: GetLoginDetailsResponse = stub.GetLoginDetails(login_request)
|
97
104
|
|
98
105
|
# Get the auth plugin
|
99
|
-
|
106
|
+
authn_type = login_response.authn_type
|
100
107
|
auth_plugin = try_obtain_cli_auth_plugin(
|
101
|
-
app, federation, federation_config,
|
108
|
+
app, federation, federation_config, authn_type
|
102
109
|
)
|
103
110
|
if auth_plugin is None:
|
104
111
|
typer.secho(
|
105
|
-
f'❌ Authentication type "{
|
112
|
+
f'❌ Authentication type "{authn_type}" not found',
|
106
113
|
fg=typer.colors.RED,
|
107
114
|
bold=True,
|
108
115
|
)
|
109
116
|
raise typer.Exit(code=1)
|
110
117
|
|
111
118
|
# Login
|
112
|
-
details =
|
113
|
-
|
119
|
+
details = AccountAuthLoginDetails(
|
120
|
+
authn_type=login_response.authn_type,
|
114
121
|
device_code=login_response.device_code,
|
115
122
|
verification_uri_complete=login_response.verification_uri_complete,
|
116
123
|
expires_in=login_response.expires_in,
|
117
124
|
interval=login_response.interval,
|
118
125
|
)
|
119
|
-
|
120
|
-
|
126
|
+
try:
|
127
|
+
with flwr_cli_grpc_exc_handler():
|
128
|
+
credentials = auth_plugin.login(details, stub)
|
129
|
+
typer.secho(
|
130
|
+
"✅ Login successful.",
|
131
|
+
fg=typer.colors.GREEN,
|
132
|
+
bold=False,
|
133
|
+
)
|
134
|
+
except LoginError as e:
|
135
|
+
typer.secho(
|
136
|
+
f"❌ Login failed: {e.message}",
|
137
|
+
fg=typer.colors.RED,
|
138
|
+
bold=True,
|
139
|
+
)
|
140
|
+
raise typer.Exit(code=1) from None
|
121
141
|
|
122
142
|
# Store the tokens
|
123
143
|
auth_plugin.store_tokens(credentials)
|
@@ -12,4 +12,14 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
# ==============================================================================
|
15
|
-
"""Flower
|
15
|
+
"""Flower command line interface `supernode` command."""
|
16
|
+
|
17
|
+
from .create import create as create
|
18
|
+
from .delete import delete as delete
|
19
|
+
from .ls import ls as ls
|
20
|
+
|
21
|
+
__all__ = [
|
22
|
+
"create",
|
23
|
+
"delete",
|
24
|
+
"ls",
|
25
|
+
]
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# Copyright 2025 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 `supernode create` command."""
|
16
|
+
|
17
|
+
from pathlib import Path
|
18
|
+
from typing import Annotated, Optional
|
19
|
+
|
20
|
+
import typer
|
21
|
+
|
22
|
+
from flwr.cli.config_utils import (
|
23
|
+
exit_if_no_address,
|
24
|
+
load_and_validate,
|
25
|
+
process_loaded_project_config,
|
26
|
+
validate_federation_in_project_config,
|
27
|
+
)
|
28
|
+
from flwr.common.constant import FAB_CONFIG_FILE
|
29
|
+
|
30
|
+
|
31
|
+
def create( # pylint: disable=R0914
|
32
|
+
public_key: Annotated[
|
33
|
+
Path,
|
34
|
+
typer.Argument(
|
35
|
+
help="Path to the public key file.",
|
36
|
+
),
|
37
|
+
],
|
38
|
+
app: Annotated[
|
39
|
+
Path,
|
40
|
+
typer.Argument(help="Path of the Flower project"),
|
41
|
+
] = Path("."),
|
42
|
+
federation: Annotated[
|
43
|
+
Optional[str],
|
44
|
+
typer.Argument(help="Name of the federation"),
|
45
|
+
] = None,
|
46
|
+
) -> None:
|
47
|
+
"""Add a SuperNode to the federation."""
|
48
|
+
typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
|
49
|
+
|
50
|
+
pyproject_path = app / FAB_CONFIG_FILE if app else None
|
51
|
+
config, errors, warnings = load_and_validate(path=pyproject_path)
|
52
|
+
config = process_loaded_project_config(config, errors, warnings)
|
53
|
+
federation, federation_config = validate_federation_in_project_config(
|
54
|
+
federation, config
|
55
|
+
)
|
56
|
+
exit_if_no_address(federation_config, "supernode add")
|
57
|
+
|
58
|
+
_ = public_key
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# Copyright 2025 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 `supernode delete` command."""
|
16
|
+
|
17
|
+
from pathlib import Path
|
18
|
+
from typing import Annotated, Optional
|
19
|
+
|
20
|
+
import typer
|
21
|
+
|
22
|
+
from flwr.cli.config_utils import (
|
23
|
+
exit_if_no_address,
|
24
|
+
load_and_validate,
|
25
|
+
process_loaded_project_config,
|
26
|
+
validate_federation_in_project_config,
|
27
|
+
)
|
28
|
+
from flwr.common.constant import FAB_CONFIG_FILE
|
29
|
+
|
30
|
+
|
31
|
+
def delete( # pylint: disable=R0914
|
32
|
+
node_id: Annotated[
|
33
|
+
int,
|
34
|
+
typer.Argument(
|
35
|
+
help="ID of the SuperNode to remove.",
|
36
|
+
),
|
37
|
+
],
|
38
|
+
app: Annotated[
|
39
|
+
Path,
|
40
|
+
typer.Argument(help="Path of the Flower project"),
|
41
|
+
] = Path("."),
|
42
|
+
federation: Annotated[
|
43
|
+
Optional[str],
|
44
|
+
typer.Argument(help="Name of the federation"),
|
45
|
+
] = None,
|
46
|
+
) -> None:
|
47
|
+
"""Remove a SuperNode from the federation."""
|
48
|
+
typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
|
49
|
+
|
50
|
+
pyproject_path = app / FAB_CONFIG_FILE if app else None
|
51
|
+
config, errors, warnings = load_and_validate(path=pyproject_path)
|
52
|
+
config = process_loaded_project_config(config, errors, warnings)
|
53
|
+
federation, federation_config = validate_federation_in_project_config(
|
54
|
+
federation, config
|
55
|
+
)
|
56
|
+
exit_if_no_address(federation_config, "supernode rm")
|
57
|
+
|
58
|
+
_ = node_id
|
flwr/cli/supernode/ls.py
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# Copyright 2025 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 `supernode list` 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.common.constant import FAB_CONFIG_FILE
|
30
|
+
|
31
|
+
|
32
|
+
def ls( # pylint: disable=R0914
|
33
|
+
app: Annotated[
|
34
|
+
Path,
|
35
|
+
typer.Argument(help="Path of the Flower project"),
|
36
|
+
] = Path("."),
|
37
|
+
federation: Annotated[
|
38
|
+
Optional[str],
|
39
|
+
typer.Argument(help="Name of the federation"),
|
40
|
+
] = None,
|
41
|
+
) -> None:
|
42
|
+
"""List SuperNodes in the federation."""
|
43
|
+
typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
|
44
|
+
|
45
|
+
pyproject_path = app / FAB_CONFIG_FILE if app else None
|
46
|
+
config, errors, warnings = load_and_validate(path=pyproject_path)
|
47
|
+
config = process_loaded_project_config(config, errors, warnings)
|
48
|
+
federation, federation_config = validate_federation_in_project_config(
|
49
|
+
federation, config
|
50
|
+
)
|
51
|
+
exit_if_no_address(federation_config, "supernode ls")
|