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
|
@@ -16,22 +16,30 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
import hashlib
|
|
19
|
+
import json
|
|
19
20
|
import time
|
|
20
|
-
from collections.abc import Generator
|
|
21
|
+
from collections.abc import Generator, Sequence
|
|
21
22
|
from logging import ERROR, INFO
|
|
22
|
-
from typing import Any,
|
|
23
|
+
from typing import Any, cast
|
|
23
24
|
|
|
24
25
|
import grpc
|
|
26
|
+
import requests
|
|
25
27
|
|
|
26
28
|
from flwr.cli.config_utils import get_fab_metadata
|
|
27
29
|
from flwr.common import Context, RecordDict, now
|
|
28
30
|
from flwr.common.constant import (
|
|
29
31
|
FAB_MAX_SIZE,
|
|
32
|
+
HEARTBEAT_DEFAULT_INTERVAL,
|
|
30
33
|
LOG_STREAM_INTERVAL,
|
|
34
|
+
NO_ACCOUNT_AUTH_MESSAGE,
|
|
31
35
|
NO_ARTIFACT_PROVIDER_MESSAGE,
|
|
32
|
-
|
|
36
|
+
NODE_NOT_FOUND_MESSAGE,
|
|
37
|
+
PUBLIC_KEY_ALREADY_IN_USE_MESSAGE,
|
|
38
|
+
PUBLIC_KEY_NOT_VALID,
|
|
33
39
|
PULL_UNFINISHED_RUN_MESSAGE,
|
|
34
40
|
RUN_ID_NOT_FOUND_MESSAGE,
|
|
41
|
+
SUPERLINK_NODE_ID,
|
|
42
|
+
TRANSPORT_TYPE_GRPC_ADAPTER,
|
|
35
43
|
Status,
|
|
36
44
|
SubStatus,
|
|
37
45
|
)
|
|
@@ -48,24 +56,39 @@ from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
|
|
48
56
|
GetAuthTokensResponse,
|
|
49
57
|
GetLoginDetailsRequest,
|
|
50
58
|
GetLoginDetailsResponse,
|
|
59
|
+
ListFederationsRequest,
|
|
60
|
+
ListFederationsResponse,
|
|
61
|
+
ListNodesRequest,
|
|
62
|
+
ListNodesResponse,
|
|
51
63
|
ListRunsRequest,
|
|
52
64
|
ListRunsResponse,
|
|
53
65
|
PullArtifactsRequest,
|
|
54
66
|
PullArtifactsResponse,
|
|
67
|
+
RegisterNodeRequest,
|
|
68
|
+
RegisterNodeResponse,
|
|
69
|
+
ShowFederationRequest,
|
|
70
|
+
ShowFederationResponse,
|
|
55
71
|
StartRunRequest,
|
|
56
72
|
StartRunResponse,
|
|
57
73
|
StopRunRequest,
|
|
58
74
|
StopRunResponse,
|
|
59
75
|
StreamLogsRequest,
|
|
60
76
|
StreamLogsResponse,
|
|
77
|
+
UnregisterNodeRequest,
|
|
78
|
+
UnregisterNodeResponse,
|
|
61
79
|
)
|
|
80
|
+
from flwr.proto.federation_pb2 import Federation # pylint: disable=E0611
|
|
81
|
+
from flwr.proto.node_pb2 import NodeInfo # pylint: disable=E0611
|
|
62
82
|
from flwr.server.superlink.linkstate import LinkState, LinkStateFactory
|
|
83
|
+
from flwr.supercore.constant import PLATFORM_API_URL
|
|
63
84
|
from flwr.supercore.ffs import FfsFactory
|
|
64
85
|
from flwr.supercore.object_store import ObjectStore, ObjectStoreFactory
|
|
86
|
+
from flwr.supercore.primitives.asymmetric import bytes_to_public_key, uses_nist_ec_curve
|
|
87
|
+
from flwr.supercore.utils import parse_app_spec, request_download_link
|
|
65
88
|
from flwr.superlink.artifact_provider import ArtifactProvider
|
|
66
|
-
from flwr.superlink.auth_plugin import
|
|
89
|
+
from flwr.superlink.auth_plugin import ControlAuthnPlugin
|
|
67
90
|
|
|
68
|
-
from .
|
|
91
|
+
from .control_account_auth_interceptor import get_current_account_info
|
|
69
92
|
|
|
70
93
|
|
|
71
94
|
class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
@@ -77,17 +100,19 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
77
100
|
ffs_factory: FfsFactory,
|
|
78
101
|
objectstore_factory: ObjectStoreFactory,
|
|
79
102
|
is_simulation: bool,
|
|
80
|
-
|
|
81
|
-
artifact_provider:
|
|
103
|
+
authn_plugin: ControlAuthnPlugin,
|
|
104
|
+
artifact_provider: ArtifactProvider | None = None,
|
|
105
|
+
fleet_api_type: str | None = None,
|
|
82
106
|
) -> None:
|
|
83
107
|
self.linkstate_factory = linkstate_factory
|
|
84
108
|
self.ffs_factory = ffs_factory
|
|
85
109
|
self.objectstore_factory = objectstore_factory
|
|
86
110
|
self.is_simulation = is_simulation
|
|
87
|
-
self.
|
|
111
|
+
self.authn_plugin = authn_plugin
|
|
88
112
|
self.artifact_provider = artifact_provider
|
|
113
|
+
self.fleet_api_type = fleet_api_type
|
|
89
114
|
|
|
90
|
-
def StartRun( # pylint: disable=too-many-locals
|
|
115
|
+
def StartRun( # pylint: disable=too-many-locals, too-many-branches, too-many-statements
|
|
91
116
|
self, request: StartRunRequest, context: grpc.ServicerContext
|
|
92
117
|
) -> StartRunResponse:
|
|
93
118
|
"""Create run ID."""
|
|
@@ -95,7 +120,15 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
95
120
|
state = self.linkstate_factory.state()
|
|
96
121
|
ffs = self.ffs_factory.ffs()
|
|
97
122
|
|
|
98
|
-
|
|
123
|
+
verification_dict: dict[str, str] = {}
|
|
124
|
+
if request.app_spec:
|
|
125
|
+
fab_file, verification_dict = _get_remote_fab(
|
|
126
|
+
self.fleet_api_type, request.app_spec, context
|
|
127
|
+
)
|
|
128
|
+
else:
|
|
129
|
+
fab_file = request.fab.content
|
|
130
|
+
|
|
131
|
+
if len(fab_file) > FAB_MAX_SIZE:
|
|
99
132
|
log(
|
|
100
133
|
ERROR,
|
|
101
134
|
"FAB size exceeds maximum allowed size of %d bytes.",
|
|
@@ -103,10 +136,10 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
103
136
|
)
|
|
104
137
|
return StartRunResponse()
|
|
105
138
|
|
|
106
|
-
flwr_aid =
|
|
139
|
+
flwr_aid = get_current_account_info().flwr_aid
|
|
140
|
+
flwr_aid = _check_flwr_aid_exists(flwr_aid, context)
|
|
107
141
|
override_config = user_config_from_proto(request.override_config)
|
|
108
142
|
federation_options = config_record_from_proto(request.federation_options)
|
|
109
|
-
fab_file = request.fab.content
|
|
110
143
|
|
|
111
144
|
try:
|
|
112
145
|
# Check that num-supernodes is set
|
|
@@ -115,9 +148,27 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
115
148
|
"Federation options doesn't contain key `num-supernodes`."
|
|
116
149
|
)
|
|
117
150
|
|
|
151
|
+
# Check (1) federation exists and (2) the flwr_aid is a member
|
|
152
|
+
federation = request.federation
|
|
153
|
+
|
|
154
|
+
if not state.federation_manager.exists(federation):
|
|
155
|
+
raise ValueError(f"Federation '{federation}' does not exist.")
|
|
156
|
+
|
|
157
|
+
if not state.federation_manager.has_member(flwr_aid, federation):
|
|
158
|
+
raise ValueError(
|
|
159
|
+
f"Account with ID '{flwr_aid}' is not a member of the "
|
|
160
|
+
f"federation '{federation}'. Please log in with another account "
|
|
161
|
+
"or request access to this federation."
|
|
162
|
+
)
|
|
163
|
+
|
|
118
164
|
# Create run
|
|
119
|
-
fab = Fab(
|
|
120
|
-
|
|
165
|
+
fab = Fab(
|
|
166
|
+
hashlib.sha256(fab_file).hexdigest(),
|
|
167
|
+
fab_file,
|
|
168
|
+
verification_dict,
|
|
169
|
+
)
|
|
170
|
+
fab_hash = ffs.put(fab.content, fab.verifications)
|
|
171
|
+
|
|
121
172
|
if fab_hash != fab.hash_str:
|
|
122
173
|
raise RuntimeError(
|
|
123
174
|
f"FAB ({fab.hash_str}) hash from request doesn't match contents"
|
|
@@ -129,6 +180,7 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
129
180
|
fab_version,
|
|
130
181
|
fab_hash,
|
|
131
182
|
override_config,
|
|
183
|
+
request.federation,
|
|
132
184
|
federation_options,
|
|
133
185
|
flwr_aid,
|
|
134
186
|
)
|
|
@@ -144,7 +196,7 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
144
196
|
# Create an empty context for the Run
|
|
145
197
|
context = Context(
|
|
146
198
|
run_id=run_id,
|
|
147
|
-
node_id=
|
|
199
|
+
node_id=SUPERLINK_NODE_ID,
|
|
148
200
|
# Dict is invariant in mypy
|
|
149
201
|
node_config=node_config, # type: ignore[arg-type]
|
|
150
202
|
state=RecordDict(),
|
|
@@ -157,7 +209,10 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
157
209
|
# pylint: disable-next=broad-except
|
|
158
210
|
except Exception as e:
|
|
159
211
|
log(ERROR, "Could not start run: %s", str(e))
|
|
160
|
-
|
|
212
|
+
context.abort(
|
|
213
|
+
grpc.StatusCode.FAILED_PRECONDITION,
|
|
214
|
+
str(e),
|
|
215
|
+
)
|
|
161
216
|
|
|
162
217
|
log(INFO, "Created run %s", str(run_id))
|
|
163
218
|
return StartRunResponse(run_id=run_id)
|
|
@@ -177,12 +232,9 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
177
232
|
if not run:
|
|
178
233
|
context.abort(grpc.StatusCode.NOT_FOUND, RUN_ID_NOT_FOUND_MESSAGE)
|
|
179
234
|
|
|
180
|
-
#
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
_check_flwr_aid_in_run(
|
|
184
|
-
flwr_aid=flwr_aid, run=cast(Run, run), context=context
|
|
185
|
-
)
|
|
235
|
+
# Check if `flwr_aid` matches the run's `flwr_aid`
|
|
236
|
+
flwr_aid = get_current_account_info().flwr_aid
|
|
237
|
+
_check_flwr_aid_in_run(flwr_aid=flwr_aid, run=cast(Run, run), context=context)
|
|
186
238
|
|
|
187
239
|
after_timestamp = request.after_timestamp + 1e-6
|
|
188
240
|
while context.is_active():
|
|
@@ -218,20 +270,11 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
218
270
|
|
|
219
271
|
# Build a set of run IDs for `flwr ls --runs`
|
|
220
272
|
if not request.HasField("run_id"):
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
context.abort(
|
|
227
|
-
grpc.StatusCode.PERMISSION_DENIED,
|
|
228
|
-
"️⛔️ User authentication is enabled, but `flwr_aid` is None",
|
|
229
|
-
)
|
|
230
|
-
run_ids = state.get_run_ids(flwr_aid=flwr_aid)
|
|
231
|
-
else:
|
|
232
|
-
# If no `run_id` is specified and no user auth is enabled,
|
|
233
|
-
# return all run IDs
|
|
234
|
-
run_ids = state.get_run_ids(None)
|
|
273
|
+
# If no `run_id` is specified and account auth is enabled,
|
|
274
|
+
# return run IDs for the authenticated account
|
|
275
|
+
flwr_aid = get_current_account_info().flwr_aid
|
|
276
|
+
_check_flwr_aid_exists(flwr_aid, context)
|
|
277
|
+
run_ids = state.get_run_ids(flwr_aid=flwr_aid)
|
|
235
278
|
# Build a set of run IDs for `flwr ls --run-id <run_id>`
|
|
236
279
|
else:
|
|
237
280
|
# Retrieve run ID and run
|
|
@@ -241,13 +284,11 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
241
284
|
# Exit if `run_id` not found
|
|
242
285
|
if not run:
|
|
243
286
|
context.abort(grpc.StatusCode.NOT_FOUND, RUN_ID_NOT_FOUND_MESSAGE)
|
|
287
|
+
raise grpc.RpcError() # This line is unreachable
|
|
244
288
|
|
|
245
|
-
#
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
_check_flwr_aid_in_run(
|
|
249
|
-
flwr_aid=flwr_aid, run=cast(Run, run), context=context
|
|
250
|
-
)
|
|
289
|
+
# Check if `flwr_aid` matches the run's `flwr_aid`
|
|
290
|
+
flwr_aid = get_current_account_info().flwr_aid
|
|
291
|
+
_check_flwr_aid_in_run(flwr_aid=flwr_aid, run=run, context=context)
|
|
251
292
|
|
|
252
293
|
run_ids = {run_id}
|
|
253
294
|
|
|
@@ -269,13 +310,11 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
269
310
|
# Exit if `run_id` not found
|
|
270
311
|
if not run:
|
|
271
312
|
context.abort(grpc.StatusCode.NOT_FOUND, RUN_ID_NOT_FOUND_MESSAGE)
|
|
313
|
+
raise grpc.RpcError() # This line is unreachable
|
|
272
314
|
|
|
273
|
-
#
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
_check_flwr_aid_in_run(
|
|
277
|
-
flwr_aid=flwr_aid, run=cast(Run, run), context=context
|
|
278
|
-
)
|
|
315
|
+
# Check if `flwr_aid` matches the run's `flwr_aid`
|
|
316
|
+
flwr_aid = get_current_account_info().flwr_aid
|
|
317
|
+
_check_flwr_aid_in_run(flwr_aid=flwr_aid, run=run, context=context)
|
|
279
318
|
|
|
280
319
|
run_status = state.get_run_status({run_id})[run_id]
|
|
281
320
|
if run_status.status == Status.FINISHED:
|
|
@@ -284,11 +323,15 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
284
323
|
f"Run ID {run_id} is already finished",
|
|
285
324
|
)
|
|
286
325
|
|
|
326
|
+
# Update run status to finished:stopped
|
|
287
327
|
update_success = state.update_run_status(
|
|
288
328
|
run_id=run_id,
|
|
289
329
|
new_status=RunStatus(Status.FINISHED, SubStatus.STOPPED, ""),
|
|
290
330
|
)
|
|
291
331
|
|
|
332
|
+
# Delete the token associated with the run to stop further operations
|
|
333
|
+
state.delete_token(run_id)
|
|
334
|
+
|
|
292
335
|
if update_success:
|
|
293
336
|
message_ids: set[str] = state.get_message_ids_from_run_id(run_id)
|
|
294
337
|
|
|
@@ -305,22 +348,22 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
305
348
|
) -> GetLoginDetailsResponse:
|
|
306
349
|
"""Start login."""
|
|
307
350
|
log(INFO, "ControlServicer.GetLoginDetails")
|
|
308
|
-
if self.
|
|
351
|
+
if self.authn_plugin is None:
|
|
309
352
|
context.abort(
|
|
310
353
|
grpc.StatusCode.UNIMPLEMENTED,
|
|
311
|
-
|
|
354
|
+
NO_ACCOUNT_AUTH_MESSAGE,
|
|
312
355
|
)
|
|
313
356
|
raise grpc.RpcError() # This line is unreachable
|
|
314
357
|
|
|
315
358
|
# Get login details
|
|
316
|
-
details = self.
|
|
359
|
+
details = self.authn_plugin.get_login_details()
|
|
317
360
|
|
|
318
361
|
# Return empty response if details is None
|
|
319
362
|
if details is None:
|
|
320
363
|
return GetLoginDetailsResponse()
|
|
321
364
|
|
|
322
365
|
return GetLoginDetailsResponse(
|
|
323
|
-
|
|
366
|
+
authn_type=details.authn_type,
|
|
324
367
|
device_code=details.device_code,
|
|
325
368
|
verification_uri_complete=details.verification_uri_complete,
|
|
326
369
|
expires_in=details.expires_in,
|
|
@@ -332,15 +375,15 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
332
375
|
) -> GetAuthTokensResponse:
|
|
333
376
|
"""Get auth token."""
|
|
334
377
|
log(INFO, "ControlServicer.GetAuthTokens")
|
|
335
|
-
if self.
|
|
378
|
+
if self.authn_plugin is None:
|
|
336
379
|
context.abort(
|
|
337
380
|
grpc.StatusCode.UNIMPLEMENTED,
|
|
338
|
-
|
|
381
|
+
NO_ACCOUNT_AUTH_MESSAGE,
|
|
339
382
|
)
|
|
340
383
|
raise grpc.RpcError() # This line is unreachable
|
|
341
384
|
|
|
342
385
|
# Get auth tokens
|
|
343
|
-
credentials = self.
|
|
386
|
+
credentials = self.authn_plugin.get_auth_tokens(request.device_code)
|
|
344
387
|
|
|
345
388
|
# Return empty response if credentials is None
|
|
346
389
|
if credentials is None:
|
|
@@ -383,15 +426,160 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
383
426
|
grpc.StatusCode.FAILED_PRECONDITION, PULL_UNFINISHED_RUN_MESSAGE
|
|
384
427
|
)
|
|
385
428
|
|
|
386
|
-
# Check if `flwr_aid` matches the run's `flwr_aid`
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
_check_flwr_aid_in_run(flwr_aid=flwr_aid, run=run, context=context)
|
|
429
|
+
# Check if `flwr_aid` matches the run's `flwr_aid`
|
|
430
|
+
flwr_aid = get_current_account_info().flwr_aid
|
|
431
|
+
_check_flwr_aid_in_run(flwr_aid=flwr_aid, run=run, context=context)
|
|
390
432
|
|
|
391
433
|
# Call artifact provider
|
|
392
434
|
download_url = self.artifact_provider.get_url(run_id)
|
|
393
435
|
return PullArtifactsResponse(url=download_url)
|
|
394
436
|
|
|
437
|
+
def RegisterNode(
|
|
438
|
+
self, request: RegisterNodeRequest, context: grpc.ServicerContext
|
|
439
|
+
) -> RegisterNodeResponse:
|
|
440
|
+
"""Add a SuperNode."""
|
|
441
|
+
log(INFO, "ControlServicer.RegisterNode")
|
|
442
|
+
|
|
443
|
+
# Verify public key
|
|
444
|
+
try:
|
|
445
|
+
# Attempt to deserialize public key
|
|
446
|
+
pub_key = bytes_to_public_key(request.public_key)
|
|
447
|
+
# Check if it's a NIST EC curve public key
|
|
448
|
+
if not uses_nist_ec_curve(pub_key):
|
|
449
|
+
err_msg = "The provided public key is not a NIST EC curve public key."
|
|
450
|
+
log(ERROR, "%s", err_msg)
|
|
451
|
+
raise ValueError(err_msg)
|
|
452
|
+
except (ValueError, AttributeError) as err:
|
|
453
|
+
log(ERROR, "%s", err)
|
|
454
|
+
context.abort(grpc.StatusCode.FAILED_PRECONDITION, PUBLIC_KEY_NOT_VALID)
|
|
455
|
+
|
|
456
|
+
# Init link state
|
|
457
|
+
state = self.linkstate_factory.state()
|
|
458
|
+
node_id = 0
|
|
459
|
+
|
|
460
|
+
flwr_aid = get_current_account_info().flwr_aid
|
|
461
|
+
flwr_aid = _check_flwr_aid_exists(flwr_aid, context)
|
|
462
|
+
# Account name exists if `flwr_aid` exists
|
|
463
|
+
account_name = cast(str, get_current_account_info().account_name)
|
|
464
|
+
try:
|
|
465
|
+
node_id = state.create_node(
|
|
466
|
+
owner_aid=flwr_aid,
|
|
467
|
+
owner_name=account_name,
|
|
468
|
+
public_key=request.public_key,
|
|
469
|
+
heartbeat_interval=HEARTBEAT_DEFAULT_INTERVAL,
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
except ValueError:
|
|
473
|
+
# Public key already in use
|
|
474
|
+
log(ERROR, PUBLIC_KEY_ALREADY_IN_USE_MESSAGE)
|
|
475
|
+
context.abort(
|
|
476
|
+
grpc.StatusCode.FAILED_PRECONDITION, PUBLIC_KEY_ALREADY_IN_USE_MESSAGE
|
|
477
|
+
)
|
|
478
|
+
log(INFO, "[ControlServicer.RegisterNode] Created node_id=%s", node_id)
|
|
479
|
+
|
|
480
|
+
return RegisterNodeResponse(node_id=node_id)
|
|
481
|
+
|
|
482
|
+
def UnregisterNode(
|
|
483
|
+
self, request: UnregisterNodeRequest, context: grpc.ServicerContext
|
|
484
|
+
) -> UnregisterNodeResponse:
|
|
485
|
+
"""Remove a SuperNode."""
|
|
486
|
+
log(INFO, "ControlServicer.UnregisterNode")
|
|
487
|
+
|
|
488
|
+
# Init link state
|
|
489
|
+
state = self.linkstate_factory.state()
|
|
490
|
+
|
|
491
|
+
flwr_aid = get_current_account_info().flwr_aid
|
|
492
|
+
flwr_aid = _check_flwr_aid_exists(flwr_aid, context)
|
|
493
|
+
try:
|
|
494
|
+
state.delete_node(owner_aid=flwr_aid, node_id=request.node_id)
|
|
495
|
+
except ValueError:
|
|
496
|
+
log(ERROR, NODE_NOT_FOUND_MESSAGE)
|
|
497
|
+
context.abort(grpc.StatusCode.NOT_FOUND, NODE_NOT_FOUND_MESSAGE)
|
|
498
|
+
|
|
499
|
+
return UnregisterNodeResponse()
|
|
500
|
+
|
|
501
|
+
def ListNodes(
|
|
502
|
+
self, request: ListNodesRequest, context: grpc.ServicerContext
|
|
503
|
+
) -> ListNodesResponse:
|
|
504
|
+
"""List all SuperNodes."""
|
|
505
|
+
log(INFO, "ControlServicer.ListNodes")
|
|
506
|
+
|
|
507
|
+
if self.is_simulation:
|
|
508
|
+
log(ERROR, "ListNodes is not available in simulation mode.")
|
|
509
|
+
context.abort(
|
|
510
|
+
grpc.StatusCode.UNIMPLEMENTED,
|
|
511
|
+
"ListNodes is not available in simulation mode.",
|
|
512
|
+
)
|
|
513
|
+
raise grpc.RpcError() # This line is unreachable
|
|
514
|
+
|
|
515
|
+
nodes_info: Sequence[NodeInfo] = []
|
|
516
|
+
# Init link state
|
|
517
|
+
state = self.linkstate_factory.state()
|
|
518
|
+
|
|
519
|
+
flwr_aid = get_current_account_info().flwr_aid
|
|
520
|
+
flwr_aid = _check_flwr_aid_exists(flwr_aid, context)
|
|
521
|
+
# Retrieve all nodes for the account
|
|
522
|
+
nodes_info = state.get_node_info(owner_aids=[flwr_aid])
|
|
523
|
+
|
|
524
|
+
return ListNodesResponse(nodes_info=nodes_info, now=now().isoformat())
|
|
525
|
+
|
|
526
|
+
def ListFederations(
|
|
527
|
+
self, request: ListFederationsRequest, context: grpc.ServicerContext
|
|
528
|
+
) -> ListFederationsResponse:
|
|
529
|
+
"""List all SuperNodes."""
|
|
530
|
+
log(INFO, "ControlServicer.ListFederations")
|
|
531
|
+
|
|
532
|
+
# Init link state
|
|
533
|
+
state = self.linkstate_factory.state()
|
|
534
|
+
|
|
535
|
+
flwr_aid = get_current_account_info().flwr_aid
|
|
536
|
+
flwr_aid = _check_flwr_aid_exists(flwr_aid, context)
|
|
537
|
+
|
|
538
|
+
# Get federations the account is a member of
|
|
539
|
+
federations = state.federation_manager.get_federations(flwr_aid=flwr_aid)
|
|
540
|
+
|
|
541
|
+
return ListFederationsResponse(
|
|
542
|
+
federations=[Federation(name=fed) for fed in federations]
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
def ShowFederation(
|
|
546
|
+
self, request: ShowFederationRequest, context: grpc.ServicerContext
|
|
547
|
+
) -> ShowFederationResponse:
|
|
548
|
+
"""Show details of a specific Federation."""
|
|
549
|
+
log(INFO, "ControlServicer.ShowFederation")
|
|
550
|
+
|
|
551
|
+
# Init link state
|
|
552
|
+
state = self.linkstate_factory.state()
|
|
553
|
+
|
|
554
|
+
flwr_aid = get_current_account_info().flwr_aid
|
|
555
|
+
flwr_aid = _check_flwr_aid_exists(flwr_aid, context)
|
|
556
|
+
|
|
557
|
+
# Get federations the account is a member of
|
|
558
|
+
federations = state.federation_manager.get_federations(flwr_aid=flwr_aid)
|
|
559
|
+
|
|
560
|
+
# Ensure flwr_aid is a member of the requested federation
|
|
561
|
+
federation = request.federation_name
|
|
562
|
+
if federation not in federations:
|
|
563
|
+
context.abort(
|
|
564
|
+
grpc.StatusCode.FAILED_PRECONDITION,
|
|
565
|
+
f"Federation '{federation}' does not exist or you are "
|
|
566
|
+
"not a member of it.",
|
|
567
|
+
)
|
|
568
|
+
|
|
569
|
+
# Fetch federation details
|
|
570
|
+
details = state.federation_manager.get_details(federation)
|
|
571
|
+
|
|
572
|
+
# Build Federation proto object
|
|
573
|
+
federation_proto = Federation(
|
|
574
|
+
name=federation,
|
|
575
|
+
member_aids=details.member_aids,
|
|
576
|
+
nodes=details.nodes,
|
|
577
|
+
runs=[run_to_proto(run) for run in details.runs],
|
|
578
|
+
)
|
|
579
|
+
return ShowFederationResponse(
|
|
580
|
+
federation=federation_proto, now=now().isoformat()
|
|
581
|
+
)
|
|
582
|
+
|
|
395
583
|
|
|
396
584
|
def _create_list_runs_response(
|
|
397
585
|
run_ids: set[int], state: LinkState, store: ObjectStore
|
|
@@ -410,29 +598,101 @@ def _create_list_runs_response(
|
|
|
410
598
|
)
|
|
411
599
|
|
|
412
600
|
|
|
413
|
-
def
|
|
414
|
-
|
|
415
|
-
) -> None:
|
|
416
|
-
"""Guard clause to check if `flwr_aid` matches the run's `flwr_aid`."""
|
|
417
|
-
# `flwr_aid` must not be None. Abort if it is None.
|
|
601
|
+
def _check_flwr_aid_exists(flwr_aid: str | None, context: grpc.ServicerContext) -> str:
|
|
602
|
+
"""Guard clause to check if `flwr_aid` exists."""
|
|
418
603
|
if flwr_aid is None:
|
|
419
604
|
context.abort(
|
|
420
605
|
grpc.StatusCode.PERMISSION_DENIED,
|
|
421
|
-
"️⛔️
|
|
606
|
+
"️⛔️ Failed to fetch the account information.",
|
|
422
607
|
)
|
|
608
|
+
raise RuntimeError # This line is unreachable
|
|
609
|
+
return flwr_aid
|
|
610
|
+
|
|
423
611
|
|
|
612
|
+
def _check_flwr_aid_in_run(
|
|
613
|
+
flwr_aid: str | None, run: Run, context: grpc.ServicerContext
|
|
614
|
+
) -> None:
|
|
615
|
+
"""Guard clause to check if `flwr_aid` matches the run's `flwr_aid`."""
|
|
616
|
+
_check_flwr_aid_exists(flwr_aid, context)
|
|
424
617
|
# `run.flwr_aid` must not be an empty string. Abort if it is empty.
|
|
425
618
|
run_flwr_aid = run.flwr_aid
|
|
426
619
|
if not run_flwr_aid:
|
|
427
620
|
context.abort(
|
|
428
621
|
grpc.StatusCode.PERMISSION_DENIED,
|
|
429
|
-
"⛔️
|
|
430
|
-
"with a `flwr_aid`.",
|
|
622
|
+
"⛔️ Run is not associated with a `flwr_aid`.",
|
|
431
623
|
)
|
|
432
624
|
|
|
433
625
|
# Exit if `flwr_aid` does not match the run's `flwr_aid`
|
|
434
626
|
if run_flwr_aid != flwr_aid:
|
|
435
627
|
context.abort(
|
|
436
628
|
grpc.StatusCode.PERMISSION_DENIED,
|
|
437
|
-
"⛔️ Run ID does not belong to the
|
|
629
|
+
"⛔️ Run ID does not belong to the account",
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
def _format_verification(verifications: list[dict[str, str]]) -> dict[str, str]:
|
|
634
|
+
"""Format verification information for FAB."""
|
|
635
|
+
# Convert verifications to dict[str, str] type
|
|
636
|
+
verification_dict = {
|
|
637
|
+
item["public_key_id"]: json.dumps(
|
|
638
|
+
{k: v for k, v in item.items() if k != "public_key_id"}
|
|
639
|
+
)
|
|
640
|
+
for item in verifications
|
|
641
|
+
}
|
|
642
|
+
verification_dict.update({"valid_license": "Valid"})
|
|
643
|
+
|
|
644
|
+
return verification_dict
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
def _get_remote_fab(
|
|
648
|
+
fleet_api_type: str | None,
|
|
649
|
+
app_spec: str,
|
|
650
|
+
context: grpc.ServicerContext,
|
|
651
|
+
) -> tuple[bytes, dict[str, str]]:
|
|
652
|
+
"""Get remote FAB from Flower platform API."""
|
|
653
|
+
if fleet_api_type == TRANSPORT_TYPE_GRPC_ADAPTER:
|
|
654
|
+
context.abort(
|
|
655
|
+
grpc.StatusCode.FAILED_PRECONDITION,
|
|
656
|
+
"The selected SuperLink transport type is not "
|
|
657
|
+
"supported for connecting to Flower Platform.",
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
# Parse and validate app specification
|
|
661
|
+
try:
|
|
662
|
+
app_id, app_version = parse_app_spec(app_spec)
|
|
663
|
+
except ValueError as e:
|
|
664
|
+
context.abort(
|
|
665
|
+
grpc.StatusCode.FAILED_PRECONDITION,
|
|
666
|
+
f"{e}",
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
# Request download link and verification information
|
|
670
|
+
url = f"{PLATFORM_API_URL}/hub/fetch-fab"
|
|
671
|
+
try:
|
|
672
|
+
presigned_url, verifications = request_download_link(
|
|
673
|
+
app_id, app_version, url, "fab_url"
|
|
674
|
+
)
|
|
675
|
+
except ValueError as e:
|
|
676
|
+
context.abort(
|
|
677
|
+
grpc.StatusCode.FAILED_PRECONDITION,
|
|
678
|
+
f"{e}",
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
# Format verification information
|
|
682
|
+
verification_dict = (
|
|
683
|
+
_format_verification(verifications)
|
|
684
|
+
if verifications is not None
|
|
685
|
+
else {"valid_license": ""}
|
|
686
|
+
)
|
|
687
|
+
|
|
688
|
+
# Download FAB from Flower platform API
|
|
689
|
+
try:
|
|
690
|
+
r = requests.get(presigned_url, timeout=60)
|
|
691
|
+
r.raise_for_status()
|
|
692
|
+
except requests.RequestException as e:
|
|
693
|
+
context.abort(
|
|
694
|
+
grpc.StatusCode.FAILED_PRECONDITION,
|
|
695
|
+
f"FAB download failed: {str(e)}",
|
|
438
696
|
)
|
|
697
|
+
fab_file = r.content
|
|
698
|
+
return fab_file, verification_dict
|