flwr 1.22.0__py3-none-any.whl → 1.24.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 +34 -1
- flwr/cli/app_cmd/__init__.py +23 -0
- flwr/cli/app_cmd/publish.py +285 -0
- flwr/cli/app_cmd/review.py +252 -0
- flwr/cli/auth_plugin/__init__.py +15 -6
- flwr/cli/auth_plugin/auth_plugin.py +94 -0
- flwr/cli/auth_plugin/noop_auth_plugin.py +101 -0
- flwr/cli/auth_plugin/oidc_cli_plugin.py +46 -32
- flwr/cli/build.py +166 -53
- flwr/cli/{cli_user_auth_interceptor.py → cli_account_auth_interceptor.py} +29 -11
- flwr/cli/config_utils.py +101 -13
- flwr/cli/federation/__init__.py +24 -0
- flwr/cli/federation/ls.py +140 -0
- flwr/cli/federation/show.py +317 -0
- flwr/cli/install.py +91 -13
- flwr/cli/log.py +54 -11
- flwr/cli/login/login.py +41 -27
- flwr/cli/ls.py +177 -133
- flwr/cli/new/new.py +175 -40
- flwr/cli/new/templates/app/code/task.pytorch.py.tpl +1 -0
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +1 -1
- 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 +3 -3
- flwr/cli/new/templates/app/pyproject.pytorch_legacy_api.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +2 -2
- flwr/cli/new/templates/app/pyproject.xgboost.toml.tpl +1 -1
- flwr/cli/pull.py +12 -7
- flwr/cli/run/run.py +82 -31
- flwr/cli/run_utils.py +130 -0
- flwr/cli/stop.py +27 -9
- flwr/cli/supernode/__init__.py +25 -0
- flwr/cli/supernode/ls.py +268 -0
- flwr/cli/supernode/register.py +190 -0
- flwr/cli/supernode/unregister.py +140 -0
- flwr/cli/utils.py +464 -81
- flwr/client/__init__.py +2 -1
- flwr/client/dpfedavg_numpy_client.py +4 -1
- flwr/client/grpc_adapter_client/connection.py +12 -15
- flwr/client/grpc_rere_client/connection.py +68 -41
- flwr/client/grpc_rere_client/grpc_adapter.py +34 -14
- flwr/client/grpc_rere_client/{client_interceptor.py → node_auth_client_interceptor.py} +5 -7
- flwr/client/message_handler/message_handler.py +2 -2
- flwr/client/mod/secure_aggregation/secaggplus_mod.py +10 -8
- flwr/client/numpy_client.py +1 -1
- flwr/client/rest_client/connection.py +94 -51
- flwr/client/run_info_store.py +4 -5
- flwr/client/typing.py +1 -1
- flwr/clientapp/__init__.py +1 -2
- flwr/{client → 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/{client/clientapp → clientapp}/utils.py +4 -4
- flwr/common/address.py +1 -2
- flwr/common/args.py +3 -4
- flwr/common/config.py +13 -16
- flwr/common/constant.py +56 -13
- 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 +39 -10
- 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 +48 -31
- flwr/common/logger.py +19 -19
- flwr/common/message.py +4 -4
- flwr/common/object_ref.py +7 -7
- flwr/common/record/array.py +6 -6
- flwr/common/record/arrayrecord.py +18 -21
- flwr/common/record/configrecord.py +3 -3
- flwr/common/record/recorddict.py +5 -5
- flwr/common/record/typeddict.py +9 -2
- flwr/common/recorddict_compat.py +7 -10
- flwr/common/retry_invoker.py +20 -20
- flwr/common/secure_aggregation/crypto/symmetric_encryption.py +1 -89
- flwr/common/secure_aggregation/ndarrays_arithmetic.py +3 -3
- flwr/common/serde.py +9 -6
- flwr/common/serde_utils.py +2 -2
- flwr/common/telemetry.py +9 -5
- flwr/common/typing.py +59 -43
- flwr/compat/client/app.py +39 -38
- flwr/compat/client/grpc_client/connection.py +13 -13
- 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 +72 -40
- flwr/proto/control_pb2.pyi +319 -87
- flwr/proto/control_pb2_grpc.py +339 -28
- flwr/proto/control_pb2_grpc.pyi +209 -37
- flwr/proto/error_pb2.py +13 -3
- flwr/proto/error_pb2.pyi +24 -6
- flwr/proto/error_pb2_grpc.py +20 -0
- flwr/proto/error_pb2_grpc.pyi +27 -0
- flwr/proto/fab_pb2.py +24 -10
- flwr/proto/fab_pb2.pyi +68 -20
- flwr/proto/fab_pb2_grpc.py +20 -0
- flwr/proto/fab_pb2_grpc.pyi +27 -0
- flwr/proto/federation_pb2.py +38 -0
- flwr/proto/federation_pb2.pyi +56 -0
- flwr/proto/federation_pb2_grpc.py +24 -0
- flwr/proto/federation_pb2_grpc.pyi +31 -0
- flwr/proto/fleet_pb2.py +45 -27
- flwr/proto/fleet_pb2.pyi +186 -70
- flwr/proto/fleet_pb2_grpc.py +277 -66
- flwr/proto/fleet_pb2_grpc.pyi +201 -55
- flwr/proto/grpcadapter_pb2.py +14 -4
- flwr/proto/grpcadapter_pb2.pyi +38 -16
- flwr/proto/grpcadapter_pb2_grpc.py +35 -4
- flwr/proto/grpcadapter_pb2_grpc.pyi +38 -7
- flwr/proto/heartbeat_pb2.py +17 -7
- flwr/proto/heartbeat_pb2.pyi +51 -22
- flwr/proto/heartbeat_pb2_grpc.py +20 -0
- flwr/proto/heartbeat_pb2_grpc.pyi +27 -0
- flwr/proto/log_pb2.py +13 -3
- flwr/proto/log_pb2.pyi +34 -11
- flwr/proto/log_pb2_grpc.py +20 -0
- flwr/proto/log_pb2_grpc.pyi +27 -0
- flwr/proto/message_pb2.py +15 -5
- flwr/proto/message_pb2.pyi +154 -86
- flwr/proto/message_pb2_grpc.py +20 -0
- flwr/proto/message_pb2_grpc.pyi +27 -0
- flwr/proto/node_pb2.py +16 -4
- flwr/proto/node_pb2.pyi +77 -4
- flwr/proto/node_pb2_grpc.py +20 -0
- flwr/proto/node_pb2_grpc.pyi +27 -0
- flwr/proto/recorddict_pb2.py +13 -3
- flwr/proto/recorddict_pb2.pyi +184 -107
- flwr/proto/recorddict_pb2_grpc.py +20 -0
- flwr/proto/recorddict_pb2_grpc.pyi +27 -0
- flwr/proto/run_pb2.py +40 -31
- flwr/proto/run_pb2.pyi +149 -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 +173 -127
- 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 +19 -8
- flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -2
- flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py +3 -4
- flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py +10 -12
- flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +1 -3
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +136 -42
- flwr/server/superlink/fleet/grpc_rere/{server_interceptor.py → node_auth_server_interceptor.py} +28 -51
- flwr/server/superlink/fleet/message_handler/message_handler.py +100 -49
- flwr/server/superlink/fleet/rest_rere/rest_api.py +54 -33
- flwr/server/superlink/fleet/vce/backend/backend.py +2 -2
- flwr/server/superlink/fleet/vce/backend/raybackend.py +6 -6
- flwr/server/superlink/fleet/vce/vce_api.py +32 -13
- flwr/server/superlink/linkstate/in_memory_linkstate.py +266 -207
- flwr/server/superlink/linkstate/linkstate.py +161 -62
- flwr/server/superlink/linkstate/linkstate_factory.py +24 -6
- flwr/server/superlink/linkstate/sqlite_linkstate.py +698 -638
- flwr/server/superlink/linkstate/utils.py +9 -60
- flwr/server/superlink/serverappio/serverappio_grpc.py +1 -2
- flwr/server/superlink/serverappio/serverappio_servicer.py +28 -23
- flwr/server/superlink/simulation/simulationio_grpc.py +1 -2
- flwr/server/superlink/simulation/simulationio_servicer.py +19 -14
- flwr/server/superlink/utils.py +4 -6
- flwr/server/typing.py +1 -1
- flwr/server/utils/tensorboard.py +15 -8
- flwr/server/utils/validator.py +2 -3
- flwr/server/workflow/default_workflows.py +5 -5
- flwr/server/workflow/secure_aggregation/secagg_workflow.py +2 -4
- flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +12 -10
- 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 +11 -12
- flwr/simulation/ray_transport/ray_client_proxy.py +12 -13
- flwr/simulation/run_simulation.py +44 -43
- flwr/simulation/simulationio_connection.py +4 -4
- flwr/supercore/cli/flower_superexec.py +3 -4
- flwr/supercore/constant.py +52 -0
- 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 -6
- flwr/supercore/object_store/object_store.py +1 -2
- flwr/supercore/object_store/object_store_factory.py +27 -8
- flwr/supercore/object_store/sqlite_object_store.py +253 -0
- flwr/{client/clientapp → supercore/primitives}/__init__.py +1 -1
- flwr/supercore/primitives/asymmetric.py +117 -0
- flwr/supercore/primitives/asymmetric_ed25519.py +175 -0
- flwr/supercore/sqlite_mixin.py +159 -0
- flwr/supercore/superexec/plugin/base_exec_plugin.py +1 -2
- flwr/supercore/superexec/plugin/exec_plugin.py +3 -3
- flwr/supercore/superexec/run_superexec.py +9 -13
- flwr/supercore/utils.py +20 -0
- flwr/superlink/artifact_provider/artifact_provider.py +1 -2
- flwr/{common → superlink}/auth_plugin/__init__.py +6 -6
- flwr/superlink/auth_plugin/auth_plugin.py +88 -0
- flwr/superlink/auth_plugin/noop_auth_plugin.py +84 -0
- flwr/superlink/federation/__init__.py +24 -0
- flwr/superlink/federation/federation_manager.py +64 -0
- flwr/superlink/federation/noop_federation_manager.py +71 -0
- flwr/superlink/servicer/control/{control_user_auth_interceptor.py → control_account_auth_interceptor.py} +41 -32
- flwr/superlink/servicer/control/control_event_log_interceptor.py +7 -7
- flwr/superlink/servicer/control/control_grpc.py +18 -17
- flwr/superlink/servicer/control/control_license_interceptor.py +3 -3
- flwr/superlink/servicer/control/control_servicer.py +239 -63
- flwr/supernode/cli/flower_supernode.py +74 -26
- flwr/supernode/nodestate/in_memory_nodestate.py +60 -49
- flwr/supernode/nodestate/nodestate.py +7 -8
- flwr/supernode/nodestate/nodestate_factory.py +7 -4
- flwr/supernode/runtime/run_clientapp.py +43 -24
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +40 -10
- flwr/supernode/start_client_internal.py +175 -51
- {flwr-1.22.0.dist-info → flwr-1.24.0.dist-info}/METADATA +8 -8
- flwr-1.24.0.dist-info/RECORD +454 -0
- flwr/common/auth_plugin/auth_plugin.py +0 -149
- flwr/supercore/object_store/utils.py +0 -43
- flwr-1.22.0.dist-info/RECORD +0 -428
- {flwr-1.22.0.dist-info → flwr-1.24.0.dist-info}/WHEEL +0 -0
- {flwr-1.22.0.dist-info → flwr-1.24.0.dist-info}/entry_points.txt +0 -0
flwr/cli/build.py
CHANGED
|
@@ -19,28 +19,45 @@ import hashlib
|
|
|
19
19
|
import zipfile
|
|
20
20
|
from io import BytesIO
|
|
21
21
|
from pathlib import Path
|
|
22
|
-
from typing import Annotated, Any
|
|
22
|
+
from typing import Annotated, Any
|
|
23
23
|
|
|
24
24
|
import pathspec
|
|
25
|
+
import tomli
|
|
25
26
|
import tomli_w
|
|
26
27
|
import typer
|
|
27
28
|
|
|
28
29
|
from flwr.common.constant import (
|
|
29
|
-
|
|
30
|
+
FAB_CONFIG_FILE,
|
|
30
31
|
FAB_DATE,
|
|
32
|
+
FAB_EXCLUDE_PATTERNS,
|
|
31
33
|
FAB_HASH_TRUNCATION,
|
|
34
|
+
FAB_INCLUDE_PATTERNS,
|
|
32
35
|
FAB_MAX_SIZE,
|
|
33
36
|
)
|
|
34
37
|
|
|
35
|
-
from .config_utils import load as load_toml
|
|
36
38
|
from .config_utils import load_and_validate
|
|
37
|
-
from .utils import is_valid_project_name
|
|
39
|
+
from .utils import build_pathspec, is_valid_project_name, load_gitignore_patterns
|
|
38
40
|
|
|
39
41
|
|
|
40
42
|
def write_to_zip(
|
|
41
|
-
zipfile_obj: zipfile.ZipFile, filename: str, contents:
|
|
43
|
+
zipfile_obj: zipfile.ZipFile, filename: str, contents: bytes | str
|
|
42
44
|
) -> zipfile.ZipFile:
|
|
43
|
-
"""Set a fixed date and write contents to a zip file.
|
|
45
|
+
"""Set a fixed date and write contents to a zip file.
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
zipfile_obj : zipfile.ZipFile
|
|
50
|
+
The ZipFile object to write to.
|
|
51
|
+
filename : str
|
|
52
|
+
Name of the file within the zip archive.
|
|
53
|
+
contents : bytes | str
|
|
54
|
+
The file contents to write.
|
|
55
|
+
|
|
56
|
+
Returns
|
|
57
|
+
-------
|
|
58
|
+
ZipFile
|
|
59
|
+
The modified ZipFile object.
|
|
60
|
+
"""
|
|
44
61
|
zip_info = zipfile.ZipInfo(filename)
|
|
45
62
|
zip_info.date_time = FAB_DATE
|
|
46
63
|
zipfile_obj.writestr(zip_info, contents)
|
|
@@ -48,7 +65,21 @@ def write_to_zip(
|
|
|
48
65
|
|
|
49
66
|
|
|
50
67
|
def get_fab_filename(config: dict[str, Any], fab_hash: str) -> str:
|
|
51
|
-
"""Get the FAB filename based on the given config and FAB hash.
|
|
68
|
+
"""Get the FAB filename based on the given config and FAB hash.
|
|
69
|
+
|
|
70
|
+
Parameters
|
|
71
|
+
----------
|
|
72
|
+
config : dict[str, Any]
|
|
73
|
+
The project configuration dictionary.
|
|
74
|
+
fab_hash : str
|
|
75
|
+
The SHA-256 hash of the FAB file.
|
|
76
|
+
|
|
77
|
+
Returns
|
|
78
|
+
-------
|
|
79
|
+
str
|
|
80
|
+
The formatted FAB filename in the pattern:
|
|
81
|
+
<publisher>.<name>.<version>.<hash_prefix>.fab
|
|
82
|
+
"""
|
|
52
83
|
publisher = config["tool"]["flwr"]["app"]["publisher"]
|
|
53
84
|
name = config["project"]["name"]
|
|
54
85
|
version = config["project"]["version"].replace(".", "-")
|
|
@@ -59,7 +90,7 @@ def get_fab_filename(config: dict[str, Any], fab_hash: str) -> str:
|
|
|
59
90
|
# pylint: disable=too-many-locals, too-many-statements
|
|
60
91
|
def build(
|
|
61
92
|
app: Annotated[
|
|
62
|
-
|
|
93
|
+
Path | None,
|
|
63
94
|
typer.Option(help="Path of the Flower App to bundle into a FAB"),
|
|
64
95
|
] = None,
|
|
65
96
|
) -> None:
|
|
@@ -80,6 +111,7 @@ def build(
|
|
|
80
111
|
f"❌ The path {app} is not a valid path to a Flower app.",
|
|
81
112
|
fg=typer.colors.RED,
|
|
82
113
|
bold=True,
|
|
114
|
+
err=True,
|
|
83
115
|
)
|
|
84
116
|
raise typer.Exit(code=1)
|
|
85
117
|
|
|
@@ -90,6 +122,7 @@ def build(
|
|
|
90
122
|
"and can only contain letters, digits, and hyphens.",
|
|
91
123
|
fg=typer.colors.RED,
|
|
92
124
|
bold=True,
|
|
125
|
+
err=True,
|
|
93
126
|
)
|
|
94
127
|
raise typer.Exit(code=1)
|
|
95
128
|
|
|
@@ -100,6 +133,7 @@ def build(
|
|
|
100
133
|
+ "\n".join([f"- {line}" for line in errors]),
|
|
101
134
|
fg=typer.colors.RED,
|
|
102
135
|
bold=True,
|
|
136
|
+
err=True,
|
|
103
137
|
)
|
|
104
138
|
raise typer.Exit(code=1)
|
|
105
139
|
|
|
@@ -112,7 +146,10 @@ def build(
|
|
|
112
146
|
)
|
|
113
147
|
|
|
114
148
|
# Build FAB
|
|
115
|
-
fab_bytes
|
|
149
|
+
fab_bytes = build_fab_from_disk(app)
|
|
150
|
+
|
|
151
|
+
# Calculate hash for filename
|
|
152
|
+
fab_hash = hashlib.sha256(fab_bytes).hexdigest()
|
|
116
153
|
|
|
117
154
|
# Get the name of the zip file
|
|
118
155
|
fab_filename = get_fab_filename(config, fab_hash)
|
|
@@ -125,11 +162,10 @@ def build(
|
|
|
125
162
|
)
|
|
126
163
|
|
|
127
164
|
|
|
128
|
-
def
|
|
129
|
-
"""Build a FAB
|
|
165
|
+
def build_fab_from_disk(app: Path) -> bytes:
|
|
166
|
+
"""Build a FAB from files on disk and return the FAB as bytes.
|
|
130
167
|
|
|
131
|
-
This function
|
|
132
|
-
bundles it into a FAB without performing additional validation.
|
|
168
|
+
This function reads files from disk and bundles them into a FAB.
|
|
133
169
|
|
|
134
170
|
Parameters
|
|
135
171
|
----------
|
|
@@ -138,18 +174,67 @@ def build_fab(app: Path) -> tuple[bytes, str, dict[str, Any]]:
|
|
|
138
174
|
|
|
139
175
|
Returns
|
|
140
176
|
-------
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
- the FAB as bytes
|
|
144
|
-
- the SHA256 hash of the FAB
|
|
145
|
-
- the project configuration (with the 'federations' field removed)
|
|
177
|
+
bytes
|
|
178
|
+
The FAB as bytes.
|
|
146
179
|
"""
|
|
147
180
|
app = app.resolve()
|
|
148
181
|
|
|
149
|
-
#
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
182
|
+
# Collect all files recursively (including pyproject.toml and .gitignore)
|
|
183
|
+
all_files = [f for f in app.rglob("*") if f.is_file()]
|
|
184
|
+
|
|
185
|
+
# Create dict mapping relative paths to Path objects
|
|
186
|
+
files_dict: dict[str, bytes | Path] = {
|
|
187
|
+
# Ensure consistent path separators across platforms
|
|
188
|
+
str(file_path.relative_to(app)).replace("\\", "/"): file_path
|
|
189
|
+
for file_path in all_files
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
# Build FAB from the files dict
|
|
193
|
+
return build_fab_from_files(files_dict)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def build_fab_from_files(files: dict[str, bytes | Path]) -> bytes:
|
|
197
|
+
r"""Build a FAB from in-memory files and return the FAB as bytes.
|
|
198
|
+
|
|
199
|
+
This is the core FAB building function that works with in-memory data.
|
|
200
|
+
It accepts either bytes or Path objects as file contents, applies filtering
|
|
201
|
+
rules (include/exclude patterns), and builds the FAB.
|
|
202
|
+
|
|
203
|
+
Parameters
|
|
204
|
+
----------
|
|
205
|
+
files : dict[str, Union[bytes, Path]]
|
|
206
|
+
Dictionary mapping relative file paths to their contents.
|
|
207
|
+
- Keys: Relative paths (strings)
|
|
208
|
+
- Values: Either bytes (file contents) or Path (will be read)
|
|
209
|
+
Must include "pyproject.toml" and optionally ".gitignore".
|
|
210
|
+
|
|
211
|
+
Returns
|
|
212
|
+
-------
|
|
213
|
+
bytes
|
|
214
|
+
The FAB as bytes.
|
|
215
|
+
|
|
216
|
+
Examples
|
|
217
|
+
--------
|
|
218
|
+
Build a FAB from in-memory files::
|
|
219
|
+
|
|
220
|
+
files = {
|
|
221
|
+
"pyproject.toml": b"[project]\nname = 'myapp'\n...",
|
|
222
|
+
".gitignore": b"*.pyc\n__pycache__/\n",
|
|
223
|
+
"src/client.py": Path("/path/to/client.py"),
|
|
224
|
+
"src/server.py": b"print('hello')",
|
|
225
|
+
"README.md": b"# My App\n",
|
|
226
|
+
}
|
|
227
|
+
fab_bytes = build_fab_from_files(files)
|
|
228
|
+
"""
|
|
229
|
+
|
|
230
|
+
def to_bytes(content: bytes | Path) -> bytes:
|
|
231
|
+
return content.read_bytes() if isinstance(content, Path) else content
|
|
232
|
+
|
|
233
|
+
# Extract, load, and parse pyproject.toml
|
|
234
|
+
if FAB_CONFIG_FILE not in files:
|
|
235
|
+
raise ValueError(f"{FAB_CONFIG_FILE} not found in files")
|
|
236
|
+
pyproject_content = to_bytes(files[FAB_CONFIG_FILE])
|
|
237
|
+
config = tomli.loads(pyproject_content.decode("utf-8"))
|
|
153
238
|
|
|
154
239
|
# Remove the 'federations' field if it exists
|
|
155
240
|
if (
|
|
@@ -159,18 +244,22 @@ def build_fab(app: Path) -> tuple[bytes, str, dict[str, Any]]:
|
|
|
159
244
|
):
|
|
160
245
|
del config["tool"]["flwr"]["federations"]
|
|
161
246
|
|
|
162
|
-
#
|
|
163
|
-
|
|
247
|
+
# Extract and load .gitignore if present
|
|
248
|
+
gitignore_content = None
|
|
249
|
+
if ".gitignore" in files:
|
|
250
|
+
gitignore_content = to_bytes(files[".gitignore"])
|
|
251
|
+
|
|
252
|
+
# Get exclude and include specs
|
|
253
|
+
exclude_spec = get_fab_exclude_pathspec(gitignore_content)
|
|
254
|
+
include_spec = get_fab_include_pathspec()
|
|
164
255
|
|
|
165
|
-
#
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
for
|
|
169
|
-
if not
|
|
170
|
-
and f.suffix in FAB_ALLOWED_EXTENSIONS
|
|
171
|
-
and f.name != "pyproject.toml" # Exclude the original pyproject.toml
|
|
256
|
+
# Filter files based on include/exclude specs
|
|
257
|
+
filtered_paths = [
|
|
258
|
+
path.replace("\\", "/") # Ensure consistent path separators across platforms
|
|
259
|
+
for path in files.keys()
|
|
260
|
+
if include_spec.match_file(path) and not exclude_spec.match_file(path)
|
|
172
261
|
]
|
|
173
|
-
|
|
262
|
+
filtered_paths.sort() # Sort for deterministic output
|
|
174
263
|
|
|
175
264
|
# Create a zip file in memory
|
|
176
265
|
list_file_content = ""
|
|
@@ -178,41 +267,65 @@ def build_fab(app: Path) -> tuple[bytes, str, dict[str, Any]]:
|
|
|
178
267
|
fab_buffer = BytesIO()
|
|
179
268
|
with zipfile.ZipFile(fab_buffer, "w", zipfile.ZIP_DEFLATED) as fab_file:
|
|
180
269
|
# Add pyproject.toml
|
|
181
|
-
write_to_zip(fab_file,
|
|
270
|
+
write_to_zip(fab_file, FAB_CONFIG_FILE, tomli_w.dumps(config))
|
|
271
|
+
|
|
272
|
+
for file_path in filtered_paths:
|
|
182
273
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
file_contents = file_path.read_bytes()
|
|
274
|
+
# Get file contents as bytes
|
|
275
|
+
file_content = to_bytes(files[file_path])
|
|
186
276
|
|
|
187
|
-
|
|
188
|
-
write_to_zip(fab_file,
|
|
277
|
+
# Write file to FAB
|
|
278
|
+
write_to_zip(fab_file, file_path, file_content)
|
|
189
279
|
|
|
190
|
-
# Calculate file info
|
|
191
|
-
sha256_hash = hashlib.sha256(
|
|
192
|
-
file_size_bits = len(
|
|
193
|
-
list_file_content += f"{
|
|
280
|
+
# Calculate file info for CONTENT manifest
|
|
281
|
+
sha256_hash = hashlib.sha256(file_content).hexdigest()
|
|
282
|
+
file_size_bits = len(file_content) * 8 # size in bits
|
|
283
|
+
list_file_content += f"{file_path},{sha256_hash},{file_size_bits}\n"
|
|
194
284
|
|
|
195
|
-
# Add CONTENT
|
|
285
|
+
# Add CONTENT manifest to the zip file
|
|
196
286
|
write_to_zip(fab_file, ".info/CONTENT", list_file_content)
|
|
197
287
|
|
|
198
288
|
fab_bytes = fab_buffer.getvalue()
|
|
289
|
+
|
|
290
|
+
# Validate FAB size
|
|
199
291
|
if len(fab_bytes) > FAB_MAX_SIZE:
|
|
200
292
|
raise ValueError(
|
|
201
|
-
f"FAB size exceeds maximum allowed size of {FAB_MAX_SIZE:,} bytes."
|
|
293
|
+
f"FAB size exceeds maximum allowed size of {FAB_MAX_SIZE:,} bytes. "
|
|
202
294
|
"To reduce the package size, consider ignoring unnecessary files "
|
|
203
295
|
"via your `.gitignore` file or excluding them from the build."
|
|
204
296
|
)
|
|
205
297
|
|
|
206
|
-
|
|
298
|
+
return fab_bytes
|
|
299
|
+
|
|
207
300
|
|
|
208
|
-
|
|
301
|
+
def get_fab_include_pathspec() -> pathspec.PathSpec:
|
|
302
|
+
"""Get the PathSpec for files to include in a FAB.
|
|
209
303
|
|
|
304
|
+
Returns
|
|
305
|
+
-------
|
|
306
|
+
PathSpec
|
|
307
|
+
PathSpec object with default include patterns for FAB files.
|
|
308
|
+
"""
|
|
309
|
+
return build_pathspec(FAB_INCLUDE_PATTERNS)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def get_fab_exclude_pathspec(gitignore_content: bytes | None) -> pathspec.PathSpec:
|
|
313
|
+
"""Get the PathSpec for files to exclude from a FAB.
|
|
314
|
+
|
|
315
|
+
If gitignore_content is provided, its patterns will be combined with the default
|
|
316
|
+
exclude patterns.
|
|
210
317
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
318
|
+
Parameters
|
|
319
|
+
----------
|
|
320
|
+
gitignore_content : bytes | None
|
|
321
|
+
Optional gitignore file content as bytes.
|
|
322
|
+
|
|
323
|
+
Returns
|
|
324
|
+
-------
|
|
325
|
+
PathSpec
|
|
326
|
+
PathSpec object with combined exclude patterns.
|
|
327
|
+
"""
|
|
328
|
+
patterns = list(FAB_EXCLUDE_PATTERNS)
|
|
329
|
+
if gitignore_content:
|
|
330
|
+
patterns += load_gitignore_patterns(gitignore_content)
|
|
218
331
|
return pathspec.PathSpec.from_lines("gitwildmatch", patterns)
|
|
@@ -15,26 +15,29 @@
|
|
|
15
15
|
"""Flower run interceptor."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
from
|
|
18
|
+
from collections.abc import Callable
|
|
19
|
+
from typing import Any
|
|
19
20
|
|
|
20
21
|
import grpc
|
|
21
22
|
|
|
22
|
-
from flwr.common.auth_plugin import CliAuthPlugin
|
|
23
23
|
from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
|
24
24
|
StartRunRequest,
|
|
25
25
|
StreamLogsRequest,
|
|
26
26
|
)
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
]
|
|
28
|
+
from .auth_plugin import CliAuthPlugin
|
|
29
|
+
|
|
30
|
+
Request = StartRunRequest | StreamLogsRequest
|
|
32
31
|
|
|
33
32
|
|
|
34
|
-
class
|
|
33
|
+
class CliAccountAuthInterceptor(
|
|
35
34
|
grpc.UnaryUnaryClientInterceptor, grpc.UnaryStreamClientInterceptor # type: ignore
|
|
36
35
|
):
|
|
37
|
-
"""CLI interceptor for
|
|
36
|
+
"""CLI interceptor for account authentication.
|
|
37
|
+
|
|
38
|
+
This interceptor adds authentication tokens to gRPC metadata for CLI requests and
|
|
39
|
+
handles token refresh from response metadata.
|
|
40
|
+
"""
|
|
38
41
|
|
|
39
42
|
def __init__(self, auth_plugin: CliAuthPlugin):
|
|
40
43
|
self.auth_plugin = auth_plugin
|
|
@@ -45,7 +48,22 @@ class CliUserAuthInterceptor(
|
|
|
45
48
|
client_call_details: grpc.ClientCallDetails,
|
|
46
49
|
request: Request,
|
|
47
50
|
) -> grpc.Call:
|
|
48
|
-
"""Send and receive tokens via metadata.
|
|
51
|
+
"""Send and receive tokens via metadata.
|
|
52
|
+
|
|
53
|
+
Parameters
|
|
54
|
+
----------
|
|
55
|
+
continuation : Callable[[Any, Any], Any]
|
|
56
|
+
The next interceptor or handler in the chain.
|
|
57
|
+
client_call_details : grpc.ClientCallDetails
|
|
58
|
+
Details of the RPC call as a NamedTuple.
|
|
59
|
+
request : Request
|
|
60
|
+
The RPC request object.
|
|
61
|
+
|
|
62
|
+
Returns
|
|
63
|
+
-------
|
|
64
|
+
grpc.Call
|
|
65
|
+
The RPC response.
|
|
66
|
+
"""
|
|
49
67
|
new_metadata = self.auth_plugin.write_tokens_to_metadata(
|
|
50
68
|
client_call_details.metadata or []
|
|
51
69
|
)
|
|
@@ -69,7 +87,7 @@ class CliUserAuthInterceptor(
|
|
|
69
87
|
client_call_details: grpc.ClientCallDetails,
|
|
70
88
|
request: Request,
|
|
71
89
|
) -> grpc.Call:
|
|
72
|
-
"""Intercept a unary-unary call for
|
|
90
|
+
"""Intercept a unary-unary call for account authentication.
|
|
73
91
|
|
|
74
92
|
This method intercepts a unary-unary RPC call initiated from the CLI and adds
|
|
75
93
|
the required authentication tokens to the RPC metadata.
|
|
@@ -82,7 +100,7 @@ class CliUserAuthInterceptor(
|
|
|
82
100
|
client_call_details: grpc.ClientCallDetails,
|
|
83
101
|
request: Request,
|
|
84
102
|
) -> grpc.Call:
|
|
85
|
-
"""Intercept a unary-stream call for
|
|
103
|
+
"""Intercept a unary-stream call for account authentication.
|
|
86
104
|
|
|
87
105
|
This method intercepts a unary-stream RPC call initiated from the CLI and adds
|
|
88
106
|
the required authentication tokens to the RPC metadata.
|
flwr/cli/config_utils.py
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
from pathlib import Path
|
|
19
|
-
from typing import Any
|
|
19
|
+
from typing import Any
|
|
20
20
|
|
|
21
21
|
import tomli
|
|
22
22
|
import typer
|
|
@@ -30,7 +30,7 @@ from flwr.common.config import (
|
|
|
30
30
|
)
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
def get_fab_metadata(fab_file:
|
|
33
|
+
def get_fab_metadata(fab_file: Path | bytes) -> tuple[str, str]:
|
|
34
34
|
"""Extract the fab_id and the fab_version from a FAB file or path.
|
|
35
35
|
|
|
36
36
|
Parameters
|
|
@@ -48,9 +48,9 @@ def get_fab_metadata(fab_file: Union[Path, bytes]) -> tuple[str, str]:
|
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
def load_and_validate(
|
|
51
|
-
path:
|
|
51
|
+
path: Path | None = None,
|
|
52
52
|
check_module: bool = True,
|
|
53
|
-
) -> tuple[
|
|
53
|
+
) -> tuple[dict[str, Any] | None, list[str], list[str]]:
|
|
54
54
|
"""Load and validate pyproject.toml as dict.
|
|
55
55
|
|
|
56
56
|
Parameters
|
|
@@ -89,8 +89,20 @@ def load_and_validate(
|
|
|
89
89
|
return (config, errors, warnings)
|
|
90
90
|
|
|
91
91
|
|
|
92
|
-
def load(toml_path: Path) ->
|
|
93
|
-
"""Load pyproject.toml and return as dict.
|
|
92
|
+
def load(toml_path: Path) -> dict[str, Any] | None:
|
|
93
|
+
"""Load pyproject.toml and return as dict.
|
|
94
|
+
|
|
95
|
+
Parameters
|
|
96
|
+
----------
|
|
97
|
+
toml_path : Path
|
|
98
|
+
Path to the pyproject.toml file.
|
|
99
|
+
|
|
100
|
+
Returns
|
|
101
|
+
-------
|
|
102
|
+
dict[str, Any] | None
|
|
103
|
+
Parsed TOML configuration as dictionary, or None if file doesn't exist
|
|
104
|
+
or has invalid TOML syntax.
|
|
105
|
+
"""
|
|
94
106
|
if not toml_path.is_file():
|
|
95
107
|
return None
|
|
96
108
|
|
|
@@ -102,12 +114,31 @@ def load(toml_path: Path) -> Optional[dict[str, Any]]:
|
|
|
102
114
|
|
|
103
115
|
|
|
104
116
|
def process_loaded_project_config(
|
|
105
|
-
config:
|
|
117
|
+
config: dict[str, Any] | None, errors: list[str], warnings: list[str]
|
|
106
118
|
) -> dict[str, Any]:
|
|
107
119
|
"""Process and return the loaded project configuration.
|
|
108
120
|
|
|
109
121
|
This function handles errors and warnings from the `load_and_validate` function,
|
|
110
122
|
exits on critical issues, and returns the validated configuration.
|
|
123
|
+
|
|
124
|
+
Parameters
|
|
125
|
+
----------
|
|
126
|
+
config : dict[str, Any] | None
|
|
127
|
+
The loaded configuration dictionary, or None if loading failed.
|
|
128
|
+
errors : list[str]
|
|
129
|
+
List of error messages from validation.
|
|
130
|
+
warnings : list[str]
|
|
131
|
+
List of warning messages from validation.
|
|
132
|
+
|
|
133
|
+
Returns
|
|
134
|
+
-------
|
|
135
|
+
dict[str, Any]
|
|
136
|
+
The validated configuration dictionary.
|
|
137
|
+
|
|
138
|
+
Raises
|
|
139
|
+
------
|
|
140
|
+
typer.Exit
|
|
141
|
+
If config is None or contains critical errors.
|
|
111
142
|
"""
|
|
112
143
|
if config is None:
|
|
113
144
|
typer.secho(
|
|
@@ -116,6 +147,7 @@ def process_loaded_project_config(
|
|
|
116
147
|
+ "\n".join([f"- {line}" for line in errors]),
|
|
117
148
|
fg=typer.colors.RED,
|
|
118
149
|
bold=True,
|
|
150
|
+
err=True,
|
|
119
151
|
)
|
|
120
152
|
raise typer.Exit(code=1)
|
|
121
153
|
|
|
@@ -133,11 +165,32 @@ def process_loaded_project_config(
|
|
|
133
165
|
|
|
134
166
|
|
|
135
167
|
def validate_federation_in_project_config(
|
|
136
|
-
federation:
|
|
168
|
+
federation: str | None,
|
|
137
169
|
config: dict[str, Any],
|
|
138
|
-
overrides:
|
|
170
|
+
overrides: list[str] | None = None,
|
|
139
171
|
) -> tuple[str, dict[str, Any]]:
|
|
140
|
-
"""Validate the federation name in the Flower project configuration.
|
|
172
|
+
"""Validate the federation name in the Flower project configuration.
|
|
173
|
+
|
|
174
|
+
Parameters
|
|
175
|
+
----------
|
|
176
|
+
federation : str | None
|
|
177
|
+
Name of the federation, or None to use default from config.
|
|
178
|
+
config : dict[str, Any]
|
|
179
|
+
The project configuration dictionary.
|
|
180
|
+
overrides : list[str] | None
|
|
181
|
+
List of configuration override strings. Default is None.
|
|
182
|
+
|
|
183
|
+
Returns
|
|
184
|
+
-------
|
|
185
|
+
tuple[str, dict[str, Any]]
|
|
186
|
+
A tuple of (federation_name, federation_config).
|
|
187
|
+
|
|
188
|
+
Raises
|
|
189
|
+
------
|
|
190
|
+
typer.Exit
|
|
191
|
+
If no federation name provided and no default found, or if federation
|
|
192
|
+
doesn't exist in config.
|
|
193
|
+
"""
|
|
141
194
|
federation = federation or config["tool"]["flwr"]["federations"].get("default")
|
|
142
195
|
|
|
143
196
|
if federation is None:
|
|
@@ -147,6 +200,7 @@ def validate_federation_in_project_config(
|
|
|
147
200
|
"`options.num-supernodes` value).",
|
|
148
201
|
fg=typer.colors.RED,
|
|
149
202
|
bold=True,
|
|
203
|
+
err=True,
|
|
150
204
|
)
|
|
151
205
|
raise typer.Exit(code=1)
|
|
152
206
|
|
|
@@ -162,6 +216,7 @@ def validate_federation_in_project_config(
|
|
|
162
216
|
+ "\n".join(available_feds),
|
|
163
217
|
fg=typer.colors.RED,
|
|
164
218
|
bold=True,
|
|
219
|
+
err=True,
|
|
165
220
|
)
|
|
166
221
|
raise typer.Exit(code=1)
|
|
167
222
|
|
|
@@ -175,7 +230,7 @@ def validate_federation_in_project_config(
|
|
|
175
230
|
|
|
176
231
|
def validate_certificate_in_federation_config(
|
|
177
232
|
app: Path, federation_config: dict[str, Any]
|
|
178
|
-
) -> tuple[bool,
|
|
233
|
+
) -> tuple[bool, bytes | None]:
|
|
179
234
|
"""Validate the certificates in the Flower project configuration.
|
|
180
235
|
|
|
181
236
|
Accepted configurations:
|
|
@@ -207,6 +262,7 @@ def validate_certificate_in_federation_config(
|
|
|
207
262
|
"is set to `True`.",
|
|
208
263
|
fg=typer.colors.RED,
|
|
209
264
|
bold=True,
|
|
265
|
+
err=True,
|
|
210
266
|
)
|
|
211
267
|
raise typer.Exit(code=1)
|
|
212
268
|
|
|
@@ -218,6 +274,7 @@ def validate_certificate_in_federation_config(
|
|
|
218
274
|
f"❌ Failed to read certificate file `{root_certificates}`: {e}",
|
|
219
275
|
fg=typer.colors.RED,
|
|
220
276
|
bold=True,
|
|
277
|
+
err=True,
|
|
221
278
|
)
|
|
222
279
|
raise typer.Exit(code=1) from e
|
|
223
280
|
else:
|
|
@@ -227,19 +284,49 @@ def validate_certificate_in_federation_config(
|
|
|
227
284
|
|
|
228
285
|
|
|
229
286
|
def exit_if_no_address(federation_config: dict[str, Any], cmd: str) -> None:
|
|
230
|
-
"""Exit if the provided federation_config has no "address" key.
|
|
287
|
+
"""Exit if the provided federation_config has no "address" key.
|
|
288
|
+
|
|
289
|
+
Parameters
|
|
290
|
+
----------
|
|
291
|
+
federation_config : dict[str, Any]
|
|
292
|
+
The federation configuration dictionary to check.
|
|
293
|
+
cmd : str
|
|
294
|
+
The command name to display in the error message.
|
|
295
|
+
|
|
296
|
+
Raises
|
|
297
|
+
------
|
|
298
|
+
typer.Exit
|
|
299
|
+
If 'address' key is not present in federation_config.
|
|
300
|
+
"""
|
|
231
301
|
if "address" not in federation_config:
|
|
232
302
|
typer.secho(
|
|
233
303
|
f"❌ `flwr {cmd}` currently works with a SuperLink. Ensure that the "
|
|
234
304
|
"correct SuperLink (Control API) address is provided in `pyproject.toml`.",
|
|
235
305
|
fg=typer.colors.RED,
|
|
236
306
|
bold=True,
|
|
307
|
+
err=True,
|
|
237
308
|
)
|
|
238
309
|
raise typer.Exit(code=1)
|
|
239
310
|
|
|
240
311
|
|
|
241
312
|
def get_insecure_flag(federation_config: dict[str, Any]) -> bool:
|
|
242
|
-
"""Extract and validate the `insecure` flag from the federation configuration.
|
|
313
|
+
"""Extract and validate the `insecure` flag from the federation configuration.
|
|
314
|
+
|
|
315
|
+
Parameters
|
|
316
|
+
----------
|
|
317
|
+
federation_config : dict[str, Any]
|
|
318
|
+
The federation configuration dictionary.
|
|
319
|
+
|
|
320
|
+
Returns
|
|
321
|
+
-------
|
|
322
|
+
bool
|
|
323
|
+
The insecure flag value. Returns False if not specified.
|
|
324
|
+
|
|
325
|
+
Raises
|
|
326
|
+
------
|
|
327
|
+
typer.Exit
|
|
328
|
+
If insecure value is not a boolean type.
|
|
329
|
+
"""
|
|
243
330
|
insecure_value = federation_config.get("insecure")
|
|
244
331
|
|
|
245
332
|
if insecure_value is None:
|
|
@@ -252,5 +339,6 @@ def get_insecure_flag(federation_config: dict[str, Any]) -> bool:
|
|
|
252
339
|
"(`insecure = true` or `insecure = false`)",
|
|
253
340
|
fg=typer.colors.RED,
|
|
254
341
|
bold=True,
|
|
342
|
+
err=True,
|
|
255
343
|
)
|
|
256
344
|
raise typer.Exit(code=1)
|
|
@@ -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
|
+
"""Flower command line interface `federation` command."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from .ls import ls as ls
|
|
19
|
+
from .show import show as show
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"ls",
|
|
23
|
+
"show",
|
|
24
|
+
]
|