flwr 1.18.0__py3-none-any.whl → 1.20.0__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.
Files changed (174) hide show
  1. flwr/app/__init__.py +15 -0
  2. flwr/app/error.py +68 -0
  3. flwr/app/metadata.py +223 -0
  4. flwr/cli/build.py +94 -59
  5. flwr/cli/log.py +3 -3
  6. flwr/cli/login/login.py +3 -7
  7. flwr/cli/ls.py +15 -36
  8. flwr/cli/new/new.py +12 -4
  9. flwr/cli/new/templates/app/README.flowertune.md.tpl +2 -0
  10. flwr/cli/new/templates/app/README.md.tpl +5 -0
  11. flwr/cli/new/templates/app/code/client.baseline.py.tpl +1 -1
  12. flwr/cli/new/templates/app/code/model.baseline.py.tpl +1 -1
  13. flwr/cli/new/templates/app/code/server.baseline.py.tpl +2 -3
  14. flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +25 -17
  15. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +13 -1
  16. flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +21 -2
  17. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +18 -1
  18. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +19 -2
  19. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +18 -1
  20. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +20 -3
  21. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +18 -1
  22. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +18 -1
  23. flwr/cli/run/run.py +48 -49
  24. flwr/cli/stop.py +2 -2
  25. flwr/cli/utils.py +38 -5
  26. flwr/client/__init__.py +2 -2
  27. flwr/client/client_app.py +1 -1
  28. flwr/client/clientapp/__init__.py +0 -7
  29. flwr/client/grpc_adapter_client/connection.py +15 -8
  30. flwr/client/grpc_rere_client/connection.py +142 -97
  31. flwr/client/grpc_rere_client/grpc_adapter.py +34 -6
  32. flwr/client/message_handler/message_handler.py +1 -1
  33. flwr/client/mod/comms_mods.py +36 -17
  34. flwr/client/rest_client/connection.py +176 -103
  35. flwr/clientapp/__init__.py +15 -0
  36. flwr/common/__init__.py +2 -2
  37. flwr/common/auth_plugin/__init__.py +2 -0
  38. flwr/common/auth_plugin/auth_plugin.py +29 -3
  39. flwr/common/constant.py +39 -8
  40. flwr/common/event_log_plugin/event_log_plugin.py +3 -3
  41. flwr/common/exit/exit_code.py +16 -1
  42. flwr/common/exit_handlers.py +30 -0
  43. flwr/common/grpc.py +12 -1
  44. flwr/common/heartbeat.py +165 -0
  45. flwr/common/inflatable.py +290 -0
  46. flwr/common/inflatable_protobuf_utils.py +141 -0
  47. flwr/common/inflatable_utils.py +508 -0
  48. flwr/common/message.py +110 -242
  49. flwr/common/record/__init__.py +2 -1
  50. flwr/common/record/array.py +402 -0
  51. flwr/common/record/arraychunk.py +59 -0
  52. flwr/common/record/arrayrecord.py +103 -225
  53. flwr/common/record/configrecord.py +59 -4
  54. flwr/common/record/conversion_utils.py +1 -1
  55. flwr/common/record/metricrecord.py +55 -4
  56. flwr/common/record/recorddict.py +69 -1
  57. flwr/common/recorddict_compat.py +2 -2
  58. flwr/common/retry_invoker.py +5 -1
  59. flwr/common/serde.py +59 -211
  60. flwr/common/serde_utils.py +175 -0
  61. flwr/common/typing.py +5 -3
  62. flwr/compat/__init__.py +15 -0
  63. flwr/compat/client/__init__.py +15 -0
  64. flwr/{client → compat/client}/app.py +28 -185
  65. flwr/compat/common/__init__.py +15 -0
  66. flwr/compat/server/__init__.py +15 -0
  67. flwr/compat/server/app.py +174 -0
  68. flwr/compat/simulation/__init__.py +15 -0
  69. flwr/proto/appio_pb2.py +43 -0
  70. flwr/proto/appio_pb2.pyi +151 -0
  71. flwr/proto/appio_pb2_grpc.py +4 -0
  72. flwr/proto/appio_pb2_grpc.pyi +4 -0
  73. flwr/proto/clientappio_pb2.py +12 -19
  74. flwr/proto/clientappio_pb2.pyi +23 -101
  75. flwr/proto/clientappio_pb2_grpc.py +269 -28
  76. flwr/proto/clientappio_pb2_grpc.pyi +114 -20
  77. flwr/proto/fleet_pb2.py +24 -27
  78. flwr/proto/fleet_pb2.pyi +19 -35
  79. flwr/proto/fleet_pb2_grpc.py +117 -13
  80. flwr/proto/fleet_pb2_grpc.pyi +47 -6
  81. flwr/proto/heartbeat_pb2.py +33 -0
  82. flwr/proto/heartbeat_pb2.pyi +66 -0
  83. flwr/proto/heartbeat_pb2_grpc.py +4 -0
  84. flwr/proto/heartbeat_pb2_grpc.pyi +4 -0
  85. flwr/proto/message_pb2.py +28 -11
  86. flwr/proto/message_pb2.pyi +125 -0
  87. flwr/proto/recorddict_pb2.py +16 -28
  88. flwr/proto/recorddict_pb2.pyi +46 -64
  89. flwr/proto/run_pb2.py +24 -32
  90. flwr/proto/run_pb2.pyi +4 -52
  91. flwr/proto/serverappio_pb2.py +9 -23
  92. flwr/proto/serverappio_pb2.pyi +0 -110
  93. flwr/proto/serverappio_pb2_grpc.py +177 -72
  94. flwr/proto/serverappio_pb2_grpc.pyi +75 -33
  95. flwr/proto/simulationio_pb2.py +12 -11
  96. flwr/proto/simulationio_pb2_grpc.py +35 -0
  97. flwr/proto/simulationio_pb2_grpc.pyi +14 -0
  98. flwr/server/__init__.py +1 -1
  99. flwr/server/app.py +69 -187
  100. flwr/server/compat/app_utils.py +50 -28
  101. flwr/server/fleet_event_log_interceptor.py +6 -2
  102. flwr/server/grid/grpc_grid.py +148 -41
  103. flwr/server/grid/inmemory_grid.py +5 -4
  104. flwr/server/serverapp/app.py +45 -17
  105. flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +21 -3
  106. flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +102 -8
  107. flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +2 -5
  108. flwr/server/superlink/fleet/message_handler/message_handler.py +130 -19
  109. flwr/server/superlink/fleet/rest_rere/rest_api.py +73 -13
  110. flwr/server/superlink/fleet/vce/vce_api.py +6 -3
  111. flwr/server/superlink/linkstate/in_memory_linkstate.py +138 -43
  112. flwr/server/superlink/linkstate/linkstate.py +53 -20
  113. flwr/server/superlink/linkstate/sqlite_linkstate.py +149 -55
  114. flwr/server/superlink/linkstate/utils.py +33 -29
  115. flwr/server/superlink/serverappio/serverappio_grpc.py +4 -1
  116. flwr/server/superlink/serverappio/serverappio_servicer.py +230 -84
  117. flwr/server/superlink/simulation/simulationio_grpc.py +1 -1
  118. flwr/server/superlink/simulation/simulationio_servicer.py +26 -2
  119. flwr/server/superlink/utils.py +9 -2
  120. flwr/server/utils/validator.py +2 -2
  121. flwr/serverapp/__init__.py +15 -0
  122. flwr/simulation/app.py +25 -0
  123. flwr/simulation/run_simulation.py +17 -0
  124. flwr/supercore/__init__.py +15 -0
  125. flwr/{server/superlink → supercore}/ffs/__init__.py +2 -0
  126. flwr/{server/superlink → supercore}/ffs/disk_ffs.py +1 -1
  127. flwr/supercore/grpc_health/__init__.py +22 -0
  128. flwr/supercore/grpc_health/simple_health_servicer.py +38 -0
  129. flwr/supercore/license_plugin/__init__.py +22 -0
  130. flwr/supercore/license_plugin/license_plugin.py +26 -0
  131. flwr/supercore/object_store/__init__.py +24 -0
  132. flwr/supercore/object_store/in_memory_object_store.py +229 -0
  133. flwr/supercore/object_store/object_store.py +170 -0
  134. flwr/supercore/object_store/object_store_factory.py +44 -0
  135. flwr/supercore/object_store/utils.py +43 -0
  136. flwr/supercore/scheduler/__init__.py +22 -0
  137. flwr/supercore/scheduler/plugin.py +71 -0
  138. flwr/{client/nodestate/nodestate.py → supercore/utils.py} +14 -13
  139. flwr/superexec/deployment.py +7 -4
  140. flwr/superexec/exec_event_log_interceptor.py +8 -4
  141. flwr/superexec/exec_grpc.py +25 -5
  142. flwr/superexec/exec_license_interceptor.py +82 -0
  143. flwr/superexec/exec_servicer.py +135 -24
  144. flwr/superexec/exec_user_auth_interceptor.py +45 -8
  145. flwr/superexec/executor.py +5 -1
  146. flwr/superexec/simulation.py +8 -3
  147. flwr/superlink/__init__.py +15 -0
  148. flwr/{client/supernode → supernode}/__init__.py +0 -7
  149. flwr/supernode/cli/__init__.py +24 -0
  150. flwr/{client/supernode/app.py → supernode/cli/flower_supernode.py} +3 -19
  151. flwr/supernode/cli/flwr_clientapp.py +88 -0
  152. flwr/supernode/nodestate/in_memory_nodestate.py +199 -0
  153. flwr/supernode/nodestate/nodestate.py +227 -0
  154. flwr/supernode/runtime/__init__.py +15 -0
  155. flwr/{client/clientapp/app.py → supernode/runtime/run_clientapp.py} +135 -89
  156. flwr/supernode/scheduler/__init__.py +22 -0
  157. flwr/supernode/scheduler/simple_clientapp_scheduler_plugin.py +49 -0
  158. flwr/supernode/servicer/__init__.py +15 -0
  159. flwr/supernode/servicer/clientappio/__init__.py +22 -0
  160. flwr/supernode/servicer/clientappio/clientappio_servicer.py +303 -0
  161. flwr/supernode/start_client_internal.py +589 -0
  162. {flwr-1.18.0.dist-info → flwr-1.20.0.dist-info}/METADATA +6 -4
  163. {flwr-1.18.0.dist-info → flwr-1.20.0.dist-info}/RECORD +171 -123
  164. {flwr-1.18.0.dist-info → flwr-1.20.0.dist-info}/WHEEL +1 -1
  165. {flwr-1.18.0.dist-info → flwr-1.20.0.dist-info}/entry_points.txt +2 -2
  166. flwr/client/clientapp/clientappio_servicer.py +0 -244
  167. flwr/client/heartbeat.py +0 -74
  168. flwr/client/nodestate/in_memory_nodestate.py +0 -38
  169. /flwr/{client → compat/client}/grpc_client/__init__.py +0 -0
  170. /flwr/{client → compat/client}/grpc_client/connection.py +0 -0
  171. /flwr/{server/superlink → supercore}/ffs/ffs.py +0 -0
  172. /flwr/{server/superlink → supercore}/ffs/ffs_factory.py +0 -0
  173. /flwr/{client → supernode}/nodestate/__init__.py +0 -0
  174. /flwr/{client → supernode}/nodestate/nodestate_factory.py +0 -0
@@ -1,244 +0,0 @@
1
- # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
- # ==============================================================================
15
- """ClientAppIo API servicer."""
16
-
17
-
18
- from dataclasses import dataclass
19
- from logging import DEBUG, ERROR
20
- from typing import Optional, cast
21
-
22
- import grpc
23
-
24
- from flwr.common import Context, Message, typing
25
- from flwr.common.logger import log
26
- from flwr.common.serde import (
27
- clientappstatus_to_proto,
28
- context_from_proto,
29
- context_to_proto,
30
- fab_to_proto,
31
- message_from_proto,
32
- message_to_proto,
33
- run_to_proto,
34
- )
35
- from flwr.common.typing import Fab, Run
36
-
37
- # pylint: disable=E0611
38
- from flwr.proto import clientappio_pb2_grpc
39
- from flwr.proto.clientappio_pb2 import ( # pylint: disable=E0401
40
- GetTokenRequest,
41
- GetTokenResponse,
42
- PullClientAppInputsRequest,
43
- PullClientAppInputsResponse,
44
- PushClientAppOutputsRequest,
45
- PushClientAppOutputsResponse,
46
- )
47
-
48
-
49
- @dataclass
50
- class ClientAppInputs:
51
- """Specify the inputs to the ClientApp."""
52
-
53
- message: Message
54
- context: Context
55
- run: Run
56
- fab: Optional[Fab]
57
- token: int
58
-
59
-
60
- @dataclass
61
- class ClientAppOutputs:
62
- """Specify the outputs from the ClientApp."""
63
-
64
- message: Message
65
- context: Context
66
-
67
-
68
- # pylint: disable=C0103,W0613,W0201
69
- class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
70
- """ClientAppIo API servicer."""
71
-
72
- def __init__(self) -> None:
73
- self.clientapp_input: Optional[ClientAppInputs] = None
74
- self.clientapp_output: Optional[ClientAppOutputs] = None
75
- self.token_returned: bool = False
76
- self.inputs_returned: bool = False
77
-
78
- def GetToken(
79
- self, request: GetTokenRequest, context: grpc.ServicerContext
80
- ) -> GetTokenResponse:
81
- """Get token."""
82
- log(DEBUG, "ClientAppIo.GetToken")
83
-
84
- # Fail if no ClientAppInputs are available
85
- if self.clientapp_input is None:
86
- context.abort(
87
- grpc.StatusCode.FAILED_PRECONDITION,
88
- "No inputs available.",
89
- )
90
- clientapp_input = cast(ClientAppInputs, self.clientapp_input)
91
-
92
- # Fail if token was already returned in a previous call
93
- if self.token_returned:
94
- context.abort(
95
- grpc.StatusCode.FAILED_PRECONDITION,
96
- "Token already returned. A token can be returned only once.",
97
- )
98
-
99
- # If
100
- # - ClientAppInputs is set, and
101
- # - token hasn't been returned before,
102
- # return token
103
- self.token_returned = True
104
- return GetTokenResponse(token=clientapp_input.token)
105
-
106
- def PullClientAppInputs(
107
- self, request: PullClientAppInputsRequest, context: grpc.ServicerContext
108
- ) -> PullClientAppInputsResponse:
109
- """Pull Message, Context, and Run."""
110
- log(DEBUG, "ClientAppIo.PullClientAppInputs")
111
-
112
- # Fail if no ClientAppInputs are available
113
- if self.clientapp_input is None:
114
- context.abort(
115
- grpc.StatusCode.FAILED_PRECONDITION,
116
- "No inputs available.",
117
- )
118
- clientapp_input = cast(ClientAppInputs, self.clientapp_input)
119
-
120
- # Fail if token wasn't returned in a previous call
121
- if not self.token_returned:
122
- context.abort(
123
- grpc.StatusCode.FAILED_PRECONDITION,
124
- "Token hasn't been returned."
125
- "Token must be returned before can be returned only once.",
126
- )
127
-
128
- # Fail if token isn't matching
129
- if request.token != clientapp_input.token:
130
- context.abort(
131
- grpc.StatusCode.INVALID_ARGUMENT,
132
- "Mismatch between ClientApp and SuperNode token",
133
- )
134
-
135
- # Success
136
- self.inputs_returned = True
137
- return PullClientAppInputsResponse(
138
- message=message_to_proto(clientapp_input.message),
139
- context=context_to_proto(clientapp_input.context),
140
- run=run_to_proto(clientapp_input.run),
141
- fab=fab_to_proto(clientapp_input.fab) if clientapp_input.fab else None,
142
- )
143
-
144
- def PushClientAppOutputs(
145
- self, request: PushClientAppOutputsRequest, context: grpc.ServicerContext
146
- ) -> PushClientAppOutputsResponse:
147
- """Push Message and Context."""
148
- log(DEBUG, "ClientAppIo.PushClientAppOutputs")
149
-
150
- # Fail if no ClientAppInputs are available
151
- if not self.clientapp_input:
152
- context.abort(
153
- grpc.StatusCode.FAILED_PRECONDITION,
154
- "No inputs available.",
155
- )
156
- clientapp_input = cast(ClientAppInputs, self.clientapp_input)
157
-
158
- # Fail if token wasn't returned in a previous call
159
- if not self.token_returned:
160
- context.abort(
161
- grpc.StatusCode.FAILED_PRECONDITION,
162
- "Token hasn't been returned."
163
- "Token must be returned before can be returned only once.",
164
- )
165
-
166
- # Fail if inputs weren't delivered in a previous call
167
- if not self.inputs_returned:
168
- context.abort(
169
- grpc.StatusCode.FAILED_PRECONDITION,
170
- "Inputs haven't been delivered."
171
- "Inputs must be delivered before can be returned only once.",
172
- )
173
-
174
- # Fail if token isn't matching
175
- if request.token != clientapp_input.token:
176
- context.abort(
177
- grpc.StatusCode.INVALID_ARGUMENT,
178
- "Mismatch between ClientApp and SuperNode token",
179
- )
180
-
181
- # Preconditions met
182
- try:
183
- # Update Message and Context
184
- self.clientapp_output = ClientAppOutputs(
185
- message=message_from_proto(request.message),
186
- context=context_from_proto(request.context),
187
- )
188
-
189
- # Set status
190
- code = typing.ClientAppOutputCode.SUCCESS
191
- status = typing.ClientAppOutputStatus(code=code, message="Success")
192
- except Exception as e: # pylint: disable=broad-exception-caught
193
- log(ERROR, "ClientApp failed to push message to SuperNode, %s", e)
194
- code = typing.ClientAppOutputCode.UNKNOWN_ERROR
195
- status = typing.ClientAppOutputStatus(code=code, message="Unkonwn error")
196
-
197
- # Return status to ClientApp process
198
- proto_status = clientappstatus_to_proto(status=status)
199
- return PushClientAppOutputsResponse(status=proto_status)
200
-
201
- def set_inputs(
202
- self, clientapp_input: ClientAppInputs, token_returned: bool
203
- ) -> None:
204
- """Set ClientApp inputs.
205
-
206
- Parameters
207
- ----------
208
- clientapp_input : ClientAppInputs
209
- The inputs to the ClientApp.
210
- token_returned : bool
211
- A boolean indicating if the token has been returned.
212
- Set to `True` when passing the token to `flwr-clientap`
213
- and `False` otherwise.
214
- """
215
- if (
216
- self.clientapp_input is not None
217
- or self.clientapp_output is not None
218
- or self.token_returned
219
- ):
220
- raise ValueError(
221
- "ClientAppInputs and ClientAppOutputs must not be set before "
222
- "calling `set_inputs`."
223
- )
224
- log(DEBUG, "ClientAppInputs set (token: %s)", clientapp_input.token)
225
- self.clientapp_input = clientapp_input
226
- self.token_returned = token_returned
227
-
228
- def has_outputs(self) -> bool:
229
- """Check if ClientAppOutputs are available."""
230
- return self.clientapp_output is not None
231
-
232
- def get_outputs(self) -> ClientAppOutputs:
233
- """Get ClientApp outputs."""
234
- if self.clientapp_output is None:
235
- raise ValueError("ClientAppOutputs not set before calling `get_outputs`.")
236
-
237
- # Set outputs to a local variable and clear state
238
- output: ClientAppOutputs = self.clientapp_output
239
- self.clientapp_input = None
240
- self.clientapp_output = None
241
- self.token_returned = False
242
- self.inputs_returned = False
243
-
244
- return output
flwr/client/heartbeat.py DELETED
@@ -1,74 +0,0 @@
1
- # Copyright 2025 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
- """Heartbeat utility functions."""
16
-
17
-
18
- import threading
19
- from typing import Callable
20
-
21
- import grpc
22
-
23
- from flwr.common.constant import PING_CALL_TIMEOUT
24
- from flwr.common.retry_invoker import RetryInvoker, RetryState, exponential
25
-
26
-
27
- def _ping_loop(ping_fn: Callable[[], None], stop_event: threading.Event) -> None:
28
- def wait_fn(wait_time: float) -> None:
29
- if not stop_event.is_set():
30
- stop_event.wait(wait_time)
31
-
32
- def on_backoff(state: RetryState) -> None:
33
- err = state.exception
34
- if not isinstance(err, grpc.RpcError):
35
- return
36
- status_code = err.code()
37
- # If ping call timeout is triggered
38
- if status_code == grpc.StatusCode.DEADLINE_EXCEEDED:
39
- # Avoid long wait time.
40
- if state.actual_wait is None:
41
- return
42
- state.actual_wait = max(state.actual_wait - PING_CALL_TIMEOUT, 0.0)
43
-
44
- def wrapped_ping() -> None:
45
- if not stop_event.is_set():
46
- ping_fn()
47
-
48
- retrier = RetryInvoker(
49
- exponential,
50
- grpc.RpcError,
51
- max_tries=None,
52
- max_time=None,
53
- on_backoff=on_backoff,
54
- wait_function=wait_fn,
55
- )
56
- while not stop_event.is_set():
57
- retrier.invoke(wrapped_ping)
58
-
59
-
60
- def start_ping_loop(
61
- ping_fn: Callable[[], None], stop_event: threading.Event
62
- ) -> threading.Thread:
63
- """Start a ping loop in a separate thread.
64
-
65
- This function initializes a new thread that runs a ping loop, allowing for
66
- asynchronous ping operations. The loop can be terminated through the provided stop
67
- event.
68
- """
69
- thread = threading.Thread(
70
- target=_ping_loop, args=(ping_fn, stop_event), daemon=True
71
- )
72
- thread.start()
73
-
74
- return thread
@@ -1,38 +0,0 @@
1
- # Copyright 2025 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
- """In-memory NodeState implementation."""
16
-
17
-
18
- from typing import Optional
19
-
20
- from flwr.client.nodestate.nodestate import NodeState
21
-
22
-
23
- class InMemoryNodeState(NodeState):
24
- """In-memory NodeState implementation."""
25
-
26
- def __init__(self) -> None:
27
- # Store node_id
28
- self.node_id: Optional[int] = None
29
-
30
- def set_node_id(self, node_id: Optional[int]) -> None:
31
- """Set the node ID."""
32
- self.node_id = node_id
33
-
34
- def get_node_id(self) -> int:
35
- """Get the node ID."""
36
- if self.node_id is None:
37
- raise ValueError("Node ID not set")
38
- return self.node_id
File without changes
File without changes
File without changes
File without changes