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
|
@@ -16,24 +16,27 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
import random
|
|
19
|
+
import signal
|
|
19
20
|
import threading
|
|
20
|
-
from
|
|
21
|
+
from collections.abc import Callable
|
|
21
22
|
|
|
22
23
|
import grpc
|
|
23
24
|
|
|
25
|
+
from flwr.common.constant import (
|
|
26
|
+
HEARTBEAT_BASE_MULTIPLIER,
|
|
27
|
+
HEARTBEAT_CALL_TIMEOUT,
|
|
28
|
+
HEARTBEAT_DEFAULT_INTERVAL,
|
|
29
|
+
HEARTBEAT_RANDOM_RANGE,
|
|
30
|
+
)
|
|
31
|
+
from flwr.common.retry_invoker import RetryInvoker, exponential
|
|
32
|
+
from flwr.proto.clientappio_pb2_grpc import ClientAppIoStub
|
|
33
|
+
|
|
24
34
|
# pylint: disable=E0611
|
|
25
35
|
from flwr.proto.heartbeat_pb2 import SendAppHeartbeatRequest
|
|
26
36
|
from flwr.proto.serverappio_pb2_grpc import ServerAppIoStub
|
|
27
37
|
from flwr.proto.simulationio_pb2_grpc import SimulationIoStub
|
|
28
38
|
|
|
29
39
|
# pylint: enable=E0611
|
|
30
|
-
from .constant import (
|
|
31
|
-
HEARTBEAT_BASE_MULTIPLIER,
|
|
32
|
-
HEARTBEAT_CALL_TIMEOUT,
|
|
33
|
-
HEARTBEAT_DEFAULT_INTERVAL,
|
|
34
|
-
HEARTBEAT_RANDOM_RANGE,
|
|
35
|
-
)
|
|
36
|
-
from .retry_invoker import RetryInvoker, exponential
|
|
37
40
|
|
|
38
41
|
|
|
39
42
|
class HeartbeatFailure(Exception):
|
|
@@ -116,24 +119,18 @@ class HeartbeatSender:
|
|
|
116
119
|
raise HeartbeatFailure
|
|
117
120
|
|
|
118
121
|
|
|
119
|
-
def
|
|
120
|
-
stub:
|
|
121
|
-
|
|
122
|
-
*,
|
|
123
|
-
failure_message: str,
|
|
122
|
+
def make_app_heartbeat_fn_grpc(
|
|
123
|
+
stub: ServerAppIoStub | SimulationIoStub | ClientAppIoStub,
|
|
124
|
+
token: str,
|
|
124
125
|
) -> Callable[[], bool]:
|
|
125
|
-
"""Get the function to send a heartbeat to gRPC endpoint.
|
|
126
|
-
|
|
127
|
-
This function is for app heartbeats only. It is not used for node heartbeats.
|
|
126
|
+
"""Get the function to send a heartbeat to gRPC endpoint from an app process.
|
|
128
127
|
|
|
129
128
|
Parameters
|
|
130
129
|
----------
|
|
131
130
|
stub : Union[ServerAppIoStub, SimulationIoStub]
|
|
132
131
|
gRPC stub to send the heartbeat.
|
|
133
|
-
|
|
134
|
-
The
|
|
135
|
-
failure_message : str
|
|
136
|
-
Error message to raise if the heartbeat fails.
|
|
132
|
+
token : str
|
|
133
|
+
The token to use in the heartbeat request.
|
|
137
134
|
|
|
138
135
|
Returns
|
|
139
136
|
-------
|
|
@@ -141,9 +138,7 @@ def get_grpc_app_heartbeat_fn(
|
|
|
141
138
|
Function that sends a heartbeat to the gRPC endpoint.
|
|
142
139
|
"""
|
|
143
140
|
# Construct the heartbeat request
|
|
144
|
-
req = SendAppHeartbeatRequest(
|
|
145
|
-
run_id=run_id, heartbeat_interval=HEARTBEAT_DEFAULT_INTERVAL
|
|
146
|
-
)
|
|
141
|
+
req = SendAppHeartbeatRequest(token=token)
|
|
147
142
|
|
|
148
143
|
def fn() -> bool:
|
|
149
144
|
# Call ServerAppIo API
|
|
@@ -157,9 +152,9 @@ def get_grpc_app_heartbeat_fn(
|
|
|
157
152
|
return False
|
|
158
153
|
raise
|
|
159
154
|
|
|
160
|
-
#
|
|
155
|
+
# Raise SIGINT to trigger graceful shutdown if heartbeat failed
|
|
161
156
|
if not res.success:
|
|
162
|
-
|
|
157
|
+
signal.raise_signal(signal.SIGINT)
|
|
163
158
|
return True
|
|
164
159
|
|
|
165
160
|
return fn
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
|
|
18
18
|
import threading
|
|
19
19
|
from dataclasses import dataclass
|
|
20
|
-
from typing import Optional
|
|
21
20
|
|
|
22
21
|
from flwr.common.inflatable import (
|
|
23
22
|
get_object_id,
|
|
@@ -154,7 +153,7 @@ class InMemoryObjectStore(ObjectStore):
|
|
|
154
153
|
self.store[object_id].content = object_content
|
|
155
154
|
self.store[object_id].is_available = True
|
|
156
155
|
|
|
157
|
-
def get(self, object_id: str) ->
|
|
156
|
+
def get(self, object_id: str) -> bytes | None:
|
|
158
157
|
"""Get an object from the store."""
|
|
159
158
|
with self.lock_store:
|
|
160
159
|
# Check if the object ID is pre-registered
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
import abc
|
|
19
|
-
from typing import Optional
|
|
20
19
|
|
|
21
20
|
from flwr.proto.message_pb2 import ObjectTree # pylint: disable=E0611
|
|
22
21
|
|
|
@@ -89,7 +88,7 @@ class ObjectStore(abc.ABC):
|
|
|
89
88
|
"""
|
|
90
89
|
|
|
91
90
|
@abc.abstractmethod
|
|
92
|
-
def get(self, object_id: str) ->
|
|
91
|
+
def get(self, object_id: str) -> bytes | None:
|
|
93
92
|
"""Get an object from the store.
|
|
94
93
|
|
|
95
94
|
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
|
from flwr.supercore.constant import FLWR_IN_MEMORY_DB_NAME
|
|
@@ -40,7 +39,7 @@ class ObjectStoreFactory:
|
|
|
40
39
|
|
|
41
40
|
def __init__(self, database: str = FLWR_IN_MEMORY_DB_NAME) -> None:
|
|
42
41
|
self.database = database
|
|
43
|
-
self.store_instance:
|
|
42
|
+
self.store_instance: ObjectStore | None = None
|
|
44
43
|
|
|
45
44
|
def store(self) -> ObjectStore:
|
|
46
45
|
"""Return an ObjectStore instance and create it, if necessary.
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"""Flower SQLite ObjectStore implementation."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
from typing import
|
|
18
|
+
from typing import cast
|
|
19
19
|
|
|
20
20
|
from flwr.common.inflatable import (
|
|
21
21
|
get_object_id,
|
|
@@ -63,13 +63,12 @@ class SqliteObjectStore(ObjectStore, SqliteMixin):
|
|
|
63
63
|
super().__init__(database_path)
|
|
64
64
|
self.verify = verify
|
|
65
65
|
|
|
66
|
-
def
|
|
67
|
-
"""
|
|
68
|
-
return
|
|
66
|
+
def get_sql_statements(self) -> tuple[str, ...]:
|
|
67
|
+
"""Return SQL statements for ObjectStore tables."""
|
|
68
|
+
return (
|
|
69
69
|
SQL_CREATE_OBJECTS,
|
|
70
70
|
SQL_CREATE_OBJECT_CHILDREN,
|
|
71
71
|
SQL_CREATE_RUN_OBJECTS,
|
|
72
|
-
log_queries=log_queries,
|
|
73
72
|
)
|
|
74
73
|
|
|
75
74
|
def preregister(self, run_id: int, object_tree: ObjectTree) -> list[str]:
|
|
@@ -126,7 +125,9 @@ class SqliteObjectStore(ObjectStore, SqliteMixin):
|
|
|
126
125
|
"SELECT object_id FROM objects WHERE object_id=?", (object_id,)
|
|
127
126
|
).fetchone()
|
|
128
127
|
if not row:
|
|
129
|
-
raise NoObjectInStoreError(
|
|
128
|
+
raise NoObjectInStoreError(
|
|
129
|
+
f"Object {object_id} was not pre-registered."
|
|
130
|
+
)
|
|
130
131
|
children = self.query(
|
|
131
132
|
"SELECT child_id FROM object_children WHERE parent_id=?", (object_id,)
|
|
132
133
|
)
|
|
@@ -176,7 +177,7 @@ class SqliteObjectStore(ObjectStore, SqliteMixin):
|
|
|
176
177
|
(object_content, object_id),
|
|
177
178
|
)
|
|
178
179
|
|
|
179
|
-
def get(self, object_id: str) ->
|
|
180
|
+
def get(self, object_id: str) -> bytes | None:
|
|
180
181
|
"""Get an object from the store."""
|
|
181
182
|
rows = self.query("SELECT content FROM objects WHERE object_id=?", (object_id,))
|
|
182
183
|
return rows[0]["content"] if rows else None
|
|
@@ -113,5 +113,5 @@ def uses_nist_ec_curve(public_key: ec.EllipticCurvePublicKey) -> bool:
|
|
|
113
113
|
"""Return True if the provided key uses a NIST EC curve."""
|
|
114
114
|
return isinstance(
|
|
115
115
|
public_key.curve,
|
|
116
|
-
(ec.SECP192R1
|
|
116
|
+
(ec.SECP192R1 | ec.SECP224R1 | ec.SECP256R1 | ec.SECP384R1 | ec.SECP521R1),
|
|
117
117
|
)
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"""Ed25519-only asymmetric cryptography utilities."""
|
|
16
16
|
|
|
17
17
|
import base64
|
|
18
|
+
from pathlib import Path
|
|
18
19
|
|
|
19
20
|
from cryptography.exceptions import InvalidSignature
|
|
20
21
|
from cryptography.hazmat.primitives import serialization
|
|
@@ -150,7 +151,7 @@ def verify_signature(
|
|
|
150
151
|
return False
|
|
151
152
|
|
|
152
153
|
|
|
153
|
-
def
|
|
154
|
+
def create_message_to_sign(fab_digest: bytes, timestamp: int) -> bytes:
|
|
154
155
|
"""Create a canonical message:
|
|
155
156
|
timestamp (8 bytes big-endian) + fab_digest.
|
|
156
157
|
"""
|
|
@@ -163,3 +164,12 @@ def decode_base64url(sig: str) -> bytes:
|
|
|
163
164
|
# add missing padding (=) to a multiple of 4
|
|
164
165
|
pad = (-len(sig)) % 4
|
|
165
166
|
return base64.urlsafe_b64decode(sig + ("=" * pad))
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def load_private_key(path: Path) -> ed25519.Ed25519PrivateKey:
|
|
170
|
+
"""Load an SSH-format private key (Ed25519) using cryptography."""
|
|
171
|
+
key_bytes = path.read_bytes()
|
|
172
|
+
private_key = serialization.load_ssh_private_key(key_bytes, password=None)
|
|
173
|
+
if not isinstance(private_key, ed25519.Ed25519PrivateKey):
|
|
174
|
+
raise ValueError("Private key is not Ed25519")
|
|
175
|
+
return private_key
|
flwr/supercore/sqlite_mixin.py
CHANGED
|
@@ -17,14 +17,14 @@
|
|
|
17
17
|
|
|
18
18
|
import re
|
|
19
19
|
import sqlite3
|
|
20
|
-
from abc import ABC
|
|
20
|
+
from abc import ABC
|
|
21
21
|
from collections.abc import Sequence
|
|
22
22
|
from logging import DEBUG, ERROR
|
|
23
|
-
from typing import Any
|
|
23
|
+
from typing import Any
|
|
24
24
|
|
|
25
25
|
from flwr.common.logger import log
|
|
26
26
|
|
|
27
|
-
DictOrTuple =
|
|
27
|
+
DictOrTuple = tuple[Any, ...] | dict[str, Any]
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
class SqliteMixin(ABC):
|
|
@@ -32,7 +32,7 @@ class SqliteMixin(ABC):
|
|
|
32
32
|
|
|
33
33
|
def __init__(self, database_path: str) -> None:
|
|
34
34
|
self.database_path = database_path
|
|
35
|
-
self._conn:
|
|
35
|
+
self._conn: sqlite3.Connection | None = None
|
|
36
36
|
|
|
37
37
|
@property
|
|
38
38
|
def conn(self) -> sqlite3.Connection:
|
|
@@ -41,10 +41,24 @@ class SqliteMixin(ABC):
|
|
|
41
41
|
raise AttributeError("Database not initialized. Call initialize() first.")
|
|
42
42
|
return self._conn
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
def get_sql_statements(self) -> tuple[str, ...]:
|
|
45
|
+
"""Return SQL statements for this class.
|
|
46
|
+
|
|
47
|
+
Subclasses can override this to provide their SQL CREATE statements.
|
|
48
|
+
The base implementation returns an empty tuple.
|
|
49
|
+
|
|
50
|
+
Returns
|
|
51
|
+
-------
|
|
52
|
+
tuple[str, ...]
|
|
53
|
+
SQL CREATE TABLE/INDEX statements for this class.
|
|
54
|
+
"""
|
|
55
|
+
return ()
|
|
56
|
+
|
|
45
57
|
def initialize(self, log_queries: bool = False) -> list[tuple[str]]:
|
|
46
58
|
"""Connect to the DB, enable FK support, and create tables if needed.
|
|
47
59
|
|
|
60
|
+
This method executes SQL statements returned by `get_sql_statements()`.
|
|
61
|
+
|
|
48
62
|
Parameters
|
|
49
63
|
----------
|
|
50
64
|
log_queries : bool
|
|
@@ -57,45 +71,34 @@ class SqliteMixin(ABC):
|
|
|
57
71
|
|
|
58
72
|
Examples
|
|
59
73
|
--------
|
|
60
|
-
|
|
74
|
+
Override `get_sql_statements()` in your subclass:
|
|
61
75
|
|
|
62
76
|
.. code:: python
|
|
63
77
|
|
|
64
|
-
def
|
|
65
|
-
return
|
|
78
|
+
def get_sql_statements(self) -> tuple[str, ...]:
|
|
79
|
+
return (
|
|
66
80
|
SQL_CREATE_TABLE_FOO,
|
|
67
81
|
SQL_CREATE_TABLE_BAR,
|
|
68
|
-
log_queries=log_queries
|
|
69
82
|
)
|
|
70
|
-
"""
|
|
71
|
-
|
|
72
|
-
def _ensure_initialized(
|
|
73
|
-
self,
|
|
74
|
-
*create_statements: str,
|
|
75
|
-
log_queries: bool = False,
|
|
76
|
-
) -> list[tuple[str]]:
|
|
77
|
-
"""Connect to the DB, enable FK support, and create tables if needed.
|
|
78
83
|
|
|
79
|
-
|
|
80
|
-
their `.initialize()` methods.
|
|
84
|
+
To include parent SQL statements, call super():
|
|
81
85
|
|
|
82
|
-
|
|
83
|
-
----------
|
|
84
|
-
create_statements : str
|
|
85
|
-
SQL statements to create tables and indexes.
|
|
86
|
-
log_queries : bool
|
|
87
|
-
Log each query which is executed.
|
|
86
|
+
.. code:: python
|
|
88
87
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
88
|
+
def get_sql_statements(self) -> tuple[str, ...]:
|
|
89
|
+
return super().get_sql_statements() + (
|
|
90
|
+
SQL_CREATE_TABLE_FOO,
|
|
91
|
+
SQL_CREATE_TABLE_BAR,
|
|
92
|
+
)
|
|
93
93
|
"""
|
|
94
94
|
self._conn = sqlite3.connect(self.database_path)
|
|
95
95
|
# Enable Write-Ahead Logging (WAL) for better concurrency
|
|
96
96
|
self._conn.execute("PRAGMA journal_mode = WAL;")
|
|
97
97
|
self._conn.execute("PRAGMA synchronous = NORMAL;")
|
|
98
98
|
self._conn.execute("PRAGMA foreign_keys = ON;")
|
|
99
|
+
self._conn.execute("PRAGMA cache_size = -64000;") # 64MB cache
|
|
100
|
+
self._conn.execute("PRAGMA temp_store = MEMORY;") # In-memory temp tables
|
|
101
|
+
self._conn.execute("PRAGMA mmap_size = 268435456;") # 256MB memory-mapped I/O
|
|
99
102
|
self._conn.row_factory = dict_factory
|
|
100
103
|
|
|
101
104
|
if log_queries:
|
|
@@ -103,7 +106,7 @@ class SqliteMixin(ABC):
|
|
|
103
106
|
|
|
104
107
|
# Create tables and indexes
|
|
105
108
|
cur = self._conn.cursor()
|
|
106
|
-
for sql in
|
|
109
|
+
for sql in self.get_sql_statements():
|
|
107
110
|
cur.execute(sql)
|
|
108
111
|
res = cur.execute("SELECT name FROM sqlite_schema;")
|
|
109
112
|
return res.fetchall()
|
|
@@ -111,7 +114,7 @@ class SqliteMixin(ABC):
|
|
|
111
114
|
def query(
|
|
112
115
|
self,
|
|
113
116
|
query: str,
|
|
114
|
-
data:
|
|
117
|
+
data: Sequence[DictOrTuple] | DictOrTuple | None = None,
|
|
115
118
|
) -> list[dict[str, Any]]:
|
|
116
119
|
"""Execute a SQL query and return the results as list of dicts."""
|
|
117
120
|
if self._conn is None:
|
|
@@ -127,8 +130,8 @@ class SqliteMixin(ABC):
|
|
|
127
130
|
with self._conn:
|
|
128
131
|
if (
|
|
129
132
|
len(data) > 0
|
|
130
|
-
and isinstance(data, (tuple
|
|
131
|
-
and isinstance(data[0], (tuple
|
|
133
|
+
and isinstance(data, (tuple | list))
|
|
134
|
+
and isinstance(data[0], (tuple | dict))
|
|
132
135
|
):
|
|
133
136
|
rows = self._conn.executemany(query, data)
|
|
134
137
|
else:
|
|
@@ -153,4 +156,4 @@ def dict_factory(
|
|
|
153
156
|
Less efficent for retrival of large amounts of data but easier to use.
|
|
154
157
|
"""
|
|
155
158
|
fields = [column[0] for column in cursor.description]
|
|
156
|
-
return dict(zip(fields, row))
|
|
159
|
+
return dict(zip(fields, row, strict=True))
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
import os
|
|
19
19
|
import subprocess
|
|
20
20
|
from collections.abc import Sequence
|
|
21
|
-
from typing import Optional
|
|
22
21
|
|
|
23
22
|
from .exec_plugin import ExecPlugin
|
|
24
23
|
|
|
@@ -33,7 +32,7 @@ class BaseExecPlugin(ExecPlugin):
|
|
|
33
32
|
command = ""
|
|
34
33
|
appio_api_address_arg = ""
|
|
35
34
|
|
|
36
|
-
def select_run_id(self, candidate_run_ids: Sequence[int]) ->
|
|
35
|
+
def select_run_id(self, candidate_run_ids: Sequence[int]) -> int | None:
|
|
37
36
|
"""Select a run ID to execute from a sequence of candidates."""
|
|
38
37
|
if not candidate_run_ids:
|
|
39
38
|
return None
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
from abc import ABC, abstractmethod
|
|
19
|
-
from collections.abc import Sequence
|
|
20
|
-
from typing import Any
|
|
19
|
+
from collections.abc import Callable, Sequence
|
|
20
|
+
from typing import Any
|
|
21
21
|
|
|
22
22
|
from flwr.common.typing import Run
|
|
23
23
|
|
|
@@ -36,7 +36,7 @@ class ExecPlugin(ABC):
|
|
|
36
36
|
self.get_run = get_run
|
|
37
37
|
|
|
38
38
|
@abstractmethod
|
|
39
|
-
def select_run_id(self, candidate_run_ids: Sequence[int]) ->
|
|
39
|
+
def select_run_id(self, candidate_run_ids: Sequence[int]) -> int | None:
|
|
40
40
|
"""Select a run ID to execute from a sequence of candidates.
|
|
41
41
|
|
|
42
42
|
A candidate run ID is one that has at least one pending message and is
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import time
|
|
19
19
|
from logging import WARN
|
|
20
|
-
from typing import Any
|
|
20
|
+
from typing import Any
|
|
21
21
|
|
|
22
22
|
from flwr.common.config import get_flwr_dir
|
|
23
23
|
from flwr.common.exit import ExitCode, flwr_exit, register_signal_handlers
|
|
@@ -43,14 +43,12 @@ from .plugin import ExecPlugin
|
|
|
43
43
|
|
|
44
44
|
def run_superexec( # pylint: disable=R0913,R0914,R0917
|
|
45
45
|
plugin_class: type[ExecPlugin],
|
|
46
|
-
stub_class:
|
|
47
|
-
type[ClientAppIoStub], type[ServerAppIoStub], type[SimulationIoStub]
|
|
48
|
-
],
|
|
46
|
+
stub_class: type[ClientAppIoStub] | type[ServerAppIoStub] | type[SimulationIoStub],
|
|
49
47
|
appio_api_address: str,
|
|
50
|
-
plugin_config:
|
|
51
|
-
flwr_dir:
|
|
52
|
-
parent_pid:
|
|
53
|
-
health_server_address:
|
|
48
|
+
plugin_config: dict[str, Any] | None = None,
|
|
49
|
+
flwr_dir: str | None = None,
|
|
50
|
+
parent_pid: int | None = None,
|
|
51
|
+
health_server_address: str | None = None,
|
|
54
52
|
) -> None:
|
|
55
53
|
"""Run Flower SuperExec.
|
|
56
54
|
|
|
@@ -158,12 +156,10 @@ def run_with_deprecation_warning( # pylint: disable=R0913, R0917
|
|
|
158
156
|
cmd: str,
|
|
159
157
|
plugin_type: str,
|
|
160
158
|
plugin_class: type[ExecPlugin],
|
|
161
|
-
stub_class:
|
|
162
|
-
type[ClientAppIoStub], type[ServerAppIoStub], type[SimulationIoStub]
|
|
163
|
-
],
|
|
159
|
+
stub_class: type[ClientAppIoStub] | type[ServerAppIoStub] | type[SimulationIoStub],
|
|
164
160
|
appio_api_address: str,
|
|
165
|
-
flwr_dir:
|
|
166
|
-
parent_pid:
|
|
161
|
+
flwr_dir: str | None,
|
|
162
|
+
parent_pid: int | None,
|
|
167
163
|
warn_run_once: bool,
|
|
168
164
|
) -> None:
|
|
169
165
|
"""Log a deprecation warning and run the equivalent `flower-superexec` command.
|
|
@@ -16,14 +16,13 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
from abc import ABC, abstractmethod
|
|
19
|
-
from typing import Optional
|
|
20
19
|
|
|
21
20
|
|
|
22
21
|
class ArtifactProvider(ABC):
|
|
23
22
|
"""ArtifactProvider interface for providing artifact download links."""
|
|
24
23
|
|
|
25
24
|
@abstractmethod
|
|
26
|
-
def get_url(self, run_id: int) ->
|
|
25
|
+
def get_url(self, run_id: int) -> str | None:
|
|
27
26
|
"""Return the artifact download link for the given run ID."""
|
|
28
27
|
|
|
29
28
|
@property
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
from abc import ABC, abstractmethod
|
|
19
19
|
from collections.abc import Sequence
|
|
20
20
|
from pathlib import Path
|
|
21
|
-
from typing import Optional, Union
|
|
22
21
|
|
|
23
22
|
from flwr.common.typing import (
|
|
24
23
|
AccountAuthCredentials,
|
|
@@ -48,25 +47,23 @@ class ControlAuthnPlugin(ABC):
|
|
|
48
47
|
"""Abstract constructor."""
|
|
49
48
|
|
|
50
49
|
@abstractmethod
|
|
51
|
-
def get_login_details(self) ->
|
|
50
|
+
def get_login_details(self) -> AccountAuthLoginDetails | None:
|
|
52
51
|
"""Get the login details."""
|
|
53
52
|
|
|
54
53
|
@abstractmethod
|
|
55
54
|
def validate_tokens_in_metadata(
|
|
56
|
-
self, metadata: Sequence[tuple[str,
|
|
57
|
-
) -> tuple[bool,
|
|
55
|
+
self, metadata: Sequence[tuple[str, str | bytes]]
|
|
56
|
+
) -> tuple[bool, AccountInfo | None]:
|
|
58
57
|
"""Validate authentication tokens in the provided metadata."""
|
|
59
58
|
|
|
60
59
|
@abstractmethod
|
|
61
|
-
def get_auth_tokens(self, device_code: str) ->
|
|
60
|
+
def get_auth_tokens(self, device_code: str) -> AccountAuthCredentials | None:
|
|
62
61
|
"""Get authentication tokens."""
|
|
63
62
|
|
|
64
63
|
@abstractmethod
|
|
65
64
|
def refresh_tokens(
|
|
66
|
-
self, metadata: Sequence[tuple[str,
|
|
67
|
-
) -> tuple[
|
|
68
|
-
Optional[Sequence[tuple[str, Union[str, bytes]]]], Optional[AccountInfo]
|
|
69
|
-
]:
|
|
65
|
+
self, metadata: Sequence[tuple[str, str | bytes]]
|
|
66
|
+
) -> tuple[Sequence[tuple[str, str | bytes]] | None, AccountInfo | None]:
|
|
70
67
|
"""Refresh authentication tokens in the provided metadata."""
|
|
71
68
|
|
|
72
69
|
|
|
@@ -18,7 +18,6 @@ authorization plugins."""
|
|
|
18
18
|
|
|
19
19
|
from collections.abc import Sequence
|
|
20
20
|
from pathlib import Path
|
|
21
|
-
from typing import Optional, Union
|
|
22
21
|
|
|
23
22
|
from flwr.common.constant import NOOP_ACCOUNT_NAME, NOOP_FLWR_AID, AuthnType
|
|
24
23
|
from flwr.common.typing import (
|
|
@@ -45,7 +44,7 @@ class NoOpControlAuthnPlugin(ControlAuthnPlugin):
|
|
|
45
44
|
):
|
|
46
45
|
pass
|
|
47
46
|
|
|
48
|
-
def get_login_details(self) ->
|
|
47
|
+
def get_login_details(self) -> AccountAuthLoginDetails | None:
|
|
49
48
|
"""Get the login details."""
|
|
50
49
|
# This allows the `flwr login` command to load the NoOp plugin accordingly,
|
|
51
50
|
# which then raises a LoginError when attempting to login.
|
|
@@ -58,20 +57,18 @@ class NoOpControlAuthnPlugin(ControlAuthnPlugin):
|
|
|
58
57
|
)
|
|
59
58
|
|
|
60
59
|
def validate_tokens_in_metadata(
|
|
61
|
-
self, metadata: Sequence[tuple[str,
|
|
62
|
-
) -> tuple[bool,
|
|
60
|
+
self, metadata: Sequence[tuple[str, str | bytes]]
|
|
61
|
+
) -> tuple[bool, AccountInfo | None]:
|
|
63
62
|
"""Return valid for no-op plugin."""
|
|
64
63
|
return True, NOOP_ACCOUNT_INFO
|
|
65
64
|
|
|
66
|
-
def get_auth_tokens(self, device_code: str) ->
|
|
65
|
+
def get_auth_tokens(self, device_code: str) -> AccountAuthCredentials | None:
|
|
67
66
|
"""Get authentication tokens."""
|
|
68
67
|
raise RuntimeError("NoOp plugin does not support getting auth tokens.")
|
|
69
68
|
|
|
70
69
|
def refresh_tokens(
|
|
71
|
-
self, metadata: Sequence[tuple[str,
|
|
72
|
-
) -> tuple[
|
|
73
|
-
Optional[Sequence[tuple[str, Union[str, bytes]]]], Optional[AccountInfo]
|
|
74
|
-
]:
|
|
70
|
+
self, metadata: Sequence[tuple[str, str | bytes]]
|
|
71
|
+
) -> tuple[Sequence[tuple[str, str | bytes]] | None, AccountInfo | None]:
|
|
75
72
|
"""Refresh authentication tokens in the provided metadata."""
|
|
76
73
|
return metadata, NOOP_ACCOUNT_INFO
|
|
77
74
|
|
|
@@ -0,0 +1,24 @@
|
|
|
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
|
+
"""Federation Managers."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from .federation_manager import FederationManager
|
|
19
|
+
from .noop_federation_manager import NoOpFederationManager
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"FederationManager",
|
|
23
|
+
"NoOpFederationManager",
|
|
24
|
+
]
|
|
@@ -0,0 +1,64 @@
|
|
|
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
|
+
"""Abstract base class FederationManager."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from abc import ABC, abstractmethod
|
|
19
|
+
from typing import TYPE_CHECKING
|
|
20
|
+
|
|
21
|
+
from flwr.common.typing import Federation
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from flwr.server.superlink.linkstate.linkstate import LinkState
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class FederationManager(ABC):
|
|
28
|
+
"""Abstract base class for FederationManager."""
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def linkstate(self) -> "LinkState":
|
|
32
|
+
"""Return the LinkState instance."""
|
|
33
|
+
if not (ret := getattr(self, "_linkstate", None)):
|
|
34
|
+
raise RuntimeError("linkstate not set. Assign to linkstate property first.")
|
|
35
|
+
return ret # type: ignore
|
|
36
|
+
|
|
37
|
+
@linkstate.setter
|
|
38
|
+
def linkstate(self, linkstate: "LinkState") -> None:
|
|
39
|
+
"""Set the LinkState instance."""
|
|
40
|
+
self._linkstate = linkstate
|
|
41
|
+
|
|
42
|
+
@abstractmethod
|
|
43
|
+
def exists(self, federation: str) -> bool:
|
|
44
|
+
"""Check if a federation exists."""
|
|
45
|
+
|
|
46
|
+
@abstractmethod
|
|
47
|
+
def has_member(self, flwr_aid: str, federation: str) -> bool:
|
|
48
|
+
"""Check if the given account is a member of the federation."""
|
|
49
|
+
|
|
50
|
+
@abstractmethod
|
|
51
|
+
def filter_nodes(self, node_ids: set[int], federation: str) -> set[int]:
|
|
52
|
+
"""Given a list of node IDs, return sublist with nodes in federation."""
|
|
53
|
+
|
|
54
|
+
@abstractmethod
|
|
55
|
+
def has_node(self, node_id: int, federation: str) -> bool:
|
|
56
|
+
"""Given a node ID, check if it is in the federation."""
|
|
57
|
+
|
|
58
|
+
@abstractmethod
|
|
59
|
+
def get_federations(self, flwr_aid: str) -> list[str]:
|
|
60
|
+
"""Get federations of which the account is a member."""
|
|
61
|
+
|
|
62
|
+
@abstractmethod
|
|
63
|
+
def get_details(self, federation: str) -> Federation:
|
|
64
|
+
"""Get details of the federation."""
|