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
@@ -0,0 +1,199 @@
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
+ import secrets
19
+ from collections.abc import Sequence
20
+ from dataclasses import dataclass
21
+ from threading import Lock
22
+ from typing import Optional
23
+
24
+ from flwr.common import Context, Message
25
+ from flwr.common.constant import FLWR_APP_TOKEN_LENGTH
26
+ from flwr.common.typing import Run
27
+
28
+ from .nodestate import NodeState
29
+
30
+
31
+ @dataclass
32
+ class MessageEntry:
33
+ """Data class to represent a message entry."""
34
+
35
+ message: Message
36
+ is_retrieved: bool = False
37
+
38
+
39
+ class InMemoryNodeState(NodeState): # pylint: disable=too-many-instance-attributes
40
+ """In-memory NodeState implementation."""
41
+
42
+ def __init__(self) -> None:
43
+ # Store node_id
44
+ self.node_id: Optional[int] = None
45
+ # Store Object ID to MessageEntry mapping
46
+ self.msg_store: dict[str, MessageEntry] = {}
47
+ self.lock_msg_store = Lock()
48
+ # Store run ID to Run mapping
49
+ self.run_store: dict[int, Run] = {}
50
+ self.lock_run_store = Lock()
51
+ # Store run ID to Context mapping
52
+ self.ctx_store: dict[int, Context] = {}
53
+ self.lock_ctx_store = Lock()
54
+ # Store run ID to token mapping and token to run ID mapping
55
+ self.token_store: dict[int, str] = {}
56
+ self.token_to_run_id: dict[str, int] = {}
57
+ self.lock_token_store = Lock()
58
+
59
+ def set_node_id(self, node_id: Optional[int]) -> None:
60
+ """Set the node ID."""
61
+ self.node_id = node_id
62
+
63
+ def get_node_id(self) -> int:
64
+ """Get the node ID."""
65
+ if self.node_id is None:
66
+ raise ValueError("Node ID not set")
67
+ return self.node_id
68
+
69
+ def store_message(self, message: Message) -> Optional[str]:
70
+ """Store a message."""
71
+ with self.lock_msg_store:
72
+ msg_id = message.metadata.message_id
73
+ if msg_id == "" or msg_id in self.msg_store:
74
+ return None
75
+ self.msg_store[msg_id] = MessageEntry(message=message)
76
+ return msg_id
77
+
78
+ def get_messages(
79
+ self,
80
+ *,
81
+ run_ids: Optional[Sequence[int]] = None,
82
+ is_reply: Optional[bool] = None,
83
+ limit: Optional[int] = None,
84
+ ) -> Sequence[Message]:
85
+ """Retrieve messages based on the specified filters."""
86
+ selected_messages: list[Message] = []
87
+
88
+ with self.lock_msg_store:
89
+ # Iterate through all messages in the store
90
+ for object_id in list(self.msg_store.keys()):
91
+ entry = self.msg_store[object_id]
92
+ message = entry.message
93
+
94
+ # Skip messages that have already been retrieved
95
+ if entry.is_retrieved:
96
+ continue
97
+
98
+ # Skip messages whose run_id doesn't match the filter
99
+ if run_ids is not None:
100
+ if message.metadata.run_id not in run_ids:
101
+ continue
102
+
103
+ # If is_reply filter is set, filter for reply/non-reply messages
104
+ if is_reply is not None:
105
+ is_reply_message = message.metadata.reply_to_message_id != ""
106
+ # XOR logic to filter mismatched types (reply vs non-reply)
107
+ if is_reply ^ is_reply_message:
108
+ continue
109
+
110
+ # Add the message to the result set
111
+ selected_messages.append(message)
112
+
113
+ # Mark the message as retrieved
114
+ entry.is_retrieved = True
115
+
116
+ # Stop if the number of collected messages reaches the limit
117
+ if limit is not None and len(selected_messages) >= limit:
118
+ break
119
+
120
+ return selected_messages
121
+
122
+ def delete_messages(
123
+ self,
124
+ *,
125
+ message_ids: Optional[Sequence[str]] = None,
126
+ ) -> None:
127
+ """Delete messages based on the specified filters."""
128
+ with self.lock_msg_store:
129
+ if message_ids is None:
130
+ # If no message IDs are provided, clear the entire store
131
+ self.msg_store.clear()
132
+ return
133
+
134
+ # Remove specified messages from the store
135
+ for msg_id in message_ids:
136
+ self.msg_store.pop(msg_id, None)
137
+
138
+ def store_run(self, run: Run) -> None:
139
+ """Store a run."""
140
+ with self.lock_run_store:
141
+ self.run_store[run.run_id] = run
142
+
143
+ def get_run(self, run_id: int) -> Optional[Run]:
144
+ """Retrieve a run by its ID."""
145
+ with self.lock_run_store:
146
+ return self.run_store.get(run_id)
147
+
148
+ def store_context(self, context: Context) -> None:
149
+ """Store a context."""
150
+ with self.lock_ctx_store:
151
+ self.ctx_store[context.run_id] = context
152
+
153
+ def get_context(self, run_id: int) -> Optional[Context]:
154
+ """Retrieve a context by its run ID."""
155
+ with self.lock_ctx_store:
156
+ return self.ctx_store.get(run_id)
157
+
158
+ def get_run_ids_with_pending_messages(self) -> Sequence[int]:
159
+ """Retrieve run IDs that have at least one pending message."""
160
+ # Collect run IDs from messages
161
+ with self.lock_msg_store:
162
+ ret = {
163
+ entry.message.metadata.run_id
164
+ for entry in self.msg_store.values()
165
+ if entry.message.metadata.reply_to_message_id == ""
166
+ and not entry.is_retrieved
167
+ }
168
+
169
+ # Remove run IDs that have tokens stored (indicating they are in progress)
170
+ with self.lock_token_store:
171
+ ret -= set(self.token_store.keys())
172
+ return list(ret)
173
+
174
+ def create_token(self, run_id: int) -> str:
175
+ """Create a token for the given run ID."""
176
+ token = secrets.token_hex(FLWR_APP_TOKEN_LENGTH) # Generate a random token
177
+ with self.lock_token_store:
178
+ if run_id in self.token_store:
179
+ raise ValueError("Token already created for this run ID")
180
+ self.token_store[run_id] = token
181
+ self.token_to_run_id[token] = run_id
182
+ return token
183
+
184
+ def verify_token(self, run_id: int, token: str) -> bool:
185
+ """Verify a token for the given run ID."""
186
+ with self.lock_token_store:
187
+ return self.token_store.get(run_id) == token
188
+
189
+ def delete_token(self, run_id: int) -> None:
190
+ """Delete the token for the given run ID."""
191
+ with self.lock_token_store:
192
+ token = self.token_store.pop(run_id, None)
193
+ if token is not None:
194
+ self.token_to_run_id.pop(token, None)
195
+
196
+ def get_run_id_by_token(self, token: str) -> Optional[int]:
197
+ """Get the run ID associated with a given token."""
198
+ with self.lock_token_store:
199
+ return self.token_to_run_id.get(token)
@@ -0,0 +1,227 @@
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
+ """Abstract base class NodeState."""
16
+
17
+
18
+ from abc import ABC, abstractmethod
19
+ from collections.abc import Sequence
20
+ from typing import Optional
21
+
22
+ from flwr.common import Context, Message
23
+ from flwr.common.typing import Run
24
+
25
+
26
+ class NodeState(ABC):
27
+ """Abstract base class for node state."""
28
+
29
+ @abstractmethod
30
+ def set_node_id(self, node_id: int) -> None:
31
+ """Set the node ID."""
32
+
33
+ @abstractmethod
34
+ def get_node_id(self) -> int:
35
+ """Get the node ID."""
36
+
37
+ @abstractmethod
38
+ def store_message(self, message: Message) -> Optional[str]:
39
+ """Store a message.
40
+
41
+ Parameters
42
+ ----------
43
+ message : Message
44
+ The message to store.
45
+
46
+ Returns
47
+ -------
48
+ Optional[str]
49
+ The object ID of the stored message, or None if storage failed.
50
+ """
51
+
52
+ @abstractmethod
53
+ def get_messages(
54
+ self,
55
+ *,
56
+ run_ids: Optional[Sequence[int]] = None,
57
+ is_reply: Optional[bool] = None,
58
+ limit: Optional[int] = None,
59
+ ) -> Sequence[Message]:
60
+ """Retrieve messages based on the specified filters.
61
+
62
+ If a filter is set to None, it is ignored.
63
+ If multiple filters are provided, they are combined using AND logic.
64
+
65
+ Parameters
66
+ ----------
67
+ run_ids : Optional[Sequence[int]] (default: None)
68
+ Sequence of run IDs to filter by. If a sequence is provided,
69
+ it is treated as an OR condition.
70
+ is_reply : Optional[bool] (default: None)
71
+ If True, filter for reply messages; if False, filter for non-reply
72
+ (instruction) messages.
73
+ limit : Optional[int] (default: None)
74
+ Maximum number of messages to return. If None, no limit is applied.
75
+
76
+ Returns
77
+ -------
78
+ Sequence[Message]
79
+ A sequence of messages matching the specified filters.
80
+
81
+ Notes
82
+ -----
83
+ **IMPORTANT:** Retrieved messages will **NOT** be returned again by subsequent
84
+ calls to this method, even if the filters match them.
85
+ """
86
+
87
+ @abstractmethod
88
+ def delete_messages(
89
+ self,
90
+ *,
91
+ message_ids: Optional[Sequence[str]] = None,
92
+ ) -> None:
93
+ """Delete messages based on the specified filters.
94
+
95
+ If a filter is set to None, it is ignored.
96
+ If multiple filters are provided, they are combined using AND logic.
97
+
98
+ Parameters
99
+ ----------
100
+ message_ids : Optional[Sequence[str]] (default: None)
101
+ Sequence of message (object) IDs to filter by. If a sequence is provided,
102
+ it is treated as an OR condition.
103
+
104
+ Notes
105
+ -----
106
+ **IMPORTANT:** **All messages** will be deleted if no filters are provided.
107
+ """
108
+
109
+ @abstractmethod
110
+ def store_run(self, run: Run) -> None:
111
+ """Store a run.
112
+
113
+ Parameters
114
+ ----------
115
+ run : Run
116
+ The `Run` instance to store.
117
+ """
118
+
119
+ @abstractmethod
120
+ def get_run(self, run_id: int) -> Optional[Run]:
121
+ """Retrieve a run by its ID.
122
+
123
+ Parameters
124
+ ----------
125
+ run_id : int
126
+ The ID of the run to retrieve.
127
+
128
+ Returns
129
+ -------
130
+ Optional[Run]
131
+ The `Run` instance if found, otherwise None.
132
+ """
133
+
134
+ @abstractmethod
135
+ def store_context(self, context: Context) -> None:
136
+ """Store a context.
137
+
138
+ Parameters
139
+ ----------
140
+ context : Context
141
+ The context to store.
142
+ """
143
+
144
+ @abstractmethod
145
+ def get_context(self, run_id: int) -> Optional[Context]:
146
+ """Retrieve a context by its run ID.
147
+
148
+ Parameters
149
+ ----------
150
+ run_id : int
151
+ The ID of the run with which the context is associated.
152
+
153
+ Returns
154
+ -------
155
+ Optional[Context]
156
+ The `Context` instance if found, otherwise None.
157
+ """
158
+
159
+ @abstractmethod
160
+ def get_run_ids_with_pending_messages(self) -> Sequence[int]:
161
+ """Retrieve run IDs that have at least one pending message.
162
+
163
+ Run IDs that are currently in progress (i.e., those associated with tokens)
164
+ will not be returned, even if they have pending messages.
165
+
166
+ Returns
167
+ -------
168
+ Sequence[int]
169
+ Sequence of run IDs with pending messages.
170
+ """
171
+
172
+ @abstractmethod
173
+ def create_token(self, run_id: int) -> str:
174
+ """Create a token for the given run ID.
175
+
176
+ Parameters
177
+ ----------
178
+ run_id : int
179
+ The ID of the run for which to create a token.
180
+
181
+ Returns
182
+ -------
183
+ str
184
+ A unique token associated with the run ID.
185
+ """
186
+
187
+ @abstractmethod
188
+ def verify_token(self, run_id: int, token: str) -> bool:
189
+ """Verify a token for the given run ID.
190
+
191
+ Parameters
192
+ ----------
193
+ run_id : int
194
+ The ID of the run for which to verify the token.
195
+ token : str
196
+ The token to verify.
197
+
198
+ Returns
199
+ -------
200
+ bool
201
+ True if the token is valid for the run ID, False otherwise.
202
+ """
203
+
204
+ @abstractmethod
205
+ def delete_token(self, run_id: int) -> None:
206
+ """Delete the token for the given run ID.
207
+
208
+ Parameters
209
+ ----------
210
+ run_id : int
211
+ The ID of the run for which to delete the token.
212
+ """
213
+
214
+ @abstractmethod
215
+ def get_run_id_by_token(self, token: str) -> Optional[int]:
216
+ """Get the run ID associated with a given token.
217
+
218
+ Parameters
219
+ ----------
220
+ token : str
221
+ The token to look up.
222
+
223
+ Returns
224
+ -------
225
+ Optional[int]
226
+ The run ID if the token is valid, otherwise None.
227
+ """
@@ -0,0 +1,15 @@
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
+ """Flower SuperNode components."""