flwr-nightly 1.8.0.dev20240314__py3-none-any.whl → 1.11.0.dev20240813__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.
Potentially problematic release.
This version of flwr-nightly might be problematic. Click here for more details.
- flwr/cli/app.py +7 -0
- flwr/cli/build.py +150 -0
- flwr/cli/config_utils.py +219 -0
- flwr/cli/example.py +3 -1
- flwr/cli/install.py +227 -0
- flwr/cli/new/new.py +179 -48
- flwr/cli/new/templates/app/.gitignore.tpl +160 -0
- flwr/cli/new/templates/app/README.flowertune.md.tpl +56 -0
- flwr/cli/new/templates/app/README.md.tpl +1 -5
- flwr/cli/new/templates/app/code/__init__.py.tpl +1 -1
- flwr/cli/new/templates/app/code/client.huggingface.py.tpl +65 -0
- flwr/cli/new/templates/app/code/client.jax.py.tpl +56 -0
- flwr/cli/new/templates/app/code/client.mlx.py.tpl +93 -0
- flwr/cli/new/templates/app/code/client.numpy.py.tpl +3 -2
- flwr/cli/new/templates/app/code/client.pytorch.py.tpl +23 -11
- flwr/cli/new/templates/app/code/client.sklearn.py.tpl +97 -0
- flwr/cli/new/templates/app/code/client.tensorflow.py.tpl +60 -1
- flwr/cli/new/templates/app/code/flwr_tune/__init__.py +15 -0
- flwr/cli/new/templates/app/code/flwr_tune/app.py.tpl +89 -0
- flwr/cli/new/templates/app/code/flwr_tune/client.py.tpl +126 -0
- flwr/cli/new/templates/app/code/flwr_tune/config.yaml.tpl +34 -0
- flwr/cli/new/templates/app/code/flwr_tune/dataset.py.tpl +57 -0
- flwr/cli/new/templates/app/code/flwr_tune/models.py.tpl +59 -0
- flwr/cli/new/templates/app/code/flwr_tune/server.py.tpl +48 -0
- flwr/cli/new/templates/app/code/flwr_tune/static_config.yaml.tpl +11 -0
- flwr/cli/new/templates/app/code/server.huggingface.py.tpl +23 -0
- flwr/cli/new/templates/app/code/server.jax.py.tpl +20 -0
- flwr/cli/new/templates/app/code/server.mlx.py.tpl +20 -0
- flwr/cli/new/templates/app/code/server.numpy.py.tpl +17 -9
- flwr/cli/new/templates/app/code/server.pytorch.py.tpl +21 -18
- flwr/cli/new/templates/app/code/server.sklearn.py.tpl +24 -0
- flwr/cli/new/templates/app/code/server.tensorflow.py.tpl +29 -1
- flwr/cli/new/templates/app/code/task.huggingface.py.tpl +99 -0
- flwr/cli/new/templates/app/code/task.jax.py.tpl +57 -0
- flwr/cli/new/templates/app/code/task.mlx.py.tpl +102 -0
- flwr/cli/new/templates/app/code/task.pytorch.py.tpl +28 -23
- flwr/cli/new/templates/app/code/task.tensorflow.py.tpl +53 -0
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +39 -0
- flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +38 -0
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +34 -0
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +39 -0
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +25 -12
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +29 -14
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +33 -0
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +29 -14
- flwr/cli/run/run.py +168 -17
- flwr/cli/utils.py +75 -4
- flwr/client/__init__.py +6 -1
- flwr/client/app.py +239 -248
- flwr/client/client_app.py +70 -9
- flwr/client/dpfedavg_numpy_client.py +1 -1
- flwr/client/grpc_adapter_client/__init__.py +15 -0
- flwr/client/grpc_adapter_client/connection.py +97 -0
- flwr/client/grpc_client/connection.py +18 -5
- flwr/client/grpc_rere_client/__init__.py +1 -1
- flwr/client/grpc_rere_client/client_interceptor.py +158 -0
- flwr/client/grpc_rere_client/connection.py +127 -33
- flwr/client/grpc_rere_client/grpc_adapter.py +140 -0
- flwr/client/heartbeat.py +74 -0
- flwr/client/message_handler/__init__.py +1 -1
- flwr/client/message_handler/message_handler.py +7 -7
- flwr/client/mod/__init__.py +5 -5
- flwr/client/mod/centraldp_mods.py +4 -2
- flwr/client/mod/comms_mods.py +4 -4
- flwr/client/mod/localdp_mod.py +9 -4
- flwr/client/mod/secure_aggregation/__init__.py +1 -1
- flwr/client/mod/secure_aggregation/secaggplus_mod.py +1 -1
- flwr/client/mod/utils.py +1 -1
- flwr/client/node_state.py +60 -10
- flwr/client/node_state_tests.py +4 -3
- flwr/client/rest_client/__init__.py +1 -1
- flwr/client/rest_client/connection.py +177 -157
- flwr/client/supernode/__init__.py +26 -0
- flwr/client/supernode/app.py +464 -0
- flwr/client/typing.py +1 -0
- flwr/common/__init__.py +13 -11
- flwr/common/address.py +1 -1
- flwr/common/config.py +193 -0
- flwr/common/constant.py +42 -1
- flwr/common/context.py +26 -1
- flwr/common/date.py +1 -1
- flwr/common/dp.py +1 -1
- flwr/common/grpc.py +6 -2
- flwr/common/logger.py +79 -8
- flwr/common/message.py +167 -105
- flwr/common/object_ref.py +126 -25
- flwr/common/record/__init__.py +1 -1
- flwr/common/record/parametersrecord.py +0 -1
- flwr/common/record/recordset.py +78 -27
- flwr/common/recordset_compat.py +8 -1
- flwr/common/retry_invoker.py +25 -13
- flwr/common/secure_aggregation/__init__.py +1 -1
- flwr/common/secure_aggregation/crypto/__init__.py +1 -1
- flwr/common/secure_aggregation/crypto/shamir.py +1 -1
- flwr/common/secure_aggregation/crypto/symmetric_encryption.py +21 -2
- 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 +209 -3
- flwr/common/telemetry.py +25 -0
- flwr/common/typing.py +38 -0
- flwr/common/version.py +14 -0
- flwr/proto/clientappio_pb2.py +41 -0
- flwr/proto/clientappio_pb2.pyi +110 -0
- flwr/proto/clientappio_pb2_grpc.py +101 -0
- flwr/proto/clientappio_pb2_grpc.pyi +40 -0
- flwr/proto/common_pb2.py +36 -0
- flwr/proto/common_pb2.pyi +121 -0
- flwr/proto/common_pb2_grpc.py +4 -0
- flwr/proto/common_pb2_grpc.pyi +4 -0
- flwr/proto/driver_pb2.py +26 -19
- flwr/proto/driver_pb2.pyi +34 -0
- flwr/proto/driver_pb2_grpc.py +70 -0
- flwr/proto/driver_pb2_grpc.pyi +28 -0
- flwr/proto/exec_pb2.py +43 -0
- flwr/proto/exec_pb2.pyi +95 -0
- flwr/proto/exec_pb2_grpc.py +101 -0
- flwr/proto/exec_pb2_grpc.pyi +41 -0
- flwr/proto/fab_pb2.py +30 -0
- flwr/proto/fab_pb2.pyi +56 -0
- flwr/proto/fab_pb2_grpc.py +4 -0
- flwr/proto/fab_pb2_grpc.pyi +4 -0
- flwr/proto/fleet_pb2.py +29 -23
- flwr/proto/fleet_pb2.pyi +33 -0
- flwr/proto/fleet_pb2_grpc.py +102 -0
- flwr/proto/fleet_pb2_grpc.pyi +35 -0
- flwr/proto/grpcadapter_pb2.py +32 -0
- flwr/proto/grpcadapter_pb2.pyi +43 -0
- flwr/proto/grpcadapter_pb2_grpc.py +66 -0
- flwr/proto/grpcadapter_pb2_grpc.pyi +24 -0
- flwr/proto/message_pb2.py +41 -0
- flwr/proto/message_pb2.pyi +122 -0
- flwr/proto/message_pb2_grpc.py +4 -0
- flwr/proto/message_pb2_grpc.pyi +4 -0
- flwr/proto/run_pb2.py +35 -0
- flwr/proto/run_pb2.pyi +76 -0
- flwr/proto/run_pb2_grpc.py +4 -0
- flwr/proto/run_pb2_grpc.pyi +4 -0
- flwr/proto/task_pb2.py +7 -8
- flwr/proto/task_pb2.pyi +8 -5
- flwr/server/__init__.py +4 -8
- flwr/server/app.py +298 -350
- flwr/server/compat/app.py +6 -57
- flwr/server/compat/app_utils.py +5 -4
- flwr/server/compat/driver_client_proxy.py +29 -48
- flwr/server/compat/legacy_context.py +5 -4
- flwr/server/driver/__init__.py +2 -0
- flwr/server/driver/driver.py +22 -132
- flwr/server/driver/grpc_driver.py +224 -74
- flwr/server/driver/inmemory_driver.py +183 -0
- flwr/server/history.py +20 -20
- flwr/server/run_serverapp.py +121 -34
- flwr/server/server.py +11 -7
- flwr/server/server_app.py +59 -10
- flwr/server/serverapp_components.py +52 -0
- flwr/server/strategy/__init__.py +2 -2
- flwr/server/strategy/bulyan.py +1 -1
- flwr/server/strategy/dp_adaptive_clipping.py +3 -3
- flwr/server/strategy/dp_fixed_clipping.py +4 -3
- flwr/server/strategy/dpfedavg_adaptive.py +1 -1
- flwr/server/strategy/dpfedavg_fixed.py +1 -1
- flwr/server/strategy/fedadagrad.py +1 -1
- flwr/server/strategy/fedadam.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/fedxgb_bagging.py +1 -1
- flwr/server/strategy/fedxgb_cyclic.py +1 -1
- flwr/server/strategy/fedxgb_nn_avg.py +1 -1
- flwr/server/strategy/fedyogi.py +1 -1
- flwr/server/strategy/krum.py +1 -1
- flwr/server/strategy/qfedavg.py +1 -1
- flwr/server/superlink/driver/__init__.py +1 -1
- flwr/server/superlink/driver/driver_grpc.py +1 -1
- flwr/server/superlink/driver/driver_servicer.py +51 -4
- flwr/server/superlink/ffs/__init__.py +24 -0
- flwr/server/superlink/ffs/disk_ffs.py +104 -0
- flwr/server/superlink/ffs/ffs.py +79 -0
- flwr/server/superlink/fleet/__init__.py +1 -1
- flwr/server/superlink/fleet/grpc_adapter/__init__.py +15 -0
- flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +131 -0
- 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 +8 -2
- flwr/server/superlink/fleet/grpc_rere/__init__.py +1 -1
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +30 -2
- flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +214 -0
- flwr/server/superlink/fleet/message_handler/__init__.py +1 -1
- flwr/server/superlink/fleet/message_handler/message_handler.py +42 -2
- flwr/server/superlink/fleet/rest_rere/__init__.py +1 -1
- flwr/server/superlink/fleet/rest_rere/rest_api.py +59 -1
- flwr/server/superlink/fleet/vce/backend/__init__.py +1 -1
- flwr/server/superlink/fleet/vce/backend/backend.py +5 -5
- flwr/server/superlink/fleet/vce/backend/raybackend.py +53 -56
- flwr/server/superlink/fleet/vce/vce_api.py +190 -127
- flwr/server/superlink/state/__init__.py +1 -1
- flwr/server/superlink/state/in_memory_state.py +159 -42
- flwr/server/superlink/state/sqlite_state.py +243 -39
- flwr/server/superlink/state/state.py +81 -6
- flwr/server/superlink/state/state_factory.py +11 -2
- flwr/server/superlink/state/utils.py +62 -0
- flwr/server/typing.py +2 -0
- flwr/server/utils/__init__.py +1 -1
- flwr/server/utils/tensorboard.py +1 -1
- flwr/server/utils/validator.py +23 -9
- flwr/server/workflow/default_workflows.py +67 -25
- flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +18 -6
- flwr/simulation/__init__.py +7 -4
- flwr/simulation/app.py +67 -36
- flwr/simulation/ray_transport/__init__.py +1 -1
- flwr/simulation/ray_transport/ray_actor.py +20 -46
- flwr/simulation/ray_transport/ray_client_proxy.py +36 -16
- flwr/simulation/run_simulation.py +308 -92
- flwr/superexec/__init__.py +21 -0
- flwr/superexec/app.py +184 -0
- flwr/superexec/deployment.py +185 -0
- flwr/superexec/exec_grpc.py +55 -0
- flwr/superexec/exec_servicer.py +70 -0
- flwr/superexec/executor.py +75 -0
- flwr/superexec/simulation.py +193 -0
- {flwr_nightly-1.8.0.dev20240314.dist-info → flwr_nightly-1.11.0.dev20240813.dist-info}/METADATA +10 -6
- flwr_nightly-1.11.0.dev20240813.dist-info/RECORD +288 -0
- flwr_nightly-1.11.0.dev20240813.dist-info/entry_points.txt +10 -0
- flwr/cli/flower_toml.py +0 -140
- flwr/cli/new/templates/app/flower.toml.tpl +0 -13
- flwr/cli/new/templates/app/requirements.numpy.txt.tpl +0 -2
- flwr/cli/new/templates/app/requirements.pytorch.txt.tpl +0 -4
- flwr/cli/new/templates/app/requirements.tensorflow.txt.tpl +0 -4
- flwr_nightly-1.8.0.dev20240314.dist-info/RECORD +0 -211
- flwr_nightly-1.8.0.dev20240314.dist-info/entry_points.txt +0 -9
- {flwr_nightly-1.8.0.dev20240314.dist-info → flwr_nightly-1.11.0.dev20240813.dist-info}/LICENSE +0 -0
- {flwr_nightly-1.8.0.dev20240314.dist-info → flwr_nightly-1.11.0.dev20240813.dist-info}/WHEEL +0 -0
flwr/common/config.py
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Copyright 2024 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
|
+
"""Provide functions for managing global Flower config."""
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any, Dict, List, Optional, Tuple, Union, cast, get_args
|
|
20
|
+
|
|
21
|
+
import tomli
|
|
22
|
+
|
|
23
|
+
from flwr.cli.config_utils import validate_fields
|
|
24
|
+
from flwr.common.constant import APP_DIR, FAB_CONFIG_FILE, FLWR_HOME
|
|
25
|
+
from flwr.common.typing import Run, UserConfig, UserConfigValue
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_flwr_dir(provided_path: Optional[str] = None) -> Path:
|
|
29
|
+
"""Return the Flower home directory based on env variables."""
|
|
30
|
+
if provided_path is None or not Path(provided_path).is_dir():
|
|
31
|
+
return Path(
|
|
32
|
+
os.getenv(
|
|
33
|
+
FLWR_HOME,
|
|
34
|
+
Path(f"{os.getenv('XDG_DATA_HOME', os.getenv('HOME'))}") / ".flwr",
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
return Path(provided_path).absolute()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_project_dir(
|
|
41
|
+
fab_id: str, fab_version: str, flwr_dir: Optional[Union[str, Path]] = None
|
|
42
|
+
) -> Path:
|
|
43
|
+
"""Return the project directory based on the given fab_id and fab_version."""
|
|
44
|
+
# Check the fab_id
|
|
45
|
+
if fab_id.count("/") != 1:
|
|
46
|
+
raise ValueError(
|
|
47
|
+
f"Invalid FAB ID: {fab_id}",
|
|
48
|
+
)
|
|
49
|
+
publisher, project_name = fab_id.split("/")
|
|
50
|
+
if flwr_dir is None:
|
|
51
|
+
flwr_dir = get_flwr_dir()
|
|
52
|
+
return Path(flwr_dir) / APP_DIR / publisher / project_name / fab_version
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_project_config(project_dir: Union[str, Path]) -> Dict[str, Any]:
|
|
56
|
+
"""Return pyproject.toml in the given project directory."""
|
|
57
|
+
# Load pyproject.toml file
|
|
58
|
+
toml_path = Path(project_dir) / FAB_CONFIG_FILE
|
|
59
|
+
if not toml_path.is_file():
|
|
60
|
+
raise FileNotFoundError(
|
|
61
|
+
f"Cannot find {FAB_CONFIG_FILE} in {project_dir}",
|
|
62
|
+
)
|
|
63
|
+
with toml_path.open(encoding="utf-8") as toml_file:
|
|
64
|
+
config = tomli.loads(toml_file.read())
|
|
65
|
+
|
|
66
|
+
# Validate pyproject.toml fields
|
|
67
|
+
is_valid, errors, _ = validate_fields(config)
|
|
68
|
+
if not is_valid:
|
|
69
|
+
error_msg = "\n".join([f" - {error}" for error in errors])
|
|
70
|
+
raise ValueError(
|
|
71
|
+
f"Invalid {FAB_CONFIG_FILE}:\n{error_msg}",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
return config
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _fuse_dicts(
|
|
78
|
+
main_dict: UserConfig,
|
|
79
|
+
override_dict: UserConfig,
|
|
80
|
+
) -> UserConfig:
|
|
81
|
+
fused_dict = main_dict.copy()
|
|
82
|
+
|
|
83
|
+
for key, value in override_dict.items():
|
|
84
|
+
if key in main_dict:
|
|
85
|
+
fused_dict[key] = value
|
|
86
|
+
|
|
87
|
+
return fused_dict
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def get_fused_config_from_dir(
|
|
91
|
+
project_dir: Path, override_config: UserConfig
|
|
92
|
+
) -> UserConfig:
|
|
93
|
+
"""Merge the overrides from a given dict with the config from a Flower App."""
|
|
94
|
+
default_config = get_project_config(project_dir)["tool"]["flwr"]["app"].get(
|
|
95
|
+
"config", {}
|
|
96
|
+
)
|
|
97
|
+
flat_default_config = flatten_dict(default_config)
|
|
98
|
+
|
|
99
|
+
return _fuse_dicts(flat_default_config, override_config)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def get_fused_config(run: Run, flwr_dir: Optional[Path]) -> UserConfig:
|
|
103
|
+
"""Merge the overrides from a `Run` with the config from a FAB.
|
|
104
|
+
|
|
105
|
+
Get the config using the fab_id and the fab_version, remove the nesting by adding
|
|
106
|
+
the nested keys as prefixes separated by dots, and fuse it with the override dict.
|
|
107
|
+
"""
|
|
108
|
+
# Return empty dict if fab_id or fab_version is empty
|
|
109
|
+
if not run.fab_id or not run.fab_version:
|
|
110
|
+
return {}
|
|
111
|
+
|
|
112
|
+
project_dir = get_project_dir(run.fab_id, run.fab_version, flwr_dir)
|
|
113
|
+
|
|
114
|
+
# Return empty dict if project directory does not exist
|
|
115
|
+
if not project_dir.is_dir():
|
|
116
|
+
return {}
|
|
117
|
+
|
|
118
|
+
return get_fused_config_from_dir(project_dir, run.override_config)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def flatten_dict(
|
|
122
|
+
raw_dict: Optional[Dict[str, Any]], parent_key: str = ""
|
|
123
|
+
) -> UserConfig:
|
|
124
|
+
"""Flatten dict by joining nested keys with a given separator."""
|
|
125
|
+
if raw_dict is None:
|
|
126
|
+
return {}
|
|
127
|
+
|
|
128
|
+
items: List[Tuple[str, UserConfigValue]] = []
|
|
129
|
+
separator: str = "."
|
|
130
|
+
for k, v in raw_dict.items():
|
|
131
|
+
new_key = f"{parent_key}{separator}{k}" if parent_key else k
|
|
132
|
+
if isinstance(v, dict):
|
|
133
|
+
items.extend(flatten_dict(v, parent_key=new_key).items())
|
|
134
|
+
elif isinstance(v, get_args(UserConfigValue)):
|
|
135
|
+
items.append((new_key, cast(UserConfigValue, v)))
|
|
136
|
+
else:
|
|
137
|
+
raise ValueError(
|
|
138
|
+
f"The value for key {k} needs to be of type `int`, `float`, "
|
|
139
|
+
"`bool, `str`, or a `dict` of those.",
|
|
140
|
+
)
|
|
141
|
+
return dict(items)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def unflatten_dict(flat_dict: Dict[str, Any]) -> Dict[str, Any]:
|
|
145
|
+
"""Unflatten a dict with keys containing separators into a nested dict."""
|
|
146
|
+
unflattened_dict: Dict[str, Any] = {}
|
|
147
|
+
separator: str = "."
|
|
148
|
+
|
|
149
|
+
for key, value in flat_dict.items():
|
|
150
|
+
parts = key.split(separator)
|
|
151
|
+
d = unflattened_dict
|
|
152
|
+
for part in parts[:-1]:
|
|
153
|
+
if part not in d:
|
|
154
|
+
d[part] = {}
|
|
155
|
+
d = d[part]
|
|
156
|
+
d[parts[-1]] = value
|
|
157
|
+
|
|
158
|
+
return unflattened_dict
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def parse_config_args(
|
|
162
|
+
config: Optional[List[str]],
|
|
163
|
+
separator: str = ",",
|
|
164
|
+
) -> UserConfig:
|
|
165
|
+
"""Parse separator separated list of key-value pairs separated by '='."""
|
|
166
|
+
overrides: UserConfig = {}
|
|
167
|
+
|
|
168
|
+
if config is None:
|
|
169
|
+
return overrides
|
|
170
|
+
|
|
171
|
+
for config_line in config:
|
|
172
|
+
if config_line:
|
|
173
|
+
overrides_list = config_line.split(separator)
|
|
174
|
+
if (
|
|
175
|
+
len(overrides_list) == 1
|
|
176
|
+
and "=" not in overrides_list
|
|
177
|
+
and overrides_list[0].endswith(".toml")
|
|
178
|
+
):
|
|
179
|
+
with Path(overrides_list[0]).open("rb") as config_file:
|
|
180
|
+
overrides = flatten_dict(tomli.load(config_file))
|
|
181
|
+
else:
|
|
182
|
+
toml_str = "\n".join(overrides_list)
|
|
183
|
+
overrides.update(tomli.loads(toml_str))
|
|
184
|
+
|
|
185
|
+
return overrides
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def get_metadata_from_config(config: Dict[str, Any]) -> Tuple[str, str]:
|
|
189
|
+
"""Extract `fab_version` and `fab_id` from a project config."""
|
|
190
|
+
return (
|
|
191
|
+
config["project"]["version"],
|
|
192
|
+
f"{config['tool']['flwr']['app']['publisher']}/{config['project']['name']}",
|
|
193
|
+
)
|
flwr/common/constant.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright
|
|
1
|
+
# Copyright 2023 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,6 +27,7 @@ To use the REST API, install `flwr` with the `rest` extra:
|
|
|
27
27
|
|
|
28
28
|
TRANSPORT_TYPE_GRPC_BIDI = "grpc-bidi"
|
|
29
29
|
TRANSPORT_TYPE_GRPC_RERE = "grpc-rere"
|
|
30
|
+
TRANSPORT_TYPE_GRPC_ADAPTER = "grpc-adapter"
|
|
30
31
|
TRANSPORT_TYPE_REST = "rest"
|
|
31
32
|
TRANSPORT_TYPE_VCE = "vce"
|
|
32
33
|
TRANSPORT_TYPES = [
|
|
@@ -36,6 +37,33 @@ TRANSPORT_TYPES = [
|
|
|
36
37
|
TRANSPORT_TYPE_VCE,
|
|
37
38
|
]
|
|
38
39
|
|
|
40
|
+
SUPEREXEC_DEFAULT_ADDRESS = "0.0.0.0:9093"
|
|
41
|
+
|
|
42
|
+
# Constants for ping
|
|
43
|
+
PING_DEFAULT_INTERVAL = 30
|
|
44
|
+
PING_CALL_TIMEOUT = 5
|
|
45
|
+
PING_BASE_MULTIPLIER = 0.8
|
|
46
|
+
PING_RANDOM_RANGE = (-0.1, 0.1)
|
|
47
|
+
PING_MAX_INTERVAL = 1e300
|
|
48
|
+
|
|
49
|
+
# IDs
|
|
50
|
+
RUN_ID_NUM_BYTES = 8
|
|
51
|
+
NODE_ID_NUM_BYTES = 8
|
|
52
|
+
GRPC_ADAPTER_METADATA_FLOWER_VERSION_KEY = "flower-version"
|
|
53
|
+
GRPC_ADAPTER_METADATA_SHOULD_EXIT_KEY = "should-exit"
|
|
54
|
+
|
|
55
|
+
# Constants for FAB
|
|
56
|
+
APP_DIR = "apps"
|
|
57
|
+
FAB_CONFIG_FILE = "pyproject.toml"
|
|
58
|
+
FLWR_HOME = "FLWR_HOME"
|
|
59
|
+
|
|
60
|
+
# Constants entries in Node config for Simulation
|
|
61
|
+
PARTITION_ID_KEY = "partition-id"
|
|
62
|
+
NUM_PARTITIONS_KEY = "num-partitions"
|
|
63
|
+
|
|
64
|
+
GRPC_ADAPTER_METADATA_FLOWER_VERSION_KEY = "flower-version"
|
|
65
|
+
GRPC_ADAPTER_METADATA_SHOULD_EXIT_KEY = "should-exit"
|
|
66
|
+
|
|
39
67
|
|
|
40
68
|
class MessageType:
|
|
41
69
|
"""Message type."""
|
|
@@ -68,3 +96,16 @@ class SType:
|
|
|
68
96
|
def __new__(cls) -> SType:
|
|
69
97
|
"""Prevent instantiation."""
|
|
70
98
|
raise TypeError(f"{cls.__name__} cannot be instantiated.")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class ErrorCode:
|
|
102
|
+
"""Error codes for Message's Error."""
|
|
103
|
+
|
|
104
|
+
UNKNOWN = 0
|
|
105
|
+
LOAD_CLIENT_APP_EXCEPTION = 1
|
|
106
|
+
CLIENT_APP_RAISED_EXCEPTION = 2
|
|
107
|
+
NODE_UNAVAILABLE = 3
|
|
108
|
+
|
|
109
|
+
def __new__(cls) -> ErrorCode:
|
|
110
|
+
"""Prevent instantiation."""
|
|
111
|
+
raise TypeError(f"{cls.__name__} cannot be instantiated.")
|
flwr/common/context.py
CHANGED
|
@@ -18,14 +18,20 @@
|
|
|
18
18
|
from dataclasses import dataclass
|
|
19
19
|
|
|
20
20
|
from .record import RecordSet
|
|
21
|
+
from .typing import UserConfig
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
@dataclass
|
|
24
25
|
class Context:
|
|
25
|
-
"""
|
|
26
|
+
"""Context of your run.
|
|
26
27
|
|
|
27
28
|
Parameters
|
|
28
29
|
----------
|
|
30
|
+
node_id : int
|
|
31
|
+
The ID that identifies the node.
|
|
32
|
+
node_config : UserConfig
|
|
33
|
+
A config (key/value mapping) unique to the node and independent of the
|
|
34
|
+
`run_config`. This config persists across all runs this node participates in.
|
|
29
35
|
state : RecordSet
|
|
30
36
|
Holds records added by the entity in a given run and that will stay local.
|
|
31
37
|
This means that the data it holds will never leave the system it's running from.
|
|
@@ -33,6 +39,25 @@ class Context:
|
|
|
33
39
|
executing mods. It can also be used as a memory to access
|
|
34
40
|
at different points during the lifecycle of this entity (e.g. across
|
|
35
41
|
multiple rounds)
|
|
42
|
+
run_config : UserConfig
|
|
43
|
+
A config (key/value mapping) held by the entity in a given run and that will
|
|
44
|
+
stay local. It can be used at any point during the lifecycle of this entity
|
|
45
|
+
(e.g. across multiple rounds)
|
|
36
46
|
"""
|
|
37
47
|
|
|
48
|
+
node_id: int
|
|
49
|
+
node_config: UserConfig
|
|
38
50
|
state: RecordSet
|
|
51
|
+
run_config: UserConfig
|
|
52
|
+
|
|
53
|
+
def __init__( # pylint: disable=too-many-arguments
|
|
54
|
+
self,
|
|
55
|
+
node_id: int,
|
|
56
|
+
node_config: UserConfig,
|
|
57
|
+
state: RecordSet,
|
|
58
|
+
run_config: UserConfig,
|
|
59
|
+
) -> None:
|
|
60
|
+
self.node_id = node_id
|
|
61
|
+
self.node_config = node_config
|
|
62
|
+
self.state = state
|
|
63
|
+
self.run_config = run_config
|
flwr/common/date.py
CHANGED
flwr/common/dp.py
CHANGED
flwr/common/grpc.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright
|
|
1
|
+
# Copyright 2022 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.
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
from logging import DEBUG
|
|
19
|
-
from typing import Optional
|
|
19
|
+
from typing import Optional, Sequence
|
|
20
20
|
|
|
21
21
|
import grpc
|
|
22
22
|
|
|
@@ -30,6 +30,7 @@ def create_channel(
|
|
|
30
30
|
insecure: bool,
|
|
31
31
|
root_certificates: Optional[bytes] = None,
|
|
32
32
|
max_message_length: int = GRPC_MAX_MESSAGE_LENGTH,
|
|
33
|
+
interceptors: Optional[Sequence[grpc.UnaryUnaryClientInterceptor]] = None,
|
|
33
34
|
) -> grpc.Channel:
|
|
34
35
|
"""Create a gRPC channel, either secure or insecure."""
|
|
35
36
|
# Check for conflicting parameters
|
|
@@ -57,4 +58,7 @@ def create_channel(
|
|
|
57
58
|
)
|
|
58
59
|
log(DEBUG, "Opened secure gRPC connection using certificates")
|
|
59
60
|
|
|
61
|
+
if interceptors is not None:
|
|
62
|
+
channel = grpc.intercept_channel(channel, interceptors)
|
|
63
|
+
|
|
60
64
|
return channel
|
flwr/common/logger.py
CHANGED
|
@@ -82,13 +82,20 @@ class ConsoleHandler(StreamHandler):
|
|
|
82
82
|
return formatter.format(record)
|
|
83
83
|
|
|
84
84
|
|
|
85
|
-
def update_console_handler(
|
|
85
|
+
def update_console_handler(
|
|
86
|
+
level: Optional[int] = None,
|
|
87
|
+
timestamps: Optional[bool] = None,
|
|
88
|
+
colored: Optional[bool] = None,
|
|
89
|
+
) -> None:
|
|
86
90
|
"""Update the logging handler."""
|
|
87
91
|
for handler in logging.getLogger(LOGGER_NAME).handlers:
|
|
88
92
|
if isinstance(handler, ConsoleHandler):
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
93
|
+
if level is not None:
|
|
94
|
+
handler.setLevel(level)
|
|
95
|
+
if timestamps is not None:
|
|
96
|
+
handler.timestamps = timestamps
|
|
97
|
+
if colored is not None:
|
|
98
|
+
handler.colored = colored
|
|
92
99
|
|
|
93
100
|
|
|
94
101
|
# Configure console logger
|
|
@@ -164,13 +171,13 @@ logger = logging.getLogger(LOGGER_NAME) # pylint: disable=invalid-name
|
|
|
164
171
|
log = logger.log # pylint: disable=invalid-name
|
|
165
172
|
|
|
166
173
|
|
|
167
|
-
def
|
|
168
|
-
"""Warn the user when they use
|
|
174
|
+
def warn_preview_feature(name: str) -> None:
|
|
175
|
+
"""Warn the user when they use a preview feature."""
|
|
169
176
|
log(
|
|
170
177
|
WARN,
|
|
171
|
-
"""
|
|
178
|
+
"""PREVIEW FEATURE: %s
|
|
172
179
|
|
|
173
|
-
This is
|
|
180
|
+
This is a preview feature. It could change significantly or be removed
|
|
174
181
|
entirely in future versions of Flower.
|
|
175
182
|
""",
|
|
176
183
|
name,
|
|
@@ -188,3 +195,67 @@ def warn_deprecated_feature(name: str) -> None:
|
|
|
188
195
|
""",
|
|
189
196
|
name,
|
|
190
197
|
)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def warn_deprecated_feature_with_example(
|
|
201
|
+
deprecation_message: str, example_message: str, code_example: str
|
|
202
|
+
) -> None:
|
|
203
|
+
"""Warn if a feature is deprecated and show code example."""
|
|
204
|
+
log(
|
|
205
|
+
WARN,
|
|
206
|
+
"""DEPRECATED FEATURE: %s
|
|
207
|
+
|
|
208
|
+
Check the following `FEATURE UPDATE` warning message for the preferred
|
|
209
|
+
new mechanism to use this feature in Flower.
|
|
210
|
+
""",
|
|
211
|
+
deprecation_message,
|
|
212
|
+
)
|
|
213
|
+
log(
|
|
214
|
+
WARN,
|
|
215
|
+
"""FEATURE UPDATE: %s
|
|
216
|
+
------------------------------------------------------------
|
|
217
|
+
%s
|
|
218
|
+
------------------------------------------------------------
|
|
219
|
+
""",
|
|
220
|
+
example_message,
|
|
221
|
+
code_example,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def warn_unsupported_feature(name: str) -> None:
|
|
226
|
+
"""Warn the user when they use an unsupported feature."""
|
|
227
|
+
log(
|
|
228
|
+
WARN,
|
|
229
|
+
"""UNSUPPORTED FEATURE: %s
|
|
230
|
+
|
|
231
|
+
This is an unsupported feature. It will be removed
|
|
232
|
+
entirely in future versions of Flower.
|
|
233
|
+
""",
|
|
234
|
+
name,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def set_logger_propagation(
|
|
239
|
+
child_logger: logging.Logger, value: bool = True
|
|
240
|
+
) -> logging.Logger:
|
|
241
|
+
"""Set the logger propagation attribute.
|
|
242
|
+
|
|
243
|
+
Parameters
|
|
244
|
+
----------
|
|
245
|
+
child_logger : logging.Logger
|
|
246
|
+
Child logger object
|
|
247
|
+
value : bool
|
|
248
|
+
Boolean setting for propagation. If True, both parent and child logger
|
|
249
|
+
display messages. Otherwise, only the child logger displays a message.
|
|
250
|
+
This False setting prevents duplicate logs in Colab notebooks.
|
|
251
|
+
Reference: https://stackoverflow.com/a/19561320
|
|
252
|
+
|
|
253
|
+
Returns
|
|
254
|
+
-------
|
|
255
|
+
logging.Logger
|
|
256
|
+
Child logger object with updated propagation setting
|
|
257
|
+
"""
|
|
258
|
+
child_logger.propagate = value
|
|
259
|
+
if not child_logger.propagate:
|
|
260
|
+
child_logger.log(logging.DEBUG, "Logger propagate set to False")
|
|
261
|
+
return child_logger
|