flwr-nightly 1.11.0.dev20240813__py3-none-any.whl → 1.11.0.dev20240815__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.

Files changed (31) hide show
  1. flwr/cli/run/run.py +6 -2
  2. flwr/client/app.py +1 -1
  3. flwr/client/grpc_rere_client/connection.py +1 -0
  4. flwr/client/process/__init__.py +15 -0
  5. flwr/client/process/clientappio_servicer.py +145 -0
  6. flwr/client/rest_client/connection.py +2 -1
  7. flwr/common/config.py +7 -2
  8. flwr/common/record/recordset.py +9 -7
  9. flwr/common/record/typeddict.py +20 -58
  10. flwr/common/recordset_compat.py +6 -6
  11. flwr/common/serde.py +24 -2
  12. flwr/common/typing.py +1 -0
  13. flwr/proto/exec_pb2.py +16 -15
  14. flwr/proto/exec_pb2.pyi +7 -4
  15. flwr/proto/message_pb2.py +2 -2
  16. flwr/proto/message_pb2.pyi +4 -1
  17. flwr/server/app.py +12 -0
  18. flwr/server/driver/grpc_driver.py +1 -0
  19. flwr/server/superlink/driver/driver_grpc.py +3 -0
  20. flwr/server/superlink/driver/driver_servicer.py +14 -1
  21. flwr/server/superlink/ffs/ffs_factory.py +47 -0
  22. flwr/server/superlink/state/in_memory_state.py +7 -5
  23. flwr/server/superlink/state/sqlite_state.py +17 -7
  24. flwr/server/superlink/state/state.py +4 -3
  25. flwr/simulation/run_simulation.py +4 -1
  26. flwr/superexec/exec_servicer.py +1 -1
  27. {flwr_nightly-1.11.0.dev20240813.dist-info → flwr_nightly-1.11.0.dev20240815.dist-info}/METADATA +1 -1
  28. {flwr_nightly-1.11.0.dev20240813.dist-info → flwr_nightly-1.11.0.dev20240815.dist-info}/RECORD +31 -28
  29. {flwr_nightly-1.11.0.dev20240813.dist-info → flwr_nightly-1.11.0.dev20240815.dist-info}/LICENSE +0 -0
  30. {flwr_nightly-1.11.0.dev20240813.dist-info → flwr_nightly-1.11.0.dev20240815.dist-info}/WHEEL +0 -0
  31. {flwr_nightly-1.11.0.dev20240813.dist-info → flwr_nightly-1.11.0.dev20240815.dist-info}/entry_points.txt +0 -0
flwr/cli/run/run.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower command line interface `run` command."""
16
16
 
17
+ import hashlib
17
18
  import subprocess
18
19
  import sys
19
20
  from logging import DEBUG
@@ -28,7 +29,8 @@ from flwr.cli.config_utils import load_and_validate
28
29
  from flwr.common.config import flatten_dict, parse_config_args
29
30
  from flwr.common.grpc import GRPC_MAX_MESSAGE_LENGTH, create_channel
30
31
  from flwr.common.logger import log
31
- from flwr.common.serde import user_config_to_proto
32
+ from flwr.common.serde import fab_to_proto, user_config_to_proto
33
+ from flwr.common.typing import Fab
32
34
  from flwr.proto.exec_pb2 import StartRunRequest # pylint: disable=E0611
33
35
  from flwr.proto.exec_pb2_grpc import ExecStub
34
36
 
@@ -163,9 +165,11 @@ def _run_with_superexec(
163
165
  stub = ExecStub(channel)
164
166
 
165
167
  fab_path = Path(build(app))
168
+ content = fab_path.read_bytes()
169
+ fab = Fab(hashlib.sha256(content).hexdigest(), content)
166
170
 
167
171
  req = StartRunRequest(
168
- fab_file=fab_path.read_bytes(),
172
+ fab=fab_to_proto(fab),
169
173
  override_config=user_config_to_proto(
170
174
  parse_config_args(config_overrides, separator=",")
171
175
  ),
flwr/client/app.py CHANGED
@@ -398,7 +398,7 @@ def _start_client_internal(
398
398
  runs[run_id] = get_run(run_id)
399
399
  # If get_run is None, i.e., in grpc-bidi mode
400
400
  else:
401
- runs[run_id] = Run(run_id, "", "", {})
401
+ runs[run_id] = Run(run_id, "", "", "", {})
402
402
 
403
403
  # Register context for this run
404
404
  node_state.register_context(
@@ -286,6 +286,7 @@ def grpc_request_response( # pylint: disable=R0913, R0914, R0915
286
286
  run_id,
287
287
  get_run_response.run.fab_id,
288
288
  get_run_response.run.fab_version,
289
+ get_run_response.run.fab_hash,
289
290
  user_config_from_proto(get_run_response.run.override_config),
290
291
  )
291
292
 
@@ -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
+ """Flower AppIO service."""
@@ -0,0 +1,145 @@
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
+ """ClientAppIo API servicer."""
16
+
17
+
18
+ from dataclasses import dataclass
19
+ from logging import DEBUG, ERROR
20
+ from typing import Optional
21
+
22
+ import grpc
23
+
24
+ from flwr.common import Context, Message, typing
25
+ from flwr.common.logger import log
26
+ from flwr.common.serde import (
27
+ clientappstatus_to_proto,
28
+ context_from_proto,
29
+ context_to_proto,
30
+ message_from_proto,
31
+ message_to_proto,
32
+ run_to_proto,
33
+ )
34
+ from flwr.common.typing import Run
35
+
36
+ # pylint: disable=E0611
37
+ from flwr.proto import clientappio_pb2_grpc
38
+ from flwr.proto.clientappio_pb2 import ( # pylint: disable=E0401
39
+ PullClientAppInputsRequest,
40
+ PullClientAppInputsResponse,
41
+ PushClientAppOutputsRequest,
42
+ PushClientAppOutputsResponse,
43
+ )
44
+
45
+
46
+ @dataclass
47
+ class ClientAppIoInputs:
48
+ """Specify the inputs to the ClientApp."""
49
+
50
+ message: Message
51
+ context: Context
52
+ run: Run
53
+ token: int
54
+
55
+
56
+ @dataclass
57
+ class ClientAppIoOutputs:
58
+ """Specify the outputs from the ClientApp."""
59
+
60
+ message: Message
61
+ context: Context
62
+
63
+
64
+ # pylint: disable=C0103,W0613,W0201
65
+ class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
66
+ """ClientAppIo API servicer."""
67
+
68
+ def __init__(self) -> None:
69
+ self.clientapp_input: Optional[ClientAppIoInputs] = None
70
+ self.clientapp_output: Optional[ClientAppIoOutputs] = None
71
+
72
+ def PullClientAppInputs(
73
+ self, request: PullClientAppInputsRequest, context: grpc.ServicerContext
74
+ ) -> PullClientAppInputsResponse:
75
+ """Pull Message, Context, and Run."""
76
+ log(DEBUG, "ClientAppIo.PullClientAppInputs")
77
+ if self.clientapp_input is None:
78
+ raise ValueError(
79
+ "ClientAppIoInputs not set before calling `PullClientAppInputs`."
80
+ )
81
+ if request.token != self.clientapp_input.token:
82
+ context.abort(
83
+ grpc.StatusCode.INVALID_ARGUMENT,
84
+ "Mismatch between ClientApp and SuperNode token",
85
+ )
86
+ return PullClientAppInputsResponse(
87
+ message=message_to_proto(self.clientapp_input.message),
88
+ context=context_to_proto(self.clientapp_input.context),
89
+ run=run_to_proto(self.clientapp_input.run),
90
+ )
91
+
92
+ def PushClientAppOutputs(
93
+ self, request: PushClientAppOutputsRequest, context: grpc.ServicerContext
94
+ ) -> PushClientAppOutputsResponse:
95
+ """Push Message and Context."""
96
+ log(DEBUG, "ClientAppIo.PushClientAppOutputs")
97
+ if self.clientapp_output is None:
98
+ raise ValueError(
99
+ "ClientAppIoOutputs not set before calling `PushClientAppOutputs`."
100
+ )
101
+ if self.clientapp_input is None:
102
+ raise ValueError(
103
+ "ClientAppIoInputs not set before calling `PushClientAppOutputs`."
104
+ )
105
+ if request.token != self.clientapp_input.token:
106
+ context.abort(
107
+ grpc.StatusCode.INVALID_ARGUMENT,
108
+ "Mismatch between ClientApp and SuperNode token",
109
+ )
110
+ try:
111
+ # Update Message and Context
112
+ self.clientapp_output.message = message_from_proto(request.message)
113
+ self.clientapp_output.context = context_from_proto(request.context)
114
+ # Set status
115
+ code = typing.ClientAppOutputCode.SUCCESS
116
+ status = typing.ClientAppOutputStatus(code=code, message="Success")
117
+ proto_status = clientappstatus_to_proto(status=status)
118
+ return PushClientAppOutputsResponse(status=proto_status)
119
+ except Exception as e: # pylint: disable=broad-exception-caught
120
+ log(ERROR, "ClientApp failed to push message to SuperNode, %s", e)
121
+ code = typing.ClientAppOutputCode.UNKNOWN_ERROR
122
+ status = typing.ClientAppOutputStatus(code=code, message="Push failed")
123
+ proto_status = clientappstatus_to_proto(status=status)
124
+ return PushClientAppOutputsResponse(status=proto_status)
125
+
126
+ def set_inputs(self, clientapp_input: ClientAppIoInputs) -> None:
127
+ """Set ClientApp inputs."""
128
+ log(DEBUG, "ClientAppIo.SetInputs")
129
+ if self.clientapp_input is not None or self.clientapp_output is not None:
130
+ raise ValueError(
131
+ "ClientAppIoInputs and ClientAppIoOutputs must not be set before "
132
+ "calling `set_inputs`."
133
+ )
134
+ self.clientapp_input = clientapp_input
135
+
136
+ def get_outputs(self) -> ClientAppIoOutputs:
137
+ """Get ClientApp outputs."""
138
+ log(DEBUG, "ClientAppIo.GetOutputs")
139
+ if self.clientapp_output is None:
140
+ raise ValueError("ClientAppIoOutputs not set before calling `get_outputs`.")
141
+ # Set outputs to a local variable and clear self.clientapp_output
142
+ output: ClientAppIoOutputs = self.clientapp_output
143
+ self.clientapp_input = None
144
+ self.clientapp_output = None
145
+ return output
@@ -358,12 +358,13 @@ def http_request_response( # pylint: disable=,R0913, R0914, R0915
358
358
  # Send the request
359
359
  res = _request(req, GetRunResponse, PATH_GET_RUN)
360
360
  if res is None:
361
- return Run(run_id, "", "", {})
361
+ return Run(run_id, "", "", "", {})
362
362
 
363
363
  return Run(
364
364
  run_id,
365
365
  res.run.fab_id,
366
366
  res.run.fab_version,
367
+ res.run.fab_hash,
367
368
  user_config_from_proto(res.run.override_config),
368
369
  )
369
370
 
flwr/common/config.py CHANGED
@@ -74,10 +74,15 @@ def get_project_config(project_dir: Union[str, Path]) -> Dict[str, Any]:
74
74
  return config
75
75
 
76
76
 
77
- def _fuse_dicts(
77
+ def fuse_dicts(
78
78
  main_dict: UserConfig,
79
79
  override_dict: UserConfig,
80
80
  ) -> UserConfig:
81
+ """Merge a config with the overrides.
82
+
83
+ Remove the nesting by adding the nested keys as prefixes separated by dots, and fuse
84
+ it with the override dict.
85
+ """
81
86
  fused_dict = main_dict.copy()
82
87
 
83
88
  for key, value in override_dict.items():
@@ -96,7 +101,7 @@ def get_fused_config_from_dir(
96
101
  )
97
102
  flat_default_config = flatten_dict(default_config)
98
103
 
99
- return _fuse_dicts(flat_default_config, override_config)
104
+ return fuse_dicts(flat_default_config, override_config)
100
105
 
101
106
 
102
107
  def get_fused_config(run: Run, flwr_dir: Optional[Path]) -> UserConfig:
@@ -15,8 +15,10 @@
15
15
  """RecordSet."""
16
16
 
17
17
 
18
+ from __future__ import annotations
19
+
18
20
  from dataclasses import dataclass
19
- from typing import Dict, Optional, cast
21
+ from typing import cast
20
22
 
21
23
  from .configsrecord import ConfigsRecord
22
24
  from .metricsrecord import MetricsRecord
@@ -34,9 +36,9 @@ class RecordSetData:
34
36
 
35
37
  def __init__(
36
38
  self,
37
- parameters_records: Optional[Dict[str, ParametersRecord]] = None,
38
- metrics_records: Optional[Dict[str, MetricsRecord]] = None,
39
- configs_records: Optional[Dict[str, ConfigsRecord]] = None,
39
+ parameters_records: dict[str, ParametersRecord] | None = None,
40
+ metrics_records: dict[str, MetricsRecord] | None = None,
41
+ configs_records: dict[str, ConfigsRecord] | None = None,
40
42
  ) -> None:
41
43
  self.parameters_records = TypedDict[str, ParametersRecord](
42
44
  self._check_fn_str, self._check_fn_params
@@ -88,9 +90,9 @@ class RecordSet:
88
90
 
89
91
  def __init__(
90
92
  self,
91
- parameters_records: Optional[Dict[str, ParametersRecord]] = None,
92
- metrics_records: Optional[Dict[str, MetricsRecord]] = None,
93
- configs_records: Optional[Dict[str, ConfigsRecord]] = None,
93
+ parameters_records: dict[str, ParametersRecord] | None = None,
94
+ metrics_records: dict[str, MetricsRecord] | None = None,
95
+ configs_records: dict[str, ConfigsRecord] | None = None,
94
96
  ) -> None:
95
97
  data = RecordSetData(
96
98
  parameters_records=parameters_records,
@@ -15,99 +15,61 @@
15
15
  """Typed dict base class for *Records."""
16
16
 
17
17
 
18
- from typing import Any, Callable, Dict, Generic, Iterator, Tuple, TypeVar, cast
18
+ from typing import Callable, Dict, Generic, Iterator, MutableMapping, TypeVar, cast
19
19
 
20
20
  K = TypeVar("K") # Key type
21
21
  V = TypeVar("V") # Value type
22
22
 
23
23
 
24
- class TypedDict(Generic[K, V]):
24
+ class TypedDict(MutableMapping[K, V], Generic[K, V]):
25
25
  """Typed dictionary."""
26
26
 
27
27
  def __init__(
28
28
  self, check_key_fn: Callable[[K], None], check_value_fn: Callable[[V], None]
29
29
  ):
30
- self._data: Dict[K, V] = {}
31
- self._check_key_fn = check_key_fn
32
- self._check_value_fn = check_value_fn
30
+ self.__dict__["_check_key_fn"] = check_key_fn
31
+ self.__dict__["_check_value_fn"] = check_value_fn
32
+ self.__dict__["_data"] = {}
33
33
 
34
34
  def __setitem__(self, key: K, value: V) -> None:
35
35
  """Set the given key to the given value after type checking."""
36
36
  # Check the types of key and value
37
- self._check_key_fn(key)
38
- self._check_value_fn(value)
37
+ cast(Callable[[K], None], self.__dict__["_check_key_fn"])(key)
38
+ cast(Callable[[V], None], self.__dict__["_check_value_fn"])(value)
39
+
39
40
  # Set key-value pair
40
- self._data[key] = value
41
+ cast(Dict[K, V], self.__dict__["_data"])[key] = value
41
42
 
42
43
  def __delitem__(self, key: K) -> None:
43
44
  """Remove the item with the specified key."""
44
- del self._data[key]
45
+ del cast(Dict[K, V], self.__dict__["_data"])[key]
45
46
 
46
47
  def __getitem__(self, item: K) -> V:
47
48
  """Return the value for the specified key."""
48
- return self._data[item]
49
+ return cast(Dict[K, V], self.__dict__["_data"])[item]
49
50
 
50
51
  def __iter__(self) -> Iterator[K]:
51
52
  """Yield an iterator over the keys of the dictionary."""
52
- return iter(self._data)
53
+ return iter(cast(Dict[K, V], self.__dict__["_data"]))
53
54
 
54
55
  def __repr__(self) -> str:
55
56
  """Return a string representation of the dictionary."""
56
- return self._data.__repr__()
57
+ return cast(Dict[K, V], self.__dict__["_data"]).__repr__()
57
58
 
58
59
  def __len__(self) -> int:
59
60
  """Return the number of items in the dictionary."""
60
- return len(self._data)
61
+ return len(cast(Dict[K, V], self.__dict__["_data"]))
61
62
 
62
- def __contains__(self, key: K) -> bool:
63
+ def __contains__(self, key: object) -> bool:
63
64
  """Check if the dictionary contains the specified key."""
64
- return key in self._data
65
+ return key in cast(Dict[K, V], self.__dict__["_data"])
65
66
 
66
67
  def __eq__(self, other: object) -> bool:
67
68
  """Compare this instance to another dictionary or TypedDict."""
69
+ data = cast(Dict[K, V], self.__dict__["_data"])
68
70
  if isinstance(other, TypedDict):
69
- return self._data == other._data
71
+ other_data = cast(Dict[K, V], other.__dict__["_data"])
72
+ return data == other_data
70
73
  if isinstance(other, dict):
71
- return self._data == other
74
+ return data == other
72
75
  return NotImplemented
73
-
74
- def items(self) -> Iterator[Tuple[K, V]]:
75
- """R.items() -> a set-like object providing a view on R's items."""
76
- return cast(Iterator[Tuple[K, V]], self._data.items())
77
-
78
- def keys(self) -> Iterator[K]:
79
- """R.keys() -> a set-like object providing a view on R's keys."""
80
- return cast(Iterator[K], self._data.keys())
81
-
82
- def values(self) -> Iterator[V]:
83
- """R.values() -> an object providing a view on R's values."""
84
- return cast(Iterator[V], self._data.values())
85
-
86
- def update(self, *args: Any, **kwargs: Any) -> None:
87
- """R.update([E, ]**F) -> None.
88
-
89
- Update R from dict/iterable E and F.
90
- """
91
- for key, value in dict(*args, **kwargs).items():
92
- self[key] = value
93
-
94
- def pop(self, key: K) -> V:
95
- """R.pop(k[,d]) -> v, remove specified key and return the corresponding value.
96
-
97
- If key is not found, d is returned if given, otherwise KeyError is raised.
98
- """
99
- return self._data.pop(key)
100
-
101
- def get(self, key: K, default: V) -> V:
102
- """R.get(k[,d]) -> R[k] if k in R, else d.
103
-
104
- d defaults to None.
105
- """
106
- return self._data.get(key, default)
107
-
108
- def clear(self) -> None:
109
- """R.clear() -> None.
110
-
111
- Remove all items from R.
112
- """
113
- self._data.clear()
@@ -145,7 +145,7 @@ def _recordset_to_fit_or_evaluate_ins_components(
145
145
  # get config dict
146
146
  config_record = recordset.configs_records[f"{ins_str}.config"]
147
147
  # pylint: disable-next=protected-access
148
- config_dict = _check_mapping_from_recordscalartype_to_scalar(config_record._data)
148
+ config_dict = _check_mapping_from_recordscalartype_to_scalar(config_record)
149
149
 
150
150
  return parameters, config_dict
151
151
 
@@ -213,7 +213,7 @@ def recordset_to_fitres(recordset: RecordSet, keep_input: bool) -> FitRes:
213
213
  )
214
214
  configs_record = recordset.configs_records[f"{ins_str}.metrics"]
215
215
  # pylint: disable-next=protected-access
216
- metrics = _check_mapping_from_recordscalartype_to_scalar(configs_record._data)
216
+ metrics = _check_mapping_from_recordscalartype_to_scalar(configs_record)
217
217
  status = _extract_status_from_recordset(ins_str, recordset)
218
218
 
219
219
  return FitRes(
@@ -274,7 +274,7 @@ def recordset_to_evaluateres(recordset: RecordSet) -> EvaluateRes:
274
274
  configs_record = recordset.configs_records[f"{ins_str}.metrics"]
275
275
 
276
276
  # pylint: disable-next=protected-access
277
- metrics = _check_mapping_from_recordscalartype_to_scalar(configs_record._data)
277
+ metrics = _check_mapping_from_recordscalartype_to_scalar(configs_record)
278
278
  status = _extract_status_from_recordset(ins_str, recordset)
279
279
 
280
280
  return EvaluateRes(
@@ -314,7 +314,7 @@ def recordset_to_getparametersins(recordset: RecordSet) -> GetParametersIns:
314
314
  """Derive GetParametersIns from a RecordSet object."""
315
315
  config_record = recordset.configs_records["getparametersins.config"]
316
316
  # pylint: disable-next=protected-access
317
- config_dict = _check_mapping_from_recordscalartype_to_scalar(config_record._data)
317
+ config_dict = _check_mapping_from_recordscalartype_to_scalar(config_record)
318
318
 
319
319
  return GetParametersIns(config=config_dict)
320
320
 
@@ -365,7 +365,7 @@ def recordset_to_getpropertiesins(recordset: RecordSet) -> GetPropertiesIns:
365
365
  """Derive GetPropertiesIns from a RecordSet object."""
366
366
  config_record = recordset.configs_records["getpropertiesins.config"]
367
367
  # pylint: disable-next=protected-access
368
- config_dict = _check_mapping_from_recordscalartype_to_scalar(config_record._data)
368
+ config_dict = _check_mapping_from_recordscalartype_to_scalar(config_record)
369
369
 
370
370
  return GetPropertiesIns(config=config_dict)
371
371
 
@@ -384,7 +384,7 @@ def recordset_to_getpropertiesres(recordset: RecordSet) -> GetPropertiesRes:
384
384
  res_str = "getpropertiesres"
385
385
  config_record = recordset.configs_records[f"{res_str}.properties"]
386
386
  # pylint: disable-next=protected-access
387
- properties = _check_mapping_from_recordscalartype_to_scalar(config_record._data)
387
+ properties = _check_mapping_from_recordscalartype_to_scalar(config_record)
388
388
 
389
389
  status = _extract_status_from_recordset(res_str, recordset=recordset)
390
390
 
flwr/common/serde.py CHANGED
@@ -22,6 +22,7 @@ from google.protobuf.message import Message as GrpcMessage
22
22
  # pylint: disable=E0611
23
23
  from flwr.proto.clientappio_pb2 import ClientAppOutputCode, ClientAppOutputStatus
24
24
  from flwr.proto.error_pb2 import Error as ProtoError
25
+ from flwr.proto.fab_pb2 import Fab as ProtoFab
25
26
  from flwr.proto.message_pb2 import Context as ProtoContext
26
27
  from flwr.proto.message_pb2 import Message as ProtoMessage
27
28
  from flwr.proto.message_pb2 import Metadata as ProtoMetadata
@@ -686,6 +687,19 @@ def message_from_taskres(taskres: TaskRes) -> Message:
686
687
  return message
687
688
 
688
689
 
690
+ # === FAB ===
691
+
692
+
693
+ def fab_to_proto(fab: typing.Fab) -> ProtoFab:
694
+ """Create a proto Fab object from a Python Fab."""
695
+ return ProtoFab(hash_str=fab.hash_str, content=fab.content)
696
+
697
+
698
+ def fab_from_proto(fab: ProtoFab) -> typing.Fab:
699
+ """Create a Python Fab object from a proto Fab."""
700
+ return typing.Fab(fab.hash_str, fab.content)
701
+
702
+
689
703
  # === User configs ===
690
704
 
691
705
 
@@ -745,6 +759,7 @@ def metadata_to_proto(metadata: Metadata) -> ProtoMetadata:
745
759
  group_id=metadata.group_id,
746
760
  ttl=metadata.ttl,
747
761
  message_type=metadata.message_type,
762
+ created_at=metadata.created_at,
748
763
  )
749
764
  return proto
750
765
 
@@ -771,7 +786,9 @@ def message_to_proto(message: Message) -> ProtoMessage:
771
786
  """Serialize `Message` to ProtoBuf."""
772
787
  proto = ProtoMessage(
773
788
  metadata=metadata_to_proto(message.metadata),
774
- content=recordset_to_proto(message.content),
789
+ content=(
790
+ recordset_to_proto(message.content) if message.has_content() else None
791
+ ),
775
792
  error=error_to_proto(message.error) if message.has_error() else None,
776
793
  )
777
794
  return proto
@@ -779,6 +796,7 @@ def message_to_proto(message: Message) -> ProtoMessage:
779
796
 
780
797
  def message_from_proto(message_proto: ProtoMessage) -> Message:
781
798
  """Deserialize `Message` from ProtoBuf."""
799
+ created_at = message_proto.metadata.created_at
782
800
  message = Message(
783
801
  metadata=metadata_from_proto(message_proto.metadata),
784
802
  content=(
@@ -792,6 +810,9 @@ def message_from_proto(message_proto: ProtoMessage) -> Message:
792
810
  else None
793
811
  ),
794
812
  )
813
+ # `.created_at` is set upon Message object construction
814
+ # we need to manually set it to the original value
815
+ message.metadata.created_at = created_at
795
816
  return message
796
817
 
797
818
 
@@ -829,8 +850,8 @@ def run_to_proto(run: typing.Run) -> ProtoRun:
829
850
  run_id=run.run_id,
830
851
  fab_id=run.fab_id,
831
852
  fab_version=run.fab_version,
853
+ fab_hash=run.fab_hash,
832
854
  override_config=user_config_to_proto(run.override_config),
833
- fab_hash="",
834
855
  )
835
856
  return proto
836
857
 
@@ -841,6 +862,7 @@ def run_from_proto(run_proto: ProtoRun) -> typing.Run:
841
862
  run_id=run_proto.run_id,
842
863
  fab_id=run_proto.fab_id,
843
864
  fab_version=run_proto.fab_version,
865
+ fab_hash=run_proto.fab_hash,
844
866
  override_config=user_config_from_proto(run_proto.override_config),
845
867
  )
846
868
  return run
flwr/common/typing.py CHANGED
@@ -214,6 +214,7 @@ class Run:
214
214
  run_id: int
215
215
  fab_id: str
216
216
  fab_version: str
217
+ fab_hash: str
217
218
  override_config: UserConfig
218
219
 
219
220
 
flwr/proto/exec_pb2.py CHANGED
@@ -12,10 +12,11 @@ from google.protobuf.internal import builder as _builder
12
12
  _sym_db = _symbol_database.Default()
13
13
 
14
14
 
15
+ from flwr.proto import fab_pb2 as flwr_dot_proto_dot_fab__pb2
15
16
  from flwr.proto import transport_pb2 as flwr_dot_proto_dot_transport__pb2
16
17
 
17
18
 
18
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x66lwr/proto/exec.proto\x12\nflwr.proto\x1a\x1a\x66lwr/proto/transport.proto\"\xd3\x02\n\x0fStartRunRequest\x12\x10\n\x08\x66\x61\x62_file\x18\x01 \x01(\x0c\x12H\n\x0foverride_config\x18\x02 \x03(\x0b\x32/.flwr.proto.StartRunRequest.OverrideConfigEntry\x12L\n\x11\x66\x65\x64\x65ration_config\x18\x03 \x03(\x0b\x32\x31.flwr.proto.StartRunRequest.FederationConfigEntry\x1aI\n\x13OverrideConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.flwr.proto.Scalar:\x02\x38\x01\x1aK\n\x15\x46\x65\x64\x65rationConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.flwr.proto.Scalar:\x02\x38\x01\"\"\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')
19
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x66lwr/proto/exec.proto\x12\nflwr.proto\x1a\x14\x66lwr/proto/fab.proto\x1a\x1a\x66lwr/proto/transport.proto\"\xdf\x02\n\x0fStartRunRequest\x12\x1c\n\x03\x66\x61\x62\x18\x01 \x01(\x0b\x32\x0f.flwr.proto.Fab\x12H\n\x0foverride_config\x18\x02 \x03(\x0b\x32/.flwr.proto.StartRunRequest.OverrideConfigEntry\x12L\n\x11\x66\x65\x64\x65ration_config\x18\x03 \x03(\x0b\x32\x31.flwr.proto.StartRunRequest.FederationConfigEntry\x1aI\n\x13OverrideConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.flwr.proto.Scalar:\x02\x38\x01\x1aK\n\x15\x46\x65\x64\x65rationConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.flwr.proto.Scalar:\x02\x38\x01\"\"\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')
19
20
 
20
21
  _globals = globals()
21
22
  _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -26,18 +27,18 @@ if _descriptor._USE_C_DESCRIPTORS == False:
26
27
  _globals['_STARTRUNREQUEST_OVERRIDECONFIGENTRY']._serialized_options = b'8\001'
27
28
  _globals['_STARTRUNREQUEST_FEDERATIONCONFIGENTRY']._options = None
28
29
  _globals['_STARTRUNREQUEST_FEDERATIONCONFIGENTRY']._serialized_options = b'8\001'
29
- _globals['_STARTRUNREQUEST']._serialized_start=66
30
- _globals['_STARTRUNREQUEST']._serialized_end=405
31
- _globals['_STARTRUNREQUEST_OVERRIDECONFIGENTRY']._serialized_start=255
32
- _globals['_STARTRUNREQUEST_OVERRIDECONFIGENTRY']._serialized_end=328
33
- _globals['_STARTRUNREQUEST_FEDERATIONCONFIGENTRY']._serialized_start=330
34
- _globals['_STARTRUNREQUEST_FEDERATIONCONFIGENTRY']._serialized_end=405
35
- _globals['_STARTRUNRESPONSE']._serialized_start=407
36
- _globals['_STARTRUNRESPONSE']._serialized_end=441
37
- _globals['_STREAMLOGSREQUEST']._serialized_start=443
38
- _globals['_STREAMLOGSREQUEST']._serialized_end=478
39
- _globals['_STREAMLOGSRESPONSE']._serialized_start=480
40
- _globals['_STREAMLOGSRESPONSE']._serialized_end=520
41
- _globals['_EXEC']._serialized_start=523
42
- _globals['_EXEC']._serialized_end=683
30
+ _globals['_STARTRUNREQUEST']._serialized_start=88
31
+ _globals['_STARTRUNREQUEST']._serialized_end=439
32
+ _globals['_STARTRUNREQUEST_OVERRIDECONFIGENTRY']._serialized_start=289
33
+ _globals['_STARTRUNREQUEST_OVERRIDECONFIGENTRY']._serialized_end=362
34
+ _globals['_STARTRUNREQUEST_FEDERATIONCONFIGENTRY']._serialized_start=364
35
+ _globals['_STARTRUNREQUEST_FEDERATIONCONFIGENTRY']._serialized_end=439
36
+ _globals['_STARTRUNRESPONSE']._serialized_start=441
37
+ _globals['_STARTRUNRESPONSE']._serialized_end=475
38
+ _globals['_STREAMLOGSREQUEST']._serialized_start=477
39
+ _globals['_STREAMLOGSREQUEST']._serialized_end=512
40
+ _globals['_STREAMLOGSRESPONSE']._serialized_start=514
41
+ _globals['_STREAMLOGSRESPONSE']._serialized_end=554
42
+ _globals['_EXEC']._serialized_start=557
43
+ _globals['_EXEC']._serialized_end=717
43
44
  # @@protoc_insertion_point(module_scope)
flwr/proto/exec_pb2.pyi CHANGED
@@ -3,6 +3,7 @@
3
3
  isort:skip_file
4
4
  """
5
5
  import builtins
6
+ import flwr.proto.fab_pb2
6
7
  import flwr.proto.transport_pb2
7
8
  import google.protobuf.descriptor
8
9
  import google.protobuf.internal.containers
@@ -44,21 +45,23 @@ class StartRunRequest(google.protobuf.message.Message):
44
45
  def HasField(self, field_name: typing_extensions.Literal["value",b"value"]) -> builtins.bool: ...
45
46
  def ClearField(self, field_name: typing_extensions.Literal["key",b"key","value",b"value"]) -> None: ...
46
47
 
47
- FAB_FILE_FIELD_NUMBER: builtins.int
48
+ FAB_FIELD_NUMBER: builtins.int
48
49
  OVERRIDE_CONFIG_FIELD_NUMBER: builtins.int
49
50
  FEDERATION_CONFIG_FIELD_NUMBER: builtins.int
50
- fab_file: builtins.bytes
51
+ @property
52
+ def fab(self) -> flwr.proto.fab_pb2.Fab: ...
51
53
  @property
52
54
  def override_config(self) -> google.protobuf.internal.containers.MessageMap[typing.Text, flwr.proto.transport_pb2.Scalar]: ...
53
55
  @property
54
56
  def federation_config(self) -> google.protobuf.internal.containers.MessageMap[typing.Text, flwr.proto.transport_pb2.Scalar]: ...
55
57
  def __init__(self,
56
58
  *,
57
- fab_file: builtins.bytes = ...,
59
+ fab: typing.Optional[flwr.proto.fab_pb2.Fab] = ...,
58
60
  override_config: typing.Optional[typing.Mapping[typing.Text, flwr.proto.transport_pb2.Scalar]] = ...,
59
61
  federation_config: typing.Optional[typing.Mapping[typing.Text, flwr.proto.transport_pb2.Scalar]] = ...,
60
62
  ) -> None: ...
61
- def ClearField(self, field_name: typing_extensions.Literal["fab_file",b"fab_file","federation_config",b"federation_config","override_config",b"override_config"]) -> None: ...
63
+ def HasField(self, field_name: typing_extensions.Literal["fab",b"fab"]) -> builtins.bool: ...
64
+ def ClearField(self, field_name: typing_extensions.Literal["fab",b"fab","federation_config",b"federation_config","override_config",b"override_config"]) -> None: ...
62
65
  global___StartRunRequest = StartRunRequest
63
66
 
64
67
  class StartRunResponse(google.protobuf.message.Message):
flwr/proto/message_pb2.py CHANGED
@@ -17,7 +17,7 @@ from flwr.proto import recordset_pb2 as flwr_dot_proto_dot_recordset__pb2
17
17
  from flwr.proto import transport_pb2 as flwr_dot_proto_dot_transport__pb2
18
18
 
19
19
 
20
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x18\x66lwr/proto/message.proto\x12\nflwr.proto\x1a\x16\x66lwr/proto/error.proto\x1a\x1a\x66lwr/proto/recordset.proto\x1a\x1a\x66lwr/proto/transport.proto\"{\n\x07Message\x12&\n\x08metadata\x18\x01 \x01(\x0b\x32\x14.flwr.proto.Metadata\x12&\n\x07\x63ontent\x18\x02 \x01(\x0b\x32\x15.flwr.proto.RecordSet\x12 \n\x05\x65rror\x18\x03 \x01(\x0b\x32\x11.flwr.proto.Error\"\xbf\x02\n\x07\x43ontext\x12\x0f\n\x07node_id\x18\x01 \x01(\x12\x12\x38\n\x0bnode_config\x18\x02 \x03(\x0b\x32#.flwr.proto.Context.NodeConfigEntry\x12$\n\x05state\x18\x03 \x01(\x0b\x32\x15.flwr.proto.RecordSet\x12\x36\n\nrun_config\x18\x04 \x03(\x0b\x32\".flwr.proto.Context.RunConfigEntry\x1a\x45\n\x0fNodeConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.flwr.proto.Scalar:\x02\x38\x01\x1a\x44\n\x0eRunConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.flwr.proto.Scalar:\x02\x38\x01\"\xa7\x01\n\x08Metadata\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\x12\x12\n\nmessage_id\x18\x02 \x01(\t\x12\x13\n\x0bsrc_node_id\x18\x03 \x01(\x12\x12\x13\n\x0b\x64st_node_id\x18\x04 \x01(\x12\x12\x18\n\x10reply_to_message\x18\x05 \x01(\t\x12\x10\n\x08group_id\x18\x06 \x01(\t\x12\x0b\n\x03ttl\x18\x07 \x01(\x01\x12\x14\n\x0cmessage_type\x18\x08 \x01(\tb\x06proto3')
20
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x18\x66lwr/proto/message.proto\x12\nflwr.proto\x1a\x16\x66lwr/proto/error.proto\x1a\x1a\x66lwr/proto/recordset.proto\x1a\x1a\x66lwr/proto/transport.proto\"{\n\x07Message\x12&\n\x08metadata\x18\x01 \x01(\x0b\x32\x14.flwr.proto.Metadata\x12&\n\x07\x63ontent\x18\x02 \x01(\x0b\x32\x15.flwr.proto.RecordSet\x12 \n\x05\x65rror\x18\x03 \x01(\x0b\x32\x11.flwr.proto.Error\"\xbf\x02\n\x07\x43ontext\x12\x0f\n\x07node_id\x18\x01 \x01(\x12\x12\x38\n\x0bnode_config\x18\x02 \x03(\x0b\x32#.flwr.proto.Context.NodeConfigEntry\x12$\n\x05state\x18\x03 \x01(\x0b\x32\x15.flwr.proto.RecordSet\x12\x36\n\nrun_config\x18\x04 \x03(\x0b\x32\".flwr.proto.Context.RunConfigEntry\x1a\x45\n\x0fNodeConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.flwr.proto.Scalar:\x02\x38\x01\x1a\x44\n\x0eRunConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.flwr.proto.Scalar:\x02\x38\x01\"\xbb\x01\n\x08Metadata\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\x12\x12\n\nmessage_id\x18\x02 \x01(\t\x12\x13\n\x0bsrc_node_id\x18\x03 \x01(\x12\x12\x13\n\x0b\x64st_node_id\x18\x04 \x01(\x12\x12\x18\n\x10reply_to_message\x18\x05 \x01(\t\x12\x10\n\x08group_id\x18\x06 \x01(\t\x12\x0b\n\x03ttl\x18\x07 \x01(\x01\x12\x14\n\x0cmessage_type\x18\x08 \x01(\t\x12\x12\n\ncreated_at\x18\t \x01(\x01\x62\x06proto3')
21
21
 
22
22
  _globals = globals()
23
23
  _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -37,5 +37,5 @@ if _descriptor._USE_C_DESCRIPTORS == False:
37
37
  _globals['_CONTEXT_RUNCONFIGENTRY']._serialized_start=497
38
38
  _globals['_CONTEXT_RUNCONFIGENTRY']._serialized_end=565
39
39
  _globals['_METADATA']._serialized_start=568
40
- _globals['_METADATA']._serialized_end=735
40
+ _globals['_METADATA']._serialized_end=755
41
41
  # @@protoc_insertion_point(module_scope)
@@ -99,6 +99,7 @@ class Metadata(google.protobuf.message.Message):
99
99
  GROUP_ID_FIELD_NUMBER: builtins.int
100
100
  TTL_FIELD_NUMBER: builtins.int
101
101
  MESSAGE_TYPE_FIELD_NUMBER: builtins.int
102
+ CREATED_AT_FIELD_NUMBER: builtins.int
102
103
  run_id: builtins.int
103
104
  message_id: typing.Text
104
105
  src_node_id: builtins.int
@@ -107,6 +108,7 @@ class Metadata(google.protobuf.message.Message):
107
108
  group_id: typing.Text
108
109
  ttl: builtins.float
109
110
  message_type: typing.Text
111
+ created_at: builtins.float
110
112
  def __init__(self,
111
113
  *,
112
114
  run_id: builtins.int = ...,
@@ -117,6 +119,7 @@ class Metadata(google.protobuf.message.Message):
117
119
  group_id: typing.Text = ...,
118
120
  ttl: builtins.float = ...,
119
121
  message_type: typing.Text = ...,
122
+ created_at: builtins.float = ...,
120
123
  ) -> None: ...
121
- def ClearField(self, field_name: typing_extensions.Literal["dst_node_id",b"dst_node_id","group_id",b"group_id","message_id",b"message_id","message_type",b"message_type","reply_to_message",b"reply_to_message","run_id",b"run_id","src_node_id",b"src_node_id","ttl",b"ttl"]) -> None: ...
124
+ def ClearField(self, field_name: typing_extensions.Literal["created_at",b"created_at","dst_node_id",b"dst_node_id","group_id",b"group_id","message_id",b"message_id","message_type",b"message_type","reply_to_message",b"reply_to_message","run_id",b"run_id","src_node_id",b"src_node_id","ttl",b"ttl"]) -> None: ...
122
125
  global___Metadata = Metadata
flwr/server/app.py CHANGED
@@ -34,6 +34,7 @@ from cryptography.hazmat.primitives.serialization import (
34
34
 
35
35
  from flwr.common import GRPC_MAX_MESSAGE_LENGTH, EventType, event
36
36
  from flwr.common.address import parse_address
37
+ from flwr.common.config import get_flwr_dir
37
38
  from flwr.common.constant import (
38
39
  MISSING_EXTRA_REST,
39
40
  TRANSPORT_TYPE_GRPC_ADAPTER,
@@ -57,6 +58,7 @@ from .server import Server, init_defaults, run_fl
57
58
  from .server_config import ServerConfig
58
59
  from .strategy import Strategy
59
60
  from .superlink.driver.driver_grpc import run_driver_api_grpc
61
+ from .superlink.ffs.ffs_factory import FfsFactory
60
62
  from .superlink.fleet.grpc_adapter.grpc_adapter_servicer import GrpcAdapterServicer
61
63
  from .superlink.fleet.grpc_bidi.grpc_server import (
62
64
  generic_create_grpc_server,
@@ -72,6 +74,7 @@ ADDRESS_FLEET_API_GRPC_BIDI = "[::]:8080" # IPv6 to keep start_server compatibl
72
74
  ADDRESS_FLEET_API_REST = "0.0.0.0:9093"
73
75
 
74
76
  DATABASE = ":flwr-in-memory-state:"
77
+ BASE_DIR = get_flwr_dir() / "superlink" / "ffs"
75
78
 
76
79
 
77
80
  def start_server( # pylint: disable=too-many-arguments,too-many-locals
@@ -211,10 +214,14 @@ def run_superlink() -> None:
211
214
  # Initialize StateFactory
212
215
  state_factory = StateFactory(args.database)
213
216
 
217
+ # Initialize FfsFactory
218
+ ffs_factory = FfsFactory(args.storage_dir)
219
+
214
220
  # Start Driver API
215
221
  driver_server: grpc.Server = run_driver_api_grpc(
216
222
  address=driver_address,
217
223
  state_factory=state_factory,
224
+ ffs_factory=ffs_factory,
218
225
  certificates=certificates,
219
226
  )
220
227
 
@@ -610,6 +617,11 @@ def _add_args_common(parser: argparse.ArgumentParser) -> None:
610
617
  "Flower will just create a state in memory.",
611
618
  default=DATABASE,
612
619
  )
620
+ parser.add_argument(
621
+ "--storage-dir",
622
+ help="The base directory to store the objects for the Flower File System.",
623
+ default=BASE_DIR,
624
+ )
613
625
  parser.add_argument(
614
626
  "--auth-list-public-keys",
615
627
  type=str,
@@ -131,6 +131,7 @@ class GrpcDriver(Driver):
131
131
  run_id=res.run.run_id,
132
132
  fab_id=res.run.fab_id,
133
133
  fab_version=res.run.fab_version,
134
+ fab_hash=res.run.fab_hash,
134
135
  override_config=user_config_from_proto(res.run.override_config),
135
136
  )
136
137
 
@@ -24,6 +24,7 @@ from flwr.common.logger import log
24
24
  from flwr.proto.driver_pb2_grpc import ( # pylint: disable=E0611
25
25
  add_DriverServicer_to_server,
26
26
  )
27
+ from flwr.server.superlink.ffs.ffs_factory import FfsFactory
27
28
  from flwr.server.superlink.state import StateFactory
28
29
 
29
30
  from ..fleet.grpc_bidi.grpc_server import generic_create_grpc_server
@@ -33,12 +34,14 @@ from .driver_servicer import DriverServicer
33
34
  def run_driver_api_grpc(
34
35
  address: str,
35
36
  state_factory: StateFactory,
37
+ ffs_factory: FfsFactory,
36
38
  certificates: Optional[Tuple[bytes, bytes, bytes]],
37
39
  ) -> grpc.Server:
38
40
  """Run Driver API (gRPC, request-response)."""
39
41
  # Create Driver API gRPC server
40
42
  driver_servicer: grpc.Server = DriverServicer(
41
43
  state_factory=state_factory,
44
+ ffs_factory=ffs_factory,
42
45
  )
43
46
  driver_add_servicer_to_server_fn = add_DriverServicer_to_server
44
47
  driver_grpc_server = generic_create_grpc_server(
@@ -43,6 +43,8 @@ from flwr.proto.run_pb2 import ( # pylint: disable=E0611
43
43
  Run,
44
44
  )
45
45
  from flwr.proto.task_pb2 import TaskRes # pylint: disable=E0611
46
+ from flwr.server.superlink.ffs import Ffs
47
+ from flwr.server.superlink.ffs.ffs_factory import FfsFactory
46
48
  from flwr.server.superlink.state import State, StateFactory
47
49
  from flwr.server.utils.validator import validate_task_ins_or_res
48
50
 
@@ -50,8 +52,9 @@ from flwr.server.utils.validator import validate_task_ins_or_res
50
52
  class DriverServicer(driver_pb2_grpc.DriverServicer):
51
53
  """Driver API servicer."""
52
54
 
53
- def __init__(self, state_factory: StateFactory) -> None:
55
+ def __init__(self, state_factory: StateFactory, ffs_factory: FfsFactory) -> None:
54
56
  self.state_factory = state_factory
57
+ self.ffs_factory = ffs_factory
55
58
 
56
59
  def GetNodes(
57
60
  self, request: GetNodesRequest, context: grpc.ServicerContext
@@ -71,9 +74,19 @@ class DriverServicer(driver_pb2_grpc.DriverServicer):
71
74
  """Create run ID."""
72
75
  log(DEBUG, "DriverServicer.CreateRun")
73
76
  state: State = self.state_factory.state()
77
+ if request.HasField("fab") and request.fab.HasField("content"):
78
+ ffs: Ffs = self.ffs_factory.ffs()
79
+ fab_hash = ffs.put(request.fab.content, {})
80
+ _raise_if(
81
+ fab_hash != request.fab.hash_str,
82
+ f"FAB ({request.fab}) hash from request doesn't match contents",
83
+ )
84
+ else:
85
+ fab_hash = ""
74
86
  run_id = state.create_run(
75
87
  request.fab_id,
76
88
  request.fab_version,
89
+ fab_hash,
77
90
  user_config_from_proto(request.override_config),
78
91
  )
79
92
  return CreateRunResponse(run_id=run_id)
@@ -0,0 +1,47 @@
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
+ """Factory class that creates Ffs instances."""
16
+
17
+
18
+ from logging import DEBUG
19
+ from typing import Optional
20
+
21
+ from flwr.common.logger import log
22
+
23
+ from .disk_ffs import DiskFfs
24
+ from .ffs import Ffs
25
+
26
+
27
+ class FfsFactory:
28
+ """Factory class that creates Ffs instances.
29
+
30
+ Parameters
31
+ ----------
32
+ base_dir : str
33
+ The base directory used by DiskFfs to store objects.
34
+ """
35
+
36
+ def __init__(self, base_dir: str) -> None:
37
+ self.base_dir = base_dir
38
+ self.ffs_instance: Optional[Ffs] = None
39
+
40
+ def ffs(self) -> Ffs:
41
+ """Return a Ffs instance and create it, if necessary."""
42
+ if not self.ffs_instance:
43
+ log(DEBUG, "Initializing DiskFfs")
44
+ self.ffs_instance = DiskFfs(self.base_dir)
45
+
46
+ log(DEBUG, "Using DiskFfs")
47
+ return self.ffs_instance
@@ -277,11 +277,12 @@ class InMemoryState(State): # pylint: disable=R0902,R0904
277
277
 
278
278
  def create_run(
279
279
  self,
280
- fab_id: str,
281
- fab_version: str,
280
+ fab_id: Optional[str],
281
+ fab_version: Optional[str],
282
+ fab_hash: Optional[str],
282
283
  override_config: UserConfig,
283
284
  ) -> int:
284
- """Create a new run for the specified `fab_id` and `fab_version`."""
285
+ """Create a new run for the specified `fab_hash`."""
285
286
  # Sample a random int64 as run_id
286
287
  with self.lock:
287
288
  run_id = generate_rand_int_from_bytes(RUN_ID_NUM_BYTES)
@@ -289,8 +290,9 @@ class InMemoryState(State): # pylint: disable=R0902,R0904
289
290
  if run_id not in self.run_ids:
290
291
  self.run_ids[run_id] = Run(
291
292
  run_id=run_id,
292
- fab_id=fab_id,
293
- fab_version=fab_version,
293
+ fab_id=fab_id if fab_id else "",
294
+ fab_version=fab_version if fab_version else "",
295
+ fab_hash=fab_hash if fab_hash else "",
294
296
  override_config=override_config,
295
297
  )
296
298
  return run_id
@@ -65,6 +65,7 @@ CREATE TABLE IF NOT EXISTS run(
65
65
  run_id INTEGER UNIQUE,
66
66
  fab_id TEXT,
67
67
  fab_version TEXT,
68
+ fab_hash TEXT,
68
69
  override_config TEXT
69
70
  );
70
71
  """
@@ -617,8 +618,9 @@ class SqliteState(State): # pylint: disable=R0904
617
618
 
618
619
  def create_run(
619
620
  self,
620
- fab_id: str,
621
- fab_version: str,
621
+ fab_id: Optional[str],
622
+ fab_version: Optional[str],
623
+ fab_hash: Optional[str],
622
624
  override_config: UserConfig,
623
625
  ) -> int:
624
626
  """Create a new run for the specified `fab_id` and `fab_version`."""
@@ -630,12 +632,19 @@ class SqliteState(State): # pylint: disable=R0904
630
632
  # If run_id does not exist
631
633
  if self.query(query, (run_id,))[0]["COUNT(*)"] == 0:
632
634
  query = (
633
- "INSERT INTO run (run_id, fab_id, fab_version, override_config)"
634
- "VALUES (?, ?, ?, ?);"
635
- )
636
- self.query(
637
- query, (run_id, fab_id, fab_version, json.dumps(override_config))
635
+ "INSERT INTO run "
636
+ "(run_id, fab_id, fab_version, fab_hash, override_config)"
637
+ "VALUES (?, ?, ?, ?, ?);"
638
638
  )
639
+ if fab_hash:
640
+ self.query(
641
+ query, (run_id, "", "", fab_hash, json.dumps(override_config))
642
+ )
643
+ else:
644
+ self.query(
645
+ query,
646
+ (run_id, fab_id, fab_version, "", json.dumps(override_config)),
647
+ )
639
648
  return run_id
640
649
  log(ERROR, "Unexpected run creation failure.")
641
650
  return 0
@@ -702,6 +711,7 @@ class SqliteState(State): # pylint: disable=R0904
702
711
  run_id=run_id,
703
712
  fab_id=row["fab_id"],
704
713
  fab_version=row["fab_version"],
714
+ fab_hash=row["fab_hash"],
705
715
  override_config=json.loads(row["override_config"]),
706
716
  )
707
717
  except sqlite3.IntegrityError:
@@ -159,11 +159,12 @@ class State(abc.ABC): # pylint: disable=R0904
159
159
  @abc.abstractmethod
160
160
  def create_run(
161
161
  self,
162
- fab_id: str,
163
- fab_version: str,
162
+ fab_id: Optional[str],
163
+ fab_version: Optional[str],
164
+ fab_hash: Optional[str],
164
165
  override_config: UserConfig,
165
166
  ) -> int:
166
- """Create a new run for the specified `fab_id` and `fab_version`."""
167
+ """Create a new run for the specified `fab_hash`."""
167
168
 
168
169
  @abc.abstractmethod
169
170
  def get_run(self, run_id: int) -> Optional[Run]:
@@ -163,6 +163,7 @@ def run_simulation_from_cli() -> None:
163
163
  run_id=run_id,
164
164
  fab_id="",
165
165
  fab_version="",
166
+ fab_hash="",
166
167
  override_config=override_config,
167
168
  )
168
169
 
@@ -529,7 +530,9 @@ def _run_simulation(
529
530
  # If no `Run` object is set, create one
530
531
  if run is None:
531
532
  run_id = generate_rand_int_from_bytes(RUN_ID_NUM_BYTES)
532
- run = Run(run_id=run_id, fab_id="", fab_version="", override_config={})
533
+ run = Run(
534
+ run_id=run_id, fab_id="", fab_version="", fab_hash="", override_config={}
535
+ )
533
536
 
534
537
  args = (
535
538
  num_supernodes,
@@ -47,7 +47,7 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
47
47
  log(INFO, "ExecServicer.StartRun")
48
48
 
49
49
  run = self.executor.start_run(
50
- request.fab_file,
50
+ request.fab.content,
51
51
  user_config_from_proto(request.override_config),
52
52
  user_config_from_proto(request.federation_config),
53
53
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: flwr-nightly
3
- Version: 1.11.0.dev20240813
3
+ Version: 1.11.0.dev20240815
4
4
  Summary: Flower: A Friendly Federated Learning Framework
5
5
  Home-page: https://flower.ai
6
6
  License: Apache-2.0
@@ -50,10 +50,10 @@ flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl,sha256=vIO1ArukTC76ogYLNmJ
50
50
  flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl,sha256=jk_5teoyOVM9QdBea8J-nk10S6TKw81QZiiKB54ATF0,654
51
51
  flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl,sha256=bRIvPCPvTTI4Eo5b61Rmw8WdDw3sjcohciTXgULN5l8,702
52
52
  flwr/cli/run/__init__.py,sha256=oCd6HmQDx-sqver1gecgx-uMA38BLTSiiKpl7RGNceg,789
53
- flwr/cli/run/run.py,sha256=te6J1HxOOzyC47HMIrvbh-_4xVEGM1x_yXLIhlCBkb8,7521
53
+ flwr/cli/run/run.py,sha256=Au7v9imSRRETacU16CJScbp5lr-jcduHen_ZFCeO6eY,7672
54
54
  flwr/cli/utils.py,sha256=l65Ul0YsSBPuypk0uorAtEDmLEYiUrzpCXi6zCg9mJ4,4506
55
55
  flwr/client/__init__.py,sha256=wzJZsYJIHf_8-PMzvfbinyzzjgh1UP1vLrAw2_yEbKI,1345
56
- flwr/client/app.py,sha256=ZeAl3pKGAFSRtH8WpICCZVgy9ESAo7_8R5GdP3tCXQA,26166
56
+ flwr/client/app.py,sha256=4Phq-PQ56kXRZ_hqUhet72JO28To0lgwYLuwYhCa4Eo,26170
57
57
  flwr/client/client.py,sha256=Vp9UkOkoHdNfn6iMYZsj_5m_GICiFfUlKEVaLad-YhM,8183
58
58
  flwr/client/client_app.py,sha256=WcO4r6wrdfaus__3s22D2sYjfcptdgmVujUAYdNE6HU,10393
59
59
  flwr/client/dpfedavg_numpy_client.py,sha256=ylZ-LpBIKmL1HCiS8kq4pkp2QGalc8rYEzDHdRG3VRQ,7435
@@ -63,7 +63,7 @@ flwr/client/grpc_client/__init__.py,sha256=LsnbqXiJhgQcB0XzAlUQgPx011Uf7Y7yabIC1
63
63
  flwr/client/grpc_client/connection.py,sha256=czhRm23fwTgjN24Vf5nyNQ3hcb5Fo_5k-o9yZnelQCs,9362
64
64
  flwr/client/grpc_rere_client/__init__.py,sha256=MK-oSoV3kwUEQnIwl0GN4OpiHR7eLOrMA8ikunET130,752
65
65
  flwr/client/grpc_rere_client/client_interceptor.py,sha256=sYPEznuQPdy2BPDlvM9FK0ZRRucb4NfwUee1Z_mN82E,4954
66
- flwr/client/grpc_rere_client/connection.py,sha256=5n8AknkYAx3q6zuYXY3UqoatP2gQyw8v6rjBr93ee0c,10541
66
+ flwr/client/grpc_rere_client/connection.py,sha256=-1hVwuVNKo3R5ZcEqKbAQT6I4L6P5fIKsQxHcwf0QNk,10584
67
67
  flwr/client/grpc_rere_client/grpc_adapter.py,sha256=Pw7Toi4wCUIEdBMyv4yKirjgW6814gFhhAmsTYmV4IM,5005
68
68
  flwr/client/heartbeat.py,sha256=cx37mJBH8LyoIN4Lks85wtqT1mnU5GulQnr4pGCvAq0,2404
69
69
  flwr/client/message_handler/__init__.py,sha256=QxxQuBNpFPTHx3KiUNvQSlqMKlEnbRR1kFfc1KVje08,719
@@ -80,14 +80,16 @@ flwr/client/mod/utils.py,sha256=UAJXiB0wwVyLkCkpW_i5BXikdBR65p8sNFr7VNHm2nk,1226
80
80
  flwr/client/node_state.py,sha256=Z0ZUPL5BWde9nfAPwR7cSV9YiNQeiEHbuMKDxqpy6-M,3492
81
81
  flwr/client/node_state_tests.py,sha256=-4fVsn7y-z9NYBuhq-cjepgxgVuPqqQgDOL4SofrdIo,2239
82
82
  flwr/client/numpy_client.py,sha256=u76GWAdHmJM88Agm2EgLQSvO8Jnk225mJTk-_TmPjFE,10283
83
+ flwr/client/process/__init__.py,sha256=s3hTr2dOWHkSnwivaL7YbXur38bEDgSPKKvwFLa1204,711
84
+ flwr/client/process/clientappio_servicer.py,sha256=ygh6wFC_q7fHV5L9G_W3Ftkn5la_BJixtOW-QzwxqyY,5536
83
85
  flwr/client/rest_client/__init__.py,sha256=5KGlp7pjc1dhNRkKlaNtUfQmg8wrRFh9lS3P3uRS-7Q,735
84
- flwr/client/rest_client/connection.py,sha256=N9D1YCTy6Jk8MLWDGFWTE261_EEgIfzw4rjXD8k3JS4,12350
86
+ flwr/client/rest_client/connection.py,sha256=YOE2cfgRtkpNveeg88GZTTjUscgGN8XF2RAJ-M_TQ_U,12384
85
87
  flwr/client/supernode/__init__.py,sha256=8JuFMx5B06SymNWZ9ndvUimD0mopq0Yp2RdQaUANsw8,937
86
88
  flwr/client/supernode/app.py,sha256=mbSs612GCpRsv6xIi_TD_V2Gi7aYBdpo_e4nKs8kAvI,15910
87
89
  flwr/client/typing.py,sha256=dxoTBnTMfqXr5J7G3y-uNjqxYCddvxhu89spfj4Lm2U,1048
88
90
  flwr/common/__init__.py,sha256=4cBLNNnNTwHDnL_HCxhU5ILCSZ6fYh3A_aMBtlvHTVw,3721
89
91
  flwr/common/address.py,sha256=wRu1Luezx1PWadwV9OA_KNko01oVvbRnPqfzaDn8QOk,1882
90
- flwr/common/config.py,sha256=soJEX0bo3gcKpYlZBzu4NGiNVmwF0tEvqSnwWQsu3pw,6548
92
+ flwr/common/config.py,sha256=-aRW4UPv4T215gjwmDNQ1p9S-9T92_DlQ9SY39roDDU,6717
91
93
  flwr/common/constant.py,sha256=1XxuRezsr9fl3xvQNPR2kyFkwNeG_f5vZayv0PFh0kY,3012
92
94
  flwr/common/context.py,sha256=5Bd9RCrhLkYZOVR7vr97OVhzVBHQkS1fUsYiIKTwpxU,2239
93
95
  flwr/common/date.py,sha256=OcQuwpb2HxcblTqYm6H223ufop5UZw5N_fzalbpOVzY,891
@@ -106,9 +108,9 @@ flwr/common/record/configsrecord.py,sha256=VKeFEYa6cneyStqQlUOaKj12by5ZI_NXYR25L
106
108
  flwr/common/record/conversion_utils.py,sha256=n3I3SI2P6hUjyxbWNc0QAch-SEhfMK6Hm-UUaplAlUc,1393
107
109
  flwr/common/record/metricsrecord.py,sha256=Yv99oRa3LzFgSfwl903S8sB8rAgr3Sv6i6ovW7pdHsA,3923
108
110
  flwr/common/record/parametersrecord.py,sha256=2sgjxsolFBUfnYYstIciOir0HAs95lqWY3pdcsYvsso,4838
109
- flwr/common/record/recordset.py,sha256=wOonAziLalABXzCHF5ih-QzXsKXZAKCls3HhMFJCWkY,5056
110
- flwr/common/record/typeddict.py,sha256=2NW8JF27p1uNWaqDbJ7bMkItA5x4ygYT8aHrf8NaqnE,3879
111
- flwr/common/recordset_compat.py,sha256=SYuJJmsfsWUP60nXREviyl78nOojKfMekALjzeZLsP8,14009
111
+ flwr/common/record/recordset.py,sha256=CKEfjk-mTIlg_aFpTfDyXme1UAXhqz2Ei-h55xxzRiY,5058
112
+ flwr/common/record/typeddict.py,sha256=M9zU1vjuPmKRxA0uiP2v752TB3D2VfrJzKNWx-EFviw,3016
113
+ flwr/common/recordset_compat.py,sha256=5prvfx8mT4ZWIEU4Gmz2wzCbO4yPjQ3ONr20uKt3UM0,13973
112
114
  flwr/common/retry_invoker.py,sha256=dQY5fPIKhy9OiFswZhLxA9fB455u-DYCvDVcFJmrPDk,11707
113
115
  flwr/common/secure_aggregation/__init__.py,sha256=erPnTWdOfMH0K0HQTmj5foDJ6t3iYcExy2aACy8iZNQ,731
114
116
  flwr/common/secure_aggregation/crypto/__init__.py,sha256=nlHesCWy8xxE5s6qHWnauCtyClcMQ2K0CEXAHakY5n0,738
@@ -118,9 +120,9 @@ flwr/common/secure_aggregation/ndarrays_arithmetic.py,sha256=7Y0WnWcYi8UWZEKnUb4
118
120
  flwr/common/secure_aggregation/quantization.py,sha256=1obYr9qneaI8r-A0F_pghrPNG9FcAwM5svSLbzMw6C4,2310
119
121
  flwr/common/secure_aggregation/secaggplus_constants.py,sha256=9MF-oQh62uD7rt9VeNB-rHf2gBLd5GL3S9OejCxmILY,2183
120
122
  flwr/common/secure_aggregation/secaggplus_utils.py,sha256=3VssKgYF7HQIkSpROnEUoYWVt47p12PE_Rj4nYqqg04,3221
121
- flwr/common/serde.py,sha256=VgfTKU4B0g7qJkOf7w8uaHlVysEd_NrwzQCNDfkfRHU,28466
123
+ flwr/common/serde.py,sha256=SIKZa-TNCSWVtqx8L3aUkylN6gxK-IwXlgoukiYwYyc,29209
122
124
  flwr/common/telemetry.py,sha256=nSjJHDitPhzB2qUl6LeSMT9Zld5lIk9uW98RpxQwiZw,8366
123
- flwr/common/typing.py,sha256=VJvdhrVxu6a1y2lgZSjHdSgYqXQ6Cma4t_owI3EhOes,4986
125
+ flwr/common/typing.py,sha256=rGabiSkjFvGIHwmhDqtuu-LBvz7LVSj1vyMlNtA7VA0,5004
124
126
  flwr/common/version.py,sha256=W1ntylR04xkCP6zeSet6sRtBn7P1cje2lOqBJgYBjJY,1349
125
127
  flwr/proto/__init__.py,sha256=hbY7JYakwZwCkYgCNlmHdc8rtvfoJbAZLalMdc--CGc,683
126
128
  flwr/proto/clientappio_pb2.py,sha256=018Lj2HBXmEeq6KsmqFVVx2DGO2nDQqTA6YHEPjCTbc,3242
@@ -139,8 +141,8 @@ flwr/proto/error_pb2.py,sha256=LarjKL90LbwkXKlhzNrDssgl4DXcvIPve8NVCXHpsKA,1084
139
141
  flwr/proto/error_pb2.pyi,sha256=ZNH4HhJTU_KfMXlyCeg8FwU-fcUYxTqEmoJPtWtHikc,734
140
142
  flwr/proto/error_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
141
143
  flwr/proto/error_pb2_grpc.pyi,sha256=ff2TSiLVnG6IVQcTGzb2DIH3XRSoAvAo_RMcvbMFyc0,76
142
- flwr/proto/exec_pb2.py,sha256=7b1JUZgt04lBxdXpnd3WOOwmY2egUausm4G1wbmXFPs,3120
143
- flwr/proto/exec_pb2.pyi,sha256=R9xCAUxkLvuXcvQfhNGZ5Qy8qiUCdjN-gp3Iy5BmkNo,4110
144
+ flwr/proto/exec_pb2.py,sha256=iqAvJ03atrueo5duiRHD_peegJiLsrWuNnVU8lA2PXw,3231
145
+ flwr/proto/exec_pb2.pyi,sha256=5y6L3xFkAuCfLTn2pVIHQAlXp17YcTTq8WVDS7_MPl4,4273
144
146
  flwr/proto/exec_pb2_grpc.py,sha256=faAN19XEMP8GTKrcIU6jvlWkN44n2KiUsZh_OG0sYcg,4072
145
147
  flwr/proto/exec_pb2_grpc.pyi,sha256=VrFhT1Um3Nb8UC2YqnR9GIiM-Yyx0FqaxVOWljh-G_w,1208
146
148
  flwr/proto/fab_pb2.py,sha256=ztC3HnD5e-bYpp7lxrlxkdc30haAlBHswTnt4X5G1eg,1432
@@ -155,8 +157,8 @@ flwr/proto/grpcadapter_pb2.py,sha256=bb8mW09XzNCpMdr1KuYQkefPFWR8lc8y1uL6Uk0TtsM
155
157
  flwr/proto/grpcadapter_pb2.pyi,sha256=AR77gDsF6f8zqSIQp3877DUd7S8lP95lFak5Ir_WPkw,1716
156
158
  flwr/proto/grpcadapter_pb2_grpc.py,sha256=rRNuNES5nBugUZWfeA8oAy8dMHgzqU_PF1srTseo3b8,2634
157
159
  flwr/proto/grpcadapter_pb2_grpc.pyi,sha256=AgA9Qo_lnANb9SNuPzbZGAxupau-xcqYawZz6vqf-24,735
158
- flwr/proto/message_pb2.py,sha256=7sP1PEOoZnfynNcM_KmlHn2bFM8eZpHYJbzkCSoAT2E,3110
159
- flwr/proto/message_pb2.pyi,sha256=0IF_l1-fgQOs2uBNqu6PszzfulRQppdUIvDA-T6XeJ8,5475
160
+ flwr/proto/message_pb2.py,sha256=6fiapmq94u3T9wqW7tGsWVuyLanW31VAg0-rD2Hfk8U,3151
161
+ flwr/proto/message_pb2.pyi,sha256=_J9NjZa7Pr-kSO7-GGOL5EvIXQx5mQD04iVIvnVHBMU,5617
160
162
  flwr/proto/message_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
161
163
  flwr/proto/message_pb2_grpc.pyi,sha256=ff2TSiLVnG6IVQcTGzb2DIH3XRSoAvAo_RMcvbMFyc0,76
162
164
  flwr/proto/node_pb2.py,sha256=1zfXEvgGObglIcaVb4SLFmOcHZvA8eHzEtMFM5A6FYY,1081
@@ -181,7 +183,7 @@ flwr/proto/transport_pb2_grpc.py,sha256=vLN3EHtx2aEEMCO4f1Upu-l27BPzd3-5pV-u8wPc
181
183
  flwr/proto/transport_pb2_grpc.pyi,sha256=AGXf8RiIiW2J5IKMlm_3qT3AzcDa4F3P5IqUjve_esA,766
182
184
  flwr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
183
185
  flwr/server/__init__.py,sha256=BxzPhvouvWFGi7CFpI5b4EeVR9XDqbK7Ndqg24EL_Rw,1679
184
- flwr/server/app.py,sha256=WABxljYzn9mEaHvnXXBceVkBx2V6lmlFGpl5f0Uue08,23736
186
+ flwr/server/app.py,sha256=haNCyIL0v6jCwZemjzok_LB8zWxL_T9J4dOZ7AY2x78,24153
185
187
  flwr/server/client_manager.py,sha256=T8UDSRJBVD3fyIDI7NTAA-NA7GPrMNNgH2OAF54RRxE,6127
186
188
  flwr/server/client_proxy.py,sha256=4G-oTwhb45sfWLx2uZdcXD98IZwdTS6F88xe3akCdUg,2399
187
189
  flwr/server/compat/__init__.py,sha256=VxnJtJyOjNFQXMNi9hIuzNlZM5n0Hj1p3aq_Pm2udw4,892
@@ -192,7 +194,7 @@ flwr/server/compat/legacy_context.py,sha256=wBzBcfV6YO6IQGriM_FdJ5XZfiBBEEJdS_Od
192
194
  flwr/server/criterion.py,sha256=ypbAexbztzGUxNen9RCHF91QeqiEQix4t4Ih3E-42MM,1061
193
195
  flwr/server/driver/__init__.py,sha256=bikRv6CjTwSvYh7tf10gziU5o2YotOWhhftz2tr3KDc,886
194
196
  flwr/server/driver/driver.py,sha256=NT_yaeit7_kZEIsCEqOWPID1GrVD3ywH4xZ2wtIh5lM,5217
195
- flwr/server/driver/grpc_driver.py,sha256=4LMLDXjMU1VdHsj9nyqFIF71GWVsUR85fsO6biWMHRU,9710
197
+ flwr/server/driver/grpc_driver.py,sha256=EbnOtrTR4cmu2ZhmPqKEmOHAhNQD1fd0hAd_xN1MePQ,9749
196
198
  flwr/server/driver/inmemory_driver.py,sha256=RcK94_NtjGZ4aZDIscnU7A3Uv1u8jGx29-xcbjQvZTM,6444
197
199
  flwr/server/history.py,sha256=bBOHKyX1eQONIsUx4EUU-UnAk1i0EbEl8ioyMq_UWQ8,5063
198
200
  flwr/server/run_serverapp.py,sha256=8HCGSVaNYd2cnd_j-JpVTRH7Cg8_U7ATZUejiMmJe0o,8894
@@ -226,11 +228,12 @@ flwr/server/strategy/qfedavg.py,sha256=LZ-zVHRFPVMeeROgDHfMshHaAKaq9Hu9Ev9blREQm
226
228
  flwr/server/strategy/strategy.py,sha256=g6VoIFogEviRub6G4QsKdIp6M_Ek6GhBhqcdNx5ueUk,7543
227
229
  flwr/server/superlink/__init__.py,sha256=8tHYCfodUlRD8PCP9fHgvu8cz5N31A2QoRVL0jDJ15E,707
228
230
  flwr/server/superlink/driver/__init__.py,sha256=_JaRW-FdyikHc7souUrnk3mwTGViraEJCeUBY_M_ocs,712
229
- flwr/server/superlink/driver/driver_grpc.py,sha256=wMqYVeDwIc_FEfQPbCzd9p1YyIcjwiFUMX4QZjGSx5k,1932
230
- flwr/server/superlink/driver/driver_servicer.py,sha256=UWJKw7-eVILw97MdTGjZt9yV0noXnrzcO72bYZ0b2kM,6010
231
+ flwr/server/superlink/driver/driver_grpc.py,sha256=XZ8veSfKStieUgkRntvOw10F0fmHytw_lr7CDtgzLpY,2055
232
+ flwr/server/superlink/driver/driver_servicer.py,sha256=k_3IuteN0zRDz-xsM7RImHn7JODXrcvTRQdobVH7rCw,6581
231
233
  flwr/server/superlink/ffs/__init__.py,sha256=FAY-zShcfPmOxosok2QyT6hTNMNctG8cH9s_nIl8jkI,840
232
234
  flwr/server/superlink/ffs/disk_ffs.py,sha256=ZsBCQVpuScm5QNul_XWlXU65XEaOGbH9ULIls6h7p1A,3213
233
235
  flwr/server/superlink/ffs/ffs.py,sha256=AVl1AM7ZfVTPOIEQJuMR-aXUUd2foSkRe49NWoPecL8,2384
236
+ flwr/server/superlink/ffs/ffs_factory.py,sha256=N_eMuUZggotdGiDQ5r_Tf21xsu_ob0e3jyM6ag7d3kk,1490
234
237
  flwr/server/superlink/fleet/__init__.py,sha256=76od-HhYjOUoZFLFDFCFnNHI4JLAmaXQEAyp7LWlQpc,711
235
238
  flwr/server/superlink/fleet/grpc_adapter/__init__.py,sha256=spBQQJeYz8zPOBOfyMLv87kqWPASGB73AymcLXdFaYA,742
236
239
  flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py,sha256=K4LkOsVzIdsR7Py_9cfo6CR-bDocpP15ktHTc2UnJ9k,4957
@@ -252,9 +255,9 @@ flwr/server/superlink/fleet/vce/backend/backend.py,sha256=KL0eHScWr_YfP2eY3VP8_O
252
255
  flwr/server/superlink/fleet/vce/backend/raybackend.py,sha256=6-CjUTv1cFnAb53cZzEYCuVLrVu60tPczTo8meqeqbk,6289
253
256
  flwr/server/superlink/fleet/vce/vce_api.py,sha256=AeJBEZIC3V54Wg4bzBJa0aO4IXUNiRzRDRv-EKhqtpQ,12750
254
257
  flwr/server/superlink/state/__init__.py,sha256=Gj2OTFLXvA-mAjBvwuKDM3rDrVaQPcIoybSa2uskMTE,1003
255
- flwr/server/superlink/state/in_memory_state.py,sha256=LnKlnXe9JjVHb5_XOo6eD1RQhlCvJVKgz_CkXrMz8DY,13069
256
- flwr/server/superlink/state/sqlite_state.py,sha256=LdLnHtF8C-1L1IAglfZPqIuKa782Qo7qAYzTXMdMYGM,29052
257
- flwr/server/superlink/state/state.py,sha256=juv9a8pQLxylfcZcXRaDBLG5gFG4MXytIcG6NlZTn4E,8117
258
+ flwr/server/superlink/state/in_memory_state.py,sha256=XMcT5WvKPOrFOuKcByr5BRFacX4aR2n9bRkABUuPg-M,13206
259
+ flwr/server/superlink/state/sqlite_state.py,sha256=N8eOLZUveJOzdzL31-hXRYOMqV_-w75S1InyDenaDWU,29420
260
+ flwr/server/superlink/state/state.py,sha256=peHACUjhUF4gfZ5icMsBGjQNa6A6rvku9a_mziRJW40,8154
258
261
  flwr/server/superlink/state/state_factory.py,sha256=Fo8pBQ1WWrVJK5TOEPZ_zgJE69_mfTGjTO6czh6571o,2021
259
262
  flwr/server/superlink/state/utils.py,sha256=155ngcaSePy7nD8X4LHgpuVok6fcH5_CPNRiFAbLWDA,2407
260
263
  flwr/server/typing.py,sha256=5kaRLZuxTEse9A0g7aVna2VhYxU3wTq1f3d3mtw7kXs,1019
@@ -273,16 +276,16 @@ flwr/simulation/ray_transport/__init__.py,sha256=wzcEEwUUlulnXsg6raCA1nGpP3LlAQD
273
276
  flwr/simulation/ray_transport/ray_actor.py,sha256=3j0HgzjrlYjnzdTRy8aA4Nf6VoUvxi1hGRQkGSU5z6c,19020
274
277
  flwr/simulation/ray_transport/ray_client_proxy.py,sha256=0abIsU0VBk9rNJZOKHIyzYGy3ZnWBgqYocX_oct1EP0,7307
275
278
  flwr/simulation/ray_transport/utils.py,sha256=TYdtfg1P9VfTdLMOJlifInGpxWHYs9UfUqIv2wfkRLA,2392
276
- flwr/simulation/run_simulation.py,sha256=BPW_zn5xuGlOHKZDvl8J-IuwH7E8ZZAIR2zcXNjsLMo,23391
279
+ flwr/simulation/run_simulation.py,sha256=pKkyrAWzxf05j8AoXh2eYyYeV6PbAXtq1egv2l5JM3I,23447
277
280
  flwr/superexec/__init__.py,sha256=9h94ogLxi6eJ3bUuJYq3E3pApThSabTPiSmPAGlTkHE,800
278
281
  flwr/superexec/app.py,sha256=bmYl8zABnWka9WhRQxX4p1YAI76cYG655dP09ro-V0o,6485
279
282
  flwr/superexec/deployment.py,sha256=B--96bAvyotQX-c4-umXIe50uCw4HVjD79rXCQtSUH4,6357
280
283
  flwr/superexec/exec_grpc.py,sha256=PhqGoZEpTMxSQmUSV8Wgtzb1Za_pHJ-adZqo5RYnDyE,1942
281
- flwr/superexec/exec_servicer.py,sha256=fxQAKfgmQRSnYq5anjryfGeRbsZrNFEkuiNcTZhRwiE,2320
284
+ flwr/superexec/exec_servicer.py,sha256=jl0aKVjm0PLQABcTL5c3jdSIzb0Z6hpVOtrAn4Ob7ts,2323
282
285
  flwr/superexec/executor.py,sha256=k_adivto6R2U82DADOHNvdtobehBYreRek1gOEBIQnQ,2318
283
286
  flwr/superexec/simulation.py,sha256=lfdClQYSAIMHe43aJ0Pk-kBw_xoV09LsIMfHo2eo-Ck,6775
284
- flwr_nightly-1.11.0.dev20240813.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
285
- flwr_nightly-1.11.0.dev20240813.dist-info/METADATA,sha256=ZfHPCwurXVhKgVFFPJ3XAwtN6e45R-GcF0rIlNPMhQw,15690
286
- flwr_nightly-1.11.0.dev20240813.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
287
- flwr_nightly-1.11.0.dev20240813.dist-info/entry_points.txt,sha256=KqQQGNjnr-2IT-bheOkO9yM4Gv5BtjaBgYKRkp-NhVg,388
288
- flwr_nightly-1.11.0.dev20240813.dist-info/RECORD,,
287
+ flwr_nightly-1.11.0.dev20240815.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
288
+ flwr_nightly-1.11.0.dev20240815.dist-info/METADATA,sha256=3zvVGv43ZwKbuaVf2LIn5jvzKs0k-30AY2WBWRLrFWA,15690
289
+ flwr_nightly-1.11.0.dev20240815.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
290
+ flwr_nightly-1.11.0.dev20240815.dist-info/entry_points.txt,sha256=KqQQGNjnr-2IT-bheOkO9yM4Gv5BtjaBgYKRkp-NhVg,388
291
+ flwr_nightly-1.11.0.dev20240815.dist-info/RECORD,,