flwr 1.17.0__py3-none-any.whl → 1.19.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/app/__init__.py +15 -0
- flwr/app/error.py +68 -0
- flwr/app/metadata.py +223 -0
- flwr/cli/__init__.py +1 -1
- flwr/cli/app.py +21 -2
- flwr/cli/build.py +83 -58
- 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 +4 -4
- flwr/cli/login/__init__.py +1 -1
- flwr/cli/login/login.py +15 -8
- flwr/cli/ls.py +16 -37
- 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/client.baseline.py.tpl +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 +4 -4
- flwr/cli/new/templates/app/code/model.baseline.py.tpl +1 -1
- flwr/cli/new/templates/app/code/server.baseline.py.tpl +2 -3
- flwr/cli/new/templates/app/code/task.sklearn.py.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +14 -17
- 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 +11 -19
- flwr/cli/stop.py +3 -3
- flwr/cli/utils.py +42 -17
- flwr/client/__init__.py +3 -3
- flwr/client/client.py +1 -1
- flwr/client/client_app.py +140 -138
- flwr/client/clientapp/__init__.py +1 -8
- 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 +5 -5
- 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 +131 -61
- flwr/client/grpc_rere_client/grpc_adapter.py +35 -7
- flwr/client/message_handler/__init__.py +1 -1
- flwr/client/message_handler/message_handler.py +2 -2
- flwr/client/mod/__init__.py +1 -1
- flwr/client/mod/centraldp_mods.py +1 -1
- flwr/client/mod/comms_mods.py +39 -20
- flwr/client/mod/localdp_mod.py +6 -6
- 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 +1 -1
- flwr/client/mod/utils.py +1 -1
- flwr/client/numpy_client.py +1 -1
- flwr/client/rest_client/__init__.py +1 -1
- flwr/client/rest_client/connection.py +174 -68
- flwr/client/run_info_store.py +1 -1
- flwr/client/typing.py +1 -1
- flwr/clientapp/__init__.py +15 -0
- flwr/common/__init__.py +3 -3
- flwr/common/address.py +1 -1
- flwr/common/args.py +1 -1
- flwr/common/auth_plugin/__init__.py +3 -1
- flwr/common/auth_plugin/auth_plugin.py +30 -4
- flwr/common/config.py +1 -1
- flwr/common/constant.py +37 -8
- flwr/common/context.py +1 -1
- 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 +31 -1
- flwr/common/grpc.py +1 -1
- flwr/common/heartbeat.py +165 -0
- flwr/common/inflatable.py +290 -0
- flwr/common/inflatable_grpc_utils.py +99 -0
- flwr/common/inflatable_rest_utils.py +99 -0
- flwr/common/inflatable_utils.py +341 -0
- flwr/common/logger.py +1 -1
- flwr/common/message.py +137 -252
- flwr/common/object_ref.py +1 -1
- flwr/common/parameter.py +1 -1
- flwr/common/pyproject.py +1 -1
- flwr/common/record/__init__.py +3 -2
- flwr/common/record/array.py +323 -0
- flwr/common/record/arrayrecord.py +121 -243
- flwr/common/record/configrecord.py +71 -16
- flwr/common/record/conversion_utils.py +2 -2
- flwr/common/record/metricrecord.py +71 -20
- flwr/common/record/recorddict.py +207 -90
- flwr/common/record/typeddict.py +1 -1
- flwr/common/recorddict_compat.py +2 -2
- flwr/common/retry_invoker.py +15 -11
- 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 +1 -1
- flwr/common/secure_aggregation/secaggplus_utils.py +1 -1
- flwr/common/serde.py +60 -184
- flwr/common/serde_utils.py +175 -0
- flwr/common/telemetry.py +2 -2
- flwr/common/typing.py +6 -4
- flwr/common/version.py +1 -1
- flwr/compat/__init__.py +15 -0
- flwr/compat/client/__init__.py +15 -0
- flwr/{client → compat/client}/app.py +71 -211
- flwr/{client → compat/client}/grpc_client/__init__.py +1 -1
- flwr/{client → compat/client}/grpc_client/connection.py +13 -13
- flwr/compat/common/__init__.py +15 -0
- flwr/compat/server/__init__.py +15 -0
- flwr/compat/server/app.py +174 -0
- flwr/compat/simulation/__init__.py +15 -0
- flwr/proto/__init__.py +1 -1
- flwr/proto/fleet_pb2.py +32 -27
- flwr/proto/fleet_pb2.pyi +49 -35
- flwr/proto/fleet_pb2_grpc.py +117 -13
- flwr/proto/fleet_pb2_grpc.pyi +47 -6
- flwr/proto/heartbeat_pb2.py +33 -0
- flwr/proto/heartbeat_pb2.pyi +66 -0
- flwr/proto/heartbeat_pb2_grpc.py +4 -0
- flwr/proto/heartbeat_pb2_grpc.pyi +4 -0
- flwr/proto/message_pb2.py +28 -11
- flwr/proto/message_pb2.pyi +125 -0
- flwr/proto/recorddict_pb2.py +16 -28
- flwr/proto/recorddict_pb2.pyi +46 -64
- flwr/proto/run_pb2.py +24 -32
- flwr/proto/run_pb2.pyi +4 -52
- flwr/proto/serverappio_pb2.py +32 -23
- flwr/proto/serverappio_pb2.pyi +45 -3
- flwr/proto/serverappio_pb2_grpc.py +138 -34
- flwr/proto/serverappio_pb2_grpc.pyi +54 -13
- flwr/proto/simulationio_pb2.py +12 -11
- flwr/proto/simulationio_pb2_grpc.py +35 -0
- flwr/proto/simulationio_pb2_grpc.pyi +14 -0
- flwr/server/__init__.py +2 -2
- flwr/server/app.py +69 -187
- flwr/server/client_manager.py +1 -1
- flwr/server/client_proxy.py +1 -1
- flwr/server/compat/__init__.py +1 -1
- flwr/server/compat/app.py +1 -1
- flwr/server/compat/app_utils.py +51 -29
- flwr/server/compat/legacy_context.py +1 -1
- flwr/server/criterion.py +1 -1
- flwr/server/fleet_event_log_interceptor.py +2 -2
- flwr/server/grid/grid.py +3 -3
- flwr/server/grid/grpc_grid.py +104 -34
- flwr/server/grid/inmemory_grid.py +5 -4
- flwr/server/history.py +1 -1
- flwr/server/run_serverapp.py +1 -1
- flwr/server/server.py +1 -1
- flwr/server/server_app.py +65 -58
- flwr/server/server_config.py +1 -1
- flwr/server/serverapp/__init__.py +1 -1
- flwr/server/serverapp/app.py +19 -1
- 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 +3 -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 +14 -4
- 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 +102 -8
- 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 +136 -19
- flwr/server/superlink/fleet/rest_rere/__init__.py +1 -1
- flwr/server/superlink/fleet/rest_rere/rest_api.py +73 -12
- 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 +1 -1
- flwr/server/superlink/fleet/vce/backend/raybackend.py +1 -1
- flwr/server/superlink/fleet/vce/vce_api.py +7 -4
- flwr/server/superlink/linkstate/__init__.py +1 -1
- flwr/server/superlink/linkstate/in_memory_linkstate.py +139 -44
- flwr/server/superlink/linkstate/linkstate.py +54 -21
- flwr/server/superlink/linkstate/linkstate_factory.py +1 -1
- flwr/server/superlink/linkstate/sqlite_linkstate.py +150 -56
- flwr/server/superlink/linkstate/utils.py +34 -30
- flwr/server/superlink/serverappio/serverappio_grpc.py +3 -0
- flwr/server/superlink/serverappio/serverappio_servicer.py +211 -57
- flwr/server/superlink/simulation/__init__.py +1 -1
- flwr/server/superlink/simulation/simulationio_grpc.py +1 -1
- flwr/server/superlink/simulation/simulationio_servicer.py +26 -2
- flwr/server/superlink/utils.py +45 -3
- flwr/server/typing.py +1 -1
- flwr/server/utils/__init__.py +1 -1
- flwr/server/utils/tensorboard.py +1 -1
- flwr/server/utils/validator.py +3 -3
- flwr/server/workflow/__init__.py +1 -1
- flwr/server/workflow/constant.py +1 -1
- flwr/server/workflow/default_workflows.py +1 -1
- 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 +1 -1
- flwr/serverapp/__init__.py +15 -0
- flwr/simulation/__init__.py +1 -1
- flwr/simulation/app.py +18 -1
- flwr/simulation/legacy_app.py +1 -1
- flwr/simulation/ray_transport/__init__.py +1 -1
- flwr/simulation/ray_transport/ray_actor.py +1 -1
- flwr/simulation/ray_transport/ray_client_proxy.py +1 -1
- flwr/simulation/ray_transport/utils.py +1 -1
- flwr/simulation/run_simulation.py +2 -2
- flwr/simulation/simulationio_connection.py +1 -1
- flwr/supercore/__init__.py +15 -0
- flwr/supercore/object_store/__init__.py +24 -0
- flwr/supercore/object_store/in_memory_object_store.py +229 -0
- flwr/supercore/object_store/object_store.py +192 -0
- flwr/supercore/object_store/object_store_factory.py +44 -0
- flwr/superexec/__init__.py +1 -1
- flwr/superexec/app.py +1 -1
- flwr/superexec/deployment.py +7 -3
- flwr/superexec/exec_event_log_interceptor.py +4 -4
- flwr/superexec/exec_grpc.py +8 -4
- flwr/superexec/exec_servicer.py +126 -24
- flwr/superexec/exec_user_auth_interceptor.py +38 -9
- flwr/superexec/executor.py +5 -1
- flwr/superexec/simulation.py +8 -2
- flwr/superlink/__init__.py +15 -0
- flwr/{client/supernode → supernode}/__init__.py +1 -8
- flwr/{client/nodestate/nodestate.py → supernode/cli/__init__.py} +8 -15
- flwr/{client/supernode/app.py → supernode/cli/flower_supernode.py} +4 -13
- flwr/supernode/cli/flwr_clientapp.py +81 -0
- flwr/{client → supernode}/nodestate/__init__.py +1 -1
- flwr/supernode/nodestate/in_memory_nodestate.py +190 -0
- flwr/supernode/nodestate/nodestate.py +212 -0
- flwr/{client → supernode}/nodestate/nodestate_factory.py +1 -1
- flwr/supernode/runtime/__init__.py +15 -0
- flwr/{client/clientapp/app.py → supernode/runtime/run_clientapp.py} +26 -57
- flwr/supernode/servicer/__init__.py +15 -0
- flwr/supernode/servicer/clientappio/__init__.py +24 -0
- flwr/{client/clientapp → supernode/servicer/clientappio}/clientappio_servicer.py +1 -1
- flwr/supernode/start_client_internal.py +491 -0
- {flwr-1.17.0.dist-info → flwr-1.19.0.dist-info}/METADATA +6 -5
- flwr-1.19.0.dist-info/RECORD +365 -0
- {flwr-1.17.0.dist-info → flwr-1.19.0.dist-info}/WHEEL +1 -1
- {flwr-1.17.0.dist-info → flwr-1.19.0.dist-info}/entry_points.txt +2 -2
- flwr/client/heartbeat.py +0 -74
- flwr/client/nodestate/in_memory_nodestate.py +0 -38
- flwr-1.17.0.dist-info/LICENSE +0 -202
- flwr-1.17.0.dist-info/RECORD +0 -333
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Copyright 2025 Flower Labs GmbH. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
# ==============================================================================
|
|
15
|
+
"""`flwr-clientapp` command."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
from logging import DEBUG, INFO
|
|
20
|
+
|
|
21
|
+
from flwr.common.args import add_args_flwr_app_common
|
|
22
|
+
from flwr.common.constant import CLIENTAPPIO_API_DEFAULT_CLIENT_ADDRESS
|
|
23
|
+
from flwr.common.exit import ExitCode, flwr_exit
|
|
24
|
+
from flwr.common.logger import log
|
|
25
|
+
from flwr.supernode.runtime.run_clientapp import run_clientapp
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def flwr_clientapp() -> None:
|
|
29
|
+
"""Run process-isolated Flower ClientApp."""
|
|
30
|
+
args = _parse_args_run_flwr_clientapp().parse_args()
|
|
31
|
+
if not args.insecure:
|
|
32
|
+
flwr_exit(
|
|
33
|
+
ExitCode.COMMON_TLS_NOT_SUPPORTED,
|
|
34
|
+
"flwr-clientapp does not support TLS yet.",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
log(INFO, "Start `flwr-clientapp` process")
|
|
38
|
+
log(
|
|
39
|
+
DEBUG,
|
|
40
|
+
"`flwr-clientapp` will attempt to connect to SuperNode's "
|
|
41
|
+
"ClientAppIo API at %s with token %s",
|
|
42
|
+
args.clientappio_api_address,
|
|
43
|
+
args.token,
|
|
44
|
+
)
|
|
45
|
+
run_clientapp(
|
|
46
|
+
clientappio_api_address=args.clientappio_api_address,
|
|
47
|
+
run_once=(args.token is not None),
|
|
48
|
+
token=args.token,
|
|
49
|
+
flwr_dir=args.flwr_dir,
|
|
50
|
+
certificates=None,
|
|
51
|
+
parent_pid=args.parent_pid,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _parse_args_run_flwr_clientapp() -> argparse.ArgumentParser:
|
|
56
|
+
"""Parse flwr-clientapp command line arguments."""
|
|
57
|
+
parser = argparse.ArgumentParser(
|
|
58
|
+
description="Run a Flower ClientApp",
|
|
59
|
+
)
|
|
60
|
+
parser.add_argument(
|
|
61
|
+
"--clientappio-api-address",
|
|
62
|
+
default=CLIENTAPPIO_API_DEFAULT_CLIENT_ADDRESS,
|
|
63
|
+
type=str,
|
|
64
|
+
help="Address of SuperNode's ClientAppIo API (IPv4, IPv6, or a domain name)."
|
|
65
|
+
f"By default, it is set to {CLIENTAPPIO_API_DEFAULT_CLIENT_ADDRESS}.",
|
|
66
|
+
)
|
|
67
|
+
parser.add_argument(
|
|
68
|
+
"--token",
|
|
69
|
+
type=int,
|
|
70
|
+
required=False,
|
|
71
|
+
help="Unique token generated by SuperNode for each ClientApp execution",
|
|
72
|
+
)
|
|
73
|
+
parser.add_argument(
|
|
74
|
+
"--parent-pid",
|
|
75
|
+
type=int,
|
|
76
|
+
default=None,
|
|
77
|
+
help="The PID of the parent process. When set, the process will terminate "
|
|
78
|
+
"when the parent process exits.",
|
|
79
|
+
)
|
|
80
|
+
add_args_flwr_app_common(parser=parser)
|
|
81
|
+
return parser
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# Copyright 2025 Flower Labs GmbH. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
# ==============================================================================
|
|
15
|
+
"""In-memory NodeState implementation."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
import secrets
|
|
19
|
+
from collections.abc import Sequence
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
from threading import Lock
|
|
22
|
+
from typing import Optional
|
|
23
|
+
|
|
24
|
+
from flwr.common import Context, Message
|
|
25
|
+
from flwr.common.constant import FLWR_APP_TOKEN_LENGTH
|
|
26
|
+
from flwr.common.typing import Run
|
|
27
|
+
|
|
28
|
+
from .nodestate import NodeState
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class MessageEntry:
|
|
33
|
+
"""Data class to represent a message entry."""
|
|
34
|
+
|
|
35
|
+
message: Message
|
|
36
|
+
is_retrieved: bool = False
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class InMemoryNodeState(NodeState): # pylint: disable=too-many-instance-attributes
|
|
40
|
+
"""In-memory NodeState implementation."""
|
|
41
|
+
|
|
42
|
+
def __init__(self) -> None:
|
|
43
|
+
# Store node_id
|
|
44
|
+
self.node_id: Optional[int] = None
|
|
45
|
+
# Store Object ID to MessageEntry mapping
|
|
46
|
+
self.msg_store: dict[str, MessageEntry] = {}
|
|
47
|
+
self.lock_msg_store = Lock()
|
|
48
|
+
# Store run ID to Run mapping
|
|
49
|
+
self.run_store: dict[int, Run] = {}
|
|
50
|
+
self.lock_run_store = Lock()
|
|
51
|
+
# Store run ID to Context mapping
|
|
52
|
+
self.ctx_store: dict[int, Context] = {}
|
|
53
|
+
self.lock_ctx_store = Lock()
|
|
54
|
+
# Store run ID to token mapping
|
|
55
|
+
self.token_store: dict[int, str] = {}
|
|
56
|
+
self.lock_token_store = Lock()
|
|
57
|
+
|
|
58
|
+
def set_node_id(self, node_id: Optional[int]) -> None:
|
|
59
|
+
"""Set the node ID."""
|
|
60
|
+
self.node_id = node_id
|
|
61
|
+
|
|
62
|
+
def get_node_id(self) -> int:
|
|
63
|
+
"""Get the node ID."""
|
|
64
|
+
if self.node_id is None:
|
|
65
|
+
raise ValueError("Node ID not set")
|
|
66
|
+
return self.node_id
|
|
67
|
+
|
|
68
|
+
def store_message(self, message: Message) -> Optional[str]:
|
|
69
|
+
"""Store a message."""
|
|
70
|
+
with self.lock_msg_store:
|
|
71
|
+
msg_id = message.metadata.message_id
|
|
72
|
+
if msg_id == "" or msg_id in self.msg_store:
|
|
73
|
+
return None
|
|
74
|
+
self.msg_store[msg_id] = MessageEntry(message=message)
|
|
75
|
+
return msg_id
|
|
76
|
+
|
|
77
|
+
def get_messages(
|
|
78
|
+
self,
|
|
79
|
+
*,
|
|
80
|
+
run_ids: Optional[Sequence[int]] = None,
|
|
81
|
+
is_reply: Optional[bool] = None,
|
|
82
|
+
limit: Optional[int] = None,
|
|
83
|
+
) -> Sequence[Message]:
|
|
84
|
+
"""Retrieve messages based on the specified filters."""
|
|
85
|
+
selected_messages: list[Message] = []
|
|
86
|
+
|
|
87
|
+
with self.lock_msg_store:
|
|
88
|
+
# Iterate through all messages in the store
|
|
89
|
+
for object_id in list(self.msg_store.keys()):
|
|
90
|
+
entry = self.msg_store[object_id]
|
|
91
|
+
message = entry.message
|
|
92
|
+
|
|
93
|
+
# Skip messages that have already been retrieved
|
|
94
|
+
if entry.is_retrieved:
|
|
95
|
+
continue
|
|
96
|
+
|
|
97
|
+
# Skip messages whose run_id doesn't match the filter
|
|
98
|
+
if run_ids is not None:
|
|
99
|
+
if message.metadata.run_id not in run_ids:
|
|
100
|
+
continue
|
|
101
|
+
|
|
102
|
+
# If is_reply filter is set, filter for reply/non-reply messages
|
|
103
|
+
if is_reply is not None:
|
|
104
|
+
is_reply_message = message.metadata.reply_to_message_id != ""
|
|
105
|
+
# XOR logic to filter mismatched types (reply vs non-reply)
|
|
106
|
+
if is_reply ^ is_reply_message:
|
|
107
|
+
continue
|
|
108
|
+
|
|
109
|
+
# Add the message to the result set
|
|
110
|
+
selected_messages.append(message)
|
|
111
|
+
|
|
112
|
+
# Mark the message as retrieved
|
|
113
|
+
entry.is_retrieved = True
|
|
114
|
+
|
|
115
|
+
# Stop if the number of collected messages reaches the limit
|
|
116
|
+
if limit is not None and len(selected_messages) >= limit:
|
|
117
|
+
break
|
|
118
|
+
|
|
119
|
+
return selected_messages
|
|
120
|
+
|
|
121
|
+
def delete_messages(
|
|
122
|
+
self,
|
|
123
|
+
*,
|
|
124
|
+
message_ids: Optional[Sequence[str]] = None,
|
|
125
|
+
) -> None:
|
|
126
|
+
"""Delete messages based on the specified filters."""
|
|
127
|
+
with self.lock_msg_store:
|
|
128
|
+
if message_ids is None:
|
|
129
|
+
# If no message IDs are provided, clear the entire store
|
|
130
|
+
self.msg_store.clear()
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
# Remove specified messages from the store
|
|
134
|
+
for msg_id in message_ids:
|
|
135
|
+
self.msg_store.pop(msg_id, None)
|
|
136
|
+
|
|
137
|
+
def store_run(self, run: Run) -> None:
|
|
138
|
+
"""Store a run."""
|
|
139
|
+
with self.lock_run_store:
|
|
140
|
+
self.run_store[run.run_id] = run
|
|
141
|
+
|
|
142
|
+
def get_run(self, run_id: int) -> Optional[Run]:
|
|
143
|
+
"""Retrieve a run by its ID."""
|
|
144
|
+
with self.lock_run_store:
|
|
145
|
+
return self.run_store.get(run_id)
|
|
146
|
+
|
|
147
|
+
def store_context(self, context: Context) -> None:
|
|
148
|
+
"""Store a context."""
|
|
149
|
+
with self.lock_ctx_store:
|
|
150
|
+
self.ctx_store[context.run_id] = context
|
|
151
|
+
|
|
152
|
+
def get_context(self, run_id: int) -> Optional[Context]:
|
|
153
|
+
"""Retrieve a context by its run ID."""
|
|
154
|
+
with self.lock_ctx_store:
|
|
155
|
+
return self.ctx_store.get(run_id)
|
|
156
|
+
|
|
157
|
+
def get_run_ids_with_pending_messages(self) -> Sequence[int]:
|
|
158
|
+
"""Retrieve run IDs that have at least one pending message."""
|
|
159
|
+
# Collect run IDs from messages
|
|
160
|
+
with self.lock_msg_store:
|
|
161
|
+
ret = {
|
|
162
|
+
entry.message.metadata.run_id
|
|
163
|
+
for entry in self.msg_store.values()
|
|
164
|
+
if entry.message.metadata.reply_to_message_id == ""
|
|
165
|
+
and not entry.is_retrieved
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
# Remove run IDs that have tokens stored (indicating they are in progress)
|
|
169
|
+
with self.lock_token_store:
|
|
170
|
+
ret -= set(self.token_store.keys())
|
|
171
|
+
return list(ret)
|
|
172
|
+
|
|
173
|
+
def create_token(self, run_id: int) -> str:
|
|
174
|
+
"""Create a token for the given run ID."""
|
|
175
|
+
token = secrets.token_hex(FLWR_APP_TOKEN_LENGTH) # Generate a random token
|
|
176
|
+
with self.lock_token_store:
|
|
177
|
+
if run_id in self.token_store:
|
|
178
|
+
raise ValueError("Token already created for this run ID")
|
|
179
|
+
self.token_store[run_id] = token
|
|
180
|
+
return token
|
|
181
|
+
|
|
182
|
+
def verify_token(self, run_id: int, token: str) -> bool:
|
|
183
|
+
"""Verify a token for the given run ID."""
|
|
184
|
+
with self.lock_token_store:
|
|
185
|
+
return self.token_store.get(run_id) == token
|
|
186
|
+
|
|
187
|
+
def delete_token(self, run_id: int) -> None:
|
|
188
|
+
"""Delete the token for the given run ID."""
|
|
189
|
+
with self.lock_token_store:
|
|
190
|
+
self.token_store.pop(run_id, None)
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# Copyright 2025 Flower Labs GmbH. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
# ==============================================================================
|
|
15
|
+
"""Abstract base class NodeState."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from abc import ABC, abstractmethod
|
|
19
|
+
from collections.abc import Sequence
|
|
20
|
+
from typing import Optional
|
|
21
|
+
|
|
22
|
+
from flwr.common import Context, Message
|
|
23
|
+
from flwr.common.typing import Run
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class NodeState(ABC):
|
|
27
|
+
"""Abstract base class for node state."""
|
|
28
|
+
|
|
29
|
+
@abstractmethod
|
|
30
|
+
def set_node_id(self, node_id: int) -> None:
|
|
31
|
+
"""Set the node ID."""
|
|
32
|
+
|
|
33
|
+
@abstractmethod
|
|
34
|
+
def get_node_id(self) -> int:
|
|
35
|
+
"""Get the node ID."""
|
|
36
|
+
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def store_message(self, message: Message) -> Optional[str]:
|
|
39
|
+
"""Store a message.
|
|
40
|
+
|
|
41
|
+
Parameters
|
|
42
|
+
----------
|
|
43
|
+
message : Message
|
|
44
|
+
The message to store.
|
|
45
|
+
|
|
46
|
+
Returns
|
|
47
|
+
-------
|
|
48
|
+
Optional[str]
|
|
49
|
+
The object ID of the stored message, or None if storage failed.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
@abstractmethod
|
|
53
|
+
def get_messages(
|
|
54
|
+
self,
|
|
55
|
+
*,
|
|
56
|
+
run_ids: Optional[Sequence[int]] = None,
|
|
57
|
+
is_reply: Optional[bool] = None,
|
|
58
|
+
limit: Optional[int] = None,
|
|
59
|
+
) -> Sequence[Message]:
|
|
60
|
+
"""Retrieve messages based on the specified filters.
|
|
61
|
+
|
|
62
|
+
If a filter is set to None, it is ignored.
|
|
63
|
+
If multiple filters are provided, they are combined using AND logic.
|
|
64
|
+
|
|
65
|
+
Parameters
|
|
66
|
+
----------
|
|
67
|
+
run_ids : Optional[Sequence[int]] (default: None)
|
|
68
|
+
Sequence of run IDs to filter by. If a sequence is provided,
|
|
69
|
+
it is treated as an OR condition.
|
|
70
|
+
is_reply : Optional[bool] (default: None)
|
|
71
|
+
If True, filter for reply messages; if False, filter for non-reply
|
|
72
|
+
(instruction) messages.
|
|
73
|
+
limit : Optional[int] (default: None)
|
|
74
|
+
Maximum number of messages to return. If None, no limit is applied.
|
|
75
|
+
|
|
76
|
+
Returns
|
|
77
|
+
-------
|
|
78
|
+
Sequence[Message]
|
|
79
|
+
A sequence of messages matching the specified filters.
|
|
80
|
+
|
|
81
|
+
Notes
|
|
82
|
+
-----
|
|
83
|
+
**IMPORTANT:** Retrieved messages will **NOT** be returned again by subsequent
|
|
84
|
+
calls to this method, even if the filters match them.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
@abstractmethod
|
|
88
|
+
def delete_messages(
|
|
89
|
+
self,
|
|
90
|
+
*,
|
|
91
|
+
message_ids: Optional[Sequence[str]] = None,
|
|
92
|
+
) -> None:
|
|
93
|
+
"""Delete messages based on the specified filters.
|
|
94
|
+
|
|
95
|
+
If a filter is set to None, it is ignored.
|
|
96
|
+
If multiple filters are provided, they are combined using AND logic.
|
|
97
|
+
|
|
98
|
+
Parameters
|
|
99
|
+
----------
|
|
100
|
+
message_ids : Optional[Sequence[str]] (default: None)
|
|
101
|
+
Sequence of message (object) IDs to filter by. If a sequence is provided,
|
|
102
|
+
it is treated as an OR condition.
|
|
103
|
+
|
|
104
|
+
Notes
|
|
105
|
+
-----
|
|
106
|
+
**IMPORTANT:** **All messages** will be deleted if no filters are provided.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
@abstractmethod
|
|
110
|
+
def store_run(self, run: Run) -> None:
|
|
111
|
+
"""Store a run.
|
|
112
|
+
|
|
113
|
+
Parameters
|
|
114
|
+
----------
|
|
115
|
+
run : Run
|
|
116
|
+
The `Run` instance to store.
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
@abstractmethod
|
|
120
|
+
def get_run(self, run_id: int) -> Optional[Run]:
|
|
121
|
+
"""Retrieve a run by its ID.
|
|
122
|
+
|
|
123
|
+
Parameters
|
|
124
|
+
----------
|
|
125
|
+
run_id : int
|
|
126
|
+
The ID of the run to retrieve.
|
|
127
|
+
|
|
128
|
+
Returns
|
|
129
|
+
-------
|
|
130
|
+
Optional[Run]
|
|
131
|
+
The `Run` instance if found, otherwise None.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
@abstractmethod
|
|
135
|
+
def store_context(self, context: Context) -> None:
|
|
136
|
+
"""Store a context.
|
|
137
|
+
|
|
138
|
+
Parameters
|
|
139
|
+
----------
|
|
140
|
+
context : Context
|
|
141
|
+
The context to store.
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
@abstractmethod
|
|
145
|
+
def get_context(self, run_id: int) -> Optional[Context]:
|
|
146
|
+
"""Retrieve a context by its run ID.
|
|
147
|
+
|
|
148
|
+
Parameters
|
|
149
|
+
----------
|
|
150
|
+
run_id : int
|
|
151
|
+
The ID of the run with which the context is associated.
|
|
152
|
+
|
|
153
|
+
Returns
|
|
154
|
+
-------
|
|
155
|
+
Optional[Context]
|
|
156
|
+
The `Context` instance if found, otherwise None.
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
@abstractmethod
|
|
160
|
+
def get_run_ids_with_pending_messages(self) -> Sequence[int]:
|
|
161
|
+
"""Retrieve run IDs that have at least one pending message.
|
|
162
|
+
|
|
163
|
+
Run IDs that are currently in progress (i.e., those associated with tokens)
|
|
164
|
+
will not be returned, even if they have pending messages.
|
|
165
|
+
|
|
166
|
+
Returns
|
|
167
|
+
-------
|
|
168
|
+
Sequence[int]
|
|
169
|
+
Sequence of run IDs with pending messages.
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
@abstractmethod
|
|
173
|
+
def create_token(self, run_id: int) -> str:
|
|
174
|
+
"""Create a token for the given run ID.
|
|
175
|
+
|
|
176
|
+
Parameters
|
|
177
|
+
----------
|
|
178
|
+
run_id : int
|
|
179
|
+
The ID of the run for which to create a token.
|
|
180
|
+
|
|
181
|
+
Returns
|
|
182
|
+
-------
|
|
183
|
+
str
|
|
184
|
+
A unique token associated with the run ID.
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
@abstractmethod
|
|
188
|
+
def verify_token(self, run_id: int, token: str) -> bool:
|
|
189
|
+
"""Verify a token for the given run ID.
|
|
190
|
+
|
|
191
|
+
Parameters
|
|
192
|
+
----------
|
|
193
|
+
run_id : int
|
|
194
|
+
The ID of the run for which to verify the token.
|
|
195
|
+
token : str
|
|
196
|
+
The token to verify.
|
|
197
|
+
|
|
198
|
+
Returns
|
|
199
|
+
-------
|
|
200
|
+
bool
|
|
201
|
+
True if the token is valid for the run ID, False otherwise.
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
@abstractmethod
|
|
205
|
+
def delete_token(self, run_id: int) -> None:
|
|
206
|
+
"""Delete the token for the given run ID.
|
|
207
|
+
|
|
208
|
+
Parameters
|
|
209
|
+
----------
|
|
210
|
+
run_id : int
|
|
211
|
+
The ID of the run for which to delete the token.
|
|
212
|
+
"""
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Copyright 2025 Flower Labs GmbH. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
# ==============================================================================
|
|
15
|
+
"""Flower SuperNode components."""
|
|
@@ -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.
|
|
@@ -15,24 +15,24 @@
|
|
|
15
15
|
"""Flower ClientApp process."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
import argparse
|
|
19
18
|
import gc
|
|
19
|
+
import os
|
|
20
|
+
import threading
|
|
20
21
|
import time
|
|
21
22
|
from logging import DEBUG, ERROR, INFO
|
|
22
23
|
from typing import Optional
|
|
23
24
|
|
|
24
25
|
import grpc
|
|
25
26
|
|
|
27
|
+
from flwr.app.error import Error
|
|
26
28
|
from flwr.cli.install import install_from_fab
|
|
27
29
|
from flwr.client.client_app import ClientApp, LoadClientAppError
|
|
30
|
+
from flwr.client.clientapp.utils import get_load_client_app_fn
|
|
28
31
|
from flwr.common import Context, Message
|
|
29
|
-
from flwr.common.args import add_args_flwr_app_common
|
|
30
32
|
from flwr.common.config import get_flwr_dir
|
|
31
|
-
from flwr.common.constant import
|
|
32
|
-
from flwr.common.exit import ExitCode, flwr_exit
|
|
33
|
+
from flwr.common.constant import ErrorCode
|
|
33
34
|
from flwr.common.grpc import create_channel, on_channel_state_change
|
|
34
35
|
from flwr.common.logger import log
|
|
35
|
-
from flwr.common.message import Error
|
|
36
36
|
from flwr.common.retry_invoker import _make_simple_grpc_retry_invoker, _wrap_stub
|
|
37
37
|
from flwr.common.serde import (
|
|
38
38
|
context_from_proto,
|
|
@@ -55,43 +55,20 @@ from flwr.proto.clientappio_pb2 import (
|
|
|
55
55
|
)
|
|
56
56
|
from flwr.proto.clientappio_pb2_grpc import ClientAppIoStub
|
|
57
57
|
|
|
58
|
-
from .utils import get_load_client_app_fn
|
|
59
58
|
|
|
60
|
-
|
|
61
|
-
def flwr_clientapp() -> None:
|
|
62
|
-
"""Run process-isolated Flower ClientApp."""
|
|
63
|
-
args = _parse_args_run_flwr_clientapp().parse_args()
|
|
64
|
-
if not args.insecure:
|
|
65
|
-
flwr_exit(
|
|
66
|
-
ExitCode.COMMON_TLS_NOT_SUPPORTED,
|
|
67
|
-
"flwr-clientapp does not support TLS yet.",
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
log(INFO, "Start `flwr-clientapp` process")
|
|
71
|
-
log(
|
|
72
|
-
DEBUG,
|
|
73
|
-
"`flwr-clientapp` will attempt to connect to SuperNode's "
|
|
74
|
-
"ClientAppIo API at %s with token %s",
|
|
75
|
-
args.clientappio_api_address,
|
|
76
|
-
args.token,
|
|
77
|
-
)
|
|
78
|
-
run_clientapp(
|
|
79
|
-
clientappio_api_address=args.clientappio_api_address,
|
|
80
|
-
run_once=(args.token is not None),
|
|
81
|
-
token=args.token,
|
|
82
|
-
flwr_dir=args.flwr_dir,
|
|
83
|
-
certificates=None,
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
def run_clientapp( # pylint: disable=R0914
|
|
59
|
+
def run_clientapp( # pylint: disable=R0913, R0914, R0917
|
|
88
60
|
clientappio_api_address: str,
|
|
89
61
|
run_once: bool,
|
|
90
62
|
token: Optional[int] = None,
|
|
91
63
|
flwr_dir: Optional[str] = None,
|
|
92
64
|
certificates: Optional[bytes] = None,
|
|
65
|
+
parent_pid: Optional[int] = None,
|
|
93
66
|
) -> None:
|
|
94
67
|
"""Run Flower ClientApp process."""
|
|
68
|
+
# Monitor the main process in case of SIGKILL
|
|
69
|
+
if parent_pid is not None:
|
|
70
|
+
start_parent_process_monitor(parent_pid)
|
|
71
|
+
|
|
95
72
|
channel = create_channel(
|
|
96
73
|
server_address=clientappio_api_address,
|
|
97
74
|
insecure=(certificates is None),
|
|
@@ -181,6 +158,20 @@ def run_clientapp( # pylint: disable=R0914
|
|
|
181
158
|
channel.close()
|
|
182
159
|
|
|
183
160
|
|
|
161
|
+
def start_parent_process_monitor(
|
|
162
|
+
parent_pid: int,
|
|
163
|
+
) -> None:
|
|
164
|
+
"""Monitor the parent process and exit if it terminates."""
|
|
165
|
+
|
|
166
|
+
def monitor() -> None:
|
|
167
|
+
while True:
|
|
168
|
+
time.sleep(0.2)
|
|
169
|
+
if os.getppid() != parent_pid:
|
|
170
|
+
os.kill(os.getpid(), 9)
|
|
171
|
+
|
|
172
|
+
threading.Thread(target=monitor, daemon=True).start()
|
|
173
|
+
|
|
174
|
+
|
|
184
175
|
def get_token(stub: grpc.Channel) -> Optional[int]:
|
|
185
176
|
"""Get a token from SuperNode."""
|
|
186
177
|
log(DEBUG, "[flwr-clientapp] Request token")
|
|
@@ -233,25 +224,3 @@ def push_clientappoutputs(
|
|
|
233
224
|
except grpc.RpcError as e:
|
|
234
225
|
log(ERROR, "[PushClientAppOutputs] gRPC error occurred: %s", str(e))
|
|
235
226
|
raise e
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
def _parse_args_run_flwr_clientapp() -> argparse.ArgumentParser:
|
|
239
|
-
"""Parse flwr-clientapp command line arguments."""
|
|
240
|
-
parser = argparse.ArgumentParser(
|
|
241
|
-
description="Run a Flower ClientApp",
|
|
242
|
-
)
|
|
243
|
-
parser.add_argument(
|
|
244
|
-
"--clientappio-api-address",
|
|
245
|
-
default=CLIENTAPPIO_API_DEFAULT_CLIENT_ADDRESS,
|
|
246
|
-
type=str,
|
|
247
|
-
help="Address of SuperNode's ClientAppIo API (IPv4, IPv6, or a domain name)."
|
|
248
|
-
f"By default, it is set to {CLIENTAPPIO_API_DEFAULT_CLIENT_ADDRESS}.",
|
|
249
|
-
)
|
|
250
|
-
parser.add_argument(
|
|
251
|
-
"--token",
|
|
252
|
-
type=int,
|
|
253
|
-
required=False,
|
|
254
|
-
help="Unique token generated by SuperNode for each ClientApp execution",
|
|
255
|
-
)
|
|
256
|
-
add_args_flwr_app_common(parser=parser)
|
|
257
|
-
return parser
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Copyright 2025 Flower Labs GmbH. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
# ==============================================================================
|
|
15
|
+
"""Flower SuperNode servicers."""
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Copyright 2025 Flower Labs GmbH. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
# ==============================================================================
|
|
15
|
+
"""ClientAppIo API Servicer."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from .clientappio_servicer import ClientAppInputs, ClientAppIoServicer, ClientAppOutputs
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"ClientAppInputs",
|
|
22
|
+
"ClientAppIoServicer",
|
|
23
|
+
"ClientAppOutputs",
|
|
24
|
+
]
|