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
flwr/cli/utils.py
CHANGED
|
@@ -18,40 +18,68 @@
|
|
|
18
18
|
import hashlib
|
|
19
19
|
import json
|
|
20
20
|
import re
|
|
21
|
-
from collections.abc import Iterator
|
|
21
|
+
from collections.abc import Callable, Iterable, Iterator
|
|
22
22
|
from contextlib import contextmanager
|
|
23
23
|
from pathlib import Path
|
|
24
|
-
from typing import Any,
|
|
24
|
+
from typing import Any, cast
|
|
25
25
|
|
|
26
|
+
import click
|
|
26
27
|
import grpc
|
|
28
|
+
import pathspec
|
|
27
29
|
import typer
|
|
28
30
|
|
|
31
|
+
from flwr.cli.typing import SuperLinkConnection
|
|
29
32
|
from flwr.common.constant import (
|
|
30
|
-
|
|
33
|
+
ACCESS_TOKEN_KEY,
|
|
34
|
+
AUTHN_TYPE_JSON_KEY,
|
|
31
35
|
CREDENTIALS_DIR,
|
|
32
36
|
FLWR_DIR,
|
|
37
|
+
NO_ACCOUNT_AUTH_MESSAGE,
|
|
33
38
|
NO_ARTIFACT_PROVIDER_MESSAGE,
|
|
34
|
-
|
|
39
|
+
NODE_NOT_FOUND_MESSAGE,
|
|
40
|
+
PUBLIC_KEY_ALREADY_IN_USE_MESSAGE,
|
|
41
|
+
PUBLIC_KEY_NOT_VALID,
|
|
35
42
|
PULL_UNFINISHED_RUN_MESSAGE,
|
|
43
|
+
REFRESH_TOKEN_KEY,
|
|
36
44
|
RUN_ID_NOT_FOUND_MESSAGE,
|
|
45
|
+
AuthnType,
|
|
37
46
|
)
|
|
38
47
|
from flwr.common.grpc import (
|
|
39
48
|
GRPC_MAX_MESSAGE_LENGTH,
|
|
40
49
|
create_channel,
|
|
41
50
|
on_channel_state_change,
|
|
42
51
|
)
|
|
52
|
+
from flwr.supercore.utils import get_flwr_home
|
|
43
53
|
|
|
44
|
-
from .auth_plugin import CliAuthPlugin,
|
|
45
|
-
from .
|
|
46
|
-
from .config_utils import
|
|
54
|
+
from .auth_plugin import CliAuthPlugin, get_cli_plugin_class
|
|
55
|
+
from .cli_account_auth_interceptor import CliAccountAuthInterceptor
|
|
56
|
+
from .config_utils import (
|
|
57
|
+
load_certificate_in_connection,
|
|
58
|
+
validate_certificate_in_federation_config,
|
|
59
|
+
)
|
|
47
60
|
|
|
48
61
|
|
|
49
62
|
def prompt_text(
|
|
50
63
|
text: str,
|
|
51
64
|
predicate: Callable[[str], bool] = lambda _: True,
|
|
52
|
-
default:
|
|
65
|
+
default: str | None = None,
|
|
53
66
|
) -> str:
|
|
54
|
-
"""Ask user to enter text input.
|
|
67
|
+
"""Ask user to enter text input.
|
|
68
|
+
|
|
69
|
+
Parameters
|
|
70
|
+
----------
|
|
71
|
+
text : str
|
|
72
|
+
The prompt text to display to the user.
|
|
73
|
+
predicate : Callable[[str], bool] (default: lambda _: True)
|
|
74
|
+
A function to validate the user input. Default accepts all non-empty strings.
|
|
75
|
+
default : str | None (default: None)
|
|
76
|
+
Default value to use if user presses enter without input.
|
|
77
|
+
|
|
78
|
+
Returns
|
|
79
|
+
-------
|
|
80
|
+
str
|
|
81
|
+
The validated user input.
|
|
82
|
+
"""
|
|
55
83
|
while True:
|
|
56
84
|
result = typer.prompt(
|
|
57
85
|
typer.style(f"\n💬 {text}", fg=typer.colors.MAGENTA, bold=True),
|
|
@@ -65,7 +93,20 @@ def prompt_text(
|
|
|
65
93
|
|
|
66
94
|
|
|
67
95
|
def prompt_options(text: str, options: list[str]) -> str:
|
|
68
|
-
"""Ask user to select one of the given options and return the selected item.
|
|
96
|
+
"""Ask user to select one of the given options and return the selected item.
|
|
97
|
+
|
|
98
|
+
Parameters
|
|
99
|
+
----------
|
|
100
|
+
text : str
|
|
101
|
+
The prompt text to display to the user.
|
|
102
|
+
options : list[str]
|
|
103
|
+
List of options to present to the user.
|
|
104
|
+
|
|
105
|
+
Returns
|
|
106
|
+
-------
|
|
107
|
+
str
|
|
108
|
+
The selected option from the list.
|
|
109
|
+
"""
|
|
69
110
|
# Turn options into a list with index as in " [ 0] quickstart-pytorch"
|
|
70
111
|
options_formatted = [
|
|
71
112
|
" [ "
|
|
@@ -123,9 +164,19 @@ def is_valid_project_name(name: str) -> bool:
|
|
|
123
164
|
def sanitize_project_name(name: str) -> str:
|
|
124
165
|
"""Sanitize the given string to make it a valid Python project name.
|
|
125
166
|
|
|
126
|
-
This
|
|
167
|
+
This function replaces spaces, dots, slashes, and underscores with dashes, removes
|
|
127
168
|
any characters not allowed in Python project names, makes the string lowercase, and
|
|
128
169
|
ensures it starts with a valid character.
|
|
170
|
+
|
|
171
|
+
Parameters
|
|
172
|
+
----------
|
|
173
|
+
name : str
|
|
174
|
+
The project name to sanitize.
|
|
175
|
+
|
|
176
|
+
Returns
|
|
177
|
+
-------
|
|
178
|
+
str
|
|
179
|
+
The sanitized project name that is valid for Python projects.
|
|
129
180
|
"""
|
|
130
181
|
# Replace whitespace with '_'
|
|
131
182
|
name_with_hyphens = re.sub(r"[ ./_]", "-", name)
|
|
@@ -150,8 +201,19 @@ def sanitize_project_name(name: str) -> str:
|
|
|
150
201
|
return sanitized_name
|
|
151
202
|
|
|
152
203
|
|
|
153
|
-
def get_sha256_hash(file_path_or_int:
|
|
154
|
-
"""Calculate the SHA-256 hash of a file.
|
|
204
|
+
def get_sha256_hash(file_path_or_int: Path | int) -> str:
|
|
205
|
+
"""Calculate the SHA-256 hash of a file or integer.
|
|
206
|
+
|
|
207
|
+
Parameters
|
|
208
|
+
----------
|
|
209
|
+
file_path_or_int : Path | int
|
|
210
|
+
Either a path to a file to hash, or an integer to convert to string and hash.
|
|
211
|
+
|
|
212
|
+
Returns
|
|
213
|
+
-------
|
|
214
|
+
str
|
|
215
|
+
The SHA-256 hash as a hexadecimal string.
|
|
216
|
+
"""
|
|
155
217
|
sha256 = hashlib.sha256()
|
|
156
218
|
if isinstance(file_path_or_int, Path):
|
|
157
219
|
with open(file_path_or_int, "rb") as f:
|
|
@@ -165,8 +227,8 @@ def get_sha256_hash(file_path_or_int: Union[Path, int]) -> str:
|
|
|
165
227
|
return sha256.hexdigest()
|
|
166
228
|
|
|
167
229
|
|
|
168
|
-
def
|
|
169
|
-
"""Return the path to the
|
|
230
|
+
def get_account_auth_config_path(root_dir: Path, federation: str) -> Path:
|
|
231
|
+
"""Return the path to the account auth config file.
|
|
170
232
|
|
|
171
233
|
Additionally, a `.gitignore` file will be created in the Flower directory to
|
|
172
234
|
include the `.credentials` folder to be excluded from git. If the `.gitignore`
|
|
@@ -210,77 +272,180 @@ def get_user_auth_config_path(root_dir: Path, federation: str) -> Path:
|
|
|
210
272
|
f"Please check the permissions of `{gitignore_path}` and try again.",
|
|
211
273
|
fg=typer.colors.RED,
|
|
212
274
|
bold=True,
|
|
275
|
+
err=True,
|
|
213
276
|
)
|
|
214
277
|
raise typer.Exit(code=1) from err
|
|
215
278
|
|
|
216
279
|
return credentials_dir / f"{federation}.json"
|
|
217
280
|
|
|
218
281
|
|
|
219
|
-
def
|
|
282
|
+
def account_auth_enabled(federation_config: dict[str, Any]) -> bool:
|
|
283
|
+
"""Check if account authentication is enabled in the federation config.
|
|
284
|
+
|
|
285
|
+
Parameters
|
|
286
|
+
----------
|
|
287
|
+
federation_config : dict[str, Any]
|
|
288
|
+
The federation configuration dictionary.
|
|
289
|
+
|
|
290
|
+
Returns
|
|
291
|
+
-------
|
|
292
|
+
bool
|
|
293
|
+
True if account authentication is enabled, False otherwise.
|
|
294
|
+
"""
|
|
295
|
+
enabled: bool = federation_config.get("enable-user-auth", False)
|
|
296
|
+
enabled |= federation_config.get("enable-account-auth", False)
|
|
297
|
+
if "enable-user-auth" in federation_config:
|
|
298
|
+
typer.secho(
|
|
299
|
+
"`enable-user-auth` is deprecated and will be removed in a future "
|
|
300
|
+
"release. Please use `enable-account-auth` instead.",
|
|
301
|
+
fg=typer.colors.YELLOW,
|
|
302
|
+
bold=True,
|
|
303
|
+
)
|
|
304
|
+
return enabled
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def retrieve_authn_type(config_path: Path) -> str:
|
|
308
|
+
"""Retrieve the auth type from the config file or return NOOP if not found.
|
|
309
|
+
|
|
310
|
+
Parameters
|
|
311
|
+
----------
|
|
312
|
+
config_path : Path
|
|
313
|
+
Path to the authentication configuration file.
|
|
314
|
+
|
|
315
|
+
Returns
|
|
316
|
+
-------
|
|
317
|
+
str
|
|
318
|
+
The authentication type string, or AuthnType.NOOP if not found.
|
|
319
|
+
"""
|
|
320
|
+
try:
|
|
321
|
+
with config_path.open("r", encoding="utf-8") as file:
|
|
322
|
+
json_file = json.load(file)
|
|
323
|
+
authn_type: str = json_file[AUTHN_TYPE_JSON_KEY]
|
|
324
|
+
return authn_type
|
|
325
|
+
except (FileNotFoundError, KeyError):
|
|
326
|
+
return AuthnType.NOOP
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def load_cli_auth_plugin(
|
|
220
330
|
root_dir: Path,
|
|
221
331
|
federation: str,
|
|
222
332
|
federation_config: dict[str, Any],
|
|
223
|
-
|
|
224
|
-
) ->
|
|
225
|
-
"""Load the CLI-side
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
333
|
+
authn_type: str | None = None,
|
|
334
|
+
) -> CliAuthPlugin:
|
|
335
|
+
"""Load the CLI-side account auth plugin for the given authn type.
|
|
336
|
+
|
|
337
|
+
Parameters
|
|
338
|
+
----------
|
|
339
|
+
root_dir : Path
|
|
340
|
+
Root directory of the Flower project.
|
|
341
|
+
federation : str
|
|
342
|
+
Name of the federation.
|
|
343
|
+
federation_config : dict[str, Any]
|
|
344
|
+
Federation configuration dictionary.
|
|
345
|
+
authn_type : str | None
|
|
346
|
+
Authentication type. If None, will be determined from config.
|
|
347
|
+
|
|
348
|
+
Returns
|
|
349
|
+
-------
|
|
350
|
+
CliAuthPlugin
|
|
351
|
+
The loaded authentication plugin instance.
|
|
352
|
+
|
|
353
|
+
Raises
|
|
354
|
+
------
|
|
355
|
+
typer.Exit
|
|
356
|
+
If the authentication type is unknown.
|
|
357
|
+
"""
|
|
358
|
+
# Find the path to the account auth config file
|
|
359
|
+
config_path = get_account_auth_config_path(root_dir, federation)
|
|
360
|
+
|
|
361
|
+
# Determine the auth type if not provided
|
|
362
|
+
# Only `flwr login` command can provide `authn_type` explicitly, as it can query the
|
|
363
|
+
# SuperLink for the auth type.
|
|
364
|
+
if authn_type is None:
|
|
365
|
+
authn_type = AuthnType.NOOP
|
|
366
|
+
if account_auth_enabled(federation_config):
|
|
367
|
+
authn_type = retrieve_authn_type(config_path)
|
|
247
368
|
|
|
248
369
|
# Retrieve auth plugin class and instantiate it
|
|
249
370
|
try:
|
|
250
|
-
|
|
251
|
-
auth_plugin_class = all_plugins[auth_type]
|
|
371
|
+
auth_plugin_class = get_cli_plugin_class(authn_type)
|
|
252
372
|
return auth_plugin_class(config_path)
|
|
253
|
-
except
|
|
254
|
-
typer.echo(f"❌ Unknown
|
|
373
|
+
except ValueError:
|
|
374
|
+
typer.echo(f"❌ Unknown account authentication type: {authn_type}")
|
|
255
375
|
raise typer.Exit(code=1) from None
|
|
256
|
-
|
|
257
|
-
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def load_cli_auth_plugin_from_connection(
|
|
379
|
+
connection: SuperLinkConnection,
|
|
380
|
+
authn_type: str | None = None,
|
|
381
|
+
) -> CliAuthPlugin:
|
|
382
|
+
"""Load the CLI-side account auth plugin for the given connection.
|
|
383
|
+
|
|
384
|
+
Parameters
|
|
385
|
+
----------
|
|
386
|
+
connection : SuperLinkConnection
|
|
387
|
+
The SuperLink connection configuration.
|
|
388
|
+
authn_type : str | None
|
|
389
|
+
Authentication type. If None, will be determined from config.
|
|
390
|
+
|
|
391
|
+
Returns
|
|
392
|
+
-------
|
|
393
|
+
CliAuthPlugin
|
|
394
|
+
The loaded authentication plugin instance.
|
|
395
|
+
|
|
396
|
+
Raises
|
|
397
|
+
------
|
|
398
|
+
typer.Exit
|
|
399
|
+
If the authentication type is unknown.
|
|
400
|
+
"""
|
|
401
|
+
# Locate the credentials directory
|
|
402
|
+
flwr_dir = get_flwr_home()
|
|
403
|
+
credentials_dir = flwr_dir / CREDENTIALS_DIR
|
|
404
|
+
credentials_dir.mkdir(parents=True, exist_ok=True)
|
|
405
|
+
|
|
406
|
+
# Find the path to the account auth config file
|
|
407
|
+
config_path = get_account_auth_config_path(flwr_dir, connection.name)
|
|
408
|
+
|
|
409
|
+
# Determine the auth type if not provided
|
|
410
|
+
if authn_type is None:
|
|
411
|
+
authn_type = AuthnType.NOOP
|
|
412
|
+
if connection.enable_account_auth:
|
|
413
|
+
authn_type = retrieve_authn_type(config_path)
|
|
414
|
+
|
|
415
|
+
# Retrieve auth plugin class and instantiate it
|
|
416
|
+
try:
|
|
417
|
+
auth_plugin_class = get_cli_plugin_class(authn_type)
|
|
418
|
+
return auth_plugin_class(config_path)
|
|
419
|
+
except ValueError:
|
|
420
|
+
typer.echo(f"❌ Unknown account authentication type: {authn_type}")
|
|
258
421
|
raise typer.Exit(code=1) from None
|
|
259
422
|
|
|
260
423
|
|
|
261
424
|
def init_channel(
|
|
262
|
-
app: Path, federation_config: dict[str, Any], auth_plugin:
|
|
425
|
+
app: Path, federation_config: dict[str, Any], auth_plugin: CliAuthPlugin
|
|
263
426
|
) -> grpc.Channel:
|
|
264
|
-
"""Initialize gRPC channel to the Control API.
|
|
427
|
+
"""Initialize gRPC channel to the Control API.
|
|
428
|
+
|
|
429
|
+
Parameters
|
|
430
|
+
----------
|
|
431
|
+
app : Path
|
|
432
|
+
Path to the Flower app directory.
|
|
433
|
+
federation_config : dict[str, Any]
|
|
434
|
+
Federation configuration dictionary containing address and TLS settings.
|
|
435
|
+
auth_plugin : CliAuthPlugin
|
|
436
|
+
Authentication plugin instance for handling credentials.
|
|
437
|
+
|
|
438
|
+
Returns
|
|
439
|
+
-------
|
|
440
|
+
grpc.Channel
|
|
441
|
+
Configured gRPC channel with authentication interceptors.
|
|
442
|
+
"""
|
|
265
443
|
insecure, root_certificates_bytes = validate_certificate_in_federation_config(
|
|
266
444
|
app, federation_config
|
|
267
445
|
)
|
|
268
446
|
|
|
269
|
-
#
|
|
270
|
-
|
|
271
|
-
if auth_plugin is not None:
|
|
272
|
-
# Check if TLS is enabled. If not, raise an error
|
|
273
|
-
if insecure:
|
|
274
|
-
typer.secho(
|
|
275
|
-
"❌ User authentication requires TLS to be enabled. "
|
|
276
|
-
"Remove `insecure = true` from the federation configuration.",
|
|
277
|
-
fg=typer.colors.RED,
|
|
278
|
-
bold=True,
|
|
279
|
-
)
|
|
280
|
-
raise typer.Exit(code=1)
|
|
281
|
-
|
|
282
|
-
auth_plugin.load_tokens()
|
|
283
|
-
interceptors.append(CliUserAuthInterceptor(auth_plugin))
|
|
447
|
+
# Load tokens
|
|
448
|
+
auth_plugin.load_tokens()
|
|
284
449
|
|
|
285
450
|
# Create the gRPC channel
|
|
286
451
|
channel = create_channel(
|
|
@@ -288,19 +453,81 @@ def init_channel(
|
|
|
288
453
|
insecure=insecure,
|
|
289
454
|
root_certificates=root_certificates_bytes,
|
|
290
455
|
max_message_length=GRPC_MAX_MESSAGE_LENGTH,
|
|
291
|
-
interceptors=
|
|
456
|
+
interceptors=[CliAccountAuthInterceptor(auth_plugin)],
|
|
457
|
+
)
|
|
458
|
+
channel.subscribe(on_channel_state_change)
|
|
459
|
+
return channel
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def init_channel_from_connection(
|
|
463
|
+
connection: SuperLinkConnection, auth_plugin: CliAuthPlugin | None = None
|
|
464
|
+
) -> grpc.Channel:
|
|
465
|
+
"""Initialize gRPC channel to the Control API.
|
|
466
|
+
|
|
467
|
+
Parameters
|
|
468
|
+
----------
|
|
469
|
+
connection : SuperLinkConnection
|
|
470
|
+
SuperLink connection configuration.
|
|
471
|
+
auth_plugin : CliAuthPlugin | None (default: None)
|
|
472
|
+
Authentication plugin instance for handling credentials.
|
|
473
|
+
|
|
474
|
+
Returns
|
|
475
|
+
-------
|
|
476
|
+
grpc.Channel
|
|
477
|
+
Configured gRPC channel with authentication interceptors.
|
|
478
|
+
"""
|
|
479
|
+
if connection.address is None:
|
|
480
|
+
cmd = click.get_current_context().command.name
|
|
481
|
+
typer.secho(
|
|
482
|
+
f"❌ `flwr {cmd}` currently works with a SuperLink. Ensure that the "
|
|
483
|
+
"correct SuperLink (Control API) address is provided in `pyproject.toml`.",
|
|
484
|
+
fg=typer.colors.RED,
|
|
485
|
+
bold=True,
|
|
486
|
+
err=True,
|
|
487
|
+
)
|
|
488
|
+
raise typer.Exit(code=1)
|
|
489
|
+
|
|
490
|
+
root_certificates_bytes = load_certificate_in_connection(connection)
|
|
491
|
+
|
|
492
|
+
# Load authentication plugin
|
|
493
|
+
if auth_plugin is None:
|
|
494
|
+
auth_plugin = load_cli_auth_plugin_from_connection(connection)
|
|
495
|
+
|
|
496
|
+
# Load tokens
|
|
497
|
+
auth_plugin.load_tokens()
|
|
498
|
+
|
|
499
|
+
# Create the gRPC channel
|
|
500
|
+
channel = create_channel(
|
|
501
|
+
server_address=connection.address,
|
|
502
|
+
insecure=connection.insecure,
|
|
503
|
+
root_certificates=root_certificates_bytes,
|
|
504
|
+
max_message_length=GRPC_MAX_MESSAGE_LENGTH,
|
|
505
|
+
interceptors=[CliAccountAuthInterceptor(auth_plugin)],
|
|
292
506
|
)
|
|
293
507
|
channel.subscribe(on_channel_state_change)
|
|
294
508
|
return channel
|
|
295
509
|
|
|
296
510
|
|
|
297
511
|
@contextmanager
|
|
298
|
-
def flwr_cli_grpc_exc_handler() -> Iterator[None]:
|
|
512
|
+
def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-branches
|
|
299
513
|
"""Context manager to handle specific gRPC errors.
|
|
300
514
|
|
|
301
|
-
|
|
302
|
-
UNAVAILABLE,
|
|
303
|
-
application. All other exceptions will be
|
|
515
|
+
Catches grpc.RpcError exceptions with UNAUTHENTICATED, UNIMPLEMENTED,
|
|
516
|
+
UNAVAILABLE, PERMISSION_DENIED, NOT_FOUND, and FAILED_PRECONDITION statuses,
|
|
517
|
+
informs the user, and exits the application. All other exceptions will be
|
|
518
|
+
allowed to escape.
|
|
519
|
+
|
|
520
|
+
Yields
|
|
521
|
+
------
|
|
522
|
+
None
|
|
523
|
+
Context manager yields nothing.
|
|
524
|
+
|
|
525
|
+
Raises
|
|
526
|
+
------
|
|
527
|
+
typer.Exit
|
|
528
|
+
On handled gRPC error statuses with appropriate exit code.
|
|
529
|
+
grpc.RpcError
|
|
530
|
+
For unhandled gRPC error statuses.
|
|
304
531
|
"""
|
|
305
532
|
try:
|
|
306
533
|
yield
|
|
@@ -311,20 +538,23 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]:
|
|
|
311
538
|
" to authenticate and try again.",
|
|
312
539
|
fg=typer.colors.RED,
|
|
313
540
|
bold=True,
|
|
541
|
+
err=True,
|
|
314
542
|
)
|
|
315
543
|
raise typer.Exit(code=1) from None
|
|
316
544
|
if e.code() == grpc.StatusCode.UNIMPLEMENTED:
|
|
317
|
-
if e.details() ==
|
|
545
|
+
if e.details() == NO_ACCOUNT_AUTH_MESSAGE: # pylint: disable=E1101
|
|
318
546
|
typer.secho(
|
|
319
|
-
"❌
|
|
547
|
+
"❌ Account authentication is not enabled on this SuperLink.",
|
|
320
548
|
fg=typer.colors.RED,
|
|
321
549
|
bold=True,
|
|
550
|
+
err=True,
|
|
322
551
|
)
|
|
323
552
|
elif e.details() == NO_ARTIFACT_PROVIDER_MESSAGE: # pylint: disable=E1101
|
|
324
553
|
typer.secho(
|
|
325
554
|
"❌ The SuperLink does not support `flwr pull` command.",
|
|
326
555
|
fg=typer.colors.RED,
|
|
327
556
|
bold=True,
|
|
557
|
+
err=True,
|
|
328
558
|
)
|
|
329
559
|
else:
|
|
330
560
|
typer.secho(
|
|
@@ -334,6 +564,7 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]:
|
|
|
334
564
|
"the CLI and SuperLink are compatible.",
|
|
335
565
|
fg=typer.colors.RED,
|
|
336
566
|
bold=True,
|
|
567
|
+
err=True,
|
|
337
568
|
)
|
|
338
569
|
raise typer.Exit(code=1) from None
|
|
339
570
|
if e.code() == grpc.StatusCode.PERMISSION_DENIED:
|
|
@@ -341,6 +572,7 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]:
|
|
|
341
572
|
"❌ Permission denied.",
|
|
342
573
|
fg=typer.colors.RED,
|
|
343
574
|
bold=True,
|
|
575
|
+
err=True,
|
|
344
576
|
)
|
|
345
577
|
# pylint: disable-next=E1101
|
|
346
578
|
typer.secho(e.details(), fg=typer.colors.RED, bold=True)
|
|
@@ -351,18 +583,26 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]:
|
|
|
351
583
|
"connection and 'address' in the federation configuration.",
|
|
352
584
|
fg=typer.colors.RED,
|
|
353
585
|
bold=True,
|
|
586
|
+
err=True,
|
|
354
587
|
)
|
|
355
588
|
raise typer.Exit(code=1) from None
|
|
356
|
-
if (
|
|
357
|
-
e.
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
589
|
+
if e.code() == grpc.StatusCode.NOT_FOUND:
|
|
590
|
+
if e.details() == RUN_ID_NOT_FOUND_MESSAGE: # pylint: disable=E1101
|
|
591
|
+
typer.secho(
|
|
592
|
+
"❌ Run ID not found.",
|
|
593
|
+
fg=typer.colors.RED,
|
|
594
|
+
bold=True,
|
|
595
|
+
err=True,
|
|
596
|
+
)
|
|
597
|
+
raise typer.Exit(code=1) from None
|
|
598
|
+
if e.details() == NODE_NOT_FOUND_MESSAGE: # pylint: disable=E1101
|
|
599
|
+
typer.secho(
|
|
600
|
+
"❌ Node ID not found for this account.",
|
|
601
|
+
fg=typer.colors.RED,
|
|
602
|
+
bold=True,
|
|
603
|
+
err=True,
|
|
604
|
+
)
|
|
605
|
+
raise typer.Exit(code=1) from None
|
|
366
606
|
if e.code() == grpc.StatusCode.FAILED_PRECONDITION:
|
|
367
607
|
if e.details() == PULL_UNFINISHED_RUN_MESSAGE: # pylint: disable=E1101
|
|
368
608
|
typer.secho(
|
|
@@ -370,6 +610,114 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]:
|
|
|
370
610
|
"the run is finished. You can check the run status with `flwr ls`.",
|
|
371
611
|
fg=typer.colors.RED,
|
|
372
612
|
bold=True,
|
|
613
|
+
err=True,
|
|
614
|
+
)
|
|
615
|
+
raise typer.Exit(code=1) from None
|
|
616
|
+
if (
|
|
617
|
+
e.details() == PUBLIC_KEY_ALREADY_IN_USE_MESSAGE
|
|
618
|
+
): # pylint: disable=E1101
|
|
619
|
+
typer.secho(
|
|
620
|
+
"❌ The provided public key is already in use by another "
|
|
621
|
+
"SuperNode.",
|
|
622
|
+
fg=typer.colors.RED,
|
|
623
|
+
bold=True,
|
|
624
|
+
err=True,
|
|
625
|
+
)
|
|
626
|
+
raise typer.Exit(code=1) from None
|
|
627
|
+
if e.details() == PUBLIC_KEY_NOT_VALID: # pylint: disable=E1101
|
|
628
|
+
typer.secho(
|
|
629
|
+
"❌ The provided public key is invalid. Please provide a valid "
|
|
630
|
+
"NIST EC public key.",
|
|
631
|
+
fg=typer.colors.RED,
|
|
632
|
+
bold=True,
|
|
633
|
+
err=True,
|
|
373
634
|
)
|
|
374
635
|
raise typer.Exit(code=1) from None
|
|
636
|
+
|
|
637
|
+
# Log details from grpc error directly
|
|
638
|
+
typer.secho(
|
|
639
|
+
f"❌ {e.details()}",
|
|
640
|
+
fg=typer.colors.RED,
|
|
641
|
+
bold=True,
|
|
642
|
+
err=True,
|
|
643
|
+
)
|
|
644
|
+
raise typer.Exit(code=1) from None
|
|
375
645
|
raise
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
def build_pathspec(patterns: Iterable[str]) -> pathspec.PathSpec:
|
|
649
|
+
"""Build a PathSpec from a list of GitIgnore-style patterns.
|
|
650
|
+
|
|
651
|
+
Parameters
|
|
652
|
+
----------
|
|
653
|
+
patterns : Iterable[str]
|
|
654
|
+
Iterable of GitIgnore-style pattern strings.
|
|
655
|
+
|
|
656
|
+
Returns
|
|
657
|
+
-------
|
|
658
|
+
pathspec.PathSpec
|
|
659
|
+
Compiled PathSpec object for pattern matching.
|
|
660
|
+
"""
|
|
661
|
+
return pathspec.PathSpec.from_lines("gitwildmatch", patterns)
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
def load_gitignore_patterns(file: Path | bytes) -> list[str]:
|
|
665
|
+
"""Load gitignore patterns from .gitignore file bytes.
|
|
666
|
+
|
|
667
|
+
Parameters
|
|
668
|
+
----------
|
|
669
|
+
file : Path | bytes
|
|
670
|
+
The path to a .gitignore file or its bytes content.
|
|
671
|
+
|
|
672
|
+
Returns
|
|
673
|
+
-------
|
|
674
|
+
list[str]
|
|
675
|
+
List of gitignore patterns.
|
|
676
|
+
Returns empty list if content can't be decoded or the file does not exist.
|
|
677
|
+
"""
|
|
678
|
+
try:
|
|
679
|
+
if isinstance(file, Path):
|
|
680
|
+
content = file.read_text(encoding="utf-8")
|
|
681
|
+
else:
|
|
682
|
+
content = file.decode("utf-8")
|
|
683
|
+
patterns = [
|
|
684
|
+
line.strip()
|
|
685
|
+
for line in content.splitlines()
|
|
686
|
+
if line.strip() and not line.strip().startswith("#")
|
|
687
|
+
]
|
|
688
|
+
return patterns
|
|
689
|
+
except (UnicodeDecodeError, OSError):
|
|
690
|
+
return []
|
|
691
|
+
|
|
692
|
+
|
|
693
|
+
def validate_credentials_content(creds_path: Path) -> str:
|
|
694
|
+
"""Load and validate the credentials file content.
|
|
695
|
+
|
|
696
|
+
Ensures required keys exist:
|
|
697
|
+
- AUTHN_TYPE_JSON_KEY
|
|
698
|
+
- ACCESS_TOKEN_KEY
|
|
699
|
+
- REFRESH_TOKEN_KEY
|
|
700
|
+
"""
|
|
701
|
+
try:
|
|
702
|
+
creds: dict[str, str] = json.loads(creds_path.read_text(encoding="utf-8"))
|
|
703
|
+
except (OSError, json.JSONDecodeError) as err:
|
|
704
|
+
typer.secho(
|
|
705
|
+
f"Invalid credentials file at '{creds_path}': {err}",
|
|
706
|
+
fg=typer.colors.RED,
|
|
707
|
+
err=True,
|
|
708
|
+
)
|
|
709
|
+
raise typer.Exit(code=1) from err
|
|
710
|
+
|
|
711
|
+
required_keys = [AUTHN_TYPE_JSON_KEY, ACCESS_TOKEN_KEY, REFRESH_TOKEN_KEY]
|
|
712
|
+
missing = [key for key in required_keys if key not in creds]
|
|
713
|
+
|
|
714
|
+
if missing:
|
|
715
|
+
typer.secho(
|
|
716
|
+
f"Credentials file '{creds_path}' is missing "
|
|
717
|
+
f"required key(s): {', '.join(missing)}. Please log in again.",
|
|
718
|
+
fg=typer.colors.RED,
|
|
719
|
+
err=True,
|
|
720
|
+
)
|
|
721
|
+
raise typer.Exit(code=1)
|
|
722
|
+
|
|
723
|
+
return creds[ACCESS_TOKEN_KEY]
|
flwr/client/__init__.py
CHANGED
|
@@ -15,10 +15,11 @@
|
|
|
15
15
|
"""Flower client."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
from flwr.clientapp.client_app import ClientApp
|
|
19
|
+
|
|
18
20
|
from ..compat.client.app import start_client as start_client # Deprecated
|
|
19
21
|
from ..compat.client.app import start_numpy_client as start_numpy_client # Deprecated
|
|
20
22
|
from .client import Client as Client
|
|
21
|
-
from .client_app import ClientApp as ClientApp
|
|
22
23
|
from .numpy_client import NumPyClient as NumPyClient
|
|
23
24
|
from .typing import ClientFn as ClientFn
|
|
24
25
|
from .typing import ClientFnExt as ClientFnExt
|
|
@@ -120,7 +120,10 @@ class DPFedAvgNumPyClient(NumPyClient):
|
|
|
120
120
|
updated_params, num_examples, metrics = self.client.fit(parameters, config)
|
|
121
121
|
|
|
122
122
|
# Update = updated model - original model
|
|
123
|
-
update = [
|
|
123
|
+
update = [
|
|
124
|
+
np.subtract(x, y)
|
|
125
|
+
for (x, y) in zip(updated_params, original_params, strict=True)
|
|
126
|
+
]
|
|
124
127
|
|
|
125
128
|
if "dpfedavg_clip_norm" not in config:
|
|
126
129
|
raise KeyError("Clipping threshold not supplied by the server.")
|