flwr 1.24.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 (204) 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 +25 -66
  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 +72 -61
  25. flwr/cli/new/new.py +98 -309
  26. flwr/cli/pull.py +19 -37
  27. flwr/cli/run/run.py +87 -100
  28. flwr/cli/run_utils.py +23 -5
  29. flwr/cli/stop.py +33 -74
  30. flwr/cli/supernode/ls.py +35 -62
  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 -412
  35. flwr/client/grpc_adapter_client/connection.py +2 -2
  36. flwr/client/grpc_rere_client/connection.py +9 -6
  37. flwr/client/grpc_rere_client/grpc_adapter.py +1 -1
  38. flwr/client/message_handler/message_handler.py +2 -1
  39. flwr/client/mod/centraldp_mods.py +1 -1
  40. flwr/client/mod/localdp_mod.py +1 -1
  41. flwr/client/mod/secure_aggregation/secaggplus_mod.py +1 -1
  42. flwr/client/rest_client/connection.py +6 -4
  43. flwr/client/run_info_store.py +2 -1
  44. flwr/clientapp/client_app.py +2 -1
  45. flwr/common/__init__.py +3 -2
  46. flwr/common/args.py +5 -5
  47. flwr/common/config.py +12 -17
  48. flwr/common/constant.py +3 -16
  49. flwr/common/context.py +2 -1
  50. flwr/common/exit/exit.py +4 -4
  51. flwr/common/exit/exit_code.py +6 -0
  52. flwr/common/grpc.py +2 -1
  53. flwr/common/logger.py +1 -1
  54. flwr/common/message.py +1 -1
  55. flwr/common/retry_invoker.py +13 -5
  56. flwr/common/secure_aggregation/ndarrays_arithmetic.py +5 -2
  57. flwr/common/serde.py +13 -5
  58. flwr/common/telemetry.py +1 -1
  59. flwr/common/typing.py +10 -3
  60. flwr/compat/client/app.py +6 -9
  61. flwr/compat/client/grpc_client/connection.py +2 -1
  62. flwr/compat/common/constant.py +29 -0
  63. flwr/compat/server/app.py +1 -1
  64. flwr/proto/clientappio_pb2.py +2 -2
  65. flwr/proto/clientappio_pb2_grpc.py +104 -88
  66. flwr/proto/clientappio_pb2_grpc.pyi +140 -80
  67. flwr/proto/federation_pb2.py +5 -3
  68. flwr/proto/federation_pb2.pyi +32 -2
  69. flwr/proto/fleet_pb2.py +10 -10
  70. flwr/proto/fleet_pb2.pyi +5 -1
  71. flwr/proto/run_pb2.py +18 -26
  72. flwr/proto/run_pb2.pyi +10 -58
  73. flwr/proto/serverappio_pb2.py +2 -2
  74. flwr/proto/serverappio_pb2_grpc.py +138 -207
  75. flwr/proto/serverappio_pb2_grpc.pyi +189 -155
  76. flwr/proto/simulationio_pb2.py +2 -2
  77. flwr/proto/simulationio_pb2_grpc.py +62 -90
  78. flwr/proto/simulationio_pb2_grpc.pyi +95 -55
  79. flwr/server/app.py +7 -13
  80. flwr/server/compat/grid_client_proxy.py +2 -1
  81. flwr/server/grid/grpc_grid.py +5 -5
  82. flwr/server/serverapp/app.py +11 -4
  83. flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +1 -1
  84. flwr/server/superlink/fleet/grpc_rere/node_auth_server_interceptor.py +13 -12
  85. flwr/server/superlink/fleet/message_handler/message_handler.py +42 -2
  86. flwr/server/superlink/linkstate/__init__.py +2 -2
  87. flwr/server/superlink/linkstate/in_memory_linkstate.py +36 -10
  88. flwr/server/superlink/linkstate/linkstate.py +34 -21
  89. flwr/server/superlink/linkstate/linkstate_factory.py +16 -8
  90. flwr/server/superlink/linkstate/{sqlite_linkstate.py → sql_linkstate.py} +471 -516
  91. flwr/server/superlink/linkstate/utils.py +49 -2
  92. flwr/server/superlink/serverappio/serverappio_servicer.py +1 -33
  93. flwr/server/superlink/simulation/simulationio_servicer.py +0 -19
  94. flwr/server/utils/validator.py +1 -1
  95. flwr/server/workflow/default_workflows.py +2 -1
  96. flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +1 -1
  97. flwr/serverapp/strategy/bulyan.py +7 -1
  98. flwr/serverapp/strategy/dp_fixed_clipping.py +9 -1
  99. flwr/serverapp/strategy/fedavg.py +1 -1
  100. flwr/serverapp/strategy/fedxgb_cyclic.py +1 -1
  101. flwr/simulation/ray_transport/ray_client_proxy.py +2 -6
  102. flwr/simulation/run_simulation.py +3 -12
  103. flwr/simulation/simulationio_connection.py +3 -3
  104. flwr/{common → supercore}/address.py +7 -33
  105. flwr/supercore/app_utils.py +2 -1
  106. flwr/supercore/constant.py +27 -2
  107. flwr/supercore/corestate/{sqlite_corestate.py → sql_corestate.py} +19 -23
  108. flwr/supercore/credential_store/__init__.py +33 -0
  109. flwr/supercore/credential_store/credential_store.py +34 -0
  110. flwr/supercore/credential_store/file_credential_store.py +76 -0
  111. flwr/{common → supercore}/date.py +0 -11
  112. flwr/supercore/ffs/disk_ffs.py +1 -1
  113. flwr/supercore/object_store/object_store_factory.py +14 -6
  114. flwr/supercore/object_store/{sqlite_object_store.py → sql_object_store.py} +115 -117
  115. flwr/supercore/sql_mixin.py +315 -0
  116. flwr/{cli/new/templates → supercore/state}/__init__.py +2 -2
  117. flwr/{cli/new/templates/app/code/flwr_tune → supercore/state/alembic}/__init__.py +2 -2
  118. flwr/supercore/state/alembic/env.py +103 -0
  119. flwr/supercore/state/alembic/script.py.mako +43 -0
  120. flwr/supercore/state/alembic/utils.py +239 -0
  121. flwr/{cli/new/templates/app → supercore/state/alembic/versions}/__init__.py +2 -2
  122. flwr/supercore/state/alembic/versions/rev_2026_01_28_initialize_migration_of_state_tables.py +200 -0
  123. flwr/supercore/state/schema/README.md +121 -0
  124. flwr/{cli/new/templates/app/code → supercore/state/schema}/__init__.py +2 -2
  125. flwr/supercore/state/schema/corestate_tables.py +36 -0
  126. flwr/supercore/state/schema/linkstate_tables.py +152 -0
  127. flwr/supercore/state/schema/objectstore_tables.py +90 -0
  128. flwr/supercore/superexec/run_superexec.py +2 -2
  129. flwr/supercore/utils.py +225 -0
  130. flwr/superlink/federation/federation_manager.py +2 -2
  131. flwr/superlink/federation/noop_federation_manager.py +8 -6
  132. flwr/superlink/servicer/control/control_grpc.py +2 -0
  133. flwr/superlink/servicer/control/control_servicer.py +106 -21
  134. flwr/supernode/cli/flower_supernode.py +2 -1
  135. flwr/supernode/nodestate/in_memory_nodestate.py +62 -1
  136. flwr/supernode/nodestate/nodestate.py +45 -0
  137. flwr/supernode/runtime/run_clientapp.py +14 -14
  138. flwr/supernode/servicer/clientappio/clientappio_servicer.py +13 -5
  139. flwr/supernode/start_client_internal.py +17 -10
  140. {flwr-1.24.0.dist-info → flwr-1.26.0.dist-info}/METADATA +8 -8
  141. {flwr-1.24.0.dist-info → flwr-1.26.0.dist-info}/RECORD +144 -184
  142. flwr/cli/federation/show.py +0 -317
  143. flwr/cli/new/templates/app/.gitignore.tpl +0 -163
  144. flwr/cli/new/templates/app/LICENSE.tpl +0 -202
  145. flwr/cli/new/templates/app/README.baseline.md.tpl +0 -127
  146. flwr/cli/new/templates/app/README.flowertune.md.tpl +0 -68
  147. flwr/cli/new/templates/app/README.md.tpl +0 -37
  148. flwr/cli/new/templates/app/code/__init__.baseline.py.tpl +0 -1
  149. flwr/cli/new/templates/app/code/__init__.py.tpl +0 -1
  150. flwr/cli/new/templates/app/code/__init__.pytorch_legacy_api.py.tpl +0 -1
  151. flwr/cli/new/templates/app/code/client.baseline.py.tpl +0 -75
  152. flwr/cli/new/templates/app/code/client.huggingface.py.tpl +0 -93
  153. flwr/cli/new/templates/app/code/client.jax.py.tpl +0 -71
  154. flwr/cli/new/templates/app/code/client.mlx.py.tpl +0 -102
  155. flwr/cli/new/templates/app/code/client.numpy.py.tpl +0 -46
  156. flwr/cli/new/templates/app/code/client.pytorch.py.tpl +0 -80
  157. flwr/cli/new/templates/app/code/client.pytorch_legacy_api.py.tpl +0 -55
  158. flwr/cli/new/templates/app/code/client.sklearn.py.tpl +0 -108
  159. flwr/cli/new/templates/app/code/client.tensorflow.py.tpl +0 -82
  160. flwr/cli/new/templates/app/code/client.xgboost.py.tpl +0 -110
  161. flwr/cli/new/templates/app/code/dataset.baseline.py.tpl +0 -36
  162. flwr/cli/new/templates/app/code/flwr_tune/client_app.py.tpl +0 -92
  163. flwr/cli/new/templates/app/code/flwr_tune/dataset.py.tpl +0 -87
  164. flwr/cli/new/templates/app/code/flwr_tune/models.py.tpl +0 -56
  165. flwr/cli/new/templates/app/code/flwr_tune/server_app.py.tpl +0 -73
  166. flwr/cli/new/templates/app/code/flwr_tune/strategy.py.tpl +0 -78
  167. flwr/cli/new/templates/app/code/model.baseline.py.tpl +0 -66
  168. flwr/cli/new/templates/app/code/server.baseline.py.tpl +0 -43
  169. flwr/cli/new/templates/app/code/server.huggingface.py.tpl +0 -42
  170. flwr/cli/new/templates/app/code/server.jax.py.tpl +0 -39
  171. flwr/cli/new/templates/app/code/server.mlx.py.tpl +0 -41
  172. flwr/cli/new/templates/app/code/server.numpy.py.tpl +0 -38
  173. flwr/cli/new/templates/app/code/server.pytorch.py.tpl +0 -41
  174. flwr/cli/new/templates/app/code/server.pytorch_legacy_api.py.tpl +0 -31
  175. flwr/cli/new/templates/app/code/server.sklearn.py.tpl +0 -44
  176. flwr/cli/new/templates/app/code/server.tensorflow.py.tpl +0 -38
  177. flwr/cli/new/templates/app/code/server.xgboost.py.tpl +0 -56
  178. flwr/cli/new/templates/app/code/strategy.baseline.py.tpl +0 -1
  179. flwr/cli/new/templates/app/code/task.huggingface.py.tpl +0 -98
  180. flwr/cli/new/templates/app/code/task.jax.py.tpl +0 -57
  181. flwr/cli/new/templates/app/code/task.mlx.py.tpl +0 -102
  182. flwr/cli/new/templates/app/code/task.numpy.py.tpl +0 -7
  183. flwr/cli/new/templates/app/code/task.pytorch.py.tpl +0 -99
  184. flwr/cli/new/templates/app/code/task.pytorch_legacy_api.py.tpl +0 -111
  185. flwr/cli/new/templates/app/code/task.sklearn.py.tpl +0 -67
  186. flwr/cli/new/templates/app/code/task.tensorflow.py.tpl +0 -52
  187. flwr/cli/new/templates/app/code/task.xgboost.py.tpl +0 -67
  188. flwr/cli/new/templates/app/code/utils.baseline.py.tpl +0 -1
  189. flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +0 -146
  190. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +0 -80
  191. flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +0 -65
  192. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +0 -52
  193. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +0 -56
  194. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +0 -49
  195. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +0 -53
  196. flwr/cli/new/templates/app/pyproject.pytorch_legacy_api.toml.tpl +0 -53
  197. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +0 -52
  198. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +0 -53
  199. flwr/cli/new/templates/app/pyproject.xgboost.toml.tpl +0 -61
  200. flwr/common/pyproject.py +0 -42
  201. flwr/supercore/sqlite_mixin.py +0 -159
  202. /flwr/{common → supercore}/version.py +0 -0
  203. {flwr-1.24.0.dist-info → flwr-1.26.0.dist-info}/WHEEL +0 -0
  204. {flwr-1.24.0.dist-info → flwr-1.26.0.dist-info}/entry_points.txt +0 -0
@@ -45,7 +45,7 @@ def grpc_adapter( # pylint: disable=R0913,too-many-positional-arguments
45
45
  tuple[
46
46
  int,
47
47
  Callable[[], tuple[Message, ObjectTree] | None],
48
- Callable[[Message, ObjectTree], set[str]],
48
+ Callable[[Message, ObjectTree, float], set[str]],
49
49
  Callable[[int], Run],
50
50
  Callable[[str, int], Fab],
51
51
  Callable[[int, str], bytes],
@@ -81,7 +81,7 @@ def grpc_adapter( # pylint: disable=R0913,too-many-positional-arguments
81
81
  -------
82
82
  node_id : int
83
83
  receive : Callable[[], Optional[tuple[Message, ObjectTree]]]
84
- send : Callable[[Message, ObjectTree], set[str]]
84
+ send : Callable[[Message, ObjectTree, float], set[str]]
85
85
  get_run : Callable[[int], Run]
86
86
  get_fab : Callable[[str, int], Fab]
87
87
  pull_object : Callable[[str], bytes]
@@ -33,7 +33,7 @@ from flwr.common.inflatable_protobuf_utils import (
33
33
  )
34
34
  from flwr.common.logger import log
35
35
  from flwr.common.message import Message, remove_content_from_message
36
- from flwr.common.retry_invoker import RetryInvoker, _wrap_stub
36
+ from flwr.common.retry_invoker import RetryInvoker, wrap_stub
37
37
  from flwr.common.serde import (
38
38
  fab_from_proto,
39
39
  message_from_proto,
@@ -83,7 +83,7 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
83
83
  tuple[
84
84
  int,
85
85
  Callable[[], tuple[Message, ObjectTree] | None],
86
- Callable[[Message, ObjectTree], set[str]],
86
+ Callable[[Message, ObjectTree, float], set[str]],
87
87
  Callable[[int], Run],
88
88
  Callable[[str, int], Fab],
89
89
  Callable[[int, str], bytes],
@@ -128,7 +128,7 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
128
128
  -------
129
129
  node_id : int
130
130
  receive : Callable[[], Optional[tuple[Message, ObjectTree]]]
131
- send : Callable[[Message, ObjectTree], set[str]]
131
+ send : Callable[[Message, ObjectTree, float], set[str]]
132
132
  get_run : Callable[[int], Run]
133
133
  get_fab : Callable[[str, int], Fab]
134
134
  pull_object : Callable[[str], bytes]
@@ -136,7 +136,7 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
136
136
  confirm_message_received : Callable[[str], None]
137
137
  """
138
138
  if isinstance(root_certificates, str):
139
- root_certificates = Path(root_certificates).read_bytes()
139
+ root_certificates = Path(root_certificates).expanduser().read_bytes()
140
140
 
141
141
  # Automatic node auth: generate keys if user didn't provide any
142
142
  self_registered = False
@@ -165,7 +165,7 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
165
165
  node: Node | None = None
166
166
 
167
167
  # Wrap stub
168
- _wrap_stub(stub, retry_invoker)
168
+ wrap_stub(stub, retry_invoker)
169
169
  ###########################################################################
170
170
  # SuperNode functions
171
171
  ###########################################################################
@@ -277,7 +277,9 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
277
277
  # Return the Message and its object tree
278
278
  return in_message, object_tree
279
279
 
280
- def send(message: Message, object_tree: ObjectTree) -> set[str]:
280
+ def send(
281
+ message: Message, object_tree: ObjectTree, clientapp_runtime: float
282
+ ) -> set[str]:
281
283
  """Send the message with its ObjectTree to SuperLink."""
282
284
  # Get Node
283
285
  if node is None:
@@ -293,6 +295,7 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
293
295
  node=node,
294
296
  messages_list=[message_to_proto(message)],
295
297
  message_object_trees=[object_tree],
298
+ clientapp_runtime_list=[clientapp_runtime],
296
299
  )
297
300
  response: PushMessagesResponse = stub.PushMessages(request=request)
298
301
 
@@ -32,7 +32,6 @@ from flwr.common.constant import (
32
32
  GRPC_ADAPTER_METADATA_MESSAGE_QUALNAME_KEY,
33
33
  GRPC_ADAPTER_METADATA_SHOULD_EXIT_KEY,
34
34
  )
35
- from flwr.common.version import package_name, package_version
36
35
  from flwr.proto.fab_pb2 import GetFabRequest, GetFabResponse # pylint: disable=E0611
37
36
  from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
38
37
  ActivateNodeRequest,
@@ -64,6 +63,7 @@ from flwr.proto.message_pb2 import ( # pylint: disable=E0611
64
63
  )
65
64
  from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611
66
65
  from flwr.supercore.constant import FORCE_EXIT_TIMEOUT_SECONDS
66
+ from flwr.supercore.version import package_name, package_version
67
67
 
68
68
  T = TypeVar("T", bound=GrpcMessage)
69
69
 
@@ -18,6 +18,7 @@
18
18
  from logging import WARN
19
19
  from typing import cast
20
20
 
21
+ from flwr.app.message_type import MessageType
21
22
  from flwr.client.client import (
22
23
  maybe_call_evaluate,
23
24
  maybe_call_fit,
@@ -27,7 +28,7 @@ from flwr.client.client import (
27
28
  from flwr.client.numpy_client import NumPyClient
28
29
  from flwr.client.typing import ClientFnExt
29
30
  from flwr.common import ConfigRecord, Context, Message, Metadata, RecordDict, log
30
- from flwr.common.constant import MessageType, MessageTypeLegacy
31
+ from flwr.common.constant import MessageTypeLegacy
31
32
  from flwr.common.recorddict_compat import (
32
33
  evaluateres_to_recorddict,
33
34
  fitres_to_recorddict,
@@ -17,10 +17,10 @@
17
17
 
18
18
  from logging import INFO
19
19
 
20
+ from flwr.app.message_type import MessageType
20
21
  from flwr.client.typing import ClientAppCallable
21
22
  from flwr.common import ndarrays_to_parameters, parameters_to_ndarrays
22
23
  from flwr.common import recorddict_compat as compat
23
- from flwr.common.constant import MessageType
24
24
  from flwr.common.context import Context
25
25
  from flwr.common.differential_privacy import (
26
26
  compute_adaptive_clip_model_update,
@@ -19,10 +19,10 @@ from logging import INFO
19
19
 
20
20
  import numpy as np
21
21
 
22
+ from flwr.app.message_type import MessageType
22
23
  from flwr.client.typing import ClientAppCallable
23
24
  from flwr.common import ndarrays_to_parameters, parameters_to_ndarrays
24
25
  from flwr.common import recorddict_compat as compat
25
- from flwr.common.constant import MessageType
26
26
  from flwr.common.context import Context
27
27
  from flwr.common.differential_privacy import (
28
28
  add_localdp_gaussian_noise_to_params,
@@ -20,6 +20,7 @@ from dataclasses import dataclass, field
20
20
  from logging import DEBUG, WARNING
21
21
  from typing import Any, cast
22
22
 
23
+ from flwr.app.message_type import MessageType
23
24
  from flwr.client.typing import ClientAppCallable
24
25
  from flwr.common import (
25
26
  ConfigRecord,
@@ -31,7 +32,6 @@ from flwr.common import (
31
32
  parameters_to_ndarrays,
32
33
  )
33
34
  from flwr.common import recorddict_compat as compat
34
- from flwr.common.constant import MessageType
35
35
  from flwr.common.logger import log
36
36
  from flwr.common.secure_aggregation.crypto.shamir import create_shares
37
37
  from flwr.common.secure_aggregation.crypto.symmetric_encryption import (
@@ -111,7 +111,7 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
111
111
  tuple[
112
112
  int,
113
113
  Callable[[], tuple[Message, ObjectTree] | None],
114
- Callable[[Message, ObjectTree], set[str]],
114
+ Callable[[Message, ObjectTree, float], set[str]],
115
115
  Callable[[int], Run],
116
116
  Callable[[str, int], Fab],
117
117
  Callable[[int, str], bytes],
@@ -149,7 +149,7 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
149
149
  -------
150
150
  node_id : int
151
151
  receive : Callable[[], Optional[tuple[Message, ObjectTree]]]
152
- send : Callable[[Message, ObjectTree], set[str]]
152
+ send : Callable[[Message, ObjectTree, float], set[str]]
153
153
  get_run : Callable[[int], Run]
154
154
  get_fab : Callable[[str, int], Fab]
155
155
  pull_object : Callable[[str], bytes]
@@ -393,12 +393,13 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
393
393
  # Return the Message and its object tree
394
394
  return in_message, object_tree
395
395
 
396
- def send(message: Message, object_tree: ObjectTree) -> set[str]:
396
+ def send(
397
+ message: Message, object_tree: ObjectTree, clientapp_runtime: float
398
+ ) -> set[str]:
397
399
  """Send the message with its ObjectTree to SuperLink."""
398
400
  # Get Node
399
401
  if node is None:
400
402
  raise RuntimeError("Node instance missing")
401
-
402
403
  # Remove the content from the message if it has
403
404
  if message.has_content():
404
405
  message = remove_content_from_message(message)
@@ -408,6 +409,7 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
408
409
  node=node,
409
410
  messages_list=[message_to_proto(message)],
410
411
  message_object_trees=[object_tree],
412
+ clientapp_runtime_list=[clientapp_runtime],
411
413
  )
412
414
  res = _request(req, PushMessagesResponse, PATH_PUSH_MESSAGES)
413
415
  if res is None:
@@ -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 ===
@@ -632,6 +634,9 @@ def run_to_proto(run: typing.Run) -> ProtoRun:
632
634
  status=run_status_to_proto(run.status),
633
635
  flwr_aid=run.flwr_aid,
634
636
  federation=run.federation,
637
+ bytes_sent=run.bytes_sent,
638
+ bytes_recv=run.bytes_recv,
639
+ clientapp_runtime=run.clientapp_runtime,
635
640
  )
636
641
  return proto
637
642
 
@@ -651,6 +656,9 @@ def run_from_proto(run_proto: ProtoRun) -> typing.Run:
651
656
  status=run_status_from_proto(run_proto.status),
652
657
  flwr_aid=run_proto.flwr_aid,
653
658
  federation=run_proto.federation,
659
+ bytes_sent=run_proto.bytes_sent,
660
+ bytes_recv=run_proto.bytes_recv,
661
+ clientapp_runtime=run_proto.clientapp_runtime,
654
662
  )
655
663
  return run
656
664
 
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")