flwr 1.16.0__py3-none-any.whl → 1.18.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 +1 -1
- flwr/cli/__init__.py +1 -1
- flwr/cli/app.py +21 -2
- flwr/cli/build.py +1 -1
- flwr/cli/cli_user_auth_interceptor.py +1 -1
- flwr/cli/config_utils.py +53 -17
- flwr/cli/example.py +1 -1
- flwr/cli/install.py +1 -1
- flwr/cli/log.py +1 -1
- flwr/cli/login/__init__.py +1 -1
- flwr/cli/login/login.py +12 -1
- flwr/cli/ls.py +1 -1
- flwr/cli/new/__init__.py +1 -1
- flwr/cli/new/new.py +4 -4
- flwr/cli/new/templates/__init__.py +1 -1
- flwr/cli/new/templates/app/__init__.py +1 -1
- flwr/cli/new/templates/app/code/__init__.py +1 -1
- flwr/cli/new/templates/app/code/flwr_tune/__init__.py +1 -1
- flwr/cli/new/templates/app/code/flwr_tune/client_app.py.tpl +5 -5
- flwr/cli/new/templates/app/code/task.sklearn.py.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +4 -4
- flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +1 -1
- flwr/cli/run/__init__.py +1 -1
- flwr/cli/run/run.py +6 -10
- flwr/cli/stop.py +1 -1
- flwr/cli/utils.py +11 -12
- flwr/client/__init__.py +1 -1
- flwr/client/app.py +58 -56
- flwr/client/client.py +1 -1
- flwr/client/client_app.py +231 -166
- flwr/client/clientapp/__init__.py +1 -1
- flwr/client/clientapp/app.py +3 -3
- flwr/client/clientapp/clientappio_servicer.py +1 -1
- flwr/client/clientapp/utils.py +1 -1
- flwr/client/dpfedavg_numpy_client.py +1 -1
- flwr/client/grpc_adapter_client/__init__.py +1 -1
- flwr/client/grpc_adapter_client/connection.py +1 -1
- flwr/client/grpc_client/__init__.py +1 -1
- flwr/client/grpc_client/connection.py +37 -34
- flwr/client/grpc_rere_client/__init__.py +1 -1
- flwr/client/grpc_rere_client/client_interceptor.py +1 -1
- flwr/client/grpc_rere_client/connection.py +1 -1
- flwr/client/grpc_rere_client/grpc_adapter.py +1 -1
- flwr/client/heartbeat.py +1 -1
- flwr/client/message_handler/__init__.py +1 -1
- flwr/client/message_handler/message_handler.py +28 -28
- flwr/client/mod/__init__.py +3 -3
- flwr/client/mod/centraldp_mods.py +8 -8
- flwr/client/mod/comms_mods.py +17 -23
- flwr/client/mod/localdp_mod.py +10 -10
- flwr/client/mod/secure_aggregation/__init__.py +1 -1
- flwr/client/mod/secure_aggregation/secagg_mod.py +1 -1
- flwr/client/mod/secure_aggregation/secaggplus_mod.py +32 -32
- flwr/client/mod/utils.py +1 -1
- flwr/client/nodestate/__init__.py +1 -1
- flwr/client/nodestate/in_memory_nodestate.py +1 -1
- flwr/client/nodestate/nodestate.py +1 -1
- flwr/client/nodestate/nodestate_factory.py +1 -1
- flwr/client/numpy_client.py +1 -1
- flwr/client/rest_client/__init__.py +1 -1
- flwr/client/rest_client/connection.py +1 -1
- flwr/client/run_info_store.py +3 -3
- flwr/client/supernode/__init__.py +1 -1
- flwr/client/supernode/app.py +1 -1
- flwr/client/typing.py +1 -1
- flwr/common/__init__.py +13 -5
- flwr/common/address.py +1 -1
- flwr/common/args.py +1 -1
- flwr/common/auth_plugin/__init__.py +1 -1
- flwr/common/auth_plugin/auth_plugin.py +1 -1
- flwr/common/config.py +5 -5
- flwr/common/constant.py +7 -7
- flwr/common/context.py +5 -5
- flwr/common/date.py +1 -1
- flwr/common/differential_privacy.py +1 -1
- flwr/common/differential_privacy_constants.py +1 -1
- flwr/common/dp.py +1 -1
- flwr/common/event_log_plugin/event_log_plugin.py +3 -3
- flwr/common/exit/exit.py +6 -6
- flwr/common/exit_handlers.py +1 -1
- flwr/common/grpc.py +1 -1
- flwr/common/logger.py +3 -3
- flwr/common/message.py +344 -102
- flwr/common/object_ref.py +1 -1
- flwr/common/parameter.py +1 -1
- flwr/common/pyproject.py +1 -1
- flwr/common/record/__init__.py +9 -5
- flwr/common/record/arrayrecord.py +626 -0
- flwr/common/record/{configsrecord.py → configrecord.py} +83 -37
- flwr/common/record/conversion_utils.py +2 -2
- flwr/common/record/{metricsrecord.py → metricrecord.py} +90 -44
- flwr/common/record/recorddict.py +337 -0
- flwr/common/record/typeddict.py +1 -1
- flwr/common/recorddict_compat.py +410 -0
- flwr/common/retry_invoker.py +10 -10
- flwr/common/secure_aggregation/__init__.py +1 -1
- flwr/common/secure_aggregation/crypto/__init__.py +1 -1
- flwr/common/secure_aggregation/crypto/shamir.py +52 -30
- flwr/common/secure_aggregation/crypto/symmetric_encryption.py +1 -1
- flwr/common/secure_aggregation/ndarrays_arithmetic.py +1 -1
- flwr/common/secure_aggregation/quantization.py +1 -1
- flwr/common/secure_aggregation/secaggplus_constants.py +2 -2
- flwr/common/secure_aggregation/secaggplus_utils.py +1 -1
- flwr/common/serde.py +67 -72
- flwr/common/telemetry.py +2 -2
- flwr/common/typing.py +9 -9
- flwr/common/version.py +1 -1
- flwr/proto/__init__.py +1 -1
- flwr/proto/exec_pb2.py +3 -3
- flwr/proto/exec_pb2.pyi +3 -3
- flwr/proto/message_pb2.py +12 -12
- flwr/proto/message_pb2.pyi +9 -9
- flwr/proto/recorddict_pb2.py +70 -0
- flwr/proto/{recordset_pb2.pyi → recorddict_pb2.pyi} +35 -35
- flwr/proto/run_pb2.py +31 -31
- flwr/proto/run_pb2.pyi +3 -3
- flwr/server/__init__.py +4 -2
- flwr/server/app.py +67 -12
- flwr/server/client_manager.py +1 -1
- flwr/server/client_proxy.py +1 -1
- flwr/server/compat/__init__.py +3 -3
- flwr/server/compat/app.py +12 -12
- flwr/server/compat/app_utils.py +17 -17
- flwr/server/compat/{driver_client_proxy.py → grid_client_proxy.py} +39 -39
- flwr/server/compat/legacy_context.py +1 -1
- flwr/server/criterion.py +1 -1
- flwr/server/fleet_event_log_interceptor.py +94 -0
- flwr/server/{driver → grid}/__init__.py +8 -7
- flwr/server/{driver/driver.py → grid/grid.py} +48 -19
- flwr/server/{driver/grpc_driver.py → grid/grpc_grid.py} +87 -64
- flwr/server/{driver/inmemory_driver.py → grid/inmemory_grid.py} +24 -34
- flwr/server/history.py +1 -1
- flwr/server/run_serverapp.py +5 -5
- flwr/server/server.py +1 -1
- flwr/server/server_app.py +98 -71
- flwr/server/server_config.py +1 -1
- flwr/server/serverapp/__init__.py +1 -1
- flwr/server/serverapp/app.py +11 -11
- flwr/server/serverapp_components.py +1 -1
- flwr/server/strategy/__init__.py +1 -1
- flwr/server/strategy/aggregate.py +1 -1
- flwr/server/strategy/bulyan.py +2 -2
- flwr/server/strategy/dp_adaptive_clipping.py +17 -17
- flwr/server/strategy/dp_fixed_clipping.py +17 -17
- flwr/server/strategy/dpfedavg_adaptive.py +1 -1
- flwr/server/strategy/dpfedavg_fixed.py +1 -1
- flwr/server/strategy/fault_tolerant_fedavg.py +1 -1
- flwr/server/strategy/fedadagrad.py +1 -1
- flwr/server/strategy/fedadam.py +1 -1
- flwr/server/strategy/fedavg.py +1 -1
- flwr/server/strategy/fedavg_android.py +1 -1
- flwr/server/strategy/fedavgm.py +1 -1
- flwr/server/strategy/fedmedian.py +1 -1
- flwr/server/strategy/fedopt.py +1 -1
- flwr/server/strategy/fedprox.py +1 -1
- flwr/server/strategy/fedtrimmedavg.py +1 -1
- flwr/server/strategy/fedxgb_bagging.py +1 -1
- flwr/server/strategy/fedxgb_cyclic.py +1 -1
- flwr/server/strategy/fedxgb_nn_avg.py +3 -2
- flwr/server/strategy/fedyogi.py +1 -1
- flwr/server/strategy/krum.py +1 -1
- flwr/server/strategy/qfedavg.py +1 -1
- flwr/server/strategy/strategy.py +1 -1
- flwr/server/superlink/__init__.py +1 -1
- flwr/server/superlink/ffs/__init__.py +1 -1
- flwr/server/superlink/ffs/disk_ffs.py +1 -1
- flwr/server/superlink/ffs/ffs.py +1 -1
- flwr/server/superlink/ffs/ffs_factory.py +1 -1
- flwr/server/superlink/fleet/__init__.py +1 -1
- flwr/server/superlink/fleet/grpc_adapter/__init__.py +1 -1
- flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +1 -1
- flwr/server/superlink/fleet/grpc_bidi/__init__.py +1 -1
- flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -1
- flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py +1 -1
- flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py +1 -1
- flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +13 -13
- flwr/server/superlink/fleet/grpc_rere/__init__.py +1 -1
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +1 -1
- flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +1 -1
- flwr/server/superlink/fleet/message_handler/__init__.py +1 -1
- flwr/server/superlink/fleet/message_handler/message_handler.py +1 -1
- flwr/server/superlink/fleet/rest_rere/__init__.py +1 -1
- flwr/server/superlink/fleet/rest_rere/rest_api.py +1 -1
- flwr/server/superlink/fleet/vce/__init__.py +1 -1
- flwr/server/superlink/fleet/vce/backend/__init__.py +1 -1
- flwr/server/superlink/fleet/vce/backend/backend.py +3 -3
- flwr/server/superlink/fleet/vce/backend/raybackend.py +3 -3
- flwr/server/superlink/fleet/vce/vce_api.py +2 -4
- flwr/server/superlink/linkstate/__init__.py +1 -1
- flwr/server/superlink/linkstate/in_memory_linkstate.py +34 -9
- flwr/server/superlink/linkstate/linkstate.py +5 -5
- flwr/server/superlink/linkstate/linkstate_factory.py +1 -1
- flwr/server/superlink/linkstate/sqlite_linkstate.py +62 -28
- flwr/server/superlink/linkstate/utils.py +94 -28
- flwr/server/superlink/{driver → serverappio}/__init__.py +1 -1
- flwr/server/superlink/{driver → serverappio}/serverappio_grpc.py +1 -1
- flwr/server/superlink/{driver → serverappio}/serverappio_servicer.py +4 -4
- flwr/server/superlink/simulation/__init__.py +1 -1
- flwr/server/superlink/simulation/simulationio_grpc.py +1 -1
- flwr/server/superlink/simulation/simulationio_servicer.py +3 -3
- flwr/server/superlink/utils.py +1 -1
- flwr/server/typing.py +4 -4
- flwr/server/utils/__init__.py +1 -1
- flwr/server/utils/tensorboard.py +1 -1
- flwr/server/utils/validator.py +5 -5
- flwr/server/workflow/__init__.py +1 -1
- flwr/server/workflow/constant.py +1 -1
- flwr/server/workflow/default_workflows.py +49 -58
- flwr/server/workflow/secure_aggregation/__init__.py +1 -1
- flwr/server/workflow/secure_aggregation/secagg_workflow.py +1 -1
- flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +49 -51
- flwr/simulation/__init__.py +1 -1
- flwr/simulation/app.py +3 -3
- flwr/simulation/legacy_app.py +1 -1
- flwr/simulation/ray_transport/__init__.py +1 -1
- flwr/simulation/ray_transport/ray_actor.py +5 -3
- flwr/simulation/ray_transport/ray_client_proxy.py +35 -33
- flwr/simulation/ray_transport/utils.py +1 -1
- flwr/simulation/run_simulation.py +17 -17
- flwr/simulation/simulationio_connection.py +1 -1
- flwr/superexec/__init__.py +1 -1
- flwr/superexec/app.py +1 -1
- flwr/superexec/deployment.py +5 -5
- flwr/superexec/exec_event_log_interceptor.py +135 -0
- flwr/superexec/exec_grpc.py +11 -5
- flwr/superexec/exec_servicer.py +3 -3
- flwr/superexec/exec_user_auth_interceptor.py +19 -3
- flwr/superexec/executor.py +4 -4
- flwr/superexec/simulation.py +4 -4
- {flwr-1.16.0.dist-info → flwr-1.18.0.dist-info}/METADATA +3 -3
- flwr-1.18.0.dist-info/RECORD +332 -0
- flwr/common/record/parametersrecord.py +0 -339
- flwr/common/record/recordset.py +0 -209
- flwr/common/recordset_compat.py +0 -418
- flwr/proto/recordset_pb2.py +0 -70
- flwr-1.16.0.dist-info/LICENSE +0 -202
- flwr-1.16.0.dist-info/RECORD +0 -331
- /flwr/proto/{recordset_pb2_grpc.py → recorddict_pb2_grpc.py} +0 -0
- /flwr/proto/{recordset_pb2_grpc.pyi → recorddict_pb2_grpc.pyi} +0 -0
- {flwr-1.16.0.dist-info → flwr-1.18.0.dist-info}/WHEEL +0 -0
- {flwr-1.16.0.dist-info → flwr-1.18.0.dist-info}/entry_points.txt +0 -0
flwr/client/app.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright
|
|
1
|
+
# Copyright 2025 Flower Labs GmbH. All Rights Reserved.
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -158,33 +158,33 @@ def start_client(
|
|
|
158
158
|
|
|
159
159
|
Examples
|
|
160
160
|
--------
|
|
161
|
-
Starting a gRPC client with an insecure server connection
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
Starting
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
Starting
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
161
|
+
Starting a gRPC client with an insecure server connection::
|
|
162
|
+
|
|
163
|
+
start_client(
|
|
164
|
+
server_address=localhost:8080,
|
|
165
|
+
client_fn=client_fn,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
Starting a TLS-enabled gRPC client using system certificates::
|
|
169
|
+
|
|
170
|
+
def client_fn(context: Context):
|
|
171
|
+
return FlowerClient().to_client()
|
|
172
|
+
|
|
173
|
+
start_client(
|
|
174
|
+
server_address=localhost:8080,
|
|
175
|
+
client_fn=client_fn,
|
|
176
|
+
insecure=False,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
Starting a TLS-enabled gRPC client using provided certificates::
|
|
180
|
+
|
|
181
|
+
from pathlib import Path
|
|
182
|
+
|
|
183
|
+
start_client(
|
|
184
|
+
server_address=localhost:8080,
|
|
185
|
+
client_fn=client_fn,
|
|
186
|
+
root_certificates=Path("/crts/root.pem").read_bytes(),
|
|
187
|
+
)
|
|
188
188
|
"""
|
|
189
189
|
msg = (
|
|
190
190
|
"flwr.client.start_client() is deprecated."
|
|
@@ -495,8 +495,9 @@ def start_client_internal(
|
|
|
495
495
|
context = run_info_store.retrieve_context(run_id=run_id)
|
|
496
496
|
# Create an error reply message that will never be used to prevent
|
|
497
497
|
# the used-before-assignment linting error
|
|
498
|
-
reply_message =
|
|
499
|
-
|
|
498
|
+
reply_message = Message(
|
|
499
|
+
Error(code=ErrorCode.UNKNOWN, reason="Unknown"),
|
|
500
|
+
reply_to=message,
|
|
500
501
|
)
|
|
501
502
|
|
|
502
503
|
# Handle app loading and task message
|
|
@@ -593,8 +594,9 @@ def start_client_internal(
|
|
|
593
594
|
log(ERROR, "%s raised an exception", exc_entity, exc_info=ex)
|
|
594
595
|
|
|
595
596
|
# Create error message
|
|
596
|
-
reply_message =
|
|
597
|
-
|
|
597
|
+
reply_message = Message(
|
|
598
|
+
Error(code=e_code, reason=reason),
|
|
599
|
+
reply_to=message,
|
|
598
600
|
)
|
|
599
601
|
else:
|
|
600
602
|
# No exception, update node state
|
|
@@ -682,30 +684,30 @@ def start_numpy_client(
|
|
|
682
684
|
|
|
683
685
|
Examples
|
|
684
686
|
--------
|
|
685
|
-
Starting a gRPC client with an insecure server connection
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
Starting
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
Starting
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
687
|
+
Starting a gRPC client with an insecure server connection::
|
|
688
|
+
|
|
689
|
+
start_numpy_client(
|
|
690
|
+
server_address=localhost:8080,
|
|
691
|
+
client=FlowerClient(),
|
|
692
|
+
)
|
|
693
|
+
|
|
694
|
+
Starting a TLS-enabled gRPC client using system certificates::
|
|
695
|
+
|
|
696
|
+
start_numpy_client(
|
|
697
|
+
server_address=localhost:8080,
|
|
698
|
+
client=FlowerClient(),
|
|
699
|
+
insecure=False,
|
|
700
|
+
)
|
|
701
|
+
|
|
702
|
+
Starting a TLS-enabled gRPC client using provided certificates::
|
|
703
|
+
|
|
704
|
+
from pathlib import Path
|
|
705
|
+
|
|
706
|
+
start_numpy_client(
|
|
707
|
+
server_address=localhost:8080,
|
|
708
|
+
client=FlowerClient(),
|
|
709
|
+
root_certificates=Path("/crts/root.pem").read_bytes(),
|
|
710
|
+
)
|
|
709
711
|
"""
|
|
710
712
|
mssg = (
|
|
711
713
|
"flwr.client.start_numpy_client() is deprecated. \n\tInstead, use "
|
flwr/client/client.py
CHANGED
flwr/client/client_app.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright
|
|
1
|
+
# Copyright 2025 Flower Labs GmbH. All Rights Reserved.
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -27,10 +27,13 @@ from flwr.client.message_handler.message_handler import (
|
|
|
27
27
|
from flwr.client.mod.utils import make_ffn
|
|
28
28
|
from flwr.client.typing import ClientFnExt, Mod
|
|
29
29
|
from flwr.common import Context, Message, MessageType
|
|
30
|
-
from flwr.common.logger import warn_deprecated_feature
|
|
30
|
+
from flwr.common.logger import warn_deprecated_feature
|
|
31
|
+
from flwr.common.message import validate_message_type
|
|
31
32
|
|
|
32
33
|
from .typing import ClientAppCallable
|
|
33
34
|
|
|
35
|
+
DEFAULT_ACTION = "default"
|
|
36
|
+
|
|
34
37
|
|
|
35
38
|
def _alert_erroneous_client_fn() -> None:
|
|
36
39
|
raise ValueError(
|
|
@@ -92,16 +95,16 @@ class ClientApp:
|
|
|
92
95
|
|
|
93
96
|
Examples
|
|
94
97
|
--------
|
|
95
|
-
Assuming a typical
|
|
96
|
-
a
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
98
|
+
Assuming a typical ``Client`` implementation named ``FlowerClient``, you can wrap
|
|
99
|
+
it in a ``ClientApp`` as follows::
|
|
100
|
+
|
|
101
|
+
class FlowerClient(NumPyClient):
|
|
102
|
+
# ...
|
|
103
|
+
|
|
104
|
+
def client_fn(context: Context):
|
|
105
|
+
return FlowerClient().to_client()
|
|
106
|
+
|
|
107
|
+
app = ClientApp(client_fn)
|
|
105
108
|
"""
|
|
106
109
|
|
|
107
110
|
def __init__(
|
|
@@ -110,6 +113,7 @@ class ClientApp:
|
|
|
110
113
|
mods: Optional[list[Mod]] = None,
|
|
111
114
|
) -> None:
|
|
112
115
|
self._mods: list[Mod] = mods if mods is not None else []
|
|
116
|
+
self._registered_funcs: dict[str, ClientAppCallable] = {}
|
|
113
117
|
|
|
114
118
|
# Create wrapper function for `handle`
|
|
115
119
|
self._call: Optional[ClientAppCallable] = None
|
|
@@ -129,10 +133,7 @@ class ClientApp:
|
|
|
129
133
|
# Wrap mods around the wrapped handle function
|
|
130
134
|
self._call = make_ffn(ffn, mods if mods is not None else [])
|
|
131
135
|
|
|
132
|
-
#
|
|
133
|
-
self._train: Optional[ClientAppCallable] = None
|
|
134
|
-
self._evaluate: Optional[ClientAppCallable] = None
|
|
135
|
-
self._query: Optional[ClientAppCallable] = None
|
|
136
|
+
# Lifespan function
|
|
136
137
|
self._lifespan = _empty_lifespan
|
|
137
138
|
|
|
138
139
|
def __call__(self, message: Message, context: Context) -> Message:
|
|
@@ -142,159 +143,189 @@ class ClientApp:
|
|
|
142
143
|
if self._call:
|
|
143
144
|
return self._call(message, context)
|
|
144
145
|
|
|
145
|
-
#
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
146
|
+
# Get the category and the action
|
|
147
|
+
# A valid message type is of the form "<category>" or "<category>.<action>",
|
|
148
|
+
# where <category> must be "train"/"evaluate"/"query", and <action> is a
|
|
149
|
+
# valid Python identifier
|
|
150
|
+
if not validate_message_type(message.metadata.message_type):
|
|
151
|
+
raise ValueError(
|
|
152
|
+
f"Invalid message type: {message.metadata.message_type}"
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
category, action = message.metadata.message_type, DEFAULT_ACTION
|
|
156
|
+
if "." in category:
|
|
157
|
+
category, action = category.split(".")
|
|
158
|
+
|
|
159
|
+
# Check if the function is registered
|
|
160
|
+
if (full_name := f"{category}.{action}") in self._registered_funcs:
|
|
161
|
+
return self._registered_funcs[full_name](message, context)
|
|
162
|
+
|
|
163
|
+
raise ValueError(f"No {category} function registered with name '{action}'")
|
|
161
164
|
|
|
162
165
|
def train(
|
|
163
|
-
self, mods: Optional[list[Mod]] = None
|
|
166
|
+
self, action: str = DEFAULT_ACTION, *, mods: Optional[list[Mod]] = None
|
|
164
167
|
) -> Callable[[ClientAppCallable], ClientAppCallable]:
|
|
165
|
-
"""
|
|
168
|
+
"""Register a train function with the ``ClientApp``.
|
|
169
|
+
|
|
170
|
+
Parameters
|
|
171
|
+
----------
|
|
172
|
+
action : str (default: "default")
|
|
173
|
+
The action name used to route messages. Defaults to "default".
|
|
174
|
+
mods : Optional[list[Mod]] (default: None)
|
|
175
|
+
A list of function-specific modifiers.
|
|
176
|
+
|
|
177
|
+
Returns
|
|
178
|
+
-------
|
|
179
|
+
Callable[[ClientAppCallable], ClientAppCallable]
|
|
180
|
+
A decorator that registers a train function with the ``ClientApp``.
|
|
166
181
|
|
|
167
182
|
Examples
|
|
168
183
|
--------
|
|
169
|
-
Registering a train function
|
|
170
|
-
|
|
171
|
-
>>> app = ClientApp()
|
|
172
|
-
>>>
|
|
173
|
-
>>> @app.train()
|
|
174
|
-
>>> def train(message: Message, context: Context) -> Message:
|
|
175
|
-
>>> print("ClientApp training running")
|
|
176
|
-
>>> # Create and return an echo reply message
|
|
177
|
-
>>> return message.create_reply(content=message.content())
|
|
178
|
-
|
|
179
|
-
Registering a train function with a function-specific modifier:
|
|
180
|
-
|
|
181
|
-
>>> from flwr.client.mod import message_size_mod
|
|
182
|
-
>>>
|
|
183
|
-
>>> app = ClientApp()
|
|
184
|
-
>>>
|
|
185
|
-
>>> @app.train(mods=[message_size_mod])
|
|
186
|
-
>>> def train(message: Message, context: Context) -> Message:
|
|
187
|
-
>>> print("ClientApp training running with message size mod")
|
|
188
|
-
>>> return message.create_reply(content=message.content())
|
|
189
|
-
"""
|
|
184
|
+
Registering a train function::
|
|
190
185
|
|
|
191
|
-
|
|
192
|
-
"""Register the train fn with the ServerApp object."""
|
|
193
|
-
if self._call:
|
|
194
|
-
raise _registration_error(MessageType.TRAIN)
|
|
186
|
+
app = ClientApp()
|
|
195
187
|
|
|
196
|
-
|
|
188
|
+
@app.train()
|
|
189
|
+
def train(message: Message, context: Context) -> Message:
|
|
190
|
+
print("Executing default train function")
|
|
191
|
+
# Create and return an echo reply message
|
|
192
|
+
return Message(message.content, reply_to=message)
|
|
197
193
|
|
|
198
|
-
|
|
199
|
-
# Wrap mods around the wrapped step function
|
|
200
|
-
self._train = make_ffn(train_fn, self._mods + (mods or []))
|
|
194
|
+
Registering a train function with a custom action name::
|
|
201
195
|
|
|
202
|
-
|
|
203
|
-
return train_fn
|
|
196
|
+
app = ClientApp()
|
|
204
197
|
|
|
205
|
-
|
|
198
|
+
# Messages with `message_type="train.custom_action"` will be
|
|
199
|
+
# routed to this function.
|
|
200
|
+
@app.train("custom_action")
|
|
201
|
+
def custom_action(message: Message, context: Context) -> Message:
|
|
202
|
+
print("Executing train function for custom action")
|
|
203
|
+
return Message(message.content, reply_to=message)
|
|
204
|
+
|
|
205
|
+
Registering a train function with a function-specific Flower Mod::
|
|
206
|
+
|
|
207
|
+
from flwr.client.mod import message_size_mod
|
|
208
|
+
|
|
209
|
+
app = ClientApp()
|
|
210
|
+
|
|
211
|
+
# Using the `mods` argument to apply a function-specific mod.
|
|
212
|
+
@app.train(mods=[message_size_mod])
|
|
213
|
+
def train(message: Message, context: Context) -> Message:
|
|
214
|
+
print("Executing train function with message size mod")
|
|
215
|
+
# Create and return an echo reply message
|
|
216
|
+
return Message(message.content, reply_to=message)
|
|
217
|
+
"""
|
|
218
|
+
return _get_decorator(self, MessageType.TRAIN, action, mods)
|
|
206
219
|
|
|
207
220
|
def evaluate(
|
|
208
|
-
self, mods: Optional[list[Mod]] = None
|
|
221
|
+
self, action: str = DEFAULT_ACTION, *, mods: Optional[list[Mod]] = None
|
|
209
222
|
) -> Callable[[ClientAppCallable], ClientAppCallable]:
|
|
210
|
-
"""
|
|
223
|
+
"""Register an evaluate function with the ``ClientApp``.
|
|
224
|
+
|
|
225
|
+
Parameters
|
|
226
|
+
----------
|
|
227
|
+
action : str (default: "default")
|
|
228
|
+
The action name used to route messages. Defaults to "default".
|
|
229
|
+
mods : Optional[list[Mod]] (default: None)
|
|
230
|
+
A list of function-specific modifiers.
|
|
231
|
+
|
|
232
|
+
Returns
|
|
233
|
+
-------
|
|
234
|
+
Callable[[ClientAppCallable], ClientAppCallable]
|
|
235
|
+
A decorator that registers an evaluate function with the ``ClientApp``.
|
|
211
236
|
|
|
212
237
|
Examples
|
|
213
238
|
--------
|
|
214
|
-
Registering an evaluate function
|
|
215
|
-
|
|
216
|
-
>>> app = ClientApp()
|
|
217
|
-
>>>
|
|
218
|
-
>>> @app.evaluate()
|
|
219
|
-
>>> def evaluate(message: Message, context: Context) -> Message:
|
|
220
|
-
>>> print("ClientApp evaluation running")
|
|
221
|
-
>>> # Create and return an echo reply message
|
|
222
|
-
>>> return message.create_reply(content=message.content())
|
|
223
|
-
|
|
224
|
-
Registering an evaluate function with a function-specific modifier:
|
|
225
|
-
|
|
226
|
-
>>> from flwr.client.mod import message_size_mod
|
|
227
|
-
>>>
|
|
228
|
-
>>> app = ClientApp()
|
|
229
|
-
>>>
|
|
230
|
-
>>> @app.evaluate(mods=[message_size_mod])
|
|
231
|
-
>>> def evaluate(message: Message, context: Context) -> Message:
|
|
232
|
-
>>> print("ClientApp evaluation running with message size mod")
|
|
233
|
-
>>> # Create and return an echo reply message
|
|
234
|
-
>>> return message.create_reply(content=message.content())
|
|
235
|
-
"""
|
|
239
|
+
Registering an evaluate function::
|
|
236
240
|
|
|
237
|
-
|
|
238
|
-
"""Register the evaluate fn with the ServerApp object."""
|
|
239
|
-
if self._call:
|
|
240
|
-
raise _registration_error(MessageType.EVALUATE)
|
|
241
|
+
app = ClientApp()
|
|
241
242
|
|
|
242
|
-
|
|
243
|
+
@app.evaluate()
|
|
244
|
+
def evaluate(message: Message, context: Context) -> Message:
|
|
245
|
+
print("Executing default evaluate function")
|
|
246
|
+
# Create and return an echo reply message
|
|
247
|
+
return Message(message.content, reply_to=message)
|
|
243
248
|
|
|
244
|
-
|
|
245
|
-
# Wrap mods around the wrapped step function
|
|
246
|
-
self._evaluate = make_ffn(evaluate_fn, self._mods + (mods or []))
|
|
249
|
+
Registering an evaluate function with a custom action name::
|
|
247
250
|
|
|
248
|
-
|
|
249
|
-
|
|
251
|
+
app = ClientApp()
|
|
252
|
+
|
|
253
|
+
# Messages with `message_type="evaluate.custom_action"` will be
|
|
254
|
+
# routed to this function.
|
|
255
|
+
@app.evaluate("custom_action")
|
|
256
|
+
def custom_action(message: Message, context: Context) -> Message:
|
|
257
|
+
print("Executing evaluate function for custom action")
|
|
258
|
+
return Message(message.content, reply_to=message)
|
|
259
|
+
|
|
260
|
+
Registering an evaluate function with a function-specific Flower Mod::
|
|
261
|
+
|
|
262
|
+
from flwr.client.mod import message_size_mod
|
|
250
263
|
|
|
251
|
-
|
|
264
|
+
app = ClientApp()
|
|
265
|
+
|
|
266
|
+
# Using the `mods` argument to apply a function-specific mod.
|
|
267
|
+
@app.evaluate(mods=[message_size_mod])
|
|
268
|
+
def evaluate(message: Message, context: Context) -> Message:
|
|
269
|
+
print("Executing evaluate function with message size mod")
|
|
270
|
+
# Create and return an echo reply message
|
|
271
|
+
return Message(message.content, reply_to=message)
|
|
272
|
+
"""
|
|
273
|
+
return _get_decorator(self, MessageType.EVALUATE, action, mods)
|
|
252
274
|
|
|
253
275
|
def query(
|
|
254
|
-
self, mods: Optional[list[Mod]] = None
|
|
276
|
+
self, action: str = DEFAULT_ACTION, *, mods: Optional[list[Mod]] = None
|
|
255
277
|
) -> Callable[[ClientAppCallable], ClientAppCallable]:
|
|
256
|
-
"""
|
|
278
|
+
"""Register a query function with the ``ClientApp``.
|
|
279
|
+
|
|
280
|
+
Parameters
|
|
281
|
+
----------
|
|
282
|
+
action : str (default: "default")
|
|
283
|
+
The action name used to route messages. Defaults to "default".
|
|
284
|
+
mods : Optional[list[Mod]] (default: None)
|
|
285
|
+
A list of function-specific modifiers.
|
|
286
|
+
|
|
287
|
+
Returns
|
|
288
|
+
-------
|
|
289
|
+
Callable[[ClientAppCallable], ClientAppCallable]
|
|
290
|
+
A decorator that registers a query function with the ``ClientApp``.
|
|
257
291
|
|
|
258
292
|
Examples
|
|
259
293
|
--------
|
|
260
|
-
Registering a query function
|
|
261
|
-
|
|
262
|
-
>>> app = ClientApp()
|
|
263
|
-
>>>
|
|
264
|
-
>>> @app.query()
|
|
265
|
-
>>> def query(message: Message, context: Context) -> Message:
|
|
266
|
-
>>> print("ClientApp query running")
|
|
267
|
-
>>> # Create and return an echo reply message
|
|
268
|
-
>>> return message.create_reply(content=message.content())
|
|
269
|
-
|
|
270
|
-
Registering a query function with a function-specific modifier:
|
|
271
|
-
|
|
272
|
-
>>> from flwr.client.mod import message_size_mod
|
|
273
|
-
>>>
|
|
274
|
-
>>> app = ClientApp()
|
|
275
|
-
>>>
|
|
276
|
-
>>> @app.query(mods=[message_size_mod])
|
|
277
|
-
>>> def query(message: Message, context: Context) -> Message:
|
|
278
|
-
>>> print("ClientApp query running with message size mod")
|
|
279
|
-
>>> # Create and return an echo reply message
|
|
280
|
-
>>> return message.create_reply(content=message.content())
|
|
281
|
-
"""
|
|
294
|
+
Registering a query function::
|
|
282
295
|
|
|
283
|
-
|
|
284
|
-
"""Register the query fn with the ServerApp object."""
|
|
285
|
-
if self._call:
|
|
286
|
-
raise _registration_error(MessageType.QUERY)
|
|
296
|
+
app = ClientApp()
|
|
287
297
|
|
|
288
|
-
|
|
298
|
+
@app.query()
|
|
299
|
+
def query(message: Message, context: Context) -> Message:
|
|
300
|
+
print("Executing default query function")
|
|
301
|
+
# Create and return an echo reply message
|
|
302
|
+
return Message(message.content, reply_to=message)
|
|
289
303
|
|
|
290
|
-
|
|
291
|
-
# Wrap mods around the wrapped step function
|
|
292
|
-
self._query = make_ffn(query_fn, self._mods + (mods or []))
|
|
304
|
+
Registering a query function with a custom action name::
|
|
293
305
|
|
|
294
|
-
|
|
295
|
-
|
|
306
|
+
app = ClientApp()
|
|
307
|
+
|
|
308
|
+
# Messages with `message_type="query.custom_action"` will be
|
|
309
|
+
# routed to this function.
|
|
310
|
+
@app.query("custom_action")
|
|
311
|
+
def custom_action(message: Message, context: Context) -> Message:
|
|
312
|
+
print("Executing query function for custom action")
|
|
313
|
+
return Message(message.content, reply_to=message)
|
|
314
|
+
|
|
315
|
+
Registering a query function with a function-specific Flower Mod::
|
|
316
|
+
|
|
317
|
+
from flwr.client.mod import message_size_mod
|
|
296
318
|
|
|
297
|
-
|
|
319
|
+
app = ClientApp()
|
|
320
|
+
|
|
321
|
+
# Using the `mods` argument to apply a function-specific mod.
|
|
322
|
+
@app.query(mods=[message_size_mod])
|
|
323
|
+
def query(message: Message, context: Context) -> Message:
|
|
324
|
+
print("Executing query function with message size mod")
|
|
325
|
+
# Create and return an echo reply message
|
|
326
|
+
return Message(message.content, reply_to=message)
|
|
327
|
+
"""
|
|
328
|
+
return _get_decorator(self, MessageType.QUERY, action, mods)
|
|
298
329
|
|
|
299
330
|
def lifespan(
|
|
300
331
|
self,
|
|
@@ -308,24 +339,25 @@ class ClientApp:
|
|
|
308
339
|
|
|
309
340
|
Examples
|
|
310
341
|
--------
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
342
|
+
::
|
|
343
|
+
|
|
344
|
+
app = ClientApp()
|
|
345
|
+
|
|
346
|
+
@app.lifespan()
|
|
347
|
+
def lifespan(context: Context) -> None:
|
|
348
|
+
# Perform initialization tasks before the app starts
|
|
349
|
+
print("Initializing ClientApp")
|
|
350
|
+
|
|
351
|
+
yield # ClientApp is running
|
|
352
|
+
|
|
353
|
+
# Perform cleanup tasks after the app stops
|
|
354
|
+
print("Cleaning up ClientApp")
|
|
322
355
|
"""
|
|
323
356
|
|
|
324
357
|
def lifespan_decorator(
|
|
325
358
|
lifespan_fn: Callable[[Context], Iterator[None]]
|
|
326
359
|
) -> Callable[[Context], Iterator[None]]:
|
|
327
360
|
"""Register the lifespan fn with the ServerApp object."""
|
|
328
|
-
warn_preview_feature("ClientApp-register-lifespan-function")
|
|
329
361
|
|
|
330
362
|
@contextmanager
|
|
331
363
|
def decorated_lifespan(context: Context) -> Iterator[None]:
|
|
@@ -365,32 +397,65 @@ class LoadClientAppError(Exception):
|
|
|
365
397
|
"""Error when trying to load `ClientApp`."""
|
|
366
398
|
|
|
367
399
|
|
|
400
|
+
def _get_decorator(
|
|
401
|
+
app: ClientApp, category: str, action: str, mods: Optional[list[Mod]]
|
|
402
|
+
) -> Callable[[ClientAppCallable], ClientAppCallable]:
|
|
403
|
+
"""Get the decorator for the given category and action."""
|
|
404
|
+
# pylint: disable=protected-access
|
|
405
|
+
if app._call:
|
|
406
|
+
raise _registration_error(category)
|
|
407
|
+
|
|
408
|
+
def decorator(fn: ClientAppCallable) -> ClientAppCallable:
|
|
409
|
+
|
|
410
|
+
# Check if the name is a valid Python identifier
|
|
411
|
+
if not action.isidentifier():
|
|
412
|
+
raise ValueError(
|
|
413
|
+
f"Cannot register {category} function with name '{action}'. "
|
|
414
|
+
"The name must follow Python's function naming rules."
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
# Check if the name is already registered
|
|
418
|
+
full_name = f"{category}.{action}" # Full name of the message type
|
|
419
|
+
if full_name in app._registered_funcs:
|
|
420
|
+
raise ValueError(
|
|
421
|
+
f"Cannot register {category} function with name '{action}'. "
|
|
422
|
+
f"A {category} function with the name '{action}' is already registered."
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
# Register provided function with the ClientApp object
|
|
426
|
+
app._registered_funcs[full_name] = make_ffn(fn, app._mods + (mods or []))
|
|
427
|
+
|
|
428
|
+
# Return provided function unmodified
|
|
429
|
+
return fn
|
|
430
|
+
|
|
431
|
+
# pylint: enable=protected-access
|
|
432
|
+
return decorator
|
|
433
|
+
|
|
434
|
+
|
|
368
435
|
def _registration_error(fn_name: str) -> ValueError:
|
|
369
436
|
return ValueError(
|
|
370
437
|
f"""Use either `@app.{fn_name}()` or `client_fn`, but not both.
|
|
371
438
|
|
|
372
439
|
Use the `ClientApp` with an existing `client_fn`:
|
|
373
440
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
441
|
+
class FlowerClient(NumPyClient):
|
|
442
|
+
# ...
|
|
443
|
+
|
|
444
|
+
def client_fn(context: Context):
|
|
445
|
+
return FlowerClient().to_client()
|
|
446
|
+
|
|
447
|
+
app = ClientApp(
|
|
448
|
+
client_fn=client_fn,
|
|
449
|
+
)
|
|
383
450
|
|
|
384
451
|
Use the `ClientApp` with a custom {fn_name} function:
|
|
385
452
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
>>> content=message.content()
|
|
394
|
-
>>> )
|
|
453
|
+
app = ClientApp()
|
|
454
|
+
|
|
455
|
+
@app.{fn_name}()
|
|
456
|
+
def {fn_name}(message: Message, context: Context) -> Message:
|
|
457
|
+
print("ClientApp {fn_name} running")
|
|
458
|
+
# Create and return an echo reply message
|
|
459
|
+
return Message(message.content, reply_to=message)
|
|
395
460
|
""",
|
|
396
461
|
)
|