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
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,12 +38,14 @@ 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
45
|
# SUPERNODE_NODE_AUTH_KEYS_REQUIRED = 301 --- DELETED ---
|
|
45
46
|
SUPERNODE_NODE_AUTH_KEY_INVALID = 302
|
|
46
47
|
SUPERNODE_STARTED_WITHOUT_TLS_BUT_NODE_AUTH_ENABLED = 303
|
|
48
|
+
SUPERNODE_INVALID_TRUSTED_ENTITIES = 304
|
|
47
49
|
|
|
48
50
|
# SuperExec-specific exit codes (400-499)
|
|
49
51
|
SUPEREXEC_INVALID_PLUGIN_CONFIG = 400
|
|
@@ -56,6 +58,9 @@ class ExitCode:
|
|
|
56
58
|
COMMON_MISSING_EXTRA_REST = 601
|
|
57
59
|
COMMON_TLS_NOT_SUPPORTED = 602
|
|
58
60
|
|
|
61
|
+
# Simulation exit codes (700-799)
|
|
62
|
+
SIMULATION_EXCEPTION = 700
|
|
63
|
+
|
|
59
64
|
def __new__(cls) -> ExitCode:
|
|
60
65
|
"""Prevent instantiation."""
|
|
61
66
|
raise TypeError(f"{cls.__name__} cannot be instantiated.")
|
|
@@ -101,6 +106,11 @@ EXIT_CODE_HELP = {
|
|
|
101
106
|
"The strategy encountered an error during aggregation. Please check the logs "
|
|
102
107
|
"for more details."
|
|
103
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
|
+
),
|
|
104
114
|
# SuperNode-specific exit codes (300-399)
|
|
105
115
|
ExitCode.SUPERNODE_REST_ADDRESS_INVALID: (
|
|
106
116
|
"When using the REST API, please provide `https://` or "
|
|
@@ -115,6 +125,11 @@ EXIT_CODE_HELP = {
|
|
|
115
125
|
"The private key for SuperNode authentication was provided, but TLS is not "
|
|
116
126
|
"enabled. Node authentication can only be used when TLS is enabled."
|
|
117
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
|
+
),
|
|
118
133
|
# SuperExec-specific exit codes (400-499)
|
|
119
134
|
ExitCode.SUPEREXEC_INVALID_PLUGIN_CONFIG: (
|
|
120
135
|
"The YAML configuration for the SuperExec plugin is invalid."
|
|
@@ -138,4 +153,8 @@ To use the REST API, install `flwr` with the `rest` extra:
|
|
|
138
153
|
`pip install "flwr[rest]"`.
|
|
139
154
|
""",
|
|
140
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
|
+
),
|
|
141
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
|
|
flwr/common/inflatable_utils.py
CHANGED
|
@@ -14,13 +14,15 @@
|
|
|
14
14
|
# ==============================================================================
|
|
15
15
|
"""InflatableObject utilities."""
|
|
16
16
|
|
|
17
|
+
|
|
17
18
|
import concurrent.futures
|
|
18
19
|
import os
|
|
19
20
|
import random
|
|
20
21
|
import threading
|
|
21
22
|
import time
|
|
22
|
-
from collections.abc import Iterable, Iterator
|
|
23
|
-
from
|
|
23
|
+
from collections.abc import Callable, Iterable, Iterator
|
|
24
|
+
from queue import Queue
|
|
25
|
+
from typing import TypeVar
|
|
24
26
|
|
|
25
27
|
from flwr.proto.message_pb2 import ObjectTree # pylint: disable=E0611
|
|
26
28
|
|
|
@@ -116,7 +118,7 @@ def push_objects(
|
|
|
116
118
|
objects: dict[str, InflatableObject],
|
|
117
119
|
push_object_fn: Callable[[str, bytes], None],
|
|
118
120
|
*,
|
|
119
|
-
object_ids_to_push:
|
|
121
|
+
object_ids_to_push: set[str] | None = None,
|
|
120
122
|
keep_objects: bool = False,
|
|
121
123
|
max_concurrent_pushes: int = FLWR_PRIVATE_MAX_CONCURRENT_OBJ_PUSHES,
|
|
122
124
|
) -> None:
|
|
@@ -184,12 +186,21 @@ def push_object_contents_from_iterable(
|
|
|
184
186
|
max_concurrent_pushes : int (default: FLWR_PRIVATE_MAX_CONCURRENT_OBJ_PUSHES)
|
|
185
187
|
The maximum number of concurrent pushes to perform.
|
|
186
188
|
"""
|
|
189
|
+
error_event = threading.Event()
|
|
190
|
+
err_queue: Queue[Exception] = Queue()
|
|
187
191
|
|
|
188
192
|
def push(args: tuple[str, bytes]) -> None:
|
|
189
193
|
"""Push a single object."""
|
|
194
|
+
if error_event.is_set():
|
|
195
|
+
return
|
|
190
196
|
obj_id, obj_content = args
|
|
191
197
|
# Push the object using the provided function
|
|
192
|
-
|
|
198
|
+
try:
|
|
199
|
+
push_object_fn(obj_id, obj_content)
|
|
200
|
+
except Exception as err: # pylint: disable=broad-except
|
|
201
|
+
# Unexpected error during pushing
|
|
202
|
+
error_event.set()
|
|
203
|
+
err_queue.put(err)
|
|
193
204
|
|
|
194
205
|
# Push all object contents concurrently
|
|
195
206
|
num_workers = get_num_workers(max_concurrent_pushes)
|
|
@@ -205,14 +216,18 @@ def push_object_contents_from_iterable(
|
|
|
205
216
|
# Remove the executor from the list of tracked executors
|
|
206
217
|
_untrack_executor(executor)
|
|
207
218
|
|
|
219
|
+
# If an error occurred during pushing, raise it
|
|
220
|
+
if not err_queue.empty():
|
|
221
|
+
raise err_queue.get()
|
|
222
|
+
|
|
208
223
|
|
|
209
224
|
def pull_objects( # pylint: disable=too-many-arguments,too-many-locals
|
|
210
225
|
object_ids: list[str],
|
|
211
226
|
pull_object_fn: Callable[[str], bytes],
|
|
212
227
|
*,
|
|
213
228
|
max_concurrent_pulls: int = FLWR_PRIVATE_MAX_CONCURRENT_OBJ_PULLS,
|
|
214
|
-
max_time:
|
|
215
|
-
max_tries_per_object:
|
|
229
|
+
max_time: float | None = PULL_MAX_TIME,
|
|
230
|
+
max_tries_per_object: int | None = PULL_MAX_TRIES_PER_OBJECT,
|
|
216
231
|
initial_backoff: float = PULL_INITIAL_BACKOFF,
|
|
217
232
|
backoff_cap: float = PULL_BACKOFF_CAP,
|
|
218
233
|
) -> dict[str, bytes]:
|
|
@@ -254,13 +269,16 @@ def pull_objects( # pylint: disable=too-many-arguments,too-many-locals
|
|
|
254
269
|
|
|
255
270
|
results: dict[str, bytes] = {}
|
|
256
271
|
results_lock = threading.Lock()
|
|
257
|
-
|
|
272
|
+
err_queue: Queue[Exception] = Queue()
|
|
258
273
|
early_stop = threading.Event()
|
|
259
274
|
start = time.monotonic()
|
|
260
275
|
|
|
276
|
+
def stop_on_error(err: Exception) -> None:
|
|
277
|
+
early_stop.set()
|
|
278
|
+
err_queue.put(err)
|
|
279
|
+
|
|
261
280
|
def pull_with_retries(object_id: str) -> None:
|
|
262
281
|
"""Attempt to pull a single object with retry and backoff."""
|
|
263
|
-
nonlocal err_to_raise
|
|
264
282
|
tries = 0
|
|
265
283
|
delay = initial_backoff
|
|
266
284
|
|
|
@@ -278,10 +296,7 @@ def pull_objects( # pylint: disable=too-many-arguments,too-many-locals
|
|
|
278
296
|
or time.monotonic() - start >= max_time
|
|
279
297
|
):
|
|
280
298
|
# Stop all work if one object exhausts retries
|
|
281
|
-
|
|
282
|
-
with results_lock:
|
|
283
|
-
if err_to_raise is None:
|
|
284
|
-
err_to_raise = err
|
|
299
|
+
stop_on_error(err)
|
|
285
300
|
return
|
|
286
301
|
|
|
287
302
|
# Apply exponential backoff with ±20% jitter
|
|
@@ -291,10 +306,12 @@ def pull_objects( # pylint: disable=too-many-arguments,too-many-locals
|
|
|
291
306
|
|
|
292
307
|
except ObjectIdNotPreregisteredError as err:
|
|
293
308
|
# Permanent failure: object ID is invalid
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
309
|
+
stop_on_error(err)
|
|
310
|
+
return
|
|
311
|
+
|
|
312
|
+
except Exception as err: # pylint: disable=broad-except
|
|
313
|
+
# Permanent failure: unexpected error
|
|
314
|
+
stop_on_error(err)
|
|
298
315
|
return
|
|
299
316
|
|
|
300
317
|
# Submit all pull tasks concurrently
|
|
@@ -312,8 +329,8 @@ def pull_objects( # pylint: disable=too-many-arguments,too-many-locals
|
|
|
312
329
|
_untrack_executor(executor)
|
|
313
330
|
|
|
314
331
|
# If an error occurred during pulling, raise it
|
|
315
|
-
if
|
|
316
|
-
raise
|
|
332
|
+
if not err_queue.empty():
|
|
333
|
+
raise err_queue.get()
|
|
317
334
|
|
|
318
335
|
return results
|
|
319
336
|
|
|
@@ -323,7 +340,7 @@ def inflate_object_from_contents(
|
|
|
323
340
|
object_contents: dict[str, bytes],
|
|
324
341
|
*,
|
|
325
342
|
keep_object_contents: bool = False,
|
|
326
|
-
objects:
|
|
343
|
+
objects: dict[str, InflatableObject] | None = None,
|
|
327
344
|
) -> InflatableObject:
|
|
328
345
|
"""Inflate an object from object contents.
|
|
329
346
|
|
|
@@ -443,8 +460,8 @@ def pull_and_inflate_object_from_tree( # pylint: disable=R0913
|
|
|
443
460
|
*,
|
|
444
461
|
return_type: type[T] = InflatableObject, # type: ignore
|
|
445
462
|
max_concurrent_pulls: int = FLWR_PRIVATE_MAX_CONCURRENT_OBJ_PULLS,
|
|
446
|
-
max_time:
|
|
447
|
-
max_tries_per_object:
|
|
463
|
+
max_time: float | None = PULL_MAX_TIME,
|
|
464
|
+
max_tries_per_object: int | None = PULL_MAX_TRIES_PER_OBJECT,
|
|
448
465
|
initial_backoff: float = PULL_INITIAL_BACKOFF,
|
|
449
466
|
backoff_cap: float = PULL_BACKOFF_CAP,
|
|
450
467
|
) -> T:
|
flwr/common/logger.py
CHANGED
|
@@ -26,7 +26,7 @@ from io import StringIO
|
|
|
26
26
|
from logging import ERROR, WARN, LogRecord
|
|
27
27
|
from logging.handlers import HTTPHandler
|
|
28
28
|
from queue import Empty, Queue
|
|
29
|
-
from typing import TYPE_CHECKING, Any,
|
|
29
|
+
from typing import TYPE_CHECKING, Any, TextIO
|
|
30
30
|
|
|
31
31
|
import grpc
|
|
32
32
|
import typer
|
|
@@ -68,7 +68,7 @@ class ConsoleHandler(StreamHandler):
|
|
|
68
68
|
timestamps: bool = False,
|
|
69
69
|
json: bool = False,
|
|
70
70
|
colored: bool = True,
|
|
71
|
-
stream:
|
|
71
|
+
stream: TextIO | None = None,
|
|
72
72
|
) -> None:
|
|
73
73
|
super().__init__(stream)
|
|
74
74
|
self.timestamps = timestamps
|
|
@@ -103,9 +103,9 @@ class ConsoleHandler(StreamHandler):
|
|
|
103
103
|
|
|
104
104
|
|
|
105
105
|
def update_console_handler(
|
|
106
|
-
level:
|
|
107
|
-
timestamps:
|
|
108
|
-
colored:
|
|
106
|
+
level: int | str | None = None,
|
|
107
|
+
timestamps: bool | None = None,
|
|
108
|
+
colored: bool | None = None,
|
|
109
109
|
) -> None:
|
|
110
110
|
"""Update the logging handler."""
|
|
111
111
|
for handler in logging.getLogger(LOGGER_NAME).handlers:
|
|
@@ -160,7 +160,7 @@ class CustomHTTPHandler(HTTPHandler):
|
|
|
160
160
|
url: str,
|
|
161
161
|
method: str = "GET",
|
|
162
162
|
secure: bool = False,
|
|
163
|
-
credentials:
|
|
163
|
+
credentials: tuple[str, str] | None = None,
|
|
164
164
|
) -> None:
|
|
165
165
|
super().__init__(host, url, method, secure, credentials)
|
|
166
166
|
self.identifier = identifier
|
|
@@ -180,7 +180,7 @@ class CustomHTTPHandler(HTTPHandler):
|
|
|
180
180
|
|
|
181
181
|
|
|
182
182
|
def configure(
|
|
183
|
-
identifier: str, filename:
|
|
183
|
+
identifier: str, filename: str | None = None, host: str | None = None
|
|
184
184
|
) -> None:
|
|
185
185
|
"""Configure logging to file and/or remote log server."""
|
|
186
186
|
# Create formatter
|
|
@@ -298,7 +298,7 @@ def set_logger_propagation(
|
|
|
298
298
|
return child_logger
|
|
299
299
|
|
|
300
300
|
|
|
301
|
-
def mirror_output_to_queue(log_queue: Queue[
|
|
301
|
+
def mirror_output_to_queue(log_queue: Queue[str | None]) -> None:
|
|
302
302
|
"""Mirror stdout and stderr output to the provided queue."""
|
|
303
303
|
|
|
304
304
|
def get_write_fn(stream: TextIO) -> Any:
|
|
@@ -335,7 +335,7 @@ def redirect_output(output_buffer: StringIO) -> None:
|
|
|
335
335
|
|
|
336
336
|
|
|
337
337
|
def _log_uploader(
|
|
338
|
-
log_queue: Queue[
|
|
338
|
+
log_queue: Queue[str | None], node_id: int, run_id: int, stub: ServerAppIoStub
|
|
339
339
|
) -> None:
|
|
340
340
|
"""Upload logs to the SuperLink."""
|
|
341
341
|
exit_flag = False
|
|
@@ -378,10 +378,10 @@ def _log_uploader(
|
|
|
378
378
|
|
|
379
379
|
|
|
380
380
|
def start_log_uploader(
|
|
381
|
-
log_queue: Queue[
|
|
381
|
+
log_queue: Queue[str | None],
|
|
382
382
|
node_id: int,
|
|
383
383
|
run_id: int,
|
|
384
|
-
stub:
|
|
384
|
+
stub: ServerAppIoStub | SimulationIoStub,
|
|
385
385
|
) -> threading.Thread:
|
|
386
386
|
"""Start the log uploader thread and return it."""
|
|
387
387
|
thread = threading.Thread(
|
|
@@ -392,7 +392,7 @@ def start_log_uploader(
|
|
|
392
392
|
|
|
393
393
|
|
|
394
394
|
def stop_log_uploader(
|
|
395
|
-
log_queue: Queue[
|
|
395
|
+
log_queue: Queue[str | None], log_uploader: threading.Thread
|
|
396
396
|
) -> None:
|
|
397
397
|
"""Stop the log uploader thread."""
|
|
398
398
|
log_queue.put(None)
|
|
@@ -403,19 +403,19 @@ def _remove_emojis(text: str) -> str:
|
|
|
403
403
|
"""Remove emojis from the provided text."""
|
|
404
404
|
emoji_pattern = re.compile(
|
|
405
405
|
"["
|
|
406
|
-
"\
|
|
407
|
-
"\
|
|
408
|
-
"\
|
|
409
|
-
"\
|
|
410
|
-
"\U00002702-\
|
|
411
|
-
"\
|
|
406
|
+
"\U0001f600-\U0001f64f" # Emoticons
|
|
407
|
+
"\U0001f300-\U0001f5ff" # Symbols & Pictographs
|
|
408
|
+
"\U0001f680-\U0001f6ff" # Transport & Map Symbols
|
|
409
|
+
"\U0001f1e0-\U0001f1ff" # Flags
|
|
410
|
+
"\U00002702-\U000027b0" # Dingbats
|
|
411
|
+
"\U000024c2-\U0001f251"
|
|
412
412
|
"]+",
|
|
413
413
|
flags=re.UNICODE,
|
|
414
414
|
)
|
|
415
415
|
return emoji_pattern.sub(r"", text)
|
|
416
416
|
|
|
417
417
|
|
|
418
|
-
def print_json_error(msg: str, e:
|
|
418
|
+
def print_json_error(msg: str, e: typer.Exit | Exception) -> None:
|
|
419
419
|
"""Print error message as JSON."""
|
|
420
420
|
Console().print_json(
|
|
421
421
|
_json.dumps(
|
flwr/common/message.py
CHANGED
|
@@ -105,7 +105,7 @@ class Message(InflatableObject):
|
|
|
105
105
|
"""
|
|
106
106
|
|
|
107
107
|
@overload
|
|
108
|
-
def __init__( # pylint: disable=too-many-arguments
|
|
108
|
+
def __init__( # pylint: disable=too-many-arguments
|
|
109
109
|
self,
|
|
110
110
|
content: RecordDict,
|
|
111
111
|
dst_node_id: int,
|
|
@@ -116,12 +116,12 @@ class Message(InflatableObject):
|
|
|
116
116
|
) -> None: ...
|
|
117
117
|
|
|
118
118
|
@overload
|
|
119
|
-
def __init__(
|
|
119
|
+
def __init__(
|
|
120
120
|
self, content: RecordDict, *, reply_to: Message, ttl: float | None = None
|
|
121
121
|
) -> None: ...
|
|
122
122
|
|
|
123
123
|
@overload
|
|
124
|
-
def __init__(
|
|
124
|
+
def __init__(
|
|
125
125
|
self, error: Error, *, reply_to: Message, ttl: float | None = None
|
|
126
126
|
) -> None: ...
|
|
127
127
|
|
|
@@ -511,7 +511,7 @@ def _check_arg_types( # pylint: disable=too-many-arguments, R0917
|
|
|
511
511
|
and (message_type is None or isinstance(message_type, str))
|
|
512
512
|
and (content is None or isinstance(content, RecordDict))
|
|
513
513
|
and (error is None or isinstance(error, Error))
|
|
514
|
-
and (ttl is None or isinstance(ttl, (int
|
|
514
|
+
and (ttl is None or isinstance(ttl, (int | float)))
|
|
515
515
|
and (group_id is None or isinstance(group_id, str))
|
|
516
516
|
and (reply_to is None or isinstance(reply_to, Message))
|
|
517
517
|
and (metadata is None or isinstance(metadata, Metadata))
|
flwr/common/object_ref.py
CHANGED
|
@@ -21,7 +21,7 @@ import sys
|
|
|
21
21
|
from importlib.util import find_spec
|
|
22
22
|
from pathlib import Path
|
|
23
23
|
from threading import Lock
|
|
24
|
-
from typing import Any
|
|
24
|
+
from typing import Any
|
|
25
25
|
|
|
26
26
|
OBJECT_REF_HELP_STR = """
|
|
27
27
|
\n\nThe object reference string should have the form <module>:<attribute>. Valid
|
|
@@ -31,15 +31,15 @@ attribute.
|
|
|
31
31
|
"""
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
_current_sys_path:
|
|
34
|
+
_current_sys_path: str | None = None
|
|
35
35
|
_import_lock = Lock()
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
def validate(
|
|
39
39
|
module_attribute_str: str,
|
|
40
40
|
check_module: bool = True,
|
|
41
|
-
project_dir:
|
|
42
|
-
) -> tuple[bool,
|
|
41
|
+
project_dir: str | Path | None = None,
|
|
42
|
+
) -> tuple[bool, str | None]:
|
|
43
43
|
"""Validate object reference.
|
|
44
44
|
|
|
45
45
|
Parameters
|
|
@@ -114,7 +114,7 @@ def validate(
|
|
|
114
114
|
def load_app( # pylint: disable= too-many-branches
|
|
115
115
|
module_attribute_str: str,
|
|
116
116
|
error_type: type[Exception],
|
|
117
|
-
project_dir:
|
|
117
|
+
project_dir: str | Path | None = None,
|
|
118
118
|
) -> Any:
|
|
119
119
|
"""Return the object specified in a module attribute string.
|
|
120
120
|
|
|
@@ -194,12 +194,12 @@ def _unload_modules(project_dir: Path) -> None:
|
|
|
194
194
|
"""Unload modules from the project directory."""
|
|
195
195
|
dir_str = str(project_dir.absolute())
|
|
196
196
|
for name, m in list(sys.modules.items()):
|
|
197
|
-
path:
|
|
197
|
+
path: str | None = getattr(m, "__file__", None)
|
|
198
198
|
if path is not None and path.startswith(dir_str):
|
|
199
199
|
del sys.modules[name]
|
|
200
200
|
|
|
201
201
|
|
|
202
|
-
def _set_sys_path(directory:
|
|
202
|
+
def _set_sys_path(directory: str | Path | None) -> None:
|
|
203
203
|
"""Set the system path."""
|
|
204
204
|
if directory is None:
|
|
205
205
|
directory = Path.cwd()
|
flwr/common/record/array.py
CHANGED
|
@@ -117,15 +117,15 @@ class Array(InflatableObject):
|
|
|
117
117
|
data: bytes
|
|
118
118
|
|
|
119
119
|
@overload
|
|
120
|
-
def __init__(
|
|
120
|
+
def __init__(
|
|
121
121
|
self, dtype: str, shape: tuple[int, ...], stype: str, data: bytes
|
|
122
122
|
) -> None: ...
|
|
123
123
|
|
|
124
124
|
@overload
|
|
125
|
-
def __init__(self, ndarray: NDArray) -> None: ...
|
|
125
|
+
def __init__(self, ndarray: NDArray) -> None: ...
|
|
126
126
|
|
|
127
127
|
@overload
|
|
128
|
-
def __init__(self, torch_tensor: torch.Tensor) -> None: ...
|
|
128
|
+
def __init__(self, torch_tensor: torch.Tensor) -> None: ...
|
|
129
129
|
|
|
130
130
|
def __init__( # pylint: disable=too-many-arguments, too-many-locals
|
|
131
131
|
self,
|