flwr 1.23.0__py3-none-any.whl → 1.24.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.
- flwr/__init__.py +16 -5
- flwr/app/error.py +2 -2
- flwr/app/exception.py +3 -3
- flwr/cli/app.py +19 -0
- flwr/cli/app_cmd/__init__.py +23 -0
- flwr/cli/app_cmd/publish.py +285 -0
- flwr/cli/app_cmd/review.py +252 -0
- flwr/cli/auth_plugin/auth_plugin.py +4 -5
- flwr/cli/auth_plugin/noop_auth_plugin.py +54 -11
- flwr/cli/auth_plugin/oidc_cli_plugin.py +32 -9
- flwr/cli/build.py +60 -18
- flwr/cli/cli_account_auth_interceptor.py +24 -7
- flwr/cli/config_utils.py +101 -13
- flwr/cli/federation/__init__.py +24 -0
- flwr/cli/federation/ls.py +140 -0
- flwr/cli/federation/show.py +317 -0
- flwr/cli/install.py +91 -13
- flwr/cli/log.py +52 -9
- flwr/cli/login/login.py +7 -4
- flwr/cli/ls.py +170 -130
- flwr/cli/new/new.py +33 -50
- flwr/cli/new/templates/app/code/task.pytorch.py.tpl +1 -0
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +3 -3
- flwr/cli/new/templates/app/pyproject.pytorch_legacy_api.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +2 -2
- flwr/cli/new/templates/app/pyproject.xgboost.toml.tpl +1 -1
- flwr/cli/pull.py +10 -5
- flwr/cli/run/run.py +77 -30
- flwr/cli/run_utils.py +130 -0
- flwr/cli/stop.py +25 -7
- flwr/cli/supernode/ls.py +16 -8
- flwr/cli/supernode/register.py +9 -4
- flwr/cli/supernode/unregister.py +5 -3
- flwr/cli/utils.py +376 -16
- flwr/client/__init__.py +1 -1
- flwr/client/dpfedavg_numpy_client.py +4 -1
- flwr/client/grpc_adapter_client/connection.py +6 -7
- flwr/client/grpc_rere_client/connection.py +10 -11
- flwr/client/grpc_rere_client/grpc_adapter.py +6 -2
- flwr/client/grpc_rere_client/node_auth_client_interceptor.py +2 -1
- flwr/client/message_handler/message_handler.py +2 -2
- flwr/client/mod/secure_aggregation/secaggplus_mod.py +3 -3
- flwr/client/numpy_client.py +1 -1
- flwr/client/rest_client/connection.py +12 -14
- flwr/client/run_info_store.py +4 -5
- flwr/client/typing.py +1 -1
- flwr/clientapp/client_app.py +9 -10
- flwr/clientapp/mod/centraldp_mods.py +16 -17
- flwr/clientapp/mod/localdp_mod.py +8 -9
- flwr/clientapp/typing.py +1 -1
- flwr/clientapp/utils.py +3 -3
- flwr/common/address.py +1 -2
- flwr/common/args.py +3 -4
- flwr/common/config.py +13 -16
- flwr/common/constant.py +5 -2
- flwr/common/differential_privacy.py +3 -4
- flwr/common/event_log_plugin/event_log_plugin.py +3 -4
- flwr/common/exit/exit.py +15 -2
- flwr/common/exit/exit_code.py +19 -0
- flwr/common/exit/exit_handler.py +6 -2
- flwr/common/exit/signal_handler.py +5 -5
- flwr/common/grpc.py +6 -6
- flwr/common/inflatable_protobuf_utils.py +1 -1
- flwr/common/inflatable_utils.py +38 -21
- flwr/common/logger.py +19 -19
- flwr/common/message.py +4 -4
- flwr/common/object_ref.py +7 -7
- flwr/common/record/array.py +3 -3
- flwr/common/record/arrayrecord.py +18 -30
- flwr/common/record/configrecord.py +3 -3
- flwr/common/record/recorddict.py +5 -5
- flwr/common/record/typeddict.py +9 -2
- flwr/common/recorddict_compat.py +7 -10
- flwr/common/retry_invoker.py +20 -20
- flwr/common/secure_aggregation/ndarrays_arithmetic.py +3 -3
- flwr/common/serde.py +5 -4
- flwr/common/serde_utils.py +2 -2
- flwr/common/telemetry.py +9 -5
- flwr/common/typing.py +52 -37
- flwr/compat/client/app.py +38 -37
- flwr/compat/client/grpc_client/connection.py +11 -11
- flwr/compat/server/app.py +5 -6
- flwr/proto/appio_pb2.py +13 -3
- flwr/proto/appio_pb2.pyi +134 -65
- flwr/proto/appio_pb2_grpc.py +20 -0
- flwr/proto/appio_pb2_grpc.pyi +27 -0
- flwr/proto/clientappio_pb2.py +17 -7
- flwr/proto/clientappio_pb2.pyi +15 -0
- flwr/proto/clientappio_pb2_grpc.py +206 -40
- flwr/proto/clientappio_pb2_grpc.pyi +168 -53
- flwr/proto/control_pb2.py +71 -52
- flwr/proto/control_pb2.pyi +277 -111
- flwr/proto/control_pb2_grpc.py +249 -40
- flwr/proto/control_pb2_grpc.pyi +185 -52
- flwr/proto/error_pb2.py +13 -3
- flwr/proto/error_pb2.pyi +24 -6
- flwr/proto/error_pb2_grpc.py +20 -0
- flwr/proto/error_pb2_grpc.pyi +27 -0
- flwr/proto/fab_pb2.py +14 -4
- flwr/proto/fab_pb2.pyi +59 -31
- flwr/proto/fab_pb2_grpc.py +20 -0
- flwr/proto/fab_pb2_grpc.pyi +27 -0
- flwr/proto/federation_pb2.py +38 -0
- flwr/proto/federation_pb2.pyi +56 -0
- flwr/proto/federation_pb2_grpc.py +24 -0
- flwr/proto/federation_pb2_grpc.pyi +31 -0
- flwr/proto/fleet_pb2.py +14 -4
- flwr/proto/fleet_pb2.pyi +137 -61
- flwr/proto/fleet_pb2_grpc.py +189 -48
- flwr/proto/fleet_pb2_grpc.pyi +175 -61
- flwr/proto/grpcadapter_pb2.py +14 -4
- flwr/proto/grpcadapter_pb2.pyi +38 -16
- flwr/proto/grpcadapter_pb2_grpc.py +35 -4
- flwr/proto/grpcadapter_pb2_grpc.pyi +38 -7
- flwr/proto/heartbeat_pb2.py +17 -7
- flwr/proto/heartbeat_pb2.pyi +51 -22
- flwr/proto/heartbeat_pb2_grpc.py +20 -0
- flwr/proto/heartbeat_pb2_grpc.pyi +27 -0
- flwr/proto/log_pb2.py +13 -3
- flwr/proto/log_pb2.pyi +34 -11
- flwr/proto/log_pb2_grpc.py +20 -0
- flwr/proto/log_pb2_grpc.pyi +27 -0
- flwr/proto/message_pb2.py +15 -5
- flwr/proto/message_pb2.pyi +154 -86
- flwr/proto/message_pb2_grpc.py +20 -0
- flwr/proto/message_pb2_grpc.pyi +27 -0
- flwr/proto/node_pb2.py +15 -5
- flwr/proto/node_pb2.pyi +50 -25
- flwr/proto/node_pb2_grpc.py +20 -0
- flwr/proto/node_pb2_grpc.pyi +27 -0
- flwr/proto/recorddict_pb2.py +13 -3
- flwr/proto/recorddict_pb2.pyi +184 -107
- flwr/proto/recorddict_pb2_grpc.py +20 -0
- flwr/proto/recorddict_pb2_grpc.pyi +27 -0
- flwr/proto/run_pb2.py +40 -31
- flwr/proto/run_pb2.pyi +149 -84
- flwr/proto/run_pb2_grpc.py +20 -0
- flwr/proto/run_pb2_grpc.pyi +27 -0
- flwr/proto/serverappio_pb2.py +13 -3
- flwr/proto/serverappio_pb2.pyi +32 -8
- flwr/proto/serverappio_pb2_grpc.py +246 -65
- flwr/proto/serverappio_pb2_grpc.pyi +221 -85
- flwr/proto/simulationio_pb2.py +16 -8
- flwr/proto/simulationio_pb2.pyi +15 -0
- flwr/proto/simulationio_pb2_grpc.py +162 -41
- flwr/proto/simulationio_pb2_grpc.pyi +149 -55
- flwr/proto/transport_pb2.py +20 -10
- flwr/proto/transport_pb2.pyi +249 -160
- flwr/proto/transport_pb2_grpc.py +35 -4
- flwr/proto/transport_pb2_grpc.pyi +38 -8
- flwr/server/app.py +38 -17
- flwr/server/client_manager.py +4 -5
- flwr/server/client_proxy.py +10 -11
- flwr/server/compat/app.py +4 -5
- flwr/server/compat/app_utils.py +2 -1
- flwr/server/compat/grid_client_proxy.py +10 -12
- flwr/server/compat/legacy_context.py +3 -4
- flwr/server/fleet_event_log_interceptor.py +2 -1
- flwr/server/grid/grid.py +2 -3
- flwr/server/grid/grpc_grid.py +10 -8
- flwr/server/grid/inmemory_grid.py +4 -4
- flwr/server/run_serverapp.py +2 -3
- flwr/server/server.py +34 -39
- flwr/server/server_app.py +7 -8
- flwr/server/server_config.py +1 -2
- flwr/server/serverapp/app.py +34 -28
- flwr/server/serverapp_components.py +4 -5
- flwr/server/strategy/aggregate.py +9 -8
- flwr/server/strategy/bulyan.py +13 -11
- flwr/server/strategy/dp_adaptive_clipping.py +16 -20
- flwr/server/strategy/dp_fixed_clipping.py +12 -17
- flwr/server/strategy/dpfedavg_adaptive.py +3 -4
- flwr/server/strategy/dpfedavg_fixed.py +6 -10
- flwr/server/strategy/fault_tolerant_fedavg.py +14 -13
- flwr/server/strategy/fedadagrad.py +18 -14
- flwr/server/strategy/fedadam.py +16 -14
- flwr/server/strategy/fedavg.py +16 -17
- flwr/server/strategy/fedavg_android.py +15 -15
- flwr/server/strategy/fedavgm.py +21 -18
- flwr/server/strategy/fedmedian.py +2 -3
- flwr/server/strategy/fedopt.py +11 -10
- flwr/server/strategy/fedprox.py +10 -9
- flwr/server/strategy/fedtrimmedavg.py +12 -11
- flwr/server/strategy/fedxgb_bagging.py +13 -11
- flwr/server/strategy/fedxgb_cyclic.py +6 -6
- flwr/server/strategy/fedxgb_nn_avg.py +4 -4
- flwr/server/strategy/fedyogi.py +16 -14
- flwr/server/strategy/krum.py +12 -11
- flwr/server/strategy/qfedavg.py +16 -15
- flwr/server/strategy/strategy.py +6 -9
- flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +2 -1
- flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -2
- flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py +3 -4
- flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py +10 -12
- flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +1 -3
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +4 -4
- flwr/server/superlink/fleet/grpc_rere/node_auth_server_interceptor.py +3 -2
- flwr/server/superlink/fleet/message_handler/message_handler.py +34 -28
- flwr/server/superlink/fleet/rest_rere/rest_api.py +2 -2
- flwr/server/superlink/fleet/vce/backend/backend.py +1 -1
- flwr/server/superlink/fleet/vce/backend/raybackend.py +5 -5
- flwr/server/superlink/fleet/vce/vce_api.py +15 -9
- flwr/server/superlink/linkstate/in_memory_linkstate.py +115 -150
- flwr/server/superlink/linkstate/linkstate.py +59 -43
- flwr/server/superlink/linkstate/linkstate_factory.py +22 -5
- flwr/server/superlink/linkstate/sqlite_linkstate.py +447 -438
- flwr/server/superlink/linkstate/utils.py +6 -6
- flwr/server/superlink/serverappio/serverappio_grpc.py +1 -2
- flwr/server/superlink/serverappio/serverappio_servicer.py +26 -21
- flwr/server/superlink/simulation/simulationio_grpc.py +1 -2
- flwr/server/superlink/simulation/simulationio_servicer.py +18 -13
- flwr/server/superlink/utils.py +4 -6
- flwr/server/typing.py +1 -1
- flwr/server/utils/tensorboard.py +15 -8
- flwr/server/workflow/default_workflows.py +5 -5
- flwr/server/workflow/secure_aggregation/secagg_workflow.py +2 -4
- flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +8 -8
- flwr/serverapp/strategy/bulyan.py +16 -15
- flwr/serverapp/strategy/dp_adaptive_clipping.py +12 -11
- flwr/serverapp/strategy/dp_fixed_clipping.py +11 -14
- flwr/serverapp/strategy/fedadagrad.py +10 -11
- flwr/serverapp/strategy/fedadam.py +10 -11
- flwr/serverapp/strategy/fedavg.py +9 -10
- flwr/serverapp/strategy/fedavgm.py +17 -16
- flwr/serverapp/strategy/fedmedian.py +2 -2
- flwr/serverapp/strategy/fedopt.py +10 -11
- flwr/serverapp/strategy/fedprox.py +7 -8
- flwr/serverapp/strategy/fedtrimmedavg.py +9 -9
- flwr/serverapp/strategy/fedxgb_bagging.py +3 -3
- flwr/serverapp/strategy/fedxgb_cyclic.py +9 -9
- flwr/serverapp/strategy/fedyogi.py +9 -11
- flwr/serverapp/strategy/krum.py +7 -7
- flwr/serverapp/strategy/multikrum.py +9 -9
- flwr/serverapp/strategy/qfedavg.py +17 -16
- flwr/serverapp/strategy/strategy.py +6 -9
- flwr/serverapp/strategy/strategy_utils.py +7 -8
- flwr/simulation/app.py +46 -42
- flwr/simulation/legacy_app.py +12 -12
- flwr/simulation/ray_transport/ray_actor.py +10 -11
- flwr/simulation/ray_transport/ray_client_proxy.py +11 -12
- flwr/simulation/run_simulation.py +43 -43
- flwr/simulation/simulationio_connection.py +4 -4
- flwr/supercore/cli/flower_superexec.py +3 -4
- flwr/supercore/constant.py +31 -1
- flwr/supercore/corestate/corestate.py +24 -3
- flwr/supercore/corestate/in_memory_corestate.py +138 -0
- flwr/supercore/corestate/sqlite_corestate.py +157 -0
- flwr/supercore/ffs/disk_ffs.py +1 -2
- flwr/supercore/ffs/ffs.py +1 -2
- flwr/supercore/ffs/ffs_factory.py +1 -2
- flwr/{common → supercore}/heartbeat.py +20 -25
- flwr/supercore/object_store/in_memory_object_store.py +1 -2
- flwr/supercore/object_store/object_store.py +1 -2
- flwr/supercore/object_store/object_store_factory.py +1 -2
- flwr/supercore/object_store/sqlite_object_store.py +8 -7
- flwr/supercore/primitives/asymmetric.py +1 -1
- flwr/supercore/primitives/asymmetric_ed25519.py +11 -1
- flwr/supercore/sqlite_mixin.py +37 -34
- flwr/supercore/superexec/plugin/base_exec_plugin.py +1 -2
- flwr/supercore/superexec/plugin/exec_plugin.py +3 -3
- flwr/supercore/superexec/run_superexec.py +9 -13
- flwr/superlink/artifact_provider/artifact_provider.py +1 -2
- flwr/superlink/auth_plugin/auth_plugin.py +6 -9
- flwr/superlink/auth_plugin/noop_auth_plugin.py +6 -9
- flwr/superlink/federation/__init__.py +24 -0
- flwr/superlink/federation/federation_manager.py +64 -0
- flwr/superlink/federation/noop_federation_manager.py +71 -0
- flwr/superlink/servicer/control/control_account_auth_interceptor.py +22 -13
- flwr/superlink/servicer/control/control_event_log_interceptor.py +7 -7
- flwr/superlink/servicer/control/control_grpc.py +5 -6
- flwr/superlink/servicer/control/control_license_interceptor.py +3 -3
- flwr/superlink/servicer/control/control_servicer.py +102 -18
- flwr/supernode/cli/flower_supernode.py +58 -3
- flwr/supernode/nodestate/in_memory_nodestate.py +60 -49
- flwr/supernode/nodestate/nodestate.py +7 -8
- flwr/supernode/nodestate/nodestate_factory.py +7 -4
- flwr/supernode/runtime/run_clientapp.py +41 -22
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +40 -10
- flwr/supernode/start_client_internal.py +158 -42
- {flwr-1.23.0.dist-info → flwr-1.24.0.dist-info}/METADATA +8 -8
- flwr-1.24.0.dist-info/RECORD +454 -0
- flwr/supercore/object_store/utils.py +0 -43
- flwr-1.23.0.dist-info/RECORD +0 -439
- {flwr-1.23.0.dist-info → flwr-1.24.0.dist-info}/WHEEL +0 -0
- {flwr-1.23.0.dist-info → flwr-1.24.0.dist-info}/entry_points.txt +0 -0
|
@@ -26,7 +26,7 @@ import traceback
|
|
|
26
26
|
from logging import DEBUG, ERROR, INFO, WARNING
|
|
27
27
|
from pathlib import Path
|
|
28
28
|
from queue import Empty, Queue
|
|
29
|
-
from typing import Any,
|
|
29
|
+
from typing import Any, cast
|
|
30
30
|
|
|
31
31
|
from flwr.cli.config_utils import load_and_validate
|
|
32
32
|
from flwr.cli.utils import get_sha256_hash
|
|
@@ -51,7 +51,9 @@ from flwr.server.superlink.linkstate.utils import generate_rand_int_from_bytes
|
|
|
51
51
|
from flwr.simulation.ray_transport.utils import (
|
|
52
52
|
enable_tf_gpu_growth as enable_gpu_growth,
|
|
53
53
|
)
|
|
54
|
-
from flwr.supercore.constant import FLWR_IN_MEMORY_DB_NAME
|
|
54
|
+
from flwr.supercore.constant import FLWR_IN_MEMORY_DB_NAME, NOOP_FEDERATION
|
|
55
|
+
from flwr.supercore.object_store import ObjectStoreFactory
|
|
56
|
+
from flwr.superlink.federation import NoOpFederationManager
|
|
55
57
|
|
|
56
58
|
|
|
57
59
|
def _replace_keys(d: Any, match: str, target: str) -> Any:
|
|
@@ -99,12 +101,7 @@ def run_simulation_from_cli() -> None:
|
|
|
99
101
|
_check_ray_support(args.backend)
|
|
100
102
|
|
|
101
103
|
# Load JSON config
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if backend_config_dict:
|
|
105
|
-
# Backend config internally operates with `_` not with `-`
|
|
106
|
-
backend_config_dict = _replace_keys(backend_config_dict, match="-", target="_")
|
|
107
|
-
log(DEBUG, "backend_config_dict: %s", backend_config_dict)
|
|
104
|
+
backend_config = json.loads(args.backend_config)
|
|
108
105
|
|
|
109
106
|
run_id = (
|
|
110
107
|
generate_rand_int_from_bytes(RUN_ID_NUM_BYTES)
|
|
@@ -142,6 +139,7 @@ def run_simulation_from_cli() -> None:
|
|
|
142
139
|
|
|
143
140
|
# Create run
|
|
144
141
|
run = Run.create_empty(run_id)
|
|
142
|
+
run.federation = NOOP_FEDERATION
|
|
145
143
|
run.override_config = override_config
|
|
146
144
|
|
|
147
145
|
# Create Context
|
|
@@ -158,7 +156,7 @@ def run_simulation_from_cli() -> None:
|
|
|
158
156
|
client_app_attr=client_app_attr,
|
|
159
157
|
num_supernodes=args.num_supernodes,
|
|
160
158
|
backend_name=args.backend,
|
|
161
|
-
backend_config=
|
|
159
|
+
backend_config=backend_config,
|
|
162
160
|
app_dir=args.app,
|
|
163
161
|
run=run,
|
|
164
162
|
enable_tf_gpu_growth=args.enable_tf_gpu_growth,
|
|
@@ -176,7 +174,7 @@ def run_simulation(
|
|
|
176
174
|
client_app: ClientApp,
|
|
177
175
|
num_supernodes: int,
|
|
178
176
|
backend_name: str = "ray",
|
|
179
|
-
backend_config:
|
|
177
|
+
backend_config: BackendConfig | None = None,
|
|
180
178
|
enable_tf_gpu_growth: bool = False,
|
|
181
179
|
verbose_logging: bool = False,
|
|
182
180
|
) -> None:
|
|
@@ -249,8 +247,8 @@ def run_simulation(
|
|
|
249
247
|
|
|
250
248
|
# pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
251
249
|
def run_serverapp_th(
|
|
252
|
-
server_app_attr:
|
|
253
|
-
server_app:
|
|
250
|
+
server_app_attr: str | None,
|
|
251
|
+
server_app: ServerApp | None,
|
|
254
252
|
server_app_context: Context,
|
|
255
253
|
grid: Grid,
|
|
256
254
|
app_dir: str,
|
|
@@ -267,8 +265,8 @@ def run_serverapp_th(
|
|
|
267
265
|
exception_event: threading.Event,
|
|
268
266
|
_grid: Grid,
|
|
269
267
|
_server_app_dir: str,
|
|
270
|
-
_server_app_attr:
|
|
271
|
-
_server_app:
|
|
268
|
+
_server_app_attr: str | None,
|
|
269
|
+
_server_app: ServerApp | None,
|
|
272
270
|
_ctx_queue: "Queue[Context]",
|
|
273
271
|
) -> None:
|
|
274
272
|
"""Run SeverApp, after check if GPU memory growth has to be set.
|
|
@@ -328,16 +326,18 @@ def _main_loop(
|
|
|
328
326
|
enable_tf_gpu_growth: bool,
|
|
329
327
|
run: Run,
|
|
330
328
|
exit_event: EventType,
|
|
331
|
-
flwr_dir:
|
|
332
|
-
client_app:
|
|
333
|
-
client_app_attr:
|
|
334
|
-
server_app:
|
|
335
|
-
server_app_attr:
|
|
336
|
-
server_app_context:
|
|
329
|
+
flwr_dir: str | None = None,
|
|
330
|
+
client_app: ClientApp | None = None,
|
|
331
|
+
client_app_attr: str | None = None,
|
|
332
|
+
server_app: ServerApp | None = None,
|
|
333
|
+
server_app_attr: str | None = None,
|
|
334
|
+
server_app_context: Context | None = None,
|
|
337
335
|
) -> Context:
|
|
338
336
|
"""Start ServerApp on a separate thread, then launch Simulation Engine."""
|
|
339
337
|
# Initialize StateFactory
|
|
340
|
-
state_factory = LinkStateFactory(
|
|
338
|
+
state_factory = LinkStateFactory(
|
|
339
|
+
FLWR_IN_MEMORY_DB_NAME, NoOpFederationManager(), ObjectStoreFactory()
|
|
340
|
+
)
|
|
341
341
|
|
|
342
342
|
f_stop = threading.Event()
|
|
343
343
|
# A Threading event to indicate if an exception was raised in the ServerApp thread
|
|
@@ -428,16 +428,16 @@ def _main_loop(
|
|
|
428
428
|
def _run_simulation(
|
|
429
429
|
num_supernodes: int,
|
|
430
430
|
exit_event: EventType,
|
|
431
|
-
client_app:
|
|
432
|
-
server_app:
|
|
431
|
+
client_app: ClientApp | None = None,
|
|
432
|
+
server_app: ServerApp | None = None,
|
|
433
433
|
backend_name: str = "ray",
|
|
434
|
-
backend_config:
|
|
435
|
-
client_app_attr:
|
|
436
|
-
server_app_attr:
|
|
437
|
-
server_app_context:
|
|
434
|
+
backend_config: BackendConfig | None = None,
|
|
435
|
+
client_app_attr: str | None = None,
|
|
436
|
+
server_app_attr: str | None = None,
|
|
437
|
+
server_app_context: Context | None = None,
|
|
438
438
|
app_dir: str = "",
|
|
439
|
-
flwr_dir:
|
|
440
|
-
run:
|
|
439
|
+
flwr_dir: str | None = None,
|
|
440
|
+
run: Run | None = None,
|
|
441
441
|
enable_tf_gpu_growth: bool = False,
|
|
442
442
|
verbose_logging: bool = False,
|
|
443
443
|
is_app: bool = False,
|
|
@@ -445,29 +445,28 @@ def _run_simulation(
|
|
|
445
445
|
"""Launch the Simulation Engine."""
|
|
446
446
|
if backend_config is None:
|
|
447
447
|
backend_config = {}
|
|
448
|
+
elif backend_config:
|
|
449
|
+
# Backend config internally operates with `_` not with `-`
|
|
450
|
+
backend_config = cast(
|
|
451
|
+
BackendConfig, _replace_keys(backend_config, match="-", target="_")
|
|
452
|
+
)
|
|
453
|
+
log(DEBUG, "backend_config: %s", backend_config)
|
|
448
454
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
455
|
+
# Set default init_args if not passed
|
|
456
|
+
backend_config.setdefault("init_args", {})
|
|
452
457
|
# Set default client_resources if not passed
|
|
453
|
-
|
|
454
|
-
backend_config["client_resources"] = {"num_cpus": 2, "num_gpus": 0}
|
|
455
|
-
|
|
458
|
+
backend_config.setdefault("client_resources", {"num_cpus": 2, "num_gpus": 0})
|
|
456
459
|
# Initialization of backend config to enable GPU growth globally when set
|
|
457
|
-
|
|
458
|
-
backend_config["actor"] = {"tensorflow": 0}
|
|
460
|
+
backend_config.setdefault("actor", {"tensorflow": 0})
|
|
459
461
|
|
|
460
462
|
# Set logging level
|
|
461
463
|
logger = logging.getLogger("flwr")
|
|
462
464
|
if verbose_logging:
|
|
463
465
|
update_console_handler(level=DEBUG, timestamps=True, colored=True)
|
|
464
466
|
else:
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
)
|
|
468
|
-
backend_config["init_args"]["log_to_driver"] = backend_config["init_args"].get(
|
|
469
|
-
"log_to_driver", True
|
|
470
|
-
)
|
|
467
|
+
init_args = backend_config["init_args"]
|
|
468
|
+
init_args.setdefault("logging_level", WARNING)
|
|
469
|
+
init_args.setdefault("log_to_driver", True)
|
|
471
470
|
|
|
472
471
|
if enable_tf_gpu_growth:
|
|
473
472
|
# Check that Backend config has also enabled using GPU growth
|
|
@@ -483,6 +482,7 @@ def _run_simulation(
|
|
|
483
482
|
if run is None:
|
|
484
483
|
run_id = generate_rand_int_from_bytes(RUN_ID_NUM_BYTES)
|
|
485
484
|
run = Run.create_empty(run_id=run_id)
|
|
485
|
+
run.federation = NOOP_FEDERATION
|
|
486
486
|
|
|
487
487
|
args = (
|
|
488
488
|
num_supernodes,
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
from logging import DEBUG, WARNING
|
|
19
|
-
from typing import
|
|
19
|
+
from typing import cast
|
|
20
20
|
|
|
21
21
|
import grpc
|
|
22
22
|
|
|
@@ -43,12 +43,12 @@ class SimulationIoConnection:
|
|
|
43
43
|
def __init__( # pylint: disable=too-many-arguments
|
|
44
44
|
self,
|
|
45
45
|
simulationio_service_address: str = SIMULATIONIO_API_DEFAULT_CLIENT_ADDRESS,
|
|
46
|
-
root_certificates:
|
|
46
|
+
root_certificates: bytes | None = None,
|
|
47
47
|
) -> None:
|
|
48
48
|
self._addr = simulationio_service_address
|
|
49
49
|
self._cert = root_certificates
|
|
50
|
-
self._grpc_stub:
|
|
51
|
-
self._channel:
|
|
50
|
+
self._grpc_stub: SimulationIoStub | None = None
|
|
51
|
+
self._channel: grpc.Channel | None = None
|
|
52
52
|
self._retry_invoker = _make_simple_grpc_retry_invoker()
|
|
53
53
|
|
|
54
54
|
@property
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import argparse
|
|
19
19
|
from logging import INFO
|
|
20
|
-
from typing import Any
|
|
20
|
+
from typing import Any
|
|
21
21
|
|
|
22
22
|
import yaml
|
|
23
23
|
|
|
@@ -54,7 +54,7 @@ except ImportError:
|
|
|
54
54
|
|
|
55
55
|
def get_ee_plugin_and_stub_class( # pylint: disable=unused-argument
|
|
56
56
|
plugin_type: str,
|
|
57
|
-
) ->
|
|
57
|
+
) -> tuple[type[ExecPlugin], type[object]] | None:
|
|
58
58
|
"""Get the EE plugin class and stub class based on the plugin type."""
|
|
59
59
|
return None
|
|
60
60
|
|
|
@@ -75,7 +75,6 @@ def flower_superexec() -> None:
|
|
|
75
75
|
# Log the first message after parsing arguments in case of `--help`
|
|
76
76
|
log(INFO, "Starting Flower SuperExec")
|
|
77
77
|
|
|
78
|
-
# Trigger telemetry event
|
|
79
78
|
event(EventType.RUN_SUPEREXEC_ENTER, {"plugin_type": args.plugin_type})
|
|
80
79
|
|
|
81
80
|
# Load plugin config from YAML file if provided
|
|
@@ -83,7 +82,7 @@ def flower_superexec() -> None:
|
|
|
83
82
|
if plugin_config_path := getattr(args, "plugin_config", None):
|
|
84
83
|
try:
|
|
85
84
|
with open(plugin_config_path, encoding="utf-8") as file:
|
|
86
|
-
yaml_config:
|
|
85
|
+
yaml_config: dict[str, Any] | None = yaml.safe_load(file)
|
|
87
86
|
if yaml_config is None or EXEC_PLUGIN_SECTION not in yaml_config:
|
|
88
87
|
raise ValueError(f"Missing '{EXEC_PLUGIN_SECTION}' section.")
|
|
89
88
|
plugin_config = yaml_config[EXEC_PLUGIN_SECTION]
|
flwr/supercore/constant.py
CHANGED
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
+
from flwr.common.constant import FLWR_DIR
|
|
21
|
+
|
|
20
22
|
# Top-level key in YAML config for exec plugin settings
|
|
21
23
|
EXEC_PLUGIN_SECTION = "exec_plugin"
|
|
22
24
|
|
|
@@ -24,9 +26,37 @@ EXEC_PLUGIN_SECTION = "exec_plugin"
|
|
|
24
26
|
FLWR_IN_MEMORY_DB_NAME = ":flwr-in-memory:"
|
|
25
27
|
|
|
26
28
|
# Constants for Hub
|
|
27
|
-
APP_ID_PATTERN = r"^@
|
|
29
|
+
APP_ID_PATTERN = r"^@[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$"
|
|
30
|
+
APP_VERSION_PATTERN = r"^\d+\.\d+\.\d+$"
|
|
28
31
|
PLATFORM_API_URL = "https://api.flower.ai/v1"
|
|
29
32
|
|
|
33
|
+
# Specification for app publishing
|
|
34
|
+
APP_PUBLISH_INCLUDE_PATTERNS = (
|
|
35
|
+
"**/*.py",
|
|
36
|
+
"**/*.toml",
|
|
37
|
+
"**/*.md",
|
|
38
|
+
)
|
|
39
|
+
APP_PUBLISH_EXCLUDE_PATTERNS = FAB_EXCLUDE_PATTERNS = (
|
|
40
|
+
f"{FLWR_DIR}/**", # Exclude the .flwr directory
|
|
41
|
+
"**/__pycache__/**",
|
|
42
|
+
)
|
|
43
|
+
MAX_TOTAL_BYTES = 10 * 1024 * 1024 # 10 MB
|
|
44
|
+
MAX_FILE_BYTES = 1 * 1024 * 1024 # 1 MB
|
|
45
|
+
MAX_FILE_COUNT = 1000
|
|
46
|
+
MAX_DIR_DEPTH = 10 # relative depth (number of parts in relpath)
|
|
47
|
+
UTF8 = "utf-8"
|
|
48
|
+
MIME_MAP = {
|
|
49
|
+
".py": "text/x-python; charset=utf-8",
|
|
50
|
+
".md": "text/markdown; charset=utf-8",
|
|
51
|
+
".toml": "application/toml; charset=utf-8",
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# Constants for federations
|
|
55
|
+
NOOP_FEDERATION = "default"
|
|
56
|
+
|
|
57
|
+
# Constants for exit handling
|
|
58
|
+
FORCE_EXIT_TIMEOUT_SECONDS = 5 # Used in `flwr_exit` function
|
|
59
|
+
|
|
30
60
|
|
|
31
61
|
class NodeStatus:
|
|
32
62
|
"""Event log writer types."""
|
|
@@ -16,14 +16,20 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
from abc import ABC, abstractmethod
|
|
19
|
-
|
|
19
|
+
|
|
20
|
+
from ..object_store import ObjectStore
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
class CoreState(ABC):
|
|
23
24
|
"""Abstract base class for core state."""
|
|
24
25
|
|
|
26
|
+
@property
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def object_store(self) -> ObjectStore:
|
|
29
|
+
"""Return the ObjectStore instance used by this CoreState."""
|
|
30
|
+
|
|
25
31
|
@abstractmethod
|
|
26
|
-
def create_token(self, run_id: int) ->
|
|
32
|
+
def create_token(self, run_id: int) -> str | None:
|
|
27
33
|
"""Create a token for the given run ID.
|
|
28
34
|
|
|
29
35
|
Parameters
|
|
@@ -66,7 +72,7 @@ class CoreState(ABC):
|
|
|
66
72
|
"""
|
|
67
73
|
|
|
68
74
|
@abstractmethod
|
|
69
|
-
def get_run_id_by_token(self, token: str) ->
|
|
75
|
+
def get_run_id_by_token(self, token: str) -> int | None:
|
|
70
76
|
"""Get the run ID associated with a given token.
|
|
71
77
|
|
|
72
78
|
Parameters
|
|
@@ -79,3 +85,18 @@ class CoreState(ABC):
|
|
|
79
85
|
Optional[int]
|
|
80
86
|
The run ID if the token is valid, otherwise None.
|
|
81
87
|
"""
|
|
88
|
+
|
|
89
|
+
@abstractmethod
|
|
90
|
+
def acknowledge_app_heartbeat(self, token: str) -> bool:
|
|
91
|
+
"""Acknowledge an app heartbeat with the provided token.
|
|
92
|
+
|
|
93
|
+
Parameters
|
|
94
|
+
----------
|
|
95
|
+
token : str
|
|
96
|
+
The token associated with the app.
|
|
97
|
+
|
|
98
|
+
Returns
|
|
99
|
+
-------
|
|
100
|
+
bool
|
|
101
|
+
True if the heartbeat is acknowledged successfully, False otherwise.
|
|
102
|
+
"""
|
|
@@ -0,0 +1,138 @@
|
|
|
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
|
+
"""In-memory CoreState implementation."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
import secrets
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from threading import Lock
|
|
21
|
+
|
|
22
|
+
from flwr.common import now
|
|
23
|
+
from flwr.common.constant import (
|
|
24
|
+
FLWR_APP_TOKEN_LENGTH,
|
|
25
|
+
HEARTBEAT_DEFAULT_INTERVAL,
|
|
26
|
+
HEARTBEAT_PATIENCE,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
from ..object_store import ObjectStore
|
|
30
|
+
from .corestate import CoreState
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class TokenRecord:
|
|
35
|
+
"""Record containing token and heartbeat information."""
|
|
36
|
+
|
|
37
|
+
token: str
|
|
38
|
+
active_until: float
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class InMemoryCoreState(CoreState):
|
|
42
|
+
"""In-memory CoreState implementation."""
|
|
43
|
+
|
|
44
|
+
def __init__(self, object_store: ObjectStore) -> None:
|
|
45
|
+
self._object_store = object_store
|
|
46
|
+
# Store run ID to token mapping and token to run ID mapping
|
|
47
|
+
self.token_store: dict[int, TokenRecord] = {}
|
|
48
|
+
self.token_to_run_id: dict[str, int] = {}
|
|
49
|
+
self.lock_token_store = Lock()
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def object_store(self) -> ObjectStore:
|
|
53
|
+
"""Return the ObjectStore instance used by this CoreState."""
|
|
54
|
+
return self._object_store
|
|
55
|
+
|
|
56
|
+
def create_token(self, run_id: int) -> str | None:
|
|
57
|
+
"""Create a token for the given run ID."""
|
|
58
|
+
token = secrets.token_hex(FLWR_APP_TOKEN_LENGTH) # Generate a random token
|
|
59
|
+
with self.lock_token_store:
|
|
60
|
+
if run_id in self.token_store:
|
|
61
|
+
return None # Token already created for this run ID
|
|
62
|
+
|
|
63
|
+
self.token_store[run_id] = TokenRecord(
|
|
64
|
+
token=token, active_until=now().timestamp() + HEARTBEAT_DEFAULT_INTERVAL
|
|
65
|
+
)
|
|
66
|
+
self.token_to_run_id[token] = run_id
|
|
67
|
+
return token
|
|
68
|
+
|
|
69
|
+
def verify_token(self, run_id: int, token: str) -> bool:
|
|
70
|
+
"""Verify a token for the given run ID."""
|
|
71
|
+
self._cleanup_expired_tokens()
|
|
72
|
+
with self.lock_token_store:
|
|
73
|
+
record = self.token_store.get(run_id)
|
|
74
|
+
return record is not None and record.token == token
|
|
75
|
+
|
|
76
|
+
def delete_token(self, run_id: int) -> None:
|
|
77
|
+
"""Delete the token for the given run ID."""
|
|
78
|
+
with self.lock_token_store:
|
|
79
|
+
record = self.token_store.pop(run_id, None)
|
|
80
|
+
if record is not None:
|
|
81
|
+
self.token_to_run_id.pop(record.token, None)
|
|
82
|
+
|
|
83
|
+
def get_run_id_by_token(self, token: str) -> int | None:
|
|
84
|
+
"""Get the run ID associated with a given token."""
|
|
85
|
+
self._cleanup_expired_tokens()
|
|
86
|
+
with self.lock_token_store:
|
|
87
|
+
return self.token_to_run_id.get(token)
|
|
88
|
+
|
|
89
|
+
def acknowledge_app_heartbeat(self, token: str) -> bool:
|
|
90
|
+
"""Acknowledge an app heartbeat with the provided token."""
|
|
91
|
+
# Clean up expired tokens
|
|
92
|
+
self._cleanup_expired_tokens()
|
|
93
|
+
|
|
94
|
+
with self.lock_token_store:
|
|
95
|
+
# Return False if token is not found
|
|
96
|
+
if token not in self.token_to_run_id:
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
# Get the run_id and update heartbeat info
|
|
100
|
+
run_id = self.token_to_run_id[token]
|
|
101
|
+
record = self.token_store[run_id]
|
|
102
|
+
current = now().timestamp()
|
|
103
|
+
record.active_until = (
|
|
104
|
+
current + HEARTBEAT_PATIENCE * HEARTBEAT_DEFAULT_INTERVAL
|
|
105
|
+
)
|
|
106
|
+
return True
|
|
107
|
+
|
|
108
|
+
def _cleanup_expired_tokens(self) -> None:
|
|
109
|
+
"""Remove expired tokens and perform additional cleanup.
|
|
110
|
+
|
|
111
|
+
This method is called before token operations to ensure integrity.
|
|
112
|
+
Subclasses can override `_on_tokens_expired` to add custom cleanup logic.
|
|
113
|
+
"""
|
|
114
|
+
with self.lock_token_store:
|
|
115
|
+
current = now().timestamp()
|
|
116
|
+
expired_records: list[tuple[int, float]] = []
|
|
117
|
+
for run_id, record in list(self.token_store.items()):
|
|
118
|
+
if record.active_until < current:
|
|
119
|
+
expired_records.append((run_id, record.active_until))
|
|
120
|
+
# Remove from both stores
|
|
121
|
+
del self.token_store[run_id]
|
|
122
|
+
self.token_to_run_id.pop(record.token, None)
|
|
123
|
+
|
|
124
|
+
# Hook for subclasses
|
|
125
|
+
if expired_records:
|
|
126
|
+
self._on_tokens_expired(expired_records)
|
|
127
|
+
|
|
128
|
+
def _on_tokens_expired(self, expired_records: list[tuple[int, float]]) -> None:
|
|
129
|
+
"""Handle cleanup of expired tokens.
|
|
130
|
+
|
|
131
|
+
Override in subclasses to add custom cleanup logic.
|
|
132
|
+
|
|
133
|
+
Parameters
|
|
134
|
+
----------
|
|
135
|
+
expired_records : list[tuple[int, float]]
|
|
136
|
+
List of tuples containing (run_id, active_until timestamp)
|
|
137
|
+
for expired tokens.
|
|
138
|
+
"""
|
|
@@ -0,0 +1,157 @@
|
|
|
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
|
+
"""SQLite-based CoreState implementation."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
import secrets
|
|
19
|
+
import sqlite3
|
|
20
|
+
from typing import cast
|
|
21
|
+
|
|
22
|
+
from flwr.common import now
|
|
23
|
+
from flwr.common.constant import (
|
|
24
|
+
FLWR_APP_TOKEN_LENGTH,
|
|
25
|
+
HEARTBEAT_DEFAULT_INTERVAL,
|
|
26
|
+
HEARTBEAT_PATIENCE,
|
|
27
|
+
)
|
|
28
|
+
from flwr.supercore.sqlite_mixin import SqliteMixin
|
|
29
|
+
from flwr.supercore.utils import int64_to_uint64, uint64_to_int64
|
|
30
|
+
|
|
31
|
+
from ..object_store import ObjectStore
|
|
32
|
+
from .corestate import CoreState
|
|
33
|
+
|
|
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
|
+
|
|
43
|
+
class SqliteCoreState(CoreState, SqliteMixin):
|
|
44
|
+
"""SQLite-based CoreState implementation."""
|
|
45
|
+
|
|
46
|
+
def __init__(self, database_path: str, object_store: ObjectStore) -> None:
|
|
47
|
+
super().__init__(database_path)
|
|
48
|
+
self._object_store = object_store
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def object_store(self) -> ObjectStore:
|
|
52
|
+
"""Return the ObjectStore instance used by this CoreState."""
|
|
53
|
+
return self._object_store
|
|
54
|
+
|
|
55
|
+
def get_sql_statements(self) -> tuple[str, ...]:
|
|
56
|
+
"""Return SQL statements needed for CoreState tables."""
|
|
57
|
+
return (SQL_CREATE_TABLE_TOKEN_STORE,)
|
|
58
|
+
|
|
59
|
+
def create_token(self, run_id: int) -> str | None:
|
|
60
|
+
"""Create a token for the given run ID."""
|
|
61
|
+
token = secrets.token_hex(FLWR_APP_TOKEN_LENGTH) # Generate a random token
|
|
62
|
+
current = now().timestamp()
|
|
63
|
+
active_until = current + HEARTBEAT_DEFAULT_INTERVAL
|
|
64
|
+
query = """
|
|
65
|
+
INSERT INTO token_store (run_id, token, active_until)
|
|
66
|
+
VALUES (:run_id, :token, :active_until);
|
|
67
|
+
"""
|
|
68
|
+
data = {
|
|
69
|
+
"run_id": uint64_to_int64(run_id),
|
|
70
|
+
"token": token,
|
|
71
|
+
"active_until": active_until,
|
|
72
|
+
}
|
|
73
|
+
try:
|
|
74
|
+
self.query(query, data)
|
|
75
|
+
except sqlite3.IntegrityError:
|
|
76
|
+
return None # Token already created for this run ID
|
|
77
|
+
return token
|
|
78
|
+
|
|
79
|
+
def verify_token(self, run_id: int, token: str) -> bool:
|
|
80
|
+
"""Verify a token for the given run ID."""
|
|
81
|
+
self._cleanup_expired_tokens()
|
|
82
|
+
query = "SELECT token FROM token_store WHERE run_id = :run_id;"
|
|
83
|
+
data = {"run_id": uint64_to_int64(run_id)}
|
|
84
|
+
rows = self.query(query, data)
|
|
85
|
+
if not rows:
|
|
86
|
+
return False
|
|
87
|
+
return cast(str, rows[0]["token"]) == token
|
|
88
|
+
|
|
89
|
+
def delete_token(self, run_id: int) -> None:
|
|
90
|
+
"""Delete the token for the given run ID."""
|
|
91
|
+
query = "DELETE FROM token_store WHERE run_id = :run_id;"
|
|
92
|
+
data = {"run_id": uint64_to_int64(run_id)}
|
|
93
|
+
self.query(query, data)
|
|
94
|
+
|
|
95
|
+
def get_run_id_by_token(self, token: str) -> int | None:
|
|
96
|
+
"""Get the run ID associated with a given token."""
|
|
97
|
+
self._cleanup_expired_tokens()
|
|
98
|
+
query = "SELECT run_id FROM token_store WHERE token = :token;"
|
|
99
|
+
data = {"token": token}
|
|
100
|
+
rows = self.query(query, data)
|
|
101
|
+
if not rows:
|
|
102
|
+
return None
|
|
103
|
+
return int64_to_uint64(rows[0]["run_id"])
|
|
104
|
+
|
|
105
|
+
def acknowledge_app_heartbeat(self, token: str) -> bool:
|
|
106
|
+
"""Acknowledge an app heartbeat with the provided token."""
|
|
107
|
+
# Clean up expired tokens
|
|
108
|
+
self._cleanup_expired_tokens()
|
|
109
|
+
|
|
110
|
+
# Update the active_until field
|
|
111
|
+
current = now().timestamp()
|
|
112
|
+
active_until = current + HEARTBEAT_PATIENCE * HEARTBEAT_DEFAULT_INTERVAL
|
|
113
|
+
query = """
|
|
114
|
+
UPDATE token_store
|
|
115
|
+
SET active_until = :active_until
|
|
116
|
+
WHERE token = :token
|
|
117
|
+
RETURNING run_id;
|
|
118
|
+
"""
|
|
119
|
+
data = {"active_until": active_until, "token": token}
|
|
120
|
+
rows = self.query(query, data)
|
|
121
|
+
return len(rows) > 0
|
|
122
|
+
|
|
123
|
+
def _cleanup_expired_tokens(self) -> None:
|
|
124
|
+
"""Remove expired tokens and perform additional cleanup.
|
|
125
|
+
|
|
126
|
+
This method is called before token operations to ensure integrity.
|
|
127
|
+
Subclasses can override `_on_tokens_expired` to add custom cleanup logic.
|
|
128
|
+
"""
|
|
129
|
+
current = now().timestamp()
|
|
130
|
+
|
|
131
|
+
with self.conn:
|
|
132
|
+
# Delete expired tokens and get their run_ids and active_until timestamps
|
|
133
|
+
query = """
|
|
134
|
+
DELETE FROM token_store
|
|
135
|
+
WHERE active_until < :current
|
|
136
|
+
RETURNING run_id, active_until;
|
|
137
|
+
"""
|
|
138
|
+
rows = self.conn.execute(query, {"current": current}).fetchall()
|
|
139
|
+
expired_records = [
|
|
140
|
+
(int64_to_uint64(row["run_id"]), row["active_until"]) for row in rows
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
# Hook for subclasses
|
|
144
|
+
if expired_records:
|
|
145
|
+
self._on_tokens_expired(expired_records)
|
|
146
|
+
|
|
147
|
+
def _on_tokens_expired(self, expired_records: list[tuple[int, float]]) -> None:
|
|
148
|
+
"""Handle cleanup of expired tokens.
|
|
149
|
+
|
|
150
|
+
Override in subclasses to add custom cleanup logic.
|
|
151
|
+
|
|
152
|
+
Parameters
|
|
153
|
+
----------
|
|
154
|
+
expired_records : list[tuple[int, float]]
|
|
155
|
+
List of tuples containing (run_id, active_until timestamp)
|
|
156
|
+
for expired tokens.
|
|
157
|
+
"""
|
flwr/supercore/ffs/disk_ffs.py
CHANGED
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
import hashlib
|
|
19
19
|
import json
|
|
20
20
|
from pathlib import Path
|
|
21
|
-
from typing import Optional
|
|
22
21
|
|
|
23
22
|
from .ffs import Ffs
|
|
24
23
|
|
|
@@ -59,7 +58,7 @@ class DiskFfs(Ffs): # pylint: disable=R0904
|
|
|
59
58
|
|
|
60
59
|
return content_hash
|
|
61
60
|
|
|
62
|
-
def get(self, key: str) ->
|
|
61
|
+
def get(self, key: str) -> tuple[bytes, dict[str, str]] | None:
|
|
63
62
|
"""Return tuple containing the object content and metadata.
|
|
64
63
|
|
|
65
64
|
Parameters
|
flwr/supercore/ffs/ffs.py
CHANGED
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
import abc
|
|
19
|
-
from typing import Optional
|
|
20
19
|
|
|
21
20
|
|
|
22
21
|
class Ffs(abc.ABC): # pylint: disable=R0904
|
|
@@ -40,7 +39,7 @@ class Ffs(abc.ABC): # pylint: disable=R0904
|
|
|
40
39
|
"""
|
|
41
40
|
|
|
42
41
|
@abc.abstractmethod
|
|
43
|
-
def get(self, key: str) ->
|
|
42
|
+
def get(self, key: str) -> tuple[bytes, dict[str, str]] | None:
|
|
44
43
|
"""Return tuple containing the object content and metadata.
|
|
45
44
|
|
|
46
45
|
Parameters
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
from logging import DEBUG
|
|
19
|
-
from typing import Optional
|
|
20
19
|
|
|
21
20
|
from flwr.common.logger import log
|
|
22
21
|
|
|
@@ -35,7 +34,7 @@ class FfsFactory:
|
|
|
35
34
|
|
|
36
35
|
def __init__(self, base_dir: str) -> None:
|
|
37
36
|
self.base_dir = base_dir
|
|
38
|
-
self.ffs_instance:
|
|
37
|
+
self.ffs_instance: Ffs | None = None
|
|
39
38
|
|
|
40
39
|
def ffs(self) -> Ffs:
|
|
41
40
|
"""Return a Ffs instance and create it, if necessary."""
|