flwr-nightly 1.23.0.dev20250930__py3-none-any.whl → 1.26.0.dev20260121__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 +17 -6
- flwr/app/__init__.py +4 -1
- flwr/app/error.py +2 -2
- flwr/app/exception.py +3 -3
- flwr/app/message_type.py +29 -0
- flwr/app/metadata.py +5 -2
- flwr/app/user_config.py +19 -0
- flwr/cli/app.py +62 -9
- flwr/cli/{new/templates/app/code → 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/__init__.py +13 -6
- flwr/cli/auth_plugin/auth_plugin.py +26 -15
- flwr/cli/auth_plugin/noop_auth_plugin.py +101 -0
- flwr/cli/auth_plugin/oidc_cli_plugin.py +52 -32
- flwr/cli/build.py +166 -53
- flwr/cli/{cli_user_auth_interceptor.py → cli_account_auth_interceptor.py} +27 -10
- flwr/cli/config/__init__.py +21 -0
- flwr/cli/config/ls.py +104 -0
- flwr/cli/config_migration.py +300 -0
- flwr/cli/config_utils.py +154 -13
- flwr/cli/constant.py +67 -0
- flwr/cli/{new/templates/app/code/flwr_tune → federation}/__init__.py +8 -1
- flwr/cli/federation/ls.py +361 -0
- flwr/cli/flower_config.py +447 -0
- flwr/cli/install.py +91 -13
- flwr/cli/log.py +65 -36
- flwr/cli/login/login.py +41 -27
- flwr/cli/ls.py +232 -158
- flwr/cli/new/new.py +188 -244
- flwr/cli/pull.py +25 -34
- flwr/cli/run/run.py +106 -74
- flwr/cli/run_utils.py +148 -0
- flwr/cli/stop.py +46 -37
- flwr/cli/supernode/__init__.py +25 -0
- flwr/cli/supernode/ls.py +273 -0
- flwr/cli/supernode/register.py +190 -0
- flwr/cli/supernode/unregister.py +140 -0
- flwr/cli/typing.py +211 -0
- flwr/cli/utils.py +428 -80
- flwr/client/__init__.py +2 -1
- flwr/client/dpfedavg_numpy_client.py +4 -1
- flwr/client/grpc_adapter_client/connection.py +14 -17
- flwr/client/grpc_rere_client/connection.py +73 -43
- flwr/client/grpc_rere_client/grpc_adapter.py +35 -15
- flwr/client/grpc_rere_client/{client_interceptor.py → node_auth_client_interceptor.py} +5 -7
- flwr/client/message_handler/message_handler.py +4 -3
- flwr/client/mod/centraldp_mods.py +1 -1
- flwr/client/mod/localdp_mod.py +1 -1
- flwr/client/mod/secure_aggregation/secaggplus_mod.py +11 -9
- flwr/client/numpy_client.py +1 -1
- flwr/client/rest_client/connection.py +99 -54
- flwr/client/run_info_store.py +6 -6
- flwr/client/typing.py +1 -1
- flwr/clientapp/__init__.py +1 -2
- flwr/{client → clientapp}/client_app.py +11 -11
- 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/__init__.py +3 -2
- flwr/common/args.py +3 -4
- flwr/common/config.py +15 -17
- flwr/common/constant.py +56 -28
- flwr/common/context.py +2 -1
- flwr/common/differential_privacy.py +3 -4
- flwr/common/event_log_plugin/event_log_plugin.py +3 -4
- flwr/common/exit/exit.py +16 -3
- 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 +8 -7
- 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 +5 -5
- 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 +8 -5
- flwr/common/serde.py +22 -11
- flwr/common/serde_utils.py +2 -2
- flwr/common/telemetry.py +10 -6
- flwr/common/typing.py +65 -44
- flwr/compat/client/app.py +45 -47
- flwr/compat/client/grpc_client/connection.py +15 -14
- flwr/compat/common/constant.py +29 -0
- flwr/compat/server/app.py +6 -7
- 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 +190 -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 +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 +175 -128
- 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 +12 -13
- 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 +12 -10
- 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 +20 -9
- 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 -50
- flwr/server/superlink/fleet/message_handler/message_handler.py +141 -51
- 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/__init__.py +2 -0
- flwr/server/superlink/linkstate/in_memory_linkstate.py +293 -208
- flwr/server/superlink/linkstate/linkstate.py +176 -64
- flwr/server/superlink/linkstate/linkstate_factory.py +24 -6
- flwr/server/superlink/linkstate/sql_linkstate.py +221 -0
- flwr/server/superlink/linkstate/sqlite_linkstate.py +743 -648
- flwr/server/superlink/linkstate/utils.py +11 -62
- 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 +7 -6
- flwr/server/workflow/secure_aggregation/secagg_workflow.py +2 -4
- flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +13 -11
- 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 +10 -11
- 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 +10 -10
- 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 +14 -19
- flwr/simulation/run_simulation.py +46 -44
- flwr/simulation/simulationio_connection.py +4 -4
- flwr/{common → supercore}/address.py +1 -37
- flwr/supercore/cli/flower_superexec.py +3 -4
- flwr/supercore/constant.py +69 -0
- flwr/supercore/corestate/corestate.py +24 -3
- flwr/supercore/corestate/in_memory_corestate.py +138 -0
- flwr/supercore/corestate/sql_corestate.py +153 -0
- flwr/supercore/corestate/sqlite_corestate.py +157 -0
- flwr/supercore/credential_store/__init__.py +33 -0
- flwr/supercore/credential_store/credential_store.py +34 -0
- flwr/supercore/credential_store/file_credential_store.py +76 -0
- flwr/{common → supercore}/date.py +0 -11
- 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/{cli/new/templates/app → supercore/primitives}/__init__.py +1 -1
- flwr/supercore/primitives/asymmetric.py +117 -0
- flwr/supercore/primitives/asymmetric_ed25519.py +175 -0
- flwr/supercore/sql_mixin.py +292 -0
- flwr/supercore/sqlite_mixin.py +156 -0
- flwr/{client/clientapp → supercore/state}/__init__.py +2 -2
- flwr/supercore/state/schema/README.md +125 -0
- flwr/{cli/new/templates → supercore/state/schema}/__init__.py +2 -2
- flwr/supercore/state/schema/corestate_tables.py +36 -0
- flwr/supercore/state/schema/linkstate_tables.py +152 -0
- flwr/supercore/state/schema/objectstore_tables.py +90 -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 +224 -0
- flwr/superlink/artifact_provider/artifact_provider.py +1 -2
- flwr/superlink/auth_plugin/__init__.py +5 -2
- flwr/superlink/auth_plugin/auth_plugin.py +20 -19
- 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 +20 -17
- flwr/superlink/servicer/control/control_license_interceptor.py +3 -3
- flwr/superlink/servicer/control/control_servicer.py +328 -68
- flwr/supernode/cli/flower_supernode.py +74 -26
- 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 +43 -24
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +48 -10
- flwr/supernode/start_client_internal.py +185 -57
- {flwr_nightly-1.23.0.dev20250930.dist-info → flwr_nightly-1.26.0.dev20260121.dist-info}/METADATA +10 -11
- flwr_nightly-1.26.0.dev20260121.dist-info/RECORD +411 -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.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/common/pyproject.py +0 -42
- flwr/supercore/object_store/utils.py +0 -43
- flwr_nightly-1.23.0.dev20250930.dist-info/RECORD +0 -429
- /flwr/{common → supercore}/version.py +0 -0
- {flwr_nightly-1.23.0.dev20250930.dist-info → flwr_nightly-1.26.0.dev20260121.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.23.0.dev20250930.dist-info → flwr_nightly-1.26.0.dev20260121.dist-info}/entry_points.txt +0 -0
|
@@ -18,14 +18,12 @@
|
|
|
18
18
|
import argparse
|
|
19
19
|
from logging import DEBUG, INFO, WARN
|
|
20
20
|
from pathlib import Path
|
|
21
|
-
from typing import Optional
|
|
22
21
|
|
|
22
|
+
import yaml
|
|
23
23
|
from cryptography.exceptions import UnsupportedAlgorithm
|
|
24
|
-
from cryptography.hazmat.primitives.asymmetric import ec
|
|
25
|
-
from cryptography.hazmat.primitives.serialization import
|
|
26
|
-
|
|
27
|
-
load_ssh_public_key,
|
|
28
|
-
)
|
|
24
|
+
from cryptography.hazmat.primitives.asymmetric import ec, ed25519
|
|
25
|
+
from cryptography.hazmat.primitives.serialization import load_ssh_private_key
|
|
26
|
+
from cryptography.hazmat.primitives.serialization.ssh import load_ssh_public_key
|
|
29
27
|
|
|
30
28
|
from flwr.common import EventType, event
|
|
31
29
|
from flwr.common.args import try_obtain_root_certificates
|
|
@@ -61,9 +59,19 @@ def flower_supernode() -> None:
|
|
|
61
59
|
"Ignoring `--flwr-dir`.",
|
|
62
60
|
)
|
|
63
61
|
|
|
62
|
+
trusted_entities = _try_obtain_trusted_entities(args.trusted_entities)
|
|
63
|
+
if trusted_entities:
|
|
64
|
+
_validate_public_keys_ed25519(trusted_entities)
|
|
64
65
|
root_certificates = try_obtain_root_certificates(args, args.superlink)
|
|
65
66
|
authentication_keys = _try_setup_client_authentication(args)
|
|
66
67
|
|
|
68
|
+
# Warn if authentication keys are provided but transport is not grpc-rere
|
|
69
|
+
if authentication_keys is not None and args.transport != TRANSPORT_TYPE_GRPC_RERE:
|
|
70
|
+
log(
|
|
71
|
+
WARN,
|
|
72
|
+
"SuperNode Authentication is only supported with the grpc-rere transport.",
|
|
73
|
+
)
|
|
74
|
+
|
|
67
75
|
log(DEBUG, "Isolation mode: %s", args.isolation)
|
|
68
76
|
|
|
69
77
|
start_client_internal(
|
|
@@ -81,6 +89,7 @@ def flower_supernode() -> None:
|
|
|
81
89
|
isolation=args.isolation,
|
|
82
90
|
clientappio_api_address=args.clientappio_api_address,
|
|
83
91
|
health_server_address=args.health_server_address,
|
|
92
|
+
trusted_entities=trusted_entities,
|
|
84
93
|
)
|
|
85
94
|
|
|
86
95
|
|
|
@@ -120,6 +129,18 @@ def _parse_args_run_supernode() -> argparse.ArgumentParser:
|
|
|
120
129
|
help="ClientAppIo API (gRPC) server address (IPv4, IPv6, or a domain name). "
|
|
121
130
|
f"By default, it is set to {CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS}.",
|
|
122
131
|
)
|
|
132
|
+
parser.add_argument(
|
|
133
|
+
"--trusted-entities",
|
|
134
|
+
type=Path,
|
|
135
|
+
default=None,
|
|
136
|
+
metavar="YAML_FILE",
|
|
137
|
+
help=(
|
|
138
|
+
"Path to a YAML file defining trusted entities. "
|
|
139
|
+
"The file must map public key IDs to public keys. "
|
|
140
|
+
"Example: { fpk_UUID1: 'ssh-ed25519 <key1> [comment1]', "
|
|
141
|
+
"fpk_UUID2: 'ssh-ed25519 <key2> [comment2]' }"
|
|
142
|
+
),
|
|
143
|
+
)
|
|
123
144
|
add_args_health(parser)
|
|
124
145
|
|
|
125
146
|
return parser
|
|
@@ -188,12 +209,12 @@ def _parse_args_common(parser: argparse.ArgumentParser) -> None:
|
|
|
188
209
|
parser.add_argument(
|
|
189
210
|
"--auth-supernode-private-key",
|
|
190
211
|
type=str,
|
|
191
|
-
help="
|
|
212
|
+
help="Path to the SuperNode's private key to enable authentication.",
|
|
192
213
|
)
|
|
193
214
|
parser.add_argument(
|
|
194
215
|
"--auth-supernode-public-key",
|
|
195
216
|
type=str,
|
|
196
|
-
help="
|
|
217
|
+
help="This argument is deprecated and will be removed in a future release.",
|
|
197
218
|
)
|
|
198
219
|
parser.add_argument(
|
|
199
220
|
"--node-config",
|
|
@@ -206,13 +227,10 @@ def _parse_args_common(parser: argparse.ArgumentParser) -> None:
|
|
|
206
227
|
|
|
207
228
|
def _try_setup_client_authentication(
|
|
208
229
|
args: argparse.Namespace,
|
|
209
|
-
) ->
|
|
210
|
-
if not args.auth_supernode_private_key
|
|
230
|
+
) -> tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey] | None:
|
|
231
|
+
if not args.auth_supernode_private_key:
|
|
211
232
|
return None
|
|
212
233
|
|
|
213
|
-
if not args.auth_supernode_private_key or not args.auth_supernode_public_key:
|
|
214
|
-
flwr_exit(ExitCode.SUPERNODE_NODE_AUTH_KEYS_REQUIRED)
|
|
215
|
-
|
|
216
234
|
try:
|
|
217
235
|
ssh_private_key = load_ssh_private_key(
|
|
218
236
|
Path(args.auth_supernode_private_key).read_bytes(),
|
|
@@ -222,23 +240,53 @@ def _try_setup_client_authentication(
|
|
|
222
240
|
raise ValueError()
|
|
223
241
|
except (ValueError, UnsupportedAlgorithm):
|
|
224
242
|
flwr_exit(
|
|
225
|
-
ExitCode.
|
|
243
|
+
ExitCode.SUPERNODE_NODE_AUTH_KEY_INVALID,
|
|
226
244
|
"Unable to parse the private key file.",
|
|
227
245
|
)
|
|
228
246
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
247
|
+
if args.auth_supernode_public_key:
|
|
248
|
+
log(
|
|
249
|
+
WARN,
|
|
250
|
+
"The `--auth-supernode-public-key` flag is deprecated and will be "
|
|
251
|
+
"removed in a future release. The public key is now derived from the "
|
|
252
|
+
"private key provided by `--auth-supernode-private-key`.",
|
|
232
253
|
)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
254
|
+
return ssh_private_key, ssh_private_key.public_key()
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _try_obtain_trusted_entities(
|
|
258
|
+
trusted_entities_path: Path | None,
|
|
259
|
+
) -> dict[str, str] | None:
|
|
260
|
+
"""Validate and return the trust entities."""
|
|
261
|
+
if not trusted_entities_path:
|
|
262
|
+
return None
|
|
263
|
+
if not trusted_entities_path.is_file():
|
|
236
264
|
flwr_exit(
|
|
237
|
-
ExitCode.
|
|
238
|
-
"
|
|
265
|
+
ExitCode.SUPERNODE_INVALID_TRUSTED_ENTITIES,
|
|
266
|
+
"Path argument `--trusted-entities` does not point to a file.",
|
|
239
267
|
)
|
|
268
|
+
try:
|
|
269
|
+
with trusted_entities_path.open("r", encoding="utf-8") as f:
|
|
270
|
+
trusted_entities = yaml.safe_load(f)
|
|
271
|
+
if not isinstance(trusted_entities, dict):
|
|
272
|
+
raise ValueError("Invalid trusted entities format.")
|
|
273
|
+
except (yaml.YAMLError, ValueError) as e:
|
|
274
|
+
flwr_exit(
|
|
275
|
+
ExitCode.SUPERNODE_INVALID_TRUSTED_ENTITIES,
|
|
276
|
+
f"Failed to read YAML file '{trusted_entities_path}': {e}",
|
|
277
|
+
)
|
|
278
|
+
return trusted_entities
|
|
240
279
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
)
|
|
280
|
+
|
|
281
|
+
def _validate_public_keys_ed25519(trusted_entities: dict[str, str]) -> None:
|
|
282
|
+
"""Validate public keys for the trust entities are Ed25519."""
|
|
283
|
+
for public_key_id in trusted_entities.keys():
|
|
284
|
+
verifier_public_key = load_ssh_public_key(
|
|
285
|
+
trusted_entities[public_key_id].encode("utf-8")
|
|
286
|
+
)
|
|
287
|
+
if not isinstance(verifier_public_key, ed25519.Ed25519PublicKey):
|
|
288
|
+
flwr_exit(
|
|
289
|
+
ExitCode.SUPERNODE_INVALID_TRUSTED_ENTITIES,
|
|
290
|
+
"The provided public key associated with "
|
|
291
|
+
f"trusted entity {public_key_id} is not Ed25519.",
|
|
292
|
+
)
|
|
@@ -15,18 +15,28 @@
|
|
|
15
15
|
"""In-memory NodeState implementation."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
import secrets
|
|
19
18
|
from collections.abc import Sequence
|
|
20
19
|
from dataclasses import dataclass
|
|
21
|
-
from threading import Lock
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
from flwr.common import
|
|
25
|
-
from flwr.common.
|
|
20
|
+
from threading import Lock, RLock
|
|
21
|
+
|
|
22
|
+
from flwr.common import Context, Error, Message, now
|
|
23
|
+
from flwr.common.constant import ErrorCode
|
|
24
|
+
from flwr.common.inflatable import (
|
|
25
|
+
get_all_nested_objects,
|
|
26
|
+
get_object_tree,
|
|
27
|
+
no_object_id_recompute,
|
|
28
|
+
)
|
|
26
29
|
from flwr.common.typing import Run
|
|
30
|
+
from flwr.supercore.constant import MESSAGE_TIME_ENTRY_MAX_AGE_SECONDS
|
|
31
|
+
from flwr.supercore.corestate.in_memory_corestate import InMemoryCoreState
|
|
32
|
+
from flwr.supercore.object_store import ObjectStore
|
|
27
33
|
|
|
28
34
|
from .nodestate import NodeState
|
|
29
35
|
|
|
36
|
+
CLIENT_APP_CRASHED_ERROR = Error(
|
|
37
|
+
ErrorCode.CLIENT_APP_CRASHED, "ClientApp stopped responding."
|
|
38
|
+
)
|
|
39
|
+
|
|
30
40
|
|
|
31
41
|
@dataclass
|
|
32
42
|
class MessageEntry:
|
|
@@ -36,27 +46,37 @@ class MessageEntry:
|
|
|
36
46
|
is_retrieved: bool = False
|
|
37
47
|
|
|
38
48
|
|
|
39
|
-
|
|
49
|
+
@dataclass
|
|
50
|
+
class TimeEntry:
|
|
51
|
+
"""Data class to represent a time entry."""
|
|
52
|
+
|
|
53
|
+
starting_at: float
|
|
54
|
+
finished_at: float | None = None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class InMemoryNodeState(
|
|
58
|
+
NodeState, InMemoryCoreState
|
|
59
|
+
): # pylint: disable=too-many-instance-attributes
|
|
40
60
|
"""In-memory NodeState implementation."""
|
|
41
61
|
|
|
42
|
-
def __init__(self) -> None:
|
|
62
|
+
def __init__(self, object_store: ObjectStore) -> None:
|
|
63
|
+
super().__init__(object_store)
|
|
43
64
|
# Store node_id
|
|
44
|
-
self.node_id:
|
|
65
|
+
self.node_id: int | None = None
|
|
45
66
|
# Store Object ID to MessageEntry mapping
|
|
46
67
|
self.msg_store: dict[str, MessageEntry] = {}
|
|
47
|
-
self.lock_msg_store =
|
|
68
|
+
self.lock_msg_store = RLock()
|
|
48
69
|
# Store run ID to Run mapping
|
|
49
70
|
self.run_store: dict[int, Run] = {}
|
|
50
71
|
self.lock_run_store = Lock()
|
|
51
72
|
# Store run ID to Context mapping
|
|
52
73
|
self.ctx_store: dict[int, Context] = {}
|
|
53
74
|
self.lock_ctx_store = Lock()
|
|
54
|
-
# Store
|
|
55
|
-
self.
|
|
56
|
-
self.
|
|
57
|
-
self.lock_token_store = Lock()
|
|
75
|
+
# Store msg ID to TimeEntry mapping
|
|
76
|
+
self.time_store: dict[str, TimeEntry] = {}
|
|
77
|
+
self.lock_time_store = Lock()
|
|
58
78
|
|
|
59
|
-
def set_node_id(self, node_id:
|
|
79
|
+
def set_node_id(self, node_id: int | None) -> None:
|
|
60
80
|
"""Set the node ID."""
|
|
61
81
|
self.node_id = node_id
|
|
62
82
|
|
|
@@ -66,8 +86,10 @@ class InMemoryNodeState(NodeState): # pylint: disable=too-many-instance-attribu
|
|
|
66
86
|
raise ValueError("Node ID not set")
|
|
67
87
|
return self.node_id
|
|
68
88
|
|
|
69
|
-
def store_message(self, message: Message) ->
|
|
89
|
+
def store_message(self, message: Message) -> str | None:
|
|
70
90
|
"""Store a message."""
|
|
91
|
+
# No need to check for expired tokens here
|
|
92
|
+
# The ClientAppIo servicer will first verify the token before storing messages
|
|
71
93
|
with self.lock_msg_store:
|
|
72
94
|
msg_id = message.metadata.message_id
|
|
73
95
|
if msg_id == "" or msg_id in self.msg_store:
|
|
@@ -78,13 +100,14 @@ class InMemoryNodeState(NodeState): # pylint: disable=too-many-instance-attribu
|
|
|
78
100
|
def get_messages(
|
|
79
101
|
self,
|
|
80
102
|
*,
|
|
81
|
-
run_ids:
|
|
82
|
-
is_reply:
|
|
83
|
-
limit:
|
|
103
|
+
run_ids: Sequence[int] | None = None,
|
|
104
|
+
is_reply: bool | None = None,
|
|
105
|
+
limit: int | None = None,
|
|
84
106
|
) -> Sequence[Message]:
|
|
85
107
|
"""Retrieve messages based on the specified filters."""
|
|
86
|
-
|
|
108
|
+
self._cleanup_expired_tokens()
|
|
87
109
|
|
|
110
|
+
selected_messages: list[Message] = []
|
|
88
111
|
with self.lock_msg_store:
|
|
89
112
|
# Iterate through all messages in the store
|
|
90
113
|
for object_id in list(self.msg_store.keys()):
|
|
@@ -122,7 +145,7 @@ class InMemoryNodeState(NodeState): # pylint: disable=too-many-instance-attribu
|
|
|
122
145
|
def delete_messages(
|
|
123
146
|
self,
|
|
124
147
|
*,
|
|
125
|
-
message_ids:
|
|
148
|
+
message_ids: Sequence[str] | None = None,
|
|
126
149
|
) -> None:
|
|
127
150
|
"""Delete messages based on the specified filters."""
|
|
128
151
|
with self.lock_msg_store:
|
|
@@ -140,7 +163,7 @@ class InMemoryNodeState(NodeState): # pylint: disable=too-many-instance-attribu
|
|
|
140
163
|
with self.lock_run_store:
|
|
141
164
|
self.run_store[run.run_id] = run
|
|
142
165
|
|
|
143
|
-
def get_run(self, run_id: int) ->
|
|
166
|
+
def get_run(self, run_id: int) -> Run | None:
|
|
144
167
|
"""Retrieve a run by its ID."""
|
|
145
168
|
with self.lock_run_store:
|
|
146
169
|
return self.run_store.get(run_id)
|
|
@@ -150,7 +173,7 @@ class InMemoryNodeState(NodeState): # pylint: disable=too-many-instance-attribu
|
|
|
150
173
|
with self.lock_ctx_store:
|
|
151
174
|
self.ctx_store[context.run_id] = context
|
|
152
175
|
|
|
153
|
-
def get_context(self, run_id: int) ->
|
|
176
|
+
def get_context(self, run_id: int) -> Context | None:
|
|
154
177
|
"""Retrieve a context by its run ID."""
|
|
155
178
|
with self.lock_ctx_store:
|
|
156
179
|
return self.ctx_store.get(run_id)
|
|
@@ -171,29 +194,78 @@ class InMemoryNodeState(NodeState): # pylint: disable=too-many-instance-attribu
|
|
|
171
194
|
ret -= set(self.token_store.keys())
|
|
172
195
|
return list(ret)
|
|
173
196
|
|
|
174
|
-
def
|
|
175
|
-
"""
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
self.
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
197
|
+
def _on_tokens_expired(self, expired_records: list[tuple[int, float]]) -> None:
|
|
198
|
+
"""Insert error replies for messages associated with expired tokens."""
|
|
199
|
+
with self.lock_msg_store:
|
|
200
|
+
# Find all retrieved messages associated with expired run IDs
|
|
201
|
+
expired_run_ids = {run_id for run_id, _ in expired_records}
|
|
202
|
+
messages_to_reply: list[Message] = []
|
|
203
|
+
for entry in self.msg_store.values():
|
|
204
|
+
msg = entry.message
|
|
205
|
+
if msg.metadata.run_id in expired_run_ids and entry.is_retrieved:
|
|
206
|
+
messages_to_reply.append(msg)
|
|
207
|
+
|
|
208
|
+
# Create and store error replies for each message
|
|
209
|
+
for msg in messages_to_reply:
|
|
210
|
+
error_reply = Message(CLIENT_APP_CRASHED_ERROR, reply_to=msg)
|
|
211
|
+
|
|
212
|
+
# Insert objects of the error reply into the object store
|
|
213
|
+
with no_object_id_recompute():
|
|
214
|
+
# pylint: disable-next=W0212
|
|
215
|
+
error_reply.metadata._message_id = error_reply.object_id # type: ignore
|
|
216
|
+
object_tree = get_object_tree(error_reply)
|
|
217
|
+
self.object_store.preregister(msg.metadata.run_id, object_tree)
|
|
218
|
+
for obj_id, obj in get_all_nested_objects(error_reply).items():
|
|
219
|
+
self.object_store.put(obj_id, obj.deflate())
|
|
220
|
+
|
|
221
|
+
# Store the error reply message
|
|
222
|
+
self.store_message(error_reply)
|
|
223
|
+
|
|
224
|
+
def record_message_processing_start(self, message_id: str) -> None:
|
|
225
|
+
"""Record the start time of message processing based on the message ID."""
|
|
226
|
+
with self.lock_time_store:
|
|
227
|
+
self.time_store[message_id] = TimeEntry(starting_at=now().timestamp())
|
|
228
|
+
|
|
229
|
+
def record_message_processing_end(self, message_id: str) -> None:
|
|
230
|
+
"""Record the end time of message processing based on the message ID."""
|
|
231
|
+
with self.lock_time_store:
|
|
232
|
+
if message_id not in self.time_store:
|
|
233
|
+
raise ValueError(
|
|
234
|
+
f"Cannot record end time: Message ID {message_id} not found."
|
|
235
|
+
)
|
|
236
|
+
entry = self.time_store[message_id]
|
|
237
|
+
entry.finished_at = now().timestamp()
|
|
238
|
+
|
|
239
|
+
def get_message_processing_duration(self, message_id: str) -> float:
|
|
240
|
+
"""Get the message processing duration based on the message ID."""
|
|
241
|
+
# Cleanup old message processing times
|
|
242
|
+
self._cleanup_old_message_times()
|
|
243
|
+
with self.lock_time_store:
|
|
244
|
+
if message_id not in self.time_store:
|
|
245
|
+
raise ValueError(f"Message ID {message_id} not found.")
|
|
246
|
+
|
|
247
|
+
entry = self.time_store[message_id]
|
|
248
|
+
if entry.starting_at is None or entry.finished_at is None:
|
|
249
|
+
raise ValueError(
|
|
250
|
+
f"Start time or end time for message ID {message_id} is missing."
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
duration = entry.finished_at - entry.starting_at
|
|
254
|
+
return duration
|
|
255
|
+
|
|
256
|
+
def _cleanup_old_message_times(self) -> None:
|
|
257
|
+
"""Remove time entries older than MESSAGE_TIME_ENTRY_MAX_AGE_SECONDS."""
|
|
258
|
+
with self.lock_time_store:
|
|
259
|
+
cutoff = now().timestamp() - MESSAGE_TIME_ENTRY_MAX_AGE_SECONDS
|
|
260
|
+
# Find message IDs for entries that have a finishing_at time
|
|
261
|
+
# before the cutoff, and those that don't exist in msg_store
|
|
262
|
+
to_delete = [
|
|
263
|
+
msg_id
|
|
264
|
+
for msg_id, entry in self.time_store.items()
|
|
265
|
+
if (entry.finished_at and entry.finished_at < cutoff)
|
|
266
|
+
or msg_id not in self.msg_store
|
|
267
|
+
]
|
|
268
|
+
|
|
269
|
+
# Delete the identified entries
|
|
270
|
+
for msg_id in to_delete:
|
|
271
|
+
del self.time_store[msg_id]
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
|
|
18
18
|
from abc import abstractmethod
|
|
19
19
|
from collections.abc import Sequence
|
|
20
|
-
from typing import Optional
|
|
21
20
|
|
|
22
21
|
from flwr.common import Context, Message
|
|
23
22
|
from flwr.common.typing import Run
|
|
@@ -36,7 +35,7 @@ class NodeState(CoreState):
|
|
|
36
35
|
"""Get the node ID."""
|
|
37
36
|
|
|
38
37
|
@abstractmethod
|
|
39
|
-
def store_message(self, message: Message) ->
|
|
38
|
+
def store_message(self, message: Message) -> str | None:
|
|
40
39
|
"""Store a message.
|
|
41
40
|
|
|
42
41
|
Parameters
|
|
@@ -54,9 +53,9 @@ class NodeState(CoreState):
|
|
|
54
53
|
def get_messages(
|
|
55
54
|
self,
|
|
56
55
|
*,
|
|
57
|
-
run_ids:
|
|
58
|
-
is_reply:
|
|
59
|
-
limit:
|
|
56
|
+
run_ids: Sequence[int] | None = None,
|
|
57
|
+
is_reply: bool | None = None,
|
|
58
|
+
limit: int | None = None,
|
|
60
59
|
) -> Sequence[Message]:
|
|
61
60
|
"""Retrieve messages based on the specified filters.
|
|
62
61
|
|
|
@@ -89,7 +88,7 @@ class NodeState(CoreState):
|
|
|
89
88
|
def delete_messages(
|
|
90
89
|
self,
|
|
91
90
|
*,
|
|
92
|
-
message_ids:
|
|
91
|
+
message_ids: Sequence[str] | None = None,
|
|
93
92
|
) -> None:
|
|
94
93
|
"""Delete messages based on the specified filters.
|
|
95
94
|
|
|
@@ -118,7 +117,7 @@ class NodeState(CoreState):
|
|
|
118
117
|
"""
|
|
119
118
|
|
|
120
119
|
@abstractmethod
|
|
121
|
-
def get_run(self, run_id: int) ->
|
|
120
|
+
def get_run(self, run_id: int) -> Run | None:
|
|
122
121
|
"""Retrieve a run by its ID.
|
|
123
122
|
|
|
124
123
|
Parameters
|
|
@@ -143,7 +142,7 @@ class NodeState(CoreState):
|
|
|
143
142
|
"""
|
|
144
143
|
|
|
145
144
|
@abstractmethod
|
|
146
|
-
def get_context(self, run_id: int) ->
|
|
145
|
+
def get_context(self, run_id: int) -> Context | None:
|
|
147
146
|
"""Retrieve a context by its run ID.
|
|
148
147
|
|
|
149
148
|
Parameters
|
|
@@ -169,3 +168,48 @@ class NodeState(CoreState):
|
|
|
169
168
|
Sequence[int]
|
|
170
169
|
Sequence of run IDs with pending messages.
|
|
171
170
|
"""
|
|
171
|
+
|
|
172
|
+
@abstractmethod
|
|
173
|
+
def record_message_processing_start(self, message_id: str) -> None:
|
|
174
|
+
"""Record the start time of message processing based on the message ID.
|
|
175
|
+
|
|
176
|
+
Parameters
|
|
177
|
+
----------
|
|
178
|
+
message_id : str
|
|
179
|
+
The ID of the message associated with the start time.
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
@abstractmethod
|
|
183
|
+
def record_message_processing_end(self, message_id: str) -> None:
|
|
184
|
+
"""Record the end time of message processing based on the message ID.
|
|
185
|
+
|
|
186
|
+
Parameters
|
|
187
|
+
----------
|
|
188
|
+
message_id : str
|
|
189
|
+
The ID of the message associated with the end time.
|
|
190
|
+
|
|
191
|
+
Raises
|
|
192
|
+
------
|
|
193
|
+
ValueError
|
|
194
|
+
If the message ID is not found.
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
@abstractmethod
|
|
198
|
+
def get_message_processing_duration(self, message_id: str) -> float:
|
|
199
|
+
"""Get the message processing duration based on the message ID.
|
|
200
|
+
|
|
201
|
+
Parameters
|
|
202
|
+
----------
|
|
203
|
+
message_id : str
|
|
204
|
+
The ID of the message.
|
|
205
|
+
|
|
206
|
+
Returns
|
|
207
|
+
-------
|
|
208
|
+
float
|
|
209
|
+
The processing duration in seconds.
|
|
210
|
+
|
|
211
|
+
Raises
|
|
212
|
+
------
|
|
213
|
+
ValueError
|
|
214
|
+
If the message ID is not found, or if start/end times are missing.
|
|
215
|
+
"""
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
import threading
|
|
19
|
-
|
|
19
|
+
|
|
20
|
+
from flwr.supercore.object_store import ObjectStoreFactory
|
|
20
21
|
|
|
21
22
|
from .in_memory_nodestate import InMemoryNodeState
|
|
22
23
|
from .nodestate import NodeState
|
|
@@ -25,8 +26,9 @@ from .nodestate import NodeState
|
|
|
25
26
|
class NodeStateFactory:
|
|
26
27
|
"""Factory class that creates NodeState instances."""
|
|
27
28
|
|
|
28
|
-
def __init__(self) -> None:
|
|
29
|
-
self.
|
|
29
|
+
def __init__(self, objectstore_factory: ObjectStoreFactory) -> None:
|
|
30
|
+
self.objectstore_factory = objectstore_factory
|
|
31
|
+
self.state_instance: NodeState | None = None
|
|
30
32
|
self.lock = threading.RLock()
|
|
31
33
|
|
|
32
34
|
def state(self) -> NodeState:
|
|
@@ -34,5 +36,6 @@ class NodeStateFactory:
|
|
|
34
36
|
# Lock access to NodeStateFactory to prevent returning different instances
|
|
35
37
|
with self.lock:
|
|
36
38
|
if self.state_instance is None:
|
|
37
|
-
|
|
39
|
+
object_store = self.objectstore_factory.store()
|
|
40
|
+
self.state_instance = InMemoryNodeState(object_store)
|
|
38
41
|
return self.state_instance
|
|
@@ -15,19 +15,18 @@
|
|
|
15
15
|
"""Flower ClientApp process."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
import gc
|
|
19
18
|
from logging import DEBUG, ERROR, INFO
|
|
20
|
-
from typing import Optional
|
|
21
19
|
|
|
22
20
|
import grpc
|
|
23
21
|
|
|
24
22
|
from flwr.app.error import Error
|
|
25
23
|
from flwr.cli.install import install_from_fab
|
|
26
|
-
from flwr.
|
|
27
|
-
from flwr.
|
|
24
|
+
from flwr.clientapp.client_app import ClientApp, LoadClientAppError
|
|
25
|
+
from flwr.clientapp.utils import get_load_client_app_fn
|
|
28
26
|
from flwr.common import Context, Message
|
|
29
27
|
from flwr.common.config import get_flwr_dir
|
|
30
28
|
from flwr.common.constant import ErrorCode
|
|
29
|
+
from flwr.common.exit import ExitCode, flwr_exit, register_signal_handlers
|
|
31
30
|
from flwr.common.grpc import create_channel, on_channel_state_change
|
|
32
31
|
from flwr.common.inflatable import (
|
|
33
32
|
get_all_nested_objects,
|
|
@@ -50,6 +49,7 @@ from flwr.common.serde import (
|
|
|
50
49
|
message_to_proto,
|
|
51
50
|
run_from_proto,
|
|
52
51
|
)
|
|
52
|
+
from flwr.common.telemetry import EventType, event
|
|
53
53
|
from flwr.common.typing import Fab, Run
|
|
54
54
|
from flwr.proto.appio_pb2 import ( # pylint: disable=E0611
|
|
55
55
|
PullAppInputsRequest,
|
|
@@ -63,27 +63,41 @@ from flwr.proto.appio_pb2 import ( # pylint: disable=E0611
|
|
|
63
63
|
from flwr.proto.clientappio_pb2_grpc import ClientAppIoStub
|
|
64
64
|
from flwr.proto.node_pb2 import Node # pylint: disable=E0611
|
|
65
65
|
from flwr.supercore.app_utils import start_parent_process_monitor
|
|
66
|
+
from flwr.supercore.heartbeat import HeartbeatSender, make_app_heartbeat_fn_grpc
|
|
66
67
|
from flwr.supercore.utils import mask_string
|
|
67
68
|
|
|
68
69
|
|
|
69
70
|
def run_clientapp( # pylint: disable=R0913, R0914, R0917
|
|
70
71
|
clientappio_api_address: str,
|
|
71
72
|
token: str,
|
|
72
|
-
flwr_dir:
|
|
73
|
-
certificates:
|
|
74
|
-
parent_pid:
|
|
73
|
+
flwr_dir: str | None = None,
|
|
74
|
+
certificates: bytes | None = None,
|
|
75
|
+
parent_pid: int | None = None,
|
|
75
76
|
) -> None:
|
|
76
77
|
"""Run Flower ClientApp process."""
|
|
77
78
|
# Monitor the main process in case of SIGKILL
|
|
78
79
|
if parent_pid is not None:
|
|
79
80
|
start_parent_process_monitor(parent_pid)
|
|
80
81
|
|
|
82
|
+
event(EventType.FLWR_CLIENTAPP_RUN_ENTER)
|
|
83
|
+
|
|
81
84
|
channel = create_channel(
|
|
82
85
|
server_address=clientappio_api_address,
|
|
83
86
|
insecure=(certificates is None),
|
|
84
87
|
root_certificates=certificates,
|
|
85
88
|
)
|
|
86
89
|
channel.subscribe(on_channel_state_change)
|
|
90
|
+
heartbeat_sender = None
|
|
91
|
+
|
|
92
|
+
def on_exit() -> None:
|
|
93
|
+
if heartbeat_sender is not None and heartbeat_sender.is_running:
|
|
94
|
+
heartbeat_sender.stop()
|
|
95
|
+
channel.close()
|
|
96
|
+
|
|
97
|
+
register_signal_handlers(
|
|
98
|
+
event_type=EventType.FLWR_CLIENTAPP_RUN_LEAVE,
|
|
99
|
+
exit_handlers=[on_exit],
|
|
100
|
+
)
|
|
87
101
|
|
|
88
102
|
# Resolve directory where FABs are installed
|
|
89
103
|
flwr_dir_ = get_flwr_dir(flwr_dir)
|
|
@@ -91,22 +105,27 @@ def run_clientapp( # pylint: disable=R0913, R0914, R0917
|
|
|
91
105
|
stub = ClientAppIoStub(channel)
|
|
92
106
|
_wrap_stub(stub, _make_simple_grpc_retry_invoker())
|
|
93
107
|
|
|
108
|
+
# Start app heartbeat
|
|
109
|
+
heartbeat_sender = HeartbeatSender(make_app_heartbeat_fn_grpc(stub, token))
|
|
110
|
+
heartbeat_sender.start()
|
|
111
|
+
|
|
94
112
|
# Pull Message, Context, Run and (optional) FAB from SuperNode
|
|
95
113
|
message, context, run, fab = pull_clientappinputs(stub=stub, token=token)
|
|
96
114
|
|
|
97
|
-
|
|
98
|
-
if fab:
|
|
99
|
-
log(DEBUG, "[flwr-clientapp] Start FAB installation.")
|
|
100
|
-
install_from_fab(fab.content, flwr_dir=flwr_dir_, skip_prompt=True)
|
|
115
|
+
try:
|
|
101
116
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
117
|
+
# Install FAB, if provided
|
|
118
|
+
if fab:
|
|
119
|
+
log(DEBUG, "[flwr-clientapp] Start FAB installation.")
|
|
120
|
+
install_from_fab(fab.content, flwr_dir=flwr_dir_, skip_prompt=True)
|
|
121
|
+
|
|
122
|
+
load_client_app_fn = get_load_client_app_fn(
|
|
123
|
+
default_app_ref="",
|
|
124
|
+
app_path=None,
|
|
125
|
+
multi_app=True,
|
|
126
|
+
flwr_dir=str(flwr_dir_),
|
|
127
|
+
)
|
|
108
128
|
|
|
109
|
-
try:
|
|
110
129
|
# Load ClientApp
|
|
111
130
|
log(DEBUG, "[flwr-clientapp] Start `ClientApp` Loading.")
|
|
112
131
|
client_app: ClientApp = load_client_app_fn(
|
|
@@ -137,18 +156,18 @@ def run_clientapp( # pylint: disable=R0913, R0914, R0917
|
|
|
137
156
|
stub=stub, token=token, message=reply_message, context=context
|
|
138
157
|
)
|
|
139
158
|
|
|
140
|
-
del client_app, message, context, run, fab, reply_message
|
|
141
|
-
gc.collect()
|
|
142
|
-
|
|
143
159
|
except grpc.RpcError as e:
|
|
144
160
|
log(ERROR, "GRPC error occurred: %s", str(e))
|
|
145
|
-
|
|
146
|
-
|
|
161
|
+
|
|
162
|
+
flwr_exit(
|
|
163
|
+
code=ExitCode.SUCCESS,
|
|
164
|
+
event_type=EventType.FLWR_CLIENTAPP_RUN_LEAVE,
|
|
165
|
+
)
|
|
147
166
|
|
|
148
167
|
|
|
149
168
|
def pull_clientappinputs(
|
|
150
169
|
stub: ClientAppIoStub, token: str
|
|
151
|
-
) -> tuple[Message, Context, Run,
|
|
170
|
+
) -> tuple[Message, Context, Run, Fab | None]:
|
|
152
171
|
"""Pull ClientAppInputs from SuperNode."""
|
|
153
172
|
masked_token = mask_string(token)
|
|
154
173
|
log(INFO, "[flwr-clientapp] Pull `ClientAppInputs` for token %s", masked_token)
|