flwr 1.25.0__py3-none-any.whl → 1.26.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. flwr/__init__.py +1 -1
  2. flwr/app/__init__.py +4 -1
  3. flwr/app/message_type.py +29 -0
  4. flwr/app/metadata.py +5 -2
  5. flwr/app/user_config.py +19 -0
  6. flwr/cli/app.py +37 -19
  7. flwr/cli/app_cmd/publish.py +25 -75
  8. flwr/cli/app_cmd/review.py +18 -69
  9. flwr/cli/auth_plugin/auth_plugin.py +5 -10
  10. flwr/cli/auth_plugin/noop_auth_plugin.py +1 -2
  11. flwr/cli/auth_plugin/oidc_cli_plugin.py +38 -38
  12. flwr/cli/build.py +15 -28
  13. flwr/cli/config/__init__.py +21 -0
  14. flwr/cli/config/ls.py +71 -0
  15. flwr/cli/config_migration.py +297 -0
  16. flwr/cli/config_utils.py +63 -156
  17. flwr/cli/constant.py +71 -0
  18. flwr/cli/federation/__init__.py +0 -2
  19. flwr/cli/federation/ls.py +256 -64
  20. flwr/cli/flower_config.py +429 -0
  21. flwr/cli/install.py +23 -62
  22. flwr/cli/log.py +23 -37
  23. flwr/cli/login/login.py +29 -63
  24. flwr/cli/ls.py +28 -58
  25. flwr/cli/new/new.py +9 -29
  26. flwr/cli/pull.py +19 -37
  27. flwr/cli/run/run.py +85 -93
  28. flwr/cli/run_utils.py +1 -1
  29. flwr/cli/stop.py +32 -73
  30. flwr/cli/supernode/ls.py +25 -57
  31. flwr/cli/supernode/register.py +31 -80
  32. flwr/cli/supernode/unregister.py +24 -70
  33. flwr/cli/typing.py +200 -0
  34. flwr/cli/utils.py +160 -275
  35. flwr/client/grpc_rere_client/connection.py +3 -3
  36. flwr/client/grpc_rere_client/grpc_adapter.py +1 -1
  37. flwr/client/message_handler/message_handler.py +2 -1
  38. flwr/client/mod/centraldp_mods.py +1 -1
  39. flwr/client/mod/localdp_mod.py +1 -1
  40. flwr/client/mod/secure_aggregation/secaggplus_mod.py +1 -1
  41. flwr/client/run_info_store.py +2 -1
  42. flwr/clientapp/client_app.py +2 -1
  43. flwr/common/__init__.py +3 -2
  44. flwr/common/args.py +5 -5
  45. flwr/common/config.py +12 -17
  46. flwr/common/constant.py +3 -16
  47. flwr/common/context.py +2 -1
  48. flwr/common/exit/exit.py +4 -4
  49. flwr/common/exit/exit_code.py +6 -0
  50. flwr/common/grpc.py +2 -1
  51. flwr/common/logger.py +1 -1
  52. flwr/common/message.py +1 -1
  53. flwr/common/retry_invoker.py +13 -5
  54. flwr/common/secure_aggregation/ndarrays_arithmetic.py +5 -2
  55. flwr/common/serde.py +7 -5
  56. flwr/common/telemetry.py +1 -1
  57. flwr/common/typing.py +4 -3
  58. flwr/compat/client/app.py +6 -9
  59. flwr/compat/client/grpc_client/connection.py +2 -1
  60. flwr/compat/common/constant.py +29 -0
  61. flwr/compat/server/app.py +1 -1
  62. flwr/proto/clientappio_pb2.py +2 -2
  63. flwr/proto/clientappio_pb2_grpc.py +104 -88
  64. flwr/proto/clientappio_pb2_grpc.pyi +140 -80
  65. flwr/proto/federation_pb2.py +5 -3
  66. flwr/proto/federation_pb2.pyi +32 -2
  67. flwr/proto/run_pb2.py +5 -13
  68. flwr/proto/run_pb2.pyi +0 -57
  69. flwr/proto/serverappio_pb2.py +2 -2
  70. flwr/proto/serverappio_pb2_grpc.py +138 -207
  71. flwr/proto/serverappio_pb2_grpc.pyi +189 -155
  72. flwr/proto/simulationio_pb2.py +2 -2
  73. flwr/proto/simulationio_pb2_grpc.py +62 -90
  74. flwr/proto/simulationio_pb2_grpc.pyi +95 -55
  75. flwr/server/app.py +6 -13
  76. flwr/server/compat/grid_client_proxy.py +2 -1
  77. flwr/server/grid/grpc_grid.py +5 -5
  78. flwr/server/serverapp/app.py +11 -4
  79. flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +1 -1
  80. flwr/server/superlink/fleet/grpc_rere/node_auth_server_interceptor.py +13 -12
  81. flwr/server/superlink/fleet/message_handler/message_handler.py +6 -5
  82. flwr/server/superlink/linkstate/__init__.py +2 -2
  83. flwr/server/superlink/linkstate/in_memory_linkstate.py +2 -10
  84. flwr/server/superlink/linkstate/linkstate.py +2 -21
  85. flwr/server/superlink/linkstate/linkstate_factory.py +16 -8
  86. flwr/server/superlink/linkstate/{sqlite_linkstate.py → sql_linkstate.py} +432 -534
  87. flwr/server/superlink/linkstate/utils.py +49 -2
  88. flwr/server/superlink/serverappio/serverappio_servicer.py +1 -33
  89. flwr/server/superlink/simulation/simulationio_servicer.py +0 -19
  90. flwr/server/utils/validator.py +1 -1
  91. flwr/server/workflow/default_workflows.py +2 -1
  92. flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +1 -1
  93. flwr/serverapp/strategy/bulyan.py +7 -1
  94. flwr/serverapp/strategy/dp_fixed_clipping.py +9 -1
  95. flwr/serverapp/strategy/fedavg.py +1 -1
  96. flwr/serverapp/strategy/fedxgb_cyclic.py +1 -1
  97. flwr/simulation/ray_transport/ray_client_proxy.py +2 -6
  98. flwr/simulation/run_simulation.py +3 -12
  99. flwr/simulation/simulationio_connection.py +3 -3
  100. flwr/{common → supercore}/address.py +7 -33
  101. flwr/supercore/app_utils.py +2 -1
  102. flwr/supercore/constant.py +24 -2
  103. flwr/supercore/corestate/{sqlite_corestate.py → sql_corestate.py} +19 -23
  104. flwr/supercore/credential_store/__init__.py +33 -0
  105. flwr/supercore/credential_store/credential_store.py +34 -0
  106. flwr/supercore/credential_store/file_credential_store.py +76 -0
  107. flwr/{common → supercore}/date.py +0 -11
  108. flwr/supercore/ffs/disk_ffs.py +1 -1
  109. flwr/supercore/object_store/object_store_factory.py +14 -6
  110. flwr/supercore/object_store/{sqlite_object_store.py → sql_object_store.py} +115 -117
  111. flwr/supercore/sql_mixin.py +315 -0
  112. flwr/supercore/state/__init__.py +15 -0
  113. flwr/supercore/state/alembic/__init__.py +15 -0
  114. flwr/supercore/state/alembic/env.py +103 -0
  115. flwr/supercore/state/alembic/script.py.mako +43 -0
  116. flwr/supercore/state/alembic/utils.py +239 -0
  117. flwr/supercore/state/alembic/versions/__init__.py +15 -0
  118. flwr/supercore/state/alembic/versions/rev_2026_01_28_initialize_migration_of_state_tables.py +200 -0
  119. flwr/supercore/state/schema/README.md +121 -0
  120. flwr/supercore/state/schema/__init__.py +15 -0
  121. flwr/supercore/state/schema/corestate_tables.py +36 -0
  122. flwr/supercore/state/schema/linkstate_tables.py +152 -0
  123. flwr/supercore/state/schema/objectstore_tables.py +90 -0
  124. flwr/supercore/superexec/run_superexec.py +2 -2
  125. flwr/supercore/utils.py +36 -1
  126. flwr/superlink/federation/federation_manager.py +2 -2
  127. flwr/superlink/federation/noop_federation_manager.py +8 -6
  128. flwr/superlink/servicer/control/control_servicer.py +19 -17
  129. flwr/supernode/cli/flower_supernode.py +2 -1
  130. flwr/supernode/runtime/run_clientapp.py +14 -14
  131. flwr/supernode/servicer/clientappio/clientappio_servicer.py +10 -8
  132. flwr/supernode/start_client_internal.py +10 -6
  133. {flwr-1.25.0.dist-info → flwr-1.26.0.dist-info}/METADATA +7 -5
  134. {flwr-1.25.0.dist-info → flwr-1.26.0.dist-info}/RECORD +137 -116
  135. flwr/cli/federation/show.py +0 -318
  136. flwr/common/pyproject.py +0 -42
  137. flwr/supercore/sqlite_mixin.py +0 -159
  138. /flwr/{common → supercore}/version.py +0 -0
  139. {flwr-1.25.0.dist-info → flwr-1.26.0.dist-info}/WHEEL +0 -0
  140. {flwr-1.25.0.dist-info → flwr-1.26.0.dist-info}/entry_points.txt +0 -0
@@ -18,13 +18,14 @@
18
18
  from dataclasses import dataclass
19
19
  from pathlib import Path
20
20
 
21
+ from flwr.app.user_config import UserConfig
21
22
  from flwr.common import Context, RecordDict
22
23
  from flwr.common.config import (
23
24
  get_fused_config,
24
25
  get_fused_config_from_dir,
25
26
  get_fused_config_from_fab,
26
27
  )
27
- from flwr.common.typing import Fab, Run, UserConfig
28
+ from flwr.common.typing import Fab, Run
28
29
 
29
30
 
30
31
  @dataclass()
@@ -19,6 +19,7 @@ import inspect
19
19
  from collections.abc import Callable, Iterator
20
20
  from contextlib import contextmanager
21
21
 
22
+ from flwr.app.message_type import MessageType
22
23
  from flwr.app.metadata import validate_message_type
23
24
  from flwr.client.client import Client
24
25
  from flwr.client.message_handler.message_handler import (
@@ -26,7 +27,7 @@ from flwr.client.message_handler.message_handler import (
26
27
  )
27
28
  from flwr.client.mod.utils import make_ffn
28
29
  from flwr.client.typing import ClientFnExt, Mod
29
- from flwr.common import Context, Message, MessageType
30
+ from flwr.common import Context, Message
30
31
  from flwr.common.logger import warn_deprecated_feature
31
32
 
32
33
  from .typing import ClientAppCallable
flwr/common/__init__.py CHANGED
@@ -15,12 +15,13 @@
15
15
  """Common components shared between server and client."""
16
16
 
17
17
 
18
+ from flwr.app.message_type import MessageType as MessageType
19
+
18
20
  from ..app.error import Error as Error
19
21
  from ..app.metadata import Metadata as Metadata
20
- from .constant import MessageType as MessageType
22
+ from ..supercore.date import now as now
21
23
  from .constant import MessageTypeLegacy as MessageTypeLegacy
22
24
  from .context import Context as Context
23
- from .date import now as now
24
25
  from .grpc import GRPC_MAX_MESSAGE_LENGTH
25
26
  from .logger import configure as configure
26
27
  from .logger import log as log
flwr/common/args.py CHANGED
@@ -89,13 +89,13 @@ def try_obtain_root_certificates(
89
89
  else:
90
90
  # Load the certificates if provided, or load the system certificates
91
91
  if root_cert_path is None:
92
- log(INFO, "Using system certificates")
92
+ log(INFO, "Using system certificates for TLS connection")
93
93
  root_certificates = None
94
94
  elif not isfile(root_cert_path):
95
95
  log(ERROR, "Path argument `--root-certificates` does not point to a file.")
96
96
  sys.exit(1)
97
97
  else:
98
- root_certificates = Path(root_cert_path).read_bytes()
98
+ root_certificates = Path(root_cert_path).expanduser().read_bytes()
99
99
  log(
100
100
  DEBUG,
101
101
  "Starting secure HTTPS channel to %s "
@@ -129,9 +129,9 @@ def try_obtain_server_certificates(
129
129
  if not isfile(args.ssl_keyfile):
130
130
  sys.exit("Path argument `--ssl-keyfile` does not point to a file.")
131
131
  certificates = (
132
- Path(args.ssl_ca_certfile).read_bytes(), # CA certificate
133
- Path(args.ssl_certfile).read_bytes(), # server certificate
134
- Path(args.ssl_keyfile).read_bytes(), # server private key
132
+ Path(args.ssl_ca_certfile).expanduser().read_bytes(), # CA certificate
133
+ Path(args.ssl_certfile).expanduser().read_bytes(), # server certificate
134
+ Path(args.ssl_keyfile).expanduser().read_bytes(), # server private key
135
135
  )
136
136
  return certificates
137
137
  if args.ssl_certfile or args.ssl_keyfile or args.ssl_ca_certfile:
flwr/common/config.py CHANGED
@@ -22,9 +22,10 @@ from io import BytesIO
22
22
  from pathlib import Path
23
23
  from typing import IO, Any, TypeVar, cast, get_args
24
24
 
25
+ import click
25
26
  import tomli
26
- import typer
27
27
 
28
+ from flwr.app.user_config import UserConfig, UserConfigValue
28
29
  from flwr.common.constant import (
29
30
  APP_DIR,
30
31
  FAB_CONFIG_FILE,
@@ -32,7 +33,7 @@ from flwr.common.constant import (
32
33
  FLWR_DIR,
33
34
  FLWR_HOME,
34
35
  )
35
- from flwr.common.typing import Run, UserConfig, UserConfigValue
36
+ from flwr.common.typing import Run
36
37
 
37
38
  from . import ConfigRecord, object_ref
38
39
 
@@ -41,7 +42,7 @@ T_dict = TypeVar("T_dict", bound=dict[str, Any]) # pylint: disable=invalid-name
41
42
 
42
43
  def get_flwr_dir(provided_path: str | None = None) -> Path:
43
44
  """Return the Flower home directory based on env variables."""
44
- if provided_path is None or not Path(provided_path).is_dir():
45
+ if provided_path is None or not Path(provided_path).expanduser().is_dir():
45
46
  return Path(
46
47
  os.getenv(
47
48
  FLWR_HOME,
@@ -212,7 +213,7 @@ def parse_config_args(config: list[str] | None, flatten: bool = True) -> dict[st
212
213
 
213
214
  # Handle if .toml file is passed
214
215
  if len(config) == 1 and config[0].endswith(".toml"):
215
- with Path(config[0]).open("rb") as config_file:
216
+ with Path(config[0]).expanduser().open("rb") as config_file:
216
217
  overrides = flatten_dict(tomli.load(config_file))
217
218
  return overrides
218
219
 
@@ -234,17 +235,13 @@ def parse_config_args(config: list[str] | None, flatten: bool = True) -> dict[st
234
235
  overrides.update(tomli.loads(toml_str))
235
236
  flat_overrides = flatten_dict(overrides) if flatten else overrides
236
237
  except tomli.TOMLDecodeError as err:
237
- typer.secho(
238
- "The provided configuration string is in an invalid format. "
238
+ raise click.ClickException(
239
+ "The provided configuration string is in an invalid format. "
239
240
  "The correct format should be, e.g., 'key1=123 key2=false "
240
241
  'key3="string"\', where values must be of type bool, int, '
241
242
  "string, or float. Ensure proper formatting with "
242
- "space-separated key-value pairs.",
243
- fg=typer.colors.RED,
244
- bold=True,
245
- err=True,
246
- )
247
- raise typer.Exit(code=1) from err
243
+ "space-separated key-value pairs."
244
+ ) from err
248
245
 
249
246
  return flat_overrides
250
247
 
@@ -333,8 +330,6 @@ def validate_fields_in_config(
333
330
  warnings.append('Recommended property "description" missing in [project]')
334
331
  if "license" not in config["project"]:
335
332
  warnings.append('Recommended property "license" missing in [project]')
336
- if "authors" not in config["project"]:
337
- warnings.append('Recommended property "authors" missing in [project]')
338
333
 
339
334
  if (
340
335
  "tool" not in config
@@ -378,13 +373,13 @@ def validate_config(
378
373
  is_valid, reason = object_ref.validate(serverapp_ref, check_module, project_dir)
379
374
 
380
375
  if not is_valid and isinstance(reason, str):
381
- return False, [reason], []
376
+ return False, [reason], warnings
382
377
 
383
378
  # Validate clientapp
384
379
  clientapp_ref = config["tool"]["flwr"]["app"]["components"]["clientapp"]
385
380
  is_valid, reason = object_ref.validate(clientapp_ref, check_module, project_dir)
386
381
 
387
382
  if not is_valid and isinstance(reason, str):
388
- return False, [reason], []
383
+ return False, [reason], warnings
389
384
 
390
- return True, [], []
385
+ return True, [], warnings
flwr/common/constant.py CHANGED
@@ -19,13 +19,11 @@ from __future__ import annotations
19
19
 
20
20
  import os
21
21
 
22
- TRANSPORT_TYPE_GRPC_BIDI = "grpc-bidi"
23
22
  TRANSPORT_TYPE_GRPC_RERE = "grpc-rere"
24
23
  TRANSPORT_TYPE_GRPC_ADAPTER = "grpc-adapter"
25
24
  TRANSPORT_TYPE_REST = "rest"
26
25
  TRANSPORT_TYPE_VCE = "vce"
27
26
  TRANSPORT_TYPES = [
28
- TRANSPORT_TYPE_GRPC_BIDI,
29
27
  TRANSPORT_TYPE_GRPC_RERE,
30
28
  TRANSPORT_TYPE_REST,
31
29
  TRANSPORT_TYPE_VCE,
@@ -182,19 +180,8 @@ SUPERNODE_NOT_CREATED_FROM_CLI_MESSAGE = "Invalid SuperNode credentials"
182
180
  PUBLIC_KEY_ALREADY_IN_USE_MESSAGE = "Public key already in use"
183
181
  PUBLIC_KEY_NOT_VALID = "The provided public key is not valid"
184
182
  NODE_NOT_FOUND_MESSAGE = "Node ID not found for account"
185
-
186
-
187
- class MessageType:
188
- """Message type."""
189
-
190
- TRAIN = "train"
191
- EVALUATE = "evaluate"
192
- QUERY = "query"
193
- SYSTEM = "system"
194
-
195
- def __new__(cls) -> MessageType:
196
- """Prevent instantiation."""
197
- raise TypeError(f"{cls.__name__} cannot be instantiated.")
183
+ FEDERATION_NOT_SPECIFIED_MESSAGE = "No federation specified in the request"
184
+ FEDERATION_NOT_FOUND_MESSAGE = "Federation '%s' does not exist"
198
185
 
199
186
 
200
187
  class MessageTypeLegacy:
@@ -323,4 +310,4 @@ class ExecPluginType:
323
310
 
324
311
  # Constants for No-op auth plugins
325
312
  NOOP_FLWR_AID = "<id:none>"
326
- NOOP_ACCOUNT_NAME = "<name:none>"
313
+ NOOP_ACCOUNT_NAME = "none"
flwr/common/context.py CHANGED
@@ -17,8 +17,9 @@
17
17
 
18
18
  from dataclasses import dataclass
19
19
 
20
+ from flwr.app.user_config import UserConfig
21
+
20
22
  from .record import RecordDict
21
- from .typing import UserConfig
22
23
 
23
24
 
24
25
  @dataclass
flwr/common/exit/exit.py CHANGED
@@ -23,8 +23,8 @@ from logging import ERROR, INFO
23
23
  from typing import Any, NoReturn
24
24
 
25
25
  from flwr.common import EventType, event
26
- from flwr.common.version import package_version
27
26
  from flwr.supercore.constant import FORCE_EXIT_TIMEOUT_SECONDS
27
+ from flwr.supercore.version import package_version
28
28
 
29
29
  from ..logger import log
30
30
  from .exit_code import EXIT_CODE_HELP
@@ -87,9 +87,6 @@ def flwr_exit(
87
87
  # Log the exit message
88
88
  log(log_level, exit_message)
89
89
 
90
- # Trigger exit handlers
91
- trigger_exit_handlers()
92
-
93
90
  # Start a daemon thread to force exit if graceful exit fails
94
91
  def force_exit() -> None:
95
92
  time.sleep(FORCE_EXIT_TIMEOUT_SECONDS)
@@ -97,6 +94,9 @@ def flwr_exit(
97
94
 
98
95
  threading.Thread(target=force_exit, daemon=True).start()
99
96
 
97
+ # Trigger exit handlers
98
+ trigger_exit_handlers()
99
+
100
100
  # Exit
101
101
  sys.exit(sys_exit_code)
102
102
 
@@ -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/grpc.py CHANGED
@@ -24,7 +24,8 @@ from typing import Any
24
24
 
25
25
  import grpc
26
26
 
27
- from .address import is_port_in_use
27
+ from flwr.supercore.address import is_port_in_use
28
+
28
29
  from .logger import log
29
30
 
30
31
  GRPC_MAX_MESSAGE_LENGTH: int = 2_147_483_647 # == 2048 * 1024 * 1024 -1 (2GB)
flwr/common/logger.py CHANGED
@@ -385,7 +385,7 @@ def start_log_uploader(
385
385
  ) -> threading.Thread:
386
386
  """Start the log uploader thread and return it."""
387
387
  thread = threading.Thread(
388
- target=_log_uploader, args=(log_queue, node_id, run_id, stub)
388
+ target=_log_uploader, args=(log_queue, node_id, run_id, stub), daemon=True
389
389
  )
390
390
  thread.start()
391
391
  return thread
flwr/common/message.py CHANGED
@@ -20,11 +20,11 @@ from __future__ import annotations
20
20
  from logging import WARNING
21
21
  from typing import Any, cast, overload
22
22
 
23
- from flwr.common.date import now
24
23
  from flwr.common.logger import warn_deprecated_feature
25
24
  from flwr.proto.message_pb2 import Message as ProtoMessage # pylint: disable=E0611
26
25
  from flwr.proto.message_pb2 import Metadata as ProtoMetadata # pylint: disable=E0611
27
26
  from flwr.proto.message_pb2 import ObjectIDs # pylint: disable=E0611
27
+ from flwr.supercore.date import now
28
28
 
29
29
  from ..app.error import Error
30
30
  from ..app.metadata import Metadata
@@ -21,7 +21,7 @@ import threading
21
21
  import time
22
22
  from collections.abc import Callable, Generator, Iterable
23
23
  from dataclasses import dataclass
24
- from logging import INFO, WARN
24
+ from logging import DEBUG, ERROR, INFO, WARN
25
25
  from typing import Any, cast
26
26
 
27
27
  import grpc
@@ -318,7 +318,7 @@ class RetryInvoker:
318
318
  return ret
319
319
 
320
320
 
321
- def _make_simple_grpc_retry_invoker() -> RetryInvoker:
321
+ def make_simple_grpc_retry_invoker() -> RetryInvoker:
322
322
  """Create a simple gRPC retry invoker."""
323
323
  lock = threading.Lock()
324
324
  system_healthy = threading.Event()
@@ -334,8 +334,11 @@ def _make_simple_grpc_retry_invoker() -> RetryInvoker:
334
334
  retry_state.tries,
335
335
  )
336
336
 
337
- def _on_backoff(_: RetryState) -> None:
337
+ def _on_backoff(retry_state: RetryState) -> None:
338
338
  system_healthy.clear()
339
+ log(
340
+ DEBUG, "Connection attempt failed with exception: %s", retry_state.exception
341
+ )
339
342
 
340
343
  def _on_giveup(retry_state: RetryState) -> None:
341
344
  system_healthy.clear()
@@ -351,7 +354,12 @@ def _make_simple_grpc_retry_invoker() -> RetryInvoker:
351
354
  if e.code() == grpc.StatusCode.PERMISSION_DENIED: # type: ignore
352
355
  raise RunNotRunningException
353
356
  if e.code() == grpc.StatusCode.UNAVAILABLE: # type: ignore
354
- return False
357
+ # Check if this is an SSL handshake failure - these should fail fast
358
+ details = str(e.details() if hasattr(e, "details") else "").lower()
359
+ if "handshake failed" in details:
360
+ log(ERROR, "SSL/TLS handshake error detected.")
361
+ return True # Give up on SSL/TLS handshake errors
362
+ return False # Retry on other UNAVAILABLE errors (network issues)
355
363
  return True
356
364
 
357
365
  def _wait(wait_time: float) -> None:
@@ -386,7 +394,7 @@ def _make_simple_grpc_retry_invoker() -> RetryInvoker:
386
394
  )
387
395
 
388
396
 
389
- def _wrap_stub(
397
+ def wrap_stub(
390
398
  stub: (
391
399
  ServerAppIoStub | ClientAppIoStub | SimulationIoStub | FleetStub | GrpcAdapter
392
400
  ),
@@ -18,7 +18,7 @@
18
18
  from typing import Any
19
19
 
20
20
  import numpy as np
21
- from numpy.typing import DTypeLike, NDArray
21
+ from numpy.typing import NDArray
22
22
 
23
23
 
24
24
  def factor_combine(factor: int, parameters: list[NDArray[Any]]) -> list[NDArray[Any]]:
@@ -38,8 +38,11 @@ def get_parameters_shape(parameters: list[NDArray[Any]]) -> list[tuple[int, ...]
38
38
  return [arr.shape for arr in parameters]
39
39
 
40
40
 
41
+ default_numpy_dtype = np.dtype(np.int64)
42
+
43
+
41
44
  def get_zero_parameters(
42
- dimensions_list: list[tuple[int, ...]], dtype: DTypeLike = np.int64
45
+ dimensions_list: list[tuple[int, ...]], dtype: np.dtype[Any] = default_numpy_dtype
43
46
  ) -> list[NDArray[Any]]:
44
47
  """Generate zero parameters based on the dimensions list."""
45
48
  return [np.zeros(dimensions, dtype=dtype) for dimensions in dimensions_list]
flwr/common/serde.py CHANGED
@@ -17,6 +17,8 @@
17
17
 
18
18
  from typing import Any, cast
19
19
 
20
+ from flwr.app.user_config import UserConfig, UserConfigValue
21
+
20
22
  # pylint: disable=E0611
21
23
  from flwr.proto.fab_pb2 import Fab as ProtoFab
22
24
  from flwr.proto.message_pb2 import Context as ProtoContext
@@ -513,7 +515,7 @@ def fab_from_proto(fab: ProtoFab) -> typing.Fab:
513
515
  # === User configs ===
514
516
 
515
517
 
516
- def user_config_to_proto(user_config: typing.UserConfig) -> Any:
518
+ def user_config_to_proto(user_config: UserConfig) -> Any:
517
519
  """Serialize `UserConfig` to ProtoBuf."""
518
520
  proto = {}
519
521
  for key, value in user_config.items():
@@ -521,7 +523,7 @@ def user_config_to_proto(user_config: typing.UserConfig) -> Any:
521
523
  return proto
522
524
 
523
525
 
524
- def user_config_from_proto(proto: Any) -> typing.UserConfig:
526
+ def user_config_from_proto(proto: Any) -> UserConfig:
525
527
  """Deserialize `UserConfig` from ProtoBuf."""
526
528
  metrics = {}
527
529
  for key, value in proto.items():
@@ -529,7 +531,7 @@ def user_config_from_proto(proto: Any) -> typing.UserConfig:
529
531
  return metrics
530
532
 
531
533
 
532
- def user_config_value_to_proto(user_config_value: typing.UserConfigValue) -> Scalar:
534
+ def user_config_value_to_proto(user_config_value: UserConfigValue) -> Scalar:
533
535
  """Serialize `UserConfigValue` to ProtoBuf."""
534
536
  if isinstance(user_config_value, bool):
535
537
  return Scalar(bool=user_config_value)
@@ -548,11 +550,11 @@ def user_config_value_to_proto(user_config_value: typing.UserConfigValue) -> Sca
548
550
  )
549
551
 
550
552
 
551
- def user_config_value_from_proto(scalar_msg: Scalar) -> typing.UserConfigValue:
553
+ def user_config_value_from_proto(scalar_msg: Scalar) -> UserConfigValue:
552
554
  """Deserialize `UserConfigValue` from ProtoBuf."""
553
555
  scalar_field = scalar_msg.WhichOneof("scalar")
554
556
  scalar = getattr(scalar_msg, cast(str, scalar_field))
555
- return cast(typing.UserConfigValue, scalar)
557
+ return cast(UserConfigValue, scalar)
556
558
 
557
559
 
558
560
  # === Message messages ===
flwr/common/telemetry.py CHANGED
@@ -28,7 +28,7 @@ from pathlib import Path
28
28
  from typing import Any, cast
29
29
 
30
30
  from flwr.common.constant import FLWR_DIR
31
- from flwr.common.version import package_name, package_version
31
+ from flwr.supercore.version import package_name, package_version
32
32
 
33
33
  FLWR_TELEMETRY_ENABLED = os.getenv("FLWR_TELEMETRY_ENABLED", "1")
34
34
  FLWR_TELEMETRY_LOGGING = os.getenv("FLWR_TELEMETRY_LOGGING", "0")
flwr/common/typing.py CHANGED
@@ -23,6 +23,8 @@ from typing import Any
23
23
  import numpy as np
24
24
  import numpy.typing as npt
25
25
 
26
+ from flwr.app.user_config import UserConfig
27
+ from flwr.proto.federation_pb2 import Account # pylint: disable=E0611
26
28
  from flwr.proto.node_pb2 import NodeInfo # pylint: disable=E0611
27
29
 
28
30
  NDArray = npt.NDArray[Any]
@@ -64,8 +66,6 @@ Config = dict[str, Scalar]
64
66
  Properties = dict[str, Scalar]
65
67
 
66
68
  # Value type for user configs
67
- UserConfigValue = bool | float | int | str
68
- UserConfig = dict[str, UserConfigValue]
69
69
 
70
70
 
71
71
  class Code(Enum):
@@ -343,6 +343,7 @@ class Federation:
343
343
  """Federation details."""
344
344
 
345
345
  name: str
346
- member_aids: list[str]
346
+ description: str
347
+ accounts: list[Account]
347
348
  nodes: list[NodeInfo]
348
349
  runs: list[Run]
flwr/compat/client/app.py CHANGED
@@ -25,6 +25,7 @@ from cryptography.hazmat.primitives.asymmetric import ec
25
25
  from grpc import RpcError
26
26
 
27
27
  from flwr.app.error import Error
28
+ from flwr.app.user_config import UserConfig
28
29
  from flwr.cli.config_utils import get_fab_metadata
29
30
  from flwr.cli.install import install_from_fab
30
31
  from flwr.client.client import Client
@@ -34,18 +35,14 @@ from flwr.client.run_info_store import DeprecatedRunInfoStore
34
35
  from flwr.client.typing import ClientFnExt
35
36
  from flwr.clientapp.client_app import ClientApp, LoadClientAppError
36
37
  from flwr.common import GRPC_MAX_MESSAGE_LENGTH, Context, EventType, Message, event
37
- from flwr.common.address import parse_address
38
- from flwr.common.constant import (
39
- MAX_RETRY_DELAY,
40
- TRANSPORT_TYPE_GRPC_BIDI,
41
- TRANSPORT_TYPES,
42
- ErrorCode,
43
- )
38
+ from flwr.common.constant import MAX_RETRY_DELAY, ErrorCode
44
39
  from flwr.common.exit import ExitCode, flwr_exit
45
40
  from flwr.common.logger import log, warn_deprecated_feature
46
41
  from flwr.common.retry_invoker import RetryInvoker, RetryState, exponential
47
- from flwr.common.typing import Fab, Run, RunNotRunningException, UserConfig
42
+ from flwr.common.typing import Fab, Run, RunNotRunningException
48
43
  from flwr.compat.client.grpc_client.connection import grpc_connection
44
+ from flwr.compat.common.constant import TRANSPORT_TYPE_GRPC_BIDI, TRANSPORT_TYPES_COMPAT
45
+ from flwr.supercore.address import parse_address
49
46
  from flwr.supercore.object_store import ObjectStoreFactory
50
47
  from flwr.supernode.nodestate import NodeStateFactory
51
48
 
@@ -675,7 +672,7 @@ def _init_connection(transport: str | None, server_address: str) -> tuple[
675
672
  connection, error_type = grpc_connection, RpcError
676
673
  else:
677
674
  raise ValueError(
678
- f"Unknown transport type: {transport} (possible: {TRANSPORT_TYPES})"
675
+ f"Unknown transport type: {transport} (possible: {TRANSPORT_TYPES_COMPAT})"
679
676
  )
680
677
 
681
678
  return connection, address, error_type
@@ -25,6 +25,7 @@ from typing import cast
25
25
 
26
26
  from cryptography.hazmat.primitives.asymmetric import ec
27
27
 
28
+ from flwr.app.message_type import MessageType
28
29
  from flwr.common import (
29
30
  DEFAULT_TTL,
30
31
  GRPC_MAX_MESSAGE_LENGTH,
@@ -36,7 +37,7 @@ from flwr.common import (
36
37
  )
37
38
  from flwr.common import recorddict_compat as compat
38
39
  from flwr.common import serde
39
- from flwr.common.constant import MessageType, MessageTypeLegacy
40
+ from flwr.common.constant import MessageTypeLegacy
40
41
  from flwr.common.grpc import create_channel, on_channel_state_change
41
42
  from flwr.common.logger import log
42
43
  from flwr.common.message import make_message
@@ -0,0 +1,29 @@
1
+ # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+ """Flower constants."""
16
+
17
+ from flwr.common.constant import (
18
+ TRANSPORT_TYPE_GRPC_RERE,
19
+ TRANSPORT_TYPE_REST,
20
+ TRANSPORT_TYPE_VCE,
21
+ )
22
+
23
+ TRANSPORT_TYPE_GRPC_BIDI = "grpc-bidi"
24
+ TRANSPORT_TYPES_COMPAT = [
25
+ TRANSPORT_TYPE_GRPC_BIDI,
26
+ TRANSPORT_TYPE_GRPC_RERE,
27
+ TRANSPORT_TYPE_REST,
28
+ TRANSPORT_TYPE_VCE,
29
+ ]
flwr/compat/server/app.py CHANGED
@@ -19,7 +19,6 @@ import sys
19
19
  from logging import INFO
20
20
 
21
21
  from flwr.common import GRPC_MAX_MESSAGE_LENGTH, EventType, event
22
- from flwr.common.address import parse_address
23
22
  from flwr.common.constant import FLEET_API_GRPC_BIDI_DEFAULT_ADDRESS
24
23
  from flwr.common.exit import register_signal_handlers
25
24
  from flwr.common.logger import log, warn_deprecated_feature
@@ -29,6 +28,7 @@ from flwr.server.server import Server, init_defaults, run_fl
29
28
  from flwr.server.server_config import ServerConfig
30
29
  from flwr.server.strategy import Strategy
31
30
  from flwr.server.superlink.fleet.grpc_bidi.grpc_server import start_grpc_server
31
+ from flwr.supercore.address import parse_address
32
32
 
33
33
 
34
34
  def start_server( # pylint: disable=too-many-arguments,too-many-locals
@@ -28,7 +28,7 @@ from flwr.proto import message_pb2 as flwr_dot_proto_dot_message__pb2
28
28
  from flwr.proto import appio_pb2 as flwr_dot_proto_dot_appio__pb2
29
29
 
30
30
 
31
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1c\x66lwr/proto/clientappio.proto\x12\nflwr.proto\x1a\x1a\x66lwr/proto/heartbeat.proto\x1a\x14\x66lwr/proto/run.proto\x1a\x18\x66lwr/proto/message.proto\x1a\x16\x66lwr/proto/appio.proto2\xeb\x07\n\x0b\x43lientAppIo\x12_\n\x10ListAppsToLaunch\x12#.flwr.proto.ListAppsToLaunchRequest\x1a$.flwr.proto.ListAppsToLaunchResponse\"\x00\x12S\n\x0cRequestToken\x12\x1f.flwr.proto.RequestTokenRequest\x1a .flwr.proto.RequestTokenResponse\"\x00\x12\x41\n\x06GetRun\x12\x19.flwr.proto.GetRunRequest\x1a\x1a.flwr.proto.GetRunResponse\"\x00\x12\\\n\x13PullClientAppInputs\x12 .flwr.proto.PullAppInputsRequest\x1a!.flwr.proto.PullAppInputsResponse\"\x00\x12_\n\x14PushClientAppOutputs\x12!.flwr.proto.PushAppOutputsRequest\x1a\".flwr.proto.PushAppOutputsResponse\"\x00\x12X\n\x0bPushMessage\x12\".flwr.proto.PushAppMessagesRequest\x1a#.flwr.proto.PushAppMessagesResponse\"\x00\x12X\n\x0bPullMessage\x12\".flwr.proto.PullAppMessagesRequest\x1a#.flwr.proto.PullAppMessagesResponse\"\x00\x12_\n\x10SendAppHeartbeat\x12#.flwr.proto.SendAppHeartbeatRequest\x1a$.flwr.proto.SendAppHeartbeatResponse\"\x00\x12M\n\nPushObject\x12\x1d.flwr.proto.PushObjectRequest\x1a\x1e.flwr.proto.PushObjectResponse\"\x00\x12M\n\nPullObject\x12\x1d.flwr.proto.PullObjectRequest\x1a\x1e.flwr.proto.PullObjectResponse\"\x00\x12q\n\x16\x43onfirmMessageReceived\x12).flwr.proto.ConfirmMessageReceivedRequest\x1a*.flwr.proto.ConfirmMessageReceivedResponse\"\x00\x62\x06proto3')
31
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1c\x66lwr/proto/clientappio.proto\x12\nflwr.proto\x1a\x1a\x66lwr/proto/heartbeat.proto\x1a\x14\x66lwr/proto/run.proto\x1a\x18\x66lwr/proto/message.proto\x1a\x16\x66lwr/proto/appio.proto2\xdf\x07\n\x0b\x43lientAppIo\x12_\n\x10ListAppsToLaunch\x12#.flwr.proto.ListAppsToLaunchRequest\x1a$.flwr.proto.ListAppsToLaunchResponse\"\x00\x12S\n\x0cRequestToken\x12\x1f.flwr.proto.RequestTokenRequest\x1a .flwr.proto.RequestTokenResponse\"\x00\x12\x41\n\x06GetRun\x12\x19.flwr.proto.GetRunRequest\x1a\x1a.flwr.proto.GetRunResponse\"\x00\x12_\n\x10SendAppHeartbeat\x12#.flwr.proto.SendAppHeartbeatRequest\x1a$.flwr.proto.SendAppHeartbeatResponse\"\x00\x12V\n\rPullAppInputs\x12 .flwr.proto.PullAppInputsRequest\x1a!.flwr.proto.PullAppInputsResponse\"\x00\x12Y\n\x0ePushAppOutputs\x12!.flwr.proto.PushAppOutputsRequest\x1a\".flwr.proto.PushAppOutputsResponse\"\x00\x12M\n\nPushObject\x12\x1d.flwr.proto.PushObjectRequest\x1a\x1e.flwr.proto.PushObjectResponse\"\x00\x12M\n\nPullObject\x12\x1d.flwr.proto.PullObjectRequest\x1a\x1e.flwr.proto.PullObjectResponse\"\x00\x12q\n\x16\x43onfirmMessageReceived\x12).flwr.proto.ConfirmMessageReceivedRequest\x1a*.flwr.proto.ConfirmMessageReceivedResponse\"\x00\x12X\n\x0bPushMessage\x12\".flwr.proto.PushAppMessagesRequest\x1a#.flwr.proto.PushAppMessagesResponse\"\x00\x12X\n\x0bPullMessage\x12\".flwr.proto.PullAppMessagesRequest\x1a#.flwr.proto.PullAppMessagesResponse\"\x00\x62\x06proto3')
32
32
 
33
33
  _globals = globals()
34
34
  _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -36,5 +36,5 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'flwr.proto.clientappio_pb2'
36
36
  if not _descriptor._USE_C_DESCRIPTORS:
37
37
  DESCRIPTOR._loaded_options = None
38
38
  _globals['_CLIENTAPPIO']._serialized_start=145
39
- _globals['_CLIENTAPPIO']._serialized_end=1148
39
+ _globals['_CLIENTAPPIO']._serialized_end=1136
40
40
  # @@protoc_insertion_point(module_scope)