flwr 1.22.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 +34 -1
- 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/__init__.py +15 -6
- flwr/cli/auth_plugin/auth_plugin.py +94 -0
- flwr/cli/auth_plugin/noop_auth_plugin.py +101 -0
- flwr/cli/auth_plugin/oidc_cli_plugin.py +46 -32
- flwr/cli/build.py +166 -53
- flwr/cli/{cli_user_auth_interceptor.py → cli_account_auth_interceptor.py} +29 -11
- 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 +54 -11
- flwr/cli/login/login.py +41 -27
- flwr/cli/ls.py +177 -133
- flwr/cli/new/new.py +175 -40
- 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 +12 -7
- flwr/cli/run/run.py +82 -31
- flwr/cli/run_utils.py +130 -0
- flwr/cli/stop.py +27 -9
- flwr/cli/supernode/__init__.py +25 -0
- flwr/cli/supernode/ls.py +268 -0
- flwr/cli/supernode/register.py +190 -0
- flwr/cli/supernode/unregister.py +140 -0
- flwr/cli/utils.py +464 -81
- flwr/client/__init__.py +2 -1
- flwr/client/dpfedavg_numpy_client.py +4 -1
- flwr/client/grpc_adapter_client/connection.py +12 -15
- flwr/client/grpc_rere_client/connection.py +68 -41
- flwr/client/grpc_rere_client/grpc_adapter.py +34 -14
- flwr/client/grpc_rere_client/{client_interceptor.py → node_auth_client_interceptor.py} +5 -7
- flwr/client/message_handler/message_handler.py +2 -2
- flwr/client/mod/secure_aggregation/secaggplus_mod.py +10 -8
- flwr/client/numpy_client.py +1 -1
- flwr/client/rest_client/connection.py +94 -51
- flwr/client/run_info_store.py +4 -5
- flwr/client/typing.py +1 -1
- flwr/clientapp/__init__.py +1 -2
- flwr/{client → 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/{client/clientapp → clientapp}/utils.py +4 -4
- flwr/common/address.py +1 -2
- flwr/common/args.py +3 -4
- flwr/common/config.py +13 -16
- flwr/common/constant.py +56 -13
- 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 +39 -10
- 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 +48 -31
- flwr/common/logger.py +19 -19
- flwr/common/message.py +4 -4
- flwr/common/object_ref.py +7 -7
- flwr/common/record/array.py +6 -6
- flwr/common/record/arrayrecord.py +18 -21
- 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/crypto/symmetric_encryption.py +1 -89
- flwr/common/secure_aggregation/ndarrays_arithmetic.py +3 -3
- flwr/common/serde.py +9 -6
- flwr/common/serde_utils.py +2 -2
- flwr/common/telemetry.py +9 -5
- flwr/common/typing.py +59 -43
- flwr/compat/client/app.py +39 -38
- flwr/compat/client/grpc_client/connection.py +13 -13
- 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 +72 -40
- flwr/proto/control_pb2.pyi +319 -87
- flwr/proto/control_pb2_grpc.py +339 -28
- flwr/proto/control_pb2_grpc.pyi +209 -37
- 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 +24 -10
- flwr/proto/fab_pb2.pyi +68 -20
- 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 +45 -27
- flwr/proto/fleet_pb2.pyi +186 -70
- flwr/proto/fleet_pb2_grpc.py +277 -66
- flwr/proto/fleet_pb2_grpc.pyi +201 -55
- 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 +16 -4
- flwr/proto/node_pb2.pyi +77 -4
- 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 +173 -127
- 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 +19 -8
- 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 +136 -42
- flwr/server/superlink/fleet/grpc_rere/{server_interceptor.py → node_auth_server_interceptor.py} +28 -51
- flwr/server/superlink/fleet/message_handler/message_handler.py +100 -49
- flwr/server/superlink/fleet/rest_rere/rest_api.py +54 -33
- flwr/server/superlink/fleet/vce/backend/backend.py +2 -2
- flwr/server/superlink/fleet/vce/backend/raybackend.py +6 -6
- flwr/server/superlink/fleet/vce/vce_api.py +32 -13
- flwr/server/superlink/linkstate/in_memory_linkstate.py +266 -207
- flwr/server/superlink/linkstate/linkstate.py +161 -62
- flwr/server/superlink/linkstate/linkstate_factory.py +24 -6
- flwr/server/superlink/linkstate/sqlite_linkstate.py +698 -638
- flwr/server/superlink/linkstate/utils.py +9 -60
- flwr/server/superlink/serverappio/serverappio_grpc.py +1 -2
- flwr/server/superlink/serverappio/serverappio_servicer.py +28 -23
- flwr/server/superlink/simulation/simulationio_grpc.py +1 -2
- flwr/server/superlink/simulation/simulationio_servicer.py +19 -14
- flwr/server/superlink/utils.py +4 -6
- flwr/server/typing.py +1 -1
- flwr/server/utils/tensorboard.py +15 -8
- flwr/server/utils/validator.py +2 -3
- 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 +12 -10
- 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 +11 -12
- flwr/simulation/ray_transport/ray_client_proxy.py +12 -13
- flwr/simulation/run_simulation.py +44 -43
- flwr/simulation/simulationio_connection.py +4 -4
- flwr/supercore/cli/flower_superexec.py +3 -4
- flwr/supercore/constant.py +52 -0
- 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 -6
- flwr/supercore/object_store/object_store.py +1 -2
- flwr/supercore/object_store/object_store_factory.py +27 -8
- flwr/supercore/object_store/sqlite_object_store.py +253 -0
- flwr/{client/clientapp → supercore/primitives}/__init__.py +1 -1
- flwr/supercore/primitives/asymmetric.py +117 -0
- flwr/supercore/primitives/asymmetric_ed25519.py +175 -0
- flwr/supercore/sqlite_mixin.py +159 -0
- 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/supercore/utils.py +20 -0
- flwr/superlink/artifact_provider/artifact_provider.py +1 -2
- flwr/{common → superlink}/auth_plugin/__init__.py +6 -6
- flwr/superlink/auth_plugin/auth_plugin.py +88 -0
- flwr/superlink/auth_plugin/noop_auth_plugin.py +84 -0
- 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_user_auth_interceptor.py → control_account_auth_interceptor.py} +41 -32
- flwr/superlink/servicer/control/control_event_log_interceptor.py +7 -7
- flwr/superlink/servicer/control/control_grpc.py +18 -17
- flwr/superlink/servicer/control/control_license_interceptor.py +3 -3
- flwr/superlink/servicer/control/control_servicer.py +239 -63
- flwr/supernode/cli/flower_supernode.py +74 -26
- 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 +43 -24
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +40 -10
- flwr/supernode/start_client_internal.py +175 -51
- {flwr-1.22.0.dist-info → flwr-1.24.0.dist-info}/METADATA +8 -8
- flwr-1.24.0.dist-info/RECORD +454 -0
- flwr/common/auth_plugin/auth_plugin.py +0 -149
- flwr/supercore/object_store/utils.py +0 -43
- flwr-1.22.0.dist-info/RECORD +0 -428
- {flwr-1.22.0.dist-info → flwr-1.24.0.dist-info}/WHEEL +0 -0
- {flwr-1.22.0.dist-info → flwr-1.24.0.dist-info}/entry_points.txt +0 -0
flwr/common/config.py
CHANGED
|
@@ -20,7 +20,7 @@ import re
|
|
|
20
20
|
import zipfile
|
|
21
21
|
from io import BytesIO
|
|
22
22
|
from pathlib import Path
|
|
23
|
-
from typing import IO, Any,
|
|
23
|
+
from typing import IO, Any, TypeVar, cast, get_args
|
|
24
24
|
|
|
25
25
|
import tomli
|
|
26
26
|
import typer
|
|
@@ -39,7 +39,7 @@ from . import ConfigRecord, object_ref
|
|
|
39
39
|
T_dict = TypeVar("T_dict", bound=dict[str, Any]) # pylint: disable=invalid-name
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
def get_flwr_dir(provided_path:
|
|
42
|
+
def get_flwr_dir(provided_path: str | None = None) -> Path:
|
|
43
43
|
"""Return the Flower home directory based on env variables."""
|
|
44
44
|
if provided_path is None or not Path(provided_path).is_dir():
|
|
45
45
|
return Path(
|
|
@@ -55,7 +55,7 @@ def get_project_dir(
|
|
|
55
55
|
fab_id: str,
|
|
56
56
|
fab_version: str,
|
|
57
57
|
fab_hash: str,
|
|
58
|
-
flwr_dir:
|
|
58
|
+
flwr_dir: str | Path | None = None,
|
|
59
59
|
) -> Path:
|
|
60
60
|
"""Return the project directory based on the given fab_id and fab_version."""
|
|
61
61
|
# Check the fab_id
|
|
@@ -73,7 +73,7 @@ def get_project_dir(
|
|
|
73
73
|
)
|
|
74
74
|
|
|
75
75
|
|
|
76
|
-
def get_project_config(project_dir:
|
|
76
|
+
def get_project_config(project_dir: str | Path) -> dict[str, Any]:
|
|
77
77
|
"""Return pyproject.toml in the given project directory."""
|
|
78
78
|
# Load pyproject.toml file
|
|
79
79
|
toml_path = Path(project_dir) / FAB_CONFIG_FILE
|
|
@@ -134,7 +134,7 @@ def get_fused_config_from_dir(
|
|
|
134
134
|
return fuse_dicts(flat_default_config, override_config)
|
|
135
135
|
|
|
136
136
|
|
|
137
|
-
def get_fused_config_from_fab(fab_file:
|
|
137
|
+
def get_fused_config_from_fab(fab_file: Path | bytes, run: Run) -> UserConfig:
|
|
138
138
|
"""Fuse default config in a `FAB` with overrides in a `Run`.
|
|
139
139
|
|
|
140
140
|
This enables obtaining a run-config without having to install the FAB. This
|
|
@@ -146,7 +146,7 @@ def get_fused_config_from_fab(fab_file: Union[Path, bytes], run: Run) -> UserCon
|
|
|
146
146
|
return fuse_dicts(flat_config_flat, run.override_config)
|
|
147
147
|
|
|
148
148
|
|
|
149
|
-
def get_fused_config(run: Run, flwr_dir:
|
|
149
|
+
def get_fused_config(run: Run, flwr_dir: Path | None) -> UserConfig:
|
|
150
150
|
"""Merge the overrides from a `Run` with the config from a FAB.
|
|
151
151
|
|
|
152
152
|
Get the config using the fab_id and the fab_version, remove the nesting by adding
|
|
@@ -165,9 +165,7 @@ def get_fused_config(run: Run, flwr_dir: Optional[Path]) -> UserConfig:
|
|
|
165
165
|
return get_fused_config_from_dir(project_dir, run.override_config)
|
|
166
166
|
|
|
167
167
|
|
|
168
|
-
def flatten_dict(
|
|
169
|
-
raw_dict: Optional[dict[str, Any]], parent_key: str = ""
|
|
170
|
-
) -> UserConfig:
|
|
168
|
+
def flatten_dict(raw_dict: dict[str, Any] | None, parent_key: str = "") -> UserConfig:
|
|
171
169
|
"""Flatten dict by joining nested keys with a given separator."""
|
|
172
170
|
if raw_dict is None:
|
|
173
171
|
return {}
|
|
@@ -205,9 +203,7 @@ def unflatten_dict(flat_dict: dict[str, Any]) -> dict[str, Any]:
|
|
|
205
203
|
return unflattened_dict
|
|
206
204
|
|
|
207
205
|
|
|
208
|
-
def parse_config_args(
|
|
209
|
-
config: Optional[list[str]], flatten: bool = True
|
|
210
|
-
) -> dict[str, Any]:
|
|
206
|
+
def parse_config_args(config: list[str] | None, flatten: bool = True) -> dict[str, Any]:
|
|
211
207
|
"""Parse separator separated list of key-value pairs separated by '='."""
|
|
212
208
|
overrides: UserConfig = {}
|
|
213
209
|
|
|
@@ -246,6 +242,7 @@ def parse_config_args(
|
|
|
246
242
|
"space-separated key-value pairs.",
|
|
247
243
|
fg=typer.colors.RED,
|
|
248
244
|
bold=True,
|
|
245
|
+
err=True,
|
|
249
246
|
)
|
|
250
247
|
raise typer.Exit(code=1) from err
|
|
251
248
|
|
|
@@ -269,7 +266,7 @@ def user_config_to_configrecord(config: UserConfig) -> ConfigRecord:
|
|
|
269
266
|
return c_record
|
|
270
267
|
|
|
271
268
|
|
|
272
|
-
def get_fab_config(fab_file:
|
|
269
|
+
def get_fab_config(fab_file: Path | bytes) -> dict[str, Any]:
|
|
273
270
|
"""Extract the config from a FAB file or path.
|
|
274
271
|
|
|
275
272
|
Parameters
|
|
@@ -283,7 +280,7 @@ def get_fab_config(fab_file: Union[Path, bytes]) -> dict[str, Any]:
|
|
|
283
280
|
Dict[str, Any]
|
|
284
281
|
The `config` of the given Flower App Bundle.
|
|
285
282
|
"""
|
|
286
|
-
fab_file_archive:
|
|
283
|
+
fab_file_archive: Path | IO[bytes]
|
|
287
284
|
if isinstance(fab_file, bytes):
|
|
288
285
|
fab_file_archive = BytesIO(fab_file)
|
|
289
286
|
elif isinstance(fab_file, Path):
|
|
@@ -319,7 +316,7 @@ def _validate_run_config(config_dict: dict[str, Any], errors: list[str]) -> None
|
|
|
319
316
|
|
|
320
317
|
# pylint: disable=too-many-branches
|
|
321
318
|
def validate_fields_in_config(
|
|
322
|
-
config: dict[str, Any]
|
|
319
|
+
config: dict[str, Any],
|
|
323
320
|
) -> tuple[bool, list[str], list[str]]:
|
|
324
321
|
"""Validate pyproject.toml fields."""
|
|
325
322
|
errors = []
|
|
@@ -368,7 +365,7 @@ def validate_fields_in_config(
|
|
|
368
365
|
def validate_config(
|
|
369
366
|
config: dict[str, Any],
|
|
370
367
|
check_module: bool = True,
|
|
371
|
-
project_dir:
|
|
368
|
+
project_dir: str | Path | None = None,
|
|
372
369
|
) -> tuple[bool, list[str], list[str]]:
|
|
373
370
|
"""Validate pyproject.toml."""
|
|
374
371
|
is_valid, errors, warnings = validate_fields_in_config(config)
|
flwr/common/constant.py
CHANGED
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
+
import os
|
|
21
|
+
|
|
20
22
|
TRANSPORT_TYPE_GRPC_BIDI = "grpc-bidi"
|
|
21
23
|
TRANSPORT_TYPE_GRPC_RERE = "grpc-rere"
|
|
22
24
|
TRANSPORT_TYPE_GRPC_ADAPTER = "grpc-adapter"
|
|
@@ -60,7 +62,9 @@ HEARTBEAT_DEFAULT_INTERVAL = 30
|
|
|
60
62
|
HEARTBEAT_CALL_TIMEOUT = 5
|
|
61
63
|
HEARTBEAT_BASE_MULTIPLIER = 0.8
|
|
62
64
|
HEARTBEAT_RANDOM_RANGE = (-0.1, 0.1)
|
|
63
|
-
|
|
65
|
+
HEARTBEAT_MIN_INTERVAL = 10
|
|
66
|
+
HEARTBEAT_MAX_INTERVAL = 1800 # 30 minutes
|
|
67
|
+
HEARTBEAT_INTERVAL_INF = 1e300 # Large value, disabling heartbeats
|
|
64
68
|
HEARTBEAT_PATIENCE = 2
|
|
65
69
|
RUN_FAILURE_DETAILS_NO_HEARTBEAT = "No heartbeat received from the run."
|
|
66
70
|
|
|
@@ -70,13 +74,24 @@ NODE_ID_NUM_BYTES = 8
|
|
|
70
74
|
|
|
71
75
|
# Constants for FAB
|
|
72
76
|
APP_DIR = "apps"
|
|
73
|
-
FAB_ALLOWED_EXTENSIONS = {".py", ".toml", ".md"}
|
|
74
77
|
FAB_CONFIG_FILE = "pyproject.toml"
|
|
75
78
|
FAB_DATE = (2024, 10, 1, 0, 0, 0)
|
|
76
79
|
FAB_HASH_TRUNCATION = 8
|
|
77
80
|
FAB_MAX_SIZE = 10 * 1024 * 1024 # 10 MB
|
|
78
81
|
FLWR_DIR = ".flwr" # The default Flower directory: ~/.flwr/
|
|
79
82
|
FLWR_HOME = "FLWR_HOME" # If set, override the default Flower directory
|
|
83
|
+
# FAB file include patterns (gitignore-style patterns)
|
|
84
|
+
FAB_INCLUDE_PATTERNS = (
|
|
85
|
+
"**/*.py",
|
|
86
|
+
"**/*.toml",
|
|
87
|
+
"**/*.md",
|
|
88
|
+
)
|
|
89
|
+
# FAB file exclude patterns (gitignore-style patterns)
|
|
90
|
+
FAB_EXCLUDE_PATTERNS = (
|
|
91
|
+
f"{FLWR_DIR}/**", # Exclude the .flwr directory
|
|
92
|
+
"**/__pycache__/**",
|
|
93
|
+
FAB_CONFIG_FILE, # Exclude the original pyproject.toml
|
|
94
|
+
)
|
|
80
95
|
|
|
81
96
|
# Constant for SuperLink
|
|
82
97
|
SUPERLINK_NODE_ID = 1
|
|
@@ -109,14 +124,14 @@ LOG_UPLOAD_INTERVAL = 0.2 # Minimum interval between two log uploads
|
|
|
109
124
|
# Retry configurations
|
|
110
125
|
MAX_RETRY_DELAY = 20 # Maximum delay duration between two consecutive retries.
|
|
111
126
|
|
|
112
|
-
# Constants for
|
|
127
|
+
# Constants for account authentication
|
|
113
128
|
CREDENTIALS_DIR = ".credentials"
|
|
114
|
-
|
|
115
|
-
|
|
129
|
+
AUTHN_TYPE_JSON_KEY = "authn-type" # For key name in JSON file
|
|
130
|
+
AUTHN_TYPE_YAML_KEY = "authn_type" # For key name in YAML file
|
|
116
131
|
ACCESS_TOKEN_KEY = "flwr-oidc-access-token"
|
|
117
132
|
REFRESH_TOKEN_KEY = "flwr-oidc-refresh-token"
|
|
118
133
|
|
|
119
|
-
# Constants for
|
|
134
|
+
# Constants for account authorization
|
|
120
135
|
AUTHZ_TYPE_YAML_KEY = "authz_type" # For key name in YAML file
|
|
121
136
|
|
|
122
137
|
# Constants for node authentication
|
|
@@ -135,7 +150,9 @@ GC_THRESHOLD = 200_000_000 # 200 MB
|
|
|
135
150
|
# Constants for Inflatable
|
|
136
151
|
HEAD_BODY_DIVIDER = b"\x00"
|
|
137
152
|
HEAD_VALUE_DIVIDER = " "
|
|
138
|
-
|
|
153
|
+
FLWR_PRIVATE_MAX_ARRAY_CHUNK_SIZE = int(
|
|
154
|
+
os.getenv("FLWR_PRIVATE_MAX_ARRAY_CHUNK_SIZE", "5242880")
|
|
155
|
+
) # 5 MB
|
|
139
156
|
|
|
140
157
|
# Constants for serialization
|
|
141
158
|
INT64_MAX_VALUE = 9223372036854775807 # (1 << 63) - 1
|
|
@@ -144,8 +161,12 @@ INT64_MAX_VALUE = 9223372036854775807 # (1 << 63) - 1
|
|
|
144
161
|
FLWR_APP_TOKEN_LENGTH = 128 # Length of the token used
|
|
145
162
|
|
|
146
163
|
# Constants for object pushing and pulling
|
|
147
|
-
|
|
148
|
-
|
|
164
|
+
FLWR_PRIVATE_MAX_CONCURRENT_OBJ_PUSHES = int(
|
|
165
|
+
os.getenv("FLWR_PRIVATE_MAX_CONCURRENT_OBJ_PUSHES", "2")
|
|
166
|
+
) # Default maximum number of concurrent pushes
|
|
167
|
+
FLWR_PRIVATE_MAX_CONCURRENT_OBJ_PULLS = int(
|
|
168
|
+
os.getenv("FLWR_PRIVATE_MAX_CONCURRENT_OBJ_PULLS", "2")
|
|
169
|
+
) # Default maximum number of concurrent pulls
|
|
149
170
|
PULL_MAX_TIME = 7200 # Default maximum time to wait for pulling objects
|
|
150
171
|
PULL_MAX_TRIES_PER_OBJECT = 500 # Default maximum number of tries to pull an object
|
|
151
172
|
PULL_INITIAL_BACKOFF = 1 # Initial backoff time for pulling objects
|
|
@@ -154,9 +175,13 @@ PULL_BACKOFF_CAP = 10 # Maximum backoff time for pulling objects
|
|
|
154
175
|
|
|
155
176
|
# ControlServicer constants
|
|
156
177
|
RUN_ID_NOT_FOUND_MESSAGE = "Run ID not found"
|
|
157
|
-
|
|
178
|
+
NO_ACCOUNT_AUTH_MESSAGE = "ControlServicer initialized without account authentication"
|
|
158
179
|
NO_ARTIFACT_PROVIDER_MESSAGE = "ControlServicer initialized without artifact provider"
|
|
159
180
|
PULL_UNFINISHED_RUN_MESSAGE = "Cannot pull artifacts for an unfinished run"
|
|
181
|
+
SUPERNODE_NOT_CREATED_FROM_CLI_MESSAGE = "Invalid SuperNode credentials"
|
|
182
|
+
PUBLIC_KEY_ALREADY_IN_USE_MESSAGE = "Public key already in use"
|
|
183
|
+
PUBLIC_KEY_NOT_VALID = "The provided public key is not valid"
|
|
184
|
+
NODE_NOT_FOUND_MESSAGE = "Node ID not found for account"
|
|
160
185
|
|
|
161
186
|
|
|
162
187
|
class MessageType:
|
|
@@ -203,6 +228,8 @@ class ErrorCode:
|
|
|
203
228
|
REPLY_MESSAGE_UNAVAILABLE = 4
|
|
204
229
|
NODE_UNAVAILABLE = 5
|
|
205
230
|
MOD_FAILED_PRECONDITION = 6
|
|
231
|
+
INVALID_FAB = 7
|
|
232
|
+
CLIENT_APP_CRASHED = 8
|
|
206
233
|
|
|
207
234
|
def __new__(cls) -> ErrorCode:
|
|
208
235
|
"""Prevent instantiation."""
|
|
@@ -245,12 +272,23 @@ class CliOutputFormat:
|
|
|
245
272
|
raise TypeError(f"{cls.__name__} cannot be instantiated.")
|
|
246
273
|
|
|
247
274
|
|
|
248
|
-
class
|
|
249
|
-
"""
|
|
275
|
+
class AuthnType:
|
|
276
|
+
"""Account authentication types."""
|
|
250
277
|
|
|
278
|
+
NOOP = "noop"
|
|
251
279
|
OIDC = "oidc"
|
|
252
280
|
|
|
253
|
-
def __new__(cls) ->
|
|
281
|
+
def __new__(cls) -> AuthnType:
|
|
282
|
+
"""Prevent instantiation."""
|
|
283
|
+
raise TypeError(f"{cls.__name__} cannot be instantiated.")
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class AuthzType:
|
|
287
|
+
"""Account authorization types."""
|
|
288
|
+
|
|
289
|
+
NOOP = "noop"
|
|
290
|
+
|
|
291
|
+
def __new__(cls) -> AuthzType:
|
|
254
292
|
"""Prevent instantiation."""
|
|
255
293
|
raise TypeError(f"{cls.__name__} cannot be instantiated.")
|
|
256
294
|
|
|
@@ -281,3 +319,8 @@ class ExecPluginType:
|
|
|
281
319
|
"""Return all SuperExec plugin types."""
|
|
282
320
|
# Filter all constants (uppercase) of the class
|
|
283
321
|
return [v for k, v in vars(ExecPluginType).items() if k.isupper()]
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
# Constants for No-op auth plugins
|
|
325
|
+
NOOP_FLWR_AID = "<id:none>"
|
|
326
|
+
NOOP_ACCOUNT_NAME = "<name:none>"
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
from logging import WARNING
|
|
19
|
-
from typing import Optional
|
|
20
19
|
|
|
21
20
|
import numpy as np
|
|
22
21
|
|
|
@@ -70,7 +69,7 @@ def compute_clip_model_update(
|
|
|
70
69
|
"""Compute model update (param1 - param2) and clip it.
|
|
71
70
|
|
|
72
71
|
Then add the clipped value to param1."""
|
|
73
|
-
model_update = [np.subtract(x, y) for (x, y) in zip(param1, param2)]
|
|
72
|
+
model_update = [np.subtract(x, y) for (x, y) in zip(param1, param2, strict=True)]
|
|
74
73
|
clip_inputs_inplace(model_update, clipping_norm)
|
|
75
74
|
|
|
76
75
|
for i, _ in enumerate(param2):
|
|
@@ -98,7 +97,7 @@ def compute_adaptive_clip_model_update(
|
|
|
98
97
|
model update = param1 - param2
|
|
99
98
|
Return the norm_bit
|
|
100
99
|
"""
|
|
101
|
-
model_update = [np.subtract(x, y) for (x, y) in zip(param1, param2)]
|
|
100
|
+
model_update = [np.subtract(x, y) for (x, y) in zip(param1, param2, strict=True)]
|
|
102
101
|
norm_bit = adaptive_clip_inputs_inplace(model_update, clipping_norm)
|
|
103
102
|
|
|
104
103
|
for i, _ in enumerate(param2):
|
|
@@ -125,7 +124,7 @@ def add_gaussian_noise_to_params(
|
|
|
125
124
|
def compute_adaptive_noise_params(
|
|
126
125
|
noise_multiplier: float,
|
|
127
126
|
num_sampled_clients: float,
|
|
128
|
-
clipped_count_stddev:
|
|
127
|
+
clipped_count_stddev: float | None,
|
|
129
128
|
) -> tuple[float, float]:
|
|
130
129
|
"""Compute noising parameters for the adaptive clipping.
|
|
131
130
|
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
from abc import ABC, abstractmethod
|
|
19
|
-
from typing import Optional, Union
|
|
20
19
|
|
|
21
20
|
import grpc
|
|
22
21
|
from google.protobuf.message import Message as GrpcMessage
|
|
@@ -36,7 +35,7 @@ class EventLogWriterPlugin(ABC):
|
|
|
36
35
|
self,
|
|
37
36
|
request: GrpcMessage,
|
|
38
37
|
context: grpc.ServicerContext,
|
|
39
|
-
account_info:
|
|
38
|
+
account_info: AccountInfo | None,
|
|
40
39
|
method_name: str,
|
|
41
40
|
) -> LogEntry:
|
|
42
41
|
"""Compose pre-event log entry from the provided request and context."""
|
|
@@ -46,9 +45,9 @@ class EventLogWriterPlugin(ABC):
|
|
|
46
45
|
self,
|
|
47
46
|
request: GrpcMessage,
|
|
48
47
|
context: grpc.ServicerContext,
|
|
49
|
-
account_info:
|
|
48
|
+
account_info: AccountInfo | None,
|
|
50
49
|
method_name: str,
|
|
51
|
-
response:
|
|
50
|
+
response: GrpcMessage | BaseException | None,
|
|
52
51
|
) -> LogEntry:
|
|
53
52
|
"""Compose post-event log entry from the provided response and context."""
|
|
54
53
|
|
flwr/common/exit/exit.py
CHANGED
|
@@ -15,14 +15,16 @@
|
|
|
15
15
|
"""Unified exit function."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
import os
|
|
20
19
|
import sys
|
|
20
|
+
import threading
|
|
21
|
+
import time
|
|
21
22
|
from logging import ERROR, INFO
|
|
22
23
|
from typing import Any, NoReturn
|
|
23
24
|
|
|
24
25
|
from flwr.common import EventType, event
|
|
25
26
|
from flwr.common.version import package_version
|
|
27
|
+
from flwr.supercore.constant import FORCE_EXIT_TIMEOUT_SECONDS
|
|
26
28
|
|
|
27
29
|
from ..logger import log
|
|
28
30
|
from .exit_code import EXIT_CODE_HELP
|
|
@@ -53,6 +55,10 @@ def flwr_exit(
|
|
|
53
55
|
- `<message>`: Optional context or additional information about the exit.
|
|
54
56
|
- `<short-help-message>`: A brief explanation for the given exit code.
|
|
55
57
|
- `<help-page-url>`: A URL providing detailed documentation and resolution steps.
|
|
58
|
+
|
|
59
|
+
Notes
|
|
60
|
+
-----
|
|
61
|
+
This function MUST be called from the main thread.
|
|
56
62
|
"""
|
|
57
63
|
is_error = not 0 <= code < 100 # 0-99 are success exit codes
|
|
58
64
|
|
|
@@ -84,6 +90,13 @@ def flwr_exit(
|
|
|
84
90
|
# Trigger exit handlers
|
|
85
91
|
trigger_exit_handlers()
|
|
86
92
|
|
|
93
|
+
# Start a daemon thread to force exit if graceful exit fails
|
|
94
|
+
def force_exit() -> None:
|
|
95
|
+
time.sleep(FORCE_EXIT_TIMEOUT_SECONDS)
|
|
96
|
+
os._exit(sys_exit_code)
|
|
97
|
+
|
|
98
|
+
threading.Thread(target=force_exit, daemon=True).start()
|
|
99
|
+
|
|
87
100
|
# Exit
|
|
88
101
|
sys.exit(sys_exit_code)
|
|
89
102
|
|
flwr/common/exit/exit_code.py
CHANGED
|
@@ -38,20 +38,29 @@ class ExitCode:
|
|
|
38
38
|
SERVERAPP_STRATEGY_PRECONDITION_UNMET = 200
|
|
39
39
|
SERVERAPP_EXCEPTION = 201
|
|
40
40
|
SERVERAPP_STRATEGY_AGGREGATION_ERROR = 202
|
|
41
|
+
SERVERAPP_RUN_START_REJECTED = 203
|
|
41
42
|
|
|
42
43
|
# SuperNode-specific exit codes (300-399)
|
|
43
44
|
SUPERNODE_REST_ADDRESS_INVALID = 300
|
|
44
|
-
SUPERNODE_NODE_AUTH_KEYS_REQUIRED = 301
|
|
45
|
-
|
|
45
|
+
# SUPERNODE_NODE_AUTH_KEYS_REQUIRED = 301 --- DELETED ---
|
|
46
|
+
SUPERNODE_NODE_AUTH_KEY_INVALID = 302
|
|
47
|
+
SUPERNODE_STARTED_WITHOUT_TLS_BUT_NODE_AUTH_ENABLED = 303
|
|
48
|
+
SUPERNODE_INVALID_TRUSTED_ENTITIES = 304
|
|
46
49
|
|
|
47
50
|
# SuperExec-specific exit codes (400-499)
|
|
48
51
|
SUPEREXEC_INVALID_PLUGIN_CONFIG = 400
|
|
49
52
|
|
|
53
|
+
# FlowerCLI-specific exit codes (500-599)
|
|
54
|
+
FLWRCLI_NODE_AUTH_PUBLIC_KEY_INVALID = 500
|
|
55
|
+
|
|
50
56
|
# Common exit codes (600-699)
|
|
51
57
|
COMMON_ADDRESS_INVALID = 600
|
|
52
58
|
COMMON_MISSING_EXTRA_REST = 601
|
|
53
59
|
COMMON_TLS_NOT_SUPPORTED = 602
|
|
54
60
|
|
|
61
|
+
# Simulation exit codes (700-799)
|
|
62
|
+
SIMULATION_EXCEPTION = 700
|
|
63
|
+
|
|
55
64
|
def __new__(cls) -> ExitCode:
|
|
56
65
|
"""Prevent instantiation."""
|
|
57
66
|
raise TypeError(f"{cls.__name__} cannot be instantiated.")
|
|
@@ -97,25 +106,41 @@ EXIT_CODE_HELP = {
|
|
|
97
106
|
"The strategy encountered an error during aggregation. Please check the logs "
|
|
98
107
|
"for more details."
|
|
99
108
|
),
|
|
109
|
+
ExitCode.SERVERAPP_RUN_START_REJECTED: (
|
|
110
|
+
"The SuperLink rejected the request to start the run. This may occur if the "
|
|
111
|
+
"run has been stopped, the run ID or FAB is invalid, or the run failed to "
|
|
112
|
+
"start within the allowed time."
|
|
113
|
+
),
|
|
100
114
|
# SuperNode-specific exit codes (300-399)
|
|
101
115
|
ExitCode.SUPERNODE_REST_ADDRESS_INVALID: (
|
|
102
116
|
"When using the REST API, please provide `https://` or "
|
|
103
117
|
"`http://` before the server address (e.g. `http://127.0.0.1:8080`)"
|
|
104
118
|
),
|
|
105
|
-
ExitCode.
|
|
106
|
-
"Node authentication requires
|
|
107
|
-
"
|
|
108
|
-
"to be provided (providing only one of them is not sufficient)."
|
|
109
|
-
),
|
|
110
|
-
ExitCode.SUPERNODE_NODE_AUTH_KEYS_INVALID: (
|
|
111
|
-
"Node authentication requires elliptic curve private and public key pair. "
|
|
112
|
-
"Please ensure that the file path points to a valid private/public key "
|
|
119
|
+
ExitCode.SUPERNODE_NODE_AUTH_KEY_INVALID: (
|
|
120
|
+
"Node authentication requires elliptic curve private key. "
|
|
121
|
+
"Please ensure that the file path points to a valid private key "
|
|
113
122
|
"file and try again."
|
|
114
123
|
),
|
|
124
|
+
ExitCode.SUPERNODE_STARTED_WITHOUT_TLS_BUT_NODE_AUTH_ENABLED: (
|
|
125
|
+
"The private key for SuperNode authentication was provided, but TLS is not "
|
|
126
|
+
"enabled. Node authentication can only be used when TLS is enabled."
|
|
127
|
+
),
|
|
128
|
+
ExitCode.SUPERNODE_INVALID_TRUSTED_ENTITIES: (
|
|
129
|
+
"Failed to read the trusted entities YAML file. "
|
|
130
|
+
"Please ensure that a valid file is provided using "
|
|
131
|
+
"the `--trusted-entities` option."
|
|
132
|
+
),
|
|
115
133
|
# SuperExec-specific exit codes (400-499)
|
|
116
134
|
ExitCode.SUPEREXEC_INVALID_PLUGIN_CONFIG: (
|
|
117
135
|
"The YAML configuration for the SuperExec plugin is invalid."
|
|
118
136
|
),
|
|
137
|
+
# FlowerCLI-specific exit codes (500-599)
|
|
138
|
+
ExitCode.FLWRCLI_NODE_AUTH_PUBLIC_KEY_INVALID: (
|
|
139
|
+
"Node authentication requires a valid elliptic curve public key in the "
|
|
140
|
+
"SSH format and following a NIST standard elliptic curve (e.g. SECP384R1). "
|
|
141
|
+
"Please ensure that the file path points to a valid public key "
|
|
142
|
+
"file and try again."
|
|
143
|
+
),
|
|
119
144
|
# Common exit codes (600-699)
|
|
120
145
|
ExitCode.COMMON_ADDRESS_INVALID: (
|
|
121
146
|
"Please provide a valid URL, IPv4 or IPv6 address."
|
|
@@ -128,4 +153,8 @@ To use the REST API, install `flwr` with the `rest` extra:
|
|
|
128
153
|
`pip install "flwr[rest]"`.
|
|
129
154
|
""",
|
|
130
155
|
ExitCode.COMMON_TLS_NOT_SUPPORTED: "Please use the '--insecure' flag.",
|
|
156
|
+
# Simulation exit codes (700-799)
|
|
157
|
+
ExitCode.SIMULATION_EXCEPTION: (
|
|
158
|
+
"An unhandled exception occurred when running the simulation."
|
|
159
|
+
),
|
|
131
160
|
}
|
flwr/common/exit/exit_handler.py
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import signal
|
|
19
19
|
import threading
|
|
20
|
-
from
|
|
20
|
+
from collections.abc import Callable
|
|
21
21
|
|
|
22
22
|
from .exit_code import ExitCode
|
|
23
23
|
|
|
@@ -58,5 +58,9 @@ def trigger_exit_handlers() -> None:
|
|
|
58
58
|
"""Trigger all registered exit handlers in LIFO order."""
|
|
59
59
|
with _lock_handlers:
|
|
60
60
|
for handler in reversed(registered_exit_handlers):
|
|
61
|
-
|
|
61
|
+
try:
|
|
62
|
+
handler()
|
|
63
|
+
except Exception: # pylint: disable=broad-exception-caught
|
|
64
|
+
# Ignore exceptions in exit handlers
|
|
65
|
+
pass
|
|
62
66
|
registered_exit_handlers.clear()
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
import signal
|
|
19
|
+
from collections.abc import Callable
|
|
19
20
|
from threading import Thread
|
|
20
21
|
from types import FrameType
|
|
21
|
-
from typing import Callable, Optional
|
|
22
22
|
|
|
23
23
|
from grpc import Server
|
|
24
24
|
|
|
@@ -40,10 +40,10 @@ if hasattr(signal, "SIGQUIT"):
|
|
|
40
40
|
|
|
41
41
|
def register_signal_handlers(
|
|
42
42
|
event_type: EventType,
|
|
43
|
-
exit_message:
|
|
44
|
-
grpc_servers:
|
|
45
|
-
bckg_threads:
|
|
46
|
-
exit_handlers:
|
|
43
|
+
exit_message: str | None = None,
|
|
44
|
+
grpc_servers: list[Server] | None = None,
|
|
45
|
+
bckg_threads: list[Thread] | None = None,
|
|
46
|
+
exit_handlers: list[Callable[[], None]] | None = None,
|
|
47
47
|
) -> None:
|
|
48
48
|
"""Register exit handlers for `SIGINT`, `SIGTERM` and `SIGQUIT` signals.
|
|
49
49
|
|
flwr/common/grpc.py
CHANGED
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
import concurrent.futures
|
|
19
19
|
import os
|
|
20
20
|
import sys
|
|
21
|
-
from collections.abc import Sequence
|
|
21
|
+
from collections.abc import Callable, Sequence
|
|
22
22
|
from logging import DEBUG, ERROR
|
|
23
|
-
from typing import Any
|
|
23
|
+
from typing import Any
|
|
24
24
|
|
|
25
25
|
import grpc
|
|
26
26
|
|
|
@@ -46,9 +46,9 @@ if "GRPC_VERBOSITY" not in os.environ:
|
|
|
46
46
|
def create_channel(
|
|
47
47
|
server_address: str,
|
|
48
48
|
insecure: bool,
|
|
49
|
-
root_certificates:
|
|
49
|
+
root_certificates: bytes | None = None,
|
|
50
50
|
max_message_length: int = GRPC_MAX_MESSAGE_LENGTH,
|
|
51
|
-
interceptors:
|
|
51
|
+
interceptors: Sequence[grpc.UnaryUnaryClientInterceptor] | None = None,
|
|
52
52
|
) -> grpc.Channel:
|
|
53
53
|
"""Create a gRPC channel, either secure or insecure."""
|
|
54
54
|
# Check for conflicting parameters
|
|
@@ -104,8 +104,8 @@ def generic_create_grpc_server( # pylint: disable=too-many-arguments, R0914, R0
|
|
|
104
104
|
max_concurrent_workers: int = 1000,
|
|
105
105
|
max_message_length: int = GRPC_MAX_MESSAGE_LENGTH,
|
|
106
106
|
keepalive_time_ms: int = 210000,
|
|
107
|
-
certificates:
|
|
108
|
-
interceptors:
|
|
107
|
+
certificates: tuple[bytes, bytes, bytes] | None = None,
|
|
108
|
+
interceptors: Sequence[grpc.ServerInterceptor] | None = None,
|
|
109
109
|
) -> grpc.Server:
|
|
110
110
|
"""Create a gRPC server with a single servicer.
|
|
111
111
|
|