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
@@ -16,22 +16,27 @@
16
16
 
17
17
 
18
18
  from os import urandom
19
+ from typing import Any
19
20
 
20
21
  from flwr.common import ConfigRecord, Context, Error, Message, Metadata, now, serde
21
22
  from flwr.common.constant import (
22
23
  HEARTBEAT_PATIENCE,
23
24
  SUPERLINK_NODE_ID,
24
25
  ErrorCode,
25
- MessageType,
26
26
  Status,
27
27
  SubStatus,
28
28
  )
29
29
  from flwr.common.message import make_message
30
+ from flwr.common.serde import recorddict_from_proto, recorddict_to_proto
31
+ from flwr.common.serde_utils import error_from_proto, error_to_proto
30
32
  from flwr.common.typing import RunStatus
31
33
 
32
34
  # pylint: disable=E0611
35
+ from flwr.proto.error_pb2 import Error as ProtoError
33
36
  from flwr.proto.message_pb2 import Context as ProtoContext
34
37
  from flwr.proto.recorddict_pb2 import ConfigRecord as ProtoConfigRecord
38
+ from flwr.proto.recorddict_pb2 import RecordDict as ProtoRecordDict
39
+ from flwr.supercore.constant import SYSTEM_MESSAGE_TYPE
35
40
  from flwr.supercore.utils import int64_to_uint64, uint64_to_int64
36
41
 
37
42
  # pylint: enable=E0611
@@ -233,7 +238,7 @@ def create_message_error_unavailable_ins_message(reply_to_message_id: str) -> Me
233
238
  dst_node_id=SUPERLINK_NODE_ID,
234
239
  reply_to_message_id=reply_to_message_id,
235
240
  group_id="", # Unknown
236
- message_type=MessageType.SYSTEM,
241
+ message_type=SYSTEM_MESSAGE_TYPE,
237
242
  created_at=now().timestamp(),
238
243
  ttl=0,
239
244
  )
@@ -388,3 +393,45 @@ def check_node_availability_for_in_message(
388
393
  )
389
394
  ret_dict[in_message_id] = reply_message
390
395
  return ret_dict
396
+
397
+
398
+ def message_to_dict(message: Message) -> dict[str, Any]:
399
+ """Transform Message to dict."""
400
+ result = {
401
+ "message_id": message.metadata.message_id,
402
+ "group_id": message.metadata.group_id,
403
+ "run_id": message.metadata.run_id,
404
+ "src_node_id": message.metadata.src_node_id,
405
+ "dst_node_id": message.metadata.dst_node_id,
406
+ "reply_to_message_id": message.metadata.reply_to_message_id,
407
+ "created_at": message.metadata.created_at,
408
+ "delivered_at": message.metadata.delivered_at,
409
+ "ttl": message.metadata.ttl,
410
+ "message_type": message.metadata.message_type,
411
+ "content": None,
412
+ "error": None,
413
+ }
414
+
415
+ if message.has_content():
416
+ result["content"] = recorddict_to_proto(message.content).SerializeToString()
417
+ else:
418
+ result["error"] = error_to_proto(message.error).SerializeToString()
419
+
420
+ return result
421
+
422
+
423
+ def dict_to_message(message_dict: dict[str, Any]) -> Message:
424
+ """Transform dict to Message."""
425
+ content, error = None, None
426
+ if (b_content := message_dict.pop("content", None)) is not None:
427
+ content = recorddict_from_proto(ProtoRecordDict.FromString(b_content))
428
+ if (b_error := message_dict.pop("error", None)) is not None:
429
+ error = error_from_proto(ProtoError.FromString(b_error))
430
+
431
+ # Metadata constructor doesn't allow passing created_at. We set it later
432
+ metadata = Metadata(
433
+ **{k: v for k, v in message_dict.items() if k not in ["delivered_at"]}
434
+ )
435
+ msg = make_message(metadata=metadata, content=content, error=error)
436
+ msg.metadata.delivered_at = message_dict.get("delivered_at", "")
437
+ return msg
@@ -36,7 +36,6 @@ from flwr.common.serde import (
36
36
  message_from_proto,
37
37
  message_to_proto,
38
38
  run_status_from_proto,
39
- run_status_to_proto,
40
39
  run_to_proto,
41
40
  )
42
41
  from flwr.common.typing import Fab, RunStatus
@@ -55,7 +54,6 @@ from flwr.proto.appio_pb2 import ( # pylint: disable=E0611
55
54
  RequestTokenRequest,
56
55
  RequestTokenResponse,
57
56
  )
58
- from flwr.proto.fab_pb2 import GetFabRequest, GetFabResponse # pylint: disable=E0611
59
57
  from flwr.proto.heartbeat_pb2 import ( # pylint: disable=E0611
60
58
  SendAppHeartbeatRequest,
61
59
  SendAppHeartbeatResponse,
@@ -76,8 +74,6 @@ from flwr.proto.node_pb2 import Node # pylint: disable=E0611
76
74
  from flwr.proto.run_pb2 import ( # pylint: disable=E0611
77
75
  GetRunRequest,
78
76
  GetRunResponse,
79
- GetRunStatusRequest,
80
- GetRunStatusResponse,
81
77
  UpdateRunStatusRequest,
82
78
  UpdateRunStatusResponse,
83
79
  )
@@ -88,7 +84,7 @@ from flwr.proto.serverappio_pb2 import ( # pylint: disable=E0611
88
84
  from flwr.server.superlink.linkstate import LinkState, LinkStateFactory
89
85
  from flwr.server.superlink.utils import abort_if
90
86
  from flwr.server.utils.validator import validate_message
91
- from flwr.supercore.ffs import Ffs, FfsFactory
87
+ from flwr.supercore.ffs import FfsFactory
92
88
  from flwr.supercore.object_store import NoObjectInStoreError, ObjectStoreFactory
93
89
 
94
90
 
@@ -315,19 +311,6 @@ class ServerAppIoServicer(serverappio_pb2_grpc.ServerAppIoServicer):
315
311
 
316
312
  return GetRunResponse(run=run_to_proto(run))
317
313
 
318
- def GetFab(
319
- self, request: GetFabRequest, context: grpc.ServicerContext
320
- ) -> GetFabResponse:
321
- """Get FAB from Ffs."""
322
- log(DEBUG, "ServerAppIoServicer.GetFab")
323
-
324
- ffs: Ffs = self.ffs_factory.ffs()
325
- if result := ffs.get(request.hash_str):
326
- fab = Fab(request.hash_str, result[0], result[1])
327
- return GetFabResponse(fab=fab_to_proto(fab))
328
-
329
- raise ValueError(f"Found no FAB with hash: {request.hash_str}")
330
-
331
314
  def PullAppInputs(
332
315
  self, request: PullAppInputsRequest, context: grpc.ServicerContext
333
316
  ) -> PullAppInputsResponse:
@@ -434,21 +417,6 @@ class ServerAppIoServicer(serverappio_pb2_grpc.ServerAppIoServicer):
434
417
  state.add_serverapp_log(request.run_id, merged_logs)
435
418
  return PushLogsResponse()
436
419
 
437
- def GetRunStatus(
438
- self, request: GetRunStatusRequest, context: grpc.ServicerContext
439
- ) -> GetRunStatusResponse:
440
- """Get the status of a run."""
441
- log(DEBUG, "ServerAppIoServicer.GetRunStatus")
442
- state = self.state_factory.state()
443
-
444
- # Get run status from LinkState
445
- run_statuses = state.get_run_status(set(request.run_ids))
446
- run_status_dict = {
447
- run_id: run_status_to_proto(run_status)
448
- for run_id, run_status in run_statuses.items()
449
- }
450
- return GetRunStatusResponse(run_status_dict=run_status_dict)
451
-
452
420
  def SendAppHeartbeat(
453
421
  self, request: SendAppHeartbeatRequest, context: grpc.ServicerContext
454
422
  ) -> SendAppHeartbeatResponse:
@@ -29,7 +29,6 @@ from flwr.common.serde import (
29
29
  context_to_proto,
30
30
  fab_to_proto,
31
31
  run_status_from_proto,
32
- run_status_to_proto,
33
32
  run_to_proto,
34
33
  )
35
34
  from flwr.common.typing import Fab, RunStatus
@@ -57,8 +56,6 @@ from flwr.proto.run_pb2 import ( # pylint: disable=E0611
57
56
  GetFederationOptionsResponse,
58
57
  GetRunRequest,
59
58
  GetRunResponse,
60
- GetRunStatusRequest,
61
- GetRunStatusResponse,
62
59
  UpdateRunStatusRequest,
63
60
  UpdateRunStatusResponse,
64
61
  )
@@ -219,22 +216,6 @@ class SimulationIoServicer(simulationio_pb2_grpc.SimulationIoServicer):
219
216
  )
220
217
  return UpdateRunStatusResponse()
221
218
 
222
- def GetRunStatus(
223
- self, request: GetRunStatusRequest, context: ServicerContext
224
- ) -> GetRunStatusResponse:
225
- """Get status of requested runs."""
226
- log(DEBUG, "SimultionIoServicer.GetRunStatus")
227
- state = self.state_factory.state()
228
-
229
- statuses = state.get_run_status(set(request.run_ids))
230
-
231
- return GetRunStatusResponse(
232
- run_status_dict={
233
- run_id: run_status_to_proto(status)
234
- for run_id, status in statuses.items()
235
- }
236
- )
237
-
238
219
  def PushLogs(
239
220
  self, request: PushLogsRequest, context: grpc.ServicerContext
240
221
  ) -> PushLogsResponse:
@@ -17,7 +17,7 @@
17
17
 
18
18
  from flwr.common import Message
19
19
  from flwr.common.constant import SUPERLINK_NODE_ID
20
- from flwr.common.date import now
20
+ from flwr.supercore.date import now
21
21
 
22
22
 
23
23
  # pylint: disable-next=too-many-branches
@@ -21,6 +21,7 @@ from logging import INFO, WARN
21
21
  from typing import cast
22
22
 
23
23
  import flwr.common.recorddict_compat as compat
24
+ from flwr.app.message_type import MessageType
24
25
  from flwr.common import (
25
26
  ArrayRecord,
26
27
  Code,
@@ -32,7 +33,7 @@ from flwr.common import (
32
33
  Message,
33
34
  log,
34
35
  )
35
- from flwr.common.constant import MessageType, MessageTypeLegacy
36
+ from flwr.common.constant import MessageTypeLegacy
36
37
 
37
38
  from ..client_proxy import ClientProxy
38
39
  from ..compat.app_utils import start_update_client_manager_thread
@@ -21,12 +21,12 @@ from logging import DEBUG, ERROR, INFO, WARN
21
21
  from typing import cast
22
22
 
23
23
  import flwr.common.recorddict_compat as compat
24
+ from flwr.app.message_type import MessageType
24
25
  from flwr.common import (
25
26
  ConfigRecord,
26
27
  Context,
27
28
  FitRes,
28
29
  Message,
29
- MessageType,
30
30
  NDArrays,
31
31
  RecordDict,
32
32
  bytes_to_ndarray,
@@ -185,7 +185,13 @@ class Bulyan(FedAvg):
185
185
 
186
186
  # Convert to ArrayRecord
187
187
  arrays = ArrayRecord(
188
- dict(zip(array_keys, map(Array, aggregated_ndarrays), strict=True))
188
+ dict(
189
+ zip(
190
+ array_keys,
191
+ (Array(np.asarray(arr)) for arr in aggregated_ndarrays),
192
+ strict=True,
193
+ )
194
+ )
189
195
  )
190
196
 
191
197
  # Aggregate MetricRecords
@@ -22,6 +22,8 @@ from abc import ABC
22
22
  from collections.abc import Iterable
23
23
  from logging import INFO, WARNING
24
24
 
25
+ import numpy as np
26
+
25
27
  from flwr.common import Array, ArrayRecord, ConfigRecord, Message, MetricRecord, log
26
28
  from flwr.common.differential_privacy import (
27
29
  add_gaussian_noise_inplace,
@@ -216,7 +218,13 @@ class DifferentialPrivacyServerSideFixedClipping(DifferentialPrivacyFixedClippin
216
218
  )
217
219
  # Replace content while preserving keys
218
220
  reply.content[arr_name] = ArrayRecord(
219
- dict(zip(record.keys(), map(Array, reply_ndarrays), strict=True))
221
+ dict(
222
+ zip(
223
+ record.keys(),
224
+ (Array(np.asarray(v)) for v in reply_ndarrays),
225
+ strict=True,
226
+ )
227
+ )
220
228
  )
221
229
  log(
222
230
  INFO,
@@ -18,11 +18,11 @@
18
18
  from collections.abc import Callable, Iterable
19
19
  from logging import INFO, WARNING
20
20
 
21
+ from flwr.app import MessageType
21
22
  from flwr.common import (
22
23
  ArrayRecord,
23
24
  ConfigRecord,
24
25
  Message,
25
- MessageType,
26
26
  MetricRecord,
27
27
  RecordDict,
28
28
  log,
@@ -19,11 +19,11 @@ from collections.abc import Callable, Iterable
19
19
  from logging import INFO
20
20
  from typing import cast
21
21
 
22
+ from flwr.app import MessageType
22
23
  from flwr.common import (
23
24
  ArrayRecord,
24
25
  ConfigRecord,
25
26
  Message,
26
- MessageType,
27
27
  MetricRecord,
28
28
  RecordDict,
29
29
  log,
@@ -19,16 +19,12 @@ import traceback
19
19
  from logging import ERROR
20
20
 
21
21
  from flwr import common
22
+ from flwr.app.message_type import MessageType
22
23
  from flwr.client import ClientFnExt
23
24
  from flwr.client.run_info_store import DeprecatedRunInfoStore
24
25
  from flwr.clientapp.client_app import ClientApp
25
26
  from flwr.common import DEFAULT_TTL, Message, Metadata, RecordDict, now
26
- from flwr.common.constant import (
27
- NUM_PARTITIONS_KEY,
28
- PARTITION_ID_KEY,
29
- MessageType,
30
- MessageTypeLegacy,
31
- )
27
+ from flwr.common.constant import NUM_PARTITIONS_KEY, PARTITION_ID_KEY, MessageTypeLegacy
32
28
  from flwr.common.logger import log
33
29
  from flwr.common.message import make_message
34
30
  from flwr.common.recorddict_compat import (
@@ -28,6 +28,7 @@ from pathlib import Path
28
28
  from queue import Empty, Queue
29
29
  from typing import Any, cast
30
30
 
31
+ from flwr.app.user_config import UserConfig
31
32
  from flwr.cli.config_utils import load_and_validate
32
33
  from flwr.cli.utils import get_sha256_hash
33
34
  from flwr.clientapp import ClientApp
@@ -39,7 +40,7 @@ from flwr.common.logger import (
39
40
  update_console_handler,
40
41
  warn_deprecated_feature_with_example,
41
42
  )
42
- from flwr.common.typing import Run, RunStatus, UserConfig
43
+ from flwr.common.typing import Run, RunStatus
43
44
  from flwr.server.grid import Grid, InMemoryGrid
44
45
  from flwr.server.run_serverapp import run as _run
45
46
  from flwr.server.server_app import ServerApp
@@ -115,17 +116,7 @@ def run_simulation_from_cli() -> None:
115
116
  sys.exit("Simulation Engine cannot start.")
116
117
 
117
118
  # Load pyproject.toml
118
- config, errors, warnings = load_and_validate(
119
- app_path / "pyproject.toml", check_module=False
120
- )
121
- if errors:
122
- raise ValueError(errors)
123
-
124
- if warnings:
125
- log(WARNING, warnings)
126
-
127
- if config is None:
128
- raise ValueError("Config extracted from FAB's pyproject.toml is not valid")
119
+ config, _ = load_and_validate(app_path / "pyproject.toml", check_module=False)
129
120
 
130
121
  # Get ClientApp and SeverApp components
131
122
  app_components = config["tool"]["flwr"]["app"]["components"]
@@ -23,7 +23,7 @@ import grpc
23
23
  from flwr.common.constant import SIMULATIONIO_API_DEFAULT_CLIENT_ADDRESS
24
24
  from flwr.common.grpc import create_channel, on_channel_state_change
25
25
  from flwr.common.logger import log
26
- from flwr.common.retry_invoker import _make_simple_grpc_retry_invoker, _wrap_stub
26
+ from flwr.common.retry_invoker import make_simple_grpc_retry_invoker, wrap_stub
27
27
  from flwr.proto.simulationio_pb2_grpc import SimulationIoStub # pylint: disable=E0611
28
28
 
29
29
 
@@ -49,7 +49,7 @@ class SimulationIoConnection:
49
49
  self._cert = root_certificates
50
50
  self._grpc_stub: SimulationIoStub | None = None
51
51
  self._channel: grpc.Channel | None = None
52
- self._retry_invoker = _make_simple_grpc_retry_invoker()
52
+ self._retry_invoker = make_simple_grpc_retry_invoker()
53
53
 
54
54
  @property
55
55
  def _is_connected(self) -> bool:
@@ -75,7 +75,7 @@ class SimulationIoConnection:
75
75
  )
76
76
  self._channel.subscribe(on_channel_state_change)
77
77
  self._grpc_stub = SimulationIoStub(self._channel)
78
- _wrap_stub(self._grpc_stub, self._retry_invoker)
78
+ wrap_stub(self._grpc_stub, self._retry_invoker)
79
79
  log(DEBUG, "[SimulationIO] Connected to %s", self._addr)
80
80
 
81
81
  def _disconnect(self) -> None:
@@ -15,12 +15,9 @@
15
15
  """Flower IP address utils."""
16
16
 
17
17
 
18
- import re
19
18
  import socket
20
19
  from ipaddress import ip_address
21
20
 
22
- import grpc
23
-
24
21
  IPV6: int = 6
25
22
 
26
23
 
@@ -105,33 +102,10 @@ def is_port_in_use(address: str) -> bool:
105
102
  return False
106
103
 
107
104
 
108
- def get_ip_address_from_servicer_context(context: grpc.ServicerContext) -> str:
109
- """Extract the client's IPv4 or IPv6 address from the gRPC ServicerContext.
110
-
111
- Parameters
112
- ----------
113
- context : grpc.ServicerContext
114
- The gRPC ServicerContext object. The context.peer() returns a string like
115
- "ipv4:127.0.0.1:56789" for IPv4 and "ipv6:[2001:db8::1]:54321" for IPv6.
116
-
117
- Returns
118
- -------
119
- str
120
- If one of the format matches, the function will return the client's IP address,
121
- otherwise, it will raise a ValueError.
122
- """
123
- peer: str = context.peer()
124
- # Match IPv4: "ipv4:IP:port"
125
- ipv4_match = re.match(r"^ipv4:(?P<ip>[^:]+):", peer)
126
- if ipv4_match:
127
- return ipv4_match.group("ip")
128
-
129
- # Match IPv6: "ipv6:[IP]:port"
130
- ipv6_match = re.match(r"^ipv6:\[(?P<ip>[^\]]+)\]:", peer)
131
- if ipv6_match:
132
- return ipv6_match.group("ip")
133
-
134
- raise ValueError(
135
- f"Unsupported peer address format: {peer} for the transport protocol. "
136
- "The supported formats are ipv4:IP:port and ipv6:[IP]:port."
137
- )
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()
@@ -17,7 +17,7 @@
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
- from flwr.common.constant import FLWR_DIR
20
+ from flwr.common.constant import FLWR_DIR, NOOP_ACCOUNT_NAME
21
21
 
22
22
  # Top-level key in YAML config for exec plugin settings
23
23
  EXEC_PLUGIN_SECTION = "exec_plugin"
@@ -25,11 +25,17 @@ 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+$"
31
34
  PLATFORM_API_URL = "https://api.flower.ai/v1"
32
35
 
36
+ # SuperGrid constants
37
+ SUPERGRID_ADDRESS = "supergrid.flower.ai"
38
+
33
39
  # Specification for app publishing
34
40
  APP_PUBLISH_INCLUDE_PATTERNS = (
35
41
  "**/*.py",
@@ -52,7 +58,8 @@ MIME_MAP = {
52
58
  }
53
59
 
54
60
  # Constants for federations
55
- NOOP_FEDERATION = "default"
61
+ NOOP_FEDERATION = f"@{NOOP_ACCOUNT_NAME}/default"
62
+ NOOP_FEDERATION_DESCRIPTION = "A federation for testing and development purposes."
56
63
 
57
64
  # Constants for exit handling
58
65
  FORCE_EXIT_TIMEOUT_SECONDS = 5 # Used in `flwr_exit` function
@@ -61,6 +68,21 @@ FORCE_EXIT_TIMEOUT_SECONDS = 5 # Used in `flwr_exit` function
61
68
  MESSAGE_TIME_ENTRY_MAX_AGE_SECONDS = 3600
62
69
 
63
70
 
71
+ # System message type
72
+ SYSTEM_MESSAGE_TYPE = "system"
73
+
74
+ # SQLite PRAGMA settings for optimal performance and correctness
75
+ SQLITE_PRAGMAS = (
76
+ ("busy_timeout", "5000"), # Retry lock acquisition for up to 5s before SQLITE_BUSY
77
+ ("journal_mode", "WAL"), # Enable Write-Ahead Logging for better concurrency
78
+ ("synchronous", "NORMAL"),
79
+ ("foreign_keys", "ON"),
80
+ ("cache_size", "-64000"), # 64MB cache
81
+ ("temp_store", "MEMORY"), # In-memory temp tables
82
+ ("mmap_size", "268435456"), # 256MB memory-mapped I/O
83
+ )
84
+
85
+
64
86
  class NodeStatus:
65
87
  """Event log writer types."""
66
88
 
@@ -1,4 +1,4 @@
1
- # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2026 Flower Labs GmbH. All Rights Reserved.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -12,36 +12,31 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
  # ==============================================================================
15
- """SQLite-based CoreState implementation."""
15
+ """SQLAlchemy-based CoreState implementation."""
16
16
 
17
17
 
18
18
  import secrets
19
- import sqlite3
20
19
  from typing import cast
21
20
 
21
+ from sqlalchemy import MetaData, text
22
+ from sqlalchemy.exc import IntegrityError
23
+
22
24
  from flwr.common import now
23
25
  from flwr.common.constant import (
24
26
  FLWR_APP_TOKEN_LENGTH,
25
27
  HEARTBEAT_DEFAULT_INTERVAL,
26
28
  HEARTBEAT_PATIENCE,
27
29
  )
28
- from flwr.supercore.sqlite_mixin import SqliteMixin
30
+ from flwr.supercore.sql_mixin import SqlMixin
31
+ from flwr.supercore.state.schema.corestate_tables import create_corestate_metadata
29
32
  from flwr.supercore.utils import int64_to_uint64, uint64_to_int64
30
33
 
31
34
  from ..object_store import ObjectStore
32
35
  from .corestate import CoreState
33
36
 
34
- SQL_CREATE_TABLE_TOKEN_STORE = """
35
- CREATE TABLE IF NOT EXISTS token_store (
36
- run_id INTEGER PRIMARY KEY,
37
- token TEXT UNIQUE NOT NULL,
38
- active_until REAL
39
- );
40
- """
41
-
42
37
 
43
- class SqliteCoreState(CoreState, SqliteMixin):
44
- """SQLite-based CoreState implementation."""
38
+ class SqlCoreState(CoreState, SqlMixin):
39
+ """SQLAlchemy-based CoreState implementation."""
45
40
 
46
41
  def __init__(self, database_path: str, object_store: ObjectStore) -> None:
47
42
  super().__init__(database_path)
@@ -52,9 +47,9 @@ class SqliteCoreState(CoreState, SqliteMixin):
52
47
  """Return the ObjectStore instance used by this CoreState."""
53
48
  return self._object_store
54
49
 
55
- def get_sql_statements(self) -> tuple[str, ...]:
56
- """Return SQL statements needed for CoreState tables."""
57
- return (SQL_CREATE_TABLE_TOKEN_STORE,)
50
+ def get_metadata(self) -> MetaData:
51
+ """Return SQLAlchemy MetaData needed for CoreState tables."""
52
+ return create_corestate_metadata()
58
53
 
59
54
  def create_token(self, run_id: int) -> str | None:
60
55
  """Create a token for the given run ID."""
@@ -63,7 +58,8 @@ class SqliteCoreState(CoreState, SqliteMixin):
63
58
  active_until = current + HEARTBEAT_DEFAULT_INTERVAL
64
59
  query = """
65
60
  INSERT INTO token_store (run_id, token, active_until)
66
- VALUES (:run_id, :token, :active_until);
61
+ VALUES (:run_id, :token, :active_until)
62
+ RETURNING token;
67
63
  """
68
64
  data = {
69
65
  "run_id": uint64_to_int64(run_id),
@@ -71,10 +67,10 @@ class SqliteCoreState(CoreState, SqliteMixin):
71
67
  "active_until": active_until,
72
68
  }
73
69
  try:
74
- self.query(query, data)
75
- except sqlite3.IntegrityError:
70
+ rows = self.query(query, data)
71
+ return cast(str, rows[0]["token"])
72
+ except IntegrityError:
76
73
  return None # Token already created for this run ID
77
- return token
78
74
 
79
75
  def verify_token(self, run_id: int, token: str) -> bool:
80
76
  """Verify a token for the given run ID."""
@@ -128,14 +124,14 @@ class SqliteCoreState(CoreState, SqliteMixin):
128
124
  """
129
125
  current = now().timestamp()
130
126
 
131
- with self.conn:
127
+ with self.session() as session:
132
128
  # Delete expired tokens and get their run_ids and active_until timestamps
133
129
  query = """
134
130
  DELETE FROM token_store
135
131
  WHERE active_until < :current
136
132
  RETURNING run_id, active_until;
137
133
  """
138
- rows = self.conn.execute(query, {"current": current}).fetchall()
134
+ rows = session.execute(text(query), {"current": current}).mappings().all()
139
135
  expired_records = [
140
136
  (int64_to_uint64(row["run_id"]), row["active_until"]) for row in rows
141
137
  ]
@@ -0,0 +1,33 @@
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
+ """Credential store for Flower."""
16
+
17
+
18
+ from .credential_store import CredentialStore
19
+ from .file_credential_store import FileCredentialStore
20
+
21
+
22
+ def get_credential_store() -> CredentialStore:
23
+ """Get the credential store instance.
24
+
25
+ Currently, only FileCredentialStore is implemented.
26
+ """
27
+ return FileCredentialStore()
28
+
29
+
30
+ __all__ = [
31
+ "CredentialStore",
32
+ "get_credential_store",
33
+ ]