flwr-nightly 1.13.0.dev20241023__py3-none-any.whl → 1.13.0.dev20241024__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of flwr-nightly might be problematic. Click here for more details.

flwr/proto/driver_pb2.py CHANGED
@@ -13,30 +13,39 @@ _sym_db = _symbol_database.Default()
13
13
 
14
14
 
15
15
  from flwr.proto import node_pb2 as flwr_dot_proto_dot_node__pb2
16
+ from flwr.proto import message_pb2 as flwr_dot_proto_dot_message__pb2
16
17
  from flwr.proto import task_pb2 as flwr_dot_proto_dot_task__pb2
17
18
  from flwr.proto import run_pb2 as flwr_dot_proto_dot_run__pb2
18
19
  from flwr.proto import fab_pb2 as flwr_dot_proto_dot_fab__pb2
19
20
 
20
21
 
21
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17\x66lwr/proto/driver.proto\x12\nflwr.proto\x1a\x15\x66lwr/proto/node.proto\x1a\x15\x66lwr/proto/task.proto\x1a\x14\x66lwr/proto/run.proto\x1a\x14\x66lwr/proto/fab.proto\"!\n\x0fGetNodesRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\"3\n\x10GetNodesResponse\x12\x1f\n\x05nodes\x18\x01 \x03(\x0b\x32\x10.flwr.proto.Node\"@\n\x12PushTaskInsRequest\x12*\n\rtask_ins_list\x18\x01 \x03(\x0b\x32\x13.flwr.proto.TaskIns\"\'\n\x13PushTaskInsResponse\x12\x10\n\x08task_ids\x18\x02 \x03(\t\"F\n\x12PullTaskResRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12\x10\n\x08task_ids\x18\x02 \x03(\t\"A\n\x13PullTaskResResponse\x12*\n\rtask_res_list\x18\x01 \x03(\x0b\x32\x13.flwr.proto.TaskRes2\xc7\x03\n\x06\x44river\x12J\n\tCreateRun\x12\x1c.flwr.proto.CreateRunRequest\x1a\x1d.flwr.proto.CreateRunResponse\"\x00\x12G\n\x08GetNodes\x12\x1b.flwr.proto.GetNodesRequest\x1a\x1c.flwr.proto.GetNodesResponse\"\x00\x12P\n\x0bPushTaskIns\x12\x1e.flwr.proto.PushTaskInsRequest\x1a\x1f.flwr.proto.PushTaskInsResponse\"\x00\x12P\n\x0bPullTaskRes\x12\x1e.flwr.proto.PullTaskResRequest\x1a\x1f.flwr.proto.PullTaskResResponse\"\x00\x12\x41\n\x06GetRun\x12\x19.flwr.proto.GetRunRequest\x1a\x1a.flwr.proto.GetRunResponse\"\x00\x12\x41\n\x06GetFab\x12\x19.flwr.proto.GetFabRequest\x1a\x1a.flwr.proto.GetFabResponse\"\x00\x62\x06proto3')
22
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17\x66lwr/proto/driver.proto\x12\nflwr.proto\x1a\x15\x66lwr/proto/node.proto\x1a\x18\x66lwr/proto/message.proto\x1a\x15\x66lwr/proto/task.proto\x1a\x14\x66lwr/proto/run.proto\x1a\x14\x66lwr/proto/fab.proto\"!\n\x0fGetNodesRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\"3\n\x10GetNodesResponse\x12\x1f\n\x05nodes\x18\x01 \x03(\x0b\x32\x10.flwr.proto.Node\"@\n\x12PushTaskInsRequest\x12*\n\rtask_ins_list\x18\x01 \x03(\x0b\x32\x13.flwr.proto.TaskIns\"\'\n\x13PushTaskInsResponse\x12\x10\n\x08task_ids\x18\x02 \x03(\t\"F\n\x12PullTaskResRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12\x10\n\x08task_ids\x18\x02 \x03(\t\"A\n\x13PullTaskResResponse\x12*\n\rtask_res_list\x18\x01 \x03(\x0b\x32\x13.flwr.proto.TaskRes\",\n\x1aPullServerAppInputsRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\"\x7f\n\x1bPullServerAppInputsResponse\x12$\n\x07\x63ontext\x18\x01 \x01(\x0b\x32\x13.flwr.proto.Context\x12\x1c\n\x03run\x18\x02 \x01(\x0b\x32\x0f.flwr.proto.Run\x12\x1c\n\x03\x66\x61\x62\x18\x03 \x01(\x0b\x32\x0f.flwr.proto.Fab\"S\n\x1bPushServerAppOutputsRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\x12$\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x13.flwr.proto.Context\"\x1e\n\x1cPushServerAppOutputsResponse2\x9e\x05\n\x06\x44river\x12J\n\tCreateRun\x12\x1c.flwr.proto.CreateRunRequest\x1a\x1d.flwr.proto.CreateRunResponse\"\x00\x12G\n\x08GetNodes\x12\x1b.flwr.proto.GetNodesRequest\x1a\x1c.flwr.proto.GetNodesResponse\"\x00\x12P\n\x0bPushTaskIns\x12\x1e.flwr.proto.PushTaskInsRequest\x1a\x1f.flwr.proto.PushTaskInsResponse\"\x00\x12P\n\x0bPullTaskRes\x12\x1e.flwr.proto.PullTaskResRequest\x1a\x1f.flwr.proto.PullTaskResResponse\"\x00\x12\x41\n\x06GetRun\x12\x19.flwr.proto.GetRunRequest\x1a\x1a.flwr.proto.GetRunResponse\"\x00\x12\x41\n\x06GetFab\x12\x19.flwr.proto.GetFabRequest\x1a\x1a.flwr.proto.GetFabResponse\"\x00\x12h\n\x13PullServerAppInputs\x12&.flwr.proto.PullServerAppInputsRequest\x1a\'.flwr.proto.PullServerAppInputsResponse\"\x00\x12k\n\x14PushServerAppOutputs\x12\'.flwr.proto.PushServerAppOutputsRequest\x1a(.flwr.proto.PushServerAppOutputsResponse\"\x00\x62\x06proto3')
22
23
 
23
24
  _globals = globals()
24
25
  _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
25
26
  _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'flwr.proto.driver_pb2', _globals)
26
27
  if _descriptor._USE_C_DESCRIPTORS == False:
27
28
  DESCRIPTOR._options = None
28
- _globals['_GETNODESREQUEST']._serialized_start=129
29
- _globals['_GETNODESREQUEST']._serialized_end=162
30
- _globals['_GETNODESRESPONSE']._serialized_start=164
31
- _globals['_GETNODESRESPONSE']._serialized_end=215
32
- _globals['_PUSHTASKINSREQUEST']._serialized_start=217
33
- _globals['_PUSHTASKINSREQUEST']._serialized_end=281
34
- _globals['_PUSHTASKINSRESPONSE']._serialized_start=283
35
- _globals['_PUSHTASKINSRESPONSE']._serialized_end=322
36
- _globals['_PULLTASKRESREQUEST']._serialized_start=324
37
- _globals['_PULLTASKRESREQUEST']._serialized_end=394
38
- _globals['_PULLTASKRESRESPONSE']._serialized_start=396
39
- _globals['_PULLTASKRESRESPONSE']._serialized_end=461
40
- _globals['_DRIVER']._serialized_start=464
41
- _globals['_DRIVER']._serialized_end=919
29
+ _globals['_GETNODESREQUEST']._serialized_start=155
30
+ _globals['_GETNODESREQUEST']._serialized_end=188
31
+ _globals['_GETNODESRESPONSE']._serialized_start=190
32
+ _globals['_GETNODESRESPONSE']._serialized_end=241
33
+ _globals['_PUSHTASKINSREQUEST']._serialized_start=243
34
+ _globals['_PUSHTASKINSREQUEST']._serialized_end=307
35
+ _globals['_PUSHTASKINSRESPONSE']._serialized_start=309
36
+ _globals['_PUSHTASKINSRESPONSE']._serialized_end=348
37
+ _globals['_PULLTASKRESREQUEST']._serialized_start=350
38
+ _globals['_PULLTASKRESREQUEST']._serialized_end=420
39
+ _globals['_PULLTASKRESRESPONSE']._serialized_start=422
40
+ _globals['_PULLTASKRESRESPONSE']._serialized_end=487
41
+ _globals['_PULLSERVERAPPINPUTSREQUEST']._serialized_start=489
42
+ _globals['_PULLSERVERAPPINPUTSREQUEST']._serialized_end=533
43
+ _globals['_PULLSERVERAPPINPUTSRESPONSE']._serialized_start=535
44
+ _globals['_PULLSERVERAPPINPUTSRESPONSE']._serialized_end=662
45
+ _globals['_PUSHSERVERAPPOUTPUTSREQUEST']._serialized_start=664
46
+ _globals['_PUSHSERVERAPPOUTPUTSREQUEST']._serialized_end=747
47
+ _globals['_PUSHSERVERAPPOUTPUTSRESPONSE']._serialized_start=749
48
+ _globals['_PUSHSERVERAPPOUTPUTSRESPONSE']._serialized_end=779
49
+ _globals['_DRIVER']._serialized_start=782
50
+ _globals['_DRIVER']._serialized_end=1452
42
51
  # @@protoc_insertion_point(module_scope)
flwr/proto/driver_pb2.pyi CHANGED
@@ -3,7 +3,10 @@
3
3
  isort:skip_file
4
4
  """
5
5
  import builtins
6
+ import flwr.proto.fab_pb2
7
+ import flwr.proto.message_pb2
6
8
  import flwr.proto.node_pb2
9
+ import flwr.proto.run_pb2
7
10
  import flwr.proto.task_pb2
8
11
  import google.protobuf.descriptor
9
12
  import google.protobuf.internal.containers
@@ -91,3 +94,59 @@ class PullTaskResResponse(google.protobuf.message.Message):
91
94
  ) -> None: ...
92
95
  def ClearField(self, field_name: typing_extensions.Literal["task_res_list",b"task_res_list"]) -> None: ...
93
96
  global___PullTaskResResponse = PullTaskResResponse
97
+
98
+ class PullServerAppInputsRequest(google.protobuf.message.Message):
99
+ """PullServerAppInputs messages"""
100
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
101
+ RUN_ID_FIELD_NUMBER: builtins.int
102
+ run_id: builtins.int
103
+ def __init__(self,
104
+ *,
105
+ run_id: builtins.int = ...,
106
+ ) -> None: ...
107
+ def ClearField(self, field_name: typing_extensions.Literal["run_id",b"run_id"]) -> None: ...
108
+ global___PullServerAppInputsRequest = PullServerAppInputsRequest
109
+
110
+ class PullServerAppInputsResponse(google.protobuf.message.Message):
111
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
112
+ CONTEXT_FIELD_NUMBER: builtins.int
113
+ RUN_FIELD_NUMBER: builtins.int
114
+ FAB_FIELD_NUMBER: builtins.int
115
+ @property
116
+ def context(self) -> flwr.proto.message_pb2.Context: ...
117
+ @property
118
+ def run(self) -> flwr.proto.run_pb2.Run: ...
119
+ @property
120
+ def fab(self) -> flwr.proto.fab_pb2.Fab: ...
121
+ def __init__(self,
122
+ *,
123
+ context: typing.Optional[flwr.proto.message_pb2.Context] = ...,
124
+ run: typing.Optional[flwr.proto.run_pb2.Run] = ...,
125
+ fab: typing.Optional[flwr.proto.fab_pb2.Fab] = ...,
126
+ ) -> None: ...
127
+ def HasField(self, field_name: typing_extensions.Literal["context",b"context","fab",b"fab","run",b"run"]) -> builtins.bool: ...
128
+ def ClearField(self, field_name: typing_extensions.Literal["context",b"context","fab",b"fab","run",b"run"]) -> None: ...
129
+ global___PullServerAppInputsResponse = PullServerAppInputsResponse
130
+
131
+ class PushServerAppOutputsRequest(google.protobuf.message.Message):
132
+ """PushServerAppOutputs messages"""
133
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
134
+ RUN_ID_FIELD_NUMBER: builtins.int
135
+ CONTEXT_FIELD_NUMBER: builtins.int
136
+ run_id: builtins.int
137
+ @property
138
+ def context(self) -> flwr.proto.message_pb2.Context: ...
139
+ def __init__(self,
140
+ *,
141
+ run_id: builtins.int = ...,
142
+ context: typing.Optional[flwr.proto.message_pb2.Context] = ...,
143
+ ) -> None: ...
144
+ def HasField(self, field_name: typing_extensions.Literal["context",b"context"]) -> builtins.bool: ...
145
+ def ClearField(self, field_name: typing_extensions.Literal["context",b"context","run_id",b"run_id"]) -> None: ...
146
+ global___PushServerAppOutputsRequest = PushServerAppOutputsRequest
147
+
148
+ class PushServerAppOutputsResponse(google.protobuf.message.Message):
149
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
150
+ def __init__(self,
151
+ ) -> None: ...
152
+ global___PushServerAppOutputsResponse = PushServerAppOutputsResponse
@@ -46,6 +46,16 @@ class DriverStub(object):
46
46
  request_serializer=flwr_dot_proto_dot_fab__pb2.GetFabRequest.SerializeToString,
47
47
  response_deserializer=flwr_dot_proto_dot_fab__pb2.GetFabResponse.FromString,
48
48
  )
49
+ self.PullServerAppInputs = channel.unary_unary(
50
+ '/flwr.proto.Driver/PullServerAppInputs',
51
+ request_serializer=flwr_dot_proto_dot_driver__pb2.PullServerAppInputsRequest.SerializeToString,
52
+ response_deserializer=flwr_dot_proto_dot_driver__pb2.PullServerAppInputsResponse.FromString,
53
+ )
54
+ self.PushServerAppOutputs = channel.unary_unary(
55
+ '/flwr.proto.Driver/PushServerAppOutputs',
56
+ request_serializer=flwr_dot_proto_dot_driver__pb2.PushServerAppOutputsRequest.SerializeToString,
57
+ response_deserializer=flwr_dot_proto_dot_driver__pb2.PushServerAppOutputsResponse.FromString,
58
+ )
49
59
 
50
60
 
51
61
  class DriverServicer(object):
@@ -93,6 +103,20 @@ class DriverServicer(object):
93
103
  context.set_details('Method not implemented!')
94
104
  raise NotImplementedError('Method not implemented!')
95
105
 
106
+ def PullServerAppInputs(self, request, context):
107
+ """Pull ServerApp inputs
108
+ """
109
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
110
+ context.set_details('Method not implemented!')
111
+ raise NotImplementedError('Method not implemented!')
112
+
113
+ def PushServerAppOutputs(self, request, context):
114
+ """Push ServerApp outputs
115
+ """
116
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
117
+ context.set_details('Method not implemented!')
118
+ raise NotImplementedError('Method not implemented!')
119
+
96
120
 
97
121
  def add_DriverServicer_to_server(servicer, server):
98
122
  rpc_method_handlers = {
@@ -126,6 +150,16 @@ def add_DriverServicer_to_server(servicer, server):
126
150
  request_deserializer=flwr_dot_proto_dot_fab__pb2.GetFabRequest.FromString,
127
151
  response_serializer=flwr_dot_proto_dot_fab__pb2.GetFabResponse.SerializeToString,
128
152
  ),
153
+ 'PullServerAppInputs': grpc.unary_unary_rpc_method_handler(
154
+ servicer.PullServerAppInputs,
155
+ request_deserializer=flwr_dot_proto_dot_driver__pb2.PullServerAppInputsRequest.FromString,
156
+ response_serializer=flwr_dot_proto_dot_driver__pb2.PullServerAppInputsResponse.SerializeToString,
157
+ ),
158
+ 'PushServerAppOutputs': grpc.unary_unary_rpc_method_handler(
159
+ servicer.PushServerAppOutputs,
160
+ request_deserializer=flwr_dot_proto_dot_driver__pb2.PushServerAppOutputsRequest.FromString,
161
+ response_serializer=flwr_dot_proto_dot_driver__pb2.PushServerAppOutputsResponse.SerializeToString,
162
+ ),
129
163
  }
130
164
  generic_handler = grpc.method_handlers_generic_handler(
131
165
  'flwr.proto.Driver', rpc_method_handlers)
@@ -237,3 +271,37 @@ class Driver(object):
237
271
  flwr_dot_proto_dot_fab__pb2.GetFabResponse.FromString,
238
272
  options, channel_credentials,
239
273
  insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
274
+
275
+ @staticmethod
276
+ def PullServerAppInputs(request,
277
+ target,
278
+ options=(),
279
+ channel_credentials=None,
280
+ call_credentials=None,
281
+ insecure=False,
282
+ compression=None,
283
+ wait_for_ready=None,
284
+ timeout=None,
285
+ metadata=None):
286
+ return grpc.experimental.unary_unary(request, target, '/flwr.proto.Driver/PullServerAppInputs',
287
+ flwr_dot_proto_dot_driver__pb2.PullServerAppInputsRequest.SerializeToString,
288
+ flwr_dot_proto_dot_driver__pb2.PullServerAppInputsResponse.FromString,
289
+ options, channel_credentials,
290
+ insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
291
+
292
+ @staticmethod
293
+ def PushServerAppOutputs(request,
294
+ target,
295
+ options=(),
296
+ channel_credentials=None,
297
+ call_credentials=None,
298
+ insecure=False,
299
+ compression=None,
300
+ wait_for_ready=None,
301
+ timeout=None,
302
+ metadata=None):
303
+ return grpc.experimental.unary_unary(request, target, '/flwr.proto.Driver/PushServerAppOutputs',
304
+ flwr_dot_proto_dot_driver__pb2.PushServerAppOutputsRequest.SerializeToString,
305
+ flwr_dot_proto_dot_driver__pb2.PushServerAppOutputsResponse.FromString,
306
+ options, channel_credentials,
307
+ insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
@@ -40,6 +40,16 @@ class DriverStub:
40
40
  flwr.proto.fab_pb2.GetFabResponse]
41
41
  """Get FAB"""
42
42
 
43
+ PullServerAppInputs: grpc.UnaryUnaryMultiCallable[
44
+ flwr.proto.driver_pb2.PullServerAppInputsRequest,
45
+ flwr.proto.driver_pb2.PullServerAppInputsResponse]
46
+ """Pull ServerApp inputs"""
47
+
48
+ PushServerAppOutputs: grpc.UnaryUnaryMultiCallable[
49
+ flwr.proto.driver_pb2.PushServerAppOutputsRequest,
50
+ flwr.proto.driver_pb2.PushServerAppOutputsResponse]
51
+ """Push ServerApp outputs"""
52
+
43
53
 
44
54
  class DriverServicer(metaclass=abc.ABCMeta):
45
55
  @abc.abstractmethod
@@ -90,5 +100,21 @@ class DriverServicer(metaclass=abc.ABCMeta):
90
100
  """Get FAB"""
91
101
  pass
92
102
 
103
+ @abc.abstractmethod
104
+ def PullServerAppInputs(self,
105
+ request: flwr.proto.driver_pb2.PullServerAppInputsRequest,
106
+ context: grpc.ServicerContext,
107
+ ) -> flwr.proto.driver_pb2.PullServerAppInputsResponse:
108
+ """Pull ServerApp inputs"""
109
+ pass
110
+
111
+ @abc.abstractmethod
112
+ def PushServerAppOutputs(self,
113
+ request: flwr.proto.driver_pb2.PushServerAppOutputsRequest,
114
+ context: grpc.ServicerContext,
115
+ ) -> flwr.proto.driver_pb2.PushServerAppOutputsResponse:
116
+ """Push ServerApp outputs"""
117
+ pass
118
+
93
119
 
94
120
  def add_DriverServicer_to_server(servicer: DriverServicer, server: grpc.Server) -> None: ...
flwr/server/app.py CHANGED
@@ -64,7 +64,7 @@ from flwr.proto.fleet_pb2_grpc import ( # pylint: disable=E0611
64
64
  )
65
65
  from flwr.proto.grpcadapter_pb2_grpc import add_GrpcAdapterServicer_to_server
66
66
  from flwr.superexec.app import load_executor
67
- from flwr.superexec.exec_grpc import run_superexec_api_grpc
67
+ from flwr.superexec.exec_grpc import run_exec_api_grpc
68
68
 
69
69
  from .client_manager import ClientManager
70
70
  from .history import History
@@ -329,8 +329,10 @@ def run_superlink() -> None:
329
329
  raise ValueError(f"Unknown fleet_api_type: {args.fleet_api_type}")
330
330
 
331
331
  # Start Exec API
332
- exec_server: grpc.Server = run_superexec_api_grpc(
332
+ exec_server: grpc.Server = run_exec_api_grpc(
333
333
  address=exec_address,
334
+ state_factory=state_factory,
335
+ ffs_factory=ffs_factory,
334
336
  executor=load_executor(args),
335
337
  certificates=certificates,
336
338
  config=parse_config_args(
@@ -34,7 +34,6 @@ from flwr.common.config import (
34
34
  from flwr.common.constant import DRIVER_API_DEFAULT_ADDRESS
35
35
  from flwr.common.logger import log, update_console_handler, warn_deprecated_feature
36
36
  from flwr.common.object_ref import load_app
37
- from flwr.common.typing import UserConfig
38
37
  from flwr.proto.fab_pb2 import GetFabRequest, GetFabResponse # pylint: disable=E0611
39
38
  from flwr.proto.run_pb2 import ( # pylint: disable=E0611
40
39
  CreateRunRequest,
@@ -46,13 +45,14 @@ from .driver.grpc_driver import GrpcDriver
46
45
  from .server_app import LoadServerAppError, ServerApp
47
46
 
48
47
 
48
+ # pylint: disable-next=too-many-arguments,too-many-positional-arguments
49
49
  def run(
50
50
  driver: Driver,
51
+ context: Context,
51
52
  server_app_dir: str,
52
- server_app_run_config: UserConfig,
53
53
  server_app_attr: Optional[str] = None,
54
54
  loaded_server_app: Optional[ServerApp] = None,
55
- ) -> None:
55
+ ) -> Context:
56
56
  """Run ServerApp with a given Driver."""
57
57
  if not (server_app_attr is None) ^ (loaded_server_app is None):
58
58
  raise ValueError(
@@ -78,15 +78,11 @@ def run(
78
78
 
79
79
  server_app = _load()
80
80
 
81
- # Initialize Context
82
- context = Context(
83
- node_id=0, node_config={}, state=RecordSet(), run_config=server_app_run_config
84
- )
85
-
86
81
  # Call ServerApp
87
82
  server_app(driver=driver, context=context)
88
83
 
89
84
  log(DEBUG, "ServerApp finished running.")
85
+ return context
90
86
 
91
87
 
92
88
  # pylint: disable-next=too-many-branches,too-many-statements,too-many-locals
@@ -225,11 +221,19 @@ def run_server_app() -> None:
225
221
  root_certificates,
226
222
  )
227
223
 
224
+ # Initialize Context
225
+ context = Context(
226
+ node_id=0,
227
+ node_config={},
228
+ state=RecordSet(),
229
+ run_config=server_app_run_config,
230
+ )
231
+
228
232
  # Run the ServerApp with the Driver
229
233
  run(
230
234
  driver=driver,
235
+ context=context,
231
236
  server_app_dir=app_path,
232
- server_app_run_config=server_app_run_config,
233
237
  server_app_attr=server_app_attr,
234
238
  )
235
239
 
@@ -34,8 +34,12 @@ from flwr.proto import driver_pb2_grpc # pylint: disable=E0611
34
34
  from flwr.proto.driver_pb2 import ( # pylint: disable=E0611
35
35
  GetNodesRequest,
36
36
  GetNodesResponse,
37
+ PullServerAppInputsRequest,
38
+ PullServerAppInputsResponse,
37
39
  PullTaskResRequest,
38
40
  PullTaskResResponse,
41
+ PushServerAppOutputsRequest,
42
+ PushServerAppOutputsResponse,
39
43
  PushTaskInsRequest,
40
44
  PushTaskInsResponse,
41
45
  )
@@ -200,6 +204,18 @@ class DriverServicer(driver_pb2_grpc.DriverServicer):
200
204
 
201
205
  raise ValueError(f"Found no FAB with hash: {request.hash_str}")
202
206
 
207
+ def PullServerAppInputs(
208
+ self, request: PullServerAppInputsRequest, context: grpc.ServicerContext
209
+ ) -> PullServerAppInputsResponse:
210
+ """Pull ServerApp process inputs."""
211
+ raise NotImplementedError()
212
+
213
+ def PushServerAppOutputs(
214
+ self, request: PushServerAppOutputsRequest, context: grpc.ServicerContext
215
+ ) -> PushServerAppOutputsResponse:
216
+ """Push ServerApp process outputs."""
217
+ raise NotImplementedError()
218
+
203
219
 
204
220
  def _raise_if(validation_error: bool, detail: str) -> None:
205
221
  if validation_error:
@@ -22,7 +22,7 @@ from logging import ERROR, WARNING
22
22
  from typing import Optional
23
23
  from uuid import UUID, uuid4
24
24
 
25
- from flwr.common import log, now
25
+ from flwr.common import Context, log, now
26
26
  from flwr.common.constant import (
27
27
  MESSAGE_TTL_TOLERANCE,
28
28
  NODE_ID_NUM_BYTES,
@@ -65,6 +65,7 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
65
65
 
66
66
  # Map run_id to RunRecord
67
67
  self.run_ids: dict[int, RunRecord] = {}
68
+ self.contexts: dict[int, Context] = {}
68
69
  self.task_ins_store: dict[UUID, TaskIns] = {}
69
70
  self.task_res_store: dict[UUID, TaskRes] = {}
70
71
 
@@ -500,3 +501,13 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
500
501
  self.node_ids[node_id] = (time.time() + ping_interval, ping_interval)
501
502
  return True
502
503
  return False
504
+
505
+ def get_serverapp_context(self, run_id: int) -> Optional[Context]:
506
+ """Get the context for the specified `run_id`."""
507
+ return self.contexts.get(run_id)
508
+
509
+ def set_serverapp_context(self, run_id: int, context: Context) -> None:
510
+ """Set the context for the specified `run_id`."""
511
+ if run_id not in self.run_ids:
512
+ raise ValueError(f"Run {run_id} not found")
513
+ self.contexts[run_id] = context
@@ -19,6 +19,7 @@ import abc
19
19
  from typing import Optional
20
20
  from uuid import UUID
21
21
 
22
+ from flwr.common import Context
22
23
  from flwr.common.typing import Run, RunStatus, UserConfig
23
24
  from flwr.proto.task_pb2 import TaskIns, TaskRes # pylint: disable=E0611
24
25
 
@@ -270,3 +271,31 @@ class LinkState(abc.ABC): # pylint: disable=R0904
270
271
  is_acknowledged : bool
271
272
  True if the ping is successfully acknowledged; otherwise, False.
272
273
  """
274
+
275
+ @abc.abstractmethod
276
+ def get_serverapp_context(self, run_id: int) -> Optional[Context]:
277
+ """Get the context for the specified `run_id`.
278
+
279
+ Parameters
280
+ ----------
281
+ run_id : int
282
+ The identifier of the run for which to retrieve the context.
283
+
284
+ Returns
285
+ -------
286
+ Optional[Context]
287
+ The context associated with the specified `run_id`, or `None` if no context
288
+ exists for the given `run_id`.
289
+ """
290
+
291
+ @abc.abstractmethod
292
+ def set_serverapp_context(self, run_id: int, context: Context) -> None:
293
+ """Set the context for the specified `run_id`.
294
+
295
+ Parameters
296
+ ----------
297
+ run_id : int
298
+ The identifier of the run for which to set the context.
299
+ context : Context
300
+ The context to be associated with the specified `run_id`.
301
+ """
@@ -19,13 +19,14 @@
19
19
  import json
20
20
  import re
21
21
  import sqlite3
22
+ import threading
22
23
  import time
23
24
  from collections.abc import Sequence
24
25
  from logging import DEBUG, ERROR, WARNING
25
26
  from typing import Any, Optional, Union, cast
26
27
  from uuid import UUID, uuid4
27
28
 
28
- from flwr.common import log, now
29
+ from flwr.common import Context, log, now
29
30
  from flwr.common.constant import (
30
31
  MESSAGE_TTL_TOLERANCE,
31
32
  NODE_ID_NUM_BYTES,
@@ -33,13 +34,19 @@ from flwr.common.constant import (
33
34
  Status,
34
35
  )
35
36
  from flwr.common.typing import Run, RunStatus, UserConfig
36
- from flwr.proto.node_pb2 import Node # pylint: disable=E0611
37
- from flwr.proto.recordset_pb2 import RecordSet # pylint: disable=E0611
38
- from flwr.proto.task_pb2 import Task, TaskIns, TaskRes # pylint: disable=E0611
37
+
38
+ # pylint: disable=E0611
39
+ from flwr.proto.node_pb2 import Node
40
+ from flwr.proto.recordset_pb2 import RecordSet as ProtoRecordSet
41
+ from flwr.proto.task_pb2 import Task, TaskIns, TaskRes
42
+
43
+ # pylint: enable=E0611
39
44
  from flwr.server.utils.validator import validate_task_ins_or_res
40
45
 
41
46
  from .linkstate import LinkState
42
47
  from .utils import (
48
+ context_from_bytes,
49
+ context_to_bytes,
43
50
  convert_sint64_to_uint64,
44
51
  convert_sint64_values_in_dict_to_uint64,
45
52
  convert_uint64_to_sint64,
@@ -92,6 +99,14 @@ CREATE TABLE IF NOT EXISTS run(
92
99
  );
93
100
  """
94
101
 
102
+ SQL_CREATE_TABLE_CONTEXT = """
103
+ CREATE TABLE IF NOT EXISTS context(
104
+ run_id INTEGER UNIQUE,
105
+ context BLOB,
106
+ FOREIGN KEY(run_id) REFERENCES run(run_id)
107
+ );
108
+ """
109
+
95
110
  SQL_CREATE_TABLE_TASK_INS = """
96
111
  CREATE TABLE IF NOT EXISTS task_ins(
97
112
  task_id TEXT UNIQUE,
@@ -152,6 +167,7 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
152
167
  """
153
168
  self.database_path = database_path
154
169
  self.conn: Optional[sqlite3.Connection] = None
170
+ self.lock = threading.RLock()
155
171
 
156
172
  def initialize(self, log_queries: bool = False) -> list[tuple[str]]:
157
173
  """Create tables if they don't exist yet.
@@ -175,6 +191,7 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
175
191
 
176
192
  # Create each table if not exists queries
177
193
  cur.execute(SQL_CREATE_TABLE_RUN)
194
+ cur.execute(SQL_CREATE_TABLE_CONTEXT)
178
195
  cur.execute(SQL_CREATE_TABLE_TASK_INS)
179
196
  cur.execute(SQL_CREATE_TABLE_TASK_RES)
180
197
  cur.execute(SQL_CREATE_TABLE_NODE)
@@ -970,6 +987,34 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
970
987
  log(ERROR, "`node_id` does not exist.")
971
988
  return False
972
989
 
990
+ def get_serverapp_context(self, run_id: int) -> Optional[Context]:
991
+ """Get the context for the specified `run_id`."""
992
+ # Retrieve context if any
993
+ query = "SELECT context FROM context WHERE run_id = ?;"
994
+ rows = self.query(query, (convert_uint64_to_sint64(run_id),))
995
+ context = context_from_bytes(rows[0]["context"]) if rows else None
996
+ return context
997
+
998
+ def set_serverapp_context(self, run_id: int, context: Context) -> None:
999
+ """Set the context for the specified `run_id`."""
1000
+ # Convert context to bytes
1001
+ context_bytes = context_to_bytes(context)
1002
+ sint_run_id = convert_uint64_to_sint64(run_id)
1003
+
1004
+ # Check if any existing Context assigned to the run_id
1005
+ query = "SELECT COUNT(*) FROM context WHERE run_id = ?;"
1006
+ if self.query(query, (sint_run_id,))[0]["COUNT(*)"] > 0:
1007
+ # Update context
1008
+ query = "UPDATE context SET context = ? WHERE run_id = ?;"
1009
+ self.query(query, (context_bytes, sint_run_id))
1010
+ else:
1011
+ try:
1012
+ # Store context
1013
+ query = "INSERT INTO context (run_id, context) VALUES (?, ?);"
1014
+ self.query(query, (sint_run_id, context_bytes))
1015
+ except sqlite3.IntegrityError:
1016
+ raise ValueError(f"Run {run_id} not found") from None
1017
+
973
1018
  def get_valid_task_ins(self, task_id: str) -> Optional[dict[str, Any]]:
974
1019
  """Check if the TaskIns exists and is valid (not expired).
975
1020
 
@@ -1054,7 +1099,7 @@ def task_res_to_dict(task_msg: TaskRes) -> dict[str, Any]:
1054
1099
 
1055
1100
  def dict_to_task_ins(task_dict: dict[str, Any]) -> TaskIns:
1056
1101
  """Turn task_dict into protobuf message."""
1057
- recordset = RecordSet()
1102
+ recordset = ProtoRecordSet()
1058
1103
  recordset.ParseFromString(task_dict["recordset"])
1059
1104
 
1060
1105
  result = TaskIns(
@@ -1084,7 +1129,7 @@ def dict_to_task_ins(task_dict: dict[str, Any]) -> TaskIns:
1084
1129
 
1085
1130
  def dict_to_task_res(task_dict: dict[str, Any]) -> TaskRes:
1086
1131
  """Turn task_dict into protobuf message."""
1087
- recordset = RecordSet()
1132
+ recordset = ProtoRecordSet()
1088
1133
  recordset.ParseFromString(task_dict["recordset"])
1089
1134
 
1090
1135
  result = TaskRes(
@@ -20,10 +20,11 @@ from logging import ERROR
20
20
  from os import urandom
21
21
  from uuid import uuid4
22
22
 
23
- from flwr.common import log
23
+ from flwr.common import Context, log, serde
24
24
  from flwr.common.constant import ErrorCode, Status, SubStatus
25
25
  from flwr.common.typing import RunStatus
26
26
  from flwr.proto.error_pb2 import Error # pylint: disable=E0611
27
+ from flwr.proto.message_pb2 import Context as ProtoContext # pylint: disable=E0611
27
28
  from flwr.proto.node_pb2 import Node # pylint: disable=E0611
28
29
  from flwr.proto.task_pb2 import Task, TaskIns, TaskRes # pylint: disable=E0611
29
30
 
@@ -135,6 +136,16 @@ def convert_sint64_values_in_dict_to_uint64(
135
136
  data_dict[key] = convert_sint64_to_uint64(data_dict[key])
136
137
 
137
138
 
139
+ def context_to_bytes(context: Context) -> bytes:
140
+ """Serialize `Context` to bytes."""
141
+ return serde.context_to_proto(context).SerializeToString()
142
+
143
+
144
+ def context_from_bytes(context_bytes: bytes) -> Context:
145
+ """Deserialize `Context` from bytes."""
146
+ return serde.context_from_proto(ProtoContext.FromString(context_bytes))
147
+
148
+
138
149
  def make_node_unavailable_taskres(ref_taskins: TaskIns) -> TaskRes:
139
150
  """Generate a TaskRes with a node unavailable error from a TaskIns."""
140
151
  current_time = time.time()
@@ -29,7 +29,7 @@ from typing import Any, Optional
29
29
 
30
30
  from flwr.cli.config_utils import load_and_validate
31
31
  from flwr.client import ClientApp
32
- from flwr.common import EventType, event, log, now
32
+ from flwr.common import Context, EventType, RecordSet, event, log, now
33
33
  from flwr.common.config import get_fused_config_from_dir, parse_config_args
34
34
  from flwr.common.constant import RUN_ID_NUM_BYTES, Status
35
35
  from flwr.common.logger import (
@@ -40,7 +40,7 @@ from flwr.common.logger import (
40
40
  )
41
41
  from flwr.common.typing import Run, RunStatus, UserConfig
42
42
  from flwr.server.driver import Driver, InMemoryDriver
43
- from flwr.server.run_serverapp import run as run_server_app
43
+ from flwr.server.run_serverapp import run as _run
44
44
  from flwr.server.server_app import ServerApp
45
45
  from flwr.server.superlink.fleet import vce
46
46
  from flwr.server.superlink.fleet.vce.backend.backend import BackendConfig
@@ -333,11 +333,19 @@ def run_serverapp_th(
333
333
  log(INFO, "Enabling GPU growth for Tensorflow on the server thread.")
334
334
  enable_gpu_growth()
335
335
 
336
+ # Initialize Context
337
+ context = Context(
338
+ node_id=0,
339
+ node_config={},
340
+ state=RecordSet(),
341
+ run_config=_server_app_run_config,
342
+ )
343
+
336
344
  # Run ServerApp
337
- run_server_app(
345
+ _run(
338
346
  driver=_driver,
347
+ context=context,
339
348
  server_app_dir=_server_app_dir,
340
- server_app_run_config=_server_app_run_config,
341
349
  server_app_attr=_server_app_attr,
342
350
  loaded_server_app=_server_app,
343
351
  )
flwr/superexec/app.py CHANGED
@@ -16,21 +16,11 @@
16
16
 
17
17
  import argparse
18
18
  import sys
19
- from logging import INFO, WARN
20
- from pathlib import Path
21
- from typing import Optional
19
+ from logging import INFO
22
20
 
23
- import grpc
24
-
25
- from flwr.common import EventType, event, log
26
- from flwr.common.address import parse_address
27
- from flwr.common.config import parse_config_args
28
- from flwr.common.constant import EXEC_API_DEFAULT_ADDRESS
29
- from flwr.common.exit_handlers import register_exit_handlers
30
- from flwr.common.logger import warn_deprecated_feature
21
+ from flwr.common import log
31
22
  from flwr.common.object_ref import load_app, validate
32
23
 
33
- from .exec_grpc import run_superexec_api_grpc
34
24
  from .executor import Executor
35
25
 
36
26
 
@@ -38,137 +28,12 @@ def run_superexec() -> None:
38
28
  """Run Flower SuperExec."""
39
29
  log(INFO, "Starting Flower SuperExec")
40
30
 
41
- warn_deprecated_feature(
31
+ sys.exit(
42
32
  "Manually launching the SuperExec is deprecated. Since `flwr 1.13.0` "
43
33
  "the executor service runs in the SuperLink. Launching it manually is not "
44
34
  "recommended."
45
35
  )
46
36
 
47
- event(EventType.RUN_SUPEREXEC_ENTER)
48
-
49
- args = _parse_args_run_superexec().parse_args()
50
-
51
- # Parse IP address
52
- parsed_address = parse_address(args.address)
53
- if not parsed_address:
54
- sys.exit(f"SuperExec IP address ({args.address}) cannot be parsed.")
55
- host, port, is_v6 = parsed_address
56
- address = f"[{host}]:{port}" if is_v6 else f"{host}:{port}"
57
-
58
- # Obtain certificates
59
- certificates = _try_obtain_certificates(args)
60
-
61
- # Start SuperExec API
62
- superexec_server: grpc.Server = run_superexec_api_grpc(
63
- address=address,
64
- executor=load_executor(args),
65
- certificates=certificates,
66
- config=parse_config_args(
67
- [args.executor_config] if args.executor_config else args.executor_config
68
- ),
69
- )
70
-
71
- grpc_servers = [superexec_server]
72
-
73
- # Graceful shutdown
74
- register_exit_handlers(
75
- event_type=EventType.RUN_SUPEREXEC_LEAVE,
76
- grpc_servers=grpc_servers,
77
- bckg_threads=None,
78
- )
79
-
80
- superexec_server.wait_for_termination()
81
-
82
-
83
- def _parse_args_run_superexec() -> argparse.ArgumentParser:
84
- """Parse command line arguments for SuperExec."""
85
- parser = argparse.ArgumentParser(
86
- description="Start a Flower SuperExec",
87
- )
88
- parser.add_argument(
89
- "--address",
90
- help="SuperExec (gRPC) server address (IPv4, IPv6, or a domain name)",
91
- default=EXEC_API_DEFAULT_ADDRESS,
92
- )
93
- parser.add_argument(
94
- "--executor",
95
- help="For example: `deployment:exec` or `project.package.module:wrapper.exec`.",
96
- default="flwr.superexec.deployment:executor",
97
- )
98
- parser.add_argument(
99
- "--executor-dir",
100
- help="The directory for the executor.",
101
- default=".",
102
- )
103
- parser.add_argument(
104
- "--executor-config",
105
- help="Key-value pairs for the executor config, separated by spaces. "
106
- 'For example:\n\n`--executor-config \'superlink="superlink:9091" '
107
- 'root-certificates="certificates/superlink-ca.crt"\'`',
108
- )
109
- parser.add_argument(
110
- "--insecure",
111
- action="store_true",
112
- help="Run the SuperExec without HTTPS, regardless of whether certificate "
113
- "paths are provided. By default, the server runs with HTTPS enabled. "
114
- "Use this flag only if you understand the risks.",
115
- )
116
- parser.add_argument(
117
- "--ssl-certfile",
118
- help="SuperExec server SSL certificate file (as a path str) "
119
- "to create a secure connection.",
120
- type=str,
121
- default=None,
122
- )
123
- parser.add_argument(
124
- "--ssl-keyfile",
125
- help="SuperExec server SSL private key file (as a path str) "
126
- "to create a secure connection.",
127
- type=str,
128
- )
129
- parser.add_argument(
130
- "--ssl-ca-certfile",
131
- help="SuperExec server SSL CA certificate file (as a path str) "
132
- "to create a secure connection.",
133
- type=str,
134
- )
135
- return parser
136
-
137
-
138
- def _try_obtain_certificates(
139
- args: argparse.Namespace,
140
- ) -> Optional[tuple[bytes, bytes, bytes]]:
141
- # Obtain certificates
142
- if args.insecure:
143
- log(WARN, "Option `--insecure` was set. Starting insecure HTTP server.")
144
- return None
145
- # Check if certificates are provided
146
- if args.ssl_certfile and args.ssl_keyfile and args.ssl_ca_certfile:
147
- if not Path(args.ssl_ca_certfile).is_file():
148
- sys.exit("Path argument `--ssl-ca-certfile` does not point to a file.")
149
- if not Path(args.ssl_certfile).is_file():
150
- sys.exit("Path argument `--ssl-certfile` does not point to a file.")
151
- if not Path(args.ssl_keyfile).is_file():
152
- sys.exit("Path argument `--ssl-keyfile` does not point to a file.")
153
- certificates = (
154
- Path(args.ssl_ca_certfile).read_bytes(), # CA certificate
155
- Path(args.ssl_certfile).read_bytes(), # server certificate
156
- Path(args.ssl_keyfile).read_bytes(), # server private key
157
- )
158
- return certificates
159
- if args.ssl_certfile or args.ssl_keyfile or args.ssl_ca_certfile:
160
- sys.exit(
161
- "You need to provide valid file paths to `--ssl-certfile`, "
162
- "`--ssl-keyfile`, and `—-ssl-ca-certfile` to create a secure "
163
- "connection in SuperExec server (gRPC-rere)."
164
- )
165
- sys.exit(
166
- "Certificates are required unless running in insecure mode. "
167
- "Please provide certificate paths to `--ssl-certfile`, "
168
- "`--ssl-keyfile`, and `—-ssl-ca-certfile` or run the server "
169
- "in insecure mode using '--insecure' if you understand the risks."
170
- )
171
-
172
37
 
173
38
  def load_executor(
174
39
  args: argparse.Namespace,
@@ -24,12 +24,11 @@ from typing_extensions import override
24
24
 
25
25
  from flwr.cli.install import install_from_fab
26
26
  from flwr.common.constant import DRIVER_API_DEFAULT_ADDRESS
27
- from flwr.common.grpc import create_channel
28
27
  from flwr.common.logger import log
29
- from flwr.common.serde import fab_to_proto, user_config_to_proto
30
28
  from flwr.common.typing import Fab, UserConfig
31
- from flwr.proto.driver_pb2_grpc import DriverStub
32
- from flwr.proto.run_pb2 import CreateRunRequest # pylint: disable=E0611
29
+ from flwr.server.superlink.ffs import Ffs
30
+ from flwr.server.superlink.ffs.ffs_factory import FfsFactory
31
+ from flwr.server.superlink.linkstate import LinkState, LinkStateFactory
33
32
 
34
33
  from .executor import Executor, RunTracker
35
34
 
@@ -62,7 +61,30 @@ class DeploymentEngine(Executor):
62
61
  self.root_certificates = root_certificates
63
62
  self.root_certificates_bytes = Path(root_certificates).read_bytes()
64
63
  self.flwr_dir = flwr_dir
65
- self.stub: Optional[DriverStub] = None
64
+ self.linkstate_factory: Optional[LinkStateFactory] = None
65
+ self.ffs_factory: Optional[FfsFactory] = None
66
+
67
+ @override
68
+ def initialize(
69
+ self, linkstate_factory: LinkStateFactory, ffs_factory: FfsFactory
70
+ ) -> None:
71
+ """Initialize the executor with the necessary factories."""
72
+ self.linkstate_factory = linkstate_factory
73
+ self.ffs_factory = ffs_factory
74
+
75
+ @property
76
+ def linkstate(self) -> LinkState:
77
+ """Return the LinkState."""
78
+ if self.linkstate_factory is None:
79
+ raise RuntimeError("Executor is not initialized.")
80
+ return self.linkstate_factory.state()
81
+
82
+ @property
83
+ def ffs(self) -> Ffs:
84
+ """Return the Flower File Storage (FFS)."""
85
+ if self.ffs_factory is None:
86
+ raise RuntimeError("Executor is not initialized.")
87
+ return self.ffs_factory.ffs()
66
88
 
67
89
  @override
68
90
  def set_config(
@@ -101,32 +123,19 @@ class DeploymentEngine(Executor):
101
123
  raise ValueError("The `flwr-dir` value should be of type `str`.")
102
124
  self.flwr_dir = str(flwr_dir)
103
125
 
104
- def _connect(self) -> None:
105
- if self.stub is not None:
106
- return
107
- channel = create_channel(
108
- server_address=self.superlink,
109
- insecure=(self.root_certificates_bytes is None),
110
- root_certificates=self.root_certificates_bytes,
111
- )
112
- self.stub = DriverStub(channel)
113
-
114
126
  def _create_run(
115
127
  self,
116
128
  fab: Fab,
117
129
  override_config: UserConfig,
118
130
  ) -> int:
119
- if self.stub is None:
120
- self._connect()
121
-
122
- assert self.stub is not None
131
+ fab_hash = self.ffs.put(fab.content, {})
132
+ if fab_hash != fab.hash_str:
133
+ raise RuntimeError(
134
+ f"FAB ({fab.hash_str}) hash from request doesn't match contents"
135
+ )
123
136
 
124
- req = CreateRunRequest(
125
- fab=fab_to_proto(fab),
126
- override_config=user_config_to_proto(override_config),
127
- )
128
- res = self.stub.CreateRun(request=req)
129
- return int(res.run_id)
137
+ run_id = self.linkstate.create_run(None, None, fab_hash, override_config)
138
+ return run_id
130
139
 
131
140
  @override
132
141
  def start_run(
@@ -23,33 +23,40 @@ from flwr.common import GRPC_MAX_MESSAGE_LENGTH
23
23
  from flwr.common.logger import log
24
24
  from flwr.common.typing import UserConfig
25
25
  from flwr.proto.exec_pb2_grpc import add_ExecServicer_to_server
26
+ from flwr.server.superlink.ffs.ffs_factory import FfsFactory
26
27
  from flwr.server.superlink.fleet.grpc_bidi.grpc_server import generic_create_grpc_server
28
+ from flwr.server.superlink.linkstate import LinkStateFactory
27
29
 
28
30
  from .exec_servicer import ExecServicer
29
31
  from .executor import Executor
30
32
 
31
33
 
32
- def run_superexec_api_grpc(
34
+ # pylint: disable-next=too-many-arguments, too-many-positional-arguments
35
+ def run_exec_api_grpc(
33
36
  address: str,
34
37
  executor: Executor,
38
+ state_factory: LinkStateFactory,
39
+ ffs_factory: FfsFactory,
35
40
  certificates: Optional[tuple[bytes, bytes, bytes]],
36
41
  config: UserConfig,
37
42
  ) -> grpc.Server:
38
- """Run SuperExec API (gRPC, request-response)."""
43
+ """Run Exec API (gRPC, request-response)."""
39
44
  executor.set_config(config)
40
45
 
41
46
  exec_servicer: grpc.Server = ExecServicer(
47
+ linkstate_factory=state_factory,
48
+ ffs_factory=ffs_factory,
42
49
  executor=executor,
43
50
  )
44
- superexec_add_servicer_to_server_fn = add_ExecServicer_to_server
45
- superexec_grpc_server = generic_create_grpc_server(
46
- servicer_and_add_fn=(exec_servicer, superexec_add_servicer_to_server_fn),
51
+ exec_add_servicer_to_server_fn = add_ExecServicer_to_server
52
+ exec_grpc_server = generic_create_grpc_server(
53
+ servicer_and_add_fn=(exec_servicer, exec_add_servicer_to_server_fn),
47
54
  server_address=address,
48
55
  max_message_length=GRPC_MAX_MESSAGE_LENGTH,
49
56
  certificates=certificates,
50
57
  )
51
58
 
52
- log(INFO, "Starting Flower SuperExec gRPC server on %s", address)
53
- superexec_grpc_server.start()
59
+ log(INFO, "Flower Deployment Engine: Starting Exec API on %s", address)
60
+ exec_grpc_server.start()
54
61
 
55
- return superexec_grpc_server
62
+ return exec_grpc_server
@@ -34,6 +34,8 @@ from flwr.proto.exec_pb2 import ( # pylint: disable=E0611
34
34
  StreamLogsRequest,
35
35
  StreamLogsResponse,
36
36
  )
37
+ from flwr.server.superlink.ffs.ffs_factory import FfsFactory
38
+ from flwr.server.superlink.linkstate import LinkStateFactory
37
39
 
38
40
  from .executor import Executor, RunTracker
39
41
 
@@ -43,8 +45,16 @@ SELECT_TIMEOUT = 1 # Timeout for selecting ready-to-read file descriptors (in s
43
45
  class ExecServicer(exec_pb2_grpc.ExecServicer):
44
46
  """SuperExec API servicer."""
45
47
 
46
- def __init__(self, executor: Executor) -> None:
48
+ def __init__(
49
+ self,
50
+ linkstate_factory: LinkStateFactory,
51
+ ffs_factory: FfsFactory,
52
+ executor: Executor,
53
+ ) -> None:
54
+ self.linkstate_factory = linkstate_factory
55
+ self.ffs_factory = ffs_factory
47
56
  self.executor = executor
57
+ self.executor.initialize(linkstate_factory, ffs_factory)
48
58
  self.runs: dict[int, RunTracker] = {}
49
59
 
50
60
  def StartRun(
@@ -20,6 +20,8 @@ from subprocess import Popen
20
20
  from typing import Optional
21
21
 
22
22
  from flwr.common.typing import UserConfig
23
+ from flwr.server.superlink.ffs.ffs_factory import FfsFactory
24
+ from flwr.server.superlink.linkstate import LinkStateFactory
23
25
 
24
26
 
25
27
  @dataclass
@@ -34,6 +36,23 @@ class RunTracker:
34
36
  class Executor(ABC):
35
37
  """Execute and monitor a Flower run."""
36
38
 
39
+ @abstractmethod
40
+ def initialize(
41
+ self, linkstate_factory: LinkStateFactory, ffs_factory: FfsFactory
42
+ ) -> None:
43
+ """Initialize the executor with the necessary factories.
44
+
45
+ This method sets up the executor by providing it with the factories required
46
+ to access the LinkState and the Flower File Storage (FFS) in the SuperLink.
47
+
48
+ Parameters
49
+ ----------
50
+ linkstate_factory : LinkStateFactory
51
+ The factory to create access to the LinkState.
52
+ ffs_factory : FfsFactory
53
+ The factory to create access to the Flower File Storage (FFS).
54
+ """
55
+
37
56
  @abstractmethod
38
57
  def set_config(
39
58
  self,
@@ -29,6 +29,8 @@ from flwr.common.config import unflatten_dict
29
29
  from flwr.common.constant import RUN_ID_NUM_BYTES
30
30
  from flwr.common.logger import log
31
31
  from flwr.common.typing import UserConfig
32
+ from flwr.server.superlink.ffs.ffs_factory import FfsFactory
33
+ from flwr.server.superlink.linkstate import LinkStateFactory
32
34
  from flwr.server.superlink.linkstate.utils import generate_rand_int_from_bytes
33
35
 
34
36
  from .executor import Executor, RunTracker
@@ -70,6 +72,12 @@ class SimulationEngine(Executor):
70
72
  self.num_supernodes = num_supernodes
71
73
  self.verbose = verbose
72
74
 
75
+ @override
76
+ def initialize(
77
+ self, linkstate_factory: LinkStateFactory, ffs_factory: FfsFactory
78
+ ) -> None:
79
+ """Initialize the executor with the necessary factories."""
80
+
73
81
  @override
74
82
  def set_config(
75
83
  self,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: flwr-nightly
3
- Version: 1.13.0.dev20241023
3
+ Version: 1.13.0.dev20241024
4
4
  Summary: Flower: A Friendly Federated Learning Framework
5
5
  Home-page: https://flower.ai
6
6
  License: Apache-2.0
@@ -92,7 +92,6 @@ flwr/client/mod/secure_aggregation/__init__.py,sha256=A7DzZ3uvXTUkuHBzrxJMWQQD4R
92
92
  flwr/client/mod/secure_aggregation/secagg_mod.py,sha256=wI9tuIEvMUETz-wVIEbPYvh-1nK9CEylBLGoVpNhL94,1095
93
93
  flwr/client/mod/secure_aggregation/secaggplus_mod.py,sha256=7cNXsY07ZA0M5_9VSc52F8JUoAoGaraNDA2rgaLvvFo,19680
94
94
  flwr/client/mod/utils.py,sha256=dFcTHOjUuuiw34fcQlvyzytYD0sCv1w9x8fQX1Yo8Oc,1201
95
- flwr/client/node_state_tests.py,sha256=28HzAxcxF-mhzPisJMCUczrLz1KfxtTkKZ5tvQgq-gM,2291
96
95
  flwr/client/numpy_client.py,sha256=tqGyhIkeeZQGr65BR03B7TWgx4rW3FA7G2874D8z_VU,11167
97
96
  flwr/client/rest_client/__init__.py,sha256=5KGlp7pjc1dhNRkKlaNtUfQmg8wrRFh9lS3P3uRS-7Q,735
98
97
  flwr/client/rest_client/connection.py,sha256=k-RqgUFqidACAGlMFPIUM8aawXI5h2LvKUri2OAK7Bg,12817
@@ -150,10 +149,10 @@ flwr/proto/control_pb2.py,sha256=yaUkwY2J9uo-fdUIB5aHwVSDOuGunxaUr4ZlggifA_M,143
150
149
  flwr/proto/control_pb2.pyi,sha256=XbFvpZvvrS7QcH5AFXfpRGl4hQvhd3QdKO6x0oTlCCU,165
151
150
  flwr/proto/control_pb2_grpc.py,sha256=FFE21nZvEILWpe1WCR5vAwgYEtpzrdG78-_SsU0gZ7w,5783
152
151
  flwr/proto/control_pb2_grpc.pyi,sha256=9DU4sgkzJ497a4Nq6kitZWEG4g_5MO8MevichnO0oAg,1672
153
- flwr/proto/driver_pb2.py,sha256=Z2fRF9mBa0cR0p6cItgyp5Q70WUAsh--kPEq8aIJuZk,3176
154
- flwr/proto/driver_pb2.pyi,sha256=jUOe6tHWQhddVbB3xtnNvlrztNUcxRHHJS7-LqGO_70,4034
155
- flwr/proto/driver_pb2_grpc.py,sha256=SGNmNcpsSWRc0jjNyH0xYNB8a7DAxIsXaL9a0M78vZw,10444
156
- flwr/proto/driver_pb2_grpc.pyi,sha256=wTVkRgVUNq2Jnzo5yhIIqGFiLN_VRB1zpjfqRLD5QnM,2800
152
+ flwr/proto/driver_pb2.py,sha256=8WmaJ3F5iPuHeiFkfkf_9Pa86qgoUYLPaIL-9igdcIk,4528
153
+ flwr/proto/driver_pb2.pyi,sha256=Mq4xZ5Hn55CrUVLu4kvNZylKtF9AQ9NomOv1qJ3Py4Q,6636
154
+ flwr/proto/driver_pb2_grpc.py,sha256=KgJNfvIkTX4z3u9jU05z3GvyCx39cQ1r_OgiD3zoinw,13886
155
+ flwr/proto/driver_pb2_grpc.pyi,sha256=_aiUW3TsX1jPL7WqdUhiAPK_6bK8Cv5Flo_SntmlL44,3766
157
156
  flwr/proto/error_pb2.py,sha256=LarjKL90LbwkXKlhzNrDssgl4DXcvIPve8NVCXHpsKA,1084
158
157
  flwr/proto/error_pb2.pyi,sha256=ZNH4HhJTU_KfMXlyCeg8FwU-fcUYxTqEmoJPtWtHikc,734
159
158
  flwr/proto/error_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
@@ -200,7 +199,7 @@ flwr/proto/transport_pb2_grpc.py,sha256=vLN3EHtx2aEEMCO4f1Upu-l27BPzd3-5pV-u8wPc
200
199
  flwr/proto/transport_pb2_grpc.pyi,sha256=AGXf8RiIiW2J5IKMlm_3qT3AzcDa4F3P5IqUjve_esA,766
201
200
  flwr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
202
201
  flwr/server/__init__.py,sha256=cEg1oecBu4cKB69iJCqWEylC8b5XW47bl7rQiJsdTvM,1528
203
- flwr/server/app.py,sha256=qChpmZm68wCkXsi9UgNGXQDRxTAm8klrH-66A9Oxsus,28255
202
+ flwr/server/app.py,sha256=p3uTuj4tIRK67d6FCyNIwEVKkfhZBIuuhZt86gDqmv8,28315
204
203
  flwr/server/client_manager.py,sha256=7Ese0tgrH-i-ms363feYZJKwB8gWnXSmg_hYF2Bju4U,6227
205
204
  flwr/server/client_proxy.py,sha256=4G-oTwhb45sfWLx2uZdcXD98IZwdTS6F88xe3akCdUg,2399
206
205
  flwr/server/compat/__init__.py,sha256=VxnJtJyOjNFQXMNi9hIuzNlZM5n0Hj1p3aq_Pm2udw4,892
@@ -214,7 +213,7 @@ flwr/server/driver/driver.py,sha256=KpLrWeF0zP9HBdo_mucPlNVj5eS0K8DDHRRY_58E1BM,
214
213
  flwr/server/driver/grpc_driver.py,sha256=AhkqfXBSv8TnqBGxqj1nhkOxp6HVU0WaJq4YlqIz8Hw,9687
215
214
  flwr/server/driver/inmemory_driver.py,sha256=7gw1zRgNvmy2FWCEOMs8udpc2HQsxURrIwbpKv5VyoE,6656
216
215
  flwr/server/history.py,sha256=qSb5_pPTrwofpSYGsZWzMPkl_4uJ4mJFWesxXDrEvDU,5026
217
- flwr/server/run_serverapp.py,sha256=5zoXSzLWKAuhK33cLYN-y5LzTgKqIyioNmzs7MIrDOA,10498
216
+ flwr/server/run_serverapp.py,sha256=_i0kBRrSzeR4-DDswysoWYertdAq7-LfrSAJ2n-GB8Q,10530
218
217
  flwr/server/server.py,sha256=1ZsFEptmAV-L2vP2etNC9Ed5CLSxpuKzUFkAPQ4l5Xc,17893
219
218
  flwr/server/server_app.py,sha256=RsgS6PRS5Z74cMUAHzsm8r3LWddwn00MjRs6rlacHt8,6297
220
219
  flwr/server/server_config.py,sha256=CZaHVAsMvGLjpWVcLPkiYxgJN4xfIyAiUrCI3fETKY4,1349
@@ -248,7 +247,7 @@ flwr/server/strategy/strategy.py,sha256=cXapkD5uDrt5C-RbmWDn9FLoap3Q41i7GKvbmfbC
248
247
  flwr/server/superlink/__init__.py,sha256=8tHYCfodUlRD8PCP9fHgvu8cz5N31A2QoRVL0jDJ15E,707
249
248
  flwr/server/superlink/driver/__init__.py,sha256=_JaRW-FdyikHc7souUrnk3mwTGViraEJCeUBY_M_ocs,712
250
249
  flwr/server/superlink/driver/driver_grpc.py,sha256=melAgaV37Y0B9bZe5bRWQOobItZZ9DIzlcbVE8B01wo,2060
251
- flwr/server/superlink/driver/driver_servicer.py,sha256=-DAIwbR-qZCLhFlYVDMluHQeQs_unHswEv6EYMY7RQY,7005
250
+ flwr/server/superlink/driver/driver_servicer.py,sha256=hA997-qPu9RxLFsiK-Jn4tDv7VZFHz1ayv5a4cOZGsA,7601
252
251
  flwr/server/superlink/ffs/__init__.py,sha256=FAY-zShcfPmOxosok2QyT6hTNMNctG8cH9s_nIl8jkI,840
253
252
  flwr/server/superlink/ffs/disk_ffs.py,sha256=yCN6CCzegnJIOaHr5nIu49wZQa4g5BByiSKshz50RKU,3296
254
253
  flwr/server/superlink/ffs/ffs.py,sha256=qLI1UfosJugu2BKOJWqHIhafTm-YiuKqGf3OGWPH0NM,2395
@@ -274,11 +273,11 @@ flwr/server/superlink/fleet/vce/backend/backend.py,sha256=LBAQxnbfPAphVOVIvYMj0Q
274
273
  flwr/server/superlink/fleet/vce/backend/raybackend.py,sha256=7kB3re3mR53b7E6L6DPSioTSKD3YGtS3uJsPD7Hn2Fw,7155
275
274
  flwr/server/superlink/fleet/vce/vce_api.py,sha256=VL6e_Jwf4uxA-X1EelxJZMv6Eji-_p2J9D0MdHG10a4,13029
276
275
  flwr/server/superlink/linkstate/__init__.py,sha256=v-2JyJlCB3qyhMNwMjmcNVOq4rkooqFU0LHH8Zo1jls,1064
277
- flwr/server/superlink/linkstate/in_memory_linkstate.py,sha256=PCN3O96GTR4-f_EhzTo8wqcerVTvXoPOwEjNyrKsQJ8,18830
278
- flwr/server/superlink/linkstate/linkstate.py,sha256=2vGG_XkLM4OsAFLMBDqml9ZV90eAYNtnbIzhlnHRSNU,9346
276
+ flwr/server/superlink/linkstate/in_memory_linkstate.py,sha256=guqspi2WYyAXZKIxXCApuJZsuraNEVvb7aredtlXzRw,19327
277
+ flwr/server/superlink/linkstate/linkstate.py,sha256=A0LCOH0xpq9_mTPrO1LB0MDWKhfUcOSnkKeC3-AeFHU,10256
279
278
  flwr/server/superlink/linkstate/linkstate_factory.py,sha256=ISSMjDlwuN7swxjOeYlTNpI_kuZ8PGkMcJnf1dbhUSE,2069
280
- flwr/server/superlink/linkstate/sqlite_linkstate.py,sha256=aH7XJ19QRB9KfhbBn45M9XunZXYkAqL9Jr6KvqjQwk0,39547
281
- flwr/server/superlink/linkstate/utils.py,sha256=PBPsCruHLZQC2N71-ZWue5zqs1tkv6ULgSzpQJWKkro,6481
279
+ flwr/server/superlink/linkstate/sqlite_linkstate.py,sha256=JhP6DtM0xBYtT0B0b1lwy96fY6i_C_Zm6Iq_t3VVQ5o,41245
280
+ flwr/server/superlink/linkstate/utils.py,sha256=ukrMlSv0mNFd0YSpyPDpq_ND90SBkwuKgw3FFux3lqs,6914
282
281
  flwr/server/typing.py,sha256=5kaRLZuxTEse9A0g7aVna2VhYxU3wTq1f3d3mtw7kXs,1019
283
282
  flwr/server/utils/__init__.py,sha256=pltsPHJoXmUIr3utjwwYxu7_ZAGy5u4MVHzv9iA5Un8,908
284
283
  flwr/server/utils/tensorboard.py,sha256=gEBD8w_5uaIfp5aw5RYH66lYZpd_SfkObHQ7eDd9MUk,5466
@@ -295,16 +294,16 @@ flwr/simulation/ray_transport/__init__.py,sha256=wzcEEwUUlulnXsg6raCA1nGpP3LlAQD
295
294
  flwr/simulation/ray_transport/ray_actor.py,sha256=9-XBguAm5IFqm2ddPFsQtnuuFN6lzqdb00SnCxGUGBo,18996
296
295
  flwr/simulation/ray_transport/ray_client_proxy.py,sha256=2vjOKoom3B74C6XU-jC3N6DwYmsLdB-lmkHZ_Xrv96o,7367
297
296
  flwr/simulation/ray_transport/utils.py,sha256=TYdtfg1P9VfTdLMOJlifInGpxWHYs9UfUqIv2wfkRLA,2392
298
- flwr/simulation/run_simulation.py,sha256=W3wGFXlrrTW7f1Zl1xWxtGRd2Vui7gXfIVO10a3eX8c,23217
297
+ flwr/simulation/run_simulation.py,sha256=nVLoP5rzH3bmZQ-pW9Nka2m0M1pFIEFODtIY7nHY0jQ,23412
299
298
  flwr/superexec/__init__.py,sha256=fcj366jh4RFby_vDwLroU4kepzqbnJgseZD_jUr_Mko,715
300
- flwr/superexec/app.py,sha256=KynGHvQjQpVfqydAaVHizbE3GbFFFZjOQQZleMt3SH0,6827
301
- flwr/superexec/deployment.py,sha256=TbzOAAaY2sNt7O516w1GS6N5xvt0UV-dML74O6WA2O4,6344
302
- flwr/superexec/exec_grpc.py,sha256=ZPq7EP55Vwj0kRcLVuTCokFqfIgBk-7YmDykZoMKi-c,1935
303
- flwr/superexec/exec_servicer.py,sha256=TRpwPVl7eI0Y_xlCY6DmVpAo0yFU1gLwzyIeqFw9pyk,4746
304
- flwr/superexec/executor.py,sha256=-5J-ZLs-uArro3T2pCq0YQRC65cs18M888nufzdYE4E,2375
305
- flwr/superexec/simulation.py,sha256=3z9mFhSw29Fup4RwV8ucr5eCrXI3urILWDrdC1sogik,7424
306
- flwr_nightly-1.13.0.dev20241023.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
307
- flwr_nightly-1.13.0.dev20241023.dist-info/METADATA,sha256=nWx39T1F-7bEhxqNK3UgdAaMPIEB8CxTkKxY1cLDa6w,15618
308
- flwr_nightly-1.13.0.dev20241023.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
309
- flwr_nightly-1.13.0.dev20241023.dist-info/entry_points.txt,sha256=FxJQ96pmcNF2OvkTH6XF-Ip2PNrHvykjArkvkjQC7Mk,486
310
- flwr_nightly-1.13.0.dev20241023.dist-info/RECORD,,
299
+ flwr/superexec/app.py,sha256=Tt3GonnTwHrMmicwx9XaP-crP78-bf4DUWl-N5cG6zY,1841
300
+ flwr/superexec/deployment.py,sha256=3uAauE8mI4bHwVF4_Rp26mXy18QnnIRlQD_yDo_Xs00,6780
301
+ flwr/superexec/exec_grpc.py,sha256=OuhBAk7hiky9rjGceinLGIXqchtzGPQThZnwyYv6Ei0,2241
302
+ flwr/superexec/exec_servicer.py,sha256=9MdFODQkLK_942XwaqwwIi1OP0Tiv3Mh7smj4mbreBE,5124
303
+ flwr/superexec/executor.py,sha256=125FvdpjT_awBCREm_YkLMg0YgToarVg7Y3wPt5tXQA,3126
304
+ flwr/superexec/simulation.py,sha256=PGADPXcfFVOss4uwPvFq_6vrIlkxezHppnBPAYWUBuU,7739
305
+ flwr_nightly-1.13.0.dev20241024.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
306
+ flwr_nightly-1.13.0.dev20241024.dist-info/METADATA,sha256=qKxbod-8iSCiu3FrCRwd9YDAZTsOl6yfqP7Ty4HVf7A,15618
307
+ flwr_nightly-1.13.0.dev20241024.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
308
+ flwr_nightly-1.13.0.dev20241024.dist-info/entry_points.txt,sha256=FxJQ96pmcNF2OvkTH6XF-Ip2PNrHvykjArkvkjQC7Mk,486
309
+ flwr_nightly-1.13.0.dev20241024.dist-info/RECORD,,
@@ -1,65 +0,0 @@
1
- # Copyright 2023 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
- """Node state tests."""
16
-
17
-
18
- from typing import cast
19
-
20
- from flwr.client.run_info_store import DeprecatedRunInfoStore
21
- from flwr.common import ConfigsRecord, Context
22
- from flwr.proto.task_pb2 import TaskIns # pylint: disable=E0611
23
-
24
-
25
- def _run_dummy_task(context: Context) -> Context:
26
- counter_value: str = "1"
27
- if "counter" in context.state.configs_records.keys():
28
- counter_value = cast(str, context.state.configs_records["counter"]["count"])
29
- counter_value += "1"
30
-
31
- context.state.configs_records["counter"] = ConfigsRecord({"count": counter_value})
32
-
33
- return context
34
-
35
-
36
- def test_multirun_in_node_state() -> None:
37
- """Test basic DeprecatedRunInfoStore logic."""
38
- # Tasks to perform
39
- tasks = [TaskIns(run_id=run_id) for run_id in [0, 1, 1, 2, 3, 2, 1, 5]]
40
- # the "tasks" is to count how many times each run is executed
41
- expected_values = {0: "1", 1: "1" * 3, 2: "1" * 2, 3: "1", 5: "1"}
42
-
43
- node_info_store = DeprecatedRunInfoStore(node_id=0, node_config={})
44
-
45
- for task in tasks:
46
- run_id = task.run_id
47
-
48
- # Register
49
- node_info_store.register_context(run_id=run_id)
50
-
51
- # Get run state
52
- context = node_info_store.retrieve_context(run_id=run_id)
53
-
54
- # Run "task"
55
- updated_state = _run_dummy_task(context)
56
-
57
- # Update run state
58
- node_info_store.update_context(run_id=run_id, context=updated_state)
59
-
60
- # Verify values
61
- for run_id, run_info in node_info_store.run_infos.items():
62
- assert (
63
- run_info.context.state.configs_records["counter"]["count"]
64
- == expected_values[run_id]
65
- )