flwr 1.23.0__py3-none-any.whl → 1.25.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/{new/templates → app_cmd}/__init__.py +9 -1
- flwr/cli/app_cmd/publish.py +285 -0
- flwr/cli/app_cmd/review.py +262 -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/{new/templates/app/code/flwr_tune → federation}/__init__.py +10 -1
- flwr/cli/federation/ls.py +140 -0
- flwr/cli/federation/show.py +318 -0
- flwr/cli/install.py +91 -13
- flwr/cli/log.py +52 -9
- flwr/cli/login/login.py +7 -4
- flwr/cli/ls.py +211 -130
- flwr/cli/new/new.py +123 -331
- flwr/cli/pull.py +10 -5
- flwr/cli/run/run.py +71 -29
- flwr/cli/run_utils.py +148 -0
- flwr/cli/stop.py +26 -8
- flwr/cli/supernode/ls.py +25 -12
- flwr/cli/supernode/register.py +9 -4
- flwr/cli/supernode/unregister.py +5 -3
- flwr/cli/utils.py +239 -16
- flwr/client/__init__.py +1 -1
- flwr/client/dpfedavg_numpy_client.py +4 -1
- flwr/client/grpc_adapter_client/connection.py +8 -9
- flwr/client/grpc_rere_client/connection.py +16 -14
- 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 +18 -18
- 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 +11 -4
- flwr/common/serde_utils.py +2 -2
- flwr/common/telemetry.py +9 -5
- flwr/common/typing.py +58 -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 +24 -14
- flwr/proto/fleet_pb2.pyi +141 -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 +158 -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 +39 -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 +75 -30
- 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 +148 -149
- flwr/server/superlink/linkstate/linkstate.py +91 -43
- flwr/server/superlink/linkstate/linkstate_factory.py +22 -5
- flwr/server/superlink/linkstate/sqlite_linkstate.py +502 -436
- 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 +34 -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/supercore/utils.py +190 -0
- 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/{cli/new/templates/app → superlink/federation}/__init__.py +10 -1
- 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 +7 -6
- flwr/superlink/servicer/control/control_license_interceptor.py +3 -3
- flwr/superlink/servicer/control/control_servicer.py +190 -23
- flwr/supernode/cli/flower_supernode.py +58 -3
- flwr/supernode/nodestate/in_memory_nodestate.py +121 -49
- flwr/supernode/nodestate/nodestate.py +52 -8
- flwr/supernode/nodestate/nodestate_factory.py +7 -4
- flwr/supernode/runtime/run_clientapp.py +41 -22
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +46 -10
- flwr/supernode/start_client_internal.py +165 -46
- {flwr-1.23.0.dist-info → flwr-1.25.0.dist-info}/METADATA +9 -11
- flwr-1.25.0.dist-info/RECORD +393 -0
- flwr/cli/new/templates/app/.gitignore.tpl +0 -163
- flwr/cli/new/templates/app/LICENSE.tpl +0 -202
- flwr/cli/new/templates/app/README.baseline.md.tpl +0 -127
- flwr/cli/new/templates/app/README.flowertune.md.tpl +0 -68
- flwr/cli/new/templates/app/README.md.tpl +0 -37
- flwr/cli/new/templates/app/code/__init__.baseline.py.tpl +0 -1
- flwr/cli/new/templates/app/code/__init__.py +0 -15
- flwr/cli/new/templates/app/code/__init__.py.tpl +0 -1
- flwr/cli/new/templates/app/code/__init__.pytorch_legacy_api.py.tpl +0 -1
- flwr/cli/new/templates/app/code/client.baseline.py.tpl +0 -75
- flwr/cli/new/templates/app/code/client.huggingface.py.tpl +0 -93
- flwr/cli/new/templates/app/code/client.jax.py.tpl +0 -71
- flwr/cli/new/templates/app/code/client.mlx.py.tpl +0 -102
- flwr/cli/new/templates/app/code/client.numpy.py.tpl +0 -46
- flwr/cli/new/templates/app/code/client.pytorch.py.tpl +0 -80
- flwr/cli/new/templates/app/code/client.pytorch_legacy_api.py.tpl +0 -55
- flwr/cli/new/templates/app/code/client.sklearn.py.tpl +0 -108
- flwr/cli/new/templates/app/code/client.tensorflow.py.tpl +0 -82
- flwr/cli/new/templates/app/code/client.xgboost.py.tpl +0 -110
- flwr/cli/new/templates/app/code/dataset.baseline.py.tpl +0 -36
- flwr/cli/new/templates/app/code/flwr_tune/client_app.py.tpl +0 -92
- flwr/cli/new/templates/app/code/flwr_tune/dataset.py.tpl +0 -87
- flwr/cli/new/templates/app/code/flwr_tune/models.py.tpl +0 -56
- flwr/cli/new/templates/app/code/flwr_tune/server_app.py.tpl +0 -73
- flwr/cli/new/templates/app/code/flwr_tune/strategy.py.tpl +0 -78
- flwr/cli/new/templates/app/code/model.baseline.py.tpl +0 -66
- flwr/cli/new/templates/app/code/server.baseline.py.tpl +0 -43
- flwr/cli/new/templates/app/code/server.huggingface.py.tpl +0 -42
- flwr/cli/new/templates/app/code/server.jax.py.tpl +0 -39
- flwr/cli/new/templates/app/code/server.mlx.py.tpl +0 -41
- flwr/cli/new/templates/app/code/server.numpy.py.tpl +0 -38
- flwr/cli/new/templates/app/code/server.pytorch.py.tpl +0 -41
- flwr/cli/new/templates/app/code/server.pytorch_legacy_api.py.tpl +0 -31
- flwr/cli/new/templates/app/code/server.sklearn.py.tpl +0 -44
- flwr/cli/new/templates/app/code/server.tensorflow.py.tpl +0 -38
- flwr/cli/new/templates/app/code/server.xgboost.py.tpl +0 -56
- flwr/cli/new/templates/app/code/strategy.baseline.py.tpl +0 -1
- flwr/cli/new/templates/app/code/task.huggingface.py.tpl +0 -98
- flwr/cli/new/templates/app/code/task.jax.py.tpl +0 -57
- flwr/cli/new/templates/app/code/task.mlx.py.tpl +0 -102
- flwr/cli/new/templates/app/code/task.numpy.py.tpl +0 -7
- flwr/cli/new/templates/app/code/task.pytorch.py.tpl +0 -98
- flwr/cli/new/templates/app/code/task.pytorch_legacy_api.py.tpl +0 -111
- flwr/cli/new/templates/app/code/task.sklearn.py.tpl +0 -67
- flwr/cli/new/templates/app/code/task.tensorflow.py.tpl +0 -52
- flwr/cli/new/templates/app/code/task.xgboost.py.tpl +0 -67
- flwr/cli/new/templates/app/code/utils.baseline.py.tpl +0 -1
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +0 -146
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +0 -80
- flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +0 -65
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +0 -52
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +0 -56
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +0 -49
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +0 -53
- flwr/cli/new/templates/app/pyproject.pytorch_legacy_api.toml.tpl +0 -53
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +0 -52
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +0 -53
- flwr/cli/new/templates/app/pyproject.xgboost.toml.tpl +0 -61
- 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.25.0.dist-info}/WHEEL +0 -0
- {flwr-1.23.0.dist-info → flwr-1.25.0.dist-info}/entry_points.txt +0 -0
flwr/cli/utils.py
CHANGED
|
@@ -18,15 +18,17 @@
|
|
|
18
18
|
import hashlib
|
|
19
19
|
import json
|
|
20
20
|
import re
|
|
21
|
-
from collections.abc import Iterator
|
|
21
|
+
from collections.abc import Callable, Iterable, Iterator
|
|
22
22
|
from contextlib import contextmanager
|
|
23
23
|
from pathlib import Path
|
|
24
|
-
from typing import Any,
|
|
24
|
+
from typing import Any, cast
|
|
25
25
|
|
|
26
26
|
import grpc
|
|
27
|
+
import pathspec
|
|
27
28
|
import typer
|
|
28
29
|
|
|
29
30
|
from flwr.common.constant import (
|
|
31
|
+
ACCESS_TOKEN_KEY,
|
|
30
32
|
AUTHN_TYPE_JSON_KEY,
|
|
31
33
|
CREDENTIALS_DIR,
|
|
32
34
|
FLWR_DIR,
|
|
@@ -36,6 +38,7 @@ from flwr.common.constant import (
|
|
|
36
38
|
PUBLIC_KEY_ALREADY_IN_USE_MESSAGE,
|
|
37
39
|
PUBLIC_KEY_NOT_VALID,
|
|
38
40
|
PULL_UNFINISHED_RUN_MESSAGE,
|
|
41
|
+
REFRESH_TOKEN_KEY,
|
|
39
42
|
RUN_ID_NOT_FOUND_MESSAGE,
|
|
40
43
|
AuthnType,
|
|
41
44
|
)
|
|
@@ -53,9 +56,24 @@ from .config_utils import validate_certificate_in_federation_config
|
|
|
53
56
|
def prompt_text(
|
|
54
57
|
text: str,
|
|
55
58
|
predicate: Callable[[str], bool] = lambda _: True,
|
|
56
|
-
default:
|
|
59
|
+
default: str | None = None,
|
|
57
60
|
) -> str:
|
|
58
|
-
"""Ask user to enter text input.
|
|
61
|
+
"""Ask user to enter text input.
|
|
62
|
+
|
|
63
|
+
Parameters
|
|
64
|
+
----------
|
|
65
|
+
text : str
|
|
66
|
+
The prompt text to display to the user.
|
|
67
|
+
predicate : Callable[[str], bool] (default: lambda _: True)
|
|
68
|
+
A function to validate the user input. Default accepts all non-empty strings.
|
|
69
|
+
default : str | None (default: None)
|
|
70
|
+
Default value to use if user presses enter without input.
|
|
71
|
+
|
|
72
|
+
Returns
|
|
73
|
+
-------
|
|
74
|
+
str
|
|
75
|
+
The validated user input.
|
|
76
|
+
"""
|
|
59
77
|
while True:
|
|
60
78
|
result = typer.prompt(
|
|
61
79
|
typer.style(f"\n💬 {text}", fg=typer.colors.MAGENTA, bold=True),
|
|
@@ -69,7 +87,20 @@ def prompt_text(
|
|
|
69
87
|
|
|
70
88
|
|
|
71
89
|
def prompt_options(text: str, options: list[str]) -> str:
|
|
72
|
-
"""Ask user to select one of the given options and return the selected item.
|
|
90
|
+
"""Ask user to select one of the given options and return the selected item.
|
|
91
|
+
|
|
92
|
+
Parameters
|
|
93
|
+
----------
|
|
94
|
+
text : str
|
|
95
|
+
The prompt text to display to the user.
|
|
96
|
+
options : list[str]
|
|
97
|
+
List of options to present to the user.
|
|
98
|
+
|
|
99
|
+
Returns
|
|
100
|
+
-------
|
|
101
|
+
str
|
|
102
|
+
The selected option from the list.
|
|
103
|
+
"""
|
|
73
104
|
# Turn options into a list with index as in " [ 0] quickstart-pytorch"
|
|
74
105
|
options_formatted = [
|
|
75
106
|
" [ "
|
|
@@ -127,9 +158,19 @@ def is_valid_project_name(name: str) -> bool:
|
|
|
127
158
|
def sanitize_project_name(name: str) -> str:
|
|
128
159
|
"""Sanitize the given string to make it a valid Python project name.
|
|
129
160
|
|
|
130
|
-
This
|
|
161
|
+
This function replaces spaces, dots, slashes, and underscores with dashes, removes
|
|
131
162
|
any characters not allowed in Python project names, makes the string lowercase, and
|
|
132
163
|
ensures it starts with a valid character.
|
|
164
|
+
|
|
165
|
+
Parameters
|
|
166
|
+
----------
|
|
167
|
+
name : str
|
|
168
|
+
The project name to sanitize.
|
|
169
|
+
|
|
170
|
+
Returns
|
|
171
|
+
-------
|
|
172
|
+
str
|
|
173
|
+
The sanitized project name that is valid for Python projects.
|
|
133
174
|
"""
|
|
134
175
|
# Replace whitespace with '_'
|
|
135
176
|
name_with_hyphens = re.sub(r"[ ./_]", "-", name)
|
|
@@ -154,8 +195,19 @@ def sanitize_project_name(name: str) -> str:
|
|
|
154
195
|
return sanitized_name
|
|
155
196
|
|
|
156
197
|
|
|
157
|
-
def get_sha256_hash(file_path_or_int:
|
|
158
|
-
"""Calculate the SHA-256 hash of a file.
|
|
198
|
+
def get_sha256_hash(file_path_or_int: Path | int) -> str:
|
|
199
|
+
"""Calculate the SHA-256 hash of a file or integer.
|
|
200
|
+
|
|
201
|
+
Parameters
|
|
202
|
+
----------
|
|
203
|
+
file_path_or_int : Path | int
|
|
204
|
+
Either a path to a file to hash, or an integer to convert to string and hash.
|
|
205
|
+
|
|
206
|
+
Returns
|
|
207
|
+
-------
|
|
208
|
+
str
|
|
209
|
+
The SHA-256 hash as a hexadecimal string.
|
|
210
|
+
"""
|
|
159
211
|
sha256 = hashlib.sha256()
|
|
160
212
|
if isinstance(file_path_or_int, Path):
|
|
161
213
|
with open(file_path_or_int, "rb") as f:
|
|
@@ -214,6 +266,7 @@ def get_account_auth_config_path(root_dir: Path, federation: str) -> Path:
|
|
|
214
266
|
f"Please check the permissions of `{gitignore_path}` and try again.",
|
|
215
267
|
fg=typer.colors.RED,
|
|
216
268
|
bold=True,
|
|
269
|
+
err=True,
|
|
217
270
|
)
|
|
218
271
|
raise typer.Exit(code=1) from err
|
|
219
272
|
|
|
@@ -221,7 +274,18 @@ def get_account_auth_config_path(root_dir: Path, federation: str) -> Path:
|
|
|
221
274
|
|
|
222
275
|
|
|
223
276
|
def account_auth_enabled(federation_config: dict[str, Any]) -> bool:
|
|
224
|
-
"""Check if account authentication is enabled in the federation config.
|
|
277
|
+
"""Check if account authentication is enabled in the federation config.
|
|
278
|
+
|
|
279
|
+
Parameters
|
|
280
|
+
----------
|
|
281
|
+
federation_config : dict[str, Any]
|
|
282
|
+
The federation configuration dictionary.
|
|
283
|
+
|
|
284
|
+
Returns
|
|
285
|
+
-------
|
|
286
|
+
bool
|
|
287
|
+
True if account authentication is enabled, False otherwise.
|
|
288
|
+
"""
|
|
225
289
|
enabled: bool = federation_config.get("enable-user-auth", False)
|
|
226
290
|
enabled |= federation_config.get("enable-account-auth", False)
|
|
227
291
|
if "enable-user-auth" in federation_config:
|
|
@@ -235,7 +299,18 @@ def account_auth_enabled(federation_config: dict[str, Any]) -> bool:
|
|
|
235
299
|
|
|
236
300
|
|
|
237
301
|
def retrieve_authn_type(config_path: Path) -> str:
|
|
238
|
-
"""Retrieve the auth type from the config file or return NOOP if not found.
|
|
302
|
+
"""Retrieve the auth type from the config file or return NOOP if not found.
|
|
303
|
+
|
|
304
|
+
Parameters
|
|
305
|
+
----------
|
|
306
|
+
config_path : Path
|
|
307
|
+
Path to the authentication configuration file.
|
|
308
|
+
|
|
309
|
+
Returns
|
|
310
|
+
-------
|
|
311
|
+
str
|
|
312
|
+
The authentication type string, or AuthnType.NOOP if not found.
|
|
313
|
+
"""
|
|
239
314
|
try:
|
|
240
315
|
with config_path.open("r", encoding="utf-8") as file:
|
|
241
316
|
json_file = json.load(file)
|
|
@@ -249,9 +324,31 @@ def load_cli_auth_plugin(
|
|
|
249
324
|
root_dir: Path,
|
|
250
325
|
federation: str,
|
|
251
326
|
federation_config: dict[str, Any],
|
|
252
|
-
authn_type:
|
|
327
|
+
authn_type: str | None = None,
|
|
253
328
|
) -> CliAuthPlugin:
|
|
254
|
-
"""Load the CLI-side account auth plugin for the given authn type.
|
|
329
|
+
"""Load the CLI-side account auth plugin for the given authn type.
|
|
330
|
+
|
|
331
|
+
Parameters
|
|
332
|
+
----------
|
|
333
|
+
root_dir : Path
|
|
334
|
+
Root directory of the Flower project.
|
|
335
|
+
federation : str
|
|
336
|
+
Name of the federation.
|
|
337
|
+
federation_config : dict[str, Any]
|
|
338
|
+
Federation configuration dictionary.
|
|
339
|
+
authn_type : str | None
|
|
340
|
+
Authentication type. If None, will be determined from config.
|
|
341
|
+
|
|
342
|
+
Returns
|
|
343
|
+
-------
|
|
344
|
+
CliAuthPlugin
|
|
345
|
+
The loaded authentication plugin instance.
|
|
346
|
+
|
|
347
|
+
Raises
|
|
348
|
+
------
|
|
349
|
+
typer.Exit
|
|
350
|
+
If the authentication type is unknown.
|
|
351
|
+
"""
|
|
255
352
|
# Find the path to the account auth config file
|
|
256
353
|
config_path = get_account_auth_config_path(root_dir, federation)
|
|
257
354
|
|
|
@@ -275,7 +372,22 @@ def load_cli_auth_plugin(
|
|
|
275
372
|
def init_channel(
|
|
276
373
|
app: Path, federation_config: dict[str, Any], auth_plugin: CliAuthPlugin
|
|
277
374
|
) -> grpc.Channel:
|
|
278
|
-
"""Initialize gRPC channel to the Control API.
|
|
375
|
+
"""Initialize gRPC channel to the Control API.
|
|
376
|
+
|
|
377
|
+
Parameters
|
|
378
|
+
----------
|
|
379
|
+
app : Path
|
|
380
|
+
Path to the Flower app directory.
|
|
381
|
+
federation_config : dict[str, Any]
|
|
382
|
+
Federation configuration dictionary containing address and TLS settings.
|
|
383
|
+
auth_plugin : CliAuthPlugin
|
|
384
|
+
Authentication plugin instance for handling credentials.
|
|
385
|
+
|
|
386
|
+
Returns
|
|
387
|
+
-------
|
|
388
|
+
grpc.Channel
|
|
389
|
+
Configured gRPC channel with authentication interceptors.
|
|
390
|
+
"""
|
|
279
391
|
insecure, root_certificates_bytes = validate_certificate_in_federation_config(
|
|
280
392
|
app, federation_config
|
|
281
393
|
)
|
|
@@ -299,9 +411,22 @@ def init_channel(
|
|
|
299
411
|
def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-branches
|
|
300
412
|
"""Context manager to handle specific gRPC errors.
|
|
301
413
|
|
|
302
|
-
|
|
303
|
-
UNAVAILABLE,
|
|
304
|
-
application. All other exceptions will be
|
|
414
|
+
Catches grpc.RpcError exceptions with UNAUTHENTICATED, UNIMPLEMENTED,
|
|
415
|
+
UNAVAILABLE, PERMISSION_DENIED, NOT_FOUND, and FAILED_PRECONDITION statuses,
|
|
416
|
+
informs the user, and exits the application. All other exceptions will be
|
|
417
|
+
allowed to escape.
|
|
418
|
+
|
|
419
|
+
Yields
|
|
420
|
+
------
|
|
421
|
+
None
|
|
422
|
+
Context manager yields nothing.
|
|
423
|
+
|
|
424
|
+
Raises
|
|
425
|
+
------
|
|
426
|
+
typer.Exit
|
|
427
|
+
On handled gRPC error statuses with appropriate exit code.
|
|
428
|
+
grpc.RpcError
|
|
429
|
+
For unhandled gRPC error statuses.
|
|
305
430
|
"""
|
|
306
431
|
try:
|
|
307
432
|
yield
|
|
@@ -312,6 +437,7 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-b
|
|
|
312
437
|
" to authenticate and try again.",
|
|
313
438
|
fg=typer.colors.RED,
|
|
314
439
|
bold=True,
|
|
440
|
+
err=True,
|
|
315
441
|
)
|
|
316
442
|
raise typer.Exit(code=1) from None
|
|
317
443
|
if e.code() == grpc.StatusCode.UNIMPLEMENTED:
|
|
@@ -320,12 +446,14 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-b
|
|
|
320
446
|
"❌ Account authentication is not enabled on this SuperLink.",
|
|
321
447
|
fg=typer.colors.RED,
|
|
322
448
|
bold=True,
|
|
449
|
+
err=True,
|
|
323
450
|
)
|
|
324
451
|
elif e.details() == NO_ARTIFACT_PROVIDER_MESSAGE: # pylint: disable=E1101
|
|
325
452
|
typer.secho(
|
|
326
453
|
"❌ The SuperLink does not support `flwr pull` command.",
|
|
327
454
|
fg=typer.colors.RED,
|
|
328
455
|
bold=True,
|
|
456
|
+
err=True,
|
|
329
457
|
)
|
|
330
458
|
else:
|
|
331
459
|
typer.secho(
|
|
@@ -335,6 +463,7 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-b
|
|
|
335
463
|
"the CLI and SuperLink are compatible.",
|
|
336
464
|
fg=typer.colors.RED,
|
|
337
465
|
bold=True,
|
|
466
|
+
err=True,
|
|
338
467
|
)
|
|
339
468
|
raise typer.Exit(code=1) from None
|
|
340
469
|
if e.code() == grpc.StatusCode.PERMISSION_DENIED:
|
|
@@ -342,6 +471,7 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-b
|
|
|
342
471
|
"❌ Permission denied.",
|
|
343
472
|
fg=typer.colors.RED,
|
|
344
473
|
bold=True,
|
|
474
|
+
err=True,
|
|
345
475
|
)
|
|
346
476
|
# pylint: disable-next=E1101
|
|
347
477
|
typer.secho(e.details(), fg=typer.colors.RED, bold=True)
|
|
@@ -352,6 +482,7 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-b
|
|
|
352
482
|
"connection and 'address' in the federation configuration.",
|
|
353
483
|
fg=typer.colors.RED,
|
|
354
484
|
bold=True,
|
|
485
|
+
err=True,
|
|
355
486
|
)
|
|
356
487
|
raise typer.Exit(code=1) from None
|
|
357
488
|
if e.code() == grpc.StatusCode.NOT_FOUND:
|
|
@@ -360,6 +491,7 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-b
|
|
|
360
491
|
"❌ Run ID not found.",
|
|
361
492
|
fg=typer.colors.RED,
|
|
362
493
|
bold=True,
|
|
494
|
+
err=True,
|
|
363
495
|
)
|
|
364
496
|
raise typer.Exit(code=1) from None
|
|
365
497
|
if e.details() == NODE_NOT_FOUND_MESSAGE: # pylint: disable=E1101
|
|
@@ -367,6 +499,7 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-b
|
|
|
367
499
|
"❌ Node ID not found for this account.",
|
|
368
500
|
fg=typer.colors.RED,
|
|
369
501
|
bold=True,
|
|
502
|
+
err=True,
|
|
370
503
|
)
|
|
371
504
|
raise typer.Exit(code=1) from None
|
|
372
505
|
if e.code() == grpc.StatusCode.FAILED_PRECONDITION:
|
|
@@ -376,6 +509,7 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-b
|
|
|
376
509
|
"the run is finished. You can check the run status with `flwr ls`.",
|
|
377
510
|
fg=typer.colors.RED,
|
|
378
511
|
bold=True,
|
|
512
|
+
err=True,
|
|
379
513
|
)
|
|
380
514
|
raise typer.Exit(code=1) from None
|
|
381
515
|
if (
|
|
@@ -386,6 +520,7 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-b
|
|
|
386
520
|
"SuperNode.",
|
|
387
521
|
fg=typer.colors.RED,
|
|
388
522
|
bold=True,
|
|
523
|
+
err=True,
|
|
389
524
|
)
|
|
390
525
|
raise typer.Exit(code=1) from None
|
|
391
526
|
if e.details() == PUBLIC_KEY_NOT_VALID: # pylint: disable=E1101
|
|
@@ -394,6 +529,94 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-b
|
|
|
394
529
|
"NIST EC public key.",
|
|
395
530
|
fg=typer.colors.RED,
|
|
396
531
|
bold=True,
|
|
532
|
+
err=True,
|
|
397
533
|
)
|
|
398
534
|
raise typer.Exit(code=1) from None
|
|
535
|
+
|
|
536
|
+
# Log details from grpc error directly
|
|
537
|
+
typer.secho(
|
|
538
|
+
f"❌ {e.details()}",
|
|
539
|
+
fg=typer.colors.RED,
|
|
540
|
+
bold=True,
|
|
541
|
+
err=True,
|
|
542
|
+
)
|
|
543
|
+
raise typer.Exit(code=1) from None
|
|
399
544
|
raise
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
def build_pathspec(patterns: Iterable[str]) -> pathspec.PathSpec:
|
|
548
|
+
"""Build a PathSpec from a list of GitIgnore-style patterns.
|
|
549
|
+
|
|
550
|
+
Parameters
|
|
551
|
+
----------
|
|
552
|
+
patterns : Iterable[str]
|
|
553
|
+
Iterable of GitIgnore-style pattern strings.
|
|
554
|
+
|
|
555
|
+
Returns
|
|
556
|
+
-------
|
|
557
|
+
pathspec.PathSpec
|
|
558
|
+
Compiled PathSpec object for pattern matching.
|
|
559
|
+
"""
|
|
560
|
+
return pathspec.PathSpec.from_lines("gitwildmatch", patterns)
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
def load_gitignore_patterns(file: Path | bytes) -> list[str]:
|
|
564
|
+
"""Load gitignore patterns from .gitignore file bytes.
|
|
565
|
+
|
|
566
|
+
Parameters
|
|
567
|
+
----------
|
|
568
|
+
file : Path | bytes
|
|
569
|
+
The path to a .gitignore file or its bytes content.
|
|
570
|
+
|
|
571
|
+
Returns
|
|
572
|
+
-------
|
|
573
|
+
list[str]
|
|
574
|
+
List of gitignore patterns.
|
|
575
|
+
Returns empty list if content can't be decoded or the file does not exist.
|
|
576
|
+
"""
|
|
577
|
+
try:
|
|
578
|
+
if isinstance(file, Path):
|
|
579
|
+
content = file.read_text(encoding="utf-8")
|
|
580
|
+
else:
|
|
581
|
+
content = file.decode("utf-8")
|
|
582
|
+
patterns = [
|
|
583
|
+
line.strip()
|
|
584
|
+
for line in content.splitlines()
|
|
585
|
+
if line.strip() and not line.strip().startswith("#")
|
|
586
|
+
]
|
|
587
|
+
return patterns
|
|
588
|
+
except (UnicodeDecodeError, OSError):
|
|
589
|
+
return []
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
def validate_credentials_content(creds_path: Path) -> str:
|
|
593
|
+
"""Load and validate the credentials file content.
|
|
594
|
+
|
|
595
|
+
Ensures required keys exist:
|
|
596
|
+
- AUTHN_TYPE_JSON_KEY
|
|
597
|
+
- ACCESS_TOKEN_KEY
|
|
598
|
+
- REFRESH_TOKEN_KEY
|
|
599
|
+
"""
|
|
600
|
+
try:
|
|
601
|
+
creds: dict[str, str] = json.loads(creds_path.read_text(encoding="utf-8"))
|
|
602
|
+
except (OSError, json.JSONDecodeError) as err:
|
|
603
|
+
typer.secho(
|
|
604
|
+
f"Invalid credentials file at '{creds_path}': {err}",
|
|
605
|
+
fg=typer.colors.RED,
|
|
606
|
+
err=True,
|
|
607
|
+
)
|
|
608
|
+
raise typer.Exit(code=1) from err
|
|
609
|
+
|
|
610
|
+
required_keys = [AUTHN_TYPE_JSON_KEY, ACCESS_TOKEN_KEY, REFRESH_TOKEN_KEY]
|
|
611
|
+
missing = [key for key in required_keys if key not in creds]
|
|
612
|
+
|
|
613
|
+
if missing:
|
|
614
|
+
typer.secho(
|
|
615
|
+
f"Credentials file '{creds_path}' is missing "
|
|
616
|
+
f"required key(s): {', '.join(missing)}. Please log in again.",
|
|
617
|
+
fg=typer.colors.RED,
|
|
618
|
+
err=True,
|
|
619
|
+
)
|
|
620
|
+
raise typer.Exit(code=1)
|
|
621
|
+
|
|
622
|
+
return creds[ACCESS_TOKEN_KEY]
|
flwr/client/__init__.py
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"""Flower client."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
from flwr.clientapp import ClientApp
|
|
18
|
+
from flwr.clientapp.client_app import ClientApp
|
|
19
19
|
|
|
20
20
|
from ..compat.client.app import start_client as start_client # Deprecated
|
|
21
21
|
from ..compat.client.app import start_numpy_client as start_numpy_client # Deprecated
|
|
@@ -120,7 +120,10 @@ class DPFedAvgNumPyClient(NumPyClient):
|
|
|
120
120
|
updated_params, num_examples, metrics = self.client.fit(parameters, config)
|
|
121
121
|
|
|
122
122
|
# Update = updated model - original model
|
|
123
|
-
update = [
|
|
123
|
+
update = [
|
|
124
|
+
np.subtract(x, y)
|
|
125
|
+
for (x, y) in zip(updated_params, original_params, strict=True)
|
|
126
|
+
]
|
|
124
127
|
|
|
125
128
|
if "dpfedavg_clip_norm" not in config:
|
|
126
129
|
raise KeyError("Clipping threshold not supplied by the server.")
|
|
@@ -15,10 +15,9 @@
|
|
|
15
15
|
"""Contextmanager for a GrpcAdapter channel to the Flower server."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
from collections.abc import Iterator
|
|
18
|
+
from collections.abc import Callable, Iterator
|
|
19
19
|
from contextlib import contextmanager
|
|
20
20
|
from logging import ERROR
|
|
21
|
-
from typing import Callable, Optional, Union
|
|
22
21
|
|
|
23
22
|
from cryptography.hazmat.primitives.asymmetric import ec
|
|
24
23
|
|
|
@@ -38,15 +37,15 @@ def grpc_adapter( # pylint: disable=R0913,too-many-positional-arguments
|
|
|
38
37
|
insecure: bool,
|
|
39
38
|
retry_invoker: RetryInvoker,
|
|
40
39
|
max_message_length: int = GRPC_MAX_MESSAGE_LENGTH, # pylint: disable=W0613
|
|
41
|
-
root_certificates:
|
|
42
|
-
authentication_keys:
|
|
43
|
-
tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]
|
|
44
|
-
|
|
40
|
+
root_certificates: bytes | str | None = None,
|
|
41
|
+
authentication_keys: (
|
|
42
|
+
tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey] | None
|
|
43
|
+
) = None,
|
|
45
44
|
) -> Iterator[
|
|
46
45
|
tuple[
|
|
47
46
|
int,
|
|
48
|
-
Callable[[],
|
|
49
|
-
Callable[[Message, ObjectTree], set[str]],
|
|
47
|
+
Callable[[], tuple[Message, ObjectTree] | None],
|
|
48
|
+
Callable[[Message, ObjectTree, float], set[str]],
|
|
50
49
|
Callable[[int], Run],
|
|
51
50
|
Callable[[str, int], Fab],
|
|
52
51
|
Callable[[int, str], bytes],
|
|
@@ -82,7 +81,7 @@ def grpc_adapter( # pylint: disable=R0913,too-many-positional-arguments
|
|
|
82
81
|
-------
|
|
83
82
|
node_id : int
|
|
84
83
|
receive : Callable[[], Optional[tuple[Message, ObjectTree]]]
|
|
85
|
-
send : Callable[[Message, ObjectTree], set[str]]
|
|
84
|
+
send : Callable[[Message, ObjectTree, float], set[str]]
|
|
86
85
|
get_run : Callable[[int], Run]
|
|
87
86
|
get_fab : Callable[[str, int], Fab]
|
|
88
87
|
pull_object : Callable[[str], bytes]
|
|
@@ -15,11 +15,10 @@
|
|
|
15
15
|
"""Contextmanager for a gRPC request-response channel to the Flower server."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
from collections.abc import Iterator, Sequence
|
|
18
|
+
from collections.abc import Callable, Iterator, Sequence
|
|
19
19
|
from contextlib import contextmanager
|
|
20
20
|
from logging import ERROR
|
|
21
21
|
from pathlib import Path
|
|
22
|
-
from typing import Callable, Optional, Union
|
|
23
22
|
|
|
24
23
|
import grpc
|
|
25
24
|
from cryptography.hazmat.primitives.asymmetric import ec
|
|
@@ -27,7 +26,6 @@ from cryptography.hazmat.primitives.asymmetric import ec
|
|
|
27
26
|
from flwr.common import GRPC_MAX_MESSAGE_LENGTH
|
|
28
27
|
from flwr.common.constant import HEARTBEAT_CALL_TIMEOUT, HEARTBEAT_DEFAULT_INTERVAL
|
|
29
28
|
from flwr.common.grpc import create_channel, on_channel_state_change
|
|
30
|
-
from flwr.common.heartbeat import HeartbeatSender
|
|
31
29
|
from flwr.common.inflatable_protobuf_utils import (
|
|
32
30
|
make_confirm_message_received_fn_protobuf,
|
|
33
31
|
make_pull_object_fn_protobuf,
|
|
@@ -63,6 +61,7 @@ from flwr.proto.heartbeat_pb2 import ( # pylint: disable=E0611
|
|
|
63
61
|
from flwr.proto.message_pb2 import ObjectTree # pylint: disable=E0611
|
|
64
62
|
from flwr.proto.node_pb2 import Node # pylint: disable=E0611
|
|
65
63
|
from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611
|
|
64
|
+
from flwr.supercore.heartbeat import HeartbeatSender
|
|
66
65
|
from flwr.supercore.primitives.asymmetric import generate_key_pairs, public_key_to_bytes
|
|
67
66
|
|
|
68
67
|
from .grpc_adapter import GrpcAdapter
|
|
@@ -75,16 +74,16 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
|
75
74
|
insecure: bool,
|
|
76
75
|
retry_invoker: RetryInvoker,
|
|
77
76
|
max_message_length: int = GRPC_MAX_MESSAGE_LENGTH, # pylint: disable=W0613
|
|
78
|
-
root_certificates:
|
|
79
|
-
authentication_keys:
|
|
80
|
-
tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]
|
|
81
|
-
|
|
82
|
-
adapter_cls:
|
|
77
|
+
root_certificates: bytes | str | None = None,
|
|
78
|
+
authentication_keys: (
|
|
79
|
+
tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey] | None
|
|
80
|
+
) = None,
|
|
81
|
+
adapter_cls: type[FleetStub] | type[GrpcAdapter] | None = None,
|
|
83
82
|
) -> Iterator[
|
|
84
83
|
tuple[
|
|
85
84
|
int,
|
|
86
|
-
Callable[[],
|
|
87
|
-
Callable[[Message, ObjectTree], set[str]],
|
|
85
|
+
Callable[[], tuple[Message, ObjectTree] | None],
|
|
86
|
+
Callable[[Message, ObjectTree, float], set[str]],
|
|
88
87
|
Callable[[int], Run],
|
|
89
88
|
Callable[[str, int], Fab],
|
|
90
89
|
Callable[[int, str], bytes],
|
|
@@ -129,7 +128,7 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
|
129
128
|
-------
|
|
130
129
|
node_id : int
|
|
131
130
|
receive : Callable[[], Optional[tuple[Message, ObjectTree]]]
|
|
132
|
-
send : Callable[[Message, ObjectTree], set[str]]
|
|
131
|
+
send : Callable[[Message, ObjectTree, float], set[str]]
|
|
133
132
|
get_run : Callable[[int], Run]
|
|
134
133
|
get_fab : Callable[[str, int], Fab]
|
|
135
134
|
pull_object : Callable[[str], bytes]
|
|
@@ -163,7 +162,7 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
|
163
162
|
if adapter_cls is None:
|
|
164
163
|
adapter_cls = FleetStub
|
|
165
164
|
stub = adapter_cls(channel)
|
|
166
|
-
node:
|
|
165
|
+
node: Node | None = None
|
|
167
166
|
|
|
168
167
|
# Wrap stub
|
|
169
168
|
_wrap_stub(stub, retry_invoker)
|
|
@@ -253,7 +252,7 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
|
253
252
|
# Cleanup
|
|
254
253
|
node = None
|
|
255
254
|
|
|
256
|
-
def receive() ->
|
|
255
|
+
def receive() -> tuple[Message, ObjectTree] | None:
|
|
257
256
|
"""Pull a message with its ObjectTree from SuperLink."""
|
|
258
257
|
# Get Node
|
|
259
258
|
if node is None:
|
|
@@ -278,7 +277,9 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
|
278
277
|
# Return the Message and its object tree
|
|
279
278
|
return in_message, object_tree
|
|
280
279
|
|
|
281
|
-
def send(
|
|
280
|
+
def send(
|
|
281
|
+
message: Message, object_tree: ObjectTree, clientapp_runtime: float
|
|
282
|
+
) -> set[str]:
|
|
282
283
|
"""Send the message with its ObjectTree to SuperLink."""
|
|
283
284
|
# Get Node
|
|
284
285
|
if node is None:
|
|
@@ -294,6 +295,7 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
|
294
295
|
node=node,
|
|
295
296
|
messages_list=[message_to_proto(message)],
|
|
296
297
|
message_object_trees=[object_tree],
|
|
298
|
+
clientapp_runtime_list=[clientapp_runtime],
|
|
297
299
|
)
|
|
298
300
|
response: PushMessagesResponse = stub.PushMessages(request=request)
|
|
299
301
|
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"""GrpcAdapter implementation."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
import
|
|
18
|
+
import signal
|
|
19
|
+
import time
|
|
19
20
|
from logging import DEBUG
|
|
20
21
|
from typing import Any, TypeVar, cast
|
|
21
22
|
|
|
@@ -62,6 +63,7 @@ from flwr.proto.message_pb2 import ( # pylint: disable=E0611
|
|
|
62
63
|
PushObjectResponse,
|
|
63
64
|
)
|
|
64
65
|
from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611
|
|
66
|
+
from flwr.supercore.constant import FORCE_EXIT_TIMEOUT_SECONDS
|
|
65
67
|
|
|
66
68
|
T = TypeVar("T", bound=GrpcMessage)
|
|
67
69
|
|
|
@@ -108,7 +110,9 @@ class GrpcAdapter:
|
|
|
108
110
|
DEBUG,
|
|
109
111
|
'Received shutdown signal: exit flag is set to ``"true"``. Exiting...',
|
|
110
112
|
)
|
|
111
|
-
|
|
113
|
+
signal.raise_signal(signal.SIGTERM)
|
|
114
|
+
# Give some time to handle the signal
|
|
115
|
+
time.sleep(FORCE_EXIT_TIMEOUT_SECONDS + 1)
|
|
112
116
|
|
|
113
117
|
# Check the grpc_message_name of the response
|
|
114
118
|
if container_res.grpc_message_name != response_type.__qualname__:
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
from logging import WARN
|
|
19
|
-
from typing import
|
|
19
|
+
from typing import cast
|
|
20
20
|
|
|
21
21
|
from flwr.client.client import (
|
|
22
22
|
maybe_call_evaluate,
|
|
@@ -53,7 +53,7 @@ class UnknownServerMessage(Exception):
|
|
|
53
53
|
"""Exception indicating that the received message is unknown."""
|
|
54
54
|
|
|
55
55
|
|
|
56
|
-
def handle_control_message(message: Message) -> tuple[
|
|
56
|
+
def handle_control_message(message: Message) -> tuple[Message | None, int]:
|
|
57
57
|
"""Handle control part of the incoming message.
|
|
58
58
|
|
|
59
59
|
Parameters
|
|
@@ -112,9 +112,9 @@ class SecAggPlusState:
|
|
|
112
112
|
updated_values = [
|
|
113
113
|
tuple(values[i : i + 2]) for i in range(0, len(values), 2)
|
|
114
114
|
]
|
|
115
|
-
new_v = dict(zip(keys, updated_values))
|
|
115
|
+
new_v = dict(zip(keys, updated_values, strict=True))
|
|
116
116
|
else:
|
|
117
|
-
new_v = dict(zip(keys, values))
|
|
117
|
+
new_v = dict(zip(keys, values, strict=True))
|
|
118
118
|
self.__setattr__(k, new_v)
|
|
119
119
|
|
|
120
120
|
def to_dict(self) -> dict[str, ConfigRecordValues]:
|
|
@@ -426,7 +426,7 @@ def _collect_masked_vectors(
|
|
|
426
426
|
raise ValueError("Not enough available neighbour clients.")
|
|
427
427
|
|
|
428
428
|
# Decrypt ciphertexts, verify their sources, and store shares.
|
|
429
|
-
for src, ciphertext in zip(srcs, ciphertexts):
|
|
429
|
+
for src, ciphertext in zip(srcs, ciphertexts, strict=True):
|
|
430
430
|
shared_key = state.ss2_dict[src]
|
|
431
431
|
plaintext = decrypt(shared_key, ciphertext)
|
|
432
432
|
actual_src, dst, rd_seed_share, sk1_share = share_keys_plaintext_separate(
|