flwr-nightly 1.13.0.dev20241105__py3-none-any.whl → 1.13.0.dev20241107__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 (42) hide show
  1. flwr/cli/run/run.py +16 -5
  2. flwr/client/app.py +10 -6
  3. flwr/client/nodestate/__init__.py +25 -0
  4. flwr/client/nodestate/in_memory_nodestate.py +38 -0
  5. flwr/client/nodestate/nodestate.py +30 -0
  6. flwr/client/nodestate/nodestate_factory.py +37 -0
  7. flwr/client/run_info_store.py +1 -0
  8. flwr/common/config.py +10 -0
  9. flwr/common/constant.py +1 -1
  10. flwr/common/context.py +9 -4
  11. flwr/common/object_ref.py +40 -33
  12. flwr/common/serde.py +2 -0
  13. flwr/proto/exec_pb2.py +14 -17
  14. flwr/proto/exec_pb2.pyi +6 -20
  15. flwr/proto/message_pb2.py +8 -8
  16. flwr/proto/message_pb2.pyi +4 -1
  17. flwr/server/app.py +140 -107
  18. flwr/server/driver/driver.py +1 -1
  19. flwr/server/driver/grpc_driver.py +2 -6
  20. flwr/server/driver/inmemory_driver.py +1 -3
  21. flwr/server/run_serverapp.py +5 -2
  22. flwr/server/serverapp/app.py +1 -1
  23. flwr/server/superlink/driver/serverappio_servicer.py +2 -0
  24. flwr/server/superlink/linkstate/in_memory_linkstate.py +15 -16
  25. flwr/server/superlink/linkstate/linkstate.py +18 -1
  26. flwr/server/superlink/linkstate/sqlite_linkstate.py +41 -21
  27. flwr/server/superlink/linkstate/utils.py +14 -30
  28. flwr/server/superlink/simulation/__init__.py +15 -0
  29. flwr/server/superlink/simulation/simulationio_grpc.py +65 -0
  30. flwr/server/superlink/simulation/simulationio_servicer.py +132 -0
  31. flwr/simulation/__init__.py +2 -0
  32. flwr/simulation/run_simulation.py +4 -1
  33. flwr/simulation/simulationio_connection.py +86 -0
  34. flwr/superexec/deployment.py +8 -4
  35. flwr/superexec/exec_servicer.py +2 -2
  36. flwr/superexec/executor.py +4 -3
  37. flwr/superexec/simulation.py +8 -8
  38. {flwr_nightly-1.13.0.dev20241105.dist-info → flwr_nightly-1.13.0.dev20241107.dist-info}/METADATA +1 -1
  39. {flwr_nightly-1.13.0.dev20241105.dist-info → flwr_nightly-1.13.0.dev20241107.dist-info}/RECORD +42 -34
  40. {flwr_nightly-1.13.0.dev20241105.dist-info → flwr_nightly-1.13.0.dev20241107.dist-info}/LICENSE +0 -0
  41. {flwr_nightly-1.13.0.dev20241105.dist-info → flwr_nightly-1.13.0.dev20241107.dist-info}/WHEEL +0 -0
  42. {flwr_nightly-1.13.0.dev20241105.dist-info → flwr_nightly-1.13.0.dev20241107.dist-info}/entry_points.txt +0 -0
@@ -15,18 +15,15 @@
15
15
  """Utility functions for State."""
16
16
 
17
17
 
18
- import time
19
- from logging import ERROR
20
18
  from os import urandom
21
- from uuid import uuid4
22
19
 
23
- from flwr.common import Context, log, serde
24
- from flwr.common.constant import ErrorCode, Status, SubStatus
20
+ from flwr.common import ConfigsRecord, Context, serde
21
+ from flwr.common.constant import Status, SubStatus
25
22
  from flwr.common.typing import RunStatus
26
- from flwr.proto.error_pb2 import Error # pylint: disable=E0611
27
23
  from flwr.proto.message_pb2 import Context as ProtoContext # pylint: disable=E0611
28
- from flwr.proto.node_pb2 import Node # pylint: disable=E0611
29
- from flwr.proto.task_pb2 import Task, TaskIns, TaskRes # pylint: disable=E0611
24
+
25
+ # pylint: disable=E0611
26
+ from flwr.proto.recordset_pb2 import ConfigsRecord as ProtoConfigsRecord
30
27
 
31
28
  NODE_UNAVAILABLE_ERROR_REASON = (
32
29
  "Error: Node Unavailable - The destination node is currently unavailable. "
@@ -146,28 +143,15 @@ def context_from_bytes(context_bytes: bytes) -> Context:
146
143
  return serde.context_from_proto(ProtoContext.FromString(context_bytes))
147
144
 
148
145
 
149
- def make_node_unavailable_taskres(ref_taskins: TaskIns) -> TaskRes:
150
- """Generate a TaskRes with a node unavailable error from a TaskIns."""
151
- current_time = time.time()
152
- ttl = ref_taskins.task.ttl - (current_time - ref_taskins.task.created_at)
153
- if ttl < 0:
154
- log(ERROR, "Creating TaskRes for TaskIns that exceeds its TTL.")
155
- ttl = 0
156
- return TaskRes(
157
- task_id=str(uuid4()),
158
- group_id=ref_taskins.group_id,
159
- run_id=ref_taskins.run_id,
160
- task=Task(
161
- producer=Node(node_id=ref_taskins.task.consumer.node_id, anonymous=False),
162
- consumer=Node(node_id=ref_taskins.task.producer.node_id, anonymous=False),
163
- created_at=current_time,
164
- ttl=ttl,
165
- ancestry=[ref_taskins.task_id],
166
- task_type=ref_taskins.task.task_type,
167
- error=Error(
168
- code=ErrorCode.NODE_UNAVAILABLE, reason=NODE_UNAVAILABLE_ERROR_REASON
169
- ),
170
- ),
146
+ def configsrecord_to_bytes(configs_record: ConfigsRecord) -> bytes:
147
+ """Serialize a `ConfigsRecord` to bytes."""
148
+ return serde.configs_record_to_proto(configs_record).SerializeToString()
149
+
150
+
151
+ def configsrecord_from_bytes(configsrecord_bytes: bytes) -> ConfigsRecord:
152
+ """Deserialize `ConfigsRecord` from bytes."""
153
+ return serde.configs_record_from_proto(
154
+ ProtoConfigsRecord.FromString(configsrecord_bytes)
171
155
  )
172
156
 
173
157
 
@@ -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 SimulationIo service."""
@@ -0,0 +1,65 @@
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
+ """SimulationIo gRPC API."""
16
+
17
+
18
+ from logging import INFO
19
+ from typing import Optional
20
+
21
+ import grpc
22
+
23
+ from flwr.common import GRPC_MAX_MESSAGE_LENGTH
24
+ from flwr.common.logger import log
25
+ from flwr.proto.simulationio_pb2_grpc import ( # pylint: disable=E0611
26
+ add_SimulationIoServicer_to_server,
27
+ )
28
+ from flwr.server.superlink.ffs.ffs_factory import FfsFactory
29
+ from flwr.server.superlink.linkstate import LinkStateFactory
30
+
31
+ from ..fleet.grpc_bidi.grpc_server import generic_create_grpc_server
32
+ from .simulationio_servicer import SimulationIoServicer
33
+
34
+
35
+ def run_simulationio_api_grpc(
36
+ address: str,
37
+ state_factory: LinkStateFactory,
38
+ ffs_factory: FfsFactory,
39
+ certificates: Optional[tuple[bytes, bytes, bytes]],
40
+ ) -> grpc.Server:
41
+ """Run SimulationIo API (gRPC, request-response)."""
42
+ # Create SimulationIo API gRPC server
43
+ simulationio_servicer: grpc.Server = SimulationIoServicer(
44
+ state_factory=state_factory,
45
+ ffs_factory=ffs_factory,
46
+ )
47
+ simulationio_add_servicer_to_server_fn = add_SimulationIoServicer_to_server
48
+ simulationio_grpc_server = generic_create_grpc_server(
49
+ servicer_and_add_fn=(
50
+ simulationio_servicer,
51
+ simulationio_add_servicer_to_server_fn,
52
+ ),
53
+ server_address=address,
54
+ max_message_length=GRPC_MAX_MESSAGE_LENGTH,
55
+ certificates=certificates,
56
+ )
57
+
58
+ log(
59
+ INFO,
60
+ "Flower Simulation Engine: Starting SimulationIo API on %s",
61
+ address,
62
+ )
63
+ simulationio_grpc_server.start()
64
+
65
+ return simulationio_grpc_server
@@ -0,0 +1,132 @@
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
+ """SimulationIo API servicer."""
16
+
17
+ import threading
18
+ from logging import DEBUG, INFO
19
+
20
+ import grpc
21
+ from grpc import ServicerContext
22
+
23
+ from flwr.common.constant import Status
24
+ from flwr.common.logger import log
25
+ from flwr.common.serde import (
26
+ context_from_proto,
27
+ context_to_proto,
28
+ fab_to_proto,
29
+ run_status_from_proto,
30
+ run_to_proto,
31
+ )
32
+ from flwr.common.typing import Fab, RunStatus
33
+ from flwr.proto import simulationio_pb2_grpc
34
+ from flwr.proto.log_pb2 import ( # pylint: disable=E0611
35
+ PushLogsRequest,
36
+ PushLogsResponse,
37
+ )
38
+ from flwr.proto.run_pb2 import ( # pylint: disable=E0611
39
+ UpdateRunStatusRequest,
40
+ UpdateRunStatusResponse,
41
+ )
42
+ from flwr.proto.simulationio_pb2 import ( # pylint: disable=E0611
43
+ PullSimulationInputsRequest,
44
+ PullSimulationInputsResponse,
45
+ PushSimulationOutputsRequest,
46
+ PushSimulationOutputsResponse,
47
+ )
48
+ from flwr.server.superlink.ffs.ffs_factory import FfsFactory
49
+ from flwr.server.superlink.linkstate import LinkStateFactory
50
+
51
+
52
+ class SimulationIoServicer(simulationio_pb2_grpc.SimulationIoServicer):
53
+ """SimulationIo API servicer."""
54
+
55
+ def __init__(
56
+ self, state_factory: LinkStateFactory, ffs_factory: FfsFactory
57
+ ) -> None:
58
+ self.state_factory = state_factory
59
+ self.ffs_factory = ffs_factory
60
+ self.lock = threading.RLock()
61
+
62
+ def PullSimulationInputs(
63
+ self, request: PullSimulationInputsRequest, context: ServicerContext
64
+ ) -> PullSimulationInputsResponse:
65
+ """Pull SimultionIo process inputs."""
66
+ log(DEBUG, "SimultionIoServicer.SimultionIoInputs")
67
+ # Init access to LinkState and Ffs
68
+ state = self.state_factory.state()
69
+ ffs = self.ffs_factory.ffs()
70
+
71
+ # Lock access to LinkState, preventing obtaining the same pending run_id
72
+ with self.lock:
73
+ # Attempt getting the run_id of a pending run
74
+ run_id = state.get_pending_run_id()
75
+ # If there's no pending run, return an empty response
76
+ if run_id is None:
77
+ return PullSimulationInputsResponse()
78
+
79
+ # Retrieve Context, Run and Fab for the run_id
80
+ serverapp_ctxt = state.get_serverapp_context(run_id)
81
+ run = state.get_run(run_id)
82
+ fab = None
83
+ if run and run.fab_hash:
84
+ if result := ffs.get(run.fab_hash):
85
+ fab = Fab(run.fab_hash, result[0])
86
+ if run and fab and serverapp_ctxt:
87
+ # Update run status to STARTING
88
+ if state.update_run_status(run_id, RunStatus(Status.STARTING, "", "")):
89
+ log(INFO, "Starting run %d", run_id)
90
+ return PullSimulationInputsResponse(
91
+ context=context_to_proto(serverapp_ctxt),
92
+ run=run_to_proto(run),
93
+ fab=fab_to_proto(fab),
94
+ )
95
+
96
+ # Raise an exception if the Run or Fab is not found,
97
+ # or if the status cannot be updated to STARTING
98
+ raise RuntimeError(f"Failed to start run {run_id}")
99
+
100
+ def PushSimulationOutputs(
101
+ self, request: PushSimulationOutputsRequest, context: ServicerContext
102
+ ) -> PushSimulationOutputsResponse:
103
+ """Push Simulation process outputs."""
104
+ log(DEBUG, "SimultionIoServicer.PushSimulationOutputs")
105
+ state = self.state_factory.state()
106
+ state.set_serverapp_context(request.run_id, context_from_proto(request.context))
107
+ return PushSimulationOutputsResponse()
108
+
109
+ def UpdateRunStatus(
110
+ self, request: UpdateRunStatusRequest, context: grpc.ServicerContext
111
+ ) -> UpdateRunStatusResponse:
112
+ """Update the status of a run."""
113
+ log(DEBUG, "SimultionIoServicer.UpdateRunStatus")
114
+ state = self.state_factory.state()
115
+
116
+ # Update the run status
117
+ state.update_run_status(
118
+ run_id=request.run_id, new_status=run_status_from_proto(request.run_status)
119
+ )
120
+ return UpdateRunStatusResponse()
121
+
122
+ def PushLogs(
123
+ self, request: PushLogsRequest, context: grpc.ServicerContext
124
+ ) -> PushLogsResponse:
125
+ """Push logs."""
126
+ log(DEBUG, "ServerAppIoServicer.PushLogs")
127
+ state = self.state_factory.state()
128
+
129
+ # Add logs to LinkState
130
+ merged_logs = "".join(request.logs)
131
+ state.add_serverapp_log(request.run_id, merged_logs)
132
+ return PushLogsResponse()
@@ -18,6 +18,7 @@
18
18
  import importlib
19
19
 
20
20
  from flwr.simulation.run_simulation import run_simulation
21
+ from flwr.simulation.simulationio_connection import SimulationIoConnection
21
22
 
22
23
  is_ray_installed = importlib.util.find_spec("ray") is not None
23
24
 
@@ -37,6 +38,7 @@ To install the necessary dependencies, install `flwr` with the `simulation` extr
37
38
 
38
39
 
39
40
  __all__ = [
41
+ "SimulationIoConnection",
40
42
  "run_simulation",
41
43
  "start_simulation",
42
44
  ]
@@ -234,6 +234,7 @@ def run_serverapp_th(
234
234
  f_stop: threading.Event,
235
235
  has_exception: threading.Event,
236
236
  enable_tf_gpu_growth: bool,
237
+ run_id: int,
237
238
  ) -> threading.Thread:
238
239
  """Run SeverApp in a thread."""
239
240
 
@@ -258,6 +259,7 @@ def run_serverapp_th(
258
259
 
259
260
  # Initialize Context
260
261
  context = Context(
262
+ run_id=run_id,
261
263
  node_id=0,
262
264
  node_config={},
263
265
  state=RecordSet(),
@@ -345,7 +347,7 @@ def _main_loop(
345
347
 
346
348
  # Initialize Driver
347
349
  driver = InMemoryDriver(state_factory=state_factory)
348
- driver.init_run(run_id=run.run_id)
350
+ driver.set_run(run_id=run.run_id)
349
351
 
350
352
  # Get and run ServerApp thread
351
353
  serverapp_th = run_serverapp_th(
@@ -357,6 +359,7 @@ def _main_loop(
357
359
  f_stop=f_stop,
358
360
  has_exception=server_app_thread_has_exception,
359
361
  enable_tf_gpu_growth=enable_tf_gpu_growth,
362
+ run_id=run.run_id,
360
363
  )
361
364
 
362
365
  # Buffer time so the `ServerApp` in separate thread is ready
@@ -0,0 +1,86 @@
1
+ # Copyright 2024 Flower Labs GmbH. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+ """Flower SimulationIo connection."""
16
+
17
+
18
+ from logging import DEBUG, WARNING
19
+ from typing import Optional, cast
20
+
21
+ import grpc
22
+
23
+ from flwr.common.constant import SIMULATIONIO_API_DEFAULT_ADDRESS
24
+ from flwr.common.grpc import create_channel
25
+ from flwr.common.logger import log
26
+ from flwr.proto.simulationio_pb2_grpc import SimulationIoStub # pylint: disable=E0611
27
+
28
+
29
+ class SimulationIoConnection:
30
+ """`SimulationIoConnection` provides an interface to the SimulationIo API.
31
+
32
+ Parameters
33
+ ----------
34
+ simulationio_service_address : str (default: "[::]:9094")
35
+ The address (URL, IPv6, IPv4) of the SuperLink SimulationIo API service.
36
+ root_certificates : Optional[bytes] (default: None)
37
+ The PEM-encoded root certificates as a byte string.
38
+ If provided, a secure connection using the certificates will be
39
+ established to an SSL-enabled Flower server.
40
+ """
41
+
42
+ def __init__( # pylint: disable=too-many-arguments
43
+ self,
44
+ simulationio_service_address: str = SIMULATIONIO_API_DEFAULT_ADDRESS,
45
+ root_certificates: Optional[bytes] = None,
46
+ ) -> None:
47
+ self._addr = simulationio_service_address
48
+ self._cert = root_certificates
49
+ self._grpc_stub: Optional[SimulationIoStub] = None
50
+ self._channel: Optional[grpc.Channel] = None
51
+
52
+ @property
53
+ def _is_connected(self) -> bool:
54
+ """Check if connected to the SimulationIo API server."""
55
+ return self._channel is not None
56
+
57
+ @property
58
+ def _stub(self) -> SimulationIoStub:
59
+ """SimulationIo stub."""
60
+ if not self._is_connected:
61
+ self._connect()
62
+ return cast(SimulationIoStub, self._grpc_stub)
63
+
64
+ def _connect(self) -> None:
65
+ """Connect to the SimulationIo API."""
66
+ if self._is_connected:
67
+ log(WARNING, "Already connected")
68
+ return
69
+ self._channel = create_channel(
70
+ server_address=self._addr,
71
+ insecure=(self._cert is None),
72
+ root_certificates=self._cert,
73
+ )
74
+ self._grpc_stub = SimulationIoStub(self._channel)
75
+ log(DEBUG, "[SimulationIO] Connected to %s", self._addr)
76
+
77
+ def _disconnect(self) -> None:
78
+ """Disconnect from the SimulationIo API."""
79
+ if not self._is_connected:
80
+ log(DEBUG, "Already disconnected")
81
+ return
82
+ channel: grpc.Channel = self._channel
83
+ self._channel = None
84
+ self._grpc_stub = None
85
+ channel.close()
86
+ log(DEBUG, "[SimulationIO] Disconnected")
@@ -21,7 +21,7 @@ from typing import Optional
21
21
 
22
22
  from typing_extensions import override
23
23
 
24
- from flwr.common import Context, RecordSet
24
+ from flwr.common import ConfigsRecord, Context, RecordSet
25
25
  from flwr.common.constant import SERVERAPPIO_API_DEFAULT_ADDRESS, Status, SubStatus
26
26
  from flwr.common.logger import log
27
27
  from flwr.common.typing import Fab, RunStatus, UserConfig
@@ -133,13 +133,17 @@ class DeploymentEngine(Executor):
133
133
  f"FAB ({fab.hash_str}) hash from request doesn't match contents"
134
134
  )
135
135
 
136
- run_id = self.linkstate.create_run(None, None, fab_hash, override_config)
136
+ run_id = self.linkstate.create_run(
137
+ None, None, fab_hash, override_config, ConfigsRecord()
138
+ )
137
139
  return run_id
138
140
 
139
141
  def _create_context(self, run_id: int) -> None:
140
142
  """Register a Context for a Run."""
141
143
  # Create an empty context for the Run
142
- context = Context(node_id=0, node_config={}, state=RecordSet(), run_config={})
144
+ context = Context(
145
+ run_id=run_id, node_id=0, node_config={}, state=RecordSet(), run_config={}
146
+ )
143
147
 
144
148
  # Register the context at the LinkState
145
149
  self.linkstate.set_serverapp_context(run_id=run_id, context=context)
@@ -149,7 +153,7 @@ class DeploymentEngine(Executor):
149
153
  self,
150
154
  fab_file: bytes,
151
155
  override_config: UserConfig,
152
- federation_config: UserConfig,
156
+ federation_options: ConfigsRecord,
153
157
  ) -> Optional[int]:
154
158
  """Start run using the Flower Deployment Engine."""
155
159
  run_id = None
@@ -24,7 +24,7 @@ import grpc
24
24
 
25
25
  from flwr.common.constant import LOG_STREAM_INTERVAL, Status
26
26
  from flwr.common.logger import log
27
- from flwr.common.serde import user_config_from_proto
27
+ from flwr.common.serde import configs_record_from_proto, user_config_from_proto
28
28
  from flwr.proto import exec_pb2_grpc # pylint: disable=E0611
29
29
  from flwr.proto.exec_pb2 import ( # pylint: disable=E0611
30
30
  StartRunRequest,
@@ -61,7 +61,7 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
61
61
  run_id = self.executor.start_run(
62
62
  request.fab.content,
63
63
  user_config_from_proto(request.override_config),
64
- user_config_from_proto(request.federation_config),
64
+ configs_record_from_proto(request.federation_options),
65
65
  )
66
66
 
67
67
  if run_id is None:
@@ -19,6 +19,7 @@ from dataclasses import dataclass, field
19
19
  from subprocess import Popen
20
20
  from typing import Optional
21
21
 
22
+ from flwr.common import ConfigsRecord
22
23
  from flwr.common.typing import UserConfig
23
24
  from flwr.server.superlink.ffs.ffs_factory import FfsFactory
24
25
  from flwr.server.superlink.linkstate import LinkStateFactory
@@ -71,7 +72,7 @@ class Executor(ABC):
71
72
  self,
72
73
  fab_file: bytes,
73
74
  override_config: UserConfig,
74
- federation_config: UserConfig,
75
+ federation_options: ConfigsRecord,
75
76
  ) -> Optional[int]:
76
77
  """Start a run using the given Flower FAB ID and version.
77
78
 
@@ -84,8 +85,8 @@ class Executor(ABC):
84
85
  The Flower App Bundle file bytes.
85
86
  override_config: UserConfig
86
87
  The config overrides dict sent by the user (using `flwr run`).
87
- federation_config: UserConfig
88
- The federation options dict sent by the user (using `flwr run`).
88
+ federation_options: ConfigsRecord
89
+ The federation options sent by the user (using `flwr run`).
89
90
 
90
91
  Returns
91
92
  -------
@@ -25,6 +25,7 @@ from typing_extensions import override
25
25
 
26
26
  from flwr.cli.config_utils import load_and_validate
27
27
  from flwr.cli.install import install_from_fab
28
+ from flwr.common import ConfigsRecord
28
29
  from flwr.common.config import unflatten_dict
29
30
  from flwr.common.constant import RUN_ID_NUM_BYTES
30
31
  from flwr.common.logger import log
@@ -124,7 +125,7 @@ class SimulationEngine(Executor):
124
125
  self,
125
126
  fab_file: bytes,
126
127
  override_config: UserConfig,
127
- federation_config: UserConfig,
128
+ federation_options: ConfigsRecord,
128
129
  ) -> Optional[int]:
129
130
  """Start run using the Flower Simulation Engine."""
130
131
  if self.num_supernodes is None:
@@ -163,14 +164,13 @@ class SimulationEngine(Executor):
163
164
  "Config extracted from FAB's pyproject.toml is not valid"
164
165
  )
165
166
 
166
- # Flatten federated config
167
- federation_config_flat = unflatten_dict(federation_config)
167
+ # Unflatten underlaying dict
168
+ fed_opt = unflatten_dict({**federation_options})
168
169
 
169
- num_supernodes = federation_config_flat.get(
170
- "num-supernodes", self.num_supernodes
171
- )
172
- backend_cfg = federation_config_flat.get("backend", {})
173
- verbose: Optional[bool] = federation_config_flat.get("verbose")
170
+ # Read data
171
+ num_supernodes = fed_opt.get("num-supernodes", self.num_supernodes)
172
+ backend_cfg = fed_opt.get("backend", {})
173
+ verbose: Optional[bool] = fed_opt.get("verbose")
174
174
 
175
175
  # In Simulation there is no SuperLink, still we create a run_id
176
176
  run_id = generate_rand_int_from_bytes(RUN_ID_NUM_BYTES)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: flwr-nightly
3
- Version: 1.13.0.dev20241105
3
+ Version: 1.13.0.dev20241107
4
4
  Summary: Flower: A Friendly Federated Learning Framework
5
5
  Home-page: https://flower.ai
6
6
  License: Apache-2.0