flwr-nightly 1.26.0.dev20260126__py3-none-any.whl → 1.26.0.dev20260128__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. flwr/cli/app.py +0 -2
  2. flwr/cli/config/ls.py +1 -1
  3. flwr/cli/config_migration.py +4 -4
  4. flwr/cli/constant.py +0 -3
  5. flwr/cli/federation/ls.py +1 -1
  6. flwr/cli/flower_config.py +0 -4
  7. flwr/cli/login/login.py +0 -8
  8. flwr/cli/ls.py +1 -1
  9. flwr/cli/supernode/ls.py +1 -1
  10. flwr/cli/typing.py +10 -24
  11. flwr/cli/utils.py +7 -3
  12. flwr/common/exit/exit_code.py +6 -0
  13. flwr/common/typing.py +2 -0
  14. flwr/proto/federation_pb2.py +5 -3
  15. flwr/proto/federation_pb2.pyi +23 -1
  16. flwr/server/app.py +4 -11
  17. flwr/server/superlink/fleet/message_handler/message_handler.py +2 -1
  18. flwr/supercore/address.py +9 -0
  19. flwr/supercore/app_utils.py +2 -1
  20. flwr/supercore/constant.py +3 -0
  21. flwr/supercore/sql_mixin.py +2 -2
  22. flwr/supercore/state/alembic/__init__.py +15 -0
  23. flwr/supercore/state/alembic/env.py +81 -0
  24. flwr/supercore/state/alembic/script.py.mako +43 -0
  25. flwr/supercore/state/alembic/utils.py +49 -0
  26. flwr/supercore/state/alembic/versions/__init__.py +15 -0
  27. flwr/supercore/state/alembic/versions/rev_2026_01_28_initialize_migration_of_state_tables.py +200 -0
  28. flwr/supercore/state/schema/README.md +2 -6
  29. flwr/supercore/utils.py +21 -0
  30. flwr/superlink/federation/noop_federation_manager.py +3 -1
  31. flwr/supernode/start_client_internal.py +5 -2
  32. {flwr_nightly-1.26.0.dev20260126.dist-info → flwr_nightly-1.26.0.dev20260128.dist-info}/METADATA +2 -1
  33. {flwr_nightly-1.26.0.dev20260126.dist-info → flwr_nightly-1.26.0.dev20260128.dist-info}/RECORD +35 -29
  34. {flwr_nightly-1.26.0.dev20260126.dist-info → flwr_nightly-1.26.0.dev20260128.dist-info}/WHEEL +0 -0
  35. {flwr_nightly-1.26.0.dev20260126.dist-info → flwr_nightly-1.26.0.dev20260128.dist-info}/entry_points.txt +0 -0
flwr/cli/app.py CHANGED
@@ -27,7 +27,6 @@ from .app_cmd import review as app_review
27
27
  from .build import build
28
28
  from .config import ls as config_list
29
29
  from .federation import ls as federation_list
30
- from .flower_config import init_flwr_config
31
30
  from .install import install
32
31
  from .log import log
33
32
  from .login import login
@@ -115,7 +114,6 @@ def main(
115
114
  ),
116
115
  ) -> None:
117
116
  """Flower CLI."""
118
- init_flwr_config()
119
117
  if version:
120
118
  typer.secho(f"Flower version: {package_version}", fg="blue")
121
119
  raise typer.Exit()
flwr/cli/config/ls.py CHANGED
@@ -40,7 +40,7 @@ def ls(
40
40
  ),
41
41
  ] = CliOutputFormat.DEFAULT,
42
42
  ) -> None:
43
- """List all SuperLink connections."""
43
+ """List all SuperLink connections (alias: ls)."""
44
44
  suppress_output = output_format == CliOutputFormat.JSON
45
45
  captured_output = io.StringIO()
46
46
  config_path = None
@@ -31,7 +31,7 @@ from .flower_config import (
31
31
  )
32
32
 
33
33
  CONFIG_MIGRATION_NOTICE = """
34
- ##################################################################
34
+ #######################################################################
35
35
  # CONFIGURATION MIGRATION NOTICE:
36
36
  #
37
37
  # What was previously called "federation config" for SuperLink
@@ -43,8 +43,8 @@ CONFIG_MIGRATION_NOTICE = """
43
43
  # The entries below are commented out intentionally and are kept
44
44
  # only as a migration reference.
45
45
  #
46
- # Docs: <link to Flower config docs>
47
- ##################################################################
46
+ # Docs: https://flower.ai/docs/framework/ref-flower-configuration.html
47
+ #######################################################################
48
48
 
49
49
  """
50
50
 
@@ -53,7 +53,7 @@ CLI_NOTICE = (
53
53
  + "We detected legacy usage of this command that relies on connection\n"
54
54
  + "settings from your pyproject.toml.\n\n"
55
55
  + "Flower will migrate any relevant settings to the new Flower config.\n\n"
56
- + "Learn more: https://flower.ai/docs\n"
56
+ + "Learn more: https://flower.ai/docs/framework/ref-flower-configuration.html\n"
57
57
  )
58
58
 
59
59
 
flwr/cli/constant.py CHANGED
@@ -57,7 +57,6 @@ class SuperLinkConnectionTomlKey:
57
57
  ADDRESS = "address"
58
58
  ROOT_CERTIFICATES = "root-certificates"
59
59
  INSECURE = "insecure"
60
- ENABLE_ACCOUNT_AUTH = "enable-account-auth"
61
60
  FEDERATION = "federation"
62
61
  OPTIONS = "options"
63
62
 
@@ -102,8 +101,6 @@ default = "local"
102
101
 
103
102
  [superlink.supergrid]
104
103
  address = "{SUPERGRID_ADDRESS}"
105
- enable-account-auth = true
106
- federation = "YOUR-FEDERATION-HERE"
107
104
 
108
105
  [superlink.local]
109
106
  options.num-supernodes = 10
flwr/cli/federation/ls.py CHANGED
@@ -68,7 +68,7 @@ def ls( # pylint: disable=R0914, R0913, R0917, R0912
68
68
  ),
69
69
  ] = None,
70
70
  ) -> None:
71
- """List available federations."""
71
+ """List available federations or details of a specific federation (alias: ls)."""
72
72
  suppress_output = output_format == CliOutputFormat.JSON
73
73
  captured_output = io.StringIO()
74
74
 
flwr/cli/flower_config.py CHANGED
@@ -195,9 +195,6 @@ def parse_superlink_connection(
195
195
  address=conn_dict.get(SuperLinkConnectionTomlKey.ADDRESS),
196
196
  root_certificates=conn_dict.get(SuperLinkConnectionTomlKey.ROOT_CERTIFICATES),
197
197
  insecure=conn_dict.get(SuperLinkConnectionTomlKey.INSECURE),
198
- enable_account_auth=conn_dict.get(
199
- SuperLinkConnectionTomlKey.ENABLE_ACCOUNT_AUTH
200
- ),
201
198
  federation=conn_dict.get(SuperLinkConnectionTomlKey.FEDERATION),
202
199
  options=simulation_options,
203
200
  )
@@ -221,7 +218,6 @@ def serialize_superlink_connection(connection: SuperLinkConnection) -> dict[str,
221
218
  SuperLinkConnectionTomlKey.ADDRESS: connection.address,
222
219
  SuperLinkConnectionTomlKey.ROOT_CERTIFICATES: connection.root_certificates,
223
220
  SuperLinkConnectionTomlKey.INSECURE: connection._insecure,
224
- SuperLinkConnectionTomlKey.ENABLE_ACCOUNT_AUTH: connection._enable_account_auth,
225
221
  SuperLinkConnectionTomlKey.FEDERATION: connection.federation,
226
222
  }
227
223
  # Remove None values
flwr/cli/login/login.py CHANGED
@@ -61,14 +61,6 @@ def login(
61
61
  superlink_connection = read_superlink_connection(superlink)
62
62
  superlink = superlink_connection.name
63
63
 
64
- # Check if `enable-account-auth` is set to `true`
65
- if not superlink_connection.enable_account_auth:
66
- raise click.ClickException(
67
- "Account authentication is not enabled for the SuperLink connection "
68
- f"'{superlink}'. To enable it, set `enable-account-auth = true` "
69
- "in the configuration."
70
- )
71
-
72
64
  # Check if insecure flag is set to `True`
73
65
  if superlink_connection.insecure:
74
66
  raise click.ClickException(
flwr/cli/ls.py CHANGED
@@ -79,7 +79,7 @@ def ls( # pylint: disable=too-many-locals, too-many-branches, R0913, R0917
79
79
  ),
80
80
  ] = CliOutputFormat.DEFAULT,
81
81
  ) -> None:
82
- """List the details of one provided run ID or all runs in a Flower federation.
82
+ """List the details of one provided run ID or all runs (alias: ls).
83
83
 
84
84
  The following details are displayed:
85
85
 
flwr/cli/supernode/ls.py CHANGED
@@ -66,7 +66,7 @@ def ls( # pylint: disable=R0914, R0913, R0917
66
66
  ),
67
67
  ] = False,
68
68
  ) -> None:
69
- """List SuperNodes in the federation."""
69
+ """List SuperNodes in the federation (alias: ls)."""
70
70
  suppress_output = output_format == CliOutputFormat.JSON
71
71
  captured_output = io.StringIO()
72
72
 
flwr/cli/typing.py CHANGED
@@ -22,6 +22,7 @@ from flwr.cli.constant import (
22
22
  DEFAULT_SIMULATION_BACKEND_NAME,
23
23
  SuperLinkConnectionTomlKey,
24
24
  )
25
+ from flwr.supercore.utils import check_federation_format
25
26
 
26
27
  _ERROR_MSG_FMT = "SuperLinkConnection.%s is None"
27
28
 
@@ -106,8 +107,6 @@ class SuperLinkConnection:
106
107
  insecure : bool (default: False)
107
108
  Whether to use an insecure channel. If True, the
108
109
  connection will not use TLS encryption.
109
- enable_account_auth : bool (default: False)
110
- Whether to enable account authentication.
111
110
  federation : str
112
111
  The name of the federation to interface with.
113
112
  options : SuperLinkSimulationOptions
@@ -118,7 +117,6 @@ class SuperLinkConnection:
118
117
  address: str | None = None
119
118
  root_certificates: str | None = None
120
119
  _insecure: bool | None = None
121
- _enable_account_auth: bool | None = None
122
120
  federation: str | None = None
123
121
  options: SuperLinkSimulationOptions | None = None
124
122
 
@@ -129,7 +127,6 @@ class SuperLinkConnection:
129
127
  address: str | None = None,
130
128
  root_certificates: str | None = None,
131
129
  insecure: bool | None = None,
132
- enable_account_auth: bool | None = None,
133
130
  federation: str | None = None,
134
131
  options: SuperLinkSimulationOptions | None = None,
135
132
  ) -> None:
@@ -137,7 +134,6 @@ class SuperLinkConnection:
137
134
  self.address = address
138
135
  self.root_certificates = root_certificates
139
136
  self._insecure = insecure
140
- self._enable_account_auth = enable_account_auth
141
137
  self.federation = federation
142
138
  self.options = options
143
139
 
@@ -150,13 +146,6 @@ class SuperLinkConnection:
150
146
  return False
151
147
  return self._insecure
152
148
 
153
- @property
154
- def enable_account_auth(self) -> bool:
155
- """Return the enable_account_auth flag or its default (False) if unset."""
156
- if self._enable_account_auth is None:
157
- return False
158
- return self._enable_account_auth
159
-
160
149
  def __post_init__(self) -> None:
161
150
  """Validate SuperLink connection configuration."""
162
151
  err_prefix = f"Invalid value for key '%s' in connection '{self.name}': "
@@ -187,19 +176,16 @@ class SuperLinkConnection:
187
176
  err_prefix % SuperLinkConnectionTomlKey.INSECURE
188
177
  + f"expected bool, but got {type(self._insecure).__name__}."
189
178
  )
190
- if self._enable_account_auth is not None and not isinstance(
191
- self._enable_account_auth, bool
192
- ):
193
- raise ValueError(
194
- err_prefix % SuperLinkConnectionTomlKey.ENABLE_ACCOUNT_AUTH
195
- + f"expected bool, but got {type(self._enable_account_auth).__name__}."
196
- )
197
179
 
198
- if self.federation is not None and not isinstance(self.federation, str):
199
- raise ValueError(
200
- err_prefix % SuperLinkConnectionTomlKey.FEDERATION
201
- + f"expected str, but got {type(self.federation).__name__}."
202
- )
180
+ if self.federation is not None:
181
+ if not isinstance(self.federation, str):
182
+ raise ValueError(
183
+ err_prefix % SuperLinkConnectionTomlKey.FEDERATION
184
+ + f"expected str, but got {type(self.federation).__name__}."
185
+ )
186
+
187
+ # Check if the federation string is valid
188
+ check_federation_format(self.federation)
203
189
 
204
190
  # The connection needs to have either an address or options (or both).
205
191
  if self.address is None and self.options is None:
flwr/cli/utils.py CHANGED
@@ -241,7 +241,9 @@ def require_superlink_address(connection: SuperLinkConnection) -> str:
241
241
  cmd = click.get_current_context().command.name
242
242
  raise click.ClickException(
243
243
  f"`flwr {cmd}` currently works with a SuperLink. Ensure that the "
244
- "correct SuperLink (Control API) address is provided in `pyproject.toml`."
244
+ "correct SuperLink (Control API) address is provided SuperLink connection "
245
+ "you are using. Check your Flower configuration file. You may use `flwr "
246
+ "config list` to see its location in the file system."
245
247
  )
246
248
  return connection.address
247
249
 
@@ -326,8 +328,10 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-b
326
328
  raise click.ClickException(
327
329
  "The SuperLink cannot process this request. Please verify that "
328
330
  "you set the address to its Control API endpoint correctly in your "
329
- "`pyproject.toml`, and ensure that the Flower versions used by "
330
- "the CLI and SuperLink are compatible."
331
+ "SuperLink connection in your Flower Configuration file. You may use "
332
+ "`flwr config list` to see its location in the file system. "
333
+ "Additonally, ensure that the Flower versions used by the CLI and "
334
+ "SuperLink are compatible."
331
335
  ) from None
332
336
  if e.code() == grpc.StatusCode.PERMISSION_DENIED:
333
337
  # pylint: disable-next=E1101
@@ -33,6 +33,7 @@ class ExitCode:
33
33
  SUPERLINK_LICENSE_MISSING = 102
34
34
  SUPERLINK_LICENSE_URL_INVALID = 103
35
35
  SUPERLINK_INVALID_ARGS = 104
36
+ SUPERLINK_DATABASE_SCHEMA_MISMATCH = 105
36
37
 
37
38
  # ServerApp-specific exit codes (200-299)
38
39
  SERVERAPP_STRATEGY_PRECONDITION_UNMET = 200
@@ -91,6 +92,11 @@ EXIT_CODE_HELP = {
91
92
  "Invalid arguments provided to SuperLink. Use `--help` check for the correct "
92
93
  "usage. Alternatively, check the documentation."
93
94
  ),
95
+ ExitCode.SUPERLINK_DATABASE_SCHEMA_MISMATCH: (
96
+ "The database schema does not match the expected schema for this version of "
97
+ "SuperLink. Please refer to the documentation for guidance on how to resolve "
98
+ "this issue."
99
+ ),
94
100
  # ServerApp-specific exit codes (200-299)
95
101
  ExitCode.SERVERAPP_STRATEGY_PRECONDITION_UNMET: (
96
102
  "The strategy received replies that cannot be aggregated. Please ensure all "
flwr/common/typing.py CHANGED
@@ -24,6 +24,7 @@ import numpy as np
24
24
  import numpy.typing as npt
25
25
 
26
26
  from flwr.app.user_config import UserConfig
27
+ from flwr.proto.federation_pb2 import Account # pylint: disable=E0611
27
28
  from flwr.proto.node_pb2 import NodeInfo # pylint: disable=E0611
28
29
 
29
30
  NDArray = npt.NDArray[Any]
@@ -343,5 +344,6 @@ class Federation:
343
344
 
344
345
  name: str
345
346
  member_aids: list[str]
347
+ accounts: list[Account]
346
348
  nodes: list[NodeInfo]
347
349
  runs: list[Run]
@@ -26,13 +26,15 @@ from flwr.proto import run_pb2 as flwr_dot_proto_dot_run__pb2
26
26
  from flwr.proto import node_pb2 as flwr_dot_proto_dot_node__pb2
27
27
 
28
28
 
29
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1b\x66lwr/proto/federation.proto\x12\nflwr.proto\x1a\x14\x66lwr/proto/run.proto\x1a\x15\x66lwr/proto/node.proto\"s\n\nFederation\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0bmember_aids\x18\x02 \x03(\t\x12#\n\x05nodes\x18\x03 \x03(\x0b\x32\x14.flwr.proto.NodeInfo\x12\x1d\n\x04runs\x18\x04 \x03(\x0b\x32\x0f.flwr.proto.Runb\x06proto3')
29
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1b\x66lwr/proto/federation.proto\x12\nflwr.proto\x1a\x14\x66lwr/proto/run.proto\x1a\x15\x66lwr/proto/node.proto\"#\n\x07\x41\x63\x63ount\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\"\x9a\x01\n\nFederation\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0bmember_aids\x18\x02 \x03(\t\x12%\n\x08\x61\x63\x63ounts\x18\x03 \x03(\x0b\x32\x13.flwr.proto.Account\x12#\n\x05nodes\x18\x04 \x03(\x0b\x32\x14.flwr.proto.NodeInfo\x12\x1d\n\x04runs\x18\x05 \x03(\x0b\x32\x0f.flwr.proto.Runb\x06proto3')
30
30
 
31
31
  _globals = globals()
32
32
  _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
33
33
  _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'flwr.proto.federation_pb2', _globals)
34
34
  if not _descriptor._USE_C_DESCRIPTORS:
35
35
  DESCRIPTOR._loaded_options = None
36
- _globals['_FEDERATION']._serialized_start=88
37
- _globals['_FEDERATION']._serialized_end=203
36
+ _globals['_ACCOUNT']._serialized_start=88
37
+ _globals['_ACCOUNT']._serialized_end=123
38
+ _globals['_FEDERATION']._serialized_start=126
39
+ _globals['_FEDERATION']._serialized_end=280
38
40
  # @@protoc_insertion_point(module_scope)
@@ -28,18 +28,39 @@ import typing
28
28
 
29
29
  DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
30
30
 
31
+ @typing.final
32
+ class Account(google.protobuf.message.Message):
33
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
34
+
35
+ ID_FIELD_NUMBER: builtins.int
36
+ NAME_FIELD_NUMBER: builtins.int
37
+ id: builtins.str
38
+ name: builtins.str
39
+ def __init__(
40
+ self,
41
+ *,
42
+ id: builtins.str = ...,
43
+ name: builtins.str = ...,
44
+ ) -> None: ...
45
+ def ClearField(self, field_name: typing.Literal["id", b"id", "name", b"name"]) -> None: ...
46
+
47
+ global___Account = Account
48
+
31
49
  @typing.final
32
50
  class Federation(google.protobuf.message.Message):
33
51
  DESCRIPTOR: google.protobuf.descriptor.Descriptor
34
52
 
35
53
  NAME_FIELD_NUMBER: builtins.int
36
54
  MEMBER_AIDS_FIELD_NUMBER: builtins.int
55
+ ACCOUNTS_FIELD_NUMBER: builtins.int
37
56
  NODES_FIELD_NUMBER: builtins.int
38
57
  RUNS_FIELD_NUMBER: builtins.int
39
58
  name: builtins.str
40
59
  @property
41
60
  def member_aids(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: ...
42
61
  @property
62
+ def accounts(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___Account]: ...
63
+ @property
43
64
  def nodes(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[flwr.proto.node_pb2.NodeInfo]: ...
44
65
  @property
45
66
  def runs(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[flwr.proto.run_pb2.Run]: ...
@@ -48,9 +69,10 @@ class Federation(google.protobuf.message.Message):
48
69
  *,
49
70
  name: builtins.str = ...,
50
71
  member_aids: collections.abc.Iterable[builtins.str] | None = ...,
72
+ accounts: collections.abc.Iterable[global___Account] | None = ...,
51
73
  nodes: collections.abc.Iterable[flwr.proto.node_pb2.NodeInfo] | None = ...,
52
74
  runs: collections.abc.Iterable[flwr.proto.run_pb2.Run] | None = ...,
53
75
  ) -> None: ...
54
- def ClearField(self, field_name: typing.Literal["member_aids", b"member_aids", "name", b"name", "nodes", b"nodes", "runs", b"runs"]) -> None: ...
76
+ def ClearField(self, field_name: typing.Literal["accounts", b"accounts", "member_aids", b"member_aids", "name", b"name", "nodes", b"nodes", "runs", b"runs"]) -> None: ...
55
77
 
56
78
  global___Federation = Federation
flwr/server/app.py CHANGED
@@ -36,13 +36,11 @@ from flwr.common.config import get_flwr_dir
36
36
  from flwr.common.constant import (
37
37
  AUTHN_TYPE_YAML_KEY,
38
38
  AUTHZ_TYPE_YAML_KEY,
39
- CLIENT_OCTET,
40
39
  CONTROL_API_DEFAULT_SERVER_ADDRESS,
41
40
  FLEET_API_GRPC_RERE_DEFAULT_ADDRESS,
42
41
  FLEET_API_REST_DEFAULT_ADDRESS,
43
42
  ISOLATION_MODE_PROCESS,
44
43
  ISOLATION_MODE_SUBPROCESS,
45
- SERVER_OCTET,
46
44
  SERVERAPPIO_API_DEFAULT_SERVER_ADDRESS,
47
45
  SIMULATIONIO_API_DEFAULT_SERVER_ADDRESS,
48
46
  TRANSPORT_TYPE_GRPC_ADAPTER,
@@ -62,7 +60,7 @@ from flwr.proto.fleet_pb2_grpc import ( # pylint: disable=E0611
62
60
  )
63
61
  from flwr.proto.grpcadapter_pb2_grpc import add_GrpcAdapterServicer_to_server
64
62
  from flwr.server.fleet_event_log_interceptor import FleetEventLogInterceptor
65
- from flwr.supercore.address import parse_address
63
+ from flwr.supercore.address import parse_address, resolve_bind_address
66
64
  from flwr.supercore.constant import FLWR_IN_MEMORY_DB_NAME
67
65
  from flwr.supercore.ffs import FfsFactory
68
66
  from flwr.supercore.grpc_health import add_args_health, run_health_server_grpc_no_tls
@@ -429,16 +427,11 @@ def run_superlink() -> None:
429
427
  raise ValueError(f"Unknown fleet_api_type: {args.fleet_api_type}")
430
428
 
431
429
  if args.isolation == ISOLATION_MODE_SUBPROCESS:
432
-
433
- _octet, _colon, _port = serverappio_address.rpartition(":")
434
- io_address = (
435
- f"{CLIENT_OCTET}:{_port}" if _octet == SERVER_OCTET else serverappio_address
430
+ appio_address = resolve_bind_address(
431
+ simulationio_address if is_simulation else serverappio_address
436
432
  )
437
433
  command = ["flower-superexec", "--insecure"]
438
- command += [
439
- "--appio-api-address",
440
- simulationio_address if is_simulation else io_address,
441
- ]
434
+ command += ["--appio-api-address", appio_address]
442
435
  command += [
443
436
  "--plugin-type",
444
437
  ExecPluginType.SIMULATION if is_simulation else ExecPluginType.SERVER_APP,
@@ -321,7 +321,8 @@ def pull_object(
321
321
  if content is not None:
322
322
  object_available = content != b""
323
323
  # Record bytes traffic pulled by SuperNode
324
- state.store_traffic(request.run_id, bytes_sent=len(content), bytes_recv=0)
324
+ if object_available:
325
+ state.store_traffic(request.run_id, bytes_sent=len(content), bytes_recv=0)
325
326
  return PullObjectResponse(
326
327
  object_found=True,
327
328
  object_available=object_available,
flwr/supercore/address.py CHANGED
@@ -100,3 +100,12 @@ def is_port_in_use(address: str) -> bool:
100
100
  return True
101
101
 
102
102
  return False
103
+
104
+
105
+ def resolve_bind_address(address: str) -> str:
106
+ """Replace bind-all addresses (0.0.0.0, ::) with localhost (127.0.0.1, ::1)."""
107
+ if address.startswith("[::]"):
108
+ return address.replace("[::]", "[::1]", 1)
109
+ if address.startswith("0.0.0.0"):
110
+ return address.replace("0.0.0.0", "127.0.0.1", 1)
111
+ return address
@@ -53,6 +53,7 @@ def start_parent_process_monitor(
53
53
  while True:
54
54
  time.sleep(0.2)
55
55
  if not _pid_exists(parent_pid):
56
- os.kill(os.getpid(), signal.SIGKILL)
56
+ signal.raise_signal(signal.SIGINT)
57
+ break
57
58
 
58
59
  threading.Thread(target=monitor, daemon=True).start()
@@ -25,6 +25,9 @@ EXEC_PLUGIN_SECTION = "exec_plugin"
25
25
  # Flower in-memory Python-based database name
26
26
  FLWR_IN_MEMORY_DB_NAME = ":flwr-in-memory:"
27
27
 
28
+ # Flower in-memory SQLite database URL
29
+ FLWR_IN_MEMORY_SQLITE_DB_URL = "sqlite:///:memory:"
30
+
28
31
  # Constants for Hub
29
32
  APP_ID_PATTERN = r"^@[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$"
30
33
  APP_VERSION_PATTERN = r"^\d+\.\d+\.\d+$"
@@ -29,7 +29,7 @@ from sqlalchemy.exc import SQLAlchemyError
29
29
  from sqlalchemy.orm import Session, sessionmaker
30
30
 
31
31
  from flwr.common.logger import log
32
- from flwr.supercore.constant import SQLITE_PRAGMAS
32
+ from flwr.supercore.constant import FLWR_IN_MEMORY_SQLITE_DB_URL, SQLITE_PRAGMAS
33
33
 
34
34
  _current_session: ContextVar[Session | None] = ContextVar(
35
35
  "current_sqlalchemy_session",
@@ -95,7 +95,7 @@ class SqlMixin(ABC):
95
95
 
96
96
  # Auto-convert file path to SQLAlchemy SQLite URL if needed
97
97
  if database_path == ":memory:":
98
- self.database_url = "sqlite:///:memory:"
98
+ self.database_url = FLWR_IN_MEMORY_SQLITE_DB_URL
99
99
  elif not database_path.startswith("sqlite://"):
100
100
  # Treat as file path, convert to absolute and create SQLite URL
101
101
  abs_path = Path(database_path).resolve()
@@ -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
+ """Alembic helpers and migration environment for SQL state."""
@@ -0,0 +1,81 @@
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
+ """Alembic environment configuration for State migrations."""
16
+
17
+
18
+ from logging.config import fileConfig
19
+
20
+ from alembic import context
21
+ from sqlalchemy import engine_from_config, pool
22
+
23
+ from flwr.supercore.state.alembic.utils import get_combined_metadata
24
+
25
+ # Alembic Config object
26
+ config = context.config # pylint: disable=no-member
27
+
28
+ # Interpret the config file for Python logging
29
+ if config.config_file_name is not None:
30
+ fileConfig(config.config_file_name)
31
+
32
+
33
+ # Target metadata for autogenerate
34
+ target_metadata = get_combined_metadata()
35
+
36
+
37
+ def run_migrations_offline() -> None:
38
+ """Run migrations in 'offline' mode.
39
+
40
+ This configures the context with just a URL and not an Engine, though an Engine is
41
+ acceptable here as well. By skipping the Engine creation we don't even need a DBAPI
42
+ to be available.
43
+
44
+ Calls to context.execute() here emit the given string to the script output.
45
+ """
46
+ url = config.get_main_option("sqlalchemy.url")
47
+ context.configure( # pylint: disable=no-member
48
+ url=url,
49
+ target_metadata=target_metadata,
50
+ literal_binds=True,
51
+ dialect_opts={"paramstyle": "named"},
52
+ )
53
+
54
+ with context.begin_transaction(): # pylint: disable=no-member
55
+ context.run_migrations() # pylint: disable=no-member
56
+
57
+
58
+ def run_migrations_online() -> None:
59
+ """Run migrations in 'online' mode.
60
+
61
+ In this scenario we need to create an Engine and associate a connection with the
62
+ context.
63
+ """
64
+ connectable = engine_from_config(
65
+ config.get_section(config.config_ini_section, {}),
66
+ prefix="sqlalchemy.",
67
+ poolclass=pool.NullPool,
68
+ )
69
+
70
+ with connectable.connect() as connection:
71
+ # pylint: disable-next=no-member
72
+ context.configure(connection=connection, target_metadata=target_metadata)
73
+
74
+ with context.begin_transaction(): # pylint: disable=no-member
75
+ context.run_migrations() # pylint: disable=no-member
76
+
77
+
78
+ if context.is_offline_mode(): # pylint: disable=no-member
79
+ run_migrations_offline()
80
+ else:
81
+ run_migrations_online()
@@ -0,0 +1,43 @@
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
+ """${message}.
16
+
17
+ Revision ID: ${up_revision}
18
+ Revises: ${down_revision | comma,n}
19
+ Create Date: ${create_date}
20
+ """
21
+ from typing import Sequence, Union
22
+
23
+ from alembic import op
24
+ import sqlalchemy as sa
25
+ ${imports if imports else ""}
26
+
27
+ # pylint: disable=no-member
28
+
29
+ # revision identifiers, used by Alembic.
30
+ revision: str = ${repr(up_revision)}
31
+ down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)}
32
+ branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
33
+ depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
34
+
35
+
36
+ def upgrade() -> None:
37
+ """Upgrade schema."""
38
+ ${upgrades if upgrades else "pass"}
39
+
40
+
41
+ def downgrade() -> None:
42
+ """Downgrade schema."""
43
+ ${downgrades if downgrades else "pass"}
@@ -0,0 +1,49 @@
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
+ """Helpers for running and validating Alembic migrations."""
16
+
17
+
18
+ from sqlalchemy import MetaData
19
+
20
+ from flwr.supercore.state.schema.corestate_tables import create_corestate_metadata
21
+ from flwr.supercore.state.schema.linkstate_tables import create_linkstate_metadata
22
+ from flwr.supercore.state.schema.objectstore_tables import create_objectstore_metadata
23
+
24
+
25
+ def get_combined_metadata() -> MetaData:
26
+ """Combine all Flower state metadata objects into a single MetaData instance.
27
+
28
+ This ensures Alembic can track all tables across CoreState, LinkState, and
29
+ ObjectStore.
30
+
31
+ Returns
32
+ -------
33
+ MetaData
34
+ Combined SQLAlchemy MetaData with all Flower state tables.
35
+ """
36
+ # Start with linkstate tables
37
+ metadata = create_linkstate_metadata()
38
+
39
+ # Add corestate tables
40
+ corestate_metadata = create_corestate_metadata()
41
+ for table in corestate_metadata.tables.values():
42
+ table.to_metadata(metadata)
43
+
44
+ # Add objectstore tables
45
+ objectstore_metadata = create_objectstore_metadata()
46
+ for table in objectstore_metadata.tables.values():
47
+ table.to_metadata(metadata)
48
+
49
+ return metadata
@@ -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
+ """Alembic migration versions."""
@@ -0,0 +1,200 @@
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
+ """Initialize migration of state tables.
16
+
17
+ Revision ID: 8e65d8ae60b0
18
+ Revises:
19
+ Create Date: 2026-01-28 11:03:18.038794
20
+ """
21
+ from collections.abc import Sequence
22
+
23
+ import sqlalchemy as sa
24
+ from alembic import op
25
+
26
+ # pylint: disable=no-member
27
+
28
+ # revision identifiers, used by Alembic.
29
+ revision: str = "8e65d8ae60b0"
30
+ down_revision: str | Sequence[str] | None = None
31
+ branch_labels: str | Sequence[str] | None = None
32
+ depends_on: str | Sequence[str] | None = None
33
+
34
+
35
+ def upgrade() -> None:
36
+ """Upgrade schema."""
37
+ # ### commands auto generated by Alembic - please adjust! ###
38
+ # LinkState tables
39
+ op.create_table(
40
+ "node",
41
+ sa.Column("node_id", sa.Integer(), nullable=True),
42
+ sa.Column("owner_aid", sa.String(), nullable=True),
43
+ sa.Column("owner_name", sa.String(), nullable=True),
44
+ sa.Column("status", sa.String(), nullable=True),
45
+ sa.Column("registered_at", sa.String(), nullable=True),
46
+ sa.Column("last_activated_at", sa.String(), nullable=True),
47
+ sa.Column("last_deactivated_at", sa.String(), nullable=True),
48
+ sa.Column("unregistered_at", sa.String(), nullable=True),
49
+ sa.Column("online_until", sa.TIMESTAMP(), nullable=True),
50
+ sa.Column("heartbeat_interval", sa.Float(), nullable=True),
51
+ sa.Column("public_key", sa.LargeBinary(), nullable=True),
52
+ sa.UniqueConstraint("node_id"),
53
+ sa.UniqueConstraint("public_key"),
54
+ )
55
+ op.create_index("idx_node_owner_aid", "node", ["owner_aid"], unique=False)
56
+ op.create_index("idx_node_status", "node", ["status"], unique=False)
57
+ op.create_index("idx_online_until", "node", ["online_until"], unique=False)
58
+ op.create_table(
59
+ "run",
60
+ sa.Column("run_id", sa.Integer(), nullable=True),
61
+ sa.Column("fab_id", sa.String(), nullable=True),
62
+ sa.Column("fab_version", sa.String(), nullable=True),
63
+ sa.Column("fab_hash", sa.String(), nullable=True),
64
+ sa.Column("override_config", sa.String(), nullable=True),
65
+ sa.Column("pending_at", sa.String(), nullable=True),
66
+ sa.Column("starting_at", sa.String(), nullable=True),
67
+ sa.Column("running_at", sa.String(), nullable=True),
68
+ sa.Column("finished_at", sa.String(), nullable=True),
69
+ sa.Column("sub_status", sa.String(), nullable=True),
70
+ sa.Column("details", sa.String(), nullable=True),
71
+ sa.Column("federation", sa.String(), nullable=True),
72
+ sa.Column("federation_options", sa.LargeBinary(), nullable=True),
73
+ sa.Column("flwr_aid", sa.String(), nullable=True),
74
+ sa.Column("bytes_sent", sa.Integer(), server_default="0", nullable=True),
75
+ sa.Column("bytes_recv", sa.Integer(), server_default="0", nullable=True),
76
+ sa.Column("clientapp_runtime", sa.Float(), server_default="0.0", nullable=True),
77
+ sa.UniqueConstraint("run_id"),
78
+ )
79
+ op.create_table(
80
+ "logs",
81
+ sa.Column("timestamp", sa.Float(), nullable=True),
82
+ sa.Column("run_id", sa.Integer(), nullable=True),
83
+ sa.Column("node_id", sa.Integer(), nullable=True),
84
+ sa.Column("log", sa.String(), nullable=True),
85
+ sa.ForeignKeyConstraint(
86
+ ["run_id"],
87
+ ["run.run_id"],
88
+ ),
89
+ sa.UniqueConstraint("timestamp", "run_id", "node_id"),
90
+ )
91
+ op.create_table(
92
+ "context",
93
+ sa.Column("run_id", sa.Integer(), nullable=True),
94
+ sa.Column("context", sa.LargeBinary(), nullable=True),
95
+ sa.ForeignKeyConstraint(
96
+ ["run_id"],
97
+ ["run.run_id"],
98
+ ),
99
+ sa.UniqueConstraint("run_id"),
100
+ )
101
+ op.create_table(
102
+ "message_ins",
103
+ sa.Column("message_id", sa.String(), nullable=True),
104
+ sa.Column("group_id", sa.String(), nullable=True),
105
+ sa.Column("run_id", sa.Integer(), nullable=True),
106
+ sa.Column("src_node_id", sa.Integer(), nullable=True),
107
+ sa.Column("dst_node_id", sa.Integer(), nullable=True),
108
+ sa.Column("reply_to_message_id", sa.String(), nullable=True),
109
+ sa.Column("created_at", sa.Float(), nullable=True),
110
+ sa.Column("delivered_at", sa.String(), nullable=True),
111
+ sa.Column("ttl", sa.Float(), nullable=True),
112
+ sa.Column("message_type", sa.String(), nullable=True),
113
+ sa.Column("content", sa.LargeBinary(), nullable=True),
114
+ sa.Column("error", sa.LargeBinary(), nullable=True),
115
+ sa.ForeignKeyConstraint(
116
+ ["run_id"],
117
+ ["run.run_id"],
118
+ ),
119
+ sa.UniqueConstraint("message_id"),
120
+ )
121
+ op.create_table(
122
+ "message_res",
123
+ sa.Column("message_id", sa.String(), nullable=True),
124
+ sa.Column("group_id", sa.String(), nullable=True),
125
+ sa.Column("run_id", sa.Integer(), nullable=True),
126
+ sa.Column("src_node_id", sa.Integer(), nullable=True),
127
+ sa.Column("dst_node_id", sa.Integer(), nullable=True),
128
+ sa.Column("reply_to_message_id", sa.String(), nullable=True),
129
+ sa.Column("created_at", sa.Float(), nullable=True),
130
+ sa.Column("delivered_at", sa.String(), nullable=True),
131
+ sa.Column("ttl", sa.Float(), nullable=True),
132
+ sa.Column("message_type", sa.String(), nullable=True),
133
+ sa.Column("content", sa.LargeBinary(), nullable=True),
134
+ sa.Column("error", sa.LargeBinary(), nullable=True),
135
+ sa.ForeignKeyConstraint(
136
+ ["run_id"],
137
+ ["run.run_id"],
138
+ ),
139
+ sa.UniqueConstraint("message_id"),
140
+ )
141
+ # CoreState tables
142
+ op.create_table(
143
+ "token_store",
144
+ sa.Column("run_id", sa.Integer(), nullable=True),
145
+ sa.Column("token", sa.String(), nullable=False),
146
+ sa.Column("active_until", sa.Float(), nullable=True),
147
+ sa.PrimaryKeyConstraint("run_id"),
148
+ sa.UniqueConstraint("token"),
149
+ )
150
+ # ObjectStore tables
151
+ op.create_table(
152
+ "objects",
153
+ sa.Column("object_id", sa.String(), nullable=True),
154
+ sa.Column("content", sa.LargeBinary(), nullable=True),
155
+ sa.Column("is_available", sa.Integer(), server_default="0", nullable=False),
156
+ sa.Column("ref_count", sa.Integer(), server_default="0", nullable=False),
157
+ sa.CheckConstraint("is_available IN (0, 1)", name="ck_objects_is_available"),
158
+ sa.PrimaryKeyConstraint("object_id"),
159
+ )
160
+ op.create_table(
161
+ "object_children",
162
+ sa.Column("parent_id", sa.String(), nullable=False),
163
+ sa.Column("child_id", sa.String(), nullable=False),
164
+ sa.ForeignKeyConstraint(
165
+ ["child_id"], ["objects.object_id"], ondelete="CASCADE"
166
+ ),
167
+ sa.ForeignKeyConstraint(
168
+ ["parent_id"], ["objects.object_id"], ondelete="CASCADE"
169
+ ),
170
+ sa.PrimaryKeyConstraint("parent_id", "child_id"),
171
+ )
172
+ op.create_table(
173
+ "run_objects",
174
+ sa.Column("run_id", sa.Integer(), nullable=False),
175
+ sa.Column("object_id", sa.String(), nullable=False),
176
+ sa.ForeignKeyConstraint(
177
+ ["object_id"], ["objects.object_id"], ondelete="CASCADE"
178
+ ),
179
+ sa.PrimaryKeyConstraint("run_id", "object_id"),
180
+ )
181
+ # ### end Alembic commands ###
182
+
183
+
184
+ def downgrade() -> None:
185
+ """Downgrade schema."""
186
+ # ### commands auto generated by Alembic - please adjust! ###
187
+ op.drop_table("run_objects")
188
+ op.drop_table("object_children")
189
+ op.drop_table("objects")
190
+ op.drop_table("token_store")
191
+ op.drop_table("message_res")
192
+ op.drop_table("message_ins")
193
+ op.drop_table("context")
194
+ op.drop_table("logs")
195
+ op.drop_table("run")
196
+ op.drop_index("idx_online_until", table_name="node")
197
+ op.drop_index("idx_node_status", table_name="node")
198
+ op.drop_index("idx_node_owner_aid", table_name="node")
199
+ op.drop_table("node")
200
+ # ### end Alembic commands ###
@@ -2,6 +2,7 @@
2
2
 
3
3
  ## Schema
4
4
 
5
+ <!-- BEGIN_SQLALCHEMY_DOCS -->
5
6
  ```mermaid
6
7
 
7
8
  ---
@@ -77,7 +78,6 @@ erDiagram
77
78
  INTEGER ref_count
78
79
  }
79
80
 
80
- <<<<<<< HEAD
81
81
  run {
82
82
  INTEGER bytes_recv "nullable"
83
83
  INTEGER bytes_sent "nullable"
@@ -98,8 +98,6 @@ erDiagram
98
98
  VARCHAR sub_status "nullable"
99
99
  }
100
100
 
101
- =======
102
- >>>>>>> main
103
101
  run_objects {
104
102
  VARCHAR object_id PK,FK
105
103
  INTEGER run_id PK
@@ -111,15 +109,13 @@ erDiagram
111
109
  VARCHAR token UK
112
110
  }
113
111
 
114
- <<<<<<< HEAD
115
112
  run ||--o| context : run_id
116
113
  run ||--o{ logs : run_id
117
114
  run ||--o{ message_ins : run_id
118
115
  run ||--o{ message_res : run_id
119
- =======
120
- >>>>>>> main
121
116
  objects ||--o| object_children : parent_id
122
117
  objects ||--o| object_children : child_id
123
118
  objects ||--o| run_objects : object_id
124
119
 
125
120
  ```
121
+ <!-- END_SQLALCHEMY_DOCS -->
flwr/supercore/utils.py CHANGED
@@ -254,3 +254,24 @@ def humanize_bytes(num_bytes: int) -> str:
254
254
  value /= 1024
255
255
 
256
256
  raise RuntimeError("Unreachable code") # Make mypy happy
257
+
258
+
259
+ def check_federation_format(federation: str) -> None:
260
+ """Check if the federation string is valid.
261
+
262
+ Parameters
263
+ ----------
264
+ federation : str
265
+ The federation string to check.
266
+
267
+ Raises
268
+ ------
269
+ ValueError
270
+ If the federation string is not valid. The expected
271
+ format is '@<account-name>/<federation-name>'.
272
+ """
273
+ if not re.match(r"^@[a-zA-Z0-9\-_]+/[a-zA-Z0-9\-_]+$", federation):
274
+ raise ValueError(
275
+ f"Invalid federation format: {federation}. "
276
+ f"Expected format: '@<account-name>/<federation-name>'."
277
+ )
@@ -15,8 +15,9 @@
15
15
  """NoOp implementation of FederationManager."""
16
16
 
17
17
 
18
- from flwr.common.constant import NOOP_FLWR_AID
18
+ from flwr.common.constant import NOOP_ACCOUNT_NAME, NOOP_FLWR_AID
19
19
  from flwr.common.typing import Federation
20
+ from flwr.proto.federation_pb2 import Account # pylint: disable=E0611
20
21
  from flwr.supercore.constant import NOOP_FEDERATION
21
22
 
22
23
  from .federation_manager import FederationManager
@@ -66,6 +67,7 @@ class NoOpFederationManager(FederationManager):
66
67
  return Federation(
67
68
  name=NOOP_FEDERATION,
68
69
  member_aids=[NOOP_FLWR_AID],
70
+ accounts=[Account(id=NOOP_FLWR_AID, name=NOOP_ACCOUNT_NAME)],
69
71
  nodes=nodes,
70
72
  runs=runs,
71
73
  )
@@ -65,7 +65,7 @@ from flwr.common.telemetry import EventType
65
65
  from flwr.common.typing import Fab, Run, RunNotRunningException
66
66
  from flwr.proto.clientappio_pb2_grpc import add_ClientAppIoServicer_to_server
67
67
  from flwr.proto.message_pb2 import ObjectTree # pylint: disable=E0611
68
- from flwr.supercore.address import parse_address
68
+ from flwr.supercore.address import parse_address, resolve_bind_address
69
69
  from flwr.supercore.ffs import Ffs, FfsFactory
70
70
  from flwr.supercore.grpc_health import run_health_server_grpc_no_tls
71
71
  from flwr.supercore.object_store import ObjectStore, ObjectStoreFactory
@@ -213,7 +213,10 @@ def start_client_internal(
213
213
  # Launch the SuperExec if the isolation mode is `subprocess`
214
214
  if isolation == ISOLATION_MODE_SUBPROCESS:
215
215
  command = ["flower-superexec", "--insecure"]
216
- command += ["--appio-api-address", clientappio_api_address]
216
+ command += [
217
+ "--appio-api-address",
218
+ resolve_bind_address(clientappio_api_address),
219
+ ]
217
220
  command += ["--plugin-type", ExecPluginType.CLIENT_APP]
218
221
  command += ["--parent-pid", str(os.getpid())]
219
222
  # pylint: disable-next=consider-using-with
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: flwr-nightly
3
- Version: 1.26.0.dev20260126
3
+ Version: 1.26.0.dev20260128
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
@@ -31,6 +31,7 @@ Classifier: Typing :: Typed
31
31
  Provides-Extra: rest
32
32
  Provides-Extra: simulation
33
33
  Requires-Dist: SQLAlchemy (>=2.0.45,<3.0.0)
34
+ Requires-Dist: alembic (>=1.18.1,<2.0.0)
34
35
  Requires-Dist: click (<8.2.0)
35
36
  Requires-Dist: cryptography (>=44.0.1,<45.0.0)
36
37
  Requires-Dist: grpcio (>=1.70.0,<2.0.0)
@@ -6,7 +6,7 @@ flwr/app/message_type.py,sha256=WABg74wnkIdrmjD_AhlA7tYOqwni5n5Isagc8O9IdOc,1002
6
6
  flwr/app/metadata.py,sha256=rnfEHbw-IL1anLHwploK1YQQqKFHQmoF_M6rcSpRNCc,7422
7
7
  flwr/app/user_config.py,sha256=7xsOVs-NYIvE710iE1qRFqxlA3LQNnmcoVXqcg_Ez2g,802
8
8
  flwr/cli/__init__.py,sha256=EfMGmHoobET6P2blBt_eOByXL8299MgFfB7XNdaPQ6I,720
9
- flwr/cli/app.py,sha256=62ELnHyEbgTp2GDzgsuk-mJ09_Gs8WHysbRceOnH-x8,3851
9
+ flwr/cli/app.py,sha256=AEm7JewC_0FT2esOqNcB9Xkot7s7sGBf6yitzp8z6E4,3784
10
10
  flwr/cli/app_cmd/__init__.py,sha256=O_vTzS2u-n27fBYnDbjTMMKQQ2Bz80Y4JqGBHSAnK40,856
11
11
  flwr/cli/app_cmd/publish.py,sha256=DAFRu12EAO1NtzrGN9uYN_hW20gQZ2vCyf0HggzqaZ0,8006
12
12
  flwr/cli/app_cmd/review.py,sha256=FPusshF36519z_KN36mU2fcvRtb5DCh0esinsts8WEs,7200
@@ -17,19 +17,19 @@ flwr/cli/auth_plugin/oidc_cli_plugin.py,sha256=1gN00FifSrhUA5s5uaE_GpQ3N7U8fQec4
17
17
  flwr/cli/build.py,sha256=wjLrxWHGnwRFp7WxZFgm8J0cwcdQY9-QHxjFnEa2dII,10097
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
- flwr/cli/config/ls.py,sha256=llQiXC0kK7shT96Y3GD2uvVP3MjWWLpYTeExUH1rALo,3537
21
- flwr/cli/config_migration.py,sha256=UM3lZU34TOhGXkjBBChyGaE6r7uaVV2NpyP4TZhFPyY,10848
20
+ flwr/cli/config/ls.py,sha256=i976g1rzpSLEagyjEFdxxYAM-FqaP0Unqlk2EiKj4B4,3549
21
+ flwr/cli/config_migration.py,sha256=KJIVqpVbEfhY53taELFZuVGY0-sTPGiPOI6_FwEjfDI,10932
22
22
  flwr/cli/config_utils.py,sha256=t9GNUPnUH1nUNZQ3oGjx6Jd5VSO9SlnQXjz3-yro7Rs,7683
23
- flwr/cli/constant.py,sha256=Ihww6e-6V_Ocv7pRnIQeezdy72_RBL9dPYv87Yf4ppI,3612
23
+ flwr/cli/constant.py,sha256=N50fATyqwwFuy8Ty7nl4zvKOQtmdSjfoJH4bHggb6BU,3501
24
24
  flwr/cli/example.py,sha256=SNTorkKPrx1rOryGREUyZu8TcOc1-vFv1zEddaysdY0,2216
25
25
  flwr/cli/federation/__init__.py,sha256=okxswL4fAjApI9gV_alU1lRkTUcQRbwlzvtUTLz61fE,793
26
- flwr/cli/federation/ls.py,sha256=xjS3HHNwOXXsaNN-xrdWvClmV29R6yT8R2AA0SVzJas,10689
27
- flwr/cli/flower_config.py,sha256=8XGB9-XjJrpR2xYOcFWjmvmqGZRS5_M-rPhuZQQASSs,15267
26
+ flwr/cli/federation/ls.py,sha256=SZrQb_uUBrK2v4nAo3GETxyVBoNpBUJLjQuE_maX8_E,10737
27
+ flwr/cli/flower_config.py,sha256=EFsSJByBD-SmIReoBaSzuEriLz4716qh7LMU2_yfGzc,15065
28
28
  flwr/cli/install.py,sha256=gBYg1SczpvBh8MMrXJxi0q4DUdzXCdvapK3jC3CBlPk,9114
29
29
  flwr/cli/log.py,sha256=BPA0dvGlXx5PrtURPua5fJyF7iVrb28K4fEY2Uth0EE,7317
30
30
  flwr/cli/login/__init__.py,sha256=B1SXKU3HCQhWfFDMJhlC7FOl8UsvH4mxysxeBnrfyUE,800
31
- flwr/cli/login/login.py,sha256=uUUrt6TNJjV_udwS3qJ5TXcRqqtI9nBySdXzwXItoVc,3999
32
- flwr/cli/ls.py,sha256=ROSDyaQw5y9BjcBUtj57Vc3HgHTW_JbYX5NDIl-Yfhg,12793
31
+ flwr/cli/login/login.py,sha256=o-jm5QRMIpivE8kpY7cPTGbr7nQhsyFYaftMtBaaT0s,3649
32
+ flwr/cli/ls.py,sha256=7CUtSdqnEuAkAgN-7u9c8TBOoM-IYms0nh0h1SkLz7I,12782
33
33
  flwr/cli/new/__init__.py,sha256=QA1E2QtzPvFCjLTUHnFnJbufuFiGyT_0Y53Wpbvg1F0,790
34
34
  flwr/cli/new/new.py,sha256=15phs5dhEpo7ORs7uulGzHWtW9W5ZGn0EKFE_T5WXb8,7829
35
35
  flwr/cli/pull.py,sha256=XzBvLrdYGrSCuky5OoCEU4V9UR9QlyZMT7Tj-W6wSw0,2946
@@ -38,11 +38,11 @@ flwr/cli/run/run.py,sha256=6yrVljd1CqqALq-wqiv0TnZlveiN5ymiUI5iTjuvUJM,8954
38
38
  flwr/cli/run_utils.py,sha256=ZnlBOqFfRxVEPZKR_9zYrynNcohiCFY8QN6OFyOCrQs,5082
39
39
  flwr/cli/stop.py,sha256=w3frNt3TD5AukGmlGq4CbWo-3PC3B-YSLO6PjJeHjH4,4692
40
40
  flwr/cli/supernode/__init__.py,sha256=DBkjoPo2hS2Y-ghJxwLbrAbCQFBgD82_Itl2_892UBo,917
41
- flwr/cli/supernode/ls.py,sha256=x4ghJHpqcpDGuZx91ZehzSdmrUJ8ZuZvpY_UybnP63U,8224
41
+ flwr/cli/supernode/ls.py,sha256=o3YENA1e-W4oJjq2rHwwzZthllpLzEhSY--9-m_c6Qc,8236
42
42
  flwr/cli/supernode/register.py,sha256=YPf28ZDvqPAQAt6c6OG73AJa9zZPONKgpxhnnb3NWQk,5498
43
43
  flwr/cli/supernode/unregister.py,sha256=qWGg8G_KqF300ze4D1dYaSIRJZkl9zwM2ul8VLYjJMY,3719
44
- flwr/cli/typing.py,sha256=MaY3NAca2PgmNByogksDKSCoRQLQpXTgO8NO9nLP0yA,8008
45
- flwr/cli/utils.py,sha256=jLhe-Voj0v8cK16MwRXW7qf34lEffOauRIukDyYX2II,15258
44
+ flwr/cli/typing.py,sha256=COLEK_zVQuGGm4t_nYAW0_8EWu09zFiAv-tEZNg95_s,7367
45
+ flwr/cli/utils.py,sha256=HCQYvqAjO74awSWQ9dV_OfCZ9QSkMIoKQ5b0Y4EOeoo,15568
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
@@ -86,7 +86,7 @@ flwr/common/event_log_plugin/__init__.py,sha256=ts3VAL3Fk6Grp1EK_1Qg_V-BfOof9F86
86
86
  flwr/common/event_log_plugin/event_log_plugin.py,sha256=uJYR363nVzjfJePay3UHwaifwgBx2rAbKHHAuQC10VU,2014
87
87
  flwr/common/exit/__init__.py,sha256=8W7xaO1iw0vacgmQW7FTFbSh7csNv6XfsgIlnIbNF6U,978
88
88
  flwr/common/exit/exit.py,sha256=EBHZos4EhZLxW1lmZXDVlFUOf0_eTQ1FxzQiZ466hZ0,4018
89
- flwr/common/exit/exit_code.py,sha256=1S_LJNdo2lai3tjAYgZgNjN5jcIyU60Y1a-IDfUZVAM,6702
89
+ flwr/common/exit/exit_code.py,sha256=xpvi_3UOhf6AT8pR0ZGXyBxWfnuFFE50O1cKqOGVxxE,7000
90
90
  flwr/common/exit/exit_handler.py,sha256=zUAXHtp4rYK_sbkgsqS5zrVAzJUiSBrB-a4Ay3vSPE8,2302
91
91
  flwr/common/exit/signal_handler.py,sha256=o_e33v9kP0jz13q-8BZDrKF7vNCzOApvDPZ3-nTXfIc,3697
92
92
  flwr/common/grpc.py,sha256=8XoMTA5rJMze8fG6vOs1g4_rkxMhfpvMEYJPnqYPi2g,9799
@@ -119,7 +119,7 @@ flwr/common/secure_aggregation/secaggplus_utils.py,sha256=E_xU-Zd45daO1em7M6C2wO
119
119
  flwr/common/serde.py,sha256=FqqJqNqCk-DryCOgTGkabENvFeU2Q0LieLe0bt3qBbU,22342
120
120
  flwr/common/serde_utils.py,sha256=Vk49PnZWRgo0NrvBhFqH-Li7sMFVVgyrs3Ck7kHB-ZE,5820
121
121
  flwr/common/telemetry.py,sha256=7HNy5ytioJ3SbFtaKNucwYYC-IXEOXphitZbdHmSlfk,8924
122
- flwr/common/typing.py,sha256=pUhyfCA6vbApelxxDnUti5Yj_oBZv1xuuzyU8vhwqaw,7295
122
+ flwr/common/typing.py,sha256=LLL-4wB8LaijpiVCIJQBv-PI8SXM4RNjbClu3BHWQh0,7394
123
123
  flwr/compat/__init__.py,sha256=gbfDQKKKMZzi3GswyVRgyLdDlHiWj3wU6dg7y6m5O_s,752
124
124
  flwr/compat/client/__init__.py,sha256=qpbo0lcxdNL4qy5KHqiGm8OLxSxkYgI_-dLh5rwhtcI,746
125
125
  flwr/compat/client/app.py,sha256=oBOB-gYm0oqxtfAp58QZtv3dyUsPxjFm__f1_teenHU,27178
@@ -151,8 +151,8 @@ flwr/proto/fab_pb2.py,sha256=QDkVHGRGmugjQQUiN8A_okppZ5e2pJi5KipjnWmF6e4,2320
151
151
  flwr/proto/fab_pb2.pyi,sha256=PUUZ1FHeTwXxjVmTX14Vx5gmgRSFvoW_e5v_Z5qkT54,3948
152
152
  flwr/proto/fab_pb2_grpc.py,sha256=jmhCnnwSqBpZ0329XGUGARbcz5sHrr0WrXwccqzc3Vs,895
153
153
  flwr/proto/fab_pb2_grpc.pyi,sha256=fMwjLr-QCljWBa8uk0fvMCytnrF1-IzFLCbezqEmvdU,1071
154
- flwr/proto/federation_pb2.py,sha256=x8hwIFsw87e7STmvq5qL90RmcImCGw5oOqbke7xaykA,1670
155
- flwr/proto/federation_pb2.pyi,sha256=_At18SzwJjlf3k3FXjVi3Op59rs2i5XcIeBSPlvfXr0,2215
154
+ flwr/proto/federation_pb2.py,sha256=1IAerSPg5O3DDx1Nixc9PL-0ELK4C_yjeCa3IpHLCjE,1927
155
+ flwr/proto/federation_pb2.pyi,sha256=EMPXcT0b6a6rs8oB-rHp-5dIQ_83aBDd7fTmamVzy60,2970
156
156
  flwr/proto/federation_pb2_grpc.py,sha256=HPErt9uXJPbY8jLtTNFr941z82uMm7cOx10hTHgcwZM,902
157
157
  flwr/proto/federation_pb2_grpc.pyi,sha256=fMwjLr-QCljWBa8uk0fvMCytnrF1-IzFLCbezqEmvdU,1071
158
158
  flwr/proto/fleet_pb2.py,sha256=HnxtM2pqVdn2xhl3e1NlLazpSMaGoUUEG9Nd17egxqU,6543
@@ -201,7 +201,7 @@ flwr/proto/transport_pb2_grpc.py,sha256=jYsbV3KYdp4TaNfWxv3ljFEvB-Yjsa8MIPtTH-vV
201
201
  flwr/proto/transport_pb2_grpc.pyi,sha256=qd2cOXq6HBZtwRfPpKuni27NGMJCAxyP5R95Vk8WWDU,2073
202
202
  flwr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
203
203
  flwr/server/__init__.py,sha256=LQQHiuL2jy7TpNaKastRdGsexlxSt5ZWAQNVqitDnrY,1598
204
- flwr/server/app.py,sha256=Z9JRCp8S4YIGr79r1LM2wXsuoNoxt4K7dKnletzxJU0,31827
204
+ flwr/server/app.py,sha256=90rGFbhQw0vDrVDamqBVIm0RtUDfUUdUcXc9WpU7BKI,31678
205
205
  flwr/server/client_manager.py,sha256=3WOqNWh5dlqPRG-DMGwXiyvGNEYS_jKqXKkFIkMqWLQ,6187
206
206
  flwr/server/client_proxy.py,sha256=QS_RGgjSZ8V4StAHH8zc7W9_os2FyO4wNqkbylQMBTY,2341
207
207
  flwr/server/compat/__init__.py,sha256=0IsttWvY15qO98_1GyzVC-vR1e_ZPXOdu2qUlOkYMPE,886
@@ -260,7 +260,7 @@ flwr/server/superlink/fleet/grpc_rere/__init__.py,sha256=ahDJJ1e-lDxBpeBMgPk7YZt
260
260
  flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py,sha256=DtHuDP7fvgO-iamI48ACr0TQcD20eBYwMicHKIjhmXQ,12583
261
261
  flwr/server/superlink/fleet/grpc_rere/node_auth_server_interceptor.py,sha256=88e9yEapAbk8fOoqG4f4-jvXMLuJAZYQF9j6whczegc,5811
262
262
  flwr/server/superlink/fleet/message_handler/__init__.py,sha256=fHsRV0KvJ8HtgSA4_YBsEzuhJLjO8p6xx4aCY2oE1p4,731
263
- flwr/server/superlink/fleet/message_handler/message_handler.py,sha256=FO-CuOKfOwomjefmZsn_NGUM4m0ptPGwMOtfQTYfro0,12109
263
+ flwr/server/superlink/fleet/message_handler/message_handler.py,sha256=WrJkw8SX_0XMnZ8velhjjFHUIQ8SuGMpojkWkyhqB_E,12142
264
264
  flwr/server/superlink/fleet/rest_rere/__init__.py,sha256=Lzc93nA7tDqoy-zRUaPG316oqFiZX1HUCL5ELaXY_xw,735
265
265
  flwr/server/superlink/fleet/rest_rere/rest_api.py,sha256=cDWDQBf79F9tZ1mXOErc-nsnY4zFP9Tcv7Fs-bQIdiU,9616
266
266
  flwr/server/superlink/fleet/vce/__init__.py,sha256=XOKbAWOzlCqEOQ3M2cBYkH7HKA7PxlbCJMunt-ty-DY,784
@@ -324,11 +324,11 @@ flwr/simulation/ray_transport/utils.py,sha256=KrexpWYCF-dAF3UHc9yDbPQWO-ahMT-BbD
324
324
  flwr/simulation/run_simulation.py,sha256=hSJAqOfMd8I30Pq9x2TXYXDCXAeUtWqwBncX6JGEMOQ,20798
325
325
  flwr/simulation/simulationio_connection.py,sha256=cS7yTOa5yeK-bQ2YjS9eJCXL8SOItEkiJD5sN5mBb_Y,3475
326
326
  flwr/supercore/__init__.py,sha256=pqkFoow_E6UhbBlhmoD1gmTH-33yJRhBsIZqxRPFZ7U,755
327
- flwr/supercore/address.py,sha256=vVE5y-t77YL16lMDVTeK3g75N8IKbQ72sVcRI-RPPWs,2984
328
- flwr/supercore/app_utils.py,sha256=K76Zt6R670b1hUmxOsNc1WUCVYvF7lejXPcCO9K0Q0g,1753
327
+ flwr/supercore/address.py,sha256=SgM_pazKsw1Avo5nms0WWgIZFUGS349O0gOP7N3bFW4,3318
328
+ flwr/supercore/app_utils.py,sha256=P558DOr5a4UPyOdRCxbDQueQF-L-kSc6qzouPPuqj0U,1773
329
329
  flwr/supercore/cli/__init__.py,sha256=EDl2aO-fuQfxSbL-T1W9RAfA2N0hpWHmqX_GSwblJbQ,845
330
330
  flwr/supercore/cli/flower_superexec.py,sha256=IQIGzxgaeLNMNzGXGemfYK3lp8God5bTkXpVkbeP_ig,6109
331
- flwr/supercore/constant.py,sha256=qMeN0YGu_aZ95XKlLc7n4WBl_AVjA9vv_1666gGhCq8,2792
331
+ flwr/supercore/constant.py,sha256=ZtS-CpGSyth9iKjavNOLE7T-8xmCaEVoXi4ZarYry5s,2884
332
332
  flwr/supercore/corestate/__init__.py,sha256=Vau6-L_JG5QzNqtCTa9xCKGGljc09wY8avZmIjSJemg,774
333
333
  flwr/supercore/corestate/corestate.py,sha256=EZg4gPXqVOXwS7t0tlPfedajoWj5T80oeDBNxpV2y2I,2874
334
334
  flwr/supercore/corestate/in_memory_corestate.py,sha256=9qa6RuRZfCp6vs-ARYdiZjCL31VOAAxw0a_VkBXR5zY,5116
@@ -355,9 +355,15 @@ flwr/supercore/object_store/sql_object_store.py,sha256=ZBmyc3a_GXZFNaefDCxhrri-_
355
355
  flwr/supercore/primitives/__init__.py,sha256=Tx8GOjnmMo8Y74RsDGrMpfr-E0Nl8dcUDF784_ge6F8,745
356
356
  flwr/supercore/primitives/asymmetric.py,sha256=1643niHYj3uEbfPd06VuMHwN3tKVwg0uVyR3RhTdWIU,3778
357
357
  flwr/supercore/primitives/asymmetric_ed25519.py,sha256=eIhOTMibQW0FJX4MXdplHdL3HcfCiKuFu2mQ8GQTUz8,5025
358
- flwr/supercore/sql_mixin.py,sha256=eT4JHaLG4gShZrC10pkUVIGcTvZszlJI1ARhOJEA_WY,11727
358
+ flwr/supercore/sql_mixin.py,sha256=dAK_NCnXa0Ili0N5vYgQ9dlVOE-KeiQCRc2Z0RcS0sU,11765
359
359
  flwr/supercore/state/__init__.py,sha256=FkKhsNVM4LjlRlOgXTz6twINmw5ohIUKS_OER0BNo_w,724
360
- flwr/supercore/state/schema/README.md,sha256=-0QrXDhnv30gEYoIUJo7aLUolY0r_t_nC24yk7B2agM,2892
360
+ flwr/supercore/state/alembic/__init__.py,sha256=pC-y-xsfgi9YwCrqnt814yOJ9ytlDRUFGqOY6Qzt-_U,746
361
+ flwr/supercore/state/alembic/env.py,sha256=fmRDLxsmJ77pC4LLDlpx3iBeBZXN2DFZ7LKhNTmGTIg,2739
362
+ flwr/supercore/state/alembic/script.py.mako,sha256=oNrcIBPuSq0jfg3ycPAg3vo18QhA-_OaGMTkKCix74I,1416
363
+ flwr/supercore/state/alembic/utils.py,sha256=6KpFEgR4_oNjq8KmNXuq-3ilOfNbzqlCw80NbBgjn9I,1813
364
+ flwr/supercore/state/alembic/versions/__init__.py,sha256=ri3OIpl8xD_Csv25uAAT7l-MtHhixqmAGt9Aa1OpM1s,717
365
+ flwr/supercore/state/alembic/versions/rev_2026_01_28_initialize_migration_of_state_tables.py,sha256=U4j4PdRUtIAZuWUGuLO7N8W_5Ixit1rfEqfcIp2SAmE,8407
366
+ flwr/supercore/state/schema/README.md,sha256=qVCGuyU-gObFrL5MRvH9LraNi0Z_LbXzolXykbOrY9w,2884
361
367
  flwr/supercore/state/schema/__init__.py,sha256=Egnde6OY01wrpT4PuhL4NGn_NY4jdAH7kf7ktagN4Ws,724
362
368
  flwr/supercore/state/schema/corestate_tables.py,sha256=TwonogWgEUtKeE-LY59wousWzhF0w3wMKAotRqvRxao,1392
363
369
  flwr/supercore/state/schema/linkstate_tables.py,sha256=aKh9zR6cF4ecOJHS8SzniLXkcNGd4OG4xnZvwC_qers,5583
@@ -370,7 +376,7 @@ flwr/supercore/superexec/plugin/exec_plugin.py,sha256=iRRZcnTcM3wMwkkUElmfFka31a
370
376
  flwr/supercore/superexec/plugin/serverapp_exec_plugin.py,sha256=IwRzdPV-cSKwrP2krGh0De4IkAuxsmgK0WU6J-2GXqM,1035
371
377
  flwr/supercore/superexec/plugin/simulation_exec_plugin.py,sha256=upn5zE-YKkl_jTw8RzmeyQ58PU_UAlQ7CqnBXXdng8I,1060
372
378
  flwr/supercore/superexec/run_superexec.py,sha256=yeV6_eI0XFfPyNwnlgqchR9jI80FzSI80oyekrIlO2M,7144
373
- flwr/supercore/utils.py,sha256=WGHnvyZ9sC5fvFDR8rXnGyz3CQ1O-qSBMT0Alh1a090,7819
379
+ flwr/supercore/utils.py,sha256=2ywouz7y4M4NSdvZ_DMPYZX5xCpt5zIR5z5rEgl7znI,8410
374
380
  flwr/supercore/version.py,sha256=7GAGzPn73Mkh09qhrjbmjZQtcqVhBuzhFBaK4Mk4VRk,1325
375
381
  flwr/superlink/__init__.py,sha256=GNSuJ4-N6Z8wun2iZNlXqENt5beUyzC0Gi_tN396bbM,707
376
382
  flwr/superlink/artifact_provider/__init__.py,sha256=pgZEcVPKRE874LSu3cgy0HbwSJBIpVy_HxQOmne4PAs,810
@@ -380,7 +386,7 @@ flwr/superlink/auth_plugin/auth_plugin.py,sha256=xLqAYFG7sjAhYgyeTvvEY6fSMjPRq0o
380
386
  flwr/superlink/auth_plugin/noop_auth_plugin.py,sha256=0MWjNXZdz6zGRfitlnZfVKu4sKVn0HhJvnp09c3xVUA,2962
381
387
  flwr/superlink/federation/__init__.py,sha256=A1pFFvkEQrHzvRAOstr2o7nphx3JbuJgkNRFKJ9qD3M,890
382
388
  flwr/superlink/federation/federation_manager.py,sha256=-Vu1v_WWhWK7wgKEQua24lw9a6iPxG89VI-jqLO5E4g,2322
383
- flwr/superlink/federation/noop_federation_manager.py,sha256=Hc6_YVlMZZkdElS8bawi85eCTHOzVPUZVe9Dmrl2DW8,2881
389
+ flwr/superlink/federation/noop_federation_manager.py,sha256=YqAfmbxUPzFrivZNAheJtDEpUjTLaxPi-k7tuZTAvoU,3045
384
390
  flwr/superlink/servicer/__init__.py,sha256=ZC-kILcUGeh6IxJsfu24cTzUqIGXmQfEKsGfhsnhBpM,717
385
391
  flwr/superlink/servicer/control/__init__.py,sha256=qhUTMt_Mg4lxslCJYn5hDSrA-lXf5ya3617BT8kR-2Y,803
386
392
  flwr/superlink/servicer/control/control_account_auth_interceptor.py,sha256=AJs7GE-fyUBLcUEUzB058TpFzC7gyDOL17THvYkJTn8,6529
@@ -401,8 +407,8 @@ flwr/supernode/runtime/run_clientapp.py,sha256=-JsN3h7jQC12vzDBmImzYmyccI3LveIu-
401
407
  flwr/supernode/servicer/__init__.py,sha256=lucTzre5WPK7G1YLCfaqg3rbFWdNSb7ZTt-ca8gxdEo,717
402
408
  flwr/supernode/servicer/clientappio/__init__.py,sha256=7Oy62Y_oijqF7Dxi6tpcUQyOpLc_QpIRZ83NvwmB0Yg,813
403
409
  flwr/supernode/servicer/clientappio/clientappio_servicer.py,sha256=rRL4CQ0L78jF_p0ct4-JMGREt6wWRy__wy4czF4f54Y,11639
404
- flwr/supernode/start_client_internal.py,sha256=BYk69UBQ2gQJaDQxXhccUgfOWrb7ShAstrbcMOCZIIs,26173
405
- flwr_nightly-1.26.0.dev20260126.dist-info/METADATA,sha256=tcxTPuQjyJ_6Uyt2AjjiDY18bTLl4a5awalqhBFiJkM,14398
406
- flwr_nightly-1.26.0.dev20260126.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
407
- flwr_nightly-1.26.0.dev20260126.dist-info/entry_points.txt,sha256=hxHD2ixb_vJFDOlZV-zB4Ao32_BQlL34ftsDh1GXv14,420
408
- flwr_nightly-1.26.0.dev20260126.dist-info/RECORD,,
410
+ flwr/supernode/start_client_internal.py,sha256=AUzEUFJbKRvyJbOjE6b6kShJrd4aJkqqOZgCokY67_A,26252
411
+ flwr_nightly-1.26.0.dev20260128.dist-info/METADATA,sha256=vwyjsIU9OBUX7zRbFGbUOqciYyerGlRDba7iSXcEfOM,14439
412
+ flwr_nightly-1.26.0.dev20260128.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
413
+ flwr_nightly-1.26.0.dev20260128.dist-info/entry_points.txt,sha256=hxHD2ixb_vJFDOlZV-zB4Ao32_BQlL34ftsDh1GXv14,420
414
+ flwr_nightly-1.26.0.dev20260128.dist-info/RECORD,,