flwr 1.23.0__py3-none-any.whl → 1.25.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flwr/__init__.py +16 -5
- flwr/app/error.py +2 -2
- flwr/app/exception.py +3 -3
- flwr/cli/app.py +19 -0
- flwr/cli/{new/templates → app_cmd}/__init__.py +9 -1
- flwr/cli/app_cmd/publish.py +285 -0
- flwr/cli/app_cmd/review.py +262 -0
- flwr/cli/auth_plugin/auth_plugin.py +4 -5
- flwr/cli/auth_plugin/noop_auth_plugin.py +54 -11
- flwr/cli/auth_plugin/oidc_cli_plugin.py +32 -9
- flwr/cli/build.py +60 -18
- flwr/cli/cli_account_auth_interceptor.py +24 -7
- flwr/cli/config_utils.py +101 -13
- flwr/cli/{new/templates/app/code/flwr_tune → federation}/__init__.py +10 -1
- flwr/cli/federation/ls.py +140 -0
- flwr/cli/federation/show.py +318 -0
- flwr/cli/install.py +91 -13
- flwr/cli/log.py +52 -9
- flwr/cli/login/login.py +7 -4
- flwr/cli/ls.py +211 -130
- flwr/cli/new/new.py +123 -331
- flwr/cli/pull.py +10 -5
- flwr/cli/run/run.py +71 -29
- flwr/cli/run_utils.py +148 -0
- flwr/cli/stop.py +26 -8
- flwr/cli/supernode/ls.py +25 -12
- flwr/cli/supernode/register.py +9 -4
- flwr/cli/supernode/unregister.py +5 -3
- flwr/cli/utils.py +239 -16
- flwr/client/__init__.py +1 -1
- flwr/client/dpfedavg_numpy_client.py +4 -1
- flwr/client/grpc_adapter_client/connection.py +8 -9
- flwr/client/grpc_rere_client/connection.py +16 -14
- flwr/client/grpc_rere_client/grpc_adapter.py +6 -2
- flwr/client/grpc_rere_client/node_auth_client_interceptor.py +2 -1
- flwr/client/message_handler/message_handler.py +2 -2
- flwr/client/mod/secure_aggregation/secaggplus_mod.py +3 -3
- flwr/client/numpy_client.py +1 -1
- flwr/client/rest_client/connection.py +18 -18
- flwr/client/run_info_store.py +4 -5
- flwr/client/typing.py +1 -1
- flwr/clientapp/client_app.py +9 -10
- flwr/clientapp/mod/centraldp_mods.py +16 -17
- flwr/clientapp/mod/localdp_mod.py +8 -9
- flwr/clientapp/typing.py +1 -1
- flwr/clientapp/utils.py +3 -3
- flwr/common/address.py +1 -2
- flwr/common/args.py +3 -4
- flwr/common/config.py +13 -16
- flwr/common/constant.py +5 -2
- flwr/common/differential_privacy.py +3 -4
- flwr/common/event_log_plugin/event_log_plugin.py +3 -4
- flwr/common/exit/exit.py +15 -2
- flwr/common/exit/exit_code.py +19 -0
- flwr/common/exit/exit_handler.py +6 -2
- flwr/common/exit/signal_handler.py +5 -5
- flwr/common/grpc.py +6 -6
- flwr/common/inflatable_protobuf_utils.py +1 -1
- flwr/common/inflatable_utils.py +38 -21
- flwr/common/logger.py +19 -19
- flwr/common/message.py +4 -4
- flwr/common/object_ref.py +7 -7
- flwr/common/record/array.py +3 -3
- flwr/common/record/arrayrecord.py +18 -30
- flwr/common/record/configrecord.py +3 -3
- flwr/common/record/recorddict.py +5 -5
- flwr/common/record/typeddict.py +9 -2
- flwr/common/recorddict_compat.py +7 -10
- flwr/common/retry_invoker.py +20 -20
- flwr/common/secure_aggregation/ndarrays_arithmetic.py +3 -3
- flwr/common/serde.py +11 -4
- flwr/common/serde_utils.py +2 -2
- flwr/common/telemetry.py +9 -5
- flwr/common/typing.py +58 -37
- flwr/compat/client/app.py +38 -37
- flwr/compat/client/grpc_client/connection.py +11 -11
- flwr/compat/server/app.py +5 -6
- flwr/proto/appio_pb2.py +13 -3
- flwr/proto/appio_pb2.pyi +134 -65
- flwr/proto/appio_pb2_grpc.py +20 -0
- flwr/proto/appio_pb2_grpc.pyi +27 -0
- flwr/proto/clientappio_pb2.py +17 -7
- flwr/proto/clientappio_pb2.pyi +15 -0
- flwr/proto/clientappio_pb2_grpc.py +206 -40
- flwr/proto/clientappio_pb2_grpc.pyi +168 -53
- flwr/proto/control_pb2.py +71 -52
- flwr/proto/control_pb2.pyi +277 -111
- flwr/proto/control_pb2_grpc.py +249 -40
- flwr/proto/control_pb2_grpc.pyi +185 -52
- flwr/proto/error_pb2.py +13 -3
- flwr/proto/error_pb2.pyi +24 -6
- flwr/proto/error_pb2_grpc.py +20 -0
- flwr/proto/error_pb2_grpc.pyi +27 -0
- flwr/proto/fab_pb2.py +14 -4
- flwr/proto/fab_pb2.pyi +59 -31
- flwr/proto/fab_pb2_grpc.py +20 -0
- flwr/proto/fab_pb2_grpc.pyi +27 -0
- flwr/proto/federation_pb2.py +38 -0
- flwr/proto/federation_pb2.pyi +56 -0
- flwr/proto/federation_pb2_grpc.py +24 -0
- flwr/proto/federation_pb2_grpc.pyi +31 -0
- flwr/proto/fleet_pb2.py +24 -14
- flwr/proto/fleet_pb2.pyi +141 -61
- flwr/proto/fleet_pb2_grpc.py +189 -48
- flwr/proto/fleet_pb2_grpc.pyi +175 -61
- flwr/proto/grpcadapter_pb2.py +14 -4
- flwr/proto/grpcadapter_pb2.pyi +38 -16
- flwr/proto/grpcadapter_pb2_grpc.py +35 -4
- flwr/proto/grpcadapter_pb2_grpc.pyi +38 -7
- flwr/proto/heartbeat_pb2.py +17 -7
- flwr/proto/heartbeat_pb2.pyi +51 -22
- flwr/proto/heartbeat_pb2_grpc.py +20 -0
- flwr/proto/heartbeat_pb2_grpc.pyi +27 -0
- flwr/proto/log_pb2.py +13 -3
- flwr/proto/log_pb2.pyi +34 -11
- flwr/proto/log_pb2_grpc.py +20 -0
- flwr/proto/log_pb2_grpc.pyi +27 -0
- flwr/proto/message_pb2.py +15 -5
- flwr/proto/message_pb2.pyi +154 -86
- flwr/proto/message_pb2_grpc.py +20 -0
- flwr/proto/message_pb2_grpc.pyi +27 -0
- flwr/proto/node_pb2.py +15 -5
- flwr/proto/node_pb2.pyi +50 -25
- flwr/proto/node_pb2_grpc.py +20 -0
- flwr/proto/node_pb2_grpc.pyi +27 -0
- flwr/proto/recorddict_pb2.py +13 -3
- flwr/proto/recorddict_pb2.pyi +184 -107
- flwr/proto/recorddict_pb2_grpc.py +20 -0
- flwr/proto/recorddict_pb2_grpc.pyi +27 -0
- flwr/proto/run_pb2.py +40 -31
- flwr/proto/run_pb2.pyi +158 -84
- flwr/proto/run_pb2_grpc.py +20 -0
- flwr/proto/run_pb2_grpc.pyi +27 -0
- flwr/proto/serverappio_pb2.py +13 -3
- flwr/proto/serverappio_pb2.pyi +32 -8
- flwr/proto/serverappio_pb2_grpc.py +246 -65
- flwr/proto/serverappio_pb2_grpc.pyi +221 -85
- flwr/proto/simulationio_pb2.py +16 -8
- flwr/proto/simulationio_pb2.pyi +15 -0
- flwr/proto/simulationio_pb2_grpc.py +162 -41
- flwr/proto/simulationio_pb2_grpc.pyi +149 -55
- flwr/proto/transport_pb2.py +20 -10
- flwr/proto/transport_pb2.pyi +249 -160
- flwr/proto/transport_pb2_grpc.py +35 -4
- flwr/proto/transport_pb2_grpc.pyi +38 -8
- flwr/server/app.py +39 -17
- flwr/server/client_manager.py +4 -5
- flwr/server/client_proxy.py +10 -11
- flwr/server/compat/app.py +4 -5
- flwr/server/compat/app_utils.py +2 -1
- flwr/server/compat/grid_client_proxy.py +10 -12
- flwr/server/compat/legacy_context.py +3 -4
- flwr/server/fleet_event_log_interceptor.py +2 -1
- flwr/server/grid/grid.py +2 -3
- flwr/server/grid/grpc_grid.py +10 -8
- flwr/server/grid/inmemory_grid.py +4 -4
- flwr/server/run_serverapp.py +2 -3
- flwr/server/server.py +34 -39
- flwr/server/server_app.py +7 -8
- flwr/server/server_config.py +1 -2
- flwr/server/serverapp/app.py +34 -28
- flwr/server/serverapp_components.py +4 -5
- flwr/server/strategy/aggregate.py +9 -8
- flwr/server/strategy/bulyan.py +13 -11
- flwr/server/strategy/dp_adaptive_clipping.py +16 -20
- flwr/server/strategy/dp_fixed_clipping.py +12 -17
- flwr/server/strategy/dpfedavg_adaptive.py +3 -4
- flwr/server/strategy/dpfedavg_fixed.py +6 -10
- flwr/server/strategy/fault_tolerant_fedavg.py +14 -13
- flwr/server/strategy/fedadagrad.py +18 -14
- flwr/server/strategy/fedadam.py +16 -14
- flwr/server/strategy/fedavg.py +16 -17
- flwr/server/strategy/fedavg_android.py +15 -15
- flwr/server/strategy/fedavgm.py +21 -18
- flwr/server/strategy/fedmedian.py +2 -3
- flwr/server/strategy/fedopt.py +11 -10
- flwr/server/strategy/fedprox.py +10 -9
- flwr/server/strategy/fedtrimmedavg.py +12 -11
- flwr/server/strategy/fedxgb_bagging.py +13 -11
- flwr/server/strategy/fedxgb_cyclic.py +6 -6
- flwr/server/strategy/fedxgb_nn_avg.py +4 -4
- flwr/server/strategy/fedyogi.py +16 -14
- flwr/server/strategy/krum.py +12 -11
- flwr/server/strategy/qfedavg.py +16 -15
- flwr/server/strategy/strategy.py +6 -9
- flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +2 -1
- flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -2
- flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py +3 -4
- flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py +10 -12
- flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +1 -3
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +4 -4
- flwr/server/superlink/fleet/grpc_rere/node_auth_server_interceptor.py +3 -2
- flwr/server/superlink/fleet/message_handler/message_handler.py +75 -30
- flwr/server/superlink/fleet/rest_rere/rest_api.py +2 -2
- flwr/server/superlink/fleet/vce/backend/backend.py +1 -1
- flwr/server/superlink/fleet/vce/backend/raybackend.py +5 -5
- flwr/server/superlink/fleet/vce/vce_api.py +15 -9
- flwr/server/superlink/linkstate/in_memory_linkstate.py +148 -149
- flwr/server/superlink/linkstate/linkstate.py +91 -43
- flwr/server/superlink/linkstate/linkstate_factory.py +22 -5
- flwr/server/superlink/linkstate/sqlite_linkstate.py +502 -436
- flwr/server/superlink/linkstate/utils.py +6 -6
- flwr/server/superlink/serverappio/serverappio_grpc.py +1 -2
- flwr/server/superlink/serverappio/serverappio_servicer.py +26 -21
- flwr/server/superlink/simulation/simulationio_grpc.py +1 -2
- flwr/server/superlink/simulation/simulationio_servicer.py +18 -13
- flwr/server/superlink/utils.py +4 -6
- flwr/server/typing.py +1 -1
- flwr/server/utils/tensorboard.py +15 -8
- flwr/server/workflow/default_workflows.py +5 -5
- flwr/server/workflow/secure_aggregation/secagg_workflow.py +2 -4
- flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +8 -8
- flwr/serverapp/strategy/bulyan.py +16 -15
- flwr/serverapp/strategy/dp_adaptive_clipping.py +12 -11
- flwr/serverapp/strategy/dp_fixed_clipping.py +11 -14
- flwr/serverapp/strategy/fedadagrad.py +10 -11
- flwr/serverapp/strategy/fedadam.py +10 -11
- flwr/serverapp/strategy/fedavg.py +9 -10
- flwr/serverapp/strategy/fedavgm.py +17 -16
- flwr/serverapp/strategy/fedmedian.py +2 -2
- flwr/serverapp/strategy/fedopt.py +10 -11
- flwr/serverapp/strategy/fedprox.py +7 -8
- flwr/serverapp/strategy/fedtrimmedavg.py +9 -9
- flwr/serverapp/strategy/fedxgb_bagging.py +3 -3
- flwr/serverapp/strategy/fedxgb_cyclic.py +9 -9
- flwr/serverapp/strategy/fedyogi.py +9 -11
- flwr/serverapp/strategy/krum.py +7 -7
- flwr/serverapp/strategy/multikrum.py +9 -9
- flwr/serverapp/strategy/qfedavg.py +17 -16
- flwr/serverapp/strategy/strategy.py +6 -9
- flwr/serverapp/strategy/strategy_utils.py +7 -8
- flwr/simulation/app.py +46 -42
- flwr/simulation/legacy_app.py +12 -12
- flwr/simulation/ray_transport/ray_actor.py +10 -11
- flwr/simulation/ray_transport/ray_client_proxy.py +11 -12
- flwr/simulation/run_simulation.py +43 -43
- flwr/simulation/simulationio_connection.py +4 -4
- flwr/supercore/cli/flower_superexec.py +3 -4
- flwr/supercore/constant.py +34 -1
- flwr/supercore/corestate/corestate.py +24 -3
- flwr/supercore/corestate/in_memory_corestate.py +138 -0
- flwr/supercore/corestate/sqlite_corestate.py +157 -0
- flwr/supercore/ffs/disk_ffs.py +1 -2
- flwr/supercore/ffs/ffs.py +1 -2
- flwr/supercore/ffs/ffs_factory.py +1 -2
- flwr/{common → supercore}/heartbeat.py +20 -25
- flwr/supercore/object_store/in_memory_object_store.py +1 -2
- flwr/supercore/object_store/object_store.py +1 -2
- flwr/supercore/object_store/object_store_factory.py +1 -2
- flwr/supercore/object_store/sqlite_object_store.py +8 -7
- flwr/supercore/primitives/asymmetric.py +1 -1
- flwr/supercore/primitives/asymmetric_ed25519.py +11 -1
- flwr/supercore/sqlite_mixin.py +37 -34
- flwr/supercore/superexec/plugin/base_exec_plugin.py +1 -2
- flwr/supercore/superexec/plugin/exec_plugin.py +3 -3
- flwr/supercore/superexec/run_superexec.py +9 -13
- flwr/supercore/utils.py +190 -0
- flwr/superlink/artifact_provider/artifact_provider.py +1 -2
- flwr/superlink/auth_plugin/auth_plugin.py +6 -9
- flwr/superlink/auth_plugin/noop_auth_plugin.py +6 -9
- flwr/{cli/new/templates/app → superlink/federation}/__init__.py +10 -1
- flwr/superlink/federation/federation_manager.py +64 -0
- flwr/superlink/federation/noop_federation_manager.py +71 -0
- flwr/superlink/servicer/control/control_account_auth_interceptor.py +22 -13
- flwr/superlink/servicer/control/control_event_log_interceptor.py +7 -7
- flwr/superlink/servicer/control/control_grpc.py +7 -6
- flwr/superlink/servicer/control/control_license_interceptor.py +3 -3
- flwr/superlink/servicer/control/control_servicer.py +190 -23
- flwr/supernode/cli/flower_supernode.py +58 -3
- flwr/supernode/nodestate/in_memory_nodestate.py +121 -49
- flwr/supernode/nodestate/nodestate.py +52 -8
- flwr/supernode/nodestate/nodestate_factory.py +7 -4
- flwr/supernode/runtime/run_clientapp.py +41 -22
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +46 -10
- flwr/supernode/start_client_internal.py +165 -46
- {flwr-1.23.0.dist-info → flwr-1.25.0.dist-info}/METADATA +9 -11
- flwr-1.25.0.dist-info/RECORD +393 -0
- flwr/cli/new/templates/app/.gitignore.tpl +0 -163
- flwr/cli/new/templates/app/LICENSE.tpl +0 -202
- flwr/cli/new/templates/app/README.baseline.md.tpl +0 -127
- flwr/cli/new/templates/app/README.flowertune.md.tpl +0 -68
- flwr/cli/new/templates/app/README.md.tpl +0 -37
- flwr/cli/new/templates/app/code/__init__.baseline.py.tpl +0 -1
- flwr/cli/new/templates/app/code/__init__.py +0 -15
- flwr/cli/new/templates/app/code/__init__.py.tpl +0 -1
- flwr/cli/new/templates/app/code/__init__.pytorch_legacy_api.py.tpl +0 -1
- flwr/cli/new/templates/app/code/client.baseline.py.tpl +0 -75
- flwr/cli/new/templates/app/code/client.huggingface.py.tpl +0 -93
- flwr/cli/new/templates/app/code/client.jax.py.tpl +0 -71
- flwr/cli/new/templates/app/code/client.mlx.py.tpl +0 -102
- flwr/cli/new/templates/app/code/client.numpy.py.tpl +0 -46
- flwr/cli/new/templates/app/code/client.pytorch.py.tpl +0 -80
- flwr/cli/new/templates/app/code/client.pytorch_legacy_api.py.tpl +0 -55
- flwr/cli/new/templates/app/code/client.sklearn.py.tpl +0 -108
- flwr/cli/new/templates/app/code/client.tensorflow.py.tpl +0 -82
- flwr/cli/new/templates/app/code/client.xgboost.py.tpl +0 -110
- flwr/cli/new/templates/app/code/dataset.baseline.py.tpl +0 -36
- flwr/cli/new/templates/app/code/flwr_tune/client_app.py.tpl +0 -92
- flwr/cli/new/templates/app/code/flwr_tune/dataset.py.tpl +0 -87
- flwr/cli/new/templates/app/code/flwr_tune/models.py.tpl +0 -56
- flwr/cli/new/templates/app/code/flwr_tune/server_app.py.tpl +0 -73
- flwr/cli/new/templates/app/code/flwr_tune/strategy.py.tpl +0 -78
- flwr/cli/new/templates/app/code/model.baseline.py.tpl +0 -66
- flwr/cli/new/templates/app/code/server.baseline.py.tpl +0 -43
- flwr/cli/new/templates/app/code/server.huggingface.py.tpl +0 -42
- flwr/cli/new/templates/app/code/server.jax.py.tpl +0 -39
- flwr/cli/new/templates/app/code/server.mlx.py.tpl +0 -41
- flwr/cli/new/templates/app/code/server.numpy.py.tpl +0 -38
- flwr/cli/new/templates/app/code/server.pytorch.py.tpl +0 -41
- flwr/cli/new/templates/app/code/server.pytorch_legacy_api.py.tpl +0 -31
- flwr/cli/new/templates/app/code/server.sklearn.py.tpl +0 -44
- flwr/cli/new/templates/app/code/server.tensorflow.py.tpl +0 -38
- flwr/cli/new/templates/app/code/server.xgboost.py.tpl +0 -56
- flwr/cli/new/templates/app/code/strategy.baseline.py.tpl +0 -1
- flwr/cli/new/templates/app/code/task.huggingface.py.tpl +0 -98
- flwr/cli/new/templates/app/code/task.jax.py.tpl +0 -57
- flwr/cli/new/templates/app/code/task.mlx.py.tpl +0 -102
- flwr/cli/new/templates/app/code/task.numpy.py.tpl +0 -7
- flwr/cli/new/templates/app/code/task.pytorch.py.tpl +0 -98
- flwr/cli/new/templates/app/code/task.pytorch_legacy_api.py.tpl +0 -111
- flwr/cli/new/templates/app/code/task.sklearn.py.tpl +0 -67
- flwr/cli/new/templates/app/code/task.tensorflow.py.tpl +0 -52
- flwr/cli/new/templates/app/code/task.xgboost.py.tpl +0 -67
- flwr/cli/new/templates/app/code/utils.baseline.py.tpl +0 -1
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +0 -146
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +0 -80
- flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +0 -65
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +0 -52
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +0 -56
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +0 -49
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +0 -53
- flwr/cli/new/templates/app/pyproject.pytorch_legacy_api.toml.tpl +0 -53
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +0 -52
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +0 -53
- flwr/cli/new/templates/app/pyproject.xgboost.toml.tpl +0 -61
- flwr/supercore/object_store/utils.py +0 -43
- flwr-1.23.0.dist-info/RECORD +0 -439
- {flwr-1.23.0.dist-info → flwr-1.25.0.dist-info}/WHEEL +0 -0
- {flwr-1.23.0.dist-info → flwr-1.25.0.dist-info}/entry_points.txt +0 -0
|
@@ -15,23 +15,26 @@
|
|
|
15
15
|
"""Main loop for Flower SuperNode."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
import hashlib
|
|
19
|
+
import json
|
|
18
20
|
import os
|
|
19
21
|
import subprocess
|
|
20
22
|
import time
|
|
21
|
-
from collections.abc import Iterator
|
|
23
|
+
from collections.abc import Callable, Iterator
|
|
22
24
|
from contextlib import contextmanager
|
|
23
25
|
from functools import partial
|
|
24
|
-
from logging import INFO
|
|
26
|
+
from logging import ERROR, INFO, WARN
|
|
25
27
|
from pathlib import Path
|
|
26
|
-
from typing import
|
|
28
|
+
from typing import cast
|
|
27
29
|
|
|
28
30
|
import grpc
|
|
29
|
-
from cryptography.hazmat.primitives.asymmetric import ec
|
|
31
|
+
from cryptography.hazmat.primitives.asymmetric import ec, ed25519
|
|
32
|
+
from cryptography.hazmat.primitives.serialization.ssh import load_ssh_public_key
|
|
30
33
|
from grpc import RpcError
|
|
31
34
|
|
|
32
35
|
from flwr.client.grpc_adapter_client.connection import grpc_adapter
|
|
33
36
|
from flwr.client.grpc_rere_client.connection import grpc_request_response
|
|
34
|
-
from flwr.common import GRPC_MAX_MESSAGE_LENGTH, Context, Message, RecordDict
|
|
37
|
+
from flwr.common import GRPC_MAX_MESSAGE_LENGTH, Context, Error, Message, RecordDict
|
|
35
38
|
from flwr.common.address import parse_address
|
|
36
39
|
from flwr.common.config import get_flwr_dir, get_fused_config_from_fab
|
|
37
40
|
from flwr.common.constant import (
|
|
@@ -41,11 +44,17 @@ from flwr.common.constant import (
|
|
|
41
44
|
TRANSPORT_TYPE_GRPC_RERE,
|
|
42
45
|
TRANSPORT_TYPE_REST,
|
|
43
46
|
TRANSPORT_TYPES,
|
|
47
|
+
ErrorCode,
|
|
44
48
|
ExecPluginType,
|
|
45
49
|
)
|
|
46
50
|
from flwr.common.exit import ExitCode, flwr_exit, register_signal_handlers
|
|
47
51
|
from flwr.common.grpc import generic_create_grpc_server
|
|
48
|
-
from flwr.common.inflatable import
|
|
52
|
+
from flwr.common.inflatable import (
|
|
53
|
+
get_all_nested_objects,
|
|
54
|
+
get_object_tree,
|
|
55
|
+
iterate_object_tree,
|
|
56
|
+
no_object_id_recompute,
|
|
57
|
+
)
|
|
49
58
|
from flwr.common.inflatable_utils import (
|
|
50
59
|
pull_objects,
|
|
51
60
|
push_object_contents_from_iterable,
|
|
@@ -60,11 +69,18 @@ from flwr.proto.message_pb2 import ObjectTree # pylint: disable=E0611
|
|
|
60
69
|
from flwr.supercore.ffs import Ffs, FfsFactory
|
|
61
70
|
from flwr.supercore.grpc_health import run_health_server_grpc_no_tls
|
|
62
71
|
from flwr.supercore.object_store import ObjectStore, ObjectStoreFactory
|
|
72
|
+
from flwr.supercore.primitives.asymmetric_ed25519 import (
|
|
73
|
+
create_message_to_sign,
|
|
74
|
+
decode_base64url,
|
|
75
|
+
verify_signature,
|
|
76
|
+
)
|
|
63
77
|
from flwr.supernode.nodestate import NodeState, NodeStateFactory
|
|
64
78
|
from flwr.supernode.servicer.clientappio import ClientAppIoServicer
|
|
65
79
|
|
|
66
80
|
DEFAULT_FFS_DIR = get_flwr_dir() / "supernode" / "ffs"
|
|
67
81
|
|
|
82
|
+
FAB_VERIFICATION_ERROR = Error(ErrorCode.INVALID_FAB, "The FAB could not be verified.")
|
|
83
|
+
|
|
68
84
|
|
|
69
85
|
# pylint: disable=import-outside-toplevel
|
|
70
86
|
# pylint: disable=too-many-branches
|
|
@@ -75,18 +91,19 @@ def start_client_internal(
|
|
|
75
91
|
*,
|
|
76
92
|
server_address: str,
|
|
77
93
|
node_config: UserConfig,
|
|
78
|
-
root_certificates:
|
|
79
|
-
insecure:
|
|
94
|
+
root_certificates: bytes | str | None = None,
|
|
95
|
+
insecure: bool | None = None,
|
|
80
96
|
transport: str,
|
|
81
|
-
authentication_keys:
|
|
82
|
-
tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]
|
|
83
|
-
|
|
84
|
-
max_retries:
|
|
85
|
-
max_wait_time:
|
|
86
|
-
flwr_path:
|
|
97
|
+
authentication_keys: (
|
|
98
|
+
tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey] | None
|
|
99
|
+
) = None,
|
|
100
|
+
max_retries: int | None = None,
|
|
101
|
+
max_wait_time: float | None = None,
|
|
102
|
+
flwr_path: Path | None = None,
|
|
87
103
|
isolation: str = ISOLATION_MODE_SUBPROCESS,
|
|
88
104
|
clientappio_api_address: str = CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS,
|
|
89
|
-
health_server_address:
|
|
105
|
+
health_server_address: str | None = None,
|
|
106
|
+
trusted_entities: dict[str, str] | None = None,
|
|
90
107
|
) -> None:
|
|
91
108
|
"""Start a Flower client node which connects to a Flower server.
|
|
92
109
|
|
|
@@ -138,6 +155,10 @@ def start_client_internal(
|
|
|
138
155
|
health_server_address : Optional[str] (default: None)
|
|
139
156
|
The address of the health server. If `None` is provided, the health server will
|
|
140
157
|
NOT be started.
|
|
158
|
+
trusted_entities : Optional[dict[str, str]] (default: None)
|
|
159
|
+
A dictionary mapping public key IDs to public keys.
|
|
160
|
+
Only apps verified by at least one of these
|
|
161
|
+
entities can run on a supernode.
|
|
141
162
|
"""
|
|
142
163
|
if insecure is None:
|
|
143
164
|
insecure = root_certificates is None
|
|
@@ -156,9 +177,9 @@ def start_client_internal(
|
|
|
156
177
|
)
|
|
157
178
|
|
|
158
179
|
# Initialize factories
|
|
159
|
-
state_factory = NodeStateFactory()
|
|
160
|
-
ffs_factory = FfsFactory(get_flwr_dir(flwr_path) / "supernode" / "ffs") # type: ignore
|
|
161
180
|
object_store_factory = ObjectStoreFactory()
|
|
181
|
+
state_factory = NodeStateFactory(objectstore_factory=object_store_factory)
|
|
182
|
+
ffs_factory = FfsFactory(get_flwr_dir(flwr_path) / "supernode" / "ffs") # type: ignore
|
|
162
183
|
|
|
163
184
|
# Launch ClientAppIo API server
|
|
164
185
|
grpc_servers = []
|
|
@@ -218,6 +239,7 @@ def start_client_internal(
|
|
|
218
239
|
) = conn
|
|
219
240
|
# Store node_id in state
|
|
220
241
|
state.set_node_id(node_id)
|
|
242
|
+
log(INFO, "SuperNode ID: %s", node_id)
|
|
221
243
|
|
|
222
244
|
# pylint: disable=too-many-nested-blocks
|
|
223
245
|
while True:
|
|
@@ -233,6 +255,7 @@ def start_client_internal(
|
|
|
233
255
|
get_fab=get_fab,
|
|
234
256
|
pull_object=pull_object,
|
|
235
257
|
confirm_message_received=confirm_message_received,
|
|
258
|
+
trusted_entities=trusted_entities,
|
|
236
259
|
)
|
|
237
260
|
|
|
238
261
|
# No message has been pulled therefore we can skip the push stage.
|
|
@@ -249,17 +272,34 @@ def start_client_internal(
|
|
|
249
272
|
)
|
|
250
273
|
|
|
251
274
|
|
|
275
|
+
def _insert_message(msg: Message, state: NodeState, store: ObjectStore) -> None:
|
|
276
|
+
"""Insert a message into the NodeState and ObjectStore."""
|
|
277
|
+
with no_object_id_recompute():
|
|
278
|
+
# Store message in state
|
|
279
|
+
msg.metadata.__dict__["_message_id"] = msg.object_id # Set message_id
|
|
280
|
+
state.store_message(msg)
|
|
281
|
+
|
|
282
|
+
# Preregister objects in ObjectStore
|
|
283
|
+
store.preregister(msg.metadata.run_id, get_object_tree(msg))
|
|
284
|
+
|
|
285
|
+
# Store all objects in ObjectStore
|
|
286
|
+
all_objects = get_all_nested_objects(msg)
|
|
287
|
+
for obj_id, obj in all_objects.items():
|
|
288
|
+
store.put(obj_id, obj.deflate())
|
|
289
|
+
|
|
290
|
+
|
|
252
291
|
def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
|
|
253
292
|
state: NodeState,
|
|
254
293
|
ffs: Ffs,
|
|
255
294
|
object_store: ObjectStore,
|
|
256
295
|
node_config: UserConfig,
|
|
257
|
-
receive: Callable[[],
|
|
296
|
+
receive: Callable[[], tuple[Message, ObjectTree] | None],
|
|
258
297
|
get_run: Callable[[int], Run],
|
|
259
298
|
get_fab: Callable[[str, int], Fab],
|
|
260
299
|
pull_object: Callable[[int, str], bytes],
|
|
261
300
|
confirm_message_received: Callable[[int, str], None],
|
|
262
|
-
|
|
301
|
+
trusted_entities: dict[str, str] | None,
|
|
302
|
+
) -> int | None:
|
|
263
303
|
"""Pull a message from the SuperLink and store it in the state.
|
|
264
304
|
|
|
265
305
|
This function current returns None if no message is received,
|
|
@@ -267,6 +307,7 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
|
|
|
267
307
|
This behavior will change in the future to return None after
|
|
268
308
|
completing transition to the `NodeState`-based SuperNode.
|
|
269
309
|
"""
|
|
310
|
+
# pylint: disable=too-many-nested-blocks
|
|
270
311
|
message = None
|
|
271
312
|
try:
|
|
272
313
|
# Pull message
|
|
@@ -287,7 +328,7 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
|
|
|
287
328
|
log(INFO, "[RUN %s]", message.metadata.run_id)
|
|
288
329
|
log(
|
|
289
330
|
INFO,
|
|
290
|
-
"
|
|
331
|
+
"Receiving: %s message (ID: %s)",
|
|
291
332
|
message.metadata.message_type,
|
|
292
333
|
message.metadata.message_id,
|
|
293
334
|
)
|
|
@@ -299,11 +340,32 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
|
|
|
299
340
|
if (run_info := state.get_run(run_id)) is None:
|
|
300
341
|
# Pull run info from SuperLink
|
|
301
342
|
run_info = get_run(run_id)
|
|
302
|
-
state.store_run(run_info)
|
|
303
343
|
|
|
304
344
|
# Pull and store the FAB
|
|
305
345
|
fab = get_fab(run_info.fab_hash, run_id)
|
|
306
|
-
|
|
346
|
+
|
|
347
|
+
# Verify the received FAB
|
|
348
|
+
# FAB must be signed if trust entities provided
|
|
349
|
+
if trusted_entities:
|
|
350
|
+
if not fab.verifications.get("valid_license", ""):
|
|
351
|
+
log(
|
|
352
|
+
WARN,
|
|
353
|
+
"App verification is not supported by the connected SuperLink.",
|
|
354
|
+
)
|
|
355
|
+
else:
|
|
356
|
+
fab_verified = _verify_fab(fab, trusted_entities)
|
|
357
|
+
if not fab_verified:
|
|
358
|
+
# Insert an error message in the state
|
|
359
|
+
# when FAB verification fails
|
|
360
|
+
log(
|
|
361
|
+
ERROR,
|
|
362
|
+
"FAB verification failed: the provided trusted entities "
|
|
363
|
+
"could not verify the FAB. An error reply "
|
|
364
|
+
"has been generated.",
|
|
365
|
+
)
|
|
366
|
+
reply = Message(FAB_VERIFICATION_ERROR, reply_to=message)
|
|
367
|
+
_insert_message(reply, state, object_store)
|
|
368
|
+
return run_id
|
|
307
369
|
|
|
308
370
|
# Initialize the context
|
|
309
371
|
run_cfg = get_fused_config_from_fab(fab.content, run_info)
|
|
@@ -314,7 +376,11 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
|
|
|
314
376
|
state=RecordDict(),
|
|
315
377
|
run_config=run_cfg,
|
|
316
378
|
)
|
|
379
|
+
|
|
380
|
+
# Store in the state
|
|
317
381
|
state.store_context(run_ctx)
|
|
382
|
+
state.store_run(run_info)
|
|
383
|
+
ffs.put(fab.content, fab.verifications)
|
|
318
384
|
|
|
319
385
|
# Preregister the object tree of the message
|
|
320
386
|
obj_ids_to_pull = object_store.preregister(run_id, object_tree)
|
|
@@ -322,16 +388,27 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
|
|
|
322
388
|
# Store the message in the state (note this message has no content)
|
|
323
389
|
state.store_message(message)
|
|
324
390
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
391
|
+
try:
|
|
392
|
+
# Pull and store objects of the message in the ObjectStore
|
|
393
|
+
obj_contents = pull_objects(
|
|
394
|
+
obj_ids_to_pull,
|
|
395
|
+
pull_object_fn=lambda obj_id: pull_object(run_id, obj_id),
|
|
396
|
+
)
|
|
397
|
+
for obj_id in list(obj_contents.keys()):
|
|
398
|
+
object_store.put(obj_id, obj_contents.pop(obj_id))
|
|
332
399
|
|
|
333
|
-
|
|
334
|
-
|
|
400
|
+
# Confirm that the message was received
|
|
401
|
+
confirm_message_received(run_id, message.metadata.message_id)
|
|
402
|
+
log(INFO, "Received successfully")
|
|
403
|
+
except Exception as err: # pylint: disable=broad-except
|
|
404
|
+
log(
|
|
405
|
+
ERROR,
|
|
406
|
+
"Failed to receive message %s: %s",
|
|
407
|
+
message.metadata.message_id,
|
|
408
|
+
err,
|
|
409
|
+
)
|
|
410
|
+
state.delete_messages(message_ids=[message.metadata.message_id])
|
|
411
|
+
object_store.delete(message.metadata.message_id)
|
|
335
412
|
|
|
336
413
|
except RunNotRunningException:
|
|
337
414
|
if message is None:
|
|
@@ -355,7 +432,7 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
|
|
|
355
432
|
def _push_messages(
|
|
356
433
|
state: NodeState,
|
|
357
434
|
object_store: ObjectStore,
|
|
358
|
-
send: Callable[[Message, ObjectTree], set[str]],
|
|
435
|
+
send: Callable[[Message, ObjectTree, float], set[str]],
|
|
359
436
|
push_object: Callable[[int, str, bytes], None],
|
|
360
437
|
) -> None:
|
|
361
438
|
"""Push reply messages to the SuperLink."""
|
|
@@ -378,8 +455,9 @@ def _push_messages(
|
|
|
378
455
|
log(INFO, "[RUN %s]", message.metadata.run_id)
|
|
379
456
|
log(
|
|
380
457
|
INFO,
|
|
381
|
-
"Sending: %s message",
|
|
458
|
+
"Sending: %s message (ID: %s)",
|
|
382
459
|
message.metadata.message_type,
|
|
460
|
+
message.metadata.message_id,
|
|
383
461
|
)
|
|
384
462
|
|
|
385
463
|
# Get the object tree for the message
|
|
@@ -402,9 +480,12 @@ def _push_messages(
|
|
|
402
480
|
|
|
403
481
|
# Send the message
|
|
404
482
|
try:
|
|
405
|
-
|
|
483
|
+
clientapp_runtime = state.get_message_processing_duration(
|
|
484
|
+
message_id=message.metadata.reply_to_message_id,
|
|
485
|
+
)
|
|
486
|
+
# Send the reply message with its ObjectTree and ClientApp runtime
|
|
406
487
|
# Get the IDs of objects to send
|
|
407
|
-
ids_obj_to_send = send(message, object_tree)
|
|
488
|
+
ids_obj_to_send = send(message, object_tree, clientapp_runtime)
|
|
408
489
|
|
|
409
490
|
# Push object contents from the ObjectStore
|
|
410
491
|
run_id = message.metadata.run_id
|
|
@@ -424,6 +505,13 @@ def _push_messages(
|
|
|
424
505
|
message.metadata.run_id,
|
|
425
506
|
message.metadata.message_id,
|
|
426
507
|
)
|
|
508
|
+
except Exception as err: # pylint: disable=broad-except
|
|
509
|
+
log(
|
|
510
|
+
ERROR,
|
|
511
|
+
"Failed to send message %s: %s",
|
|
512
|
+
message.metadata.message_id,
|
|
513
|
+
err,
|
|
514
|
+
)
|
|
427
515
|
finally:
|
|
428
516
|
# Delete the message from the state
|
|
429
517
|
state.delete_messages(
|
|
@@ -444,17 +532,17 @@ def _init_connection( # pylint: disable=too-many-positional-arguments
|
|
|
444
532
|
transport: str,
|
|
445
533
|
server_address: str,
|
|
446
534
|
insecure: bool,
|
|
447
|
-
root_certificates:
|
|
448
|
-
authentication_keys:
|
|
449
|
-
tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]
|
|
450
|
-
|
|
451
|
-
max_retries:
|
|
452
|
-
max_wait_time:
|
|
535
|
+
root_certificates: bytes | str | None = None,
|
|
536
|
+
authentication_keys: (
|
|
537
|
+
tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey] | None
|
|
538
|
+
) = None,
|
|
539
|
+
max_retries: int | None = None,
|
|
540
|
+
max_wait_time: float | None = None,
|
|
453
541
|
) -> Iterator[
|
|
454
542
|
tuple[
|
|
455
543
|
int,
|
|
456
|
-
Callable[[],
|
|
457
|
-
Callable[[Message, ObjectTree], set[str]],
|
|
544
|
+
Callable[[], tuple[Message, ObjectTree] | None],
|
|
545
|
+
Callable[[Message, ObjectTree, float], set[str]],
|
|
458
546
|
Callable[[int], Run],
|
|
459
547
|
Callable[[str, int], Fab],
|
|
460
548
|
Callable[[int, str], bytes],
|
|
@@ -513,8 +601,8 @@ def _init_connection( # pylint: disable=too-many-positional-arguments
|
|
|
513
601
|
|
|
514
602
|
|
|
515
603
|
def _make_fleet_connection_retry_invoker(
|
|
516
|
-
max_retries:
|
|
517
|
-
max_wait_time:
|
|
604
|
+
max_retries: int | None = None,
|
|
605
|
+
max_wait_time: float | None = None,
|
|
518
606
|
connection_error_type: type[Exception] = RpcError,
|
|
519
607
|
) -> RetryInvoker:
|
|
520
608
|
"""Create a retry invoker for fleet connection."""
|
|
@@ -533,7 +621,7 @@ def run_clientappio_api_grpc(
|
|
|
533
621
|
state_factory: NodeStateFactory,
|
|
534
622
|
ffs_factory: FfsFactory,
|
|
535
623
|
objectstore_factory: ObjectStoreFactory,
|
|
536
|
-
certificates:
|
|
624
|
+
certificates: tuple[bytes, bytes, bytes] | None,
|
|
537
625
|
) -> grpc.Server:
|
|
538
626
|
"""Run ClientAppIo API gRPC server."""
|
|
539
627
|
clientappio_servicer: grpc.Server = ClientAppIoServicer(
|
|
@@ -554,3 +642,34 @@ def run_clientappio_api_grpc(
|
|
|
554
642
|
log(INFO, "Flower Deployment Runtime: Starting ClientAppIo API on %s", address)
|
|
555
643
|
clientappio_grpc_server.start()
|
|
556
644
|
return clientappio_grpc_server
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
def _verify_fab(fab: Fab, trusted_entities: dict[str, str]) -> bool:
|
|
648
|
+
"""Verify a FAB using its verification data and the provided trusted entities.
|
|
649
|
+
|
|
650
|
+
The FAB is considered verified if at least one trusted entity matches the
|
|
651
|
+
information contained in its verification records.
|
|
652
|
+
"""
|
|
653
|
+
verifications = fab.verifications
|
|
654
|
+
verif_full = {
|
|
655
|
+
k: json.loads(v) for k, v in verifications.items() if k != "valid_license"
|
|
656
|
+
}
|
|
657
|
+
fab_verified = False
|
|
658
|
+
for public_key_id, verif in verif_full.items():
|
|
659
|
+
if public_key_id in trusted_entities:
|
|
660
|
+
verifier_public_key = load_ssh_public_key(
|
|
661
|
+
trusted_entities[public_key_id].encode("utf-8")
|
|
662
|
+
)
|
|
663
|
+
message_to_verify = create_message_to_sign(
|
|
664
|
+
hashlib.sha256(fab.content).digest(),
|
|
665
|
+
verif["signed_at"],
|
|
666
|
+
)
|
|
667
|
+
assert isinstance(verifier_public_key, ed25519.Ed25519PublicKey)
|
|
668
|
+
if verify_signature(
|
|
669
|
+
verifier_public_key,
|
|
670
|
+
message_to_verify,
|
|
671
|
+
decode_base64url(verif["signature"]),
|
|
672
|
+
):
|
|
673
|
+
fab_verified = True
|
|
674
|
+
break
|
|
675
|
+
return fab_verified
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: flwr
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.25.0
|
|
4
4
|
Summary: Flower: A Friendly Federated AI Framework
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Keywords: Artificial Intelligence,Federated AI,Federated Analytics,Federated Evaluation,Federated Learning,Flower,Machine Learning
|
|
7
7
|
Author: The Flower Authors
|
|
8
8
|
Author-email: hello@flower.ai
|
|
9
|
-
Requires-Python: >=3.
|
|
9
|
+
Requires-Python: >=3.10,<4.0
|
|
10
10
|
Classifier: Development Status :: 5 - Production/Stable
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
12
12
|
Classifier: Intended Audience :: Science/Research
|
|
@@ -20,7 +20,6 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.12
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.13
|
|
22
22
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
23
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
24
23
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
25
24
|
Classifier: Topic :: Scientific/Engineering
|
|
26
25
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
@@ -33,21 +32,22 @@ Provides-Extra: rest
|
|
|
33
32
|
Provides-Extra: simulation
|
|
34
33
|
Requires-Dist: click (<8.2.0)
|
|
35
34
|
Requires-Dist: cryptography (>=44.0.1,<45.0.0)
|
|
36
|
-
Requires-Dist: grpcio (>=1.
|
|
37
|
-
Requires-Dist: grpcio-health-checking (>=1.
|
|
35
|
+
Requires-Dist: grpcio (>=1.70.0,<2.0.0)
|
|
36
|
+
Requires-Dist: grpcio-health-checking (>=1.70.0,<2.0.0)
|
|
38
37
|
Requires-Dist: iterators (>=0.0.2,<0.0.3)
|
|
39
38
|
Requires-Dist: numpy (>=1.26.0,<3.0.0)
|
|
40
39
|
Requires-Dist: pathspec (>=0.12.1,<0.13.0)
|
|
41
|
-
Requires-Dist: protobuf (>=
|
|
40
|
+
Requires-Dist: protobuf (>=5.28.0,<7.0.0)
|
|
42
41
|
Requires-Dist: pycryptodome (>=3.18.0,<4.0.0)
|
|
43
42
|
Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
|
|
44
|
-
Requires-Dist: ray (==2.
|
|
43
|
+
Requires-Dist: ray (==2.51.1) ; (python_version >= "3.10" and python_version < "3.13") and (extra == "simulation")
|
|
44
|
+
Requires-Dist: ray (==2.51.1) ; (sys_platform != "win32" and python_version == "3.13") and (extra == "simulation")
|
|
45
45
|
Requires-Dist: requests (>=2.31.0,<3.0.0)
|
|
46
46
|
Requires-Dist: rich (>=13.5.0,<14.0.0)
|
|
47
47
|
Requires-Dist: starlette (>=0.45.2,<0.46.0) ; extra == "rest"
|
|
48
48
|
Requires-Dist: tomli (>=2.0.1,<3.0.0)
|
|
49
49
|
Requires-Dist: tomli-w (>=1.0.0,<2.0.0)
|
|
50
|
-
Requires-Dist: typer (>=0.12.5,<0.
|
|
50
|
+
Requires-Dist: typer (>=0.12.5,<0.21.0)
|
|
51
51
|
Requires-Dist: uvicorn[standard] (>=0.34.0,<0.35.0) ; extra == "rest"
|
|
52
52
|
Project-URL: Documentation, https://flower.ai
|
|
53
53
|
Project-URL: Homepage, https://flower.ai
|
|
@@ -180,7 +180,7 @@ Quickstart examples:
|
|
|
180
180
|
- [Quickstart (Pandas)](https://github.com/adap/flower/tree/main/examples/quickstart-pandas)
|
|
181
181
|
- [Quickstart (JAX)](https://github.com/adap/flower/tree/main/examples/quickstart-jax)
|
|
182
182
|
- [Quickstart (MONAI)](https://github.com/adap/flower/tree/main/examples/quickstart-monai)
|
|
183
|
-
- [Quickstart (scikit-learn)](https://github.com/adap/flower/tree/main/examples/sklearn
|
|
183
|
+
- [Quickstart (scikit-learn)](https://github.com/adap/flower/tree/main/examples/quickstart-sklearn)
|
|
184
184
|
- [Quickstart (Android [TFLite])](https://github.com/adap/flower/tree/main/examples/android)
|
|
185
185
|
- [Quickstart (iOS [CoreML])](https://github.com/adap/flower/tree/main/examples/ios)
|
|
186
186
|
- [Quickstart (MLX)](https://github.com/adap/flower/tree/main/examples/quickstart-mlx)
|
|
@@ -197,10 +197,8 @@ Other [examples](https://github.com/adap/flower/tree/main/examples):
|
|
|
197
197
|
- [Advanced Flower with TensorFlow/Keras](https://github.com/adap/flower/tree/main/examples/advanced-tensorflow)
|
|
198
198
|
- [Advanced Flower with PyTorch](https://github.com/adap/flower/tree/main/examples/advanced-pytorch)
|
|
199
199
|
- [Comprehensive Flower+XGBoost](https://github.com/adap/flower/tree/main/examples/xgboost-comprehensive)
|
|
200
|
-
- [Flower through Docker Compose and with Grafana dashboard](https://github.com/adap/flower/tree/main/examples/flower-via-docker-compose)
|
|
201
200
|
- [Flower with KaplanMeierFitter from the lifelines library](https://github.com/adap/flower/tree/main/examples/federated-kaplan-meier-fitter)
|
|
202
201
|
- [Sample Level Privacy with Opacus](https://github.com/adap/flower/tree/main/examples/opacus)
|
|
203
|
-
- [Sample Level Privacy with TensorFlow-Privacy](https://github.com/adap/flower/tree/main/examples/tensorflow-privacy)
|
|
204
202
|
- [Flower with a Tabular Dataset](https://github.com/adap/flower/tree/main/examples/fl-tabular)
|
|
205
203
|
|
|
206
204
|
## Community
|