flwr-nightly 1.14.0.dev20241210__py3-none-any.whl → 1.14.0.dev20241211__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 flwr-nightly might be problematic. Click here for more details.

flwr/cli/app.py CHANGED
@@ -20,6 +20,7 @@ from typer.main import get_command
20
20
  from .build import build
21
21
  from .install import install
22
22
  from .log import log
23
+ from .login import login
23
24
  from .ls import ls
24
25
  from .new import new
25
26
  from .run import run
@@ -41,6 +42,7 @@ app.command()(install)
41
42
  app.command()(log)
42
43
  app.command()(ls)
43
44
  app.command()(stop)
45
+ app.command()(login)
44
46
 
45
47
  typer_click_object = get_command(app)
46
48
 
@@ -0,0 +1,86 @@
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 run interceptor."""
16
+
17
+
18
+ from typing import Any, Callable, Union
19
+
20
+ import grpc
21
+
22
+ from flwr.common.auth_plugin import CliAuthPlugin
23
+ from flwr.proto.exec_pb2 import ( # pylint: disable=E0611
24
+ StartRunRequest,
25
+ StreamLogsRequest,
26
+ )
27
+
28
+ Request = Union[
29
+ StartRunRequest,
30
+ StreamLogsRequest,
31
+ ]
32
+
33
+
34
+ class CliUserAuthInterceptor(
35
+ grpc.UnaryUnaryClientInterceptor, grpc.UnaryStreamClientInterceptor # type: ignore
36
+ ):
37
+ """CLI interceptor for user authentication."""
38
+
39
+ def __init__(self, auth_plugin: CliAuthPlugin):
40
+ self.auth_plugin = auth_plugin
41
+
42
+ def _authenticated_call(
43
+ self,
44
+ continuation: Callable[[Any, Any], Any],
45
+ client_call_details: grpc.ClientCallDetails,
46
+ request: Request,
47
+ ) -> grpc.Call:
48
+ """Send and receive tokens via metadata."""
49
+ new_metadata = self.auth_plugin.write_tokens_to_metadata(
50
+ client_call_details.metadata or []
51
+ )
52
+
53
+ details = client_call_details._replace(metadata=new_metadata)
54
+
55
+ response = continuation(details, request)
56
+ if response.initial_metadata():
57
+ retrieved_metadata = dict(response.initial_metadata())
58
+ self.auth_plugin.store_tokens(retrieved_metadata)
59
+
60
+ return response
61
+
62
+ def intercept_unary_unary(
63
+ self,
64
+ continuation: Callable[[Any, Any], Any],
65
+ client_call_details: grpc.ClientCallDetails,
66
+ request: Request,
67
+ ) -> grpc.Call:
68
+ """Intercept a unary-unary call for user authentication.
69
+
70
+ This method intercepts a unary-unary RPC call initiated from the CLI and adds
71
+ the required authentication tokens to the RPC metadata.
72
+ """
73
+ return self._authenticated_call(continuation, client_call_details, request)
74
+
75
+ def intercept_unary_stream(
76
+ self,
77
+ continuation: Callable[[Any, Any], Any],
78
+ client_call_details: grpc.ClientCallDetails,
79
+ request: Request,
80
+ ) -> grpc.Call:
81
+ """Intercept a unary-stream call for user authentication.
82
+
83
+ This method intercepts a unary-stream RPC call initiated from the CLI and adds
84
+ the required authentication tokens to the RPC metadata.
85
+ """
86
+ return self._authenticated_call(continuation, client_call_details, request)
flwr/cli/config_utils.py CHANGED
@@ -230,10 +230,14 @@ def load_from_string(toml_content: str) -> Optional[dict[str, Any]]:
230
230
  return None
231
231
 
232
232
 
233
- def validate_project_config(
233
+ def process_loaded_project_config(
234
234
  config: Union[dict[str, Any], None], errors: list[str], warnings: list[str]
235
235
  ) -> dict[str, Any]:
236
- """Validate and return the Flower project configuration."""
236
+ """Process and return the loaded project configuration.
237
+
238
+ This function handles errors and warnings from the `load_and_validate` function,
239
+ exits on critical issues, and returns the validated configuration.
240
+ """
237
241
  if config is None:
238
242
  typer.secho(
239
243
  "Project configuration could not be loaded.\n"
@@ -324,3 +328,15 @@ def validate_certificate_in_federation_config(
324
328
  raise typer.Exit(code=1)
325
329
 
326
330
  return insecure, root_certificates_bytes
331
+
332
+
333
+ def exit_if_no_address(federation_config: dict[str, Any], cmd: str) -> None:
334
+ """Exit if the provided federation_config has no "address" key."""
335
+ if "address" not in federation_config:
336
+ typer.secho(
337
+ f"❌ `flwr {cmd}` currently works with a SuperLink. Ensure that the correct"
338
+ "SuperLink (Exec API) address is provided in `pyproject.toml`.",
339
+ fg=typer.colors.RED,
340
+ bold=True,
341
+ )
342
+ raise typer.Exit(code=1)
flwr/cli/log.py CHANGED
@@ -23,17 +23,18 @@ import grpc
23
23
  import typer
24
24
 
25
25
  from flwr.cli.config_utils import (
26
+ exit_if_no_address,
26
27
  load_and_validate,
27
- validate_certificate_in_federation_config,
28
+ process_loaded_project_config,
28
29
  validate_federation_in_project_config,
29
- validate_project_config,
30
30
  )
31
31
  from flwr.common.constant import CONN_RECONNECT_INTERVAL, CONN_REFRESH_PERIOD
32
- from flwr.common.grpc import GRPC_MAX_MESSAGE_LENGTH, create_channel
33
32
  from flwr.common.logger import log as logger
34
33
  from flwr.proto.exec_pb2 import StreamLogsRequest # pylint: disable=E0611
35
34
  from flwr.proto.exec_pb2_grpc import ExecStub
36
35
 
36
+ from .utils import init_channel, try_obtain_cli_auth_plugin
37
+
37
38
 
38
39
  def start_stream(
39
40
  run_id: int, channel: grpc.Channel, refresh_period: int = CONN_REFRESH_PERIOD
@@ -126,11 +127,6 @@ def print_logs(run_id: int, channel: grpc.Channel, timeout: int) -> None:
126
127
  logger(DEBUG, "Channel closed")
127
128
 
128
129
 
129
- def on_channel_state_change(channel_connectivity: str) -> None:
130
- """Log channel connectivity."""
131
- logger(DEBUG, channel_connectivity)
132
-
133
-
134
130
  def log(
135
131
  run_id: Annotated[
136
132
  int,
@@ -157,41 +153,24 @@ def log(
157
153
 
158
154
  pyproject_path = app / "pyproject.toml" if app else None
159
155
  config, errors, warnings = load_and_validate(path=pyproject_path)
160
- config = validate_project_config(config, errors, warnings)
156
+ config = process_loaded_project_config(config, errors, warnings)
161
157
  federation, federation_config = validate_federation_in_project_config(
162
158
  federation, config
163
159
  )
160
+ exit_if_no_address(federation_config, "log")
164
161
 
165
- if "address" not in federation_config:
166
- typer.secho(
167
- "❌ `flwr log` currently works with Exec API. Ensure that the correct"
168
- "Exec API address is provided in the `pyproject.toml`.",
169
- fg=typer.colors.RED,
170
- bold=True,
171
- )
172
- raise typer.Exit(code=1)
173
-
174
- _log_with_exec_api(app, federation_config, run_id, stream)
162
+ _log_with_exec_api(app, federation, federation_config, run_id, stream)
175
163
 
176
164
 
177
165
  def _log_with_exec_api(
178
166
  app: Path,
167
+ federation: str,
179
168
  federation_config: dict[str, Any],
180
169
  run_id: int,
181
170
  stream: bool,
182
171
  ) -> None:
183
-
184
- insecure, root_certificates_bytes = validate_certificate_in_federation_config(
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)
172
+ auth_plugin = try_obtain_cli_auth_plugin(app, federation, federation_config)
173
+ channel = init_channel(app, federation_config, auth_plugin)
195
174
 
196
175
  if stream:
197
176
  start_stream(run_id, channel, CONN_REFRESH_PERIOD)
@@ -0,0 +1,21 @@
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
+ from .login import login as login
18
+
19
+ __all__ = [
20
+ "login",
21
+ ]
@@ -0,0 +1,82 @@
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
+ 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 AUTH_TYPE
29
+ from flwr.proto.exec_pb2 import ( # pylint: disable=E0611
30
+ GetLoginDetailsRequest,
31
+ GetLoginDetailsResponse,
32
+ )
33
+ from flwr.proto.exec_pb2_grpc import ExecStub
34
+
35
+ from ..utils import init_channel, try_obtain_cli_auth_plugin
36
+
37
+
38
+ def login( # pylint: disable=R0914
39
+ app: Annotated[
40
+ Path,
41
+ typer.Argument(help="Path of the Flower App to run."),
42
+ ] = Path("."),
43
+ federation: Annotated[
44
+ Optional[str],
45
+ typer.Argument(help="Name of the federation to login into."),
46
+ ] = None,
47
+ ) -> None:
48
+ """Login to Flower SuperLink."""
49
+ typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
50
+
51
+ pyproject_path = app / "pyproject.toml" if app else None
52
+ config, errors, warnings = load_and_validate(path=pyproject_path)
53
+
54
+ config = process_loaded_project_config(config, errors, warnings)
55
+ federation, federation_config = validate_federation_in_project_config(
56
+ federation, config
57
+ )
58
+ exit_if_no_address(federation_config, "login")
59
+ channel = init_channel(app, federation_config, None)
60
+ stub = ExecStub(channel)
61
+
62
+ login_request = GetLoginDetailsRequest()
63
+ login_response: GetLoginDetailsResponse = stub.GetLoginDetails(login_request)
64
+
65
+ # Get the auth plugin
66
+ auth_type = login_response.login_details.get(AUTH_TYPE)
67
+ auth_plugin = try_obtain_cli_auth_plugin(
68
+ app, federation, federation_config, auth_type
69
+ )
70
+ if auth_plugin is None:
71
+ typer.secho(
72
+ f'❌ Authentication type "{auth_type}" not found',
73
+ fg=typer.colors.RED,
74
+ bold=True,
75
+ )
76
+ raise typer.Exit(code=1)
77
+
78
+ # Login
79
+ auth_config = auth_plugin.login(dict(login_response.login_details), stub)
80
+
81
+ # Store the tokens
82
+ auth_plugin.store_tokens(auth_config)
flwr/cli/ls.py CHANGED
@@ -18,11 +18,9 @@
18
18
  import io
19
19
  import json
20
20
  from datetime import datetime, timedelta
21
- from logging import DEBUG
22
21
  from pathlib import Path
23
- from typing import Annotated, Any, Optional, Union
22
+ from typing import Annotated, Optional, Union
24
23
 
25
- import grpc
26
24
  import typer
27
25
  from rich.console import Console
28
26
  from rich.table import Table
@@ -30,15 +28,14 @@ from rich.text import Text
30
28
  from typer import Exit
31
29
 
32
30
  from flwr.cli.config_utils import (
31
+ exit_if_no_address,
33
32
  load_and_validate,
34
- validate_certificate_in_federation_config,
33
+ process_loaded_project_config,
35
34
  validate_federation_in_project_config,
36
- validate_project_config,
37
35
  )
38
36
  from flwr.common.constant import FAB_CONFIG_FILE, CliOutputFormat, SubStatus
39
37
  from flwr.common.date import format_timedelta, isoformat8601_utc
40
- from flwr.common.grpc import GRPC_MAX_MESSAGE_LENGTH, create_channel
41
- from flwr.common.logger import log, redirect_output, remove_emojis, restore_output
38
+ from flwr.common.logger import redirect_output, remove_emojis, restore_output
42
39
  from flwr.common.serde import run_from_proto
43
40
  from flwr.common.typing import Run
44
41
  from flwr.proto.exec_pb2 import ( # pylint: disable=E0611
@@ -47,6 +44,8 @@ from flwr.proto.exec_pb2 import ( # pylint: disable=E0611
47
44
  )
48
45
  from flwr.proto.exec_pb2_grpc import ExecStub
49
46
 
47
+ from .utils import init_channel, try_obtain_cli_auth_plugin
48
+
50
49
  _RunListType = tuple[int, str, str, str, str, str, str, str, str]
51
50
 
52
51
 
@@ -94,27 +93,19 @@ def ls( # pylint: disable=too-many-locals, too-many-branches
94
93
 
95
94
  pyproject_path = app / FAB_CONFIG_FILE if app else None
96
95
  config, errors, warnings = load_and_validate(path=pyproject_path)
97
- config = validate_project_config(config, errors, warnings)
96
+ config = process_loaded_project_config(config, errors, warnings)
98
97
  federation, federation_config = validate_federation_in_project_config(
99
98
  federation, config
100
99
  )
101
-
102
- if "address" not in federation_config:
103
- typer.secho(
104
- "❌ `flwr ls` currently works with Exec API. Ensure that the correct"
105
- "Exec API address is provided in the `pyproject.toml`.",
106
- fg=typer.colors.RED,
107
- bold=True,
108
- )
109
- raise typer.Exit(code=1)
100
+ exit_if_no_address(federation_config, "ls")
110
101
 
111
102
  try:
112
103
  if runs and run_id is not None:
113
104
  raise ValueError(
114
105
  "The options '--runs' and '--run-id' are mutually exclusive."
115
106
  )
116
-
117
- channel = _init_channel(app, federation_config)
107
+ auth_plugin = try_obtain_cli_auth_plugin(app, federation, federation_config)
108
+ channel = init_channel(app, federation_config, auth_plugin)
118
109
  stub = ExecStub(channel)
119
110
 
120
111
  # Display information about a specific run ID
@@ -154,27 +145,6 @@ def ls( # pylint: disable=too-many-locals, too-many-branches
154
145
  captured_output.close()
155
146
 
156
147
 
157
- def on_channel_state_change(channel_connectivity: str) -> None:
158
- """Log channel connectivity."""
159
- log(DEBUG, channel_connectivity)
160
-
161
-
162
- def _init_channel(app: Path, federation_config: dict[str, Any]) -> grpc.Channel:
163
- """Initialize gRPC channel to the Exec API."""
164
- insecure, root_certificates_bytes = validate_certificate_in_federation_config(
165
- app, federation_config
166
- )
167
- channel = create_channel(
168
- server_address=federation_config["address"],
169
- insecure=insecure,
170
- root_certificates=root_certificates_bytes,
171
- max_message_length=GRPC_MAX_MESSAGE_LENGTH,
172
- interceptors=None,
173
- )
174
- channel.subscribe(on_channel_state_change)
175
- return channel
176
-
177
-
178
148
  def _format_runs(run_dict: dict[int, Run], now_isoformat: str) -> list[_RunListType]:
179
149
  """Format runs to a list."""
180
150
 
flwr/cli/run/run.py CHANGED
@@ -17,7 +17,6 @@
17
17
  import io
18
18
  import json
19
19
  import subprocess
20
- from logging import DEBUG
21
20
  from pathlib import Path
22
21
  from typing import Annotated, Any, Optional, Union
23
22
 
@@ -28,9 +27,8 @@ from flwr.cli.build import build
28
27
  from flwr.cli.config_utils import (
29
28
  get_fab_metadata,
30
29
  load_and_validate,
31
- validate_certificate_in_federation_config,
30
+ process_loaded_project_config,
32
31
  validate_federation_in_project_config,
33
- validate_project_config,
34
32
  )
35
33
  from flwr.common.config import (
36
34
  flatten_dict,
@@ -38,8 +36,7 @@ from flwr.common.config import (
38
36
  user_config_to_configsrecord,
39
37
  )
40
38
  from flwr.common.constant import CliOutputFormat
41
- from flwr.common.grpc import GRPC_MAX_MESSAGE_LENGTH, create_channel
42
- from flwr.common.logger import log, redirect_output, remove_emojis, restore_output
39
+ from flwr.common.logger import redirect_output, remove_emojis, restore_output
43
40
  from flwr.common.serde import (
44
41
  configs_record_to_proto,
45
42
  fab_to_proto,
@@ -50,15 +47,11 @@ from flwr.proto.exec_pb2 import StartRunRequest # pylint: disable=E0611
50
47
  from flwr.proto.exec_pb2_grpc import ExecStub
51
48
 
52
49
  from ..log import start_stream
50
+ from ..utils import init_channel, try_obtain_cli_auth_plugin
53
51
 
54
52
  CONN_REFRESH_PERIOD = 60 # Connection refresh period for log streaming (seconds)
55
53
 
56
54
 
57
- def on_channel_state_change(channel_connectivity: str) -> None:
58
- """Log channel connectivity."""
59
- log(DEBUG, channel_connectivity)
60
-
61
-
62
55
  # pylint: disable-next=too-many-locals
63
56
  def run(
64
57
  app: Annotated[
@@ -108,14 +101,19 @@ def run(
108
101
 
109
102
  pyproject_path = app / "pyproject.toml" if app else None
110
103
  config, errors, warnings = load_and_validate(path=pyproject_path)
111
- config = validate_project_config(config, errors, warnings)
104
+ config = process_loaded_project_config(config, errors, warnings)
112
105
  federation, federation_config = validate_federation_in_project_config(
113
106
  federation, config
114
107
  )
115
108
 
116
109
  if "address" in federation_config:
117
110
  _run_with_exec_api(
118
- app, federation_config, config_overrides, stream, output_format
111
+ app,
112
+ federation,
113
+ federation_config,
114
+ config_overrides,
115
+ stream,
116
+ output_format,
119
117
  )
120
118
  else:
121
119
  _run_without_exec_api(app, federation_config, config_overrides, federation)
@@ -136,26 +134,17 @@ def run(
136
134
  captured_output.close()
137
135
 
138
136
 
139
- # pylint: disable-next=too-many-locals
137
+ # pylint: disable-next=R0913, R0914, R0917
140
138
  def _run_with_exec_api(
141
139
  app: Path,
140
+ federation: str,
142
141
  federation_config: dict[str, Any],
143
142
  config_overrides: Optional[list[str]],
144
143
  stream: bool,
145
144
  output_format: str,
146
145
  ) -> None:
147
-
148
- insecure, root_certificates_bytes = validate_certificate_in_federation_config(
149
- app, federation_config
150
- )
151
- channel = create_channel(
152
- server_address=federation_config["address"],
153
- insecure=insecure,
154
- root_certificates=root_certificates_bytes,
155
- max_message_length=GRPC_MAX_MESSAGE_LENGTH,
156
- interceptors=None,
157
- )
158
- channel.subscribe(on_channel_state_change)
146
+ auth_plugin = try_obtain_cli_auth_plugin(app, federation, federation_config)
147
+ channel = init_channel(app, federation_config, auth_plugin)
159
148
  stub = ExecStub(channel)
160
149
 
161
150
  fab_path, fab_hash = build(app)
flwr/cli/stop.py CHANGED
@@ -15,25 +15,23 @@
15
15
  """Flower command line interface `stop` command."""
16
16
 
17
17
 
18
- from logging import DEBUG
19
18
  from pathlib import Path
20
- from typing import Annotated, Any, Optional
19
+ from typing import Annotated, Optional
21
20
 
22
- import grpc
23
21
  import typer
24
22
 
25
23
  from flwr.cli.config_utils import (
24
+ exit_if_no_address,
26
25
  load_and_validate,
27
- validate_certificate_in_federation_config,
26
+ process_loaded_project_config,
28
27
  validate_federation_in_project_config,
29
- validate_project_config,
30
28
  )
31
29
  from flwr.common.constant import FAB_CONFIG_FILE
32
- from flwr.common.grpc import GRPC_MAX_MESSAGE_LENGTH, create_channel
33
- from flwr.common.logger import log
34
30
  from flwr.proto.exec_pb2 import StopRunRequest, StopRunResponse # pylint: disable=E0611
35
31
  from flwr.proto.exec_pb2_grpc import ExecStub
36
32
 
33
+ from .utils import init_channel, try_obtain_cli_auth_plugin
34
+
37
35
 
38
36
  def stop(
39
37
  run_id: Annotated[ # pylint: disable=unused-argument
@@ -55,22 +53,15 @@ def stop(
55
53
 
56
54
  pyproject_path = app / FAB_CONFIG_FILE if app else None
57
55
  config, errors, warnings = load_and_validate(path=pyproject_path)
58
- config = validate_project_config(config, errors, warnings)
56
+ config = process_loaded_project_config(config, errors, warnings)
59
57
  federation, federation_config = validate_federation_in_project_config(
60
58
  federation, config
61
59
  )
62
-
63
- if "address" not in federation_config:
64
- typer.secho(
65
- "❌ `flwr stop` currently works with Exec API. Ensure that the correct"
66
- "Exec API address is provided in the `pyproject.toml`.",
67
- fg=typer.colors.RED,
68
- bold=True,
69
- )
70
- raise typer.Exit(code=1)
60
+ exit_if_no_address(federation_config, "stop")
71
61
 
72
62
  try:
73
- channel = _init_channel(app, federation_config)
63
+ auth_plugin = try_obtain_cli_auth_plugin(app, federation, federation_config)
64
+ channel = init_channel(app, federation_config, auth_plugin)
74
65
  stub = ExecStub(channel) # pylint: disable=unused-variable # noqa: F841
75
66
 
76
67
  typer.secho(f"✋ Stopping run ID {run_id}...", fg=typer.colors.GREEN)
@@ -87,27 +78,6 @@ def stop(
87
78
  channel.close()
88
79
 
89
80
 
90
- def on_channel_state_change(channel_connectivity: str) -> None:
91
- """Log channel connectivity."""
92
- log(DEBUG, channel_connectivity)
93
-
94
-
95
- def _init_channel(app: Path, federation_config: dict[str, Any]) -> grpc.Channel:
96
- """Initialize gRPC channel to the Exec API."""
97
- insecure, root_certificates_bytes = validate_certificate_in_federation_config(
98
- app, federation_config
99
- )
100
- channel = create_channel(
101
- server_address=federation_config["address"],
102
- insecure=insecure,
103
- root_certificates=root_certificates_bytes,
104
- max_message_length=GRPC_MAX_MESSAGE_LENGTH,
105
- interceptors=None,
106
- )
107
- channel.subscribe(on_channel_state_change)
108
- return channel
109
-
110
-
111
81
  def _stop_run(
112
82
  stub: ExecStub, # pylint: disable=unused-argument
113
83
  run_id: int, # pylint: disable=unused-argument
flwr/cli/utils.py CHANGED
@@ -15,12 +15,32 @@
15
15
  """Flower command line interface utils."""
16
16
 
17
17
  import hashlib
18
+ import json
18
19
  import re
20
+ from logging import DEBUG
19
21
  from pathlib import Path
20
- from typing import Callable, Optional, cast
22
+ from typing import Any, Callable, Optional, cast
21
23
 
24
+ import grpc
22
25
  import typer
23
26
 
27
+ from flwr.cli.cli_user_auth_interceptor import CliUserAuthInterceptor
28
+ from flwr.common.address import parse_address
29
+ from flwr.common.auth_plugin import CliAuthPlugin
30
+ from flwr.common.constant import AUTH_TYPE, CREDENTIALS_DIR, FLWR_DIR
31
+ from flwr.common.grpc import GRPC_MAX_MESSAGE_LENGTH, create_channel
32
+ from flwr.common.logger import log
33
+
34
+ from .config_utils import validate_certificate_in_federation_config
35
+
36
+ try:
37
+ from flwr.ee import get_cli_auth_plugins
38
+ except ImportError:
39
+
40
+ def get_cli_auth_plugins() -> dict[str, type[CliAuthPlugin]]:
41
+ """Return all CLI authentication plugins."""
42
+ raise NotImplementedError("No authentication plugins are currently supported.")
43
+
24
44
 
25
45
  def prompt_text(
26
46
  text: str,
@@ -136,3 +156,90 @@ def get_sha256_hash(file_path: Path) -> str:
136
156
  break
137
157
  sha256.update(data)
138
158
  return sha256.hexdigest()
159
+
160
+
161
+ def get_user_auth_config_path(
162
+ root_dir: Path, federation: str, server_address: str
163
+ ) -> Path:
164
+ """Return the path to the user auth config file."""
165
+ # Parse the server address
166
+ parsed_addr = parse_address(server_address)
167
+ if parsed_addr is None:
168
+ raise ValueError(f"Invalid server address: {server_address}")
169
+ host, port, is_v6 = parsed_addr
170
+ formatted_addr = f"[{host}]_{port}" if is_v6 else f"{host}_{port}"
171
+
172
+ # Locate the credentials directory
173
+ credentials_dir = root_dir.absolute() / FLWR_DIR / CREDENTIALS_DIR
174
+ credentials_dir.mkdir(parents=True, exist_ok=True)
175
+ return credentials_dir / f"{federation}_{formatted_addr}.json"
176
+
177
+
178
+ def try_obtain_cli_auth_plugin(
179
+ root_dir: Path,
180
+ federation: str,
181
+ federation_config: dict[str, Any],
182
+ auth_type: Optional[str] = None,
183
+ ) -> Optional[CliAuthPlugin]:
184
+ """Load the CLI-side user auth plugin for the given auth type."""
185
+ config_path = get_user_auth_config_path(
186
+ root_dir, federation, federation_config["address"]
187
+ )
188
+
189
+ # Load the config file if it exists
190
+ config: dict[str, Any] = {}
191
+ if config_path.exists():
192
+ with config_path.open("r", encoding="utf-8") as file:
193
+ config = json.load(file)
194
+ # This is the case when the user auth is not enabled
195
+ elif auth_type is None:
196
+ return None
197
+
198
+ # Get the auth type from the config if not provided
199
+ if auth_type is None:
200
+ if AUTH_TYPE not in config:
201
+ return None
202
+ auth_type = config[AUTH_TYPE]
203
+
204
+ # Retrieve auth plugin class and instantiate it
205
+ try:
206
+ all_plugins: dict[str, type[CliAuthPlugin]] = get_cli_auth_plugins()
207
+ auth_plugin_class = all_plugins[auth_type]
208
+ return auth_plugin_class(config_path)
209
+ except KeyError:
210
+ typer.echo(f"❌ Unknown user authentication type: {auth_type}")
211
+ raise typer.Exit(code=1) from None
212
+ except ImportError:
213
+ typer.echo("❌ No authentication plugins are currently supported.")
214
+ raise typer.Exit(code=1) from None
215
+
216
+
217
+ def init_channel(
218
+ app: Path, federation_config: dict[str, Any], auth_plugin: Optional[CliAuthPlugin]
219
+ ) -> grpc.Channel:
220
+ """Initialize gRPC channel to the Exec API."""
221
+
222
+ def on_channel_state_change(channel_connectivity: str) -> None:
223
+ """Log channel connectivity."""
224
+ log(DEBUG, channel_connectivity)
225
+
226
+ insecure, root_certificates_bytes = validate_certificate_in_federation_config(
227
+ app, federation_config
228
+ )
229
+
230
+ # Initialize the CLI-side user auth interceptor
231
+ interceptors: list[grpc.UnaryUnaryClientInterceptor] = []
232
+ if auth_plugin is not None:
233
+ auth_plugin.load_tokens()
234
+ interceptors = CliUserAuthInterceptor(auth_plugin)
235
+
236
+ # Create the gRPC channel
237
+ channel = create_channel(
238
+ server_address=federation_config["address"],
239
+ insecure=insecure,
240
+ root_certificates=root_certificates_bytes,
241
+ max_message_length=GRPC_MAX_MESSAGE_LENGTH,
242
+ interceptors=interceptors or None,
243
+ )
244
+ channel.subscribe(on_channel_state_change)
245
+ return channel
flwr/common/config.py CHANGED
@@ -27,6 +27,7 @@ from flwr.common.constant import (
27
27
  APP_DIR,
28
28
  FAB_CONFIG_FILE,
29
29
  FAB_HASH_TRUNCATION,
30
+ FLWR_DIR,
30
31
  FLWR_HOME,
31
32
  )
32
33
  from flwr.common.typing import Run, UserConfig, UserConfigValue
@@ -38,7 +39,7 @@ def get_flwr_dir(provided_path: Optional[str] = None) -> Path:
38
39
  return Path(
39
40
  os.getenv(
40
41
  FLWR_HOME,
41
- Path(f"{os.getenv('XDG_DATA_HOME', os.getenv('HOME'))}") / ".flwr",
42
+ Path(f"{os.getenv('XDG_DATA_HOME', os.getenv('HOME'))}") / FLWR_DIR,
42
43
  )
43
44
  )
44
45
  return Path(provided_path).absolute()
flwr/common/constant.py CHANGED
@@ -80,7 +80,8 @@ FAB_ALLOWED_EXTENSIONS = {".py", ".toml", ".md"}
80
80
  FAB_CONFIG_FILE = "pyproject.toml"
81
81
  FAB_DATE = (2024, 10, 1, 0, 0, 0)
82
82
  FAB_HASH_TRUNCATION = 8
83
- FLWR_HOME = "FLWR_HOME"
83
+ FLWR_DIR = ".flwr" # The default Flower directory: ~/.flwr/
84
+ FLWR_HOME = "FLWR_HOME" # If set, override the default Flower directory
84
85
 
85
86
  # Constants entries in Node config for Simulation
86
87
  PARTITION_ID_KEY = "partition-id"
@@ -110,6 +111,8 @@ LOG_UPLOAD_INTERVAL = 0.2 # Minimum interval between two log uploads
110
111
  # Retry configurations
111
112
  MAX_RETRY_DELAY = 20 # Maximum delay duration between two consecutive retries.
112
113
 
114
+ # Constants for user authentication
115
+ CREDENTIALS_DIR = ".credentials"
113
116
  AUTH_TYPE = "auth_type"
114
117
 
115
118
 
@@ -93,8 +93,8 @@ def pseudo_rand_gen(
93
93
  output = []
94
94
  for dimension in dimensions_list:
95
95
  if len(dimension) == 0:
96
- arr = np.array(gen.randint(0, num_range - 1), dtype=int)
96
+ arr = np.array(gen.randint(0, num_range - 1), dtype=np.int64)
97
97
  else:
98
- arr = gen.randint(0, num_range - 1, dimension)
98
+ arr = gen.randint(0, num_range - 1, dimension, dtype=np.int64)
99
99
  output.append(arr)
100
100
  return output
flwr/common/telemetry.py CHANGED
@@ -27,6 +27,7 @@ from enum import Enum, auto
27
27
  from pathlib import Path
28
28
  from typing import Any, Optional, Union, cast
29
29
 
30
+ from flwr.common.constant import FLWR_DIR
30
31
  from flwr.common.version import package_name, package_version
31
32
 
32
33
  FLWR_TELEMETRY_ENABLED = os.getenv("FLWR_TELEMETRY_ENABLED", "1")
@@ -86,7 +87,7 @@ def _get_source_id() -> str:
86
87
  # If the home directory can’t be resolved, RuntimeError is raised.
87
88
  return source_id
88
89
 
89
- flwr_dir = home.joinpath(".flwr")
90
+ flwr_dir = home.joinpath(FLWR_DIR)
90
91
  # Create .flwr directory if it does not exist yet.
91
92
  try:
92
93
  flwr_dir.mkdir(parents=True, exist_ok=True)
@@ -20,7 +20,7 @@ from flwr.proto import run_pb2 as flwr_dot_proto_dot_run__pb2
20
20
  from flwr.proto import fab_pb2 as flwr_dot_proto_dot_fab__pb2
21
21
 
22
22
 
23
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1c\x66lwr/proto/serverappio.proto\x12\nflwr.proto\x1a\x14\x66lwr/proto/log.proto\x1a\x15\x66lwr/proto/node.proto\x1a\x18\x66lwr/proto/message.proto\x1a\x15\x66lwr/proto/task.proto\x1a\x14\x66lwr/proto/run.proto\x1a\x14\x66lwr/proto/fab.proto\"!\n\x0fGetNodesRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\"3\n\x10GetNodesResponse\x12\x1f\n\x05nodes\x18\x01 \x03(\x0b\x32\x10.flwr.proto.Node\"@\n\x12PushTaskInsRequest\x12*\n\rtask_ins_list\x18\x01 \x03(\x0b\x32\x13.flwr.proto.TaskIns\"\'\n\x13PushTaskInsResponse\x12\x10\n\x08task_ids\x18\x02 \x03(\t\"F\n\x12PullTaskResRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12\x10\n\x08task_ids\x18\x02 \x03(\t\"A\n\x13PullTaskResResponse\x12*\n\rtask_res_list\x18\x01 \x03(\x0b\x32\x13.flwr.proto.TaskRes\"\x1c\n\x1aPullServerAppInputsRequest\"\x7f\n\x1bPullServerAppInputsResponse\x12$\n\x07\x63ontext\x18\x01 \x01(\x0b\x32\x13.flwr.proto.Context\x12\x1c\n\x03run\x18\x02 \x01(\x0b\x32\x0f.flwr.proto.Run\x12\x1c\n\x03\x66\x61\x62\x18\x03 \x01(\x0b\x32\x0f.flwr.proto.Fab\"S\n\x1bPushServerAppOutputsRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\x12$\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x13.flwr.proto.Context\"\x1e\n\x1cPushServerAppOutputsResponse2\xca\x06\n\x0bServerAppIo\x12J\n\tCreateRun\x12\x1c.flwr.proto.CreateRunRequest\x1a\x1d.flwr.proto.CreateRunResponse\"\x00\x12G\n\x08GetNodes\x12\x1b.flwr.proto.GetNodesRequest\x1a\x1c.flwr.proto.GetNodesResponse\"\x00\x12P\n\x0bPushTaskIns\x12\x1e.flwr.proto.PushTaskInsRequest\x1a\x1f.flwr.proto.PushTaskInsResponse\"\x00\x12P\n\x0bPullTaskRes\x12\x1e.flwr.proto.PullTaskResRequest\x1a\x1f.flwr.proto.PullTaskResResponse\"\x00\x12\x41\n\x06GetRun\x12\x19.flwr.proto.GetRunRequest\x1a\x1a.flwr.proto.GetRunResponse\"\x00\x12\x41\n\x06GetFab\x12\x19.flwr.proto.GetFabRequest\x1a\x1a.flwr.proto.GetFabResponse\"\x00\x12h\n\x13PullServerAppInputs\x12&.flwr.proto.PullServerAppInputsRequest\x1a\'.flwr.proto.PullServerAppInputsResponse\"\x00\x12k\n\x14PushServerAppOutputs\x12\'.flwr.proto.PushServerAppOutputsRequest\x1a(.flwr.proto.PushServerAppOutputsResponse\"\x00\x12\\\n\x0fUpdateRunStatus\x12\".flwr.proto.UpdateRunStatusRequest\x1a#.flwr.proto.UpdateRunStatusResponse\"\x00\x12G\n\x08PushLogs\x12\x1b.flwr.proto.PushLogsRequest\x1a\x1c.flwr.proto.PushLogsResponse\"\x00\x62\x06proto3')
23
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1c\x66lwr/proto/serverappio.proto\x12\nflwr.proto\x1a\x14\x66lwr/proto/log.proto\x1a\x15\x66lwr/proto/node.proto\x1a\x18\x66lwr/proto/message.proto\x1a\x15\x66lwr/proto/task.proto\x1a\x14\x66lwr/proto/run.proto\x1a\x14\x66lwr/proto/fab.proto\"!\n\x0fGetNodesRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\"3\n\x10GetNodesResponse\x12\x1f\n\x05nodes\x18\x01 \x03(\x0b\x32\x10.flwr.proto.Node\"P\n\x12PushTaskInsRequest\x12*\n\rtask_ins_list\x18\x01 \x03(\x0b\x32\x13.flwr.proto.TaskIns\x12\x0e\n\x06run_id\x18\x02 \x01(\x04\"\'\n\x13PushTaskInsResponse\x12\x10\n\x08task_ids\x18\x02 \x03(\t\"V\n\x12PullTaskResRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12\x10\n\x08task_ids\x18\x02 \x03(\t\x12\x0e\n\x06run_id\x18\x03 \x01(\x04\"A\n\x13PullTaskResResponse\x12*\n\rtask_res_list\x18\x01 \x03(\x0b\x32\x13.flwr.proto.TaskRes\"\x1c\n\x1aPullServerAppInputsRequest\"\x7f\n\x1bPullServerAppInputsResponse\x12$\n\x07\x63ontext\x18\x01 \x01(\x0b\x32\x13.flwr.proto.Context\x12\x1c\n\x03run\x18\x02 \x01(\x0b\x32\x0f.flwr.proto.Run\x12\x1c\n\x03\x66\x61\x62\x18\x03 \x01(\x0b\x32\x0f.flwr.proto.Fab\"S\n\x1bPushServerAppOutputsRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\x12$\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x13.flwr.proto.Context\"\x1e\n\x1cPushServerAppOutputsResponse2\x9f\x07\n\x0bServerAppIo\x12J\n\tCreateRun\x12\x1c.flwr.proto.CreateRunRequest\x1a\x1d.flwr.proto.CreateRunResponse\"\x00\x12G\n\x08GetNodes\x12\x1b.flwr.proto.GetNodesRequest\x1a\x1c.flwr.proto.GetNodesResponse\"\x00\x12P\n\x0bPushTaskIns\x12\x1e.flwr.proto.PushTaskInsRequest\x1a\x1f.flwr.proto.PushTaskInsResponse\"\x00\x12P\n\x0bPullTaskRes\x12\x1e.flwr.proto.PullTaskResRequest\x1a\x1f.flwr.proto.PullTaskResResponse\"\x00\x12\x41\n\x06GetRun\x12\x19.flwr.proto.GetRunRequest\x1a\x1a.flwr.proto.GetRunResponse\"\x00\x12\x41\n\x06GetFab\x12\x19.flwr.proto.GetFabRequest\x1a\x1a.flwr.proto.GetFabResponse\"\x00\x12h\n\x13PullServerAppInputs\x12&.flwr.proto.PullServerAppInputsRequest\x1a\'.flwr.proto.PullServerAppInputsResponse\"\x00\x12k\n\x14PushServerAppOutputs\x12\'.flwr.proto.PushServerAppOutputsRequest\x1a(.flwr.proto.PushServerAppOutputsResponse\"\x00\x12\\\n\x0fUpdateRunStatus\x12\".flwr.proto.UpdateRunStatusRequest\x1a#.flwr.proto.UpdateRunStatusResponse\"\x00\x12S\n\x0cGetRunStatus\x12\x1f.flwr.proto.GetRunStatusRequest\x1a .flwr.proto.GetRunStatusResponse\"\x00\x12G\n\x08PushLogs\x12\x1b.flwr.proto.PushLogsRequest\x1a\x1c.flwr.proto.PushLogsResponse\"\x00\x62\x06proto3')
24
24
 
25
25
  _globals = globals()
26
26
  _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -32,21 +32,21 @@ if _descriptor._USE_C_DESCRIPTORS == False:
32
32
  _globals['_GETNODESRESPONSE']._serialized_start=217
33
33
  _globals['_GETNODESRESPONSE']._serialized_end=268
34
34
  _globals['_PUSHTASKINSREQUEST']._serialized_start=270
35
- _globals['_PUSHTASKINSREQUEST']._serialized_end=334
36
- _globals['_PUSHTASKINSRESPONSE']._serialized_start=336
37
- _globals['_PUSHTASKINSRESPONSE']._serialized_end=375
38
- _globals['_PULLTASKRESREQUEST']._serialized_start=377
39
- _globals['_PULLTASKRESREQUEST']._serialized_end=447
40
- _globals['_PULLTASKRESRESPONSE']._serialized_start=449
41
- _globals['_PULLTASKRESRESPONSE']._serialized_end=514
42
- _globals['_PULLSERVERAPPINPUTSREQUEST']._serialized_start=516
43
- _globals['_PULLSERVERAPPINPUTSREQUEST']._serialized_end=544
44
- _globals['_PULLSERVERAPPINPUTSRESPONSE']._serialized_start=546
45
- _globals['_PULLSERVERAPPINPUTSRESPONSE']._serialized_end=673
46
- _globals['_PUSHSERVERAPPOUTPUTSREQUEST']._serialized_start=675
47
- _globals['_PUSHSERVERAPPOUTPUTSREQUEST']._serialized_end=758
48
- _globals['_PUSHSERVERAPPOUTPUTSRESPONSE']._serialized_start=760
49
- _globals['_PUSHSERVERAPPOUTPUTSRESPONSE']._serialized_end=790
50
- _globals['_SERVERAPPIO']._serialized_start=793
51
- _globals['_SERVERAPPIO']._serialized_end=1635
35
+ _globals['_PUSHTASKINSREQUEST']._serialized_end=350
36
+ _globals['_PUSHTASKINSRESPONSE']._serialized_start=352
37
+ _globals['_PUSHTASKINSRESPONSE']._serialized_end=391
38
+ _globals['_PULLTASKRESREQUEST']._serialized_start=393
39
+ _globals['_PULLTASKRESREQUEST']._serialized_end=479
40
+ _globals['_PULLTASKRESRESPONSE']._serialized_start=481
41
+ _globals['_PULLTASKRESRESPONSE']._serialized_end=546
42
+ _globals['_PULLSERVERAPPINPUTSREQUEST']._serialized_start=548
43
+ _globals['_PULLSERVERAPPINPUTSREQUEST']._serialized_end=576
44
+ _globals['_PULLSERVERAPPINPUTSRESPONSE']._serialized_start=578
45
+ _globals['_PULLSERVERAPPINPUTSRESPONSE']._serialized_end=705
46
+ _globals['_PUSHSERVERAPPOUTPUTSREQUEST']._serialized_start=707
47
+ _globals['_PUSHSERVERAPPOUTPUTSREQUEST']._serialized_end=790
48
+ _globals['_PUSHSERVERAPPOUTPUTSRESPONSE']._serialized_start=792
49
+ _globals['_PUSHSERVERAPPOUTPUTSRESPONSE']._serialized_end=822
50
+ _globals['_SERVERAPPIO']._serialized_start=825
51
+ _globals['_SERVERAPPIO']._serialized_end=1752
52
52
  # @@protoc_insertion_point(module_scope)
@@ -44,13 +44,16 @@ class PushTaskInsRequest(google.protobuf.message.Message):
44
44
  """PushTaskIns messages"""
45
45
  DESCRIPTOR: google.protobuf.descriptor.Descriptor
46
46
  TASK_INS_LIST_FIELD_NUMBER: builtins.int
47
+ RUN_ID_FIELD_NUMBER: builtins.int
47
48
  @property
48
49
  def task_ins_list(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[flwr.proto.task_pb2.TaskIns]: ...
50
+ run_id: builtins.int
49
51
  def __init__(self,
50
52
  *,
51
53
  task_ins_list: typing.Optional[typing.Iterable[flwr.proto.task_pb2.TaskIns]] = ...,
54
+ run_id: builtins.int = ...,
52
55
  ) -> None: ...
53
- def ClearField(self, field_name: typing_extensions.Literal["task_ins_list",b"task_ins_list"]) -> None: ...
56
+ def ClearField(self, field_name: typing_extensions.Literal["run_id",b"run_id","task_ins_list",b"task_ins_list"]) -> None: ...
54
57
  global___PushTaskInsRequest = PushTaskInsRequest
55
58
 
56
59
  class PushTaskInsResponse(google.protobuf.message.Message):
@@ -70,17 +73,20 @@ class PullTaskResRequest(google.protobuf.message.Message):
70
73
  DESCRIPTOR: google.protobuf.descriptor.Descriptor
71
74
  NODE_FIELD_NUMBER: builtins.int
72
75
  TASK_IDS_FIELD_NUMBER: builtins.int
76
+ RUN_ID_FIELD_NUMBER: builtins.int
73
77
  @property
74
78
  def node(self) -> flwr.proto.node_pb2.Node: ...
75
79
  @property
76
80
  def task_ids(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[typing.Text]: ...
81
+ run_id: builtins.int
77
82
  def __init__(self,
78
83
  *,
79
84
  node: typing.Optional[flwr.proto.node_pb2.Node] = ...,
80
85
  task_ids: typing.Optional[typing.Iterable[typing.Text]] = ...,
86
+ run_id: builtins.int = ...,
81
87
  ) -> None: ...
82
88
  def HasField(self, field_name: typing_extensions.Literal["node",b"node"]) -> builtins.bool: ...
83
- def ClearField(self, field_name: typing_extensions.Literal["node",b"node","task_ids",b"task_ids"]) -> None: ...
89
+ def ClearField(self, field_name: typing_extensions.Literal["node",b"node","run_id",b"run_id","task_ids",b"task_ids"]) -> None: ...
84
90
  global___PullTaskResRequest = PullTaskResRequest
85
91
 
86
92
  class PullTaskResResponse(google.protobuf.message.Message):
@@ -62,6 +62,11 @@ class ServerAppIoStub(object):
62
62
  request_serializer=flwr_dot_proto_dot_run__pb2.UpdateRunStatusRequest.SerializeToString,
63
63
  response_deserializer=flwr_dot_proto_dot_run__pb2.UpdateRunStatusResponse.FromString,
64
64
  )
65
+ self.GetRunStatus = channel.unary_unary(
66
+ '/flwr.proto.ServerAppIo/GetRunStatus',
67
+ request_serializer=flwr_dot_proto_dot_run__pb2.GetRunStatusRequest.SerializeToString,
68
+ response_deserializer=flwr_dot_proto_dot_run__pb2.GetRunStatusResponse.FromString,
69
+ )
65
70
  self.PushLogs = channel.unary_unary(
66
71
  '/flwr.proto.ServerAppIo/PushLogs',
67
72
  request_serializer=flwr_dot_proto_dot_log__pb2.PushLogsRequest.SerializeToString,
@@ -135,6 +140,13 @@ class ServerAppIoServicer(object):
135
140
  context.set_details('Method not implemented!')
136
141
  raise NotImplementedError('Method not implemented!')
137
142
 
143
+ def GetRunStatus(self, request, context):
144
+ """Get the status of a given run
145
+ """
146
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
147
+ context.set_details('Method not implemented!')
148
+ raise NotImplementedError('Method not implemented!')
149
+
138
150
  def PushLogs(self, request, context):
139
151
  """Push ServerApp logs
140
152
  """
@@ -190,6 +202,11 @@ def add_ServerAppIoServicer_to_server(servicer, server):
190
202
  request_deserializer=flwr_dot_proto_dot_run__pb2.UpdateRunStatusRequest.FromString,
191
203
  response_serializer=flwr_dot_proto_dot_run__pb2.UpdateRunStatusResponse.SerializeToString,
192
204
  ),
205
+ 'GetRunStatus': grpc.unary_unary_rpc_method_handler(
206
+ servicer.GetRunStatus,
207
+ request_deserializer=flwr_dot_proto_dot_run__pb2.GetRunStatusRequest.FromString,
208
+ response_serializer=flwr_dot_proto_dot_run__pb2.GetRunStatusResponse.SerializeToString,
209
+ ),
193
210
  'PushLogs': grpc.unary_unary_rpc_method_handler(
194
211
  servicer.PushLogs,
195
212
  request_deserializer=flwr_dot_proto_dot_log__pb2.PushLogsRequest.FromString,
@@ -358,6 +375,23 @@ class ServerAppIo(object):
358
375
  options, channel_credentials,
359
376
  insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
360
377
 
378
+ @staticmethod
379
+ def GetRunStatus(request,
380
+ target,
381
+ options=(),
382
+ channel_credentials=None,
383
+ call_credentials=None,
384
+ insecure=False,
385
+ compression=None,
386
+ wait_for_ready=None,
387
+ timeout=None,
388
+ metadata=None):
389
+ return grpc.experimental.unary_unary(request, target, '/flwr.proto.ServerAppIo/GetRunStatus',
390
+ flwr_dot_proto_dot_run__pb2.GetRunStatusRequest.SerializeToString,
391
+ flwr_dot_proto_dot_run__pb2.GetRunStatusResponse.FromString,
392
+ options, channel_credentials,
393
+ insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
394
+
361
395
  @staticmethod
362
396
  def PushLogs(request,
363
397
  target,
@@ -56,6 +56,11 @@ class ServerAppIoStub:
56
56
  flwr.proto.run_pb2.UpdateRunStatusResponse]
57
57
  """Update the status of a given run"""
58
58
 
59
+ GetRunStatus: grpc.UnaryUnaryMultiCallable[
60
+ flwr.proto.run_pb2.GetRunStatusRequest,
61
+ flwr.proto.run_pb2.GetRunStatusResponse]
62
+ """Get the status of a given run"""
63
+
59
64
  PushLogs: grpc.UnaryUnaryMultiCallable[
60
65
  flwr.proto.log_pb2.PushLogsRequest,
61
66
  flwr.proto.log_pb2.PushLogsResponse]
@@ -135,6 +140,14 @@ class ServerAppIoServicer(metaclass=abc.ABCMeta):
135
140
  """Update the status of a given run"""
136
141
  pass
137
142
 
143
+ @abc.abstractmethod
144
+ def GetRunStatus(self,
145
+ request: flwr.proto.run_pb2.GetRunStatusRequest,
146
+ context: grpc.ServicerContext,
147
+ ) -> flwr.proto.run_pb2.GetRunStatusResponse:
148
+ """Get the status of a given run"""
149
+ pass
150
+
138
151
  @abc.abstractmethod
139
152
  def PushLogs(self,
140
153
  request: flwr.proto.log_pb2.PushLogsRequest,
@@ -203,7 +203,9 @@ class GrpcDriver(Driver):
203
203
  task_ins_list.append(taskins)
204
204
  # Call GrpcDriverStub method
205
205
  res: PushTaskInsResponse = self._stub.PushTaskIns(
206
- PushTaskInsRequest(task_ins_list=task_ins_list)
206
+ PushTaskInsRequest(
207
+ task_ins_list=task_ins_list, run_id=cast(Run, self._run).run_id
208
+ )
207
209
  )
208
210
  return list(res.task_ids)
209
211
 
@@ -215,7 +217,9 @@ class GrpcDriver(Driver):
215
217
  """
216
218
  # Pull TaskRes
217
219
  res: PullTaskResResponse = self._stub.PullTaskRes(
218
- PullTaskResRequest(node=self.node, task_ids=message_ids)
220
+ PullTaskResRequest(
221
+ node=self.node, task_ids=message_ids, run_id=cast(Run, self._run).run_id
222
+ )
219
223
  )
220
224
  # Convert TaskRes to Message
221
225
  msgs = [message_from_taskres(taskres) for taskres in res.task_res_list]
@@ -32,6 +32,7 @@ from flwr.common.serde import (
32
32
  fab_from_proto,
33
33
  fab_to_proto,
34
34
  run_status_from_proto,
35
+ run_status_to_proto,
35
36
  run_to_proto,
36
37
  user_config_from_proto,
37
38
  )
@@ -48,6 +49,8 @@ from flwr.proto.run_pb2 import ( # pylint: disable=E0611
48
49
  CreateRunResponse,
49
50
  GetRunRequest,
50
51
  GetRunResponse,
52
+ GetRunStatusRequest,
53
+ GetRunStatusResponse,
51
54
  UpdateRunStatusRequest,
52
55
  UpdateRunStatusResponse,
53
56
  )
@@ -284,6 +287,21 @@ class ServerAppIoServicer(serverappio_pb2_grpc.ServerAppIoServicer):
284
287
  state.add_serverapp_log(request.run_id, merged_logs)
285
288
  return PushLogsResponse()
286
289
 
290
+ def GetRunStatus(
291
+ self, request: GetRunStatusRequest, context: grpc.ServicerContext
292
+ ) -> GetRunStatusResponse:
293
+ """Get the status of a run."""
294
+ log(DEBUG, "ServerAppIoServicer.GetRunStatus")
295
+ state = self.state_factory.state()
296
+
297
+ # Get run status from LinkState
298
+ run_statuses = state.get_run_status(set(request.run_ids))
299
+ run_status_dict = {
300
+ run_id: run_status_to_proto(run_status)
301
+ for run_id, run_status in run_statuses.items()
302
+ }
303
+ return GetRunStatusResponse(run_status_dict=run_status_dict)
304
+
287
305
 
288
306
  def _raise_if(validation_error: bool, detail: str) -> None:
289
307
  if validation_error:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: flwr-nightly
3
- Version: 1.14.0.dev20241210
3
+ Version: 1.14.0.dev20241211
4
4
  Summary: Flower: A Friendly Federated AI Framework
5
5
  Home-page: https://flower.ai
6
6
  License: Apache-2.0
@@ -102,23 +102,23 @@ Flower's goal is to make federated learning accessible to everyone. This series
102
102
 
103
103
  0. **What is Federated Learning?**
104
104
 
105
- [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/adap/flower/blob/main/doc/source/tutorial-series-what-is-federated-learning.ipynb) (or open the [Jupyter Notebook](https://github.com/adap/flower/blob/main/doc/source/tutorial-series-what-is-federated-learning.ipynb))
105
+ [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/adap/flower/blob/main/framework/docs/source/tutorial-series-what-is-federated-learning.ipynb) (or open the [Jupyter Notebook](https://github.com/adap/flower/blob/main/framework/docs/source/tutorial-series-what-is-federated-learning.ipynb))
106
106
 
107
107
  1. **An Introduction to Federated Learning**
108
108
 
109
- [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/adap/flower/blob/main/doc/source/tutorial-series-get-started-with-flower-pytorch.ipynb) (or open the [Jupyter Notebook](https://github.com/adap/flower/blob/main/doc/source/tutorial-series-get-started-with-flower-pytorch.ipynb))
109
+ [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/adap/flower/blob/main/framework/docs/source/tutorial-series-get-started-with-flower-pytorch.ipynb) (or open the [Jupyter Notebook](https://github.com/adap/flower/blob/main/framework/docs/source/tutorial-series-get-started-with-flower-pytorch.ipynb))
110
110
 
111
111
  2. **Using Strategies in Federated Learning**
112
112
 
113
- [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/adap/flower/blob/main/doc/source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb) (or open the [Jupyter Notebook](https://github.com/adap/flower/blob/main/doc/source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb))
113
+ [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/adap/flower/blob/main/framework/docs/source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb) (or open the [Jupyter Notebook](https://github.com/adap/flower/blob/main/framework/docs/source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb))
114
114
 
115
115
  3. **Building Strategies for Federated Learning**
116
116
 
117
- [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/adap/flower/blob/main/doc/source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb) (or open the [Jupyter Notebook](https://github.com/adap/flower/blob/main/doc/source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb))
117
+ [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/adap/flower/blob/main/framework/docs/source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb) (or open the [Jupyter Notebook](https://github.com/adap/flower/blob/main/framework/docs/source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb))
118
118
 
119
119
  4. **Custom Clients for Federated Learning**
120
120
 
121
- [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/adap/flower/blob/main/doc/source/tutorial-series-customize-the-client-pytorch.ipynb) (or open the [Jupyter Notebook](https://github.com/adap/flower/blob/main/doc/source/tutorial-series-customize-the-client-pytorch.ipynb))
121
+ [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/adap/flower/blob/main/doc/source/tutorial-series-customize-the-client-pytorch.ipynb) (or open the [Jupyter Notebook](https://github.com/adap/flower/blob/main/framework/docs/source/tutorial-series-customize-the-client-pytorch.ipynb))
122
122
 
123
123
  Stay tuned, more tutorials are coming soon. Topics include **Privacy and Security in Federated Learning**, and **Scaling Federated Learning**.
124
124
 
@@ -1,12 +1,15 @@
1
1
  flwr/__init__.py,sha256=VmBWedrCxqmt4QvUHBLqyVEH6p7zaFMD_oCHerXHSVw,937
2
2
  flwr/cli/__init__.py,sha256=cZJVgozlkC6Ni2Hd_FAIrqefrkCGOV18fikToq-6iLw,720
3
- flwr/cli/app.py,sha256=4naV5q1Fepne8XAgdGISxotWSIT__sm0TIHdodSvou4,1335
3
+ flwr/cli/app.py,sha256=KF__zHSy7KQCMx_Rb0YPzcoZbQY-Zo4f70BhBgP4ENM,1381
4
4
  flwr/cli/build.py,sha256=k2M0aIY2q5WB_yXQ22Woxt1zb6m-Z1wNwmhWMxEm5Dw,6344
5
- flwr/cli/config_utils.py,sha256=n-xNkQG_0POz5UUHyE00lthNaOjuS6IYU9Thzb_BThs,11431
5
+ flwr/cli/cli_user_auth_interceptor.py,sha256=rEjgAZmzHO0GjwdyZib6bkTI2X59ErJAZlutqpqZGF0,2952
6
+ flwr/cli/config_utils.py,sha256=f4ViGujhEat9l3YDq24AE-hao4pAK_hVLRXZXDd_F_A,12078
6
7
  flwr/cli/example.py,sha256=1bGDYll3BXQY2kRqSN-oICqS5n1b9m0g0RvXTopXHl4,2215
7
8
  flwr/cli/install.py,sha256=kmD2dW-9B7645GAQx5es1o2W11gRHQ2Fg2E31SLomrg,8179
8
- flwr/cli/log.py,sha256=WlAuxZdTUYZ5bRKkm0jLWrOxHTS0TlSA5BeDtO9xF3k,6659
9
- flwr/cli/ls.py,sha256=aUaP49kkg4nV2nRYfO8qgbtV_FF5xN5gCy3ziD2HbUk,11513
9
+ flwr/cli/log.py,sha256=7V5NPGiR8FMDkkNTc5SE1pxMskQgp0H5HniG977LISo,5994
10
+ flwr/cli/login/__init__.py,sha256=PEh6QjLSx7ltN8d8Jfi25dHFPKtCNKjYJZCkYQBfmm0,799
11
+ flwr/cli/login/login.py,sha256=GVm6rkLDVQ6WuT2mw52gBKNW_Y5IjGg_OOGoEmpx9KM,2796
12
+ flwr/cli/ls.py,sha256=5uO0YG0XXn7paS4oUs1T7rwicApxMV3ac9ejBZfLN3k,10545
10
13
  flwr/cli/new/__init__.py,sha256=cQzK1WH4JP2awef1t2UQ2xjl1agVEz9rwutV18SWV1k,789
11
14
  flwr/cli/new/new.py,sha256=xgzObnhNpnGvjVs6wTj6BlJ9X-avPhRX3DuwWnk9ED0,9903
12
15
  flwr/cli/new/templates/__init__.py,sha256=4luU8RL-CK8JJCstQ_ON809W9bNTkY1l9zSaPKBkgwY,725
@@ -62,9 +65,9 @@ flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl,sha256=UtH3Vslg2S8fIKIHC-d
62
65
  flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl,sha256=01HArBqRrbZT3O7pXOM9MqduXMNm525wv7Sj6dvYMJE,686
63
66
  flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl,sha256=KVCIOEYNWnq6j7XOboXqZshc9aQ2PyRDUu7bZtmfJ24,710
64
67
  flwr/cli/run/__init__.py,sha256=oCd6HmQDx-sqver1gecgx-uMA38BLTSiiKpl7RGNceg,789
65
- flwr/cli/run/run.py,sha256=5To92BOrfM5VEwNp2zzRUoz4tHE2NtazxIQICProA8k,8503
66
- flwr/cli/stop.py,sha256=OrV_znXyu3t_gABHisAA6uHwFdFPP2Z9bKvhcE1O5Yk,4116
67
- flwr/cli/utils.py,sha256=emMUdthvoHBTB0iGQp-oFBmA5wV46lw3y3FmfXQPCsc,4500
68
+ flwr/cli/run/run.py,sha256=sklMcREZnnuot4TSeCIpVYqZJ2Ar2SOye3q0lw5s218,8139
69
+ flwr/cli/stop.py,sha256=pa3etMLCLxfGl9w2c2o6e5u46j6LimEmNp2zuQGxAIk,3143
70
+ flwr/cli/utils.py,sha256=hiT4-tDPmS5U_mH0ED9DVTElfmfYUMV6JuPWZjTaIgQ,8330
68
71
  flwr/client/__init__.py,sha256=DGDoO0AEAfz-0CUFmLdyUUweAS64-07AOnmDfWUefK4,1192
69
72
  flwr/client/app.py,sha256=3AKrJduvki_1ATvKCQV4T9_1qZuarVVTtpnsq6_cWw0,34384
70
73
  flwr/client/client.py,sha256=gy6WVlMUFAp8oevN4xpQPX30vPOIYGVqdbuFlTWkyG4,9080
@@ -110,8 +113,8 @@ flwr/common/address.py,sha256=7kM2Rqjw86-c8aKwAvrXerWqznnVv4TFJ62aSAeTn10,3017
110
113
  flwr/common/args.py,sha256=-KeQ6AZw1-G4Ifhsg4qlRnWhGH1m_OzUgxH7Z4j_0ns,6222
111
114
  flwr/common/auth_plugin/__init__.py,sha256=1Y8Oj3iB49IHDu9tvDih1J74Ygu7k85V9s2A4WORPyA,887
112
115
  flwr/common/auth_plugin/auth_plugin.py,sha256=6WEAVVPrS7LgSBpd4WyHYU4EsajT2nBGI_IN3mhYzoU,3567
113
- flwr/common/config.py,sha256=qC1QvGAGr4faBtg3Y5dWhfyK5FggyWUMjPqg-Rx_FW4,8083
114
- flwr/common/constant.py,sha256=-HoTq6u_9VGbva21qTm_vfvQV9cxV7LwsvvlHEBjNwk,5817
116
+ flwr/common/config.py,sha256=kH8u7VBRfyv5cpOC0lQ1jBbxJ6Jo2b3XhUwbIbHoHHw,8098
117
+ flwr/common/constant.py,sha256=9HwFVxFWbLTzMetIffUT3gAC9nPtqzBNxrKWr5A0oSI,5996
115
118
  flwr/common/context.py,sha256=uJ-mnoC_8y_udEb3kAX-r8CPphNTWM72z1AlsvQEu54,2403
116
119
  flwr/common/date.py,sha256=NHHpESce5wYqEwoDXf09gp9U9l_5Bmlh2BsOcwS-kDM,1554
117
120
  flwr/common/differential_privacy.py,sha256=XwcJ3rWr8S8BZUocc76vLSJAXIf6OHnWkBV6-xlIRuw,6106
@@ -140,9 +143,9 @@ flwr/common/secure_aggregation/crypto/symmetric_encryption.py,sha256=wTDbOaMGZwT
140
143
  flwr/common/secure_aggregation/ndarrays_arithmetic.py,sha256=zvVAIrIyI6OSzGhpCi8NNaTvPXmoMYQIPJT-NkBg8RU,3013
141
144
  flwr/common/secure_aggregation/quantization.py,sha256=mC4uLf05zeONo8Ke-BY0Tj8UCMOS7VD93zHCzuv3MHU,2304
142
145
  flwr/common/secure_aggregation/secaggplus_constants.py,sha256=9MF-oQh62uD7rt9VeNB-rHf2gBLd5GL3S9OejCxmILY,2183
143
- flwr/common/secure_aggregation/secaggplus_utils.py,sha256=o7IhHH6J9xqinhQy3TdPgQpoj1XyEpyv3OQFyx81RVQ,3193
146
+ flwr/common/secure_aggregation/secaggplus_utils.py,sha256=OgYd68YBRaHQYLc-YdExj9CSpwL58bVTaPrdHoAj2AE,3214
144
147
  flwr/common/serde.py,sha256=K9ExsqcTPETESkt2HMaNtIQAIAfwmuwtJFlG-59I7Sw,31046
145
- flwr/common/telemetry.py,sha256=20AYNaePOBaSEh99PIuBrxRxtY53-kZ5-2Ej0JWUJmc,8731
148
+ flwr/common/telemetry.py,sha256=CHIwFFQ13sWFavmEvkvA43XR1sbh1S3nWvD5TuCO2eI,8774
146
149
  flwr/common/typing.py,sha256=RLq2f9jhE_Nndtk023cPMG0LpoQHaacEsww-3j0xs4Q,5710
147
150
  flwr/common/version.py,sha256=tCcl_FvxVK206C1dxIJCs4TjL06WmyaODBP19FRHE1c,1324
148
151
  flwr/proto/__init__.py,sha256=hbY7JYakwZwCkYgCNlmHdc8rtvfoJbAZLalMdc--CGc,683
@@ -190,10 +193,10 @@ flwr/proto/run_pb2.py,sha256=J2TQwf-S0o9ImGuQLrczw99S0GSXm6hk-npJ8rXAC0Y,5743
190
193
  flwr/proto/run_pb2.pyi,sha256=i6TEwphuFH94_kT2hZWb_RjndLuphkPrT3C2VP-NnVs,11739
191
194
  flwr/proto/run_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
192
195
  flwr/proto/run_pb2_grpc.pyi,sha256=ff2TSiLVnG6IVQcTGzb2DIH3XRSoAvAo_RMcvbMFyc0,76
193
- flwr/proto/serverappio_pb2.py,sha256=zWnODeaj26oSx98-BFvwtWM_fYvsw9OeSIuV7JnKVvw,4822
194
- flwr/proto/serverappio_pb2.pyi,sha256=Ib9c32FCtjA9zZY54Ohi6B-DtLgSjokAqOExm_2uOvY,6429
195
- flwr/proto/serverappio_pb2_grpc.py,sha256=M__pFMmb9yTAGMHVd3_K1V6DeLRuFc9UErJHWjBAsZs,17439
196
- flwr/proto/serverappio_pb2_grpc.pyi,sha256=ERM-0cQVmUqrVXlvEbS2wfUZpZmv5SlIeNsGZPYMrVo,4779
196
+ flwr/proto/serverappio_pb2.py,sha256=VXJxFLDrH4XzCEM9VnNg3z7gVIsrvl1VE4ZtK_lI_eE,5003
197
+ flwr/proto/serverappio_pb2.pyi,sha256=5SoXVb7fyN0xkl411pmJChlWeQ-sr1Qf9J3B0cAQYc4,6665
198
+ flwr/proto/serverappio_pb2_grpc.py,sha256=fGmk0XC7il5wYFSo6zEa21Ki1OYvEuDDXL0hDDoU4QQ,19062
199
+ flwr/proto/serverappio_pb2_grpc.pyi,sha256=pKdqFpAgkHaNSjRNahnGtSndUif8uB5eFui_q37eDho,5220
197
200
  flwr/proto/simulationio_pb2.py,sha256=Fv7m8d4vR_0CIGU93nN5tDXSCk2QPbASH_8mT2wBPTE,3117
198
201
  flwr/proto/simulationio_pb2.pyi,sha256=oXx8_FLBe5B54wduZj-f89kub73XxNtQbThuW8YfPAs,2660
199
202
  flwr/proto/simulationio_pb2_grpc.py,sha256=9I3yAfJaeMuG-qH_5Ge45eFOftsIOmL9b8E_xHmcvKw,11232
@@ -219,7 +222,7 @@ flwr/server/compat/legacy_context.py,sha256=wBzBcfV6YO6IQGriM_FdJ5XZfiBBEEJdS_Od
219
222
  flwr/server/criterion.py,sha256=ypbAexbztzGUxNen9RCHF91QeqiEQix4t4Ih3E-42MM,1061
220
223
  flwr/server/driver/__init__.py,sha256=bikRv6CjTwSvYh7tf10gziU5o2YotOWhhftz2tr3KDc,886
221
224
  flwr/server/driver/driver.py,sha256=u_fMfqLYTroTafGCNwKPHI4lttRL-Z5CqeT3_FHSq-Q,5701
222
- flwr/server/driver/grpc_driver.py,sha256=aTeQVYjyp19LGUa-a5iKdQRFijLzut2bXj1h8YovdIM,11397
225
+ flwr/server/driver/grpc_driver.py,sha256=cMYtyQJRSwfhCmtJ5UEWN4iXrUKRH5iGXmigiU6sGjM,11529
223
226
  flwr/server/driver/inmemory_driver.py,sha256=gfB4jmkk1indhRa9XCdKCXghVcWBF1qBD-tAxMUyQm0,6404
224
227
  flwr/server/history.py,sha256=qSb5_pPTrwofpSYGsZWzMPkl_4uJ4mJFWesxXDrEvDU,5026
225
228
  flwr/server/run_serverapp.py,sha256=oDfHaHyVT5BRcckFFQKg8AVPCWR1ek7OhNceTC8qq9g,2493
@@ -256,7 +259,7 @@ flwr/server/strategy/strategy.py,sha256=cXapkD5uDrt5C-RbmWDn9FLoap3Q41i7GKvbmfbC
256
259
  flwr/server/superlink/__init__.py,sha256=8tHYCfodUlRD8PCP9fHgvu8cz5N31A2QoRVL0jDJ15E,707
257
260
  flwr/server/superlink/driver/__init__.py,sha256=5soEK5QSvxNjmJQ-CGTWROc4alSAeU0e9Ad9RDhsd3E,717
258
261
  flwr/server/superlink/driver/serverappio_grpc.py,sha256=oTogZLkfeThKdx9Q_bw6OMGHnLIryxQOHxbWi0qgaRM,2185
259
- flwr/server/superlink/driver/serverappio_servicer.py,sha256=nUQgQlxUfCYIvUW5NBq0ZysEL2cFoF2iQJIFabGodNE,10458
262
+ flwr/server/superlink/driver/serverappio_servicer.py,sha256=MSJPcSDim36sXPoK21XmhHYZwWI-i9Z5NiZrvyBRJyc,11124
260
263
  flwr/server/superlink/ffs/__init__.py,sha256=FAY-zShcfPmOxosok2QyT6hTNMNctG8cH9s_nIl8jkI,840
261
264
  flwr/server/superlink/ffs/disk_ffs.py,sha256=yCN6CCzegnJIOaHr5nIu49wZQa4g5BByiSKshz50RKU,3296
262
265
  flwr/server/superlink/ffs/ffs.py,sha256=qLI1UfosJugu2BKOJWqHIhafTm-YiuKqGf3OGWPH0NM,2395
@@ -317,8 +320,8 @@ flwr/superexec/exec_servicer.py,sha256=jEYcASzkQR1ftjzilmlcTPKXo8NSno9mSj_UbBvMj
317
320
  flwr/superexec/exec_user_auth_interceptor.py,sha256=K06OU-l4LnYhTDg071hGJuOaQWEJbZsYi5qxUmmtiG0,3704
318
321
  flwr/superexec/executor.py,sha256=zH3_53il6Jh0ZscIVEB9f4GNnXMeBbCGyCoBCxLgiG0,3114
319
322
  flwr/superexec/simulation.py,sha256=WQDon15oqpMopAZnwRZoTICYCfHqtkvFSqiTQ2hLD_g,4088
320
- flwr_nightly-1.14.0.dev20241210.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
321
- flwr_nightly-1.14.0.dev20241210.dist-info/METADATA,sha256=3DjOUcyJbLgmX8glq_Tt7SV70giiQh5JP39zfpFyLjE,15700
322
- flwr_nightly-1.14.0.dev20241210.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
323
- flwr_nightly-1.14.0.dev20241210.dist-info/entry_points.txt,sha256=JlNxX3qhaV18_2yj5a3kJW1ESxm31cal9iS_N_pf1Rk,538
324
- flwr_nightly-1.14.0.dev20241210.dist-info/RECORD,,
323
+ flwr_nightly-1.14.0.dev20241211.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
324
+ flwr_nightly-1.14.0.dev20241211.dist-info/METADATA,sha256=onYC4ZwXpA_wegLQm_jOd4kFEDQtuJAHOsZ3wYWmue0,15799
325
+ flwr_nightly-1.14.0.dev20241211.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
326
+ flwr_nightly-1.14.0.dev20241211.dist-info/entry_points.txt,sha256=JlNxX3qhaV18_2yj5a3kJW1ESxm31cal9iS_N_pf1Rk,538
327
+ flwr_nightly-1.14.0.dev20241211.dist-info/RECORD,,