flwr-nightly 1.23.0.dev20251020__py3-none-any.whl → 1.23.0.dev20251021__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of flwr-nightly might be problematic. Click here for more details.
- flwr/cli/ls.py +5 -5
- flwr/cli/supernode/ls.py +8 -15
- flwr/common/constant.py +12 -4
- flwr/common/inflatable_utils.py +10 -10
- flwr/common/record/array.py +3 -3
- flwr/proto/control_pb2.py +6 -6
- flwr/proto/control_pb2.pyi +0 -5
- flwr/server/superlink/linkstate/in_memory_linkstate.py +5 -1
- flwr/server/superlink/linkstate/sqlite_linkstate.py +29 -106
- flwr/supercore/sqlite_mixin.py +188 -0
- flwr/superlink/servicer/control/control_servicer.py +6 -70
- {flwr_nightly-1.23.0.dev20251020.dist-info → flwr_nightly-1.23.0.dev20251021.dist-info}/METADATA +1 -1
- {flwr_nightly-1.23.0.dev20251020.dist-info → flwr_nightly-1.23.0.dev20251021.dist-info}/RECORD +15 -14
- {flwr_nightly-1.23.0.dev20251020.dist-info → flwr_nightly-1.23.0.dev20251021.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.23.0.dev20251020.dist-info → flwr_nightly-1.23.0.dev20251021.dist-info}/entry_points.txt +0 -0
flwr/cli/ls.py
CHANGED
|
@@ -220,14 +220,14 @@ def _to_table(run_list: list[_RunListType]) -> Table:
|
|
|
220
220
|
|
|
221
221
|
# Add columns
|
|
222
222
|
table.add_column(
|
|
223
|
-
Text("Run ID", justify="center"), style="
|
|
223
|
+
Text("Run ID", justify="center"), style="bright_black", no_wrap=True
|
|
224
224
|
)
|
|
225
|
-
table.add_column(Text("FAB", justify="center"), style="
|
|
225
|
+
table.add_column(Text("FAB", justify="center"), style="bright_black")
|
|
226
226
|
table.add_column(Text("Status", justify="center"))
|
|
227
227
|
table.add_column(Text("Elapsed", justify="center"), style="blue")
|
|
228
|
-
table.add_column(Text("Created At", justify="center"), style="
|
|
229
|
-
table.add_column(Text("Running At", justify="center"), style="
|
|
230
|
-
table.add_column(Text("Finished At", justify="center"), style="
|
|
228
|
+
table.add_column(Text("Created At", justify="center"), style="bright_black")
|
|
229
|
+
table.add_column(Text("Running At", justify="center"), style="bright_black")
|
|
230
|
+
table.add_column(Text("Finished At", justify="center"), style="bright_black")
|
|
231
231
|
|
|
232
232
|
for row in run_list:
|
|
233
233
|
(
|
flwr/cli/supernode/ls.py
CHANGED
|
@@ -32,7 +32,7 @@ from flwr.cli.config_utils import (
|
|
|
32
32
|
process_loaded_project_config,
|
|
33
33
|
validate_federation_in_project_config,
|
|
34
34
|
)
|
|
35
|
-
from flwr.common.constant import FAB_CONFIG_FILE, CliOutputFormat
|
|
35
|
+
from flwr.common.constant import FAB_CONFIG_FILE, NOOP_FLWR_AID, CliOutputFormat
|
|
36
36
|
from flwr.common.date import format_timedelta, isoformat8601_utc
|
|
37
37
|
from flwr.common.logger import print_json_error, redirect_output, restore_output
|
|
38
38
|
from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
|
@@ -73,13 +73,6 @@ def ls( # pylint: disable=R0914, R0913, R0917
|
|
|
73
73
|
help="Enable verbose output",
|
|
74
74
|
),
|
|
75
75
|
] = False,
|
|
76
|
-
dry_run: Annotated[
|
|
77
|
-
bool,
|
|
78
|
-
typer.Option(
|
|
79
|
-
"--dry-run",
|
|
80
|
-
help="Simulate the command without contacting any SuperNodes",
|
|
81
|
-
),
|
|
82
|
-
] = False,
|
|
83
76
|
) -> None:
|
|
84
77
|
"""List SuperNodes in the federation."""
|
|
85
78
|
# Resolve command used (list or ls)
|
|
@@ -105,7 +98,7 @@ def ls( # pylint: disable=R0914, R0913, R0917
|
|
|
105
98
|
channel = init_channel(app, federation_config, auth_plugin)
|
|
106
99
|
stub = ControlStub(channel)
|
|
107
100
|
typer.echo("📄 Listing all nodes...")
|
|
108
|
-
formatted_nodes = _list_nodes(stub
|
|
101
|
+
formatted_nodes = _list_nodes(stub)
|
|
109
102
|
restore_output()
|
|
110
103
|
if output_format == CliOutputFormat.JSON:
|
|
111
104
|
Console().print_json(_to_json(formatted_nodes, verbose=verbose))
|
|
@@ -132,10 +125,10 @@ def ls( # pylint: disable=R0914, R0913, R0917
|
|
|
132
125
|
captured_output.close()
|
|
133
126
|
|
|
134
127
|
|
|
135
|
-
def _list_nodes(stub: ControlStub
|
|
128
|
+
def _list_nodes(stub: ControlStub) -> list[_NodeListType]:
|
|
136
129
|
"""List all nodes."""
|
|
137
130
|
with flwr_cli_grpc_exc_handler():
|
|
138
|
-
res: ListNodesResponse = stub.ListNodes(ListNodesRequest(
|
|
131
|
+
res: ListNodesResponse = stub.ListNodes(ListNodesRequest())
|
|
139
132
|
|
|
140
133
|
return _format_nodes(list(res.nodes_info), res.now)
|
|
141
134
|
|
|
@@ -185,12 +178,12 @@ def _to_table(nodes_info: list[_NodeListType], verbose: bool) -> Table:
|
|
|
185
178
|
|
|
186
179
|
# Add columns
|
|
187
180
|
table.add_column(
|
|
188
|
-
Text("Node ID", justify="center"), style="
|
|
181
|
+
Text("Node ID", justify="center"), style="bright_black", no_wrap=True
|
|
189
182
|
)
|
|
190
|
-
table.add_column(Text("Owner", justify="center")
|
|
183
|
+
table.add_column(Text("Owner", justify="center"))
|
|
191
184
|
table.add_column(Text("Status", justify="center"))
|
|
192
185
|
table.add_column(Text("Elapsed", justify="center"))
|
|
193
|
-
table.add_column(Text("Status Changed @", justify="center"), style="
|
|
186
|
+
table.add_column(Text("Status Changed @", justify="center"), style="bright_black")
|
|
194
187
|
|
|
195
188
|
for row in nodes_info:
|
|
196
189
|
(
|
|
@@ -223,7 +216,7 @@ def _to_table(nodes_info: list[_NodeListType], verbose: bool) -> Table:
|
|
|
223
216
|
|
|
224
217
|
formatted_row = (
|
|
225
218
|
f"[bold]{node_id}[/bold]",
|
|
226
|
-
f"{owner_aid}",
|
|
219
|
+
f"{owner_aid}" if owner_aid != NOOP_FLWR_AID else f"[dim]{owner_aid}[/dim]",
|
|
227
220
|
f"[{status_style}]{status}",
|
|
228
221
|
f"[cyan]{elapse_activated}[/cyan]" if status == "online" else "",
|
|
229
222
|
time_at,
|
flwr/common/constant.py
CHANGED
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
+
import os
|
|
21
|
+
|
|
20
22
|
TRANSPORT_TYPE_GRPC_BIDI = "grpc-bidi"
|
|
21
23
|
TRANSPORT_TYPE_GRPC_RERE = "grpc-rere"
|
|
22
24
|
TRANSPORT_TYPE_GRPC_ADAPTER = "grpc-adapter"
|
|
@@ -135,7 +137,9 @@ GC_THRESHOLD = 200_000_000 # 200 MB
|
|
|
135
137
|
# Constants for Inflatable
|
|
136
138
|
HEAD_BODY_DIVIDER = b"\x00"
|
|
137
139
|
HEAD_VALUE_DIVIDER = " "
|
|
138
|
-
|
|
140
|
+
FLWR_PRIVATE_MAX_ARRAY_CHUNK_SIZE = int(
|
|
141
|
+
os.getenv("FLWR_PRIVATE_MAX_ARRAY_CHUNK_SIZE", "5242880")
|
|
142
|
+
) # 5 MB
|
|
139
143
|
|
|
140
144
|
# Constants for serialization
|
|
141
145
|
INT64_MAX_VALUE = 9223372036854775807 # (1 << 63) - 1
|
|
@@ -144,8 +148,12 @@ INT64_MAX_VALUE = 9223372036854775807 # (1 << 63) - 1
|
|
|
144
148
|
FLWR_APP_TOKEN_LENGTH = 128 # Length of the token used
|
|
145
149
|
|
|
146
150
|
# Constants for object pushing and pulling
|
|
147
|
-
|
|
148
|
-
|
|
151
|
+
FLWR_PRIVATE_MAX_CONCURRENT_OBJ_PUSHES = int(
|
|
152
|
+
os.getenv("FLWR_PRIVATE_MAX_CONCURRENT_OBJ_PUSHES", "2")
|
|
153
|
+
) # Default maximum number of concurrent pushes
|
|
154
|
+
FLWR_PRIVATE_MAX_CONCURRENT_OBJ_PULLS = int(
|
|
155
|
+
os.getenv("FLWR_PRIVATE_MAX_CONCURRENT_OBJ_PULLS", "2")
|
|
156
|
+
) # Default maximum number of concurrent pulls
|
|
149
157
|
PULL_MAX_TIME = 7200 # Default maximum time to wait for pulling objects
|
|
150
158
|
PULL_MAX_TRIES_PER_OBJECT = 500 # Default maximum number of tries to pull an object
|
|
151
159
|
PULL_INITIAL_BACKOFF = 1 # Initial backoff time for pulling objects
|
|
@@ -299,5 +307,5 @@ class ExecPluginType:
|
|
|
299
307
|
|
|
300
308
|
|
|
301
309
|
# Constants for No-op auth plugins
|
|
302
|
-
NOOP_FLWR_AID = "
|
|
310
|
+
NOOP_FLWR_AID = "<none>"
|
|
303
311
|
NOOP_ACCOUNT_NAME = "sys_noauth"
|
flwr/common/inflatable_utils.py
CHANGED
|
@@ -25,10 +25,10 @@ from typing import Callable, Optional, TypeVar
|
|
|
25
25
|
from flwr.proto.message_pb2 import ObjectTree # pylint: disable=E0611
|
|
26
26
|
|
|
27
27
|
from .constant import (
|
|
28
|
+
FLWR_PRIVATE_MAX_CONCURRENT_OBJ_PULLS,
|
|
29
|
+
FLWR_PRIVATE_MAX_CONCURRENT_OBJ_PUSHES,
|
|
28
30
|
HEAD_BODY_DIVIDER,
|
|
29
31
|
HEAD_VALUE_DIVIDER,
|
|
30
|
-
MAX_CONCURRENT_PULLS,
|
|
31
|
-
MAX_CONCURRENT_PUSHES,
|
|
32
32
|
PULL_BACKOFF_CAP,
|
|
33
33
|
PULL_INITIAL_BACKOFF,
|
|
34
34
|
PULL_MAX_TIME,
|
|
@@ -118,7 +118,7 @@ def push_objects(
|
|
|
118
118
|
*,
|
|
119
119
|
object_ids_to_push: Optional[set[str]] = None,
|
|
120
120
|
keep_objects: bool = False,
|
|
121
|
-
max_concurrent_pushes: int =
|
|
121
|
+
max_concurrent_pushes: int = FLWR_PRIVATE_MAX_CONCURRENT_OBJ_PUSHES,
|
|
122
122
|
) -> None:
|
|
123
123
|
"""Push multiple objects to the servicer.
|
|
124
124
|
|
|
@@ -137,7 +137,7 @@ def push_objects(
|
|
|
137
137
|
If `True`, the original objects will be kept in the `objects` dictionary
|
|
138
138
|
after pushing. If `False`, they will be removed from the dictionary to avoid
|
|
139
139
|
high memory usage.
|
|
140
|
-
max_concurrent_pushes : int (default:
|
|
140
|
+
max_concurrent_pushes : int (default: FLWR_PRIVATE_MAX_CONCURRENT_OBJ_PUSHES)
|
|
141
141
|
The maximum number of concurrent pushes to perform.
|
|
142
142
|
"""
|
|
143
143
|
lock = threading.Lock()
|
|
@@ -168,7 +168,7 @@ def push_object_contents_from_iterable(
|
|
|
168
168
|
object_contents: Iterable[tuple[str, bytes]],
|
|
169
169
|
push_object_fn: Callable[[str, bytes], None],
|
|
170
170
|
*,
|
|
171
|
-
max_concurrent_pushes: int =
|
|
171
|
+
max_concurrent_pushes: int = FLWR_PRIVATE_MAX_CONCURRENT_OBJ_PUSHES,
|
|
172
172
|
) -> None:
|
|
173
173
|
"""Push multiple object contents to the servicer.
|
|
174
174
|
|
|
@@ -181,7 +181,7 @@ def push_object_contents_from_iterable(
|
|
|
181
181
|
A function that takes an object ID and its content as bytes, and pushes
|
|
182
182
|
it to the servicer. This function should raise `ObjectIdNotPreregisteredError`
|
|
183
183
|
if the object ID is not pre-registered.
|
|
184
|
-
max_concurrent_pushes : int (default:
|
|
184
|
+
max_concurrent_pushes : int (default: FLWR_PRIVATE_MAX_CONCURRENT_OBJ_PUSHES)
|
|
185
185
|
The maximum number of concurrent pushes to perform.
|
|
186
186
|
"""
|
|
187
187
|
|
|
@@ -210,7 +210,7 @@ def pull_objects( # pylint: disable=too-many-arguments,too-many-locals
|
|
|
210
210
|
object_ids: list[str],
|
|
211
211
|
pull_object_fn: Callable[[str], bytes],
|
|
212
212
|
*,
|
|
213
|
-
max_concurrent_pulls: int =
|
|
213
|
+
max_concurrent_pulls: int = FLWR_PRIVATE_MAX_CONCURRENT_OBJ_PULLS,
|
|
214
214
|
max_time: Optional[float] = PULL_MAX_TIME,
|
|
215
215
|
max_tries_per_object: Optional[int] = PULL_MAX_TRIES_PER_OBJECT,
|
|
216
216
|
initial_backoff: float = PULL_INITIAL_BACKOFF,
|
|
@@ -227,7 +227,7 @@ def pull_objects( # pylint: disable=too-many-arguments,too-many-locals
|
|
|
227
227
|
The function should raise `ObjectUnavailableError` if the object is not yet
|
|
228
228
|
available, or `ObjectIdNotPreregisteredError` if the object ID is not
|
|
229
229
|
pre-registered.
|
|
230
|
-
max_concurrent_pulls : int (default:
|
|
230
|
+
max_concurrent_pulls : int (default: FLWR_PRIVATE_MAX_CONCURRENT_OBJ_PULLS)
|
|
231
231
|
The maximum number of concurrent pulls to perform.
|
|
232
232
|
max_time : Optional[float] (default: PULL_MAX_TIME)
|
|
233
233
|
The maximum time to wait for all pulls to complete. If `None`, waits
|
|
@@ -442,7 +442,7 @@ def pull_and_inflate_object_from_tree( # pylint: disable=R0913
|
|
|
442
442
|
confirm_object_received_fn: Callable[[str], None],
|
|
443
443
|
*,
|
|
444
444
|
return_type: type[T] = InflatableObject, # type: ignore
|
|
445
|
-
max_concurrent_pulls: int =
|
|
445
|
+
max_concurrent_pulls: int = FLWR_PRIVATE_MAX_CONCURRENT_OBJ_PULLS,
|
|
446
446
|
max_time: Optional[float] = PULL_MAX_TIME,
|
|
447
447
|
max_tries_per_object: Optional[int] = PULL_MAX_TRIES_PER_OBJECT,
|
|
448
448
|
initial_backoff: float = PULL_INITIAL_BACKOFF,
|
|
@@ -460,7 +460,7 @@ def pull_and_inflate_object_from_tree( # pylint: disable=R0913
|
|
|
460
460
|
A function to confirm that the object has been received.
|
|
461
461
|
return_type : type[T] (default: InflatableObject)
|
|
462
462
|
The type of the object to return. Must be a subclass of `InflatableObject`.
|
|
463
|
-
max_concurrent_pulls : int (default:
|
|
463
|
+
max_concurrent_pulls : int (default: FLWR_PRIVATE_MAX_CONCURRENT_OBJ_PULLS)
|
|
464
464
|
The maximum number of concurrent pulls to perform.
|
|
465
465
|
max_time : Optional[float] (default: PULL_MAX_TIME)
|
|
466
466
|
The maximum time to wait for all pulls to complete. If `None`, waits
|
flwr/common/record/array.py
CHANGED
|
@@ -25,7 +25,7 @@ from typing import TYPE_CHECKING, Any, cast, overload
|
|
|
25
25
|
|
|
26
26
|
import numpy as np
|
|
27
27
|
|
|
28
|
-
from ..constant import
|
|
28
|
+
from ..constant import FLWR_PRIVATE_MAX_ARRAY_CHUNK_SIZE, SType
|
|
29
29
|
from ..inflatable import (
|
|
30
30
|
InflatableObject,
|
|
31
31
|
add_header_to_object_body,
|
|
@@ -272,8 +272,8 @@ class Array(InflatableObject):
|
|
|
272
272
|
chunks: list[tuple[str, InflatableObject]] = []
|
|
273
273
|
# memoryview allows for zero-copy slicing
|
|
274
274
|
data_view = memoryview(self.data)
|
|
275
|
-
for start in range(0, len(data_view),
|
|
276
|
-
end = min(start +
|
|
275
|
+
for start in range(0, len(data_view), FLWR_PRIVATE_MAX_ARRAY_CHUNK_SIZE):
|
|
276
|
+
end = min(start + FLWR_PRIVATE_MAX_ARRAY_CHUNK_SIZE, len(data_view))
|
|
277
277
|
ac = ArrayChunk(data_view[start:end])
|
|
278
278
|
chunks.append((ac.object_id, ac))
|
|
279
279
|
|
flwr/proto/control_pb2.py
CHANGED
|
@@ -19,7 +19,7 @@ from flwr.proto import run_pb2 as flwr_dot_proto_dot_run__pb2
|
|
|
19
19
|
from flwr.proto import node_pb2 as flwr_dot_proto_dot_node__pb2
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x18\x66lwr/proto/control.proto\x12\nflwr.proto\x1a\x14\x66lwr/proto/fab.proto\x1a\x1a\x66lwr/proto/transport.proto\x1a\x1b\x66lwr/proto/recorddict.proto\x1a\x14\x66lwr/proto/run.proto\x1a\x15\x66lwr/proto/node.proto\"\xfa\x01\n\x0fStartRunRequest\x12\x1c\n\x03\x66\x61\x62\x18\x01 \x01(\x0b\x32\x0f.flwr.proto.Fab\x12H\n\x0foverride_config\x18\x02 \x03(\x0b\x32/.flwr.proto.StartRunRequest.OverrideConfigEntry\x12\x34\n\x12\x66\x65\x64\x65ration_options\x18\x03 \x01(\x0b\x32\x18.flwr.proto.ConfigRecord\x1aI\n\x13OverrideConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.flwr.proto.Scalar:\x02\x38\x01\"2\n\x10StartRunResponse\x12\x13\n\x06run_id\x18\x01 \x01(\x04H\x00\x88\x01\x01\x42\t\n\x07_run_id\"<\n\x11StreamLogsRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\x12\x17\n\x0f\x61\x66ter_timestamp\x18\x02 \x01(\x01\"B\n\x12StreamLogsResponse\x12\x12\n\nlog_output\x18\x01 \x01(\t\x12\x18\n\x10latest_timestamp\x18\x02 \x01(\x01\"1\n\x0fListRunsRequest\x12\x13\n\x06run_id\x18\x01 \x01(\x04H\x00\x88\x01\x01\x42\t\n\x07_run_id\"\x9d\x01\n\x10ListRunsResponse\x12;\n\x08run_dict\x18\x01 \x03(\x0b\x32).flwr.proto.ListRunsResponse.RunDictEntry\x12\x0b\n\x03now\x18\x02 \x01(\t\x1a?\n\x0cRunDictEntry\x12\x0b\n\x03key\x18\x01 \x01(\x04\x12\x1e\n\x05value\x18\x02 \x01(\x0b\x32\x0f.flwr.proto.Run:\x02\x38\x01\"\x18\n\x16GetLoginDetailsRequest\"\x8b\x01\n\x17GetLoginDetailsResponse\x12\x12\n\nauthn_type\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65vice_code\x18\x02 \x01(\t\x12!\n\x19verification_uri_complete\x18\x03 \x01(\t\x12\x12\n\nexpires_in\x18\x04 \x01(\x03\x12\x10\n\x08interval\x18\x05 \x01(\x03\"+\n\x14GetAuthTokensRequest\x12\x13\n\x0b\x64\x65vice_code\x18\x01 \x01(\t\"D\n\x15GetAuthTokensResponse\x12\x14\n\x0c\x61\x63\x63\x65ss_token\x18\x01 \x01(\t\x12\x15\n\rrefresh_token\x18\x02 \x01(\t\" \n\x0eStopRunRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\"\"\n\x0fStopRunResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"&\n\x14PullArtifactsRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\"1\n\x15PullArtifactsResponse\x12\x10\n\x03url\x18\x01 \x01(\tH\x00\x88\x01\x01\x42\x06\n\x04_url\")\n\x13RegisterNodeRequest\x12\x12\n\npublic_key\x18\x01 \x01(\x0c\"8\n\x14RegisterNodeResponse\x12\x14\n\x07node_id\x18\x01 \x01(\x04H\x00\x88\x01\x01\x42\n\n\x08_node_id\"(\n\x15UnregisterNodeRequest\x12\x0f\n\x07node_id\x18\x01 \x01(\x04\"\x18\n\x16UnregisterNodeResponse\"
|
|
22
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x18\x66lwr/proto/control.proto\x12\nflwr.proto\x1a\x14\x66lwr/proto/fab.proto\x1a\x1a\x66lwr/proto/transport.proto\x1a\x1b\x66lwr/proto/recorddict.proto\x1a\x14\x66lwr/proto/run.proto\x1a\x15\x66lwr/proto/node.proto\"\xfa\x01\n\x0fStartRunRequest\x12\x1c\n\x03\x66\x61\x62\x18\x01 \x01(\x0b\x32\x0f.flwr.proto.Fab\x12H\n\x0foverride_config\x18\x02 \x03(\x0b\x32/.flwr.proto.StartRunRequest.OverrideConfigEntry\x12\x34\n\x12\x66\x65\x64\x65ration_options\x18\x03 \x01(\x0b\x32\x18.flwr.proto.ConfigRecord\x1aI\n\x13OverrideConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.flwr.proto.Scalar:\x02\x38\x01\"2\n\x10StartRunResponse\x12\x13\n\x06run_id\x18\x01 \x01(\x04H\x00\x88\x01\x01\x42\t\n\x07_run_id\"<\n\x11StreamLogsRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\x12\x17\n\x0f\x61\x66ter_timestamp\x18\x02 \x01(\x01\"B\n\x12StreamLogsResponse\x12\x12\n\nlog_output\x18\x01 \x01(\t\x12\x18\n\x10latest_timestamp\x18\x02 \x01(\x01\"1\n\x0fListRunsRequest\x12\x13\n\x06run_id\x18\x01 \x01(\x04H\x00\x88\x01\x01\x42\t\n\x07_run_id\"\x9d\x01\n\x10ListRunsResponse\x12;\n\x08run_dict\x18\x01 \x03(\x0b\x32).flwr.proto.ListRunsResponse.RunDictEntry\x12\x0b\n\x03now\x18\x02 \x01(\t\x1a?\n\x0cRunDictEntry\x12\x0b\n\x03key\x18\x01 \x01(\x04\x12\x1e\n\x05value\x18\x02 \x01(\x0b\x32\x0f.flwr.proto.Run:\x02\x38\x01\"\x18\n\x16GetLoginDetailsRequest\"\x8b\x01\n\x17GetLoginDetailsResponse\x12\x12\n\nauthn_type\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65vice_code\x18\x02 \x01(\t\x12!\n\x19verification_uri_complete\x18\x03 \x01(\t\x12\x12\n\nexpires_in\x18\x04 \x01(\x03\x12\x10\n\x08interval\x18\x05 \x01(\x03\"+\n\x14GetAuthTokensRequest\x12\x13\n\x0b\x64\x65vice_code\x18\x01 \x01(\t\"D\n\x15GetAuthTokensResponse\x12\x14\n\x0c\x61\x63\x63\x65ss_token\x18\x01 \x01(\t\x12\x15\n\rrefresh_token\x18\x02 \x01(\t\" \n\x0eStopRunRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\"\"\n\x0fStopRunResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"&\n\x14PullArtifactsRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\"1\n\x15PullArtifactsResponse\x12\x10\n\x03url\x18\x01 \x01(\tH\x00\x88\x01\x01\x42\x06\n\x04_url\")\n\x13RegisterNodeRequest\x12\x12\n\npublic_key\x18\x01 \x01(\x0c\"8\n\x14RegisterNodeResponse\x12\x14\n\x07node_id\x18\x01 \x01(\x04H\x00\x88\x01\x01\x42\n\n\x08_node_id\"(\n\x15UnregisterNodeRequest\x12\x0f\n\x07node_id\x18\x01 \x01(\x04\"\x18\n\x16UnregisterNodeResponse\"\x12\n\x10ListNodesRequest\"J\n\x11ListNodesResponse\x12(\n\nnodes_info\x18\x01 \x03(\x0b\x32\x14.flwr.proto.NodeInfo\x12\x0b\n\x03now\x18\x02 \x01(\t2\xbc\x06\n\x07\x43ontrol\x12G\n\x08StartRun\x12\x1b.flwr.proto.StartRunRequest\x1a\x1c.flwr.proto.StartRunResponse\"\x00\x12\x44\n\x07StopRun\x12\x1a.flwr.proto.StopRunRequest\x1a\x1b.flwr.proto.StopRunResponse\"\x00\x12O\n\nStreamLogs\x12\x1d.flwr.proto.StreamLogsRequest\x1a\x1e.flwr.proto.StreamLogsResponse\"\x00\x30\x01\x12G\n\x08ListRuns\x12\x1b.flwr.proto.ListRunsRequest\x1a\x1c.flwr.proto.ListRunsResponse\"\x00\x12\\\n\x0fGetLoginDetails\x12\".flwr.proto.GetLoginDetailsRequest\x1a#.flwr.proto.GetLoginDetailsResponse\"\x00\x12V\n\rGetAuthTokens\x12 .flwr.proto.GetAuthTokensRequest\x1a!.flwr.proto.GetAuthTokensResponse\"\x00\x12V\n\rPullArtifacts\x12 .flwr.proto.PullArtifactsRequest\x1a!.flwr.proto.PullArtifactsResponse\"\x00\x12S\n\x0cRegisterNode\x12\x1f.flwr.proto.RegisterNodeRequest\x1a .flwr.proto.RegisterNodeResponse\"\x00\x12Y\n\x0eUnregisterNode\x12!.flwr.proto.UnregisterNodeRequest\x1a\".flwr.proto.UnregisterNodeResponse\"\x00\x12J\n\tListNodes\x12\x1c.flwr.proto.ListNodesRequest\x1a\x1d.flwr.proto.ListNodesResponse\"\x00\x62\x06proto3')
|
|
23
23
|
|
|
24
24
|
_globals = globals()
|
|
25
25
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
@@ -71,9 +71,9 @@ if _descriptor._USE_C_DESCRIPTORS == False:
|
|
|
71
71
|
_globals['_UNREGISTERNODERESPONSE']._serialized_start=1397
|
|
72
72
|
_globals['_UNREGISTERNODERESPONSE']._serialized_end=1421
|
|
73
73
|
_globals['_LISTNODESREQUEST']._serialized_start=1423
|
|
74
|
-
_globals['_LISTNODESREQUEST']._serialized_end=
|
|
75
|
-
_globals['_LISTNODESRESPONSE']._serialized_start=
|
|
76
|
-
_globals['_LISTNODESRESPONSE']._serialized_end=
|
|
77
|
-
_globals['_CONTROL']._serialized_start=
|
|
78
|
-
_globals['_CONTROL']._serialized_end=
|
|
74
|
+
_globals['_LISTNODESREQUEST']._serialized_end=1441
|
|
75
|
+
_globals['_LISTNODESRESPONSE']._serialized_start=1443
|
|
76
|
+
_globals['_LISTNODESRESPONSE']._serialized_end=1517
|
|
77
|
+
_globals['_CONTROL']._serialized_start=1520
|
|
78
|
+
_globals['_CONTROL']._serialized_end=2348
|
|
79
79
|
# @@protoc_insertion_point(module_scope)
|
flwr/proto/control_pb2.pyi
CHANGED
|
@@ -279,13 +279,8 @@ global___UnregisterNodeResponse = UnregisterNodeResponse
|
|
|
279
279
|
|
|
280
280
|
class ListNodesRequest(google.protobuf.message.Message):
|
|
281
281
|
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
|
282
|
-
DRY_RUN_FIELD_NUMBER: builtins.int
|
|
283
|
-
dry_run: builtins.bool
|
|
284
282
|
def __init__(self,
|
|
285
|
-
*,
|
|
286
|
-
dry_run: builtins.bool = ...,
|
|
287
283
|
) -> None: ...
|
|
288
|
-
def ClearField(self, field_name: typing_extensions.Literal["dry_run",b"dry_run"]) -> None: ...
|
|
289
284
|
global___ListNodesRequest = ListNodesRequest
|
|
290
285
|
|
|
291
286
|
class ListNodesResponse(google.protobuf.message.Message):
|
|
@@ -262,6 +262,7 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
|
262
262
|
node_id: self.nodes[node_id].online_until
|
|
263
263
|
for node_id in dst_node_ids
|
|
264
264
|
if node_id in self.nodes
|
|
265
|
+
and self.nodes[node_id].status != NodeStatus.UNREGISTERED
|
|
265
266
|
},
|
|
266
267
|
current_time=current,
|
|
267
268
|
)
|
|
@@ -380,7 +381,10 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
|
380
381
|
)
|
|
381
382
|
|
|
382
383
|
node.status = NodeStatus.UNREGISTERED
|
|
383
|
-
|
|
384
|
+
current = now()
|
|
385
|
+
node.unregistered_at = current.isoformat()
|
|
386
|
+
# Set online_until to current timestamp on deletion, if it is in the future
|
|
387
|
+
node.online_until = min(node.online_until, current.timestamp())
|
|
384
388
|
|
|
385
389
|
def get_nodes(self, run_id: int) -> set[int]:
|
|
386
390
|
"""Return all available nodes.
|
|
@@ -18,11 +18,10 @@
|
|
|
18
18
|
# pylint: disable=too-many-lines
|
|
19
19
|
|
|
20
20
|
import json
|
|
21
|
-
import re
|
|
22
21
|
import secrets
|
|
23
22
|
import sqlite3
|
|
24
23
|
from collections.abc import Sequence
|
|
25
|
-
from logging import
|
|
24
|
+
from logging import ERROR, WARNING
|
|
26
25
|
from typing import Any, Optional, Union, cast
|
|
27
26
|
|
|
28
27
|
from flwr.common import Context, Message, Metadata, log, now
|
|
@@ -52,6 +51,7 @@ from flwr.proto.recorddict_pb2 import RecordDict as ProtoRecordDict
|
|
|
52
51
|
# pylint: enable=E0611
|
|
53
52
|
from flwr.server.utils.validator import validate_message
|
|
54
53
|
from flwr.supercore.constant import NodeStatus
|
|
54
|
+
from flwr.supercore.sqlite_mixin import SqliteMixin
|
|
55
55
|
|
|
56
56
|
from .linkstate import LinkState
|
|
57
57
|
from .utils import (
|
|
@@ -183,95 +183,25 @@ CREATE TABLE IF NOT EXISTS token_store (
|
|
|
183
183
|
);
|
|
184
184
|
"""
|
|
185
185
|
|
|
186
|
-
DictOrTuple = Union[tuple[Any, ...], dict[str, Any]]
|
|
187
186
|
|
|
188
|
-
|
|
189
|
-
class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
187
|
+
class SqliteLinkState(LinkState, SqliteMixin): # pylint: disable=R0904
|
|
190
188
|
"""SQLite-based LinkState implementation."""
|
|
191
189
|
|
|
192
|
-
def __init__(
|
|
193
|
-
self,
|
|
194
|
-
database_path: str,
|
|
195
|
-
) -> None:
|
|
196
|
-
"""Initialize an SqliteLinkState.
|
|
197
|
-
|
|
198
|
-
Parameters
|
|
199
|
-
----------
|
|
200
|
-
database : (path-like object)
|
|
201
|
-
The path to the database file to be opened. Pass ":memory:" to open
|
|
202
|
-
a connection to a database that is in RAM, instead of on disk.
|
|
203
|
-
"""
|
|
204
|
-
self.database_path = database_path
|
|
205
|
-
self.conn: Optional[sqlite3.Connection] = None
|
|
206
|
-
|
|
207
190
|
def initialize(self, log_queries: bool = False) -> list[tuple[str]]:
|
|
208
|
-
"""
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
self.conn.row_factory = dict_factory
|
|
223
|
-
if log_queries:
|
|
224
|
-
self.conn.set_trace_callback(lambda query: log(DEBUG, query))
|
|
225
|
-
cur = self.conn.cursor()
|
|
226
|
-
|
|
227
|
-
# Create each table if not exists queries
|
|
228
|
-
cur.execute(SQL_CREATE_TABLE_RUN)
|
|
229
|
-
cur.execute(SQL_CREATE_TABLE_LOGS)
|
|
230
|
-
cur.execute(SQL_CREATE_TABLE_CONTEXT)
|
|
231
|
-
cur.execute(SQL_CREATE_TABLE_MESSAGE_INS)
|
|
232
|
-
cur.execute(SQL_CREATE_TABLE_MESSAGE_RES)
|
|
233
|
-
cur.execute(SQL_CREATE_TABLE_NODE)
|
|
234
|
-
cur.execute(SQL_CREATE_TABLE_PUBLIC_KEY)
|
|
235
|
-
cur.execute(SQL_CREATE_TABLE_TOKEN_STORE)
|
|
236
|
-
cur.execute(SQL_CREATE_INDEX_ONLINE_UNTIL)
|
|
237
|
-
cur.execute(SQL_CREATE_INDEX_OWNER_AID)
|
|
238
|
-
res = cur.execute("SELECT name FROM sqlite_schema;")
|
|
239
|
-
return res.fetchall()
|
|
240
|
-
|
|
241
|
-
def query(
|
|
242
|
-
self,
|
|
243
|
-
query: str,
|
|
244
|
-
data: Optional[Union[Sequence[DictOrTuple], DictOrTuple]] = None,
|
|
245
|
-
) -> list[dict[str, Any]]:
|
|
246
|
-
"""Execute a SQL query."""
|
|
247
|
-
if self.conn is None:
|
|
248
|
-
raise AttributeError("LinkState is not initialized.")
|
|
249
|
-
|
|
250
|
-
if data is None:
|
|
251
|
-
data = []
|
|
252
|
-
|
|
253
|
-
# Clean up whitespace to make the logs nicer
|
|
254
|
-
query = re.sub(r"\s+", " ", query)
|
|
255
|
-
|
|
256
|
-
try:
|
|
257
|
-
with self.conn:
|
|
258
|
-
if (
|
|
259
|
-
len(data) > 0
|
|
260
|
-
and isinstance(data, (tuple, list))
|
|
261
|
-
and isinstance(data[0], (tuple, dict))
|
|
262
|
-
):
|
|
263
|
-
rows = self.conn.executemany(query, data)
|
|
264
|
-
else:
|
|
265
|
-
rows = self.conn.execute(query, data)
|
|
266
|
-
|
|
267
|
-
# Extract results before committing to support
|
|
268
|
-
# INSERT/UPDATE ... RETURNING
|
|
269
|
-
# style queries
|
|
270
|
-
result = rows.fetchall()
|
|
271
|
-
except KeyError as exc:
|
|
272
|
-
log(ERROR, {"query": query, "data": data, "exception": exc})
|
|
273
|
-
|
|
274
|
-
return result
|
|
191
|
+
"""Connect to the DB, enable FK support, and create tables if needed."""
|
|
192
|
+
return self._ensure_initialized(
|
|
193
|
+
SQL_CREATE_TABLE_RUN,
|
|
194
|
+
SQL_CREATE_TABLE_LOGS,
|
|
195
|
+
SQL_CREATE_TABLE_CONTEXT,
|
|
196
|
+
SQL_CREATE_TABLE_MESSAGE_INS,
|
|
197
|
+
SQL_CREATE_TABLE_MESSAGE_RES,
|
|
198
|
+
SQL_CREATE_TABLE_NODE,
|
|
199
|
+
SQL_CREATE_TABLE_PUBLIC_KEY,
|
|
200
|
+
SQL_CREATE_TABLE_TOKEN_STORE,
|
|
201
|
+
SQL_CREATE_INDEX_ONLINE_UNTIL,
|
|
202
|
+
SQL_CREATE_INDEX_OWNER_AID,
|
|
203
|
+
log_queries=log_queries,
|
|
204
|
+
)
|
|
275
205
|
|
|
276
206
|
def store_message_ins(self, message: Message) -> Optional[str]:
|
|
277
207
|
"""Store one Message."""
|
|
@@ -490,11 +420,12 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
|
490
420
|
sint_node_id = convert_uint64_to_sint64(in_message.metadata.dst_node_id)
|
|
491
421
|
dst_node_ids.add(sint_node_id)
|
|
492
422
|
query = f"""
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
423
|
+
SELECT node_id, online_until
|
|
424
|
+
FROM node
|
|
425
|
+
WHERE node_id IN ({",".join(["?"] * len(dst_node_ids))})
|
|
426
|
+
AND status != ?
|
|
427
|
+
"""
|
|
428
|
+
rows = self.query(query, tuple(dst_node_ids) + (NodeStatus.UNREGISTERED,))
|
|
498
429
|
tmp_ret_dict = check_node_availability_for_in_message(
|
|
499
430
|
inquired_in_message_ids=message_ids,
|
|
500
431
|
found_in_message_dict=found_message_ins_dict,
|
|
@@ -662,13 +593,17 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
|
662
593
|
|
|
663
594
|
query = """
|
|
664
595
|
UPDATE node
|
|
665
|
-
SET status = ?, unregistered_at =
|
|
596
|
+
SET status = ?, unregistered_at = ?,
|
|
597
|
+
online_until = IIF(online_until > ?, ?, online_until)
|
|
666
598
|
WHERE node_id = ? AND status != ? AND owner_aid = ?
|
|
667
599
|
RETURNING node_id
|
|
668
600
|
"""
|
|
601
|
+
current = now()
|
|
669
602
|
params = (
|
|
670
603
|
NodeStatus.UNREGISTERED,
|
|
671
|
-
|
|
604
|
+
current.isoformat(),
|
|
605
|
+
current.timestamp(),
|
|
606
|
+
current.timestamp(),
|
|
672
607
|
sint64_node_id,
|
|
673
608
|
NodeStatus.UNREGISTERED,
|
|
674
609
|
owner_aid,
|
|
@@ -1250,18 +1185,6 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
|
1250
1185
|
return convert_sint64_to_uint64(rows[0]["run_id"])
|
|
1251
1186
|
|
|
1252
1187
|
|
|
1253
|
-
def dict_factory(
|
|
1254
|
-
cursor: sqlite3.Cursor,
|
|
1255
|
-
row: sqlite3.Row,
|
|
1256
|
-
) -> dict[str, Any]:
|
|
1257
|
-
"""Turn SQLite results into dicts.
|
|
1258
|
-
|
|
1259
|
-
Less efficent for retrival of large amounts of data but easier to use.
|
|
1260
|
-
"""
|
|
1261
|
-
fields = [column[0] for column in cursor.description]
|
|
1262
|
-
return dict(zip(fields, row))
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
1188
|
def message_to_dict(message: Message) -> dict[str, Any]:
|
|
1266
1189
|
"""Transform Message to dict."""
|
|
1267
1190
|
result = {
|
|
@@ -0,0 +1,188 @@
|
|
|
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
|
+
"""Mixin providing common SQLite connection and initialization logic."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
import contextlib
|
|
19
|
+
import re
|
|
20
|
+
import sqlite3
|
|
21
|
+
from abc import ABC, abstractmethod
|
|
22
|
+
from collections.abc import Iterator, Sequence
|
|
23
|
+
from logging import DEBUG, ERROR
|
|
24
|
+
from typing import Any, Optional, Union
|
|
25
|
+
|
|
26
|
+
from flwr.common.logger import log
|
|
27
|
+
|
|
28
|
+
DictOrTuple = Union[tuple[Any, ...], dict[str, Any]]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class SqliteMixin(ABC):
|
|
32
|
+
"""Mixin providing common SQLite connection and initialization logic."""
|
|
33
|
+
|
|
34
|
+
def __init__(self, database_path: str) -> None:
|
|
35
|
+
self.database_path = database_path
|
|
36
|
+
self._conn: Optional[sqlite3.Connection] = None
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def conn(self) -> sqlite3.Connection:
|
|
40
|
+
"""Get the SQLite connection."""
|
|
41
|
+
if self._conn is None:
|
|
42
|
+
raise AttributeError("Database not initialized. Call initialize() first.")
|
|
43
|
+
return self._conn
|
|
44
|
+
|
|
45
|
+
@contextlib.contextmanager
|
|
46
|
+
def transaction(self) -> Iterator[None]:
|
|
47
|
+
"""Context manager for a transaction.
|
|
48
|
+
|
|
49
|
+
This allows nesting of transactions by checking if a transaction is
|
|
50
|
+
already in progress.
|
|
51
|
+
|
|
52
|
+
Examples
|
|
53
|
+
--------
|
|
54
|
+
::
|
|
55
|
+
|
|
56
|
+
with self.transaction():
|
|
57
|
+
# Do some DB operations here
|
|
58
|
+
...
|
|
59
|
+
with self.transaction():
|
|
60
|
+
# Do some more DB operations here
|
|
61
|
+
...
|
|
62
|
+
"""
|
|
63
|
+
if self._conn is None:
|
|
64
|
+
raise AttributeError("Database not initialized. Call initialize() first.")
|
|
65
|
+
|
|
66
|
+
# Start a transaction if not already in one
|
|
67
|
+
if not self._conn.in_transaction:
|
|
68
|
+
self._conn.execute("BEGIN")
|
|
69
|
+
try:
|
|
70
|
+
yield
|
|
71
|
+
self._conn.commit()
|
|
72
|
+
except Exception:
|
|
73
|
+
self._conn.rollback()
|
|
74
|
+
raise
|
|
75
|
+
# Do nothing if already in a transaction
|
|
76
|
+
else:
|
|
77
|
+
yield
|
|
78
|
+
|
|
79
|
+
@abstractmethod
|
|
80
|
+
def initialize(self, log_queries: bool = False) -> list[tuple[str]]:
|
|
81
|
+
"""Connect to the DB, enable FK support, and create tables if needed.
|
|
82
|
+
|
|
83
|
+
Parameters
|
|
84
|
+
----------
|
|
85
|
+
log_queries : bool
|
|
86
|
+
Log each query which is executed.
|
|
87
|
+
|
|
88
|
+
Returns
|
|
89
|
+
-------
|
|
90
|
+
list[tuple[str]]
|
|
91
|
+
The list of all tables in the DB.
|
|
92
|
+
|
|
93
|
+
Examples
|
|
94
|
+
--------
|
|
95
|
+
Implement in subclass:
|
|
96
|
+
|
|
97
|
+
.. code:: python
|
|
98
|
+
|
|
99
|
+
def initialize(self, log_queries: bool = False) -> list[tuple[str]]:
|
|
100
|
+
return self._ensure_initialized(
|
|
101
|
+
SQL_CREATE_TABLE_FOO,
|
|
102
|
+
SQL_CREATE_TABLE_BAR,
|
|
103
|
+
log_queries=log_queries
|
|
104
|
+
)
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
def _ensure_initialized(
|
|
108
|
+
self,
|
|
109
|
+
*create_statements: str,
|
|
110
|
+
log_queries: bool = False,
|
|
111
|
+
) -> list[tuple[str]]:
|
|
112
|
+
"""Connect to the DB, enable FK support, and create tables if needed.
|
|
113
|
+
|
|
114
|
+
Subclasses should call this with their own CREATE TABLE/INDEX statements in
|
|
115
|
+
their `.initialize()` methods.
|
|
116
|
+
|
|
117
|
+
Parameters
|
|
118
|
+
----------
|
|
119
|
+
create_statements : str
|
|
120
|
+
SQL statements to create tables and indexes.
|
|
121
|
+
log_queries : bool
|
|
122
|
+
Log each query which is executed.
|
|
123
|
+
|
|
124
|
+
Returns
|
|
125
|
+
-------
|
|
126
|
+
list[tuple[str]]
|
|
127
|
+
The list of all tables in the DB.
|
|
128
|
+
"""
|
|
129
|
+
self._conn = sqlite3.connect(self.database_path)
|
|
130
|
+
self._conn.execute("PRAGMA foreign_keys = ON;")
|
|
131
|
+
self._conn.row_factory = dict_factory
|
|
132
|
+
|
|
133
|
+
if log_queries:
|
|
134
|
+
self._conn.set_trace_callback(lambda q: log(DEBUG, q))
|
|
135
|
+
|
|
136
|
+
# Create tables and indexes
|
|
137
|
+
cur = self._conn.cursor()
|
|
138
|
+
for sql in create_statements:
|
|
139
|
+
cur.execute(sql)
|
|
140
|
+
res = cur.execute("SELECT name FROM sqlite_schema;")
|
|
141
|
+
return res.fetchall()
|
|
142
|
+
|
|
143
|
+
def query(
|
|
144
|
+
self,
|
|
145
|
+
query: str,
|
|
146
|
+
data: Optional[Union[Sequence[DictOrTuple], DictOrTuple]] = None,
|
|
147
|
+
) -> list[dict[str, Any]]:
|
|
148
|
+
"""Execute a SQL query and return the results as list of dicts."""
|
|
149
|
+
if self._conn is None:
|
|
150
|
+
raise AttributeError("LinkState is not initialized.")
|
|
151
|
+
|
|
152
|
+
if data is None:
|
|
153
|
+
data = []
|
|
154
|
+
|
|
155
|
+
# Clean up whitespace to make the logs nicer
|
|
156
|
+
query = re.sub(r"\s+", " ", query)
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
with self.transaction():
|
|
160
|
+
if (
|
|
161
|
+
len(data) > 0
|
|
162
|
+
and isinstance(data, (tuple, list))
|
|
163
|
+
and isinstance(data[0], (tuple, dict))
|
|
164
|
+
):
|
|
165
|
+
rows = self._conn.executemany(query, data)
|
|
166
|
+
else:
|
|
167
|
+
rows = self._conn.execute(query, data)
|
|
168
|
+
|
|
169
|
+
# Extract results before committing to support
|
|
170
|
+
# INSERT/UPDATE ... RETURNING
|
|
171
|
+
# style queries
|
|
172
|
+
result = rows.fetchall()
|
|
173
|
+
except KeyError as exc:
|
|
174
|
+
log(ERROR, {"query": query, "data": data, "exception": exc})
|
|
175
|
+
|
|
176
|
+
return result
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def dict_factory(
|
|
180
|
+
cursor: sqlite3.Cursor,
|
|
181
|
+
row: sqlite3.Row,
|
|
182
|
+
) -> dict[str, Any]:
|
|
183
|
+
"""Turn SQLite results into dicts.
|
|
184
|
+
|
|
185
|
+
Less efficent for retrival of large amounts of data but easier to use.
|
|
186
|
+
"""
|
|
187
|
+
fields = [column[0] for column in cursor.description]
|
|
188
|
+
return dict(zip(fields, row))
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
import hashlib
|
|
19
19
|
import time
|
|
20
20
|
from collections.abc import Generator, Sequence
|
|
21
|
-
from datetime import timedelta
|
|
22
21
|
from logging import ERROR, INFO
|
|
23
22
|
from typing import Any, Optional, cast
|
|
24
23
|
|
|
@@ -72,7 +71,6 @@ from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
|
|
72
71
|
)
|
|
73
72
|
from flwr.proto.node_pb2 import NodeInfo # pylint: disable=E0611
|
|
74
73
|
from flwr.server.superlink.linkstate import LinkState, LinkStateFactory
|
|
75
|
-
from flwr.supercore.constant import NodeStatus
|
|
76
74
|
from flwr.supercore.ffs import FfsFactory
|
|
77
75
|
from flwr.supercore.object_store import ObjectStore, ObjectStoreFactory
|
|
78
76
|
from flwr.supercore.primitives.asymmetric import bytes_to_public_key, uses_nist_ec_curve
|
|
@@ -466,79 +464,17 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
466
464
|
raise grpc.RpcError() # This line is unreachable
|
|
467
465
|
|
|
468
466
|
nodes_info: Sequence[NodeInfo] = []
|
|
469
|
-
#
|
|
470
|
-
|
|
471
|
-
nodes_info = _create_list_nodeif_for_dry_run()
|
|
472
|
-
|
|
473
|
-
else:
|
|
474
|
-
# Init link state
|
|
475
|
-
state = self.linkstate_factory.state()
|
|
467
|
+
# Init link state
|
|
468
|
+
state = self.linkstate_factory.state()
|
|
476
469
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
470
|
+
flwr_aid = shared_account_info.get().flwr_aid
|
|
471
|
+
flwr_aid = _check_flwr_aid_exists(flwr_aid, context)
|
|
472
|
+
# Retrieve all nodes for the account
|
|
473
|
+
nodes_info = state.get_node_info(owner_aids=[flwr_aid])
|
|
481
474
|
|
|
482
475
|
return ListNodesResponse(nodes_info=nodes_info, now=now().isoformat())
|
|
483
476
|
|
|
484
477
|
|
|
485
|
-
def _create_list_nodeif_for_dry_run() -> Sequence[NodeInfo]:
|
|
486
|
-
"""Create a list of NodeInfo for dry run testing."""
|
|
487
|
-
nodes_info: list[NodeInfo] = []
|
|
488
|
-
# A node registered (but not connected)
|
|
489
|
-
nodes_info.append(
|
|
490
|
-
NodeInfo(
|
|
491
|
-
node_id=15390646978706312628,
|
|
492
|
-
owner_aid="owner_aid_1",
|
|
493
|
-
status=NodeStatus.REGISTERED,
|
|
494
|
-
registered_at=(now()).isoformat(),
|
|
495
|
-
last_activated_at="",
|
|
496
|
-
last_deactivated_at="",
|
|
497
|
-
unregistered_at="",
|
|
498
|
-
)
|
|
499
|
-
)
|
|
500
|
-
|
|
501
|
-
# A node registered and connected
|
|
502
|
-
nodes_info.append(
|
|
503
|
-
NodeInfo(
|
|
504
|
-
node_id=2941141058168602545,
|
|
505
|
-
owner_aid="owner_aid_2",
|
|
506
|
-
status=NodeStatus.ONLINE,
|
|
507
|
-
registered_at=(now()).isoformat(),
|
|
508
|
-
last_activated_at=(now() + timedelta(hours=0.5)).isoformat(),
|
|
509
|
-
last_deactivated_at="",
|
|
510
|
-
unregistered_at="",
|
|
511
|
-
)
|
|
512
|
-
)
|
|
513
|
-
|
|
514
|
-
# A node registered and unregistered (never connected)
|
|
515
|
-
nodes_info.append(
|
|
516
|
-
NodeInfo(
|
|
517
|
-
node_id=906971720890549292,
|
|
518
|
-
owner_aid="owner_aid_3",
|
|
519
|
-
status=NodeStatus.UNREGISTERED,
|
|
520
|
-
registered_at=(now()).isoformat(),
|
|
521
|
-
last_activated_at="",
|
|
522
|
-
last_deactivated_at="",
|
|
523
|
-
unregistered_at=(now() + timedelta(hours=1)).isoformat(),
|
|
524
|
-
)
|
|
525
|
-
)
|
|
526
|
-
|
|
527
|
-
# A node registered, deactivate and then unregistered
|
|
528
|
-
nodes_info.append(
|
|
529
|
-
NodeInfo(
|
|
530
|
-
node_id=1781174086018058152,
|
|
531
|
-
owner_aid="owner_aid_4",
|
|
532
|
-
status=NodeStatus.OFFLINE,
|
|
533
|
-
registered_at=(now()).isoformat(),
|
|
534
|
-
last_activated_at=(now() + timedelta(hours=0.5)).isoformat(),
|
|
535
|
-
last_deactivated_at=(now() + timedelta(hours=1)).isoformat(),
|
|
536
|
-
unregistered_at=(now() + timedelta(hours=1.5)).isoformat(),
|
|
537
|
-
)
|
|
538
|
-
)
|
|
539
|
-
return nodes_info
|
|
540
|
-
|
|
541
|
-
|
|
542
478
|
def _create_list_runs_response(
|
|
543
479
|
run_ids: set[int], state: LinkState, store: ObjectStore
|
|
544
480
|
) -> ListRunsResponse:
|
{flwr_nightly-1.23.0.dev20251020.dist-info → flwr_nightly-1.23.0.dev20251021.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: flwr-nightly
|
|
3
|
-
Version: 1.23.0.
|
|
3
|
+
Version: 1.23.0.dev20251021
|
|
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
|
{flwr_nightly-1.23.0.dev20251020.dist-info → flwr_nightly-1.23.0.dev20251021.dist-info}/RECORD
RENAMED
|
@@ -18,7 +18,7 @@ flwr/cli/install.py,sha256=Jr883qR7qssVpUr3hEOEcLK-dfW67Rsve3lZchjA9RU,8180
|
|
|
18
18
|
flwr/cli/log.py,sha256=3qVYY30QqchfnKRTeeRw2ojBUj_nwvfnZkdBr4lmjpc,6541
|
|
19
19
|
flwr/cli/login/__init__.py,sha256=B1SXKU3HCQhWfFDMJhlC7FOl8UsvH4mxysxeBnrfyUE,800
|
|
20
20
|
flwr/cli/login/login.py,sha256=XeA6K9rZn3CBvtiFP3XWTAk7s8766BCqHvCsO_tJEuY,4558
|
|
21
|
-
flwr/cli/ls.py,sha256=
|
|
21
|
+
flwr/cli/ls.py,sha256=MUlWSjO0yhVTupN5Fr0zvdvRqfRBGWliuoyWA0D-Pzg,11107
|
|
22
22
|
flwr/cli/new/__init__.py,sha256=QA1E2QtzPvFCjLTUHnFnJbufuFiGyT_0Y53Wpbvg1F0,790
|
|
23
23
|
flwr/cli/new/new.py,sha256=nIuUrQSGDjI4kqnymlq-rOT0RU3AHwZrat3abqHhCwM,10598
|
|
24
24
|
flwr/cli/new/templates/__init__.py,sha256=FpjWCfIySU2DB4kh0HOXLAjlZNNFDTVU4w3HoE2TzcI,725
|
|
@@ -87,7 +87,7 @@ flwr/cli/run/__init__.py,sha256=RPyB7KbYTFl6YRiilCch6oezxrLQrl1kijV7BMGkLbA,790
|
|
|
87
87
|
flwr/cli/run/run.py,sha256=ED1mDmO1PnSAgtVOrCeWwzwPm6t3aFYSs3Rh36BJzqk,8161
|
|
88
88
|
flwr/cli/stop.py,sha256=W7ynTYLm0-_1nC5Il4IaZTji6A3GCCm_0R-HQUudAsI,4988
|
|
89
89
|
flwr/cli/supernode/__init__.py,sha256=DBkjoPo2hS2Y-ghJxwLbrAbCQFBgD82_Itl2_892UBo,917
|
|
90
|
-
flwr/cli/supernode/ls.py,sha256=
|
|
90
|
+
flwr/cli/supernode/ls.py,sha256=OVEs9zPSomxxiwCg1Jz8AN5MkZJDf3xAE-lxCG22zyE,8640
|
|
91
91
|
flwr/cli/supernode/register.py,sha256=8xLsaJWHGrFtqxPTEMkvQdlgWd5hqQZu94b_jpCN_A0,6419
|
|
92
92
|
flwr/cli/supernode/unregister.py,sha256=qbrAMbzjLhOJNQ_ndSqBLQp1up9LrxBN1ngwg97Vbj4,4578
|
|
93
93
|
flwr/cli/utils.py,sha256=66RqZdF0DKsqmR4ZqE9zZ1bX6opdxD2U50GV41WQDoY,14864
|
|
@@ -126,7 +126,7 @@ flwr/common/__init__.py,sha256=5GCLVk399Az_rTJHNticRlL0Sl_oPw_j5_LuFKfX7-M,4171
|
|
|
126
126
|
flwr/common/address.py,sha256=9JucdTwlc-jpeJkRKeUboZoacUtErwSVtnDR9kAtLqE,4119
|
|
127
127
|
flwr/common/args.py,sha256=Nq2u4yePbkSY0CWFamn0hZY6Rms8G1xYDeDGIcLIITE,5849
|
|
128
128
|
flwr/common/config.py,sha256=glcZDjco-amw1YfQcYTFJ4S1pt9APoexT-mf1QscuHs,13960
|
|
129
|
-
flwr/common/constant.py,sha256=
|
|
129
|
+
flwr/common/constant.py,sha256=y9U6LdgMzNYe-LGTjooGn0QJj5MVTlWjbriE1l5rkag,9898
|
|
130
130
|
flwr/common/context.py,sha256=Be8obQR_OvEDy1OmshuUKxGRQ7Qx89mf5F4xlhkR10s,2407
|
|
131
131
|
flwr/common/date.py,sha256=1ZT2cRSpC2DJqprOVTLXYCR_O2_OZR0zXO_brJ3LqWc,1554
|
|
132
132
|
flwr/common/differential_privacy.py,sha256=FdlpdpPl_H_2HJa8CQM1iCUGBBQ5Dc8CzxmHERM-EoE,6148
|
|
@@ -143,14 +143,14 @@ flwr/common/grpc.py,sha256=nHnFC7E84pZVTvd6BhcSYWnGd0jf8t5UmGea04qvilM,9806
|
|
|
143
143
|
flwr/common/heartbeat.py,sha256=SyEpNDnmJ0lni0cWO67rcoJVKasCLmkNHm3dKLeNrLU,5749
|
|
144
144
|
flwr/common/inflatable.py,sha256=GDL9oBKs16_yyVdlH6kBf493O5xll_h9V7XB5Mpx1Hc,9524
|
|
145
145
|
flwr/common/inflatable_protobuf_utils.py,sha256=JtRqp-fV47goDM2y8JRQ7AmwwjeGaWexwoMWLcxX5gE,5056
|
|
146
|
-
flwr/common/inflatable_utils.py,sha256=
|
|
146
|
+
flwr/common/inflatable_utils.py,sha256=1ibsNLvjbXtRSWu9pjBbL__F6N3vyUM4dgJKperlwYs,19116
|
|
147
147
|
flwr/common/logger.py,sha256=kP7Cbs2WuYFK83Wsx5o9qc9mj8jDSyUK3BfRjvxhSTQ,13049
|
|
148
148
|
flwr/common/message.py,sha256=xAL7iZN5-n-xPQpgoSFvxNrzs8fmiiPfoU0DjNQEhRw,19953
|
|
149
149
|
flwr/common/object_ref.py,sha256=p3SfTeqo3Aj16SkB-vsnNn01zswOPdGNBitcbRnqmUk,9134
|
|
150
150
|
flwr/common/parameter.py,sha256=UVw6sOgehEFhFs4uUCMl2kfVq1PD6ncmWgPLMsZPKPE,2095
|
|
151
151
|
flwr/common/pyproject.py,sha256=2SU6yJW7059SbMXgzjOdK1GZRWO6AixDH7BmdxbMvHI,1386
|
|
152
152
|
flwr/common/record/__init__.py,sha256=cNGccdDoxttqgnUgyKRIqLWULjW-NaSmOufVxtXq-sw,1197
|
|
153
|
-
flwr/common/record/array.py,sha256=
|
|
153
|
+
flwr/common/record/array.py,sha256=wI-2UmUXelxWAQJLV0n5I_G1453wpCebi5fn95Kn3FI,15077
|
|
154
154
|
flwr/common/record/arraychunk.py,sha256=gU5h6uP7H_06Z14wNixtwH2FvepQkl9FpALXg9qcbhM,1936
|
|
155
155
|
flwr/common/record/arrayrecord.py,sha256=HpNM9NmOoGhjuo5CfxrrjL3uJevpXZLkyq_wPhJW1No,18278
|
|
156
156
|
flwr/common/record/configrecord.py,sha256=G7U0q39kB0Kyi0zMxFmPxcVemL9NgwVS1qjvI4BRQuU,9763
|
|
@@ -191,8 +191,8 @@ flwr/proto/clientappio_pb2.py,sha256=vJjzwWydhg7LruK8cvRAeVQeHPsJztgdIW9nyiPBZF0
|
|
|
191
191
|
flwr/proto/clientappio_pb2.pyi,sha256=XbFvpZvvrS7QcH5AFXfpRGl4hQvhd3QdKO6x0oTlCCU,165
|
|
192
192
|
flwr/proto/clientappio_pb2_grpc.py,sha256=iobNROP0qvn5zddx7k-uIi_dJWP3T_BRp_kbKq086i8,17550
|
|
193
193
|
flwr/proto/clientappio_pb2_grpc.pyi,sha256=Ytf1O1ktKB0Vsuc3AWLIErGjIJYokzKYzi2uA7mdMeg,4785
|
|
194
|
-
flwr/proto/control_pb2.py,sha256=
|
|
195
|
-
flwr/proto/control_pb2.pyi,sha256=
|
|
194
|
+
flwr/proto/control_pb2.py,sha256=_FqVaMs-LP6eYJ3eDARnbfsq_q66sewTclDskBMOb3c,7707
|
|
195
|
+
flwr/proto/control_pb2.pyi,sha256=0vlxyu7uYVtIhqFcjHCDeWPdtuQCCzBRyAEU5VYeaxI,13368
|
|
196
196
|
flwr/proto/control_pb2_grpc.py,sha256=wLjMi8GNQ5VR7IbNdDbHC0A1QMzA_0CegBzaOO1dlo0,17152
|
|
197
197
|
flwr/proto/control_pb2_grpc.pyi,sha256=L1HGnIXWu0Q7WQ1ONWffaQ69jHtWQ6cyEUcxINL8_Uc,4801
|
|
198
198
|
flwr/proto/error_pb2.py,sha256=PQVWrfjVPo88ql_KgV9nCxyQNCcV9PVfmcw7sOzTMro,1084
|
|
@@ -317,10 +317,10 @@ flwr/server/superlink/fleet/vce/backend/backend.py,sha256=cSrHZ5SjCCvy4vI0pgsyjt
|
|
|
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=EU0DLt4njtKelOpOWfQ7zWW45bSVC6K7pPYfHSyOJwM,13332
|
|
319
319
|
flwr/server/superlink/linkstate/__init__.py,sha256=OtsgvDTnZLU3k0sUbkHbqoVwW6ql2FDmb6uT6DbNkZo,1064
|
|
320
|
-
flwr/server/superlink/linkstate/in_memory_linkstate.py,sha256=
|
|
320
|
+
flwr/server/superlink/linkstate/in_memory_linkstate.py,sha256=9JBJSWh7TAEf7WFcpdn03rL0tLrroVUPZcyNJd8Qz88,29997
|
|
321
321
|
flwr/server/superlink/linkstate/linkstate.py,sha256=JKgVEPnH2T-nixW3LHp8jR3g4ITAZYNwEoDIWZaUYmQ,14701
|
|
322
322
|
flwr/server/superlink/linkstate/linkstate_factory.py,sha256=8RlosqSpKOoD_vhUUQPY0jtE3A84GeF96Z7sWNkRRcA,2069
|
|
323
|
-
flwr/server/superlink/linkstate/sqlite_linkstate.py,sha256=
|
|
323
|
+
flwr/server/superlink/linkstate/sqlite_linkstate.py,sha256=zpLonYzlE1aOeCRGTwHJ2UBTtz-JZ-6-YgCPKjqUUjM,45585
|
|
324
324
|
flwr/server/superlink/linkstate/utils.py,sha256=IeLh7iGRCHU5MEWOl7iriaSE4L__8GWOa2OleXadK5M,15444
|
|
325
325
|
flwr/server/superlink/serverappio/__init__.py,sha256=Fy4zJuoccZe5mZSEIpOmQvU6YeXFBa1M4eZuXXmJcn8,717
|
|
326
326
|
flwr/server/superlink/serverappio/serverappio_grpc.py,sha256=-I7kBbr4w4ZVYwBZoAIle-xHKthFnZrsVfxa6WR8uxA,2310
|
|
@@ -394,6 +394,7 @@ flwr/supercore/object_store/object_store_factory.py,sha256=QVwE2ywi7vsj2iKfvWWnN
|
|
|
394
394
|
flwr/supercore/object_store/utils.py,sha256=DcPbrb9PenloAPoQRiKiXX9DrDfpXcSyY0cZpgn4PeQ,1680
|
|
395
395
|
flwr/supercore/primitives/__init__.py,sha256=Tx8GOjnmMo8Y74RsDGrMpfr-E0Nl8dcUDF784_ge6F8,745
|
|
396
396
|
flwr/supercore/primitives/asymmetric.py,sha256=wpO0o0G_vStRknFitw2SqyIBSzaBfuXfMc44u-UcxTs,3774
|
|
397
|
+
flwr/supercore/sqlite_mixin.py,sha256=uQ9LK_D2o8dYVEXzAtoJIeQya2zaUJF1ifSN06dwb1E,5934
|
|
397
398
|
flwr/supercore/superexec/__init__.py,sha256=XKX208hZ6a9gZ4KT9kMqfpCtp_8VGxekzKFfHSu2esQ,707
|
|
398
399
|
flwr/supercore/superexec/plugin/__init__.py,sha256=GNwq8uNdE8RB7ywEFRAvKjLFzgS3YXgz39-HBGsemWw,1035
|
|
399
400
|
flwr/supercore/superexec/plugin/base_exec_plugin.py,sha256=fL-Ufc9Dp56OhWOzNSJUc7HumbkuSDYqZKwde2opG4g,2074
|
|
@@ -415,7 +416,7 @@ flwr/superlink/servicer/control/control_account_auth_interceptor.py,sha256=Tbi4W
|
|
|
415
416
|
flwr/superlink/servicer/control/control_event_log_interceptor.py,sha256=5uBl6VcJlUOgCF0d4kmsmJc1Rs1qxyouaZv0-uH2axs,5969
|
|
416
417
|
flwr/superlink/servicer/control/control_grpc.py,sha256=MRCaX4I2a5ogjKmhtFs6Mj-VdWemxL2h3gU9QbQmvCA,4183
|
|
417
418
|
flwr/superlink/servicer/control/control_license_interceptor.py,sha256=T3AzmRt-PPwyTq3hrdpmZHQd5_CpPOk7TtnFZrB-JRY,3349
|
|
418
|
-
flwr/superlink/servicer/control/control_servicer.py,sha256=
|
|
419
|
+
flwr/superlink/servicer/control/control_servicer.py,sha256=52SHq8yPCDjO92jFm9bg_QMjZJ_-kDC0brn7uJAOwnw,19357
|
|
419
420
|
flwr/supernode/__init__.py,sha256=KgeCaVvXWrU3rptNR1y0oBp4YtXbAcrnCcJAiOoWkI4,707
|
|
420
421
|
flwr/supernode/cli/__init__.py,sha256=JuEMr0-s9zv-PEWKuLB9tj1ocNfroSyNJ-oyv7ati9A,887
|
|
421
422
|
flwr/supernode/cli/flower_supernode.py,sha256=bmPpg88Zq7NYMDzyyDwBzeZ6_1f26fD_iDdGw1o_4QQ,8334
|
|
@@ -430,7 +431,7 @@ flwr/supernode/servicer/__init__.py,sha256=lucTzre5WPK7G1YLCfaqg3rbFWdNSb7ZTt-ca
|
|
|
430
431
|
flwr/supernode/servicer/clientappio/__init__.py,sha256=7Oy62Y_oijqF7Dxi6tpcUQyOpLc_QpIRZ83NvwmB0Yg,813
|
|
431
432
|
flwr/supernode/servicer/clientappio/clientappio_servicer.py,sha256=nIHRu38EWK-rpNOkcgBRAAKwYQQWFeCwu0lkO7OPZGQ,10239
|
|
432
433
|
flwr/supernode/start_client_internal.py,sha256=wKqh9-_rQYi7JFKpYBLRiUeq9YJUSyUd9b-SnVnuvwI,21567
|
|
433
|
-
flwr_nightly-1.23.0.
|
|
434
|
-
flwr_nightly-1.23.0.
|
|
435
|
-
flwr_nightly-1.23.0.
|
|
436
|
-
flwr_nightly-1.23.0.
|
|
434
|
+
flwr_nightly-1.23.0.dev20251021.dist-info/METADATA,sha256=RKCjEZQAhaeqPaGtLHA3n0zhcMH1GBgkT8hXrxqwhSg,14559
|
|
435
|
+
flwr_nightly-1.23.0.dev20251021.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
436
|
+
flwr_nightly-1.23.0.dev20251021.dist-info/entry_points.txt,sha256=hxHD2ixb_vJFDOlZV-zB4Ao32_BQlL34ftsDh1GXv14,420
|
|
437
|
+
flwr_nightly-1.23.0.dev20251021.dist-info/RECORD,,
|
{flwr_nightly-1.23.0.dev20251020.dist-info → flwr_nightly-1.23.0.dev20251021.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|