flwr-nightly 1.26.0.dev20260122__py3-none-any.whl → 1.26.0.dev20260123__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.
- flwr/cli/config_utils.py +0 -132
- flwr/cli/utils.py +2 -55
- flwr/server/superlink/linkstate/__init__.py +0 -2
- flwr/supercore/object_store/object_store_factory.py +4 -4
- flwr/supercore/object_store/sql_object_store.py +171 -6
- {flwr_nightly-1.26.0.dev20260122.dist-info → flwr_nightly-1.26.0.dev20260123.dist-info}/METADATA +2 -2
- {flwr_nightly-1.26.0.dev20260122.dist-info → flwr_nightly-1.26.0.dev20260123.dist-info}/RECORD +9 -13
- flwr/server/superlink/linkstate/sqlite_linkstate.py +0 -1302
- flwr/supercore/corestate/sqlite_corestate.py +0 -157
- flwr/supercore/object_store/sqlite_object_store.py +0 -253
- flwr/supercore/sqlite_mixin.py +0 -156
- {flwr_nightly-1.26.0.dev20260122.dist-info → flwr_nightly-1.26.0.dev20260123.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.26.0.dev20260122.dist-info → flwr_nightly-1.26.0.dev20260123.dist-info}/entry_points.txt +0 -0
flwr/cli/config_utils.py
CHANGED
|
@@ -114,57 +114,6 @@ def load(toml_path: Path) -> dict[str, Any] | None:
|
|
|
114
114
|
return None
|
|
115
115
|
|
|
116
116
|
|
|
117
|
-
def process_loaded_project_config(
|
|
118
|
-
config: dict[str, Any] | None, errors: list[str], warnings: list[str]
|
|
119
|
-
) -> dict[str, Any]:
|
|
120
|
-
"""Process and return the loaded project configuration.
|
|
121
|
-
|
|
122
|
-
This function handles errors and warnings from the `load_and_validate` function,
|
|
123
|
-
exits on critical issues, and returns the validated configuration.
|
|
124
|
-
|
|
125
|
-
Parameters
|
|
126
|
-
----------
|
|
127
|
-
config : dict[str, Any] | None
|
|
128
|
-
The loaded configuration dictionary, or None if loading failed.
|
|
129
|
-
errors : list[str]
|
|
130
|
-
List of error messages from validation.
|
|
131
|
-
warnings : list[str]
|
|
132
|
-
List of warning messages from validation.
|
|
133
|
-
|
|
134
|
-
Returns
|
|
135
|
-
-------
|
|
136
|
-
dict[str, Any]
|
|
137
|
-
The validated configuration dictionary.
|
|
138
|
-
|
|
139
|
-
Raises
|
|
140
|
-
------
|
|
141
|
-
typer.Exit
|
|
142
|
-
If config is None or contains critical errors.
|
|
143
|
-
"""
|
|
144
|
-
if config is None:
|
|
145
|
-
typer.secho(
|
|
146
|
-
"Project configuration could not be loaded.\n"
|
|
147
|
-
"pyproject.toml is invalid:\n"
|
|
148
|
-
+ "\n".join([f"- {line}" for line in errors]),
|
|
149
|
-
fg=typer.colors.RED,
|
|
150
|
-
bold=True,
|
|
151
|
-
err=True,
|
|
152
|
-
)
|
|
153
|
-
raise typer.Exit(code=1)
|
|
154
|
-
|
|
155
|
-
if warnings:
|
|
156
|
-
typer.secho(
|
|
157
|
-
"Project configuration is missing the following "
|
|
158
|
-
"recommended properties:\n" + "\n".join([f"- {line}" for line in warnings]),
|
|
159
|
-
fg=typer.colors.RED,
|
|
160
|
-
bold=True,
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
typer.secho("Success", fg=typer.colors.GREEN)
|
|
164
|
-
|
|
165
|
-
return config
|
|
166
|
-
|
|
167
|
-
|
|
168
117
|
def validate_federation_in_project_config(
|
|
169
118
|
federation: str | None,
|
|
170
119
|
config: dict[str, Any],
|
|
@@ -229,61 +178,6 @@ def validate_federation_in_project_config(
|
|
|
229
178
|
return federation, federation_config
|
|
230
179
|
|
|
231
180
|
|
|
232
|
-
def validate_certificate_in_federation_config(
|
|
233
|
-
app: Path, federation_config: dict[str, Any]
|
|
234
|
-
) -> tuple[bool, bytes | None]:
|
|
235
|
-
"""Validate the certificates in the Flower project configuration.
|
|
236
|
-
|
|
237
|
-
Accepted configurations:
|
|
238
|
-
1. TLS enabled and gRPC will load(*) the trusted certificate bundle:
|
|
239
|
-
- Only `address` is provided. `root-certificates` and `insecure` not set.
|
|
240
|
-
- `address` is provided and `insecure` set to `false`. `root-certificates` not
|
|
241
|
-
set.
|
|
242
|
-
(*)gRPC uses a multi-step fallback mechanism to load the trusted certificate
|
|
243
|
-
bundle in the following sequence:
|
|
244
|
-
a. A configured file path (if set via configuration or environment),
|
|
245
|
-
b. An override callback (if registered via
|
|
246
|
-
`grpc_set_ssl_roots_override_callback`),
|
|
247
|
-
c. The OS trust store (if available),
|
|
248
|
-
d. A bundled default certificate file.
|
|
249
|
-
2. TLS enabled with self-signed certificates:
|
|
250
|
-
- `address` and `root-certificates` are provided. `insecure` not set.
|
|
251
|
-
- `address` and `root-certificates` are provided. `insecure` set to `false`.
|
|
252
|
-
3. TLS disabled. This is not recommended and should only be used for prototyping:
|
|
253
|
-
- `address` is provided and `insecure = true`. If `root-certificates` is
|
|
254
|
-
set, exit with an error.
|
|
255
|
-
"""
|
|
256
|
-
insecure = get_insecure_flag(federation_config)
|
|
257
|
-
|
|
258
|
-
# Process root certificates
|
|
259
|
-
if root_certificates := federation_config.get("root-certificates"):
|
|
260
|
-
if insecure:
|
|
261
|
-
typer.secho(
|
|
262
|
-
"❌ `root-certificates` were provided but the `insecure` parameter "
|
|
263
|
-
"is set to `True`.",
|
|
264
|
-
fg=typer.colors.RED,
|
|
265
|
-
bold=True,
|
|
266
|
-
err=True,
|
|
267
|
-
)
|
|
268
|
-
raise typer.Exit(code=1)
|
|
269
|
-
|
|
270
|
-
# TLS is enabled with self-signed certificates: attempt to read the file
|
|
271
|
-
try:
|
|
272
|
-
root_certificates_bytes = (app / root_certificates).read_bytes()
|
|
273
|
-
except Exception as e:
|
|
274
|
-
typer.secho(
|
|
275
|
-
f"❌ Failed to read certificate file `{root_certificates}`: {e}",
|
|
276
|
-
fg=typer.colors.RED,
|
|
277
|
-
bold=True,
|
|
278
|
-
err=True,
|
|
279
|
-
)
|
|
280
|
-
raise typer.Exit(code=1) from e
|
|
281
|
-
else:
|
|
282
|
-
root_certificates_bytes = None
|
|
283
|
-
|
|
284
|
-
return insecure, root_certificates_bytes
|
|
285
|
-
|
|
286
|
-
|
|
287
181
|
def load_certificate_in_connection(
|
|
288
182
|
connection: SuperLinkConnection,
|
|
289
183
|
) -> bytes | None:
|
|
@@ -336,32 +230,6 @@ def load_certificate_in_connection(
|
|
|
336
230
|
return root_certificates_bytes
|
|
337
231
|
|
|
338
232
|
|
|
339
|
-
def exit_if_no_address(federation_config: dict[str, Any], cmd: str) -> None:
|
|
340
|
-
"""Exit if the provided federation_config has no "address" key.
|
|
341
|
-
|
|
342
|
-
Parameters
|
|
343
|
-
----------
|
|
344
|
-
federation_config : dict[str, Any]
|
|
345
|
-
The federation configuration dictionary to check.
|
|
346
|
-
cmd : str
|
|
347
|
-
The command name to display in the error message.
|
|
348
|
-
|
|
349
|
-
Raises
|
|
350
|
-
------
|
|
351
|
-
typer.Exit
|
|
352
|
-
If 'address' key is not present in federation_config.
|
|
353
|
-
"""
|
|
354
|
-
if "address" not in federation_config:
|
|
355
|
-
typer.secho(
|
|
356
|
-
f"❌ `flwr {cmd}` currently works with a SuperLink. Ensure that the "
|
|
357
|
-
"correct SuperLink (Control API) address is provided in `pyproject.toml`.",
|
|
358
|
-
fg=typer.colors.RED,
|
|
359
|
-
bold=True,
|
|
360
|
-
err=True,
|
|
361
|
-
)
|
|
362
|
-
raise typer.Exit(code=1)
|
|
363
|
-
|
|
364
|
-
|
|
365
233
|
def get_insecure_flag(federation_config: dict[str, Any]) -> bool:
|
|
366
234
|
"""Extract and validate the `insecure` flag from the federation configuration.
|
|
367
235
|
|
flwr/cli/utils.py
CHANGED
|
@@ -20,7 +20,7 @@ import json
|
|
|
20
20
|
from collections.abc import Callable, Iterable, Iterator
|
|
21
21
|
from contextlib import contextmanager
|
|
22
22
|
from pathlib import Path
|
|
23
|
-
from typing import
|
|
23
|
+
from typing import cast
|
|
24
24
|
|
|
25
25
|
import click
|
|
26
26
|
import grpc
|
|
@@ -50,10 +50,7 @@ from flwr.supercore.credential_store import get_credential_store
|
|
|
50
50
|
|
|
51
51
|
from .auth_plugin import CliAuthPlugin, get_cli_plugin_class
|
|
52
52
|
from .cli_account_auth_interceptor import CliAccountAuthInterceptor
|
|
53
|
-
from .config_utils import
|
|
54
|
-
load_certificate_in_connection,
|
|
55
|
-
validate_certificate_in_federation_config,
|
|
56
|
-
)
|
|
53
|
+
from .config_utils import load_certificate_in_connection
|
|
57
54
|
from .constant import AUTHN_TYPE_STORE_KEY
|
|
58
55
|
|
|
59
56
|
|
|
@@ -197,18 +194,6 @@ def get_authn_type(host: str) -> str:
|
|
|
197
194
|
return authn_type.decode("utf-8")
|
|
198
195
|
|
|
199
196
|
|
|
200
|
-
def load_cli_auth_plugin(
|
|
201
|
-
root_dir: Path,
|
|
202
|
-
federation: str,
|
|
203
|
-
federation_config: dict[str, Any],
|
|
204
|
-
authn_type: str | None = None,
|
|
205
|
-
) -> CliAuthPlugin:
|
|
206
|
-
"""."""
|
|
207
|
-
raise RuntimeError(
|
|
208
|
-
"Deprecated function. Use `load_cli_auth_plugin_from_connection`"
|
|
209
|
-
)
|
|
210
|
-
|
|
211
|
-
|
|
212
197
|
def load_cli_auth_plugin_from_connection(
|
|
213
198
|
host: str, authn_type: str | None = None
|
|
214
199
|
) -> CliAuthPlugin:
|
|
@@ -246,44 +231,6 @@ def load_cli_auth_plugin_from_connection(
|
|
|
246
231
|
raise typer.Exit(code=1) from None
|
|
247
232
|
|
|
248
233
|
|
|
249
|
-
def init_channel(
|
|
250
|
-
app: Path, federation_config: dict[str, Any], auth_plugin: CliAuthPlugin
|
|
251
|
-
) -> grpc.Channel:
|
|
252
|
-
"""Initialize gRPC channel to the Control API.
|
|
253
|
-
|
|
254
|
-
Parameters
|
|
255
|
-
----------
|
|
256
|
-
app : Path
|
|
257
|
-
Path to the Flower app directory.
|
|
258
|
-
federation_config : dict[str, Any]
|
|
259
|
-
Federation configuration dictionary containing address and TLS settings.
|
|
260
|
-
auth_plugin : CliAuthPlugin
|
|
261
|
-
Authentication plugin instance for handling credentials.
|
|
262
|
-
|
|
263
|
-
Returns
|
|
264
|
-
-------
|
|
265
|
-
grpc.Channel
|
|
266
|
-
Configured gRPC channel with authentication interceptors.
|
|
267
|
-
"""
|
|
268
|
-
insecure, root_certificates_bytes = validate_certificate_in_federation_config(
|
|
269
|
-
app, federation_config
|
|
270
|
-
)
|
|
271
|
-
|
|
272
|
-
# Load tokens
|
|
273
|
-
auth_plugin.load_tokens()
|
|
274
|
-
|
|
275
|
-
# Create the gRPC channel
|
|
276
|
-
channel = create_channel(
|
|
277
|
-
server_address=federation_config["address"],
|
|
278
|
-
insecure=insecure,
|
|
279
|
-
root_certificates=root_certificates_bytes,
|
|
280
|
-
max_message_length=GRPC_MAX_MESSAGE_LENGTH,
|
|
281
|
-
interceptors=[CliAccountAuthInterceptor(auth_plugin)],
|
|
282
|
-
)
|
|
283
|
-
channel.subscribe(on_channel_state_change)
|
|
284
|
-
return channel
|
|
285
|
-
|
|
286
|
-
|
|
287
234
|
def require_superlink_address(connection: SuperLinkConnection) -> str:
|
|
288
235
|
"""Return the SuperLink address or exit if it is not configured."""
|
|
289
236
|
if connection.address is None:
|
|
@@ -19,12 +19,10 @@ from .in_memory_linkstate import InMemoryLinkState as InMemoryLinkState
|
|
|
19
19
|
from .linkstate import LinkState as LinkState
|
|
20
20
|
from .linkstate_factory import LinkStateFactory as LinkStateFactory
|
|
21
21
|
from .sql_linkstate import SqlLinkState as SqlLinkState
|
|
22
|
-
from .sqlite_linkstate import SqliteLinkState as SqliteLinkState
|
|
23
22
|
|
|
24
23
|
__all__ = [
|
|
25
24
|
"InMemoryLinkState",
|
|
26
25
|
"LinkState",
|
|
27
26
|
"LinkStateFactory",
|
|
28
27
|
"SqlLinkState",
|
|
29
|
-
"SqliteLinkState",
|
|
30
28
|
]
|
|
@@ -22,7 +22,7 @@ from flwr.supercore.constant import FLWR_IN_MEMORY_DB_NAME
|
|
|
22
22
|
|
|
23
23
|
from .in_memory_object_store import InMemoryObjectStore
|
|
24
24
|
from .object_store import ObjectStore
|
|
25
|
-
from .
|
|
25
|
+
from .sql_object_store import SqlObjectStore
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
class ObjectStoreFactory:
|
|
@@ -56,8 +56,8 @@ class ObjectStoreFactory:
|
|
|
56
56
|
log(DEBUG, "Using InMemoryObjectStore")
|
|
57
57
|
return self.store_instance
|
|
58
58
|
|
|
59
|
-
#
|
|
60
|
-
store =
|
|
59
|
+
# SqlObjectStore
|
|
60
|
+
store = SqlObjectStore(self.database)
|
|
61
61
|
store.initialize()
|
|
62
|
-
log(DEBUG, "Using
|
|
62
|
+
log(DEBUG, "Using SqlObjectStore")
|
|
63
63
|
return store
|
|
@@ -17,11 +17,18 @@
|
|
|
17
17
|
|
|
18
18
|
from sqlalchemy import MetaData
|
|
19
19
|
|
|
20
|
+
from flwr.common.inflatable import (
|
|
21
|
+
get_object_id,
|
|
22
|
+
is_valid_sha256_hash,
|
|
23
|
+
iterate_object_tree,
|
|
24
|
+
)
|
|
25
|
+
from flwr.common.inflatable_utils import validate_object_content
|
|
20
26
|
from flwr.proto.message_pb2 import ObjectTree # pylint: disable=E0611
|
|
21
27
|
from flwr.supercore.sql_mixin import SqlMixin
|
|
22
28
|
from flwr.supercore.state.schema.objectstore_tables import create_objectstore_metadata
|
|
29
|
+
from flwr.supercore.utils import uint64_to_int64
|
|
23
30
|
|
|
24
|
-
from .object_store import ObjectStore
|
|
31
|
+
from .object_store import NoObjectInStoreError, ObjectStore
|
|
25
32
|
|
|
26
33
|
|
|
27
34
|
class SqlObjectStore(ObjectStore, SqlMixin):
|
|
@@ -37,15 +44,119 @@ class SqlObjectStore(ObjectStore, SqlMixin):
|
|
|
37
44
|
|
|
38
45
|
def preregister(self, run_id: int, object_tree: ObjectTree) -> list[str]:
|
|
39
46
|
"""Identify and preregister missing objects in the `ObjectStore`."""
|
|
40
|
-
|
|
47
|
+
new_objects = []
|
|
48
|
+
for tree_node in iterate_object_tree(object_tree):
|
|
49
|
+
obj_id = tree_node.object_id
|
|
50
|
+
if not is_valid_sha256_hash(obj_id):
|
|
51
|
+
raise ValueError(f"Invalid object ID format: {obj_id}")
|
|
52
|
+
|
|
53
|
+
child_ids = [child.object_id for child in tree_node.children]
|
|
54
|
+
with self.session():
|
|
55
|
+
rows = self.query(
|
|
56
|
+
"SELECT object_id, is_available FROM objects "
|
|
57
|
+
"WHERE object_id = :object_id",
|
|
58
|
+
{"object_id": obj_id},
|
|
59
|
+
)
|
|
60
|
+
if not rows:
|
|
61
|
+
# Insert new object
|
|
62
|
+
self.query(
|
|
63
|
+
"INSERT INTO objects "
|
|
64
|
+
"(object_id, content, is_available, ref_count) "
|
|
65
|
+
"VALUES (:object_id, :content, :is_available, :ref_count)",
|
|
66
|
+
{
|
|
67
|
+
"object_id": obj_id,
|
|
68
|
+
"content": b"",
|
|
69
|
+
"is_available": 0,
|
|
70
|
+
"ref_count": 0,
|
|
71
|
+
},
|
|
72
|
+
)
|
|
73
|
+
for cid in child_ids:
|
|
74
|
+
self.query(
|
|
75
|
+
"INSERT INTO object_children (parent_id, child_id) "
|
|
76
|
+
"VALUES (:parent_id, :child_id)",
|
|
77
|
+
{"parent_id": obj_id, "child_id": cid},
|
|
78
|
+
)
|
|
79
|
+
self.query(
|
|
80
|
+
"UPDATE objects SET ref_count = ref_count + 1 "
|
|
81
|
+
"WHERE object_id = :object_id",
|
|
82
|
+
{"object_id": cid},
|
|
83
|
+
)
|
|
84
|
+
new_objects.append(obj_id)
|
|
85
|
+
else:
|
|
86
|
+
# Add to the list of new objects if not available
|
|
87
|
+
if not rows[0]["is_available"]:
|
|
88
|
+
new_objects.append(obj_id)
|
|
89
|
+
|
|
90
|
+
# Ensure run mapping
|
|
91
|
+
self.query(
|
|
92
|
+
"INSERT INTO run_objects (run_id, object_id) "
|
|
93
|
+
"VALUES (:run_id, :object_id) ON CONFLICT DO NOTHING",
|
|
94
|
+
{"run_id": uint64_to_int64(run_id), "object_id": obj_id},
|
|
95
|
+
)
|
|
96
|
+
return new_objects
|
|
41
97
|
|
|
42
98
|
def get_object_tree(self, object_id: str) -> ObjectTree:
|
|
43
99
|
"""Get the object tree for a given object ID."""
|
|
44
|
-
|
|
100
|
+
with self.session():
|
|
101
|
+
rows = self.query(
|
|
102
|
+
"SELECT object_id FROM objects WHERE object_id = :object_id",
|
|
103
|
+
{"object_id": object_id},
|
|
104
|
+
)
|
|
105
|
+
if not rows:
|
|
106
|
+
raise NoObjectInStoreError(
|
|
107
|
+
f"Object {object_id} was not pre-registered."
|
|
108
|
+
)
|
|
109
|
+
children = self.query(
|
|
110
|
+
"SELECT child_id FROM object_children WHERE parent_id = :parent_id",
|
|
111
|
+
{"parent_id": object_id},
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Build the object trees of all children
|
|
115
|
+
try:
|
|
116
|
+
child_trees = [self.get_object_tree(ch["child_id"]) for ch in children]
|
|
117
|
+
except NoObjectInStoreError as e:
|
|
118
|
+
# Raise an error if any child object is missing
|
|
119
|
+
# This indicates an integrity issue
|
|
120
|
+
raise NoObjectInStoreError(
|
|
121
|
+
f"Object tree for object ID '{object_id}' contains missing "
|
|
122
|
+
"children. This may indicate a corrupted object store."
|
|
123
|
+
) from e
|
|
124
|
+
|
|
125
|
+
# Create and return the ObjectTree for the current object
|
|
126
|
+
return ObjectTree(object_id=object_id, children=child_trees)
|
|
45
127
|
|
|
46
128
|
def put(self, object_id: str, object_content: bytes) -> None:
|
|
47
129
|
"""Put an object into the store."""
|
|
48
|
-
|
|
130
|
+
if self.verify:
|
|
131
|
+
# Verify object_id and object_content match
|
|
132
|
+
object_id_from_content = get_object_id(object_content)
|
|
133
|
+
if object_id != object_id_from_content:
|
|
134
|
+
raise ValueError(f"Object ID {object_id} does not match content hash")
|
|
135
|
+
|
|
136
|
+
# Validate object content
|
|
137
|
+
validate_object_content(content=object_content)
|
|
138
|
+
|
|
139
|
+
with self.session():
|
|
140
|
+
# Only allow adding the object if it has been preregistered
|
|
141
|
+
rows = self.query(
|
|
142
|
+
"SELECT is_available FROM objects WHERE object_id = :object_id",
|
|
143
|
+
{"object_id": object_id},
|
|
144
|
+
)
|
|
145
|
+
if not rows:
|
|
146
|
+
raise NoObjectInStoreError(
|
|
147
|
+
f"Object with ID '{object_id}' was not pre-registered."
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# Return if object is already present in the store
|
|
151
|
+
if rows[0]["is_available"]:
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
# Update the object entry in the store
|
|
155
|
+
self.query(
|
|
156
|
+
"UPDATE objects SET content = :content, is_available = 1 "
|
|
157
|
+
"WHERE object_id = :object_id",
|
|
158
|
+
{"content": object_content, "object_id": object_id},
|
|
159
|
+
)
|
|
49
160
|
|
|
50
161
|
def get(self, object_id: str) -> bytes | None:
|
|
51
162
|
"""Get an object from the store."""
|
|
@@ -56,11 +167,65 @@ class SqlObjectStore(ObjectStore, SqlMixin):
|
|
|
56
167
|
|
|
57
168
|
def delete(self, object_id: str) -> None:
|
|
58
169
|
"""Delete an object and its unreferenced descendants from the store."""
|
|
59
|
-
|
|
170
|
+
with self.session():
|
|
171
|
+
rows = self.query(
|
|
172
|
+
"SELECT ref_count FROM objects WHERE object_id = :object_id",
|
|
173
|
+
{"object_id": object_id},
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# If the object is not in the store, nothing to delete
|
|
177
|
+
if not rows:
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
# Skip deletion if there are still references
|
|
181
|
+
if rows[0]["ref_count"] > 0:
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
# Deleting will cascade via FK, but we need to decrement children first
|
|
185
|
+
children = self.query(
|
|
186
|
+
"SELECT child_id FROM object_children WHERE parent_id = :parent_id",
|
|
187
|
+
{"parent_id": object_id},
|
|
188
|
+
)
|
|
189
|
+
child_ids = [child["child_id"] for child in children]
|
|
190
|
+
|
|
191
|
+
if child_ids:
|
|
192
|
+
placeholders = ", ".join(f":cid{i}" for i in range(len(child_ids)))
|
|
193
|
+
params = {f"cid{i}": cid for i, cid in enumerate(child_ids)}
|
|
194
|
+
self.query(
|
|
195
|
+
"UPDATE objects SET ref_count = ref_count - 1 "
|
|
196
|
+
f"WHERE object_id IN ({placeholders})",
|
|
197
|
+
params,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
self.query(
|
|
201
|
+
"DELETE FROM objects WHERE object_id = :object_id",
|
|
202
|
+
{"object_id": object_id},
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
# Recursively clean children
|
|
206
|
+
for child_id in child_ids:
|
|
207
|
+
self.delete(child_id)
|
|
60
208
|
|
|
61
209
|
def delete_objects_in_run(self, run_id: int) -> None:
|
|
62
210
|
"""Delete all objects that were registered in a specific run."""
|
|
63
|
-
|
|
211
|
+
run_id_sint = uint64_to_int64(run_id)
|
|
212
|
+
with self.session():
|
|
213
|
+
objs = self.query(
|
|
214
|
+
"SELECT object_id FROM run_objects WHERE run_id = :run_id",
|
|
215
|
+
{"run_id": run_id_sint},
|
|
216
|
+
)
|
|
217
|
+
for obj in objs:
|
|
218
|
+
object_id = obj["object_id"]
|
|
219
|
+
rows = self.query(
|
|
220
|
+
"SELECT ref_count FROM objects WHERE object_id=:object_id",
|
|
221
|
+
{"object_id": object_id},
|
|
222
|
+
)
|
|
223
|
+
if rows and rows[0]["ref_count"] == 0:
|
|
224
|
+
self.delete(object_id)
|
|
225
|
+
self.query(
|
|
226
|
+
"DELETE FROM run_objects WHERE run_id = :run_id",
|
|
227
|
+
{"run_id": run_id_sint},
|
|
228
|
+
)
|
|
64
229
|
|
|
65
230
|
def clear(self) -> None:
|
|
66
231
|
"""Clear the store."""
|
{flwr_nightly-1.26.0.dev20260122.dist-info → flwr_nightly-1.26.0.dev20260123.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: flwr-nightly
|
|
3
|
-
Version: 1.26.0.
|
|
3
|
+
Version: 1.26.0.dev20260123
|
|
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
|
|
@@ -45,7 +45,7 @@ Requires-Dist: ray (==2.51.1) ; (python_version >= "3.10" and python_version < "
|
|
|
45
45
|
Requires-Dist: ray (==2.51.1) ; (sys_platform != "win32" and python_version == "3.13") and (extra == "simulation")
|
|
46
46
|
Requires-Dist: requests (>=2.31.0,<3.0.0)
|
|
47
47
|
Requires-Dist: rich (>=13.5.0,<14.0.0)
|
|
48
|
-
Requires-Dist: starlette (>=0.
|
|
48
|
+
Requires-Dist: starlette (>=0.47.2,<0.48.0) ; extra == "rest"
|
|
49
49
|
Requires-Dist: tomli (>=2.0.1,<3.0.0)
|
|
50
50
|
Requires-Dist: tomli-w (>=1.0.0,<2.0.0)
|
|
51
51
|
Requires-Dist: typer (>=0.12.5,<0.21.0)
|
{flwr_nightly-1.26.0.dev20260122.dist-info → flwr_nightly-1.26.0.dev20260123.dist-info}/RECORD
RENAMED
|
@@ -19,7 +19,7 @@ flwr/cli/cli_account_auth_interceptor.py,sha256=mXgxThpZjU_2Xlae9xT8ewOw60GeE64c
|
|
|
19
19
|
flwr/cli/config/__init__.py,sha256=46z6whA3hvKkl9APRs-UG7Ym3K9VOqKx_pYcgelRjtE,788
|
|
20
20
|
flwr/cli/config/ls.py,sha256=ktRga1KWt4IDK5TQNT6ixfP66zcspkf-F2j3CkkpsGo,3665
|
|
21
21
|
flwr/cli/config_migration.py,sha256=UM3lZU34TOhGXkjBBChyGaE6r7uaVV2NpyP4TZhFPyY,10848
|
|
22
|
-
flwr/cli/config_utils.py,sha256=
|
|
22
|
+
flwr/cli/config_utils.py,sha256=FR24Q07mo97u8Yv2HlMc78GG1RgDo5CUr3865xetTBo,8174
|
|
23
23
|
flwr/cli/constant.py,sha256=Ihww6e-6V_Ocv7pRnIQeezdy72_RBL9dPYv87Yf4ppI,3612
|
|
24
24
|
flwr/cli/example.py,sha256=SNTorkKPrx1rOryGREUyZu8TcOc1-vFv1zEddaysdY0,2216
|
|
25
25
|
flwr/cli/federation/__init__.py,sha256=okxswL4fAjApI9gV_alU1lRkTUcQRbwlzvtUTLz61fE,793
|
|
@@ -42,7 +42,7 @@ flwr/cli/supernode/ls.py,sha256=x4ghJHpqcpDGuZx91ZehzSdmrUJ8ZuZvpY_UybnP63U,8224
|
|
|
42
42
|
flwr/cli/supernode/register.py,sha256=b5tt6rPAA2t4cUBqnzcaCUJzy2uqKETSB43bGR5ltH0,5871
|
|
43
43
|
flwr/cli/supernode/unregister.py,sha256=O72yiDuJaNrXx-5Pv6DKFxNYwxAHwEDBkAcqrlpwpC8,3949
|
|
44
44
|
flwr/cli/typing.py,sha256=MaY3NAca2PgmNByogksDKSCoRQLQpXTgO8NO9nLP0yA,8008
|
|
45
|
-
flwr/cli/utils.py,sha256=
|
|
45
|
+
flwr/cli/utils.py,sha256=JfYmWbkalOzRHykHZvnD8TGn1R3Epk-F-dhEyXmZAYM,16394
|
|
46
46
|
flwr/client/__init__.py,sha256=xwkPJfdeWxIIfmiPE5vnmnY_JbTlErP0Qs9eBP6qRFg,1252
|
|
47
47
|
flwr/client/client.py,sha256=3HAchxvknKG9jYbB7swNyDj-e5vUWDuMKoLvbT7jCVM,7895
|
|
48
48
|
flwr/client/dpfedavg_numpy_client.py,sha256=ELDHyEJcTB-FlLhHC-JXy8HuB3ZFHfT0HL3g1VSWY5w,7451
|
|
@@ -268,12 +268,11 @@ flwr/server/superlink/fleet/vce/backend/__init__.py,sha256=PPH89Yqd1XKm-sRJN6R0W
|
|
|
268
268
|
flwr/server/superlink/fleet/vce/backend/backend.py,sha256=HPOXhT40bFwCA9pz8w2sATdamgNVotXNO8p1ZRCOg7M,2205
|
|
269
269
|
flwr/server/superlink/fleet/vce/backend/raybackend.py,sha256=LhVmM2Mi6kOskOyDFnbIb7AH304J0eUU1r2B32bzUFc,7193
|
|
270
270
|
flwr/server/superlink/fleet/vce/vce_api.py,sha256=A62Vx3r71bNI-v10IUen_27fDJoYXsOyvBM3JrkTvho,13609
|
|
271
|
-
flwr/server/superlink/linkstate/__init__.py,sha256=
|
|
271
|
+
flwr/server/superlink/linkstate/__init__.py,sha256=KSUCjOfJlW1g6GAjlqK3mTviqLvJLuQEzSJwO27Qokg,1052
|
|
272
272
|
flwr/server/superlink/linkstate/in_memory_linkstate.py,sha256=1-aaSwad77SeYND3HEF2041KNG5ZA-8i1RcheYZMNq8,31689
|
|
273
273
|
flwr/server/superlink/linkstate/linkstate.py,sha256=Zz2vBI0QFFnptCUIeNjqQCbpCvMMzfsZluprEMkkHio,16809
|
|
274
274
|
flwr/server/superlink/linkstate/linkstate_factory.py,sha256=BTzPIqrWa797YKdOEHt0iu4ujxRcK4FNvUgjsOYQfMs,2859
|
|
275
275
|
flwr/server/superlink/linkstate/sql_linkstate.py,sha256=EJHS1SC5acaxRQb1Nmpjen3elDK6L93s8pDUXNAE_Wk,47782
|
|
276
|
-
flwr/server/superlink/linkstate/sqlite_linkstate.py,sha256=vMhViLnRJoxc-TNFHY3YaRKybRdS1zNzORRwsve9MUQ,48458
|
|
277
276
|
flwr/server/superlink/linkstate/utils.py,sha256=IA1mKKhGVBPoD61VXKFa8dZ_prnfuyWIuKAeeHPLmuE,16000
|
|
278
277
|
flwr/server/superlink/serverappio/__init__.py,sha256=Fy4zJuoccZe5mZSEIpOmQvU6YeXFBa1M4eZuXXmJcn8,717
|
|
279
278
|
flwr/server/superlink/serverappio/serverappio_grpc.py,sha256=2NFPynJMpYpT9C98Fr4n0QrTTjWBWbeUzlHcc6pg2kY,2279
|
|
@@ -334,7 +333,6 @@ flwr/supercore/corestate/__init__.py,sha256=Vau6-L_JG5QzNqtCTa9xCKGGljc09wY8avZm
|
|
|
334
333
|
flwr/supercore/corestate/corestate.py,sha256=EZg4gPXqVOXwS7t0tlPfedajoWj5T80oeDBNxpV2y2I,2874
|
|
335
334
|
flwr/supercore/corestate/in_memory_corestate.py,sha256=9qa6RuRZfCp6vs-ARYdiZjCL31VOAAxw0a_VkBXR5zY,5116
|
|
336
335
|
flwr/supercore/corestate/sql_corestate.py,sha256=4cJOpAjMLe7nS5HbdQ3KcV-nK_kXVRazX05xVMtR6dw,5736
|
|
337
|
-
flwr/supercore/corestate/sqlite_corestate.py,sha256=2SAikyNG0dgxOUtQsy9WS5XCDibndw6y4xPGK61seWE,5740
|
|
338
336
|
flwr/supercore/credential_store/__init__.py,sha256=csDOy8YgD-DnmXc2HwatMfXqXATHe6OLsBPKtDndhXw,1077
|
|
339
337
|
flwr/supercore/credential_store/credential_store.py,sha256=m_YHW0_A8CBfuRVq-GJgGNYDldxo9grT_jwJ8oCM9xA,1194
|
|
340
338
|
flwr/supercore/credential_store/file_credential_store.py,sha256=PeWuigqtMGMYnj_tKnWtlmJtu7OLuVaOB4D9GS47lHc,2771
|
|
@@ -352,14 +350,12 @@ flwr/supercore/license_plugin/license_plugin.py,sha256=BFhlCH5v9KKuY7crVCsi8fuYe
|
|
|
352
350
|
flwr/supercore/object_store/__init__.py,sha256=cdfPAmjINY6iOp8oI_LdcVh2simg469Mkdl4LLV4kHI,911
|
|
353
351
|
flwr/supercore/object_store/in_memory_object_store.py,sha256=-DU6n1Ef3EmllHY9SzeXw57ftoU0mR_6or4NIDSZlcs,9322
|
|
354
352
|
flwr/supercore/object_store/object_store.py,sha256=XLo-xe258TmCel9dNLCJepFjXaQtEi0wBqzU9YsyJVg,5221
|
|
355
|
-
flwr/supercore/object_store/object_store_factory.py,sha256=
|
|
356
|
-
flwr/supercore/object_store/sql_object_store.py,sha256=
|
|
357
|
-
flwr/supercore/object_store/sqlite_object_store.py,sha256=oDfqGe6TEr1HT6_ZoxLoi7T1qEF2_GBKmfKh3yKux_Y,10251
|
|
353
|
+
flwr/supercore/object_store/object_store_factory.py,sha256=gFATFiSdKIcwL_sjk2OMbpi3Fb5_C6K3sdTRxNbi-SY,2308
|
|
354
|
+
flwr/supercore/object_store/sql_object_store.py,sha256=ZBmyc3a_GXZFNaefDCxhrri-_BgcQPOj7OtUfrPcjKo,10130
|
|
358
355
|
flwr/supercore/primitives/__init__.py,sha256=Tx8GOjnmMo8Y74RsDGrMpfr-E0Nl8dcUDF784_ge6F8,745
|
|
359
356
|
flwr/supercore/primitives/asymmetric.py,sha256=1643niHYj3uEbfPd06VuMHwN3tKVwg0uVyR3RhTdWIU,3778
|
|
360
357
|
flwr/supercore/primitives/asymmetric_ed25519.py,sha256=eIhOTMibQW0FJX4MXdplHdL3HcfCiKuFu2mQ8GQTUz8,5025
|
|
361
358
|
flwr/supercore/sql_mixin.py,sha256=eT4JHaLG4gShZrC10pkUVIGcTvZszlJI1ARhOJEA_WY,11727
|
|
362
|
-
flwr/supercore/sqlite_mixin.py,sha256=KrHxJQlJSuTdfc8YlFTft-COzT6FvVtQDWAWkdJ-jGQ,5064
|
|
363
359
|
flwr/supercore/state/__init__.py,sha256=FkKhsNVM4LjlRlOgXTz6twINmw5ohIUKS_OER0BNo_w,724
|
|
364
360
|
flwr/supercore/state/schema/README.md,sha256=-0QrXDhnv30gEYoIUJo7aLUolY0r_t_nC24yk7B2agM,2892
|
|
365
361
|
flwr/supercore/state/schema/__init__.py,sha256=Egnde6OY01wrpT4PuhL4NGn_NY4jdAH7kf7ktagN4Ws,724
|
|
@@ -406,7 +402,7 @@ flwr/supernode/servicer/__init__.py,sha256=lucTzre5WPK7G1YLCfaqg3rbFWdNSb7ZTt-ca
|
|
|
406
402
|
flwr/supernode/servicer/clientappio/__init__.py,sha256=7Oy62Y_oijqF7Dxi6tpcUQyOpLc_QpIRZ83NvwmB0Yg,813
|
|
407
403
|
flwr/supernode/servicer/clientappio/clientappio_servicer.py,sha256=rRL4CQ0L78jF_p0ct4-JMGREt6wWRy__wy4czF4f54Y,11639
|
|
408
404
|
flwr/supernode/start_client_internal.py,sha256=BYk69UBQ2gQJaDQxXhccUgfOWrb7ShAstrbcMOCZIIs,26173
|
|
409
|
-
flwr_nightly-1.26.0.
|
|
410
|
-
flwr_nightly-1.26.0.
|
|
411
|
-
flwr_nightly-1.26.0.
|
|
412
|
-
flwr_nightly-1.26.0.
|
|
405
|
+
flwr_nightly-1.26.0.dev20260123.dist-info/METADATA,sha256=6qgGCPCTOXXGCDUea3DHh770_NI9PSfIsF_sVY_ET-4,14398
|
|
406
|
+
flwr_nightly-1.26.0.dev20260123.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
407
|
+
flwr_nightly-1.26.0.dev20260123.dist-info/entry_points.txt,sha256=hxHD2ixb_vJFDOlZV-zB4Ao32_BQlL34ftsDh1GXv14,420
|
|
408
|
+
flwr_nightly-1.26.0.dev20260123.dist-info/RECORD,,
|