flwr-nightly 1.26.0.dev20260115__py3-none-any.whl → 1.26.0.dev20260117__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_migration.py +225 -0
- flwr/cli/config_utils.py +60 -0
- flwr/cli/flower_config.py +32 -8
- flwr/cli/typing.py +22 -13
- flwr/cli/utils.py +92 -1
- flwr/supercore/state/__init__.py +15 -0
- flwr/supercore/state/schema/README.md +125 -0
- flwr/supercore/state/schema/__init__.py +15 -0
- flwr/supercore/state/schema/corestate_tables.py +31 -0
- flwr/supercore/state/schema/linkstate_tables.py +152 -0
- flwr/supercore/state/schema/objectstore_tables.py +85 -0
- {flwr_nightly-1.26.0.dev20260115.dist-info → flwr_nightly-1.26.0.dev20260117.dist-info}/METADATA +1 -1
- {flwr_nightly-1.26.0.dev20260115.dist-info → flwr_nightly-1.26.0.dev20260117.dist-info}/RECORD +15 -8
- {flwr_nightly-1.26.0.dev20260115.dist-info → flwr_nightly-1.26.0.dev20260117.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.26.0.dev20260115.dist-info → flwr_nightly-1.26.0.dev20260117.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# Copyright 2026 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
|
+
"""Utilities for migrating old TOML configurations to Flower config."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
import re
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
import click
|
|
23
|
+
import typer
|
|
24
|
+
|
|
25
|
+
from .config_utils import load_and_validate, validate_federation_in_project_config
|
|
26
|
+
from .flower_config import (
|
|
27
|
+
init_flwr_config,
|
|
28
|
+
parse_superlink_connection,
|
|
29
|
+
set_default_superlink_connection,
|
|
30
|
+
write_superlink_connection,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
CONFIG_MIGRATION_NOTICE = """
|
|
34
|
+
##################################################################
|
|
35
|
+
# CONFIGURATION MIGRATION NOTICE:
|
|
36
|
+
#
|
|
37
|
+
# What was previously called "federation config" for SuperLink
|
|
38
|
+
# connections in pyproject.toml has been renamed and moved.
|
|
39
|
+
#
|
|
40
|
+
# These settings are now **SuperLink connection configuration**
|
|
41
|
+
# and are defined in the Flower configuration file.
|
|
42
|
+
#
|
|
43
|
+
# The entries below are commented out intentionally and are kept
|
|
44
|
+
# only as a migration reference.
|
|
45
|
+
#
|
|
46
|
+
# Docs: <link to Flower config docs>
|
|
47
|
+
##################################################################
|
|
48
|
+
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
CLI_NOTICE = (
|
|
52
|
+
typer.style("\n🌸 Heads up from Flower!\n\n", fg=typer.colors.MAGENTA, bold=True)
|
|
53
|
+
+ "We detected legacy usage of this command that relies on connection\n"
|
|
54
|
+
+ "settings from your pyproject.toml.\n\n"
|
|
55
|
+
+ "Flower will migrate any relevant settings to the new Flower config.\n\n"
|
|
56
|
+
+ "Learn more: https://flower.ai/docs\n"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _is_legacy_usage(superlink: str, args: list[str]) -> bool:
|
|
61
|
+
"""Check if legacy usage is detected in the given arguments."""
|
|
62
|
+
# If one and only one extra argument is given, assume legacy usage
|
|
63
|
+
if len(args) == 1:
|
|
64
|
+
return True
|
|
65
|
+
|
|
66
|
+
# If the `superlink` looks like a path, assume legacy usage
|
|
67
|
+
pth = Path(superlink)
|
|
68
|
+
if pth.is_absolute() or len(pth.parts) > 1 or superlink in (".", ".."):
|
|
69
|
+
return True
|
|
70
|
+
|
|
71
|
+
# Lastly, check if a pyproject.toml file exists at the given superlink
|
|
72
|
+
if (pth / "pyproject.toml").exists():
|
|
73
|
+
return True
|
|
74
|
+
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _check_is_migratable(app: Path) -> None:
|
|
79
|
+
"""Check if the given app path contains legacy TOML configuration."""
|
|
80
|
+
toml_path = app / "pyproject.toml"
|
|
81
|
+
if not toml_path.exists():
|
|
82
|
+
raise FileNotFoundError(f"No pyproject.toml found in '{app}'")
|
|
83
|
+
config, errors, _ = load_and_validate(toml_path, check_module=False)
|
|
84
|
+
if config is None:
|
|
85
|
+
raise ValueError(f"Failed to load TOML configuration: {toml_path}")
|
|
86
|
+
if errors:
|
|
87
|
+
raise ValueError(
|
|
88
|
+
f"Invalid TOML configuration found in '{toml_path}':\n"
|
|
89
|
+
+ "\n".join(f"- {err}" for err in errors)
|
|
90
|
+
)
|
|
91
|
+
try:
|
|
92
|
+
_ = config["tool"]["flwr"]["federations"]
|
|
93
|
+
return
|
|
94
|
+
except KeyError:
|
|
95
|
+
raise ValueError(
|
|
96
|
+
f"No '[tool.flwr.federations]' section found in '{toml_path}'"
|
|
97
|
+
) from None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _migrate_pyproject_toml_to_flower_config(
|
|
101
|
+
app: Path, toml_federation: str | None
|
|
102
|
+
) -> tuple[list[str], str | None]:
|
|
103
|
+
"""Migrate old TOML configuration to Flower config."""
|
|
104
|
+
# Load and validate the old TOML configuration
|
|
105
|
+
toml_path = app / "pyproject.toml"
|
|
106
|
+
config, _, _ = load_and_validate(toml_path, check_module=False)
|
|
107
|
+
if config is None:
|
|
108
|
+
raise ValueError(f"Failed to load TOML configuration: {toml_path}")
|
|
109
|
+
validate_federation_in_project_config(toml_federation, config)
|
|
110
|
+
|
|
111
|
+
# Construct SuperLinkConnection
|
|
112
|
+
toml_federations: dict[str, Any] = config["tool"]["flwr"]["federations"]
|
|
113
|
+
migrated_conn_names: list[str] = []
|
|
114
|
+
for name, toml_fed_config in toml_federations.items():
|
|
115
|
+
if isinstance(toml_fed_config, dict):
|
|
116
|
+
# Resolve relative root-certificates path
|
|
117
|
+
if cert_path := toml_fed_config.get("root-certificates"):
|
|
118
|
+
if not Path(cert_path).is_absolute():
|
|
119
|
+
toml_fed_config["root-certificates"] = str(
|
|
120
|
+
(app / cert_path).resolve()
|
|
121
|
+
)
|
|
122
|
+
# Parse and write SuperLink connection
|
|
123
|
+
conn = parse_superlink_connection(toml_fed_config, name)
|
|
124
|
+
write_superlink_connection(conn)
|
|
125
|
+
migrated_conn_names.append(name)
|
|
126
|
+
|
|
127
|
+
# Set default federation if applicable
|
|
128
|
+
default_toml_federation: str | None = toml_federations.get("default")
|
|
129
|
+
if default_toml_federation in toml_federations:
|
|
130
|
+
set_default_superlink_connection(default_toml_federation)
|
|
131
|
+
|
|
132
|
+
return migrated_conn_names, default_toml_federation
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _comment_out_legacy_toml_config(app: Path) -> None:
|
|
136
|
+
"""Comment out legacy TOML configuration in pyproject.toml."""
|
|
137
|
+
# Read pyproject.toml lines
|
|
138
|
+
toml_path = app / "pyproject.toml"
|
|
139
|
+
lines = toml_path.read_text(encoding="utf-8").splitlines(keepends=True)
|
|
140
|
+
section_pattern = re.compile(r"\s*\[(.*)\]")
|
|
141
|
+
|
|
142
|
+
# Comment out the [tool.flwr.federations] section
|
|
143
|
+
notice_added = False
|
|
144
|
+
in_federation_section = False
|
|
145
|
+
with toml_path.open("w", encoding="utf-8") as f:
|
|
146
|
+
for line in lines:
|
|
147
|
+
# Detect section headers
|
|
148
|
+
if match := section_pattern.match(line):
|
|
149
|
+
section = match.group(1)
|
|
150
|
+
in_federation_section = section.startswith("tool.flwr.federations")
|
|
151
|
+
|
|
152
|
+
# Comment out lines in the federation section
|
|
153
|
+
if in_federation_section:
|
|
154
|
+
if not notice_added:
|
|
155
|
+
f.write(CONFIG_MIGRATION_NOTICE)
|
|
156
|
+
notice_added = True
|
|
157
|
+
# Preserve empty lines and comment out others
|
|
158
|
+
f.write(f"# {line}" if line.strip() != "" else line)
|
|
159
|
+
else:
|
|
160
|
+
f.write(line)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def migrate(
|
|
164
|
+
app: Path,
|
|
165
|
+
toml_federation: str | None,
|
|
166
|
+
) -> None:
|
|
167
|
+
"""Migrate legacy TOML configuration to Flower config."""
|
|
168
|
+
# Initialize Flower config
|
|
169
|
+
init_flwr_config()
|
|
170
|
+
|
|
171
|
+
# Print migration notice
|
|
172
|
+
typer.echo(CLI_NOTICE)
|
|
173
|
+
|
|
174
|
+
# Check if migration is applicable
|
|
175
|
+
app = app.resolve()
|
|
176
|
+
try:
|
|
177
|
+
_check_is_migratable(app)
|
|
178
|
+
except (FileNotFoundError, ValueError) as e:
|
|
179
|
+
raise click.ClickException(f"Cannot migrate configuration:\n{e}") from e
|
|
180
|
+
|
|
181
|
+
try:
|
|
182
|
+
migrated_conns, default_conn = _migrate_pyproject_toml_to_flower_config(
|
|
183
|
+
app, toml_federation
|
|
184
|
+
)
|
|
185
|
+
except Exception as e:
|
|
186
|
+
raise click.ClickException(
|
|
187
|
+
f"Failed to migrate legacy TOML configuration to Flower config:\n{e!r}"
|
|
188
|
+
) from e
|
|
189
|
+
|
|
190
|
+
typer.secho("✅ Migration completed successfully!\n", fg=typer.colors.GREEN)
|
|
191
|
+
|
|
192
|
+
# Print migrated connections
|
|
193
|
+
typer.secho("Migrated SuperLink connections:", fg=typer.colors.BLUE)
|
|
194
|
+
for conn_name in migrated_conns:
|
|
195
|
+
typer.secho(f" {conn_name}", fg=typer.colors.GREEN, nl=False)
|
|
196
|
+
if conn_name == default_conn:
|
|
197
|
+
typer.secho(" (default)", fg=typer.colors.WHITE, nl=False)
|
|
198
|
+
typer.echo()
|
|
199
|
+
|
|
200
|
+
# print usage
|
|
201
|
+
typer.secho("\nYou should now use the Flower CLI as follows:")
|
|
202
|
+
ctx = click.get_current_context()
|
|
203
|
+
typer.secho(ctx.get_usage() + "\n", bold=True)
|
|
204
|
+
|
|
205
|
+
_comment_out_legacy_toml_config(app)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def migrate_if_legacy_usage(
|
|
209
|
+
superlink: str,
|
|
210
|
+
args: list[str],
|
|
211
|
+
) -> None:
|
|
212
|
+
"""Migrate legacy TOML configuration to Flower config if legacy usage is
|
|
213
|
+
detected."""
|
|
214
|
+
# Trigger the same typer error when detecting unexpected extra args
|
|
215
|
+
if len(args) > 1:
|
|
216
|
+
raise click.UsageError(f"Got unexpected extra arguments ({' '.join(args[1:])})")
|
|
217
|
+
|
|
218
|
+
# Skip migration if no legacy usage is detected
|
|
219
|
+
if not _is_legacy_usage(superlink, args):
|
|
220
|
+
return
|
|
221
|
+
|
|
222
|
+
migrate(
|
|
223
|
+
app=Path(superlink),
|
|
224
|
+
toml_federation=args[0] if len(args) == 1 else None,
|
|
225
|
+
)
|
flwr/cli/config_utils.py
CHANGED
|
@@ -21,6 +21,7 @@ from typing import Any
|
|
|
21
21
|
import tomli
|
|
22
22
|
import typer
|
|
23
23
|
|
|
24
|
+
from flwr.cli.typing import SuperLinkConnection
|
|
24
25
|
from flwr.common.config import (
|
|
25
26
|
fuse_dicts,
|
|
26
27
|
get_fab_config,
|
|
@@ -283,6 +284,65 @@ def validate_certificate_in_federation_config(
|
|
|
283
284
|
return insecure, root_certificates_bytes
|
|
284
285
|
|
|
285
286
|
|
|
287
|
+
def load_certificate_in_connection(
|
|
288
|
+
connection: SuperLinkConnection,
|
|
289
|
+
) -> bytes | None:
|
|
290
|
+
"""Validate TLS-related settings and load root certificates if provided.
|
|
291
|
+
|
|
292
|
+
Parameters
|
|
293
|
+
----------
|
|
294
|
+
connection : SuperLinkConnection
|
|
295
|
+
The SuperLink connection configuration.
|
|
296
|
+
|
|
297
|
+
Returns
|
|
298
|
+
-------
|
|
299
|
+
bytes | None
|
|
300
|
+
The loaded root certificate bytes if a custom certificate is configured.
|
|
301
|
+
None if TLS is disabled or if gRPC should use its default trust store.
|
|
302
|
+
|
|
303
|
+
Raises
|
|
304
|
+
------
|
|
305
|
+
ValueError
|
|
306
|
+
If required TLS settings are missing.
|
|
307
|
+
typer.Exit
|
|
308
|
+
If the configuration is invalid or the certificate file cannot be read.
|
|
309
|
+
"""
|
|
310
|
+
if connection.insecure is None:
|
|
311
|
+
raise ValueError(
|
|
312
|
+
f"SuperLink connection '{connection.name}' is missing insecure setting."
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
insecure = connection.insecure
|
|
316
|
+
|
|
317
|
+
# Process root certificates
|
|
318
|
+
if root_certificates := connection.root_certificates:
|
|
319
|
+
if insecure:
|
|
320
|
+
typer.secho(
|
|
321
|
+
"❌ `root-certificates` were provided but the `insecure` parameter "
|
|
322
|
+
"is set to `True`.",
|
|
323
|
+
fg=typer.colors.RED,
|
|
324
|
+
bold=True,
|
|
325
|
+
err=True,
|
|
326
|
+
)
|
|
327
|
+
raise typer.Exit(code=1)
|
|
328
|
+
|
|
329
|
+
# TLS is enabled with self-signed certificates: attempt to read the file
|
|
330
|
+
try:
|
|
331
|
+
root_certificates_bytes = Path(root_certificates).read_bytes()
|
|
332
|
+
except Exception as e:
|
|
333
|
+
typer.secho(
|
|
334
|
+
f"❌ Failed to read certificate file `{root_certificates}`: {e}",
|
|
335
|
+
fg=typer.colors.RED,
|
|
336
|
+
bold=True,
|
|
337
|
+
err=True,
|
|
338
|
+
)
|
|
339
|
+
raise typer.Exit(code=1) from e
|
|
340
|
+
else:
|
|
341
|
+
root_certificates_bytes = None
|
|
342
|
+
|
|
343
|
+
return root_certificates_bytes
|
|
344
|
+
|
|
345
|
+
|
|
286
346
|
def exit_if_no_address(federation_config: dict[str, Any], cmd: str) -> None:
|
|
287
347
|
"""Exit if the provided federation_config has no "address" key.
|
|
288
348
|
|
flwr/cli/flower_config.py
CHANGED
|
@@ -234,7 +234,7 @@ def serialize_superlink_connection(connection: SuperLinkConnection) -> dict[str,
|
|
|
234
234
|
|
|
235
235
|
def read_superlink_connection(
|
|
236
236
|
connection_name: str | None = None,
|
|
237
|
-
) -> SuperLinkConnection
|
|
237
|
+
) -> SuperLinkConnection:
|
|
238
238
|
"""Read a SuperLink connection from the Flower configuration file.
|
|
239
239
|
|
|
240
240
|
Parameters
|
|
@@ -245,9 +245,8 @@ def read_superlink_connection(
|
|
|
245
245
|
|
|
246
246
|
Returns
|
|
247
247
|
-------
|
|
248
|
-
SuperLinkConnection
|
|
249
|
-
The SuperLink connection
|
|
250
|
-
requested connection (or default) cannot be found.
|
|
248
|
+
SuperLinkConnection
|
|
249
|
+
The SuperLink connection.
|
|
251
250
|
|
|
252
251
|
Raises
|
|
253
252
|
------
|
|
@@ -345,10 +344,29 @@ def write_superlink_connection(connection: SuperLinkConnection) -> None:
|
|
|
345
344
|
# Add/update the connection
|
|
346
345
|
superlink_config[connection.name] = conn_dict
|
|
347
346
|
|
|
348
|
-
#
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
347
|
+
# Write back to file
|
|
348
|
+
write_flower_config(toml_dict)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def set_default_superlink_connection(connection_name: str) -> None:
|
|
352
|
+
"""Set the default SuperLink connection."""
|
|
353
|
+
toml_dict, _ = read_flower_config()
|
|
354
|
+
|
|
355
|
+
# Get superlink section
|
|
356
|
+
superlink_config = toml_dict[SuperLinkConnectionTomlKey.SUPERLINK]
|
|
357
|
+
|
|
358
|
+
# Check if the connection exists
|
|
359
|
+
if connection_name not in superlink_config:
|
|
360
|
+
typer.secho(
|
|
361
|
+
f"❌ SuperLink connection '{connection_name}' not found in the Flower "
|
|
362
|
+
"configuration file. Cannot set as default.",
|
|
363
|
+
fg=typer.colors.RED,
|
|
364
|
+
err=True,
|
|
365
|
+
)
|
|
366
|
+
raise typer.Exit(code=1)
|
|
367
|
+
|
|
368
|
+
# Set default connection
|
|
369
|
+
superlink_config[SuperLinkConnectionTomlKey.DEFAULT] = connection_name
|
|
352
370
|
|
|
353
371
|
# Write back to file
|
|
354
372
|
write_flower_config(toml_dict)
|
|
@@ -402,6 +420,12 @@ def write_flower_config(toml_dict: dict[str, Any]) -> Path:
|
|
|
402
420
|
"""
|
|
403
421
|
config_path = get_flwr_home() / FLOWER_CONFIG_FILE
|
|
404
422
|
|
|
423
|
+
# Flatten SuperLink connections
|
|
424
|
+
superlink_config: dict[str, Any] = toml_dict[SuperLinkConnectionTomlKey.SUPERLINK]
|
|
425
|
+
for name in list(superlink_config.keys()):
|
|
426
|
+
if isinstance(superlink_config[name], dict):
|
|
427
|
+
superlink_config[name] = flatten_dict(superlink_config[name])
|
|
428
|
+
|
|
405
429
|
# Get the standard TOML text
|
|
406
430
|
toml_content = tomli_w.dumps(toml_dict)
|
|
407
431
|
|
flwr/cli/typing.py
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
from dataclasses import dataclass
|
|
19
|
+
from pathlib import Path
|
|
19
20
|
|
|
20
21
|
from flwr.cli.constant import (
|
|
21
22
|
DEFAULT_SIMULATION_BACKEND_NAME,
|
|
@@ -102,38 +103,46 @@ class SuperLinkConnection:
|
|
|
102
103
|
|
|
103
104
|
def __post_init__(self) -> None:
|
|
104
105
|
"""Validate SuperLink connection configuration."""
|
|
106
|
+
err_prefix = f"Invalid value for key '%s' in connection '{self.name}': "
|
|
105
107
|
if self.address is not None and not isinstance(self.address, str):
|
|
106
108
|
raise ValueError(
|
|
107
|
-
|
|
108
|
-
f"expected str, but got {type(self.address).__name__}."
|
|
109
|
+
err_prefix % SuperLinkConnectionTomlKey.ADDRESS
|
|
110
|
+
+ f"expected str, but got {type(self.address).__name__}."
|
|
109
111
|
)
|
|
110
112
|
if self.root_certificates is not None and not isinstance(
|
|
111
113
|
self.root_certificates, str
|
|
112
114
|
):
|
|
113
115
|
raise ValueError(
|
|
114
|
-
|
|
115
|
-
f"
|
|
116
|
-
f"expected str, but got {type(self.root_certificates).__name__}."
|
|
116
|
+
err_prefix % SuperLinkConnectionTomlKey.ROOT_CERTIFICATES
|
|
117
|
+
+ f"expected str, but got {type(self.root_certificates).__name__}."
|
|
117
118
|
)
|
|
119
|
+
|
|
120
|
+
# Ensure root certificates path is absolute
|
|
121
|
+
if self.root_certificates is not None:
|
|
122
|
+
if not Path(self.root_certificates).is_absolute():
|
|
123
|
+
raise ValueError(
|
|
124
|
+
err_prefix % SuperLinkConnectionTomlKey.ROOT_CERTIFICATES
|
|
125
|
+
+ "expected absolute path, but got relative path "
|
|
126
|
+
f"'{self.root_certificates}'."
|
|
127
|
+
)
|
|
128
|
+
|
|
118
129
|
if self.insecure is not None and not isinstance(self.insecure, bool):
|
|
119
130
|
raise ValueError(
|
|
120
|
-
|
|
121
|
-
f"but got {type(self.insecure).__name__}."
|
|
131
|
+
err_prefix % SuperLinkConnectionTomlKey.INSECURE
|
|
132
|
+
+ f"expected bool, but got {type(self.insecure).__name__}."
|
|
122
133
|
)
|
|
123
134
|
if self.enable_account_auth is not None and not isinstance(
|
|
124
135
|
self.enable_account_auth, bool
|
|
125
136
|
):
|
|
126
137
|
raise ValueError(
|
|
127
|
-
|
|
128
|
-
f"
|
|
129
|
-
f"expected bool, but got {type(self.enable_account_auth).__name__}."
|
|
138
|
+
err_prefix % SuperLinkConnectionTomlKey.ENABLE_ACCOUNT_AUTH
|
|
139
|
+
+ f"expected bool, but got {type(self.enable_account_auth).__name__}."
|
|
130
140
|
)
|
|
131
141
|
|
|
132
142
|
if self.federation is not None and not isinstance(self.federation, str):
|
|
133
143
|
raise ValueError(
|
|
134
|
-
|
|
135
|
-
f"
|
|
136
|
-
f"expected str, but got {type(self.federation).__name__}."
|
|
144
|
+
err_prefix % SuperLinkConnectionTomlKey.FEDERATION
|
|
145
|
+
+ f"expected str, but got {type(self.federation).__name__}."
|
|
137
146
|
)
|
|
138
147
|
|
|
139
148
|
# The connection needs to have either an address or options (or both).
|
flwr/cli/utils.py
CHANGED
|
@@ -27,6 +27,7 @@ import grpc
|
|
|
27
27
|
import pathspec
|
|
28
28
|
import typer
|
|
29
29
|
|
|
30
|
+
from flwr.cli.typing import SuperLinkConnection
|
|
30
31
|
from flwr.common.constant import (
|
|
31
32
|
ACCESS_TOKEN_KEY,
|
|
32
33
|
AUTHN_TYPE_JSON_KEY,
|
|
@@ -47,10 +48,14 @@ from flwr.common.grpc import (
|
|
|
47
48
|
create_channel,
|
|
48
49
|
on_channel_state_change,
|
|
49
50
|
)
|
|
51
|
+
from flwr.supercore.utils import get_flwr_home
|
|
50
52
|
|
|
51
53
|
from .auth_plugin import CliAuthPlugin, get_cli_plugin_class
|
|
52
54
|
from .cli_account_auth_interceptor import CliAccountAuthInterceptor
|
|
53
|
-
from .config_utils import
|
|
55
|
+
from .config_utils import (
|
|
56
|
+
load_certificate_in_connection,
|
|
57
|
+
validate_certificate_in_federation_config,
|
|
58
|
+
)
|
|
54
59
|
|
|
55
60
|
|
|
56
61
|
def prompt_text(
|
|
@@ -369,6 +374,52 @@ def load_cli_auth_plugin(
|
|
|
369
374
|
raise typer.Exit(code=1) from None
|
|
370
375
|
|
|
371
376
|
|
|
377
|
+
def load_cli_auth_plugin_from_connection(
|
|
378
|
+
connection: SuperLinkConnection,
|
|
379
|
+
authn_type: str | None = None,
|
|
380
|
+
) -> CliAuthPlugin:
|
|
381
|
+
"""Load the CLI-side account auth plugin for the given connection.
|
|
382
|
+
|
|
383
|
+
Parameters
|
|
384
|
+
----------
|
|
385
|
+
connection : SuperLinkConnection
|
|
386
|
+
The SuperLink connection configuration.
|
|
387
|
+
authn_type : str | None
|
|
388
|
+
Authentication type. If None, will be determined from config.
|
|
389
|
+
|
|
390
|
+
Returns
|
|
391
|
+
-------
|
|
392
|
+
CliAuthPlugin
|
|
393
|
+
The loaded authentication plugin instance.
|
|
394
|
+
|
|
395
|
+
Raises
|
|
396
|
+
------
|
|
397
|
+
typer.Exit
|
|
398
|
+
If the authentication type is unknown.
|
|
399
|
+
"""
|
|
400
|
+
# Locate the credentials directory
|
|
401
|
+
flwr_dir = get_flwr_home()
|
|
402
|
+
credentials_dir = flwr_dir / CREDENTIALS_DIR
|
|
403
|
+
credentials_dir.mkdir(parents=True, exist_ok=True)
|
|
404
|
+
|
|
405
|
+
# Find the path to the account auth config file
|
|
406
|
+
config_path = get_account_auth_config_path(flwr_dir, connection.name)
|
|
407
|
+
|
|
408
|
+
# Determine the auth type if not provided
|
|
409
|
+
if authn_type is None:
|
|
410
|
+
authn_type = AuthnType.NOOP
|
|
411
|
+
if connection.enable_account_auth:
|
|
412
|
+
authn_type = retrieve_authn_type(config_path)
|
|
413
|
+
|
|
414
|
+
# Retrieve auth plugin class and instantiate it
|
|
415
|
+
try:
|
|
416
|
+
auth_plugin_class = get_cli_plugin_class(authn_type)
|
|
417
|
+
return auth_plugin_class(config_path)
|
|
418
|
+
except ValueError:
|
|
419
|
+
typer.echo(f"❌ Unknown account authentication type: {authn_type}")
|
|
420
|
+
raise typer.Exit(code=1) from None
|
|
421
|
+
|
|
422
|
+
|
|
372
423
|
def init_channel(
|
|
373
424
|
app: Path, federation_config: dict[str, Any], auth_plugin: CliAuthPlugin
|
|
374
425
|
) -> grpc.Channel:
|
|
@@ -407,6 +458,46 @@ def init_channel(
|
|
|
407
458
|
return channel
|
|
408
459
|
|
|
409
460
|
|
|
461
|
+
def init_channel_from_connection(connection: SuperLinkConnection) -> grpc.Channel:
|
|
462
|
+
"""Initialize gRPC channel to the Control API.
|
|
463
|
+
|
|
464
|
+
Parameters
|
|
465
|
+
----------
|
|
466
|
+
connection : SuperLinkConnection
|
|
467
|
+
SuperLink connection configuration.
|
|
468
|
+
|
|
469
|
+
Returns
|
|
470
|
+
-------
|
|
471
|
+
grpc.Channel
|
|
472
|
+
Configured gRPC channel with authentication interceptors.
|
|
473
|
+
"""
|
|
474
|
+
root_certificates_bytes = load_certificate_in_connection(connection)
|
|
475
|
+
|
|
476
|
+
# Load authentication plugin
|
|
477
|
+
auth_plugin = load_cli_auth_plugin_from_connection(connection)
|
|
478
|
+
|
|
479
|
+
# Load tokens
|
|
480
|
+
auth_plugin.load_tokens()
|
|
481
|
+
|
|
482
|
+
# Ensure address and insecure are set
|
|
483
|
+
if connection.address is None or connection.insecure is None:
|
|
484
|
+
raise ValueError(
|
|
485
|
+
f"Couldn't create channel. SuperLink connection '{connection.name}'"
|
|
486
|
+
" is missing address or insecure setting."
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
# Create the gRPC channel
|
|
490
|
+
channel = create_channel(
|
|
491
|
+
server_address=connection.address,
|
|
492
|
+
insecure=connection.insecure,
|
|
493
|
+
root_certificates=root_certificates_bytes,
|
|
494
|
+
max_message_length=GRPC_MAX_MESSAGE_LENGTH,
|
|
495
|
+
interceptors=[CliAccountAuthInterceptor(auth_plugin)],
|
|
496
|
+
)
|
|
497
|
+
channel.subscribe(on_channel_state_change)
|
|
498
|
+
return channel
|
|
499
|
+
|
|
500
|
+
|
|
410
501
|
@contextmanager
|
|
411
502
|
def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-branches
|
|
412
503
|
"""Context manager to handle specific gRPC errors.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Copyright 2026 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 SuperCore state components."""
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# State Entity Relationship Diagram
|
|
2
|
+
|
|
3
|
+
## Schema
|
|
4
|
+
|
|
5
|
+
```mermaid
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
config:
|
|
9
|
+
layout: elk
|
|
10
|
+
---
|
|
11
|
+
erDiagram
|
|
12
|
+
context {
|
|
13
|
+
INTEGER run_id FK "nullable"
|
|
14
|
+
BLOB context "nullable"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
logs {
|
|
18
|
+
INTEGER run_id FK "nullable"
|
|
19
|
+
VARCHAR log "nullable"
|
|
20
|
+
INTEGER node_id "nullable"
|
|
21
|
+
FLOAT timestamp "nullable"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
message_ins {
|
|
25
|
+
INTEGER run_id FK "nullable"
|
|
26
|
+
BLOB content "nullable"
|
|
27
|
+
FLOAT created_at "nullable"
|
|
28
|
+
VARCHAR delivered_at "nullable"
|
|
29
|
+
INTEGER dst_node_id "nullable"
|
|
30
|
+
BLOB error "nullable"
|
|
31
|
+
VARCHAR group_id "nullable"
|
|
32
|
+
VARCHAR message_id UK "nullable"
|
|
33
|
+
VARCHAR message_type "nullable"
|
|
34
|
+
VARCHAR reply_to_message_id "nullable"
|
|
35
|
+
INTEGER src_node_id "nullable"
|
|
36
|
+
FLOAT ttl "nullable"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
message_res {
|
|
40
|
+
INTEGER run_id FK "nullable"
|
|
41
|
+
BLOB content "nullable"
|
|
42
|
+
FLOAT created_at "nullable"
|
|
43
|
+
VARCHAR delivered_at "nullable"
|
|
44
|
+
INTEGER dst_node_id "nullable"
|
|
45
|
+
BLOB error "nullable"
|
|
46
|
+
VARCHAR group_id "nullable"
|
|
47
|
+
VARCHAR message_id UK "nullable"
|
|
48
|
+
VARCHAR message_type "nullable"
|
|
49
|
+
VARCHAR reply_to_message_id "nullable"
|
|
50
|
+
INTEGER src_node_id "nullable"
|
|
51
|
+
FLOAT ttl "nullable"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
node {
|
|
55
|
+
FLOAT heartbeat_interval "nullable"
|
|
56
|
+
VARCHAR last_activated_at "nullable"
|
|
57
|
+
VARCHAR last_deactivated_at "nullable"
|
|
58
|
+
INTEGER node_id UK "nullable"
|
|
59
|
+
TIMESTAMP online_until "nullable"
|
|
60
|
+
VARCHAR owner_aid "nullable"
|
|
61
|
+
VARCHAR owner_name "nullable"
|
|
62
|
+
BLOB public_key UK "nullable"
|
|
63
|
+
VARCHAR registered_at "nullable"
|
|
64
|
+
VARCHAR status "nullable"
|
|
65
|
+
VARCHAR unregistered_at "nullable"
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
object_children {
|
|
69
|
+
VARCHAR child_id PK,FK
|
|
70
|
+
VARCHAR parent_id PK,FK
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
objects {
|
|
74
|
+
VARCHAR object_id PK "nullable"
|
|
75
|
+
BLOB content "nullable"
|
|
76
|
+
INTEGER is_available
|
|
77
|
+
INTEGER ref_count
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
<<<<<<< HEAD
|
|
81
|
+
run {
|
|
82
|
+
INTEGER bytes_recv "nullable"
|
|
83
|
+
INTEGER bytes_sent "nullable"
|
|
84
|
+
FLOAT clientapp_runtime "nullable"
|
|
85
|
+
VARCHAR details "nullable"
|
|
86
|
+
VARCHAR fab_hash "nullable"
|
|
87
|
+
VARCHAR fab_id "nullable"
|
|
88
|
+
VARCHAR fab_version "nullable"
|
|
89
|
+
VARCHAR federation "nullable"
|
|
90
|
+
BLOB federation_options "nullable"
|
|
91
|
+
VARCHAR finished_at "nullable"
|
|
92
|
+
VARCHAR flwr_aid "nullable"
|
|
93
|
+
VARCHAR override_config "nullable"
|
|
94
|
+
VARCHAR pending_at "nullable"
|
|
95
|
+
INTEGER run_id UK "nullable"
|
|
96
|
+
VARCHAR running_at "nullable"
|
|
97
|
+
VARCHAR starting_at "nullable"
|
|
98
|
+
VARCHAR sub_status "nullable"
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
=======
|
|
102
|
+
>>>>>>> main
|
|
103
|
+
run_objects {
|
|
104
|
+
VARCHAR object_id PK,FK
|
|
105
|
+
INTEGER run_id PK
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
token_store {
|
|
109
|
+
INTEGER run_id PK "nullable"
|
|
110
|
+
FLOAT active_until "nullable"
|
|
111
|
+
VARCHAR token UK
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
<<<<<<< HEAD
|
|
115
|
+
run ||--o| context : run_id
|
|
116
|
+
run ||--o{ logs : run_id
|
|
117
|
+
run ||--o{ message_ins : run_id
|
|
118
|
+
run ||--o{ message_res : run_id
|
|
119
|
+
=======
|
|
120
|
+
>>>>>>> main
|
|
121
|
+
objects ||--o| object_children : parent_id
|
|
122
|
+
objects ||--o| object_children : child_id
|
|
123
|
+
objects ||--o| run_objects : object_id
|
|
124
|
+
|
|
125
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Copyright 2026 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 SQLAlchemy database schema."""
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Copyright 2026 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
|
+
"""SQLAlchemy Core Table definitions for CoreState."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from sqlalchemy import Column, Float, Integer, MetaData, String, Table
|
|
19
|
+
|
|
20
|
+
corestate_metadata = MetaData()
|
|
21
|
+
|
|
22
|
+
# ------------------------------------------------------------------------------
|
|
23
|
+
# Table: token_store
|
|
24
|
+
# ------------------------------------------------------------------------------
|
|
25
|
+
token_store = Table(
|
|
26
|
+
"token_store",
|
|
27
|
+
corestate_metadata,
|
|
28
|
+
Column("run_id", Integer, primary_key=True, nullable=True),
|
|
29
|
+
Column("token", String, unique=True, nullable=False),
|
|
30
|
+
Column("active_until", Float),
|
|
31
|
+
)
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Copyright 2026 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
|
+
"""SQLAlchemy Core Table definitions for LinkState."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from sqlalchemy import (
|
|
19
|
+
TIMESTAMP,
|
|
20
|
+
Column,
|
|
21
|
+
Float,
|
|
22
|
+
ForeignKey,
|
|
23
|
+
Index,
|
|
24
|
+
Integer,
|
|
25
|
+
LargeBinary,
|
|
26
|
+
MetaData,
|
|
27
|
+
String,
|
|
28
|
+
Table,
|
|
29
|
+
UniqueConstraint,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
linkstate_metadata = MetaData()
|
|
33
|
+
|
|
34
|
+
# ------------------------------------------------------------------------------
|
|
35
|
+
# Table: node
|
|
36
|
+
# ------------------------------------------------------------------------------
|
|
37
|
+
node = Table(
|
|
38
|
+
"node",
|
|
39
|
+
linkstate_metadata,
|
|
40
|
+
Column("node_id", Integer, unique=True),
|
|
41
|
+
Column("owner_aid", String),
|
|
42
|
+
Column("owner_name", String),
|
|
43
|
+
Column("status", String),
|
|
44
|
+
Column("registered_at", String),
|
|
45
|
+
Column("last_activated_at", String, nullable=True),
|
|
46
|
+
Column("last_deactivated_at", String, nullable=True),
|
|
47
|
+
Column("unregistered_at", String, nullable=True),
|
|
48
|
+
Column("online_until", TIMESTAMP, nullable=True),
|
|
49
|
+
Column("heartbeat_interval", Float),
|
|
50
|
+
Column("public_key", LargeBinary, unique=True),
|
|
51
|
+
# Indexes
|
|
52
|
+
# Used in delete_node and get_node_info (security/filtering)
|
|
53
|
+
Index("idx_node_owner_aid", "owner_aid"),
|
|
54
|
+
# Used in get_nodes and activation checks (frequent filtering)
|
|
55
|
+
Index("idx_node_status", "status"),
|
|
56
|
+
# Used in heartbeat checks to efficiently find expired nodes
|
|
57
|
+
Index("idx_online_until", "online_until"),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# ------------------------------------------------------------------------------
|
|
62
|
+
# Table: run
|
|
63
|
+
# ------------------------------------------------------------------------------
|
|
64
|
+
run = Table(
|
|
65
|
+
"run",
|
|
66
|
+
linkstate_metadata,
|
|
67
|
+
Column("run_id", Integer, unique=True),
|
|
68
|
+
Column("fab_id", String),
|
|
69
|
+
Column("fab_version", String),
|
|
70
|
+
Column("fab_hash", String),
|
|
71
|
+
Column("override_config", String),
|
|
72
|
+
Column("pending_at", String),
|
|
73
|
+
Column("starting_at", String),
|
|
74
|
+
Column("running_at", String),
|
|
75
|
+
Column("finished_at", String),
|
|
76
|
+
Column("sub_status", String),
|
|
77
|
+
Column("details", String),
|
|
78
|
+
Column("federation", String),
|
|
79
|
+
Column("federation_options", LargeBinary),
|
|
80
|
+
Column("flwr_aid", String),
|
|
81
|
+
Column("bytes_sent", Integer, server_default="0"),
|
|
82
|
+
Column("bytes_recv", Integer, server_default="0"),
|
|
83
|
+
Column("clientapp_runtime", Float, server_default="0.0"),
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# ------------------------------------------------------------------------------
|
|
88
|
+
# Table: logs
|
|
89
|
+
# ------------------------------------------------------------------------------
|
|
90
|
+
logs = Table(
|
|
91
|
+
"logs",
|
|
92
|
+
linkstate_metadata,
|
|
93
|
+
Column("timestamp", Float),
|
|
94
|
+
Column("run_id", Integer, ForeignKey("run.run_id")),
|
|
95
|
+
Column("node_id", Integer),
|
|
96
|
+
Column("log", String),
|
|
97
|
+
# Composite PK
|
|
98
|
+
UniqueConstraint("timestamp", "run_id", "node_id"),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# ------------------------------------------------------------------------------
|
|
103
|
+
# Table: context
|
|
104
|
+
# ------------------------------------------------------------------------------
|
|
105
|
+
context = Table(
|
|
106
|
+
"context",
|
|
107
|
+
linkstate_metadata,
|
|
108
|
+
Column("run_id", Integer, ForeignKey("run.run_id"), unique=True),
|
|
109
|
+
Column("context", LargeBinary),
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# ------------------------------------------------------------------------------
|
|
114
|
+
# Table: message_ins
|
|
115
|
+
# ------------------------------------------------------------------------------
|
|
116
|
+
message_ins = Table(
|
|
117
|
+
"message_ins",
|
|
118
|
+
linkstate_metadata,
|
|
119
|
+
Column("message_id", String, unique=True),
|
|
120
|
+
Column("group_id", String),
|
|
121
|
+
Column("run_id", Integer, ForeignKey("run.run_id")),
|
|
122
|
+
Column("src_node_id", Integer),
|
|
123
|
+
Column("dst_node_id", Integer),
|
|
124
|
+
Column("reply_to_message_id", String),
|
|
125
|
+
Column("created_at", Float),
|
|
126
|
+
Column("delivered_at", String),
|
|
127
|
+
Column("ttl", Float),
|
|
128
|
+
Column("message_type", String),
|
|
129
|
+
Column("content", LargeBinary, nullable=True),
|
|
130
|
+
Column("error", LargeBinary, nullable=True),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# ------------------------------------------------------------------------------
|
|
135
|
+
# Table: message_res
|
|
136
|
+
# ------------------------------------------------------------------------------
|
|
137
|
+
message_res = Table(
|
|
138
|
+
"message_res",
|
|
139
|
+
linkstate_metadata,
|
|
140
|
+
Column("message_id", String, unique=True),
|
|
141
|
+
Column("group_id", String),
|
|
142
|
+
Column("run_id", Integer, ForeignKey("run.run_id")),
|
|
143
|
+
Column("src_node_id", Integer),
|
|
144
|
+
Column("dst_node_id", Integer),
|
|
145
|
+
Column("reply_to_message_id", String),
|
|
146
|
+
Column("created_at", Float),
|
|
147
|
+
Column("delivered_at", String),
|
|
148
|
+
Column("ttl", Float),
|
|
149
|
+
Column("message_type", String),
|
|
150
|
+
Column("content", LargeBinary, nullable=True),
|
|
151
|
+
Column("error", LargeBinary, nullable=True),
|
|
152
|
+
)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Copyright 2026 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
|
+
"""SQLAlchemy Core Table definitions for ObjectStore."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from sqlalchemy import (
|
|
19
|
+
CheckConstraint,
|
|
20
|
+
Column,
|
|
21
|
+
ForeignKey,
|
|
22
|
+
Integer,
|
|
23
|
+
LargeBinary,
|
|
24
|
+
MetaData,
|
|
25
|
+
PrimaryKeyConstraint,
|
|
26
|
+
String,
|
|
27
|
+
Table,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
objectstore_metadata = MetaData()
|
|
31
|
+
|
|
32
|
+
# ------------------------------------------------------------------------------
|
|
33
|
+
# Table: objects
|
|
34
|
+
# ------------------------------------------------------------------------------
|
|
35
|
+
objects = Table(
|
|
36
|
+
"objects",
|
|
37
|
+
objectstore_metadata,
|
|
38
|
+
Column("object_id", String, primary_key=True, nullable=True),
|
|
39
|
+
Column("content", LargeBinary),
|
|
40
|
+
Column(
|
|
41
|
+
"is_available",
|
|
42
|
+
Integer,
|
|
43
|
+
nullable=False,
|
|
44
|
+
server_default="0",
|
|
45
|
+
),
|
|
46
|
+
Column("ref_count", Integer, nullable=False, server_default="0"),
|
|
47
|
+
CheckConstraint("is_available IN (0, 1)", name="ck_objects_is_available"),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# ------------------------------------------------------------------------------
|
|
51
|
+
# Table: object_children
|
|
52
|
+
# ------------------------------------------------------------------------------
|
|
53
|
+
object_children = Table(
|
|
54
|
+
"object_children",
|
|
55
|
+
objectstore_metadata,
|
|
56
|
+
Column(
|
|
57
|
+
"parent_id",
|
|
58
|
+
String,
|
|
59
|
+
ForeignKey("objects.object_id", ondelete="CASCADE"),
|
|
60
|
+
nullable=False,
|
|
61
|
+
),
|
|
62
|
+
Column(
|
|
63
|
+
"child_id",
|
|
64
|
+
String,
|
|
65
|
+
ForeignKey("objects.object_id", ondelete="CASCADE"),
|
|
66
|
+
nullable=False,
|
|
67
|
+
),
|
|
68
|
+
PrimaryKeyConstraint("parent_id", "child_id"),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# ------------------------------------------------------------------------------
|
|
72
|
+
# Table: run_objects
|
|
73
|
+
# ------------------------------------------------------------------------------
|
|
74
|
+
run_objects = Table(
|
|
75
|
+
"run_objects",
|
|
76
|
+
objectstore_metadata,
|
|
77
|
+
Column("run_id", Integer, nullable=False),
|
|
78
|
+
Column(
|
|
79
|
+
"object_id",
|
|
80
|
+
String,
|
|
81
|
+
ForeignKey("objects.object_id", ondelete="CASCADE"),
|
|
82
|
+
nullable=False,
|
|
83
|
+
),
|
|
84
|
+
PrimaryKeyConstraint("run_id", "object_id"),
|
|
85
|
+
)
|
{flwr_nightly-1.26.0.dev20260115.dist-info → flwr_nightly-1.26.0.dev20260117.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.dev20260117
|
|
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.26.0.dev20260115.dist-info → flwr_nightly-1.26.0.dev20260117.dist-info}/RECORD
RENAMED
|
@@ -18,12 +18,13 @@ flwr/cli/build.py,sha256=A9lzUNuGqLNId6PQppSWSlbMACl_LeW5Ispp9DuuX6k,10383
|
|
|
18
18
|
flwr/cli/cli_account_auth_interceptor.py,sha256=mXgxThpZjU_2Xlae9xT8ewOw60GeE64comDd57asLIY,3680
|
|
19
19
|
flwr/cli/config/__init__.py,sha256=46z6whA3hvKkl9APRs-UG7Ym3K9VOqKx_pYcgelRjtE,788
|
|
20
20
|
flwr/cli/config/ls.py,sha256=ktRga1KWt4IDK5TQNT6ixfP66zcspkf-F2j3CkkpsGo,3665
|
|
21
|
-
flwr/cli/
|
|
21
|
+
flwr/cli/config_migration.py,sha256=Jr9HMyWGS3xeNf9SELuJhPl7NhtfJMHlw37yG4r4jw4,8169
|
|
22
|
+
flwr/cli/config_utils.py,sha256=oxny7Gx2crsBYwpgcQ3OBw3hBE-o1oQNAg0o7UBB4u8,13015
|
|
22
23
|
flwr/cli/constant.py,sha256=b5WsK_KXg9yDDk2lxEiN7rggsnZYNu1fiBC9NJVnnNs,3291
|
|
23
24
|
flwr/cli/example.py,sha256=SNTorkKPrx1rOryGREUyZu8TcOc1-vFv1zEddaysdY0,2216
|
|
24
25
|
flwr/cli/federation/__init__.py,sha256=okxswL4fAjApI9gV_alU1lRkTUcQRbwlzvtUTLz61fE,793
|
|
25
26
|
flwr/cli/federation/ls.py,sha256=dGNw6quUAbUmqTNIVAPgJ9UgCJddITjKr48ETXQ9LTE,11581
|
|
26
|
-
flwr/cli/flower_config.py,sha256=
|
|
27
|
+
flwr/cli/flower_config.py,sha256=Rfw7ThkIdNiEfU-MsPdV8PFxCRcB8gx4NWcWtWJFXeM,15741
|
|
27
28
|
flwr/cli/install.py,sha256=yhgUWd_Psyc18DaoQHEQ5Ji45ZQ-3LclueM9uRxUEPw,10063
|
|
28
29
|
flwr/cli/log.py,sha256=ZWveENLdDC0Dm10YIMcc3qdGV1aO-2YZXyHBhnB6ofI,7848
|
|
29
30
|
flwr/cli/login/__init__.py,sha256=B1SXKU3HCQhWfFDMJhlC7FOl8UsvH4mxysxeBnrfyUE,800
|
|
@@ -40,8 +41,8 @@ flwr/cli/supernode/__init__.py,sha256=DBkjoPo2hS2Y-ghJxwLbrAbCQFBgD82_Itl2_892UB
|
|
|
40
41
|
flwr/cli/supernode/ls.py,sha256=LNx3C9-Vfmw1ffVPDmjmex8TD_e0sTAXEC97aqg4Lqk,8958
|
|
41
42
|
flwr/cli/supernode/register.py,sha256=2aQTT2wEp2pxxuIacC6FQkN8q_QKifZhWjD7LNw1R2A,6527
|
|
42
43
|
flwr/cli/supernode/unregister.py,sha256=PXugcJJrM7cP3HbULjgcO22DciDP2uWN7YNgce4hv5E,4632
|
|
43
|
-
flwr/cli/typing.py,sha256=
|
|
44
|
-
flwr/cli/utils.py,sha256=
|
|
44
|
+
flwr/cli/typing.py,sha256=QMph442kkoXjJoubevuYueMNGcuCJo29ekmrtiYzPK8,6108
|
|
45
|
+
flwr/cli/utils.py,sha256=pQtnKvMKGan-GaiKWTo98Oi4F6RW1ItqHaCgKEQuRvU,23348
|
|
45
46
|
flwr/client/__init__.py,sha256=xwkPJfdeWxIIfmiPE5vnmnY_JbTlErP0Qs9eBP6qRFg,1252
|
|
46
47
|
flwr/client/client.py,sha256=3HAchxvknKG9jYbB7swNyDj-e5vUWDuMKoLvbT7jCVM,7895
|
|
47
48
|
flwr/client/dpfedavg_numpy_client.py,sha256=ELDHyEJcTB-FlLhHC-JXy8HuB3ZFHfT0HL3g1VSWY5w,7451
|
|
@@ -352,6 +353,12 @@ flwr/supercore/primitives/__init__.py,sha256=Tx8GOjnmMo8Y74RsDGrMpfr-E0Nl8dcUDF7
|
|
|
352
353
|
flwr/supercore/primitives/asymmetric.py,sha256=1643niHYj3uEbfPd06VuMHwN3tKVwg0uVyR3RhTdWIU,3778
|
|
353
354
|
flwr/supercore/primitives/asymmetric_ed25519.py,sha256=eIhOTMibQW0FJX4MXdplHdL3HcfCiKuFu2mQ8GQTUz8,5025
|
|
354
355
|
flwr/supercore/sqlite_mixin.py,sha256=ZvtqDLRwCMzyrPsOTpAwJeHSpthrrtvEfUmWZmss6OA,5317
|
|
356
|
+
flwr/supercore/state/__init__.py,sha256=FkKhsNVM4LjlRlOgXTz6twINmw5ohIUKS_OER0BNo_w,724
|
|
357
|
+
flwr/supercore/state/schema/README.md,sha256=-0QrXDhnv30gEYoIUJo7aLUolY0r_t_nC24yk7B2agM,2892
|
|
358
|
+
flwr/supercore/state/schema/__init__.py,sha256=Egnde6OY01wrpT4PuhL4NGn_NY4jdAH7kf7ktagN4Ws,724
|
|
359
|
+
flwr/supercore/state/schema/corestate_tables.py,sha256=EEVz9lMCFES8y87nTTmU8X6sH7_FIlGZQ_3rUo0JI6Y,1252
|
|
360
|
+
flwr/supercore/state/schema/linkstate_tables.py,sha256=pJ0y-83TYTBJzkcxd6a9CJiNCk72eW7pB91Awg2nwuk,5186
|
|
361
|
+
flwr/supercore/state/schema/objectstore_tables.py,sha256=cCayjB9J1k9j4mcCQnl6Y-U68c__DMg_DUQxtaZRnpE,2637
|
|
355
362
|
flwr/supercore/superexec/__init__.py,sha256=XKX208hZ6a9gZ4KT9kMqfpCtp_8VGxekzKFfHSu2esQ,707
|
|
356
363
|
flwr/supercore/superexec/plugin/__init__.py,sha256=GNwq8uNdE8RB7ywEFRAvKjLFzgS3YXgz39-HBGsemWw,1035
|
|
357
364
|
flwr/supercore/superexec/plugin/base_exec_plugin.py,sha256=QmTr7AJTZO1cfmoVRjZUEhSlxNCou-XotGFAXkCqwWQ,2043
|
|
@@ -392,7 +399,7 @@ flwr/supernode/servicer/__init__.py,sha256=lucTzre5WPK7G1YLCfaqg3rbFWdNSb7ZTt-ca
|
|
|
392
399
|
flwr/supernode/servicer/clientappio/__init__.py,sha256=7Oy62Y_oijqF7Dxi6tpcUQyOpLc_QpIRZ83NvwmB0Yg,813
|
|
393
400
|
flwr/supernode/servicer/clientappio/clientappio_servicer.py,sha256=rRL4CQ0L78jF_p0ct4-JMGREt6wWRy__wy4czF4f54Y,11639
|
|
394
401
|
flwr/supernode/start_client_internal.py,sha256=BYk69UBQ2gQJaDQxXhccUgfOWrb7ShAstrbcMOCZIIs,26173
|
|
395
|
-
flwr_nightly-1.26.0.
|
|
396
|
-
flwr_nightly-1.26.0.
|
|
397
|
-
flwr_nightly-1.26.0.
|
|
398
|
-
flwr_nightly-1.26.0.
|
|
402
|
+
flwr_nightly-1.26.0.dev20260117.dist-info/METADATA,sha256=IuT7FnpFScBkGOQJlpTXFUh3_kSZyt1HKQ0yZ7gjKds,14398
|
|
403
|
+
flwr_nightly-1.26.0.dev20260117.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
404
|
+
flwr_nightly-1.26.0.dev20260117.dist-info/entry_points.txt,sha256=hxHD2ixb_vJFDOlZV-zB4Ao32_BQlL34ftsDh1GXv14,420
|
|
405
|
+
flwr_nightly-1.26.0.dev20260117.dist-info/RECORD,,
|
{flwr_nightly-1.26.0.dev20260115.dist-info → flwr_nightly-1.26.0.dev20260117.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|