flwr-nightly 1.10.0.dev20240616__py3-none-any.whl → 1.10.0.dev20240618__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.

@@ -0,0 +1,15 @@
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
+ """Client-side part of the GrpcAdapter transport layer."""
@@ -0,0 +1,94 @@
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
+ """Contextmanager for a GrpcAdapter channel to the Flower server."""
16
+
17
+
18
+ from contextlib import contextmanager
19
+ from logging import ERROR
20
+ from typing import Callable, Iterator, Optional, Tuple, Union
21
+
22
+ from cryptography.hazmat.primitives.asymmetric import ec
23
+
24
+ from flwr.client.grpc_rere_client.connection import grpc_request_response
25
+ from flwr.client.grpc_rere_client.grpc_adapter import GrpcAdapter
26
+ from flwr.common import GRPC_MAX_MESSAGE_LENGTH
27
+ from flwr.common.logger import log
28
+ from flwr.common.message import Message
29
+ from flwr.common.retry_invoker import RetryInvoker
30
+
31
+
32
+ @contextmanager
33
+ def grpc_adapter( # pylint: disable=R0913
34
+ server_address: str,
35
+ insecure: bool,
36
+ retry_invoker: RetryInvoker,
37
+ max_message_length: int = GRPC_MAX_MESSAGE_LENGTH, # pylint: disable=W0613
38
+ root_certificates: Optional[Union[bytes, str]] = None,
39
+ authentication_keys: Optional[ # pylint: disable=unused-argument
40
+ Tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]
41
+ ] = None,
42
+ ) -> Iterator[
43
+ Tuple[
44
+ Callable[[], Optional[Message]],
45
+ Callable[[Message], None],
46
+ Optional[Callable[[], None]],
47
+ Optional[Callable[[], None]],
48
+ Optional[Callable[[int], Tuple[str, str]]],
49
+ ]
50
+ ]:
51
+ """Primitives for request/response-based interaction with a server via GrpcAdapter.
52
+
53
+ Parameters
54
+ ----------
55
+ server_address : str
56
+ The IPv6 address of the server with `http://` or `https://`.
57
+ If the Flower server runs on the same machine
58
+ on port 8080, then `server_address` would be `"http://[::]:8080"`.
59
+ insecure : bool
60
+ Starts an insecure gRPC connection when True. Enables HTTPS connection
61
+ when False, using system certificates if `root_certificates` is None.
62
+ retry_invoker: RetryInvoker
63
+ `RetryInvoker` object that will try to reconnect the client to the server
64
+ after gRPC errors. If None, the client will only try to
65
+ reconnect once after a failure.
66
+ max_message_length : int
67
+ Ignored, only present to preserve API-compatibility.
68
+ root_certificates : Optional[Union[bytes, str]] (default: None)
69
+ Path of the root certificate. If provided, a secure
70
+ connection using the certificates will be established to an SSL-enabled
71
+ Flower server. Bytes won't work for the REST API.
72
+ authentication_keys : Optional[Tuple[PrivateKey, PublicKey]] (default: None)
73
+ Client authentication is not supported for this transport type.
74
+
75
+ Returns
76
+ -------
77
+ receive : Callable
78
+ send : Callable
79
+ create_node : Optional[Callable]
80
+ delete_node : Optional[Callable]
81
+ get_run : Optional[Callable]
82
+ """
83
+ if authentication_keys is not None:
84
+ log(ERROR, "Client authentication is not supported for this transport type.")
85
+ with grpc_request_response(
86
+ server_address=server_address,
87
+ insecure=insecure,
88
+ retry_invoker=retry_invoker,
89
+ max_message_length=max_message_length,
90
+ root_certificates=root_certificates,
91
+ authentication_keys=None, # Authentication is not supported
92
+ adapter_cls=GrpcAdapter,
93
+ ) as conn:
94
+ yield conn
@@ -17,7 +17,7 @@
17
17
 
18
18
  import uuid
19
19
  from contextlib import contextmanager
20
- from logging import DEBUG
20
+ from logging import DEBUG, ERROR
21
21
  from pathlib import Path
22
22
  from queue import Queue
23
23
  from typing import Callable, Iterator, Optional, Tuple, Union, cast
@@ -101,6 +101,8 @@ def grpc_connection( # pylint: disable=R0913, R0915
101
101
  The PEM-encoded root certificates as a byte string or a path string.
102
102
  If provided, a secure connection using the certificates will be
103
103
  established to an SSL-enabled Flower server.
104
+ authentication_keys : Optional[Tuple[PrivateKey, PublicKey]] (default: None)
105
+ Client authentication is not supported for this transport type.
104
106
 
105
107
  Returns
106
108
  -------
@@ -123,6 +125,8 @@ def grpc_connection( # pylint: disable=R0913, R0915
123
125
  """
124
126
  if isinstance(root_certificates, str):
125
127
  root_certificates = Path(root_certificates).read_bytes()
128
+ if authentication_keys is not None:
129
+ log(ERROR, "Client authentication is not supported for this transport type.")
126
130
 
127
131
  channel = create_channel(
128
132
  server_address=server_address,
@@ -55,6 +55,7 @@ from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=
55
55
  from flwr.proto.task_pb2 import TaskIns # pylint: disable=E0611
56
56
 
57
57
  from .client_interceptor import AuthenticateClientInterceptor
58
+ from .grpc_adapter import GrpcAdapter
58
59
 
59
60
 
60
61
  def on_channel_state_change(channel_connectivity: str) -> None:
@@ -72,7 +73,7 @@ def grpc_request_response( # pylint: disable=R0913, R0914, R0915
72
73
  authentication_keys: Optional[
73
74
  Tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]
74
75
  ] = None,
75
- adapter_cls: Optional[Type[FleetStub]] = None,
76
+ adapter_cls: Optional[Union[Type[FleetStub], Type[GrpcAdapter]]] = None,
76
77
  ) -> Iterator[
77
78
  Tuple[
78
79
  Callable[[], Optional[Message]],
@@ -106,6 +107,11 @@ def grpc_request_response( # pylint: disable=R0913, R0914, R0915
106
107
  Path of the root certificate. If provided, a secure
107
108
  connection using the certificates will be established to an SSL-enabled
108
109
  Flower server. Bytes won't work for the REST API.
110
+ authentication_keys : Optional[Tuple[PrivateKey, PublicKey]] (default: None)
111
+ Tuple containing the elliptic curve private key and public key for
112
+ authentication from the cryptography library.
113
+ Source: https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec/
114
+ Used to establish an authenticated connection with the server.
109
115
 
110
116
  Returns
111
117
  -------
@@ -113,6 +119,7 @@ def grpc_request_response( # pylint: disable=R0913, R0914, R0915
113
119
  send : Callable
114
120
  create_node : Optional[Callable]
115
121
  delete_node : Optional[Callable]
122
+ get_run : Optional[Callable]
116
123
  """
117
124
  if isinstance(root_certificates, str):
118
125
  root_certificates = Path(root_certificates).read_bytes()
@@ -0,0 +1,133 @@
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
+ """GrpcAdapter implementation."""
16
+
17
+
18
+ import sys
19
+ from logging import DEBUG
20
+ from typing import Any, Type, TypeVar, cast
21
+
22
+ import grpc
23
+ from google.protobuf.message import Message as GrpcMessage
24
+
25
+ from flwr.common import log
26
+ from flwr.common.constant import (
27
+ GRPC_ADAPTER_METADATA_FLOWER_VERSION_KEY,
28
+ GRPC_ADAPTER_METADATA_SHOULD_EXIT_KEY,
29
+ )
30
+ from flwr.common.version import package_version
31
+ from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
32
+ CreateNodeRequest,
33
+ CreateNodeResponse,
34
+ DeleteNodeRequest,
35
+ DeleteNodeResponse,
36
+ PingRequest,
37
+ PingResponse,
38
+ PullTaskInsRequest,
39
+ PullTaskInsResponse,
40
+ PushTaskResRequest,
41
+ PushTaskResResponse,
42
+ )
43
+ from flwr.proto.grpcadapter_pb2 import MessageContainer # pylint: disable=E0611
44
+ from flwr.proto.grpcadapter_pb2_grpc import GrpcAdapterStub
45
+ from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611
46
+
47
+ T = TypeVar("T", bound=GrpcMessage)
48
+
49
+
50
+ class GrpcAdapter:
51
+ """Adapter class to send and receive gRPC messages via the ``GrpcAdapterStub``.
52
+
53
+ This class utilizes the ``GrpcAdapterStub`` to send and receive gRPC messages
54
+ which are defined and used by the Fleet API, as defined in ``fleet.proto``.
55
+ """
56
+
57
+ def __init__(self, channel: grpc.Channel) -> None:
58
+ self.stub = GrpcAdapterStub(channel)
59
+
60
+ def _send_and_receive(
61
+ self, request: GrpcMessage, response_type: Type[T], **kwargs: Any
62
+ ) -> T:
63
+ # Serialize request
64
+ container_req = MessageContainer(
65
+ metadata={GRPC_ADAPTER_METADATA_FLOWER_VERSION_KEY: package_version},
66
+ grpc_message_name=request.__class__.__qualname__,
67
+ grpc_message_content=request.SerializeToString(),
68
+ )
69
+
70
+ # Send via the stub
71
+ container_res = cast(
72
+ MessageContainer, self.stub.SendReceive(container_req, **kwargs)
73
+ )
74
+
75
+ # Handle control message
76
+ should_exit = (
77
+ container_res.metadata.get(GRPC_ADAPTER_METADATA_SHOULD_EXIT_KEY, "false")
78
+ == "true"
79
+ )
80
+ if should_exit:
81
+ log(
82
+ DEBUG,
83
+ 'Received shutdown signal: exit flag is set to ``"true"``. Exiting...',
84
+ )
85
+ sys.exit(0)
86
+
87
+ # Check the grpc_message_name of the response
88
+ if container_res.grpc_message_name != response_type.__qualname__:
89
+ raise ValueError(
90
+ f"Invalid grpc_message_name. Expected {response_type.__qualname__}"
91
+ f", but got {container_res.grpc_message_name}."
92
+ )
93
+
94
+ # Deserialize response
95
+ response = response_type()
96
+ response.ParseFromString(container_res.grpc_message_content)
97
+ return response
98
+
99
+ def CreateNode( # pylint: disable=C0103
100
+ self, request: CreateNodeRequest, **kwargs: Any
101
+ ) -> CreateNodeResponse:
102
+ """."""
103
+ return self._send_and_receive(request, CreateNodeResponse, **kwargs)
104
+
105
+ def DeleteNode( # pylint: disable=C0103
106
+ self, request: DeleteNodeRequest, **kwargs: Any
107
+ ) -> DeleteNodeResponse:
108
+ """."""
109
+ return self._send_and_receive(request, DeleteNodeResponse, **kwargs)
110
+
111
+ def Ping( # pylint: disable=C0103
112
+ self, request: PingRequest, **kwargs: Any
113
+ ) -> PingResponse:
114
+ """."""
115
+ return self._send_and_receive(request, PingResponse, **kwargs)
116
+
117
+ def PullTaskIns( # pylint: disable=C0103
118
+ self, request: PullTaskInsRequest, **kwargs: Any
119
+ ) -> PullTaskInsResponse:
120
+ """."""
121
+ return self._send_and_receive(request, PullTaskInsResponse, **kwargs)
122
+
123
+ def PushTaskRes( # pylint: disable=C0103
124
+ self, request: PushTaskResRequest, **kwargs: Any
125
+ ) -> PushTaskResResponse:
126
+ """."""
127
+ return self._send_and_receive(request, PushTaskResResponse, **kwargs)
128
+
129
+ def GetRun( # pylint: disable=C0103
130
+ self, request: GetRunRequest, **kwargs: Any
131
+ ) -> GetRunResponse:
132
+ """."""
133
+ return self._send_and_receive(request, GetRunResponse, **kwargs)
@@ -117,10 +117,16 @@ def http_request_response( # pylint: disable=,R0913, R0914, R0915
117
117
  Path of the root certificate. If provided, a secure
118
118
  connection using the certificates will be established to an SSL-enabled
119
119
  Flower server. Bytes won't work for the REST API.
120
+ authentication_keys : Optional[Tuple[PrivateKey, PublicKey]] (default: None)
121
+ Client authentication is not supported for this transport type.
120
122
 
121
123
  Returns
122
124
  -------
123
- receive, send : Callable, Callable
125
+ receive : Callable
126
+ send : Callable
127
+ create_node : Optional[Callable]
128
+ delete_node : Optional[Callable]
129
+ get_run : Optional[Callable]
124
130
  """
125
131
  log(
126
132
  WARN,
@@ -145,6 +151,8 @@ def http_request_response( # pylint: disable=,R0913, R0914, R0915
145
151
  "For the REST API, the root certificates "
146
152
  "must be provided as a string path to the client.",
147
153
  )
154
+ if authentication_keys is not None:
155
+ log(ERROR, "Client authentication is not supported for this transport type.")
148
156
 
149
157
  # Shared variables for inner functions
150
158
  metadata: Optional[Metadata] = None
@@ -20,7 +20,6 @@ from logging import DEBUG, INFO, WARN
20
20
  from pathlib import Path
21
21
  from typing import Callable, Optional, Tuple
22
22
 
23
- import tomli
24
23
  from cryptography.exceptions import UnsupportedAlgorithm
25
24
  from cryptography.hazmat.primitives.asymmetric import ec
26
25
  from cryptography.hazmat.primitives.serialization import (
@@ -28,10 +27,9 @@ from cryptography.hazmat.primitives.serialization import (
28
27
  load_ssh_public_key,
29
28
  )
30
29
 
31
- from flwr.cli.config_utils import validate_fields
32
30
  from flwr.client.client_app import ClientApp, LoadClientAppError
33
31
  from flwr.common import EventType, event
34
- from flwr.common.config import get_flwr_dir
32
+ from flwr.common.config import get_flwr_dir, get_project_config, get_project_dir
35
33
  from flwr.common.exit_handlers import register_exit_handlers
36
34
  from flwr.common.logger import log, warn_deprecated_feature
37
35
  from flwr.common.object_ref import load_app, validate
@@ -172,9 +170,9 @@ def _get_load_client_app_fn(
172
170
  if args.flwr_dir is None:
173
171
  flwr_dir = get_flwr_dir()
174
172
  else:
175
- flwr_dir = Path(args.flwr_dir)
173
+ flwr_dir = Path(args.flwr_dir).absolute()
176
174
 
177
- sys.path.insert(0, str(flwr_dir))
175
+ sys.path.insert(0, str(flwr_dir.absolute()))
178
176
 
179
177
  default_app_ref: str = getattr(args, "client-app")
180
178
 
@@ -191,8 +189,8 @@ def _get_load_client_app_fn(
191
189
  def _load(fab_id: str, fab_version: str) -> ClientApp:
192
190
  # If multi-app feature is disabled
193
191
  if not multi_app:
194
- # Set sys.path
195
- sys.path[0] = args.dir
192
+ # Get sys path to be inserted
193
+ sys_path = Path(args.dir).absolute()
196
194
 
197
195
  # Set app reference
198
196
  client_app_ref = default_app_ref
@@ -204,52 +202,28 @@ def _get_load_client_app_fn(
204
202
  ) from None
205
203
 
206
204
  log(WARN, "FAB ID is not provided; the default ClientApp will be loaded.")
207
- # Set sys.path
208
- sys.path[0] = args.dir
205
+ # Get sys path to be inserted
206
+ sys_path = Path(args.dir).absolute()
209
207
 
210
208
  # Set app reference
211
209
  client_app_ref = default_app_ref
212
210
  # If multi-app feature is enabled
213
211
  else:
214
- # Check the fab_id
215
- if fab_id.count("/") != 1:
216
- raise LoadClientAppError(
217
- f"Invalid FAB ID: {fab_id}",
218
- ) from None
219
- username, project_name = fab_id.split("/")
212
+ try:
213
+ project_dir = get_project_dir(fab_id, fab_version, flwr_dir)
214
+ config = get_project_config(project_dir)
215
+ except Exception as e:
216
+ raise LoadClientAppError("Failed to load ClientApp") from e
220
217
 
221
- # Locate the directory
222
- project_dir = flwr_dir / "apps" / username / project_name / fab_version
223
-
224
- # Check if the directory exists
225
- if not project_dir.exists():
226
- raise LoadClientAppError(
227
- f"Invalid Flower App directory: {project_dir}",
228
- ) from None
229
-
230
- # Load pyproject.toml file
231
- toml_path = project_dir / "pyproject.toml"
232
- if not toml_path.is_file():
233
- raise LoadClientAppError(
234
- f"Cannot find pyproject.toml in {project_dir}",
235
- ) from None
236
- with open(toml_path, encoding="utf-8") as toml_file:
237
- config = tomli.loads(toml_file.read())
238
-
239
- # Validate pyproject.toml fields
240
- is_valid, errors, _ = validate_fields(config)
241
- if not is_valid:
242
- error_msg = "\n".join([f" - {error}" for error in errors])
243
- raise LoadClientAppError(
244
- f"Invalid pyproject.toml:\n{error_msg}",
245
- ) from None
246
-
247
- # Set sys.path
248
- sys.path[0] = str(project_dir)
218
+ # Get sys path to be inserted
219
+ sys_path = Path(project_dir).absolute()
249
220
 
250
221
  # Set app reference
251
222
  client_app_ref = config["flower"]["components"]["clientapp"]
252
223
 
224
+ # Set sys.path
225
+ sys.path.insert(0, str(sys_path))
226
+
253
227
  # Load ClientApp
254
228
  log(
255
229
  DEBUG,
flwr/common/config.py CHANGED
@@ -16,13 +16,56 @@
16
16
 
17
17
  import os
18
18
  from pathlib import Path
19
+ from typing import Any, Dict, Optional, Union
20
+
21
+ import tomli
22
+
23
+ from flwr.cli.config_utils import validate_fields
24
+ from flwr.common.constant import APP_DIR, FAB_CONFIG_FILE, FLWR_HOME
19
25
 
20
26
 
21
27
  def get_flwr_dir() -> Path:
22
28
  """Return the Flower home directory based on env variables."""
23
29
  return Path(
24
30
  os.getenv(
25
- "FLWR_HOME",
31
+ FLWR_HOME,
26
32
  f"{os.getenv('XDG_DATA_HOME', os.getenv('HOME'))}/.flwr",
27
33
  )
28
34
  )
35
+
36
+
37
+ def get_project_dir(
38
+ fab_id: str, fab_version: str, flwr_dir: Optional[Union[str, Path]] = None
39
+ ) -> Path:
40
+ """Return the project directory based on the given fab_id and fab_version."""
41
+ # Check the fab_id
42
+ if fab_id.count("/") != 1:
43
+ raise ValueError(
44
+ f"Invalid FAB ID: {fab_id}",
45
+ )
46
+ publisher, project_name = fab_id.split("/")
47
+ if flwr_dir is None:
48
+ flwr_dir = get_flwr_dir()
49
+ return Path(flwr_dir) / APP_DIR / publisher / project_name / fab_version
50
+
51
+
52
+ def get_project_config(project_dir: Union[str, Path]) -> Dict[str, Any]:
53
+ """Return pyproject.toml in the given project directory."""
54
+ # Load pyproject.toml file
55
+ toml_path = Path(project_dir) / FAB_CONFIG_FILE
56
+ if not toml_path.is_file():
57
+ raise FileNotFoundError(
58
+ f"Cannot find {FAB_CONFIG_FILE} in {project_dir}",
59
+ )
60
+ with toml_path.open(encoding="utf-8") as toml_file:
61
+ config = tomli.loads(toml_file.read())
62
+
63
+ # Validate pyproject.toml fields
64
+ is_valid, errors, _ = validate_fields(config)
65
+ if not is_valid:
66
+ error_msg = "\n".join([f" - {error}" for error in errors])
67
+ raise ValueError(
68
+ f"Invalid {FAB_CONFIG_FILE}:\n{error_msg}",
69
+ )
70
+
71
+ return config
flwr/common/constant.py CHANGED
@@ -45,6 +45,15 @@ PING_BASE_MULTIPLIER = 0.8
45
45
  PING_RANDOM_RANGE = (-0.1, 0.1)
46
46
  PING_MAX_INTERVAL = 1e300
47
47
 
48
+ # Constants for FAB
49
+ APP_DIR = "apps"
50
+ FAB_CONFIG_FILE = "pyproject.toml"
51
+ FLWR_HOME = "FLWR_HOME"
52
+
53
+
54
+ GRPC_ADAPTER_METADATA_FLOWER_VERSION_KEY = "flower-version"
55
+ GRPC_ADAPTER_METADATA_SHOULD_EXIT_KEY = "should-exit"
56
+
48
57
 
49
58
  class MessageType:
50
59
  """Message type."""
flwr/common/typing.py CHANGED
@@ -185,3 +185,12 @@ class ClientMessage:
185
185
  get_parameters_res: Optional[GetParametersRes] = None
186
186
  fit_res: Optional[FitRes] = None
187
187
  evaluate_res: Optional[EvaluateRes] = None
188
+
189
+
190
+ @dataclass
191
+ class Run:
192
+ """Run details."""
193
+
194
+ run_id: int
195
+ fab_id: str
196
+ fab_version: str
flwr/proto/exec_pb2.py CHANGED
@@ -14,7 +14,7 @@ _sym_db = _symbol_database.Default()
14
14
 
15
15
 
16
16
 
17
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x66lwr/proto/exec.proto\x12\nflwr.proto\"#\n\x0fStartRunRequest\x12\x10\n\x08\x66\x61\x62_file\x18\x01 \x01(\x0c\"\"\n\x10StartRunResponse\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\x32O\n\x04\x45xec\x12G\n\x08StartRun\x12\x1b.flwr.proto.StartRunRequest\x1a\x1c.flwr.proto.StartRunResponse\"\x00\x62\x06proto3')
17
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x66lwr/proto/exec.proto\x12\nflwr.proto\"#\n\x0fStartRunRequest\x12\x10\n\x08\x66\x61\x62_file\x18\x01 \x01(\x0c\"\"\n\x10StartRunResponse\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\"#\n\x11StreamLogsRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\"(\n\x12StreamLogsResponse\x12\x12\n\nlog_output\x18\x01 \x01(\t2\xa0\x01\n\x04\x45xec\x12G\n\x08StartRun\x12\x1b.flwr.proto.StartRunRequest\x1a\x1c.flwr.proto.StartRunResponse\"\x00\x12O\n\nStreamLogs\x12\x1d.flwr.proto.StreamLogsRequest\x1a\x1e.flwr.proto.StreamLogsResponse\"\x00\x30\x01\x62\x06proto3')
18
18
 
19
19
  _globals = globals()
20
20
  _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -25,6 +25,10 @@ if _descriptor._USE_C_DESCRIPTORS == False:
25
25
  _globals['_STARTRUNREQUEST']._serialized_end=72
26
26
  _globals['_STARTRUNRESPONSE']._serialized_start=74
27
27
  _globals['_STARTRUNRESPONSE']._serialized_end=108
28
- _globals['_EXEC']._serialized_start=110
29
- _globals['_EXEC']._serialized_end=189
28
+ _globals['_STREAMLOGSREQUEST']._serialized_start=110
29
+ _globals['_STREAMLOGSREQUEST']._serialized_end=145
30
+ _globals['_STREAMLOGSRESPONSE']._serialized_start=147
31
+ _globals['_STREAMLOGSRESPONSE']._serialized_end=187
32
+ _globals['_EXEC']._serialized_start=190
33
+ _globals['_EXEC']._serialized_end=350
30
34
  # @@protoc_insertion_point(module_scope)
flwr/proto/exec_pb2.pyi CHANGED
@@ -5,6 +5,7 @@ isort:skip_file
5
5
  import builtins
6
6
  import google.protobuf.descriptor
7
7
  import google.protobuf.message
8
+ import typing
8
9
  import typing_extensions
9
10
 
10
11
  DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
@@ -30,3 +31,25 @@ class StartRunResponse(google.protobuf.message.Message):
30
31
  ) -> None: ...
31
32
  def ClearField(self, field_name: typing_extensions.Literal["run_id",b"run_id"]) -> None: ...
32
33
  global___StartRunResponse = StartRunResponse
34
+
35
+ class StreamLogsRequest(google.protobuf.message.Message):
36
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
37
+ RUN_ID_FIELD_NUMBER: builtins.int
38
+ run_id: builtins.int
39
+ def __init__(self,
40
+ *,
41
+ run_id: builtins.int = ...,
42
+ ) -> None: ...
43
+ def ClearField(self, field_name: typing_extensions.Literal["run_id",b"run_id"]) -> None: ...
44
+ global___StreamLogsRequest = StreamLogsRequest
45
+
46
+ class StreamLogsResponse(google.protobuf.message.Message):
47
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
48
+ LOG_OUTPUT_FIELD_NUMBER: builtins.int
49
+ log_output: typing.Text
50
+ def __init__(self,
51
+ *,
52
+ log_output: typing.Text = ...,
53
+ ) -> None: ...
54
+ def ClearField(self, field_name: typing_extensions.Literal["log_output",b"log_output"]) -> None: ...
55
+ global___StreamLogsResponse = StreamLogsResponse
@@ -19,6 +19,11 @@ class ExecStub(object):
19
19
  request_serializer=flwr_dot_proto_dot_exec__pb2.StartRunRequest.SerializeToString,
20
20
  response_deserializer=flwr_dot_proto_dot_exec__pb2.StartRunResponse.FromString,
21
21
  )
22
+ self.StreamLogs = channel.unary_stream(
23
+ '/flwr.proto.Exec/StreamLogs',
24
+ request_serializer=flwr_dot_proto_dot_exec__pb2.StreamLogsRequest.SerializeToString,
25
+ response_deserializer=flwr_dot_proto_dot_exec__pb2.StreamLogsResponse.FromString,
26
+ )
22
27
 
23
28
 
24
29
  class ExecServicer(object):
@@ -31,6 +36,13 @@ class ExecServicer(object):
31
36
  context.set_details('Method not implemented!')
32
37
  raise NotImplementedError('Method not implemented!')
33
38
 
39
+ def StreamLogs(self, request, context):
40
+ """Start log stream upon request
41
+ """
42
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
43
+ context.set_details('Method not implemented!')
44
+ raise NotImplementedError('Method not implemented!')
45
+
34
46
 
35
47
  def add_ExecServicer_to_server(servicer, server):
36
48
  rpc_method_handlers = {
@@ -39,6 +51,11 @@ def add_ExecServicer_to_server(servicer, server):
39
51
  request_deserializer=flwr_dot_proto_dot_exec__pb2.StartRunRequest.FromString,
40
52
  response_serializer=flwr_dot_proto_dot_exec__pb2.StartRunResponse.SerializeToString,
41
53
  ),
54
+ 'StreamLogs': grpc.unary_stream_rpc_method_handler(
55
+ servicer.StreamLogs,
56
+ request_deserializer=flwr_dot_proto_dot_exec__pb2.StreamLogsRequest.FromString,
57
+ response_serializer=flwr_dot_proto_dot_exec__pb2.StreamLogsResponse.SerializeToString,
58
+ ),
42
59
  }
43
60
  generic_handler = grpc.method_handlers_generic_handler(
44
61
  'flwr.proto.Exec', rpc_method_handlers)
@@ -65,3 +82,20 @@ class Exec(object):
65
82
  flwr_dot_proto_dot_exec__pb2.StartRunResponse.FromString,
66
83
  options, channel_credentials,
67
84
  insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
85
+
86
+ @staticmethod
87
+ def StreamLogs(request,
88
+ target,
89
+ options=(),
90
+ channel_credentials=None,
91
+ call_credentials=None,
92
+ insecure=False,
93
+ compression=None,
94
+ wait_for_ready=None,
95
+ timeout=None,
96
+ metadata=None):
97
+ return grpc.experimental.unary_stream(request, target, '/flwr.proto.Exec/StreamLogs',
98
+ flwr_dot_proto_dot_exec__pb2.StreamLogsRequest.SerializeToString,
99
+ flwr_dot_proto_dot_exec__pb2.StreamLogsResponse.FromString,
100
+ options, channel_credentials,
101
+ insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
@@ -5,6 +5,7 @@ isort:skip_file
5
5
  import abc
6
6
  import flwr.proto.exec_pb2
7
7
  import grpc
8
+ import typing
8
9
 
9
10
  class ExecStub:
10
11
  def __init__(self, channel: grpc.Channel) -> None: ...
@@ -13,6 +14,11 @@ class ExecStub:
13
14
  flwr.proto.exec_pb2.StartRunResponse]
14
15
  """Start run upon request"""
15
16
 
17
+ StreamLogs: grpc.UnaryStreamMultiCallable[
18
+ flwr.proto.exec_pb2.StreamLogsRequest,
19
+ flwr.proto.exec_pb2.StreamLogsResponse]
20
+ """Start log stream upon request"""
21
+
16
22
 
17
23
  class ExecServicer(metaclass=abc.ABCMeta):
18
24
  @abc.abstractmethod
@@ -23,5 +29,13 @@ class ExecServicer(metaclass=abc.ABCMeta):
23
29
  """Start run upon request"""
24
30
  pass
25
31
 
32
+ @abc.abstractmethod
33
+ def StreamLogs(self,
34
+ request: flwr.proto.exec_pb2.StreamLogsRequest,
35
+ context: grpc.ServicerContext,
36
+ ) -> typing.Iterator[flwr.proto.exec_pb2.StreamLogsResponse]:
37
+ """Start log stream upon request"""
38
+ pass
39
+
26
40
 
27
41
  def add_ExecServicer_to_server(servicer: ExecServicer, server: grpc.Server) -> None: ...
@@ -45,7 +45,7 @@ def run(
45
45
  )
46
46
 
47
47
  if server_app_dir is not None:
48
- sys.path.insert(0, server_app_dir)
48
+ sys.path.insert(0, str(Path(server_app_dir).absolute()))
49
49
 
50
50
  # Load ServerApp if needed
51
51
  def _load() -> ServerApp:
@@ -112,6 +112,6 @@ def get_run(
112
112
  request: GetRunRequest, state: State # pylint: disable=W0613
113
113
  ) -> GetRunResponse:
114
114
  """Get run information."""
115
- run_id, fab_id, fab_version = state.get_run(request.run_id)
116
- run = Run(run_id=run_id, fab_id=fab_id, fab_version=fab_version)
117
- return GetRunResponse(run=run)
115
+ run = state.get_run(request.run_id)
116
+ run_proto = None if run is None else Run(**vars(run))
117
+ return GetRunResponse(run=run_proto)
@@ -20,6 +20,7 @@ import sys
20
20
  import time
21
21
  import traceback
22
22
  from logging import DEBUG, ERROR, INFO, WARN
23
+ from pathlib import Path
23
24
  from typing import Callable, Dict, List, Optional
24
25
 
25
26
  from flwr.client.client_app import ClientApp, ClientAppException, LoadClientAppError
@@ -274,6 +275,7 @@ def start_vce(
274
275
  # Use mapping constructed externally. This also means nodes
275
276
  # have previously being registered.
276
277
  nodes_mapping = existing_nodes_mapping
278
+ app_dir = str(Path(app_dir).absolute())
277
279
 
278
280
  if not state_factory:
279
281
  log(INFO, "A StateFactory was not supplied to the SimulationEngine.")
@@ -23,6 +23,7 @@ from typing import Dict, List, Optional, Set, Tuple
23
23
  from uuid import UUID, uuid4
24
24
 
25
25
  from flwr.common import log, now
26
+ from flwr.common.typing import Run
26
27
  from flwr.proto.task_pb2 import TaskIns, TaskRes # pylint: disable=E0611
27
28
  from flwr.server.superlink.state.state import State
28
29
  from flwr.server.utils import validate_task_ins_or_res
@@ -40,7 +41,7 @@ class InMemoryState(State): # pylint: disable=R0902,R0904
40
41
  self.public_key_to_node_id: Dict[bytes, int] = {}
41
42
 
42
43
  # Map run_id to (fab_id, fab_version)
43
- self.run_ids: Dict[int, Tuple[str, str]] = {}
44
+ self.run_ids: Dict[int, Run] = {}
44
45
  self.task_ins_store: Dict[UUID, TaskIns] = {}
45
46
  self.task_res_store: Dict[UUID, TaskRes] = {}
46
47
 
@@ -281,7 +282,9 @@ class InMemoryState(State): # pylint: disable=R0902,R0904
281
282
  run_id: int = int.from_bytes(os.urandom(8), "little", signed=True)
282
283
 
283
284
  if run_id not in self.run_ids:
284
- self.run_ids[run_id] = (fab_id, fab_version)
285
+ self.run_ids[run_id] = Run(
286
+ run_id=run_id, fab_id=fab_id, fab_version=fab_version
287
+ )
285
288
  return run_id
286
289
  log(ERROR, "Unexpected run creation failure.")
287
290
  return 0
@@ -319,13 +322,13 @@ class InMemoryState(State): # pylint: disable=R0902,R0904
319
322
  """Retrieve all currently stored `client_public_keys` as a set."""
320
323
  return self.client_public_keys
321
324
 
322
- def get_run(self, run_id: int) -> Tuple[int, str, str]:
325
+ def get_run(self, run_id: int) -> Optional[Run]:
323
326
  """Retrieve information about the run with the specified `run_id`."""
324
327
  with self.lock:
325
328
  if run_id not in self.run_ids:
326
329
  log(ERROR, "`run_id` is invalid")
327
- return 0, "", ""
328
- return run_id, *self.run_ids[run_id]
330
+ return None
331
+ return self.run_ids[run_id]
329
332
 
330
333
  def acknowledge_ping(self, node_id: int, ping_interval: float) -> bool:
331
334
  """Acknowledge a ping received from a node, serving as a heartbeat."""
@@ -24,6 +24,7 @@ from typing import Any, Dict, List, Optional, Sequence, Set, Tuple, Union, cast
24
24
  from uuid import UUID, uuid4
25
25
 
26
26
  from flwr.common import log, now
27
+ from flwr.common.typing import Run
27
28
  from flwr.proto.node_pb2 import Node # pylint: disable=E0611
28
29
  from flwr.proto.recordset_pb2 import RecordSet # pylint: disable=E0611
29
30
  from flwr.proto.task_pb2 import Task, TaskIns, TaskRes # pylint: disable=E0611
@@ -680,15 +681,17 @@ class SqliteState(State): # pylint: disable=R0904
680
681
  result: Set[bytes] = {row["public_key"] for row in rows}
681
682
  return result
682
683
 
683
- def get_run(self, run_id: int) -> Tuple[int, str, str]:
684
+ def get_run(self, run_id: int) -> Optional[Run]:
684
685
  """Retrieve information about the run with the specified `run_id`."""
685
686
  query = "SELECT * FROM run WHERE run_id = ?;"
686
687
  try:
687
688
  row = self.query(query, (run_id,))[0]
688
- return run_id, row["fab_id"], row["fab_version"]
689
+ return Run(
690
+ run_id=run_id, fab_id=row["fab_id"], fab_version=row["fab_version"]
691
+ )
689
692
  except sqlite3.IntegrityError:
690
693
  log(ERROR, "`run_id` does not exist.")
691
- return 0, "", ""
694
+ return None
692
695
 
693
696
  def acknowledge_ping(self, node_id: int, ping_interval: float) -> bool:
694
697
  """Acknowledge a ping received from a node, serving as a heartbeat."""
@@ -16,9 +16,10 @@
16
16
 
17
17
 
18
18
  import abc
19
- from typing import List, Optional, Set, Tuple
19
+ from typing import List, Optional, Set
20
20
  from uuid import UUID
21
21
 
22
+ from flwr.common.typing import Run
22
23
  from flwr.proto.task_pb2 import TaskIns, TaskRes # pylint: disable=E0611
23
24
 
24
25
 
@@ -160,7 +161,7 @@ class State(abc.ABC): # pylint: disable=R0904
160
161
  """Create a new run for the specified `fab_id` and `fab_version`."""
161
162
 
162
163
  @abc.abstractmethod
163
- def get_run(self, run_id: int) -> Tuple[int, str, str]:
164
+ def get_run(self, run_id: int) -> Optional[Run]:
164
165
  """Retrieve information about the run with the specified `run_id`.
165
166
 
166
167
  Parameters
@@ -170,8 +171,8 @@ class State(abc.ABC): # pylint: disable=R0904
170
171
 
171
172
  Returns
172
173
  -------
173
- Tuple[int, str, str]
174
- A tuple containing three elements:
174
+ Optional[Run]
175
+ A dataclass instance containing three elements if `run_id` is valid:
175
176
  - `run_id`: The identifier of the run, same as the specified `run_id`.
176
177
  - `fab_id`: The identifier of the FAB used in the specified run.
177
178
  - `fab_version`: The version of the FAB used in the specified run.
@@ -16,7 +16,7 @@
16
16
 
17
17
 
18
18
  from logging import ERROR, INFO
19
- from typing import Dict
19
+ from typing import Any, Dict, Generator
20
20
 
21
21
  import grpc
22
22
 
@@ -25,6 +25,8 @@ from flwr.proto import exec_pb2_grpc # pylint: disable=E0611
25
25
  from flwr.proto.exec_pb2 import ( # pylint: disable=E0611
26
26
  StartRunRequest,
27
27
  StartRunResponse,
28
+ StreamLogsRequest,
29
+ StreamLogsResponse,
28
30
  )
29
31
 
30
32
  from .executor import Executor, RunTracker
@@ -52,3 +54,12 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
52
54
  self.runs[run.run_id] = run
53
55
 
54
56
  return StartRunResponse(run_id=run.run_id)
57
+
58
+ def StreamLogs(
59
+ self, request: StreamLogsRequest, context: grpc.ServicerContext
60
+ ) -> Generator[StreamLogsResponse, Any, None]:
61
+ """Get logs."""
62
+ logs = ["a", "b", "c"]
63
+ while context.is_active():
64
+ for i in range(len(logs)): # pylint: disable=C0200
65
+ yield StreamLogsResponse(log_output=logs[i])
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: flwr-nightly
3
- Version: 1.10.0.dev20240616
3
+ Version: 1.10.0.dev20240618
4
4
  Summary: Flower: A Friendly Federated Learning Framework
5
5
  Home-page: https://flower.ai
6
6
  License: Apache-2.0
@@ -47,11 +47,14 @@ flwr/client/app.py,sha256=GhL-eR_Y2H8e2XhUnq5AUGQWQya51b5iVr4I6RDW7Hc,24189
47
47
  flwr/client/client.py,sha256=Vp9UkOkoHdNfn6iMYZsj_5m_GICiFfUlKEVaLad-YhM,8183
48
48
  flwr/client/client_app.py,sha256=2jyVTzu8pwDtg66z4FjAa_kPzg31Q8-hx-RkDhguIqw,8635
49
49
  flwr/client/dpfedavg_numpy_client.py,sha256=9Tnig4iml2J88HBKNahegjXjbfvIQyBtaIQaqjbeqsA,7435
50
+ flwr/client/grpc_adapter_client/__init__.py,sha256=QyNWIbsq9DpyMk7oemiO1P3TBFfkfkctnJ1JoAkTl3s,742
51
+ flwr/client/grpc_adapter_client/connection.py,sha256=kUObTsJ_4OwxE4tvIlWwn_iSWk2snjLWMo19tqdXo1s,3841
50
52
  flwr/client/grpc_client/__init__.py,sha256=LsnbqXiJhgQcB0XzAlUQgPx011Uf7Y7yabIC1HxivJ8,735
51
- flwr/client/grpc_client/connection.py,sha256=KWbBwuvn1-2wjrAKteydGCZC_7A2zmEjk3DycQWafrA,8993
53
+ flwr/client/grpc_client/connection.py,sha256=NGPFSq98wBXpVXZlei6jlg679nL07GDwyQ4z37AzoGc,9279
52
54
  flwr/client/grpc_rere_client/__init__.py,sha256=avn6W_vHEM_yZEB1S7hCZgnTbXb6ZujqRP_vAzyXu-0,752
53
55
  flwr/client/grpc_rere_client/client_interceptor.py,sha256=sYPEznuQPdy2BPDlvM9FK0ZRRucb4NfwUee1Z_mN82E,4954
54
- flwr/client/grpc_rere_client/connection.py,sha256=rYRPUFphUUzNxGj2jVnflcspDiFSzsi-G0xmp5gVQpY,9696
56
+ flwr/client/grpc_rere_client/connection.py,sha256=nEbmqvbPsSyESrwVBRipDFqsOmg4wQM6D0mf9-flMyo,10157
57
+ flwr/client/grpc_rere_client/grpc_adapter.py,sha256=woljH8yr1pyLH4W4Azogyy7Nafn6y9DHBnDCIIVKwCw,4711
55
58
  flwr/client/heartbeat.py,sha256=cx37mJBH8LyoIN4Lks85wtqT1mnU5GulQnr4pGCvAq0,2404
56
59
  flwr/client/message_handler/__init__.py,sha256=abHvBRJJiiaAMNgeILQbMOa6h8WqMK2BcnvxwQZFpic,719
57
60
  flwr/client/message_handler/message_handler.py,sha256=ml_FlduAJ5pxO31n1tKRrWfQRSxkMgKLbwXXcRsNSos,6553
@@ -68,14 +71,14 @@ flwr/client/node_state.py,sha256=KTTs_l4I0jBM7IsSsbAGjhfL_yZC3QANbzyvyfZBRDM,177
68
71
  flwr/client/node_state_tests.py,sha256=gPwz0zf2iuDSa11jedkur_u3Xm7lokIDG5ALD2MCvSw,2195
69
72
  flwr/client/numpy_client.py,sha256=u76GWAdHmJM88Agm2EgLQSvO8Jnk225mJTk-_TmPjFE,10283
70
73
  flwr/client/rest_client/__init__.py,sha256=ThwOnkMdzxo_UuyTI47Q7y9oSpuTgNT2OuFvJCfuDiw,735
71
- flwr/client/rest_client/connection.py,sha256=GvDPX2BdPwhBQGH6LQE50AzUxQvC7bisWK1pk_OR7eE,11567
74
+ flwr/client/rest_client/connection.py,sha256=GmnHoeWuSMv0OWV5GUZweIft8BTextGEY9oxyc7pyYk,11957
72
75
  flwr/client/supernode/__init__.py,sha256=SUhWOzcgXRNXk1V9UgB5-FaWukqqrOEajVUHEcPkwyQ,865
73
- flwr/client/supernode/app.py,sha256=KPVVUHQBgMtde-TVoJtXQ2qDqlE3shY1OCiDFbVYTTg,14704
76
+ flwr/client/supernode/app.py,sha256=9mwrPfgzNOsrpw2ToXmBRabrXHAn8yINsXBHw457IJs,13789
74
77
  flwr/client/typing.py,sha256=c9EvjlEjasxn1Wqx6bGl6Xg6vM1gMFfmXht-E2i5J-k,1006
75
78
  flwr/common/__init__.py,sha256=dHOptgKxna78CEQLD5Yu0QIsoSgpIIw5AhIUZCHDWAU,3721
76
79
  flwr/common/address.py,sha256=iTAN9jtmIGMrWFnx9XZQl45ZEtQJVZZLYPRBSNVARGI,1882
77
- flwr/common/config.py,sha256=WEPvVsGPrJKVppNlEGK23p33vKbOuMqSvi8DVtyW_dU,1022
78
- flwr/common/constant.py,sha256=k3QmlqQL4rfgjsO4xqTJ1QiGBHyJ5rTRE01FDEiNFCQ,2468
80
+ flwr/common/config.py,sha256=iXcpC7Eg1YK6C5apOIoWCI-mV92djyvVfcrMraOfDLM,2461
81
+ flwr/common/constant.py,sha256=QSfjp6kfDGtJxh1TspF4d88aYDCLtA_Y9TE0jC7F2no,2681
79
82
  flwr/common/context.py,sha256=ounF-mWPPtXGwtae3sg5EhF58ScviOa3MVqxRpGVu-8,1313
80
83
  flwr/common/date.py,sha256=UWhBZj49yX9LD4BmatS_ZFZu_-kweGh0KQJ1djyWWH4,891
81
84
  flwr/common/differential_privacy.py,sha256=WZWrL7C9XaB9l9NDkLDI5PvM7jwcoTTFu08ZVG8-M5Q,6113
@@ -107,7 +110,7 @@ flwr/common/secure_aggregation/secaggplus_constants.py,sha256=Fh7-n6pgL4TUnHpNYX
107
110
  flwr/common/secure_aggregation/secaggplus_utils.py,sha256=87bNZX6CmQekj935R4u3m5hsaEkkfKtGSA-VG2c-O9w,3221
108
111
  flwr/common/serde.py,sha256=Yn83kbSf9vJndTa5ldL4DR_bL_wy_bD4lTlD3ZbB658,22250
109
112
  flwr/common/telemetry.py,sha256=IGzOp87BYReCj5bEoZS6zDSKH0aXsmhMvhHx8fdC1v0,7948
110
- flwr/common/typing.py,sha256=3Wu6Ol1Ja6Gb0WdlcXVEn1EHYJbc4oRRJA81vEegxBo,4382
113
+ flwr/common/typing.py,sha256=q1RtnEtI2jX9VGT12_ECM3euMIcjmwh1Pd1tVrYf6i8,4483
111
114
  flwr/common/version.py,sha256=_RDSMGZPEuGKYViZuXPotDtXMvh4iyDH9XOCO4qtPO8,666
112
115
  flwr/proto/__init__.py,sha256=hbY7JYakwZwCkYgCNlmHdc8rtvfoJbAZLalMdc--CGc,683
113
116
  flwr/proto/driver_pb2.py,sha256=xqEN0UxFAUl1h_S_tP8SQShNYSJt_oSdb1npMn5KfL0,3394
@@ -118,10 +121,10 @@ flwr/proto/error_pb2.py,sha256=LarjKL90LbwkXKlhzNrDssgl4DXcvIPve8NVCXHpsKA,1084
118
121
  flwr/proto/error_pb2.pyi,sha256=ZNH4HhJTU_KfMXlyCeg8FwU-fcUYxTqEmoJPtWtHikc,734
119
122
  flwr/proto/error_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
120
123
  flwr/proto/error_pb2_grpc.pyi,sha256=ff2TSiLVnG6IVQcTGzb2DIH3XRSoAvAo_RMcvbMFyc0,76
121
- flwr/proto/exec_pb2.py,sha256=fwuyyK6WEwx-RngMZm6z5dqDeYcMKKxBBhwh9MJfvMA,1450
122
- flwr/proto/exec_pb2.pyi,sha256=IMUD1GlhK0mizORQ-lkiU-JbpAefmo9GLpDNfAib2GM,1068
123
- flwr/proto/exec_pb2_grpc.py,sha256=7yyi_J1Sri8LXzj5_MjhI7_hoxUMcjIAQcaE-Ghnvco,2480
124
- flwr/proto/exec_pb2_grpc.pyi,sha256=w721aoQ_ggktlIbR973bFRhwbIlEhfNMjOH4hnwgQE0,743
124
+ flwr/proto/exec_pb2.py,sha256=eA1VsoMtSAtBI8uWhEiM_xO_5bY9H-TZkrwFbDgzM-8,1909
125
+ flwr/proto/exec_pb2.pyi,sha256=eQ262F1-bvE60BthMh4rQOWKCRw_E4J4FEnHXtabIlc,1929
126
+ flwr/proto/exec_pb2_grpc.py,sha256=faAN19XEMP8GTKrcIU6jvlWkN44n2KiUsZh_OG0sYcg,4072
127
+ flwr/proto/exec_pb2_grpc.pyi,sha256=VrFhT1Um3Nb8UC2YqnR9GIiM-Yyx0FqaxVOWljh-G_w,1208
125
128
  flwr/proto/fab_pb2.py,sha256=k1L3z4L3pJGRIUmgt609xUe-UAtSKFwT0C7wXnb12IY,1427
126
129
  flwr/proto/fab_pb2.pyi,sha256=G2eHjgIAeVAf4TchXg3XPdeUk-h5-OMJnAv7CLyxdGs,1930
127
130
  flwr/proto/fab_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
@@ -170,7 +173,7 @@ flwr/server/driver/driver.py,sha256=t9SSSDlo9wT_y2Nl7waGYMTm2VlkvK3_bOb7ggPPlho,
170
173
  flwr/server/driver/grpc_driver.py,sha256=rdjkcAmtRWKeqJw4xDFqULuwVf0G2nLhfbOTrNUvPeY,11832
171
174
  flwr/server/driver/inmemory_driver.py,sha256=XfdLV3mVorTWBfthBkErJDLm8jXZ834IHF3139lTS5o,6490
172
175
  flwr/server/history.py,sha256=bBOHKyX1eQONIsUx4EUU-UnAk1i0EbEl8ioyMq_UWQ8,5063
173
- flwr/server/run_serverapp.py,sha256=nKrA9Vhs3OKeY_T4-kFL-41Qhsp5dszW98yfycOCD6c,6887
176
+ flwr/server/run_serverapp.py,sha256=svfVK-4tS2j9qYoXsLQAziJht_XiClQiSsM7OUPA6II,6909
174
177
  flwr/server/server.py,sha256=wsXsxMZ9SQ0B42nBnUlcV83NJPycgrgg5bFwcQ4BYBE,17821
175
178
  flwr/server/server_app.py,sha256=Re5Y9ftXlBRJXYHY_8TrNWsjyOUCPC5F_93H0xiZDhI,4400
176
179
  flwr/server/server_config.py,sha256=CZaHVAsMvGLjpWVcLPkiYxgJN4xfIyAiUrCI3fETKY4,1349
@@ -212,18 +215,18 @@ flwr/server/superlink/fleet/grpc_rere/__init__.py,sha256=bEJOMWbSlqkw-y5ZHtEXczh
212
215
  flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py,sha256=wuFaEAsuKfAYQKeXbB9Jg0SQaW9gmb_1k6pOPIlXrYY,3434
213
216
  flwr/server/superlink/fleet/grpc_rere/server_interceptor.py,sha256=v54f7yEX0Xwqh9KRDPzKgJdSBsosus4RzUyzTl-_u5Q,7738
214
217
  flwr/server/superlink/fleet/message_handler/__init__.py,sha256=hEY0l61ojH8Iz30_K1btm1HJ6J49iZJSFUsVYqUTw3A,731
215
- flwr/server/superlink/fleet/message_handler/message_handler.py,sha256=RAfQTcT0xkew0UO-CIB1ssN42MutdKwwfTvU8s-7boQ,3682
218
+ flwr/server/superlink/fleet/message_handler/message_handler.py,sha256=qDlWhCsbXssHTvoMZPjYTGgmOVflszkFmL1oJzVQrDA,3653
216
219
  flwr/server/superlink/fleet/rest_rere/__init__.py,sha256=VKDvDq5H8koOUztpmQacVzGJXPLEEkL1Vmolxt3mvnY,735
217
220
  flwr/server/superlink/fleet/rest_rere/rest_api.py,sha256=Sm_pSxpeT1BdkSKou8YwreP7qSpMR5ENEsjwGW1h5VY,7672
218
221
  flwr/server/superlink/fleet/vce/__init__.py,sha256=36MHKiefnJeyjwMQzVUK4m06Ojon3WDcwZGQsAcyVhQ,783
219
222
  flwr/server/superlink/fleet/vce/backend/__init__.py,sha256=oBIzmnrSSRvH_H0vRGEGWhWzQQwqe3zn6e13RsNwlIY,1466
220
223
  flwr/server/superlink/fleet/vce/backend/backend.py,sha256=LJsKl7oixVvptcG98Rd9ejJycNWcEVB0ODvSreLGp-A,2260
221
224
  flwr/server/superlink/fleet/vce/backend/raybackend.py,sha256=KCzV-n-czXxIKPwNfuD-JEVCl4-xAJaHe4taGmw9cTQ,6722
222
- flwr/server/superlink/fleet/vce/vce_api.py,sha256=aH-1h1EhTPCxdiqgH0_t8oDPiXX8VNNLV_BiDvu6kRk,12456
225
+ flwr/server/superlink/fleet/vce/vce_api.py,sha256=DQtEZ6VsL5fK1gpki0FxaJ33MUGPPXC6OEZ6oKCnZs0,12525
223
226
  flwr/server/superlink/state/__init__.py,sha256=ij-7Ms-hyordQdRmGQxY1-nVa4OhixJ0jr7_YDkys0s,1003
224
- flwr/server/superlink/state/in_memory_state.py,sha256=WoIOwgayuCu1DLRkkV6KgBsc28SKzSDxtXwO2a9Phuw,12750
225
- flwr/server/superlink/state/sqlite_state.py,sha256=8xvJgufEbl_ZRAz9VWXykKP3viUZjQNVS7yDY5dildw,28528
226
- flwr/server/superlink/state/state.py,sha256=LD3kqwuJdoNHu5O3c9B5V18sFCDX4Y3fHSO7ytlizUg,7989
227
+ flwr/server/superlink/state/in_memory_state.py,sha256=pb_asr8sh9IHPA97yYDTq1RZi8qYpACTzwSg56LMnsM,12827
228
+ flwr/server/superlink/state/sqlite_state.py,sha256=1PBYQGEIaj_67ojFw51mujfaxOFSeA8kPn_irwAX7eE,28612
229
+ flwr/server/superlink/state/state.py,sha256=_2EwLpOxSMEs0IsemuZNTBidTK3hoUt9vYcf7TRyg40,8037
227
230
  flwr/server/superlink/state/state_factory.py,sha256=91cSB-KOAFM37z7T098WxTkVeKNaAZ_mTI75snn2_tk,1654
228
231
  flwr/server/superlink/state/utils.py,sha256=qhIjBu5_rqm9GLMB6QS5TIRrMDVs85lmY17BqZ1ccLk,2207
229
232
  flwr/server/typing.py,sha256=2zSG-KuDAgwFPuzgVjTLDaEqJ8gXXGqFR2RD-qIk730,913
@@ -246,10 +249,10 @@ flwr/simulation/run_simulation.py,sha256=nTw8VG_agsxVyKmlOw-0umVuXJflcdAGqkKOL-Z
246
249
  flwr/superexec/__init__.py,sha256=9h94ogLxi6eJ3bUuJYq3E3pApThSabTPiSmPAGlTkHE,800
247
250
  flwr/superexec/app.py,sha256=8qW72a4LZRGMfNxsY6PPv-ClsKrk_ai9l0GrLI4vvTw,6078
248
251
  flwr/superexec/exec_grpc.py,sha256=u-rztpOleqSGqgvNE-ZLw1HchNsBHU1-eB3m52GZ0pQ,1852
249
- flwr/superexec/exec_servicer.py,sha256=KosYdHNwShkGdmJH1bxnZpJbMt5rz5N4I4c-ouimF6Y,1698
252
+ flwr/superexec/exec_servicer.py,sha256=qf8CT4RLXnY8omOy75kwfsWmMnfTD42B4ENTh5S-BCY,2120
250
253
  flwr/superexec/executor.py,sha256=GouXCY2LiZ-ffsOoZ_z-fh4JwbzMmhTl-gwpWFgGWTY,1688
251
- flwr_nightly-1.10.0.dev20240616.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
252
- flwr_nightly-1.10.0.dev20240616.dist-info/METADATA,sha256=po_948pH8WZNl0Si9fUYyWNCYSvYuE7XdxM7nkYrzk4,15518
253
- flwr_nightly-1.10.0.dev20240616.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
254
- flwr_nightly-1.10.0.dev20240616.dist-info/entry_points.txt,sha256=7qBQcA-bDGDxnJmLd9FYqglFQubjCNqyg9M8a-lukps,336
255
- flwr_nightly-1.10.0.dev20240616.dist-info/RECORD,,
254
+ flwr_nightly-1.10.0.dev20240618.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
255
+ flwr_nightly-1.10.0.dev20240618.dist-info/METADATA,sha256=UN4lU0Xz1YCoosjbt1YPwzsrqTI8qodVoRuHtWGnvm4,15518
256
+ flwr_nightly-1.10.0.dev20240618.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
257
+ flwr_nightly-1.10.0.dev20240618.dist-info/entry_points.txt,sha256=7qBQcA-bDGDxnJmLd9FYqglFQubjCNqyg9M8a-lukps,336
258
+ flwr_nightly-1.10.0.dev20240618.dist-info/RECORD,,