flwr-nightly 1.10.0.dev20240721__py3-none-any.whl → 1.10.0.dev20240723__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/config_utils.py +20 -18
- flwr/cli/new/new.py +1 -1
- flwr/cli/new/templates/app/code/{client.hf.py.tpl → client.huggingface.py.tpl} +7 -5
- flwr/cli/new/templates/app/code/client.mlx.py.tpl +28 -10
- flwr/cli/new/templates/app/code/client.pytorch.py.tpl +7 -5
- flwr/cli/new/templates/app/code/client.sklearn.py.tpl +2 -2
- flwr/cli/new/templates/app/code/client.tensorflow.py.tpl +17 -7
- flwr/cli/new/templates/app/code/flwr_tune/app.py.tpl +20 -17
- flwr/cli/new/templates/app/code/flwr_tune/client.py.tpl +5 -3
- flwr/cli/new/templates/app/code/{server.hf.py.tpl → server.huggingface.py.tpl} +2 -1
- flwr/cli/new/templates/app/code/server.jax.py.tpl +2 -1
- flwr/cli/new/templates/app/code/server.mlx.py.tpl +2 -1
- flwr/cli/new/templates/app/code/server.numpy.py.tpl +2 -1
- flwr/cli/new/templates/app/code/server.pytorch.py.tpl +1 -1
- flwr/cli/new/templates/app/code/server.sklearn.py.tpl +2 -1
- flwr/cli/new/templates/app/code/server.tensorflow.py.tpl +1 -1
- flwr/cli/new/templates/app/code/{task.hf.py.tpl → task.huggingface.py.tpl} +13 -1
- flwr/cli/new/templates/app/code/task.mlx.py.tpl +14 -1
- flwr/cli/new/templates/app/code/task.pytorch.py.tpl +13 -2
- flwr/cli/new/templates/app/code/task.tensorflow.py.tpl +13 -1
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +1 -1
- flwr/cli/new/templates/app/{pyproject.hf.toml.tpl → pyproject.huggingface.toml.tpl} +2 -2
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +6 -6
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +2 -2
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +4 -4
- flwr/cli/run/run.py +35 -28
- flwr/client/app.py +3 -3
- flwr/client/grpc_rere_client/connection.py +6 -2
- flwr/client/node_state.py +3 -3
- flwr/client/rest_client/connection.py +6 -2
- flwr/client/supernode/app.py +12 -43
- flwr/common/config.py +23 -17
- flwr/common/context.py +7 -7
- flwr/common/object_ref.py +84 -21
- flwr/common/serde.py +45 -0
- flwr/common/telemetry.py +17 -0
- flwr/common/typing.py +5 -1
- flwr/proto/common_pb2.py +13 -1
- flwr/proto/common_pb2.pyi +114 -0
- flwr/proto/driver_pb2.py +22 -21
- flwr/proto/driver_pb2.pyi +7 -4
- flwr/proto/exec_pb2.py +18 -13
- flwr/proto/exec_pb2.pyi +27 -5
- flwr/proto/run_pb2.py +10 -9
- flwr/proto/run_pb2.pyi +7 -4
- flwr/proto/task_pb2.py +7 -8
- flwr/server/compat/legacy_context.py +5 -4
- flwr/server/driver/grpc_driver.py +6 -2
- flwr/server/run_serverapp.py +3 -5
- flwr/server/superlink/driver/driver_servicer.py +14 -3
- flwr/server/superlink/fleet/message_handler/message_handler.py +13 -2
- flwr/server/superlink/fleet/vce/backend/__init__.py +1 -1
- flwr/server/superlink/fleet/vce/vce_api.py +4 -4
- flwr/server/superlink/state/in_memory_state.py +2 -2
- flwr/server/superlink/state/sqlite_state.py +2 -2
- flwr/server/superlink/state/state.py +3 -3
- flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +18 -2
- flwr/simulation/__init__.py +1 -1
- flwr/simulation/ray_transport/ray_client_proxy.py +1 -1
- flwr/simulation/run_simulation.py +39 -11
- flwr/superexec/app.py +4 -5
- flwr/superexec/deployment.py +19 -8
- flwr/superexec/exec_grpc.py +3 -2
- flwr/superexec/exec_servicer.py +3 -1
- flwr/superexec/executor.py +10 -5
- flwr/superexec/simulation.py +41 -15
- {flwr_nightly-1.10.0.dev20240721.dist-info → flwr_nightly-1.10.0.dev20240723.dist-info}/METADATA +1 -1
- {flwr_nightly-1.10.0.dev20240721.dist-info → flwr_nightly-1.10.0.dev20240723.dist-info}/RECORD +74 -74
- {flwr_nightly-1.10.0.dev20240721.dist-info → flwr_nightly-1.10.0.dev20240723.dist-info}/LICENSE +0 -0
- {flwr_nightly-1.10.0.dev20240721.dist-info → flwr_nightly-1.10.0.dev20240723.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.10.0.dev20240721.dist-info → flwr_nightly-1.10.0.dev20240723.dist-info}/entry_points.txt +0 -0
|
@@ -25,12 +25,12 @@ serverapp = "$import_name.server_app:app"
|
|
|
25
25
|
clientapp = "$import_name.client_app:app"
|
|
26
26
|
|
|
27
27
|
[tool.flwr.app.config]
|
|
28
|
-
num-server-rounds =
|
|
29
|
-
local-epochs =
|
|
30
|
-
num-layers =
|
|
31
|
-
hidden-dim =
|
|
32
|
-
batch-size =
|
|
33
|
-
lr =
|
|
28
|
+
num-server-rounds = 3
|
|
29
|
+
local-epochs = 1
|
|
30
|
+
num-layers = 2
|
|
31
|
+
hidden-dim = 32
|
|
32
|
+
batch-size = 256
|
|
33
|
+
lr = 0.1
|
|
34
34
|
|
|
35
35
|
[tool.flwr.federations]
|
|
36
36
|
default = "localhost"
|
|
@@ -25,8 +25,8 @@ serverapp = "$import_name.server_app:app"
|
|
|
25
25
|
clientapp = "$import_name.client_app:app"
|
|
26
26
|
|
|
27
27
|
[tool.flwr.app.config]
|
|
28
|
-
num-server-rounds =
|
|
29
|
-
local-epochs =
|
|
28
|
+
num-server-rounds = 3
|
|
29
|
+
local-epochs = 1
|
|
30
30
|
|
|
31
31
|
[tool.flwr.federations]
|
|
32
32
|
default = "localhost"
|
|
@@ -24,10 +24,10 @@ serverapp = "$import_name.server_app:app"
|
|
|
24
24
|
clientapp = "$import_name.client_app:app"
|
|
25
25
|
|
|
26
26
|
[tool.flwr.app.config]
|
|
27
|
-
num-server-rounds =
|
|
28
|
-
local-epochs =
|
|
29
|
-
batch-size =
|
|
30
|
-
verbose =
|
|
27
|
+
num-server-rounds = 3
|
|
28
|
+
local-epochs = 1
|
|
29
|
+
batch-size = 32
|
|
30
|
+
verbose = false
|
|
31
31
|
|
|
32
32
|
[tool.flwr.federations]
|
|
33
33
|
default = "localhost"
|
flwr/cli/run/run.py
CHANGED
|
@@ -25,36 +25,40 @@ from typing_extensions import Annotated
|
|
|
25
25
|
|
|
26
26
|
from flwr.cli.build import build
|
|
27
27
|
from flwr.cli.config_utils import load_and_validate
|
|
28
|
-
from flwr.common.config import parse_config_args
|
|
28
|
+
from flwr.common.config import flatten_dict, parse_config_args
|
|
29
29
|
from flwr.common.grpc import GRPC_MAX_MESSAGE_LENGTH, create_channel
|
|
30
30
|
from flwr.common.logger import log
|
|
31
|
+
from flwr.common.serde import user_config_to_proto
|
|
31
32
|
from flwr.proto.exec_pb2 import StartRunRequest # pylint: disable=E0611
|
|
32
33
|
from flwr.proto.exec_pb2_grpc import ExecStub
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
# pylint: disable-next=too-many-locals
|
|
36
37
|
def run(
|
|
37
|
-
|
|
38
|
+
app_dir: Annotated[
|
|
38
39
|
Path,
|
|
39
|
-
typer.Argument(help="Path of the Flower project to run"),
|
|
40
|
+
typer.Argument(help="Path of the Flower project to run."),
|
|
40
41
|
] = Path("."),
|
|
41
|
-
|
|
42
|
+
federation: Annotated[
|
|
42
43
|
Optional[str],
|
|
43
|
-
typer.Argument(help="Name of the federation to run the app on"),
|
|
44
|
+
typer.Argument(help="Name of the federation to run the app on."),
|
|
44
45
|
] = None,
|
|
45
46
|
config_overrides: Annotated[
|
|
46
47
|
Optional[List[str]],
|
|
47
48
|
typer.Option(
|
|
48
49
|
"--run-config",
|
|
49
50
|
"-c",
|
|
50
|
-
help="Override configuration key-value pairs"
|
|
51
|
+
help="Override configuration key-value pairs, should be of the format:\n\n"
|
|
52
|
+
"`--run-config key1=value1,key2=value2 --run-config key3=value3`\n\n"
|
|
53
|
+
"Note that `key1`, `key2`, and `key3` in this example need to exist "
|
|
54
|
+
"inside the `pyproject.toml` in order to be properly overriden.",
|
|
51
55
|
),
|
|
52
56
|
] = None,
|
|
53
57
|
) -> None:
|
|
54
58
|
"""Run Flower project."""
|
|
55
59
|
typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
|
|
56
60
|
|
|
57
|
-
pyproject_path =
|
|
61
|
+
pyproject_path = app_dir / "pyproject.toml" if app_dir else None
|
|
58
62
|
config, errors, warnings = load_and_validate(path=pyproject_path)
|
|
59
63
|
|
|
60
64
|
if config is None:
|
|
@@ -77,11 +81,9 @@ def run(
|
|
|
77
81
|
|
|
78
82
|
typer.secho("Success", fg=typer.colors.GREEN)
|
|
79
83
|
|
|
80
|
-
|
|
81
|
-
"default"
|
|
82
|
-
)
|
|
84
|
+
federation = federation or config["tool"]["flwr"]["federations"].get("default")
|
|
83
85
|
|
|
84
|
-
if
|
|
86
|
+
if federation is None:
|
|
85
87
|
typer.secho(
|
|
86
88
|
"❌ No federation name was provided and the project's `pyproject.toml` "
|
|
87
89
|
"doesn't declare a default federation (with a SuperExec address or an "
|
|
@@ -92,13 +94,13 @@ def run(
|
|
|
92
94
|
raise typer.Exit(code=1)
|
|
93
95
|
|
|
94
96
|
# Validate the federation exists in the configuration
|
|
95
|
-
|
|
96
|
-
if
|
|
97
|
+
federation_config = config["tool"]["flwr"]["federations"].get(federation)
|
|
98
|
+
if federation_config is None:
|
|
97
99
|
available_feds = {
|
|
98
100
|
fed for fed in config["tool"]["flwr"]["federations"] if fed != "default"
|
|
99
101
|
}
|
|
100
102
|
typer.secho(
|
|
101
|
-
f"❌ There is no `{
|
|
103
|
+
f"❌ There is no `{federation}` federation declared in "
|
|
102
104
|
"`pyproject.toml`.\n The following federations were found:\n\n"
|
|
103
105
|
+ "\n".join(available_feds),
|
|
104
106
|
fg=typer.colors.RED,
|
|
@@ -106,15 +108,15 @@ def run(
|
|
|
106
108
|
)
|
|
107
109
|
raise typer.Exit(code=1)
|
|
108
110
|
|
|
109
|
-
if "address" in
|
|
110
|
-
_run_with_superexec(
|
|
111
|
+
if "address" in federation_config:
|
|
112
|
+
_run_with_superexec(federation_config, app_dir, config_overrides)
|
|
111
113
|
else:
|
|
112
|
-
_run_without_superexec(
|
|
114
|
+
_run_without_superexec(app_dir, federation_config, federation, config_overrides)
|
|
113
115
|
|
|
114
116
|
|
|
115
117
|
def _run_with_superexec(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
+
federation_config: Dict[str, Any],
|
|
119
|
+
app_dir: Optional[Path],
|
|
118
120
|
config_overrides: Optional[List[str]],
|
|
119
121
|
) -> None:
|
|
120
122
|
|
|
@@ -122,8 +124,8 @@ def _run_with_superexec(
|
|
|
122
124
|
"""Log channel connectivity."""
|
|
123
125
|
log(DEBUG, channel_connectivity)
|
|
124
126
|
|
|
125
|
-
insecure_str =
|
|
126
|
-
if root_certificates :=
|
|
127
|
+
insecure_str = federation_config.get("insecure")
|
|
128
|
+
if root_certificates := federation_config.get("root-certificates"):
|
|
127
129
|
root_certificates_bytes = Path(root_certificates).read_bytes()
|
|
128
130
|
if insecure := bool(insecure_str):
|
|
129
131
|
typer.secho(
|
|
@@ -151,7 +153,7 @@ def _run_with_superexec(
|
|
|
151
153
|
raise typer.Exit(code=1)
|
|
152
154
|
|
|
153
155
|
channel = create_channel(
|
|
154
|
-
server_address=
|
|
156
|
+
server_address=federation_config["address"],
|
|
155
157
|
insecure=insecure,
|
|
156
158
|
root_certificates=root_certificates_bytes,
|
|
157
159
|
max_message_length=GRPC_MAX_MESSAGE_LENGTH,
|
|
@@ -160,11 +162,16 @@ def _run_with_superexec(
|
|
|
160
162
|
channel.subscribe(on_channel_state_change)
|
|
161
163
|
stub = ExecStub(channel)
|
|
162
164
|
|
|
163
|
-
fab_path = build(
|
|
165
|
+
fab_path = build(app_dir)
|
|
164
166
|
|
|
165
167
|
req = StartRunRequest(
|
|
166
168
|
fab_file=Path(fab_path).read_bytes(),
|
|
167
|
-
override_config=
|
|
169
|
+
override_config=user_config_to_proto(
|
|
170
|
+
parse_config_args(config_overrides, separator=",")
|
|
171
|
+
),
|
|
172
|
+
federation_config=user_config_to_proto(
|
|
173
|
+
flatten_dict(federation_config.get("options"))
|
|
174
|
+
),
|
|
168
175
|
)
|
|
169
176
|
res = stub.StartRun(req)
|
|
170
177
|
typer.secho(f"🎊 Successfully started run {res.run_id}", fg=typer.colors.GREEN)
|
|
@@ -172,18 +179,18 @@ def _run_with_superexec(
|
|
|
172
179
|
|
|
173
180
|
def _run_without_superexec(
|
|
174
181
|
app_path: Optional[Path],
|
|
175
|
-
|
|
176
|
-
|
|
182
|
+
federation_config: Dict[str, Any],
|
|
183
|
+
federation: str,
|
|
177
184
|
config_overrides: Optional[List[str]],
|
|
178
185
|
) -> None:
|
|
179
186
|
try:
|
|
180
|
-
num_supernodes =
|
|
187
|
+
num_supernodes = federation_config["options"]["num-supernodes"]
|
|
181
188
|
except KeyError as err:
|
|
182
189
|
typer.secho(
|
|
183
190
|
"❌ The project's `pyproject.toml` needs to declare the number of"
|
|
184
191
|
" SuperNodes in the simulation. To simulate 10 SuperNodes,"
|
|
185
192
|
" use the following notation:\n\n"
|
|
186
|
-
f"[tool.flwr.federations.{
|
|
193
|
+
f"[tool.flwr.federations.{federation}]\n"
|
|
187
194
|
"options.num-supernodes = 10\n",
|
|
188
195
|
fg=typer.colors.RED,
|
|
189
196
|
bold=True,
|
flwr/client/app.py
CHANGED
|
@@ -42,7 +42,7 @@ from flwr.common.constant import (
|
|
|
42
42
|
from flwr.common.logger import log, warn_deprecated_feature
|
|
43
43
|
from flwr.common.message import Error
|
|
44
44
|
from flwr.common.retry_invoker import RetryInvoker, RetryState, exponential
|
|
45
|
-
from flwr.common.typing import Run
|
|
45
|
+
from flwr.common.typing import Run, UserConfig
|
|
46
46
|
|
|
47
47
|
from .grpc_adapter_client.connection import grpc_adapter
|
|
48
48
|
from .grpc_client.connection import grpc_connection
|
|
@@ -182,7 +182,7 @@ def start_client(
|
|
|
182
182
|
def _start_client_internal(
|
|
183
183
|
*,
|
|
184
184
|
server_address: str,
|
|
185
|
-
node_config:
|
|
185
|
+
node_config: UserConfig,
|
|
186
186
|
load_client_app_fn: Optional[Callable[[str, str], ClientApp]] = None,
|
|
187
187
|
client_fn: Optional[ClientFnExt] = None,
|
|
188
188
|
client: Optional[Client] = None,
|
|
@@ -205,7 +205,7 @@ def _start_client_internal(
|
|
|
205
205
|
The IPv4 or IPv6 address of the server. If the Flower
|
|
206
206
|
server runs on the same machine on port 8080, then `server_address`
|
|
207
207
|
would be `"[::]:8080"`.
|
|
208
|
-
node_config:
|
|
208
|
+
node_config: UserConfig
|
|
209
209
|
The configuration of the node.
|
|
210
210
|
load_client_app_fn : Optional[Callable[[], ClientApp]] (default: None)
|
|
211
211
|
A function that can be used to load a `ClientApp` instance.
|
|
@@ -40,7 +40,11 @@ from flwr.common.grpc import create_channel
|
|
|
40
40
|
from flwr.common.logger import log
|
|
41
41
|
from flwr.common.message import Message, Metadata
|
|
42
42
|
from flwr.common.retry_invoker import RetryInvoker
|
|
43
|
-
from flwr.common.serde import
|
|
43
|
+
from flwr.common.serde import (
|
|
44
|
+
message_from_taskins,
|
|
45
|
+
message_to_taskres,
|
|
46
|
+
user_config_from_proto,
|
|
47
|
+
)
|
|
44
48
|
from flwr.common.typing import Run
|
|
45
49
|
from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
|
|
46
50
|
CreateNodeRequest,
|
|
@@ -281,7 +285,7 @@ def grpc_request_response( # pylint: disable=R0913, R0914, R0915
|
|
|
281
285
|
run_id,
|
|
282
286
|
get_run_response.run.fab_id,
|
|
283
287
|
get_run_response.run.fab_version,
|
|
284
|
-
|
|
288
|
+
user_config_from_proto(get_run_response.run.override_config),
|
|
285
289
|
)
|
|
286
290
|
|
|
287
291
|
try:
|
flwr/client/node_state.py
CHANGED
|
@@ -21,7 +21,7 @@ from typing import Dict, Optional
|
|
|
21
21
|
|
|
22
22
|
from flwr.common import Context, RecordSet
|
|
23
23
|
from flwr.common.config import get_fused_config, get_fused_config_from_dir
|
|
24
|
-
from flwr.common.typing import Run
|
|
24
|
+
from flwr.common.typing import Run, UserConfig
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
@dataclass()
|
|
@@ -29,7 +29,7 @@ class RunInfo:
|
|
|
29
29
|
"""Contains the Context and initial run_config of a Run."""
|
|
30
30
|
|
|
31
31
|
context: Context
|
|
32
|
-
initial_run_config:
|
|
32
|
+
initial_run_config: UserConfig
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
class NodeState:
|
|
@@ -38,7 +38,7 @@ class NodeState:
|
|
|
38
38
|
def __init__(
|
|
39
39
|
self,
|
|
40
40
|
node_id: int,
|
|
41
|
-
node_config:
|
|
41
|
+
node_config: UserConfig,
|
|
42
42
|
) -> None:
|
|
43
43
|
self.node_id = node_id
|
|
44
44
|
self.node_config = node_config
|
|
@@ -40,7 +40,11 @@ from flwr.common.constant import (
|
|
|
40
40
|
from flwr.common.logger import log
|
|
41
41
|
from flwr.common.message import Message, Metadata
|
|
42
42
|
from flwr.common.retry_invoker import RetryInvoker
|
|
43
|
-
from flwr.common.serde import
|
|
43
|
+
from flwr.common.serde import (
|
|
44
|
+
message_from_taskins,
|
|
45
|
+
message_to_taskres,
|
|
46
|
+
user_config_from_proto,
|
|
47
|
+
)
|
|
44
48
|
from flwr.common.typing import Run
|
|
45
49
|
from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
|
|
46
50
|
CreateNodeRequest,
|
|
@@ -359,7 +363,7 @@ def http_request_response( # pylint: disable=,R0913, R0914, R0915
|
|
|
359
363
|
run_id,
|
|
360
364
|
res.run.fab_id,
|
|
361
365
|
res.run.fab_version,
|
|
362
|
-
|
|
366
|
+
user_config_from_proto(res.run.override_config),
|
|
363
367
|
)
|
|
364
368
|
|
|
365
369
|
try:
|
flwr/client/supernode/app.py
CHANGED
|
@@ -62,8 +62,8 @@ def run_supernode() -> None:
|
|
|
62
62
|
root_certificates = _get_certificates(args)
|
|
63
63
|
load_fn = _get_load_client_app_fn(
|
|
64
64
|
default_app_ref=getattr(args, "client-app"),
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
project_dir=args.dir,
|
|
66
|
+
flwr_dir=args.flwr_dir,
|
|
67
67
|
multi_app=True,
|
|
68
68
|
)
|
|
69
69
|
authentication_keys = _try_setup_client_authentication(args)
|
|
@@ -100,7 +100,7 @@ def run_client_app() -> None:
|
|
|
100
100
|
root_certificates = _get_certificates(args)
|
|
101
101
|
load_fn = _get_load_client_app_fn(
|
|
102
102
|
default_app_ref=getattr(args, "client-app"),
|
|
103
|
-
|
|
103
|
+
project_dir=args.dir,
|
|
104
104
|
multi_app=False,
|
|
105
105
|
)
|
|
106
106
|
authentication_keys = _try_setup_client_authentication(args)
|
|
@@ -176,9 +176,9 @@ def _get_certificates(args: argparse.Namespace) -> Optional[bytes]:
|
|
|
176
176
|
|
|
177
177
|
def _get_load_client_app_fn(
|
|
178
178
|
default_app_ref: str,
|
|
179
|
-
|
|
179
|
+
project_dir: str,
|
|
180
180
|
multi_app: bool,
|
|
181
|
-
|
|
181
|
+
flwr_dir: Optional[str] = None,
|
|
182
182
|
) -> Callable[[str, str], ClientApp]:
|
|
183
183
|
"""Get the load_client_app_fn function.
|
|
184
184
|
|
|
@@ -189,38 +189,21 @@ def _get_load_client_app_fn(
|
|
|
189
189
|
If `multi_app` is False, it ignores `fab_id` and `fab_version` and
|
|
190
190
|
loads a default ClientApp.
|
|
191
191
|
"""
|
|
192
|
-
# Find the Flower directory containing Flower Apps (only for multi-app)
|
|
193
|
-
if not multi_app:
|
|
194
|
-
flwr_dir = Path("")
|
|
195
|
-
else:
|
|
196
|
-
if flwr_dir_arg is None:
|
|
197
|
-
flwr_dir = get_flwr_dir()
|
|
198
|
-
else:
|
|
199
|
-
flwr_dir = Path(flwr_dir_arg).absolute()
|
|
200
|
-
|
|
201
|
-
inserted_path = None
|
|
202
|
-
|
|
203
192
|
if not multi_app:
|
|
204
193
|
log(
|
|
205
194
|
DEBUG,
|
|
206
195
|
"Flower SuperNode will load and validate ClientApp `%s`",
|
|
207
196
|
default_app_ref,
|
|
208
197
|
)
|
|
209
|
-
# Insert sys.path
|
|
210
|
-
dir_path = Path(dir_arg).absolute()
|
|
211
|
-
sys.path.insert(0, str(dir_path))
|
|
212
|
-
inserted_path = str(dir_path)
|
|
213
198
|
|
|
214
|
-
valid, error_msg = validate(default_app_ref)
|
|
199
|
+
valid, error_msg = validate(default_app_ref, project_dir=project_dir)
|
|
215
200
|
if not valid and error_msg:
|
|
216
201
|
raise LoadClientAppError(error_msg) from None
|
|
217
202
|
|
|
218
203
|
def _load(fab_id: str, fab_version: str) -> ClientApp:
|
|
204
|
+
runtime_project_dir = Path(project_dir).absolute()
|
|
219
205
|
# If multi-app feature is disabled
|
|
220
206
|
if not multi_app:
|
|
221
|
-
# Get sys path to be inserted
|
|
222
|
-
dir_path = Path(dir_arg).absolute()
|
|
223
|
-
|
|
224
207
|
# Set app reference
|
|
225
208
|
client_app_ref = default_app_ref
|
|
226
209
|
# If multi-app feature is enabled but the fab id is not specified
|
|
@@ -231,43 +214,29 @@ def _get_load_client_app_fn(
|
|
|
231
214
|
) from None
|
|
232
215
|
|
|
233
216
|
log(WARN, "FAB ID is not provided; the default ClientApp will be loaded.")
|
|
234
|
-
# Get sys path to be inserted
|
|
235
|
-
dir_path = Path(dir_arg).absolute()
|
|
236
217
|
|
|
237
218
|
# Set app reference
|
|
238
219
|
client_app_ref = default_app_ref
|
|
239
220
|
# If multi-app feature is enabled
|
|
240
221
|
else:
|
|
241
222
|
try:
|
|
242
|
-
|
|
243
|
-
|
|
223
|
+
runtime_project_dir = get_project_dir(
|
|
224
|
+
fab_id, fab_version, get_flwr_dir(flwr_dir)
|
|
225
|
+
)
|
|
226
|
+
config = get_project_config(runtime_project_dir)
|
|
244
227
|
except Exception as e:
|
|
245
228
|
raise LoadClientAppError("Failed to load ClientApp") from e
|
|
246
229
|
|
|
247
|
-
# Get sys path to be inserted
|
|
248
|
-
dir_path = Path(project_dir).absolute()
|
|
249
|
-
|
|
250
230
|
# Set app reference
|
|
251
231
|
client_app_ref = config["tool"]["flwr"]["app"]["components"]["clientapp"]
|
|
252
232
|
|
|
253
|
-
# Set sys.path
|
|
254
|
-
nonlocal inserted_path
|
|
255
|
-
if inserted_path != str(dir_path):
|
|
256
|
-
# Remove the previously inserted path
|
|
257
|
-
if inserted_path is not None:
|
|
258
|
-
sys.path.remove(inserted_path)
|
|
259
|
-
# Insert the new path
|
|
260
|
-
sys.path.insert(0, str(dir_path))
|
|
261
|
-
|
|
262
|
-
inserted_path = str(dir_path)
|
|
263
|
-
|
|
264
233
|
# Load ClientApp
|
|
265
234
|
log(
|
|
266
235
|
DEBUG,
|
|
267
236
|
"Loading ClientApp `%s`",
|
|
268
237
|
client_app_ref,
|
|
269
238
|
)
|
|
270
|
-
client_app = load_app(client_app_ref, LoadClientAppError,
|
|
239
|
+
client_app = load_app(client_app_ref, LoadClientAppError, runtime_project_dir)
|
|
271
240
|
|
|
272
241
|
if not isinstance(client_app, ClientApp):
|
|
273
242
|
raise LoadClientAppError(
|
flwr/common/config.py
CHANGED
|
@@ -16,13 +16,13 @@
|
|
|
16
16
|
|
|
17
17
|
import os
|
|
18
18
|
from pathlib import Path
|
|
19
|
-
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
19
|
+
from typing import Any, Dict, List, Optional, Tuple, Union, cast, get_args
|
|
20
20
|
|
|
21
21
|
import tomli
|
|
22
22
|
|
|
23
23
|
from flwr.cli.config_utils import validate_fields
|
|
24
24
|
from flwr.common.constant import APP_DIR, FAB_CONFIG_FILE, FLWR_HOME
|
|
25
|
-
from flwr.common.typing import Run
|
|
25
|
+
from flwr.common.typing import Run, UserConfig, UserConfigValue
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
def get_flwr_dir(provided_path: Optional[str] = None) -> Path:
|
|
@@ -75,8 +75,9 @@ def get_project_config(project_dir: Union[str, Path]) -> Dict[str, Any]:
|
|
|
75
75
|
|
|
76
76
|
|
|
77
77
|
def _fuse_dicts(
|
|
78
|
-
main_dict:
|
|
79
|
-
|
|
78
|
+
main_dict: UserConfig,
|
|
79
|
+
override_dict: UserConfig,
|
|
80
|
+
) -> UserConfig:
|
|
80
81
|
fused_dict = main_dict.copy()
|
|
81
82
|
|
|
82
83
|
for key, value in override_dict.items():
|
|
@@ -87,8 +88,8 @@ def _fuse_dicts(
|
|
|
87
88
|
|
|
88
89
|
|
|
89
90
|
def get_fused_config_from_dir(
|
|
90
|
-
project_dir: Path, override_config:
|
|
91
|
-
) ->
|
|
91
|
+
project_dir: Path, override_config: UserConfig
|
|
92
|
+
) -> UserConfig:
|
|
92
93
|
"""Merge the overrides from a given dict with the config from a Flower App."""
|
|
93
94
|
default_config = get_project_config(project_dir)["tool"]["flwr"]["app"].get(
|
|
94
95
|
"config", {}
|
|
@@ -98,7 +99,7 @@ def get_fused_config_from_dir(
|
|
|
98
99
|
return _fuse_dicts(flat_default_config, override_config)
|
|
99
100
|
|
|
100
101
|
|
|
101
|
-
def get_fused_config(run: Run, flwr_dir: Optional[Path]) ->
|
|
102
|
+
def get_fused_config(run: Run, flwr_dir: Optional[Path]) -> UserConfig:
|
|
102
103
|
"""Merge the overrides from a `Run` with the config from a FAB.
|
|
103
104
|
|
|
104
105
|
Get the config using the fab_id and the fab_version, remove the nesting by adding
|
|
@@ -112,19 +113,25 @@ def get_fused_config(run: Run, flwr_dir: Optional[Path]) -> Dict[str, str]:
|
|
|
112
113
|
return get_fused_config_from_dir(project_dir, run.override_config)
|
|
113
114
|
|
|
114
115
|
|
|
115
|
-
def flatten_dict(
|
|
116
|
+
def flatten_dict(
|
|
117
|
+
raw_dict: Optional[Dict[str, Any]], parent_key: str = ""
|
|
118
|
+
) -> UserConfig:
|
|
116
119
|
"""Flatten dict by joining nested keys with a given separator."""
|
|
117
|
-
|
|
120
|
+
if raw_dict is None:
|
|
121
|
+
return {}
|
|
122
|
+
|
|
123
|
+
items: List[Tuple[str, UserConfigValue]] = []
|
|
118
124
|
separator: str = "."
|
|
119
125
|
for k, v in raw_dict.items():
|
|
120
126
|
new_key = f"{parent_key}{separator}{k}" if parent_key else k
|
|
121
127
|
if isinstance(v, dict):
|
|
122
128
|
items.extend(flatten_dict(v, parent_key=new_key).items())
|
|
123
|
-
elif isinstance(v,
|
|
124
|
-
items.append((new_key, v))
|
|
129
|
+
elif isinstance(v, get_args(UserConfigValue)):
|
|
130
|
+
items.append((new_key, cast(UserConfigValue, v)))
|
|
125
131
|
else:
|
|
126
132
|
raise ValueError(
|
|
127
|
-
f"The value for key {k} needs to be
|
|
133
|
+
f"The value for key {k} needs to be of type `int`, `float`, "
|
|
134
|
+
"`bool, `str`, or a `dict` of those.",
|
|
128
135
|
)
|
|
129
136
|
return dict(items)
|
|
130
137
|
|
|
@@ -132,9 +139,9 @@ def flatten_dict(raw_dict: Dict[str, Any], parent_key: str = "") -> Dict[str, st
|
|
|
132
139
|
def parse_config_args(
|
|
133
140
|
config: Optional[List[str]],
|
|
134
141
|
separator: str = ",",
|
|
135
|
-
) ->
|
|
142
|
+
) -> UserConfig:
|
|
136
143
|
"""Parse separator separated list of key-value pairs separated by '='."""
|
|
137
|
-
overrides:
|
|
144
|
+
overrides: UserConfig = {}
|
|
138
145
|
|
|
139
146
|
if config is None:
|
|
140
147
|
return overrides
|
|
@@ -150,8 +157,7 @@ def parse_config_args(
|
|
|
150
157
|
with Path(overrides_list[0]).open("rb") as config_file:
|
|
151
158
|
overrides = flatten_dict(tomli.load(config_file))
|
|
152
159
|
else:
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
overrides[key] = value
|
|
160
|
+
toml_str = "\n".join(overrides_list)
|
|
161
|
+
overrides.update(tomli.loads(toml_str))
|
|
156
162
|
|
|
157
163
|
return overrides
|
flwr/common/context.py
CHANGED
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
from dataclasses import dataclass
|
|
19
|
-
from typing import Dict
|
|
20
19
|
|
|
21
20
|
from .record import RecordSet
|
|
21
|
+
from .typing import UserConfig
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
@dataclass
|
|
@@ -29,7 +29,7 @@ class Context:
|
|
|
29
29
|
----------
|
|
30
30
|
node_id : int
|
|
31
31
|
The ID that identifies the node.
|
|
32
|
-
node_config :
|
|
32
|
+
node_config : UserConfig
|
|
33
33
|
A config (key/value mapping) unique to the node and independent of the
|
|
34
34
|
`run_config`. This config persists across all runs this node participates in.
|
|
35
35
|
state : RecordSet
|
|
@@ -39,23 +39,23 @@ class Context:
|
|
|
39
39
|
executing mods. It can also be used as a memory to access
|
|
40
40
|
at different points during the lifecycle of this entity (e.g. across
|
|
41
41
|
multiple rounds)
|
|
42
|
-
run_config :
|
|
42
|
+
run_config : UserConfig
|
|
43
43
|
A config (key/value mapping) held by the entity in a given run and that will
|
|
44
44
|
stay local. It can be used at any point during the lifecycle of this entity
|
|
45
45
|
(e.g. across multiple rounds)
|
|
46
46
|
"""
|
|
47
47
|
|
|
48
48
|
node_id: int
|
|
49
|
-
node_config:
|
|
49
|
+
node_config: UserConfig
|
|
50
50
|
state: RecordSet
|
|
51
|
-
run_config:
|
|
51
|
+
run_config: UserConfig
|
|
52
52
|
|
|
53
53
|
def __init__( # pylint: disable=too-many-arguments
|
|
54
54
|
self,
|
|
55
55
|
node_id: int,
|
|
56
|
-
node_config:
|
|
56
|
+
node_config: UserConfig,
|
|
57
57
|
state: RecordSet,
|
|
58
|
-
run_config:
|
|
58
|
+
run_config: UserConfig,
|
|
59
59
|
) -> None:
|
|
60
60
|
self.node_id = node_id
|
|
61
61
|
self.node_config = node_config
|