flwr-nightly 1.23.0.dev20251028__py3-none-any.whl → 1.23.0.dev20251029__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.

@@ -34,14 +34,22 @@ from flwr.common.constant import (
34
34
  from flwr.common.version import package_name, package_version
35
35
  from flwr.proto.fab_pb2 import GetFabRequest, GetFabResponse # pylint: disable=E0611
36
36
  from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
37
+ ActivateNodeRequest,
38
+ ActivateNodeResponse,
37
39
  CreateNodeRequest,
38
40
  CreateNodeResponse,
41
+ DeactivateNodeRequest,
42
+ DeactivateNodeResponse,
39
43
  DeleteNodeRequest,
40
44
  DeleteNodeResponse,
41
45
  PullMessagesRequest,
42
46
  PullMessagesResponse,
43
47
  PushMessagesRequest,
44
48
  PushMessagesResponse,
49
+ RegisterNodeFleetRequest,
50
+ RegisterNodeFleetResponse,
51
+ UnregisterNodeFleetRequest,
52
+ UnregisterNodeFleetResponse,
45
53
  )
46
54
  from flwr.proto.grpcadapter_pb2 import MessageContainer # pylint: disable=E0611
47
55
  from flwr.proto.grpcadapter_pb2_grpc import GrpcAdapterStub
@@ -130,6 +138,30 @@ class GrpcAdapter:
130
138
  """."""
131
139
  return self._send_and_receive(request, DeleteNodeResponse, **kwargs)
132
140
 
141
+ def RegisterNode( # pylint: disable=C0103
142
+ self, request: RegisterNodeFleetRequest, **kwargs: Any
143
+ ) -> RegisterNodeFleetResponse:
144
+ """."""
145
+ return self._send_and_receive(request, RegisterNodeFleetResponse, **kwargs)
146
+
147
+ def ActivateNode( # pylint: disable=C0103
148
+ self, request: ActivateNodeRequest, **kwargs: Any
149
+ ) -> ActivateNodeResponse:
150
+ """."""
151
+ return self._send_and_receive(request, ActivateNodeResponse, **kwargs)
152
+
153
+ def DeactivateNode( # pylint: disable=C0103
154
+ self, request: DeactivateNodeRequest, **kwargs: Any
155
+ ) -> DeactivateNodeResponse:
156
+ """."""
157
+ return self._send_and_receive(request, DeactivateNodeResponse, **kwargs)
158
+
159
+ def UnregisterNode( # pylint: disable=C0103
160
+ self, request: UnregisterNodeFleetRequest, **kwargs: Any
161
+ ) -> UnregisterNodeFleetResponse:
162
+ """."""
163
+ return self._send_and_receive(request, UnregisterNodeFleetResponse, **kwargs)
164
+
133
165
  def SendNodeHeartbeat( # pylint: disable=C0103
134
166
  self, request: SendNodeHeartbeatRequest, **kwargs: Any
135
167
  ) -> SendNodeHeartbeatResponse:
flwr/proto/fleet_pb2.py CHANGED
@@ -19,7 +19,7 @@ from flwr.proto import fab_pb2 as flwr_dot_proto_dot_fab__pb2
19
19
  from flwr.proto import message_pb2 as flwr_dot_proto_dot_message__pb2
20
20
 
21
21
 
22
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x16\x66lwr/proto/fleet.proto\x12\nflwr.proto\x1a\x1a\x66lwr/proto/heartbeat.proto\x1a\x15\x66lwr/proto/node.proto\x1a\x14\x66lwr/proto/run.proto\x1a\x14\x66lwr/proto/fab.proto\x1a\x18\x66lwr/proto/message.proto\"C\n\x11\x43reateNodeRequest\x12\x12\n\npublic_key\x18\x01 \x01(\x0c\x12\x1a\n\x12heartbeat_interval\x18\x02 \x01(\x01\"4\n\x12\x43reateNodeResponse\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\"3\n\x11\x44\x65leteNodeRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\"\x14\n\x12\x44\x65leteNodeResponse\"J\n\x13PullMessagesRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12\x13\n\x0bmessage_ids\x18\x02 \x03(\t\"\xa2\x01\n\x14PullMessagesResponse\x12(\n\treconnect\x18\x01 \x01(\x0b\x32\x15.flwr.proto.Reconnect\x12*\n\rmessages_list\x18\x02 \x03(\x0b\x32\x13.flwr.proto.Message\x12\x34\n\x14message_object_trees\x18\x03 \x03(\x0b\x32\x16.flwr.proto.ObjectTree\"\x97\x01\n\x13PushMessagesRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12*\n\rmessages_list\x18\x02 \x03(\x0b\x32\x13.flwr.proto.Message\x12\x34\n\x14message_object_trees\x18\x03 \x03(\x0b\x32\x16.flwr.proto.ObjectTree\"\xc9\x01\n\x14PushMessagesResponse\x12(\n\treconnect\x18\x01 \x01(\x0b\x32\x15.flwr.proto.Reconnect\x12>\n\x07results\x18\x02 \x03(\x0b\x32-.flwr.proto.PushMessagesResponse.ResultsEntry\x12\x17\n\x0fobjects_to_push\x18\x03 \x03(\t\x1a.\n\x0cResultsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\r:\x02\x38\x01\"\x1e\n\tReconnect\x12\x11\n\treconnect\x18\x01 \x01(\x04\x32\xca\x06\n\x05\x46leet\x12M\n\nCreateNode\x12\x1d.flwr.proto.CreateNodeRequest\x1a\x1e.flwr.proto.CreateNodeResponse\"\x00\x12M\n\nDeleteNode\x12\x1d.flwr.proto.DeleteNodeRequest\x1a\x1e.flwr.proto.DeleteNodeResponse\"\x00\x12\x62\n\x11SendNodeHeartbeat\x12$.flwr.proto.SendNodeHeartbeatRequest\x1a%.flwr.proto.SendNodeHeartbeatResponse\"\x00\x12S\n\x0cPullMessages\x12\x1f.flwr.proto.PullMessagesRequest\x1a .flwr.proto.PullMessagesResponse\"\x00\x12S\n\x0cPushMessages\x12\x1f.flwr.proto.PushMessagesRequest\x1a .flwr.proto.PushMessagesResponse\"\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\x12M\n\nPushObject\x12\x1d.flwr.proto.PushObjectRequest\x1a\x1e.flwr.proto.PushObjectResponse\"\x00\x12M\n\nPullObject\x12\x1d.flwr.proto.PullObjectRequest\x1a\x1e.flwr.proto.PullObjectResponse\"\x00\x12q\n\x16\x43onfirmMessageReceived\x12).flwr.proto.ConfirmMessageReceivedRequest\x1a*.flwr.proto.ConfirmMessageReceivedResponse\"\x00\x62\x06proto3')
22
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x16\x66lwr/proto/fleet.proto\x12\nflwr.proto\x1a\x1a\x66lwr/proto/heartbeat.proto\x1a\x15\x66lwr/proto/node.proto\x1a\x14\x66lwr/proto/run.proto\x1a\x14\x66lwr/proto/fab.proto\x1a\x18\x66lwr/proto/message.proto\"C\n\x11\x43reateNodeRequest\x12\x12\n\npublic_key\x18\x01 \x01(\x0c\x12\x1a\n\x12heartbeat_interval\x18\x02 \x01(\x01\"4\n\x12\x43reateNodeResponse\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\"3\n\x11\x44\x65leteNodeRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\"\x14\n\x12\x44\x65leteNodeResponse\".\n\x18RegisterNodeFleetRequest\x12\x12\n\npublic_key\x18\x01 \x01(\x0c\"\x1b\n\x19RegisterNodeFleetResponse\"E\n\x13\x41\x63tivateNodeRequest\x12\x12\n\npublic_key\x18\x01 \x01(\x0c\x12\x1a\n\x12heartbeat_interval\x18\x02 \x01(\x01\"\'\n\x14\x41\x63tivateNodeResponse\x12\x0f\n\x07node_id\x18\x01 \x01(\x04\"(\n\x15\x44\x65\x61\x63tivateNodeRequest\x12\x0f\n\x07node_id\x18\x01 \x01(\x04\"\x18\n\x16\x44\x65\x61\x63tivateNodeResponse\"-\n\x1aUnregisterNodeFleetRequest\x12\x0f\n\x07node_id\x18\x01 \x01(\x04\"\x1d\n\x1bUnregisterNodeFleetResponse\"J\n\x13PullMessagesRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12\x13\n\x0bmessage_ids\x18\x02 \x03(\t\"\xa2\x01\n\x14PullMessagesResponse\x12(\n\treconnect\x18\x01 \x01(\x0b\x32\x15.flwr.proto.Reconnect\x12*\n\rmessages_list\x18\x02 \x03(\x0b\x32\x13.flwr.proto.Message\x12\x34\n\x14message_object_trees\x18\x03 \x03(\x0b\x32\x16.flwr.proto.ObjectTree\"\x97\x01\n\x13PushMessagesRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12*\n\rmessages_list\x18\x02 \x03(\x0b\x32\x13.flwr.proto.Message\x12\x34\n\x14message_object_trees\x18\x03 \x03(\x0b\x32\x16.flwr.proto.ObjectTree\"\xc9\x01\n\x14PushMessagesResponse\x12(\n\treconnect\x18\x01 \x01(\x0b\x32\x15.flwr.proto.Reconnect\x12>\n\x07results\x18\x02 \x03(\x0b\x32-.flwr.proto.PushMessagesResponse.ResultsEntry\x12\x17\n\x0fobjects_to_push\x18\x03 \x03(\t\x1a.\n\x0cResultsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\r:\x02\x38\x01\"\x1e\n\tReconnect\x12\x11\n\treconnect\x18\x01 \x01(\x04\x32\xbe\t\n\x05\x46leet\x12M\n\nCreateNode\x12\x1d.flwr.proto.CreateNodeRequest\x1a\x1e.flwr.proto.CreateNodeResponse\"\x00\x12M\n\nDeleteNode\x12\x1d.flwr.proto.DeleteNodeRequest\x1a\x1e.flwr.proto.DeleteNodeResponse\"\x00\x12]\n\x0cRegisterNode\x12$.flwr.proto.RegisterNodeFleetRequest\x1a%.flwr.proto.RegisterNodeFleetResponse\"\x00\x12S\n\x0c\x41\x63tivateNode\x12\x1f.flwr.proto.ActivateNodeRequest\x1a .flwr.proto.ActivateNodeResponse\"\x00\x12Y\n\x0e\x44\x65\x61\x63tivateNode\x12!.flwr.proto.DeactivateNodeRequest\x1a\".flwr.proto.DeactivateNodeResponse\"\x00\x12\x63\n\x0eUnregisterNode\x12&.flwr.proto.UnregisterNodeFleetRequest\x1a\'.flwr.proto.UnregisterNodeFleetResponse\"\x00\x12\x62\n\x11SendNodeHeartbeat\x12$.flwr.proto.SendNodeHeartbeatRequest\x1a%.flwr.proto.SendNodeHeartbeatResponse\"\x00\x12S\n\x0cPullMessages\x12\x1f.flwr.proto.PullMessagesRequest\x1a .flwr.proto.PullMessagesResponse\"\x00\x12S\n\x0cPushMessages\x12\x1f.flwr.proto.PushMessagesRequest\x1a .flwr.proto.PushMessagesResponse\"\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\x12M\n\nPushObject\x12\x1d.flwr.proto.PushObjectRequest\x1a\x1e.flwr.proto.PushObjectResponse\"\x00\x12M\n\nPullObject\x12\x1d.flwr.proto.PullObjectRequest\x1a\x1e.flwr.proto.PullObjectResponse\"\x00\x12q\n\x16\x43onfirmMessageReceived\x12).flwr.proto.ConfirmMessageReceivedRequest\x1a*.flwr.proto.ConfirmMessageReceivedResponse\"\x00\x62\x06proto3')
23
23
 
24
24
  _globals = globals()
25
25
  _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -36,18 +36,34 @@ if _descriptor._USE_C_DESCRIPTORS == False:
36
36
  _globals['_DELETENODEREQUEST']._serialized_end=333
37
37
  _globals['_DELETENODERESPONSE']._serialized_start=335
38
38
  _globals['_DELETENODERESPONSE']._serialized_end=355
39
- _globals['_PULLMESSAGESREQUEST']._serialized_start=357
40
- _globals['_PULLMESSAGESREQUEST']._serialized_end=431
41
- _globals['_PULLMESSAGESRESPONSE']._serialized_start=434
42
- _globals['_PULLMESSAGESRESPONSE']._serialized_end=596
43
- _globals['_PUSHMESSAGESREQUEST']._serialized_start=599
44
- _globals['_PUSHMESSAGESREQUEST']._serialized_end=750
45
- _globals['_PUSHMESSAGESRESPONSE']._serialized_start=753
46
- _globals['_PUSHMESSAGESRESPONSE']._serialized_end=954
47
- _globals['_PUSHMESSAGESRESPONSE_RESULTSENTRY']._serialized_start=908
48
- _globals['_PUSHMESSAGESRESPONSE_RESULTSENTRY']._serialized_end=954
49
- _globals['_RECONNECT']._serialized_start=956
50
- _globals['_RECONNECT']._serialized_end=986
51
- _globals['_FLEET']._serialized_start=989
52
- _globals['_FLEET']._serialized_end=1831
39
+ _globals['_REGISTERNODEFLEETREQUEST']._serialized_start=357
40
+ _globals['_REGISTERNODEFLEETREQUEST']._serialized_end=403
41
+ _globals['_REGISTERNODEFLEETRESPONSE']._serialized_start=405
42
+ _globals['_REGISTERNODEFLEETRESPONSE']._serialized_end=432
43
+ _globals['_ACTIVATENODEREQUEST']._serialized_start=434
44
+ _globals['_ACTIVATENODEREQUEST']._serialized_end=503
45
+ _globals['_ACTIVATENODERESPONSE']._serialized_start=505
46
+ _globals['_ACTIVATENODERESPONSE']._serialized_end=544
47
+ _globals['_DEACTIVATENODEREQUEST']._serialized_start=546
48
+ _globals['_DEACTIVATENODEREQUEST']._serialized_end=586
49
+ _globals['_DEACTIVATENODERESPONSE']._serialized_start=588
50
+ _globals['_DEACTIVATENODERESPONSE']._serialized_end=612
51
+ _globals['_UNREGISTERNODEFLEETREQUEST']._serialized_start=614
52
+ _globals['_UNREGISTERNODEFLEETREQUEST']._serialized_end=659
53
+ _globals['_UNREGISTERNODEFLEETRESPONSE']._serialized_start=661
54
+ _globals['_UNREGISTERNODEFLEETRESPONSE']._serialized_end=690
55
+ _globals['_PULLMESSAGESREQUEST']._serialized_start=692
56
+ _globals['_PULLMESSAGESREQUEST']._serialized_end=766
57
+ _globals['_PULLMESSAGESRESPONSE']._serialized_start=769
58
+ _globals['_PULLMESSAGESRESPONSE']._serialized_end=931
59
+ _globals['_PUSHMESSAGESREQUEST']._serialized_start=934
60
+ _globals['_PUSHMESSAGESREQUEST']._serialized_end=1085
61
+ _globals['_PUSHMESSAGESRESPONSE']._serialized_start=1088
62
+ _globals['_PUSHMESSAGESRESPONSE']._serialized_end=1289
63
+ _globals['_PUSHMESSAGESRESPONSE_RESULTSENTRY']._serialized_start=1243
64
+ _globals['_PUSHMESSAGESRESPONSE_RESULTSENTRY']._serialized_end=1289
65
+ _globals['_RECONNECT']._serialized_start=1291
66
+ _globals['_RECONNECT']._serialized_end=1321
67
+ _globals['_FLEET']._serialized_start=1324
68
+ _globals['_FLEET']._serialized_end=2538
53
69
  # @@protoc_insertion_point(module_scope)
flwr/proto/fleet_pb2.pyi CHANGED
@@ -61,6 +61,86 @@ class DeleteNodeResponse(google.protobuf.message.Message):
61
61
  ) -> None: ...
62
62
  global___DeleteNodeResponse = DeleteNodeResponse
63
63
 
64
+ class RegisterNodeFleetRequest(google.protobuf.message.Message):
65
+ """RegisterNode messages (add prefix to avoid name clash)"""
66
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
67
+ PUBLIC_KEY_FIELD_NUMBER: builtins.int
68
+ public_key: builtins.bytes
69
+ def __init__(self,
70
+ *,
71
+ public_key: builtins.bytes = ...,
72
+ ) -> None: ...
73
+ def ClearField(self, field_name: typing_extensions.Literal["public_key",b"public_key"]) -> None: ...
74
+ global___RegisterNodeFleetRequest = RegisterNodeFleetRequest
75
+
76
+ class RegisterNodeFleetResponse(google.protobuf.message.Message):
77
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
78
+ def __init__(self,
79
+ ) -> None: ...
80
+ global___RegisterNodeFleetResponse = RegisterNodeFleetResponse
81
+
82
+ class ActivateNodeRequest(google.protobuf.message.Message):
83
+ """ActivateNode messages"""
84
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
85
+ PUBLIC_KEY_FIELD_NUMBER: builtins.int
86
+ HEARTBEAT_INTERVAL_FIELD_NUMBER: builtins.int
87
+ public_key: builtins.bytes
88
+ heartbeat_interval: builtins.float
89
+ def __init__(self,
90
+ *,
91
+ public_key: builtins.bytes = ...,
92
+ heartbeat_interval: builtins.float = ...,
93
+ ) -> None: ...
94
+ def ClearField(self, field_name: typing_extensions.Literal["heartbeat_interval",b"heartbeat_interval","public_key",b"public_key"]) -> None: ...
95
+ global___ActivateNodeRequest = ActivateNodeRequest
96
+
97
+ class ActivateNodeResponse(google.protobuf.message.Message):
98
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
99
+ NODE_ID_FIELD_NUMBER: builtins.int
100
+ node_id: builtins.int
101
+ def __init__(self,
102
+ *,
103
+ node_id: builtins.int = ...,
104
+ ) -> None: ...
105
+ def ClearField(self, field_name: typing_extensions.Literal["node_id",b"node_id"]) -> None: ...
106
+ global___ActivateNodeResponse = ActivateNodeResponse
107
+
108
+ class DeactivateNodeRequest(google.protobuf.message.Message):
109
+ """DeactivateNode messages"""
110
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
111
+ NODE_ID_FIELD_NUMBER: builtins.int
112
+ node_id: builtins.int
113
+ def __init__(self,
114
+ *,
115
+ node_id: builtins.int = ...,
116
+ ) -> None: ...
117
+ def ClearField(self, field_name: typing_extensions.Literal["node_id",b"node_id"]) -> None: ...
118
+ global___DeactivateNodeRequest = DeactivateNodeRequest
119
+
120
+ class DeactivateNodeResponse(google.protobuf.message.Message):
121
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
122
+ def __init__(self,
123
+ ) -> None: ...
124
+ global___DeactivateNodeResponse = DeactivateNodeResponse
125
+
126
+ class UnregisterNodeFleetRequest(google.protobuf.message.Message):
127
+ """UnregisterNode messages (add prefix to avoid name clash)"""
128
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
129
+ NODE_ID_FIELD_NUMBER: builtins.int
130
+ node_id: builtins.int
131
+ def __init__(self,
132
+ *,
133
+ node_id: builtins.int = ...,
134
+ ) -> None: ...
135
+ def ClearField(self, field_name: typing_extensions.Literal["node_id",b"node_id"]) -> None: ...
136
+ global___UnregisterNodeFleetRequest = UnregisterNodeFleetRequest
137
+
138
+ class UnregisterNodeFleetResponse(google.protobuf.message.Message):
139
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
140
+ def __init__(self,
141
+ ) -> None: ...
142
+ global___UnregisterNodeFleetResponse = UnregisterNodeFleetResponse
143
+
64
144
  class PullMessagesRequest(google.protobuf.message.Message):
65
145
  """PullMessages messages"""
66
146
  DESCRIPTOR: google.protobuf.descriptor.Descriptor
@@ -28,6 +28,26 @@ class FleetStub(object):
28
28
  request_serializer=flwr_dot_proto_dot_fleet__pb2.DeleteNodeRequest.SerializeToString,
29
29
  response_deserializer=flwr_dot_proto_dot_fleet__pb2.DeleteNodeResponse.FromString,
30
30
  )
31
+ self.RegisterNode = channel.unary_unary(
32
+ '/flwr.proto.Fleet/RegisterNode',
33
+ request_serializer=flwr_dot_proto_dot_fleet__pb2.RegisterNodeFleetRequest.SerializeToString,
34
+ response_deserializer=flwr_dot_proto_dot_fleet__pb2.RegisterNodeFleetResponse.FromString,
35
+ )
36
+ self.ActivateNode = channel.unary_unary(
37
+ '/flwr.proto.Fleet/ActivateNode',
38
+ request_serializer=flwr_dot_proto_dot_fleet__pb2.ActivateNodeRequest.SerializeToString,
39
+ response_deserializer=flwr_dot_proto_dot_fleet__pb2.ActivateNodeResponse.FromString,
40
+ )
41
+ self.DeactivateNode = channel.unary_unary(
42
+ '/flwr.proto.Fleet/DeactivateNode',
43
+ request_serializer=flwr_dot_proto_dot_fleet__pb2.DeactivateNodeRequest.SerializeToString,
44
+ response_deserializer=flwr_dot_proto_dot_fleet__pb2.DeactivateNodeResponse.FromString,
45
+ )
46
+ self.UnregisterNode = channel.unary_unary(
47
+ '/flwr.proto.Fleet/UnregisterNode',
48
+ request_serializer=flwr_dot_proto_dot_fleet__pb2.UnregisterNodeFleetRequest.SerializeToString,
49
+ response_deserializer=flwr_dot_proto_dot_fleet__pb2.UnregisterNodeFleetResponse.FromString,
50
+ )
31
51
  self.SendNodeHeartbeat = channel.unary_unary(
32
52
  '/flwr.proto.Fleet/SendNodeHeartbeat',
33
53
  request_serializer=flwr_dot_proto_dot_heartbeat__pb2.SendNodeHeartbeatRequest.SerializeToString,
@@ -85,6 +105,34 @@ class FleetServicer(object):
85
105
  context.set_details('Method not implemented!')
86
106
  raise NotImplementedError('Method not implemented!')
87
107
 
108
+ def RegisterNode(self, request, context):
109
+ """Register Node
110
+ """
111
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
112
+ context.set_details('Method not implemented!')
113
+ raise NotImplementedError('Method not implemented!')
114
+
115
+ def ActivateNode(self, request, context):
116
+ """Activate Node
117
+ """
118
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
119
+ context.set_details('Method not implemented!')
120
+ raise NotImplementedError('Method not implemented!')
121
+
122
+ def DeactivateNode(self, request, context):
123
+ """Deactivate Node
124
+ """
125
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
126
+ context.set_details('Method not implemented!')
127
+ raise NotImplementedError('Method not implemented!')
128
+
129
+ def UnregisterNode(self, request, context):
130
+ """Unregister Node
131
+ """
132
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
133
+ context.set_details('Method not implemented!')
134
+ raise NotImplementedError('Method not implemented!')
135
+
88
136
  def SendNodeHeartbeat(self, request, context):
89
137
  """Missing associated documentation comment in .proto file."""
90
138
  context.set_code(grpc.StatusCode.UNIMPLEMENTED)
@@ -156,6 +204,26 @@ def add_FleetServicer_to_server(servicer, server):
156
204
  request_deserializer=flwr_dot_proto_dot_fleet__pb2.DeleteNodeRequest.FromString,
157
205
  response_serializer=flwr_dot_proto_dot_fleet__pb2.DeleteNodeResponse.SerializeToString,
158
206
  ),
207
+ 'RegisterNode': grpc.unary_unary_rpc_method_handler(
208
+ servicer.RegisterNode,
209
+ request_deserializer=flwr_dot_proto_dot_fleet__pb2.RegisterNodeFleetRequest.FromString,
210
+ response_serializer=flwr_dot_proto_dot_fleet__pb2.RegisterNodeFleetResponse.SerializeToString,
211
+ ),
212
+ 'ActivateNode': grpc.unary_unary_rpc_method_handler(
213
+ servicer.ActivateNode,
214
+ request_deserializer=flwr_dot_proto_dot_fleet__pb2.ActivateNodeRequest.FromString,
215
+ response_serializer=flwr_dot_proto_dot_fleet__pb2.ActivateNodeResponse.SerializeToString,
216
+ ),
217
+ 'DeactivateNode': grpc.unary_unary_rpc_method_handler(
218
+ servicer.DeactivateNode,
219
+ request_deserializer=flwr_dot_proto_dot_fleet__pb2.DeactivateNodeRequest.FromString,
220
+ response_serializer=flwr_dot_proto_dot_fleet__pb2.DeactivateNodeResponse.SerializeToString,
221
+ ),
222
+ 'UnregisterNode': grpc.unary_unary_rpc_method_handler(
223
+ servicer.UnregisterNode,
224
+ request_deserializer=flwr_dot_proto_dot_fleet__pb2.UnregisterNodeFleetRequest.FromString,
225
+ response_serializer=flwr_dot_proto_dot_fleet__pb2.UnregisterNodeFleetResponse.SerializeToString,
226
+ ),
159
227
  'SendNodeHeartbeat': grpc.unary_unary_rpc_method_handler(
160
228
  servicer.SendNodeHeartbeat,
161
229
  request_deserializer=flwr_dot_proto_dot_heartbeat__pb2.SendNodeHeartbeatRequest.FromString,
@@ -240,6 +308,74 @@ class Fleet(object):
240
308
  options, channel_credentials,
241
309
  insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
242
310
 
311
+ @staticmethod
312
+ def RegisterNode(request,
313
+ target,
314
+ options=(),
315
+ channel_credentials=None,
316
+ call_credentials=None,
317
+ insecure=False,
318
+ compression=None,
319
+ wait_for_ready=None,
320
+ timeout=None,
321
+ metadata=None):
322
+ return grpc.experimental.unary_unary(request, target, '/flwr.proto.Fleet/RegisterNode',
323
+ flwr_dot_proto_dot_fleet__pb2.RegisterNodeFleetRequest.SerializeToString,
324
+ flwr_dot_proto_dot_fleet__pb2.RegisterNodeFleetResponse.FromString,
325
+ options, channel_credentials,
326
+ insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
327
+
328
+ @staticmethod
329
+ def ActivateNode(request,
330
+ target,
331
+ options=(),
332
+ channel_credentials=None,
333
+ call_credentials=None,
334
+ insecure=False,
335
+ compression=None,
336
+ wait_for_ready=None,
337
+ timeout=None,
338
+ metadata=None):
339
+ return grpc.experimental.unary_unary(request, target, '/flwr.proto.Fleet/ActivateNode',
340
+ flwr_dot_proto_dot_fleet__pb2.ActivateNodeRequest.SerializeToString,
341
+ flwr_dot_proto_dot_fleet__pb2.ActivateNodeResponse.FromString,
342
+ options, channel_credentials,
343
+ insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
344
+
345
+ @staticmethod
346
+ def DeactivateNode(request,
347
+ target,
348
+ options=(),
349
+ channel_credentials=None,
350
+ call_credentials=None,
351
+ insecure=False,
352
+ compression=None,
353
+ wait_for_ready=None,
354
+ timeout=None,
355
+ metadata=None):
356
+ return grpc.experimental.unary_unary(request, target, '/flwr.proto.Fleet/DeactivateNode',
357
+ flwr_dot_proto_dot_fleet__pb2.DeactivateNodeRequest.SerializeToString,
358
+ flwr_dot_proto_dot_fleet__pb2.DeactivateNodeResponse.FromString,
359
+ options, channel_credentials,
360
+ insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
361
+
362
+ @staticmethod
363
+ def UnregisterNode(request,
364
+ target,
365
+ options=(),
366
+ channel_credentials=None,
367
+ call_credentials=None,
368
+ insecure=False,
369
+ compression=None,
370
+ wait_for_ready=None,
371
+ timeout=None,
372
+ metadata=None):
373
+ return grpc.experimental.unary_unary(request, target, '/flwr.proto.Fleet/UnregisterNode',
374
+ flwr_dot_proto_dot_fleet__pb2.UnregisterNodeFleetRequest.SerializeToString,
375
+ flwr_dot_proto_dot_fleet__pb2.UnregisterNodeFleetResponse.FromString,
376
+ options, channel_credentials,
377
+ insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
378
+
243
379
  @staticmethod
244
380
  def SendNodeHeartbeat(request,
245
381
  target,
@@ -20,6 +20,26 @@ class FleetStub:
20
20
  flwr.proto.fleet_pb2.DeleteNodeRequest,
21
21
  flwr.proto.fleet_pb2.DeleteNodeResponse]
22
22
 
23
+ RegisterNode: grpc.UnaryUnaryMultiCallable[
24
+ flwr.proto.fleet_pb2.RegisterNodeFleetRequest,
25
+ flwr.proto.fleet_pb2.RegisterNodeFleetResponse]
26
+ """Register Node"""
27
+
28
+ ActivateNode: grpc.UnaryUnaryMultiCallable[
29
+ flwr.proto.fleet_pb2.ActivateNodeRequest,
30
+ flwr.proto.fleet_pb2.ActivateNodeResponse]
31
+ """Activate Node"""
32
+
33
+ DeactivateNode: grpc.UnaryUnaryMultiCallable[
34
+ flwr.proto.fleet_pb2.DeactivateNodeRequest,
35
+ flwr.proto.fleet_pb2.DeactivateNodeResponse]
36
+ """Deactivate Node"""
37
+
38
+ UnregisterNode: grpc.UnaryUnaryMultiCallable[
39
+ flwr.proto.fleet_pb2.UnregisterNodeFleetRequest,
40
+ flwr.proto.fleet_pb2.UnregisterNodeFleetResponse]
41
+ """Unregister Node"""
42
+
23
43
  SendNodeHeartbeat: grpc.UnaryUnaryMultiCallable[
24
44
  flwr.proto.heartbeat_pb2.SendNodeHeartbeatRequest,
25
45
  flwr.proto.heartbeat_pb2.SendNodeHeartbeatResponse]
@@ -78,6 +98,38 @@ class FleetServicer(metaclass=abc.ABCMeta):
78
98
  context: grpc.ServicerContext,
79
99
  ) -> flwr.proto.fleet_pb2.DeleteNodeResponse: ...
80
100
 
101
+ @abc.abstractmethod
102
+ def RegisterNode(self,
103
+ request: flwr.proto.fleet_pb2.RegisterNodeFleetRequest,
104
+ context: grpc.ServicerContext,
105
+ ) -> flwr.proto.fleet_pb2.RegisterNodeFleetResponse:
106
+ """Register Node"""
107
+ pass
108
+
109
+ @abc.abstractmethod
110
+ def ActivateNode(self,
111
+ request: flwr.proto.fleet_pb2.ActivateNodeRequest,
112
+ context: grpc.ServicerContext,
113
+ ) -> flwr.proto.fleet_pb2.ActivateNodeResponse:
114
+ """Activate Node"""
115
+ pass
116
+
117
+ @abc.abstractmethod
118
+ def DeactivateNode(self,
119
+ request: flwr.proto.fleet_pb2.DeactivateNodeRequest,
120
+ context: grpc.ServicerContext,
121
+ ) -> flwr.proto.fleet_pb2.DeactivateNodeResponse:
122
+ """Deactivate Node"""
123
+ pass
124
+
125
+ @abc.abstractmethod
126
+ def UnregisterNode(self,
127
+ request: flwr.proto.fleet_pb2.UnregisterNodeFleetRequest,
128
+ context: grpc.ServicerContext,
129
+ ) -> flwr.proto.fleet_pb2.UnregisterNodeFleetResponse:
130
+ """Unregister Node"""
131
+ pass
132
+
81
133
  @abc.abstractmethod
82
134
  def SendNodeHeartbeat(self,
83
135
  request: flwr.proto.heartbeat_pb2.SendNodeHeartbeatRequest,
@@ -33,10 +33,14 @@ from flwr.common.version import package_name, package_version
33
33
  from flwr.proto import grpcadapter_pb2_grpc # pylint: disable=E0611
34
34
  from flwr.proto.fab_pb2 import GetFabRequest # pylint: disable=E0611
35
35
  from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
36
+ ActivateNodeRequest,
36
37
  CreateNodeRequest,
38
+ DeactivateNodeRequest,
37
39
  DeleteNodeRequest,
38
40
  PullMessagesRequest,
39
41
  PushMessagesRequest,
42
+ RegisterNodeFleetRequest,
43
+ UnregisterNodeFleetRequest,
40
44
  )
41
45
  from flwr.proto.grpcadapter_pb2 import MessageContainer # pylint: disable=E0611
42
46
  from flwr.proto.heartbeat_pb2 import SendNodeHeartbeatRequest # pylint: disable=E0611
@@ -77,7 +81,7 @@ def _handle(
77
81
  class GrpcAdapterServicer(grpcadapter_pb2_grpc.GrpcAdapterServicer, FleetServicer):
78
82
  """Fleet API via GrpcAdapter servicer."""
79
83
 
80
- def SendReceive( # pylint: disable=too-many-return-statements
84
+ def SendReceive( # pylint: disable=too-many-return-statements, too-many-branches
81
85
  self, request: MessageContainer, context: grpc.ServicerContext
82
86
  ) -> MessageContainer:
83
87
  """."""
@@ -86,6 +90,18 @@ class GrpcAdapterServicer(grpcadapter_pb2_grpc.GrpcAdapterServicer, FleetService
86
90
  return _handle(request, context, CreateNodeRequest, self.CreateNode)
87
91
  if request.grpc_message_name == DeleteNodeRequest.__qualname__:
88
92
  return _handle(request, context, DeleteNodeRequest, self.DeleteNode)
93
+ if request.grpc_message_name == RegisterNodeFleetRequest.__qualname__:
94
+ return _handle(
95
+ request, context, RegisterNodeFleetRequest, self.RegisterNode
96
+ )
97
+ if request.grpc_message_name == ActivateNodeRequest.__qualname__:
98
+ return _handle(request, context, ActivateNodeRequest, self.ActivateNode)
99
+ if request.grpc_message_name == DeactivateNodeRequest.__qualname__:
100
+ return _handle(request, context, DeactivateNodeRequest, self.DeactivateNode)
101
+ if request.grpc_message_name == UnregisterNodeFleetRequest.__qualname__:
102
+ return _handle(
103
+ request, context, UnregisterNodeFleetRequest, self.UnregisterNode
104
+ )
89
105
  if request.grpc_message_name == SendNodeHeartbeatRequest.__qualname__:
90
106
  return _handle(
91
107
  request, context, SendNodeHeartbeatRequest, self.SendNodeHeartbeat
@@ -28,14 +28,22 @@ from flwr.common.typing import InvalidRunStatusException
28
28
  from flwr.proto import fleet_pb2_grpc # pylint: disable=E0611
29
29
  from flwr.proto.fab_pb2 import GetFabRequest, GetFabResponse # pylint: disable=E0611
30
30
  from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
31
+ ActivateNodeRequest,
32
+ ActivateNodeResponse,
31
33
  CreateNodeRequest,
32
34
  CreateNodeResponse,
35
+ DeactivateNodeRequest,
36
+ DeactivateNodeResponse,
33
37
  DeleteNodeRequest,
34
38
  DeleteNodeResponse,
35
39
  PullMessagesRequest,
36
40
  PullMessagesResponse,
37
41
  PushMessagesRequest,
38
42
  PushMessagesResponse,
43
+ RegisterNodeFleetRequest,
44
+ RegisterNodeFleetResponse,
45
+ UnregisterNodeFleetRequest,
46
+ UnregisterNodeFleetResponse,
39
47
  )
40
48
  from flwr.proto.heartbeat_pb2 import ( # pylint: disable=E0611
41
49
  SendNodeHeartbeatRequest,
@@ -140,6 +148,50 @@ class FleetServicer(fleet_pb2_grpc.FleetServicer):
140
148
  log(DEBUG, "[Fleet.CreateNode] Response: %s", MessageToDict(response))
141
149
  return response
142
150
 
151
+ def RegisterNode(
152
+ self, request: RegisterNodeFleetRequest, context: grpc.ServicerContext
153
+ ) -> RegisterNodeFleetResponse:
154
+ """Register a node (not implemented)."""
155
+ log(ERROR, "[Fleet.RegisterNode] RegisterNode is not implemented")
156
+ context.abort(
157
+ grpc.StatusCode.UNIMPLEMENTED,
158
+ "RegisterNode RPC is not yet implemented",
159
+ )
160
+ raise NotImplementedError
161
+
162
+ def ActivateNode(
163
+ self, request: ActivateNodeRequest, context: grpc.ServicerContext
164
+ ) -> ActivateNodeResponse:
165
+ """Activate a node (not implemented)."""
166
+ log(ERROR, "[Fleet.ActivateNode] ActivateNode is not implemented")
167
+ context.abort(
168
+ grpc.StatusCode.UNIMPLEMENTED,
169
+ "ActivateNode RPC is not yet implemented",
170
+ )
171
+ raise NotImplementedError
172
+
173
+ def DeactivateNode(
174
+ self, request: DeactivateNodeRequest, context: grpc.ServicerContext
175
+ ) -> DeactivateNodeResponse:
176
+ """Deactivate a node (not implemented)."""
177
+ log(ERROR, "[Fleet.DeactivateNode] DeactivateNode is not implemented")
178
+ context.abort(
179
+ grpc.StatusCode.UNIMPLEMENTED,
180
+ "DeactivateNode RPC is not yet implemented",
181
+ )
182
+ raise NotImplementedError
183
+
184
+ def UnregisterNode(
185
+ self, request: UnregisterNodeFleetRequest, context: grpc.ServicerContext
186
+ ) -> UnregisterNodeFleetResponse:
187
+ """Unregister a node (not implemented)."""
188
+ log(ERROR, "[Fleet.UnregisterNode] UnregisterNode is not implemented")
189
+ context.abort(
190
+ grpc.StatusCode.UNIMPLEMENTED,
191
+ "UnregisterNode RPC is not yet implemented",
192
+ )
193
+ raise NotImplementedError
194
+
143
195
  def DeleteNode(
144
196
  self, request: DeleteNodeRequest, context: grpc.ServicerContext
145
197
  ) -> DeleteNodeResponse:
@@ -25,14 +25,22 @@ from google.protobuf.message import Message as GrpcMessage
25
25
  from flwr.common.exit import ExitCode, flwr_exit
26
26
  from flwr.proto.fab_pb2 import GetFabRequest, GetFabResponse # pylint: disable=E0611
27
27
  from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
28
+ ActivateNodeRequest,
29
+ ActivateNodeResponse,
28
30
  CreateNodeRequest,
29
31
  CreateNodeResponse,
32
+ DeactivateNodeRequest,
33
+ DeactivateNodeResponse,
30
34
  DeleteNodeRequest,
31
35
  DeleteNodeResponse,
32
36
  PullMessagesRequest,
33
37
  PullMessagesResponse,
34
38
  PushMessagesRequest,
35
39
  PushMessagesResponse,
40
+ RegisterNodeFleetRequest,
41
+ RegisterNodeFleetResponse,
42
+ UnregisterNodeFleetRequest,
43
+ UnregisterNodeFleetResponse,
36
44
  )
37
45
  from flwr.proto.heartbeat_pb2 import ( # pylint: disable=E0611
38
46
  SendNodeHeartbeatRequest,
@@ -69,6 +77,8 @@ GrpcResponse = TypeVar("GrpcResponse", bound=GrpcMessage)
69
77
  GrpcAsyncFunction = Callable[[GrpcRequest], Awaitable[GrpcResponse]]
70
78
  RestEndPoint = Callable[[Request], Awaitable[Response]]
71
79
 
80
+ routes = []
81
+
72
82
 
73
83
  def rest_request_response(
74
84
  grpc_request_type: type[GrpcRequest],
@@ -76,6 +86,7 @@ def rest_request_response(
76
86
  """Convert an async gRPC-based function into a RESTful HTTP endpoint."""
77
87
 
78
88
  def decorator(func: GrpcAsyncFunction[GrpcRequest, GrpcResponse]) -> RestEndPoint:
89
+
79
90
  async def wrapper(request: Request) -> Response:
80
91
  _check_headers(request.headers)
81
92
 
@@ -91,6 +102,9 @@ def rest_request_response(
91
102
  headers={"Content-Type": "application/protobuf"},
92
103
  )
93
104
 
105
+ # Register route
106
+ path = f"/api/v0/fleet/{func.__name__.replace('_', '-')}"
107
+ routes.append(Route(path, wrapper, methods=["POST"]))
94
108
  return wrapper
95
109
 
96
110
  return decorator
@@ -116,8 +130,40 @@ async def delete_node(request: DeleteNodeRequest) -> DeleteNodeResponse:
116
130
  return message_handler.delete_node(request=request, state=state)
117
131
 
118
132
 
133
+ @rest_request_response(RegisterNodeFleetRequest)
134
+ async def register_node(
135
+ request: RegisterNodeFleetRequest,
136
+ ) -> RegisterNodeFleetResponse:
137
+ """Register a node (Fleet API only)."""
138
+ raise NotImplementedError("RegisterNode is not yet implemented.")
139
+
140
+
141
+ @rest_request_response(ActivateNodeRequest)
142
+ async def activate_node(
143
+ request: ActivateNodeRequest,
144
+ ) -> ActivateNodeResponse:
145
+ """Activate a node."""
146
+ raise NotImplementedError("ActivateNode is not yet implemented.")
147
+
148
+
149
+ @rest_request_response(DeactivateNodeRequest)
150
+ async def deactivate_node(
151
+ request: DeactivateNodeRequest,
152
+ ) -> DeactivateNodeResponse:
153
+ """Deactivate a node."""
154
+ raise NotImplementedError("DeactivateNode is not yet implemented.")
155
+
156
+
157
+ @rest_request_response(UnregisterNodeFleetRequest)
158
+ async def unregister_node(
159
+ request: UnregisterNodeFleetRequest,
160
+ ) -> UnregisterNodeFleetResponse:
161
+ """Unregister a node (Fleet API only)."""
162
+ raise NotImplementedError("UnregisterNode is not yet implemented.")
163
+
164
+
119
165
  @rest_request_response(PullMessagesRequest)
120
- async def pull_message(request: PullMessagesRequest) -> PullMessagesResponse:
166
+ async def pull_messages(request: PullMessagesRequest) -> PullMessagesResponse:
121
167
  """Pull PullMessages."""
122
168
  # Get state from app
123
169
  state: LinkState = cast(LinkStateFactory, app.state.STATE_FACTORY).state()
@@ -128,7 +174,7 @@ async def pull_message(request: PullMessagesRequest) -> PullMessagesResponse:
128
174
 
129
175
 
130
176
  @rest_request_response(PushMessagesRequest)
131
- async def push_message(request: PushMessagesRequest) -> PushMessagesResponse:
177
+ async def push_messages(request: PushMessagesRequest) -> PushMessagesResponse:
132
178
  """Pull PushMessages."""
133
179
  # Get state from app
134
180
  state: LinkState = cast(LinkStateFactory, app.state.STATE_FACTORY).state()
@@ -212,23 +258,6 @@ async def confirm_message_received(
212
258
  )
213
259
 
214
260
 
215
- routes = [
216
- Route("/api/v0/fleet/create-node", create_node, methods=["POST"]),
217
- Route("/api/v0/fleet/delete-node", delete_node, methods=["POST"]),
218
- Route("/api/v0/fleet/pull-messages", pull_message, methods=["POST"]),
219
- Route("/api/v0/fleet/push-messages", push_message, methods=["POST"]),
220
- Route("/api/v0/fleet/pull-object", pull_object, methods=["POST"]),
221
- Route("/api/v0/fleet/push-object", push_object, methods=["POST"]),
222
- Route("/api/v0/fleet/send-node-heartbeat", send_node_heartbeat, methods=["POST"]),
223
- Route("/api/v0/fleet/get-run", get_run, methods=["POST"]),
224
- Route("/api/v0/fleet/get-fab", get_fab, methods=["POST"]),
225
- Route(
226
- "/api/v0/fleet/confirm-message-received",
227
- confirm_message_received,
228
- methods=["POST"],
229
- ),
230
- ]
231
-
232
261
  app: Starlette = Starlette(
233
262
  debug=False,
234
263
  routes=routes,
@@ -386,6 +386,47 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
386
386
  # Set online_until to current timestamp on deletion, if it is in the future
387
387
  node.online_until = min(node.online_until, current.timestamp())
388
388
 
389
+ def activate_node(self, node_id: int, heartbeat_interval: float) -> bool:
390
+ """Activate the node with the specified `node_id`."""
391
+ with self.lock:
392
+ self._check_and_tag_offline_nodes(node_ids=[node_id])
393
+
394
+ # Check if the node exists
395
+ if not (node := self.nodes.get(node_id)):
396
+ return False
397
+
398
+ # Only activate if the node is currently registered or offline
399
+ current_dt = now()
400
+ if node.status in (NodeStatus.REGISTERED, NodeStatus.OFFLINE):
401
+ node.status = NodeStatus.ONLINE
402
+ node.last_activated_at = current_dt.isoformat()
403
+ node.online_until = (
404
+ current_dt.timestamp() + HEARTBEAT_PATIENCE * heartbeat_interval
405
+ )
406
+ node.heartbeat_interval = heartbeat_interval
407
+ return True
408
+ return False
409
+
410
+ def deactivate_node(self, node_id: int) -> bool:
411
+ """Deactivate the node with the specified `node_id`."""
412
+ with self.lock:
413
+ self._check_and_tag_offline_nodes(node_ids=[node_id])
414
+
415
+ # Check if the node exists
416
+ if not (node := self.nodes.get(node_id)):
417
+ return False
418
+
419
+ # Only deactivate if the node is currently online
420
+ current_dt = now()
421
+ if node.status == NodeStatus.ONLINE:
422
+ node.status = NodeStatus.OFFLINE
423
+ node.last_deactivated_at = current_dt.isoformat()
424
+
425
+ # Set online_until to current timestamp
426
+ node.online_until = current_dt.timestamp()
427
+ return True
428
+ return False
429
+
389
430
  def get_nodes(self, run_id: int) -> set[int]:
390
431
  """Return all available nodes.
391
432
 
@@ -139,6 +139,45 @@ class LinkState(CoreState): # pylint: disable=R0904
139
139
  def delete_node(self, owner_aid: str, node_id: int) -> None:
140
140
  """Remove `node_id` from the link state."""
141
141
 
142
+ @abc.abstractmethod
143
+ def activate_node(self, node_id: int, heartbeat_interval: float) -> bool:
144
+ """Activate the node with the specified `node_id`.
145
+
146
+ Transitions the node status to "online". The transition will fail
147
+ if the current status is not "registered" or "offline".
148
+
149
+ Parameters
150
+ ----------
151
+ node_id : int
152
+ The identifier of the node to activate.
153
+ heartbeat_interval : float
154
+ The interval (in seconds) from the current timestamp within which
155
+ the next heartbeat from this node is expected to be received.
156
+
157
+ Returns
158
+ -------
159
+ bool
160
+ True if the status transition was successful, False otherwise.
161
+ """
162
+
163
+ @abc.abstractmethod
164
+ def deactivate_node(self, node_id: int) -> bool:
165
+ """Deactivate the node with the specified `node_id`.
166
+
167
+ Transitions the node status to "offline". The transition will fail
168
+ if the current status is not "online".
169
+
170
+ Parameters
171
+ ----------
172
+ node_id : int
173
+ The identifier of the node to deactivate.
174
+
175
+ Returns
176
+ -------
177
+ bool
178
+ True if the status transition was successful, False otherwise.
179
+ """
180
+
142
181
  @abc.abstractmethod
143
182
  def get_nodes(self, run_id: int) -> set[int]:
144
183
  """Retrieve all currently stored node IDs as a set.
@@ -618,6 +618,61 @@ class SqliteLinkState(LinkState, SqliteMixin): # pylint: disable=R0904
618
618
  "deletion attempt."
619
619
  )
620
620
 
621
+ def activate_node(self, node_id: int, heartbeat_interval: float) -> bool:
622
+ """Activate the node with the specified `node_id`."""
623
+ with self.conn:
624
+ self._check_and_tag_offline_nodes([node_id])
625
+
626
+ # Only activate if the node is currently registered or offline
627
+ current_dt = now()
628
+ query = """
629
+ UPDATE node
630
+ SET status = ?,
631
+ last_activated_at = ?,
632
+ online_until = ?,
633
+ heartbeat_interval = ?
634
+ WHERE node_id = ? AND status in (?, ?)
635
+ RETURNING node_id
636
+ """
637
+ params = (
638
+ NodeStatus.ONLINE,
639
+ current_dt.isoformat(),
640
+ current_dt.timestamp() + HEARTBEAT_PATIENCE * heartbeat_interval,
641
+ heartbeat_interval,
642
+ uint64_to_int64(node_id),
643
+ NodeStatus.REGISTERED,
644
+ NodeStatus.OFFLINE,
645
+ )
646
+
647
+ row = self.conn.execute(query, params).fetchone()
648
+ return row is not None
649
+
650
+ def deactivate_node(self, node_id: int) -> bool:
651
+ """Deactivate the node with the specified `node_id`."""
652
+ with self.conn:
653
+ self._check_and_tag_offline_nodes([node_id])
654
+
655
+ # Only deactivate if the node is currently online
656
+ current_dt = now()
657
+ query = """
658
+ UPDATE node
659
+ SET status = ?,
660
+ last_deactivated_at = ?,
661
+ online_until = ?
662
+ WHERE node_id = ? AND status = ?
663
+ RETURNING node_id
664
+ """
665
+ params = (
666
+ NodeStatus.OFFLINE,
667
+ current_dt.isoformat(),
668
+ current_dt.timestamp(),
669
+ uint64_to_int64(node_id),
670
+ NodeStatus.ONLINE,
671
+ )
672
+
673
+ row = self.conn.execute(query, params).fetchone()
674
+ return row is not None
675
+
621
676
  def get_nodes(self, run_id: int) -> set[int]:
622
677
  """Retrieve all currently stored node IDs as a set.
623
678
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: flwr-nightly
3
- Version: 1.23.0.dev20251028
3
+ Version: 1.23.0.dev20251029
4
4
  Summary: Flower: A Friendly Federated AI Framework
5
5
  License: Apache-2.0
6
6
  Keywords: Artificial Intelligence,Federated AI,Federated Analytics,Federated Evaluation,Federated Learning,Flower,Machine Learning
@@ -98,7 +98,7 @@ flwr/client/grpc_adapter_client/__init__.py,sha256=RQWP5mFPROLHKgombiRvPXVWSoVrQ
98
98
  flwr/client/grpc_adapter_client/connection.py,sha256=JGv02EjSOAG1E5BRUD4lwXc1LLiYJ0OhInvp31qx1cU,4404
99
99
  flwr/client/grpc_rere_client/__init__.py,sha256=i7iS0Lt8B7q0E2L72e4F_YrKm6ClRKnd71PNA6PW2O0,752
100
100
  flwr/client/grpc_rere_client/connection.py,sha256=3_CI7wx5ZrkGrVCXjDKiUGX9aJP9Zm9yl4iqSBAmH3U,13100
101
- flwr/client/grpc_rere_client/grpc_adapter.py,sha256=dLGB5GriszAmtgvuFGuz_F7rIwpzLfDxhJ7T3Un-Ce0,6694
101
+ flwr/client/grpc_rere_client/grpc_adapter.py,sha256=YwsZOvFgCd1JdiHnl1Cc18vKOiGwWSSJ_9-ZXwlGZvs,7900
102
102
  flwr/client/grpc_rere_client/node_auth_client_interceptor.py,sha256=EdTyb5ThFrpkeyXy9_nTKH7E9GnfAdXjcNZ_-uxt1AA,2410
103
103
  flwr/client/message_handler/__init__.py,sha256=0lyljDVqre3WljiZbPcwCCf8GiIaSVI_yo_ylEyPwSE,719
104
104
  flwr/client/message_handler/message_handler.py,sha256=X9SXX6et97Lw9_DGD93HKsEBGNjXClcFgc_5aLK0oiU,6541
@@ -203,10 +203,10 @@ flwr/proto/fab_pb2.py,sha256=AQAUJmTt31Kq-lJQid3CJpQPrply2-OA_aSHokpVF84,2070
203
203
  flwr/proto/fab_pb2.pyi,sha256=GOxeDi-UYzQVwIEzRk9YkzkriowmNdibZu3jMMtBPFo,3273
204
204
  flwr/proto/fab_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
205
205
  flwr/proto/fab_pb2_grpc.pyi,sha256=ff2TSiLVnG6IVQcTGzb2DIH3XRSoAvAo_RMcvbMFyc0,76
206
- flwr/proto/fleet_pb2.py,sha256=xGnNGmBbPds6LeB34HN8_vy4U1oW25-w-r-C6xNpoRo,5190
207
- flwr/proto/fleet_pb2.pyi,sha256=btn0ertRlY5LenpN-YoiqA4X8NQgbkZsAJZQWN8Tia0,7939
208
- flwr/proto/fleet_pb2_grpc.py,sha256=NmzrDYxyM3MQNh3vwYczQNuFimZz3prU6ke3E-fKk_g,17539
209
- flwr/proto/fleet_pb2_grpc.pyi,sha256=PDERhzOrBCMAytTLS65Qck8A45bTIYni7Lotq6_I0sM,4721
206
+ flwr/proto/fleet_pb2.py,sha256=FyQJ-FCsYJLwQZeYrs_p6Y_OgA9jPoxGHyIvWeukXxU,7171
207
+ flwr/proto/fleet_pb2.pyi,sha256=-cKglysTm0FPkAqdD2cKzfxFhb1rP_z5aOD5RSPrFxU,11209
208
+ flwr/proto/fleet_pb2_grpc.py,sha256=ss_fFtTJuVSzYLxWsW5Fn6XfUBnJIKV7BTBX5vyhPMY,24083
209
+ flwr/proto/fleet_pb2_grpc.pyi,sha256=WJIV7EmoePxY6CaYF1cj3oZ2ss6E1pt7Zs5C6ehz69c,6461
210
210
  flwr/proto/grpcadapter_pb2.py,sha256=PJ8DtfeV29g_y4Z3aNZlSZocLqSxeLmTsYCdOZDYCiE,1843
211
211
  flwr/proto/grpcadapter_pb2.pyi,sha256=AR77gDsF6f8zqSIQp3877DUd7S8lP95lFak5Ir_WPkw,1716
212
212
  flwr/proto/grpcadapter_pb2_grpc.py,sha256=rRNuNES5nBugUZWfeA8oAy8dMHgzqU_PF1srTseo3b8,2634
@@ -298,29 +298,29 @@ flwr/server/strategy/strategy.py,sha256=n4r52i5gK4KGToZvcJUeWuEif1tuI0HZUT3YJPTC
298
298
  flwr/server/superlink/__init__.py,sha256=GNSuJ4-N6Z8wun2iZNlXqENt5beUyzC0Gi_tN396bbM,707
299
299
  flwr/server/superlink/fleet/__init__.py,sha256=Uiwr33yfW_eL-pEfj80c_JUhIKRkCPsN1JSs2v4aglU,711
300
300
  flwr/server/superlink/fleet/grpc_adapter/__init__.py,sha256=fUu1V63YrzjxAOZnBJx99WjuD4Mro7dJIFH-1V4NLV8,742
301
- flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py,sha256=hHTQ3ryI31TIGN4v9zkYNf8tx3hsiYHiovmsq3PCB6c,5018
301
+ flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py,sha256=zFWNbVnT-HMHJiZO154ciaT2GeA8TcFpwEJuNI793eU,5879
302
302
  flwr/server/superlink/fleet/grpc_bidi/__init__.py,sha256=dOM49q1b9MrtUr5jldjEnQ38NhcUyYs-zC3gsJb1TtI,735
303
303
  flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py,sha256=UKEp-3YBaTvNt7vKZW7KLgK5xsAiO7jxU-omG7CaO_s,6021
304
304
  flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py,sha256=KouR9PUcrPmMtoLooF4O9SRAwIvfiroo8mPmqUc2EZc,6485
305
305
  flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py,sha256=iSf0mbBAlig7G6subQwBSVjcUCgSihONKdZ1RmQPTOk,4887
306
306
  flwr/server/superlink/fleet/grpc_bidi/grpc_server.py,sha256=OsS-6GgCIzMMZDVu5Y-OKjynHVUrpdc_5OrtuB-IbU0,5174
307
307
  flwr/server/superlink/fleet/grpc_rere/__init__.py,sha256=ahDJJ1e-lDxBpeBMgPk7YZt2wB38_QltcpOC0gLbpFs,758
308
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py,sha256=V9CMyaYVc1t5Wt_63H5zcMbs8iC8HD-V31TXLlCPts4,11672
308
+ flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py,sha256=0HwtOgCuQ1x8YCZXMFN6a2Y7agR4TR5e6rruBuxqqrI,13620
309
309
  flwr/server/superlink/fleet/grpc_rere/node_auth_server_interceptor.py,sha256=UWeFQNBW2pGBRVN36HodHcv7bKTgMmdToh96Lhqip1M,5411
310
310
  flwr/server/superlink/fleet/message_handler/__init__.py,sha256=fHsRV0KvJ8HtgSA4_YBsEzuhJLjO8p6xx4aCY2oE1p4,731
311
311
  flwr/server/superlink/fleet/message_handler/message_handler.py,sha256=GYveEXgKRlyrKRUr2H6kjWPx_g4-fnATrMdJD2G4Gaw,8762
312
312
  flwr/server/superlink/fleet/rest_rere/__init__.py,sha256=Lzc93nA7tDqoy-zRUaPG316oqFiZX1HUCL5ELaXY_xw,735
313
- flwr/server/superlink/fleet/rest_rere/rest_api.py,sha256=mxWKwGpgHPqd7cGFqd2ASnR-KZduIzLfT-d2yiNCqQ0,9257
313
+ flwr/server/superlink/fleet/rest_rere/rest_api.py,sha256=HzLx_3sY2_EXBLzLA_ZvKgZrgFDzIh0wesDfLrqyxvU,9875
314
314
  flwr/server/superlink/fleet/vce/__init__.py,sha256=XOKbAWOzlCqEOQ3M2cBYkH7HKA7PxlbCJMunt-ty-DY,784
315
315
  flwr/server/superlink/fleet/vce/backend/__init__.py,sha256=PPH89Yqd1XKm-sRJN6R0WQlKT_b4v54Kzl2yzHAFzM8,1437
316
316
  flwr/server/superlink/fleet/vce/backend/backend.py,sha256=cSrHZ5SjCCvy4vI0pgsyjtx3cDMuMQve8KcKkK-dWWo,2196
317
317
  flwr/server/superlink/fleet/vce/backend/raybackend.py,sha256=cBZYTmfiAsb1HmVUmOQXYLU-UJmJTFWkj1wW4RYRDuc,7218
318
318
  flwr/server/superlink/fleet/vce/vce_api.py,sha256=sgsAx7dgIADA-Ae71s5dwoPY0bw3-qA7RukJgnh8pgc,13389
319
319
  flwr/server/superlink/linkstate/__init__.py,sha256=OtsgvDTnZLU3k0sUbkHbqoVwW6ql2FDmb6uT6DbNkZo,1064
320
- flwr/server/superlink/linkstate/in_memory_linkstate.py,sha256=vtQZnVIQuLX6rOvKg6qLdPRjfWbxKyE84rEXhlFFdbI,30168
321
- flwr/server/superlink/linkstate/linkstate.py,sha256=JKgVEPnH2T-nixW3LHp8jR3g4ITAZYNwEoDIWZaUYmQ,14701
320
+ flwr/server/superlink/linkstate/in_memory_linkstate.py,sha256=STJs4CNgdRknGFvreCdumBSDfUxqW8X0hpCun1wcROM,31829
321
+ flwr/server/superlink/linkstate/linkstate.py,sha256=DabTgFFissJhT_MCPiluz5eFrS3c412q8FnRqQrAidY,15945
322
322
  flwr/server/superlink/linkstate/linkstate_factory.py,sha256=KVBpc8UxVrJCQ7IkOjVZVWb-kqWx08AGVi7qXzZS190,2126
323
- flwr/server/superlink/linkstate/sqlite_linkstate.py,sha256=1r9Jo4GLKlyhRzFOic-J5uv2Dbk6qZ0wNKgOZyTJZSo,45555
323
+ flwr/server/superlink/linkstate/sqlite_linkstate.py,sha256=5KJnrqDz1bNphLhJI9iZz2_mrbl1_DsTOSbwfncsE1E,47528
324
324
  flwr/server/superlink/linkstate/utils.py,sha256=ZtyqSo4HzGrHXW3Wn_4irYMpIiq4onNI2XCIVOOJmJM,13971
325
325
  flwr/server/superlink/serverappio/__init__.py,sha256=Fy4zJuoccZe5mZSEIpOmQvU6YeXFBa1M4eZuXXmJcn8,717
326
326
  flwr/server/superlink/serverappio/serverappio_grpc.py,sha256=-I7kBbr4w4ZVYwBZoAIle-xHKthFnZrsVfxa6WR8uxA,2310
@@ -433,7 +433,7 @@ flwr/supernode/servicer/__init__.py,sha256=lucTzre5WPK7G1YLCfaqg3rbFWdNSb7ZTt-ca
433
433
  flwr/supernode/servicer/clientappio/__init__.py,sha256=7Oy62Y_oijqF7Dxi6tpcUQyOpLc_QpIRZ83NvwmB0Yg,813
434
434
  flwr/supernode/servicer/clientappio/clientappio_servicer.py,sha256=ZvKosLV7GN1_fOF-tOmhqFQysYQywCysRc-m23DVIWA,10265
435
435
  flwr/supernode/start_client_internal.py,sha256=wKqh9-_rQYi7JFKpYBLRiUeq9YJUSyUd9b-SnVnuvwI,21567
436
- flwr_nightly-1.23.0.dev20251028.dist-info/METADATA,sha256=_BUO-CINcA_9ZN6iiZpJSlOLP_D6K9VIgBvxDuYI37A,14559
437
- flwr_nightly-1.23.0.dev20251028.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
438
- flwr_nightly-1.23.0.dev20251028.dist-info/entry_points.txt,sha256=hxHD2ixb_vJFDOlZV-zB4Ao32_BQlL34ftsDh1GXv14,420
439
- flwr_nightly-1.23.0.dev20251028.dist-info/RECORD,,
436
+ flwr_nightly-1.23.0.dev20251029.dist-info/METADATA,sha256=IbVE7qYkOpvhbjaQ8hYXUx3el8wwZN8UQKbeTKeKrjE,14559
437
+ flwr_nightly-1.23.0.dev20251029.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
438
+ flwr_nightly-1.23.0.dev20251029.dist-info/entry_points.txt,sha256=hxHD2ixb_vJFDOlZV-zB4Ao32_BQlL34ftsDh1GXv14,420
439
+ flwr_nightly-1.23.0.dev20251029.dist-info/RECORD,,