flwr-nightly 1.10.0.dev20240715__py3-none-any.whl → 1.10.0.dev20240717__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.

Files changed (37) hide show
  1. flwr/cli/build.py +16 -2
  2. flwr/cli/config_utils.py +23 -15
  3. flwr/cli/install.py +17 -1
  4. flwr/cli/new/new.py +4 -3
  5. flwr/cli/new/templates/app/code/flwr_tune/app.py.tpl +2 -2
  6. flwr/cli/new/templates/app/code/flwr_tune/server.py.tpl +1 -1
  7. flwr/cli/new/templates/app/code/server.hf.py.tpl +4 -1
  8. flwr/cli/new/templates/app/code/server.jax.py.tpl +4 -1
  9. flwr/cli/new/templates/app/code/server.mlx.py.tpl +4 -1
  10. flwr/cli/new/templates/app/code/server.numpy.py.tpl +4 -1
  11. flwr/cli/new/templates/app/code/server.pytorch.py.tpl +4 -1
  12. flwr/cli/new/templates/app/code/server.sklearn.py.tpl +4 -1
  13. flwr/cli/new/templates/app/code/server.tensorflow.py.tpl +4 -1
  14. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +6 -3
  15. flwr/cli/new/templates/app/pyproject.hf.toml.tpl +8 -5
  16. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +8 -5
  17. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +8 -5
  18. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +8 -5
  19. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +8 -5
  20. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +8 -5
  21. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +8 -5
  22. flwr/cli/run/run.py +29 -15
  23. flwr/client/app.py +3 -3
  24. flwr/client/node_state.py +17 -3
  25. flwr/client/supernode/app.py +4 -4
  26. flwr/common/config.py +28 -17
  27. flwr/server/run_serverapp.py +1 -1
  28. flwr/server/superlink/fleet/vce/vce_api.py +10 -3
  29. flwr/simulation/run_simulation.py +148 -14
  30. flwr/superexec/app.py +1 -1
  31. flwr/superexec/deployment.py +1 -9
  32. flwr/superexec/simulation.py +8 -18
  33. {flwr_nightly-1.10.0.dev20240715.dist-info → flwr_nightly-1.10.0.dev20240717.dist-info}/METADATA +2 -1
  34. {flwr_nightly-1.10.0.dev20240715.dist-info → flwr_nightly-1.10.0.dev20240717.dist-info}/RECORD +37 -37
  35. {flwr_nightly-1.10.0.dev20240715.dist-info → flwr_nightly-1.10.0.dev20240717.dist-info}/LICENSE +0 -0
  36. {flwr_nightly-1.10.0.dev20240715.dist-info → flwr_nightly-1.10.0.dev20240717.dist-info}/WHEEL +0 -0
  37. {flwr_nightly-1.10.0.dev20240715.dist-info → flwr_nightly-1.10.0.dev20240717.dist-info}/entry_points.txt +0 -0
flwr/client/app.py CHANGED
@@ -195,7 +195,7 @@ def _start_client_internal(
195
195
  ] = None,
196
196
  max_retries: Optional[int] = None,
197
197
  max_wait_time: Optional[float] = None,
198
- flwr_dir: Optional[Path] = None,
198
+ flwr_path: Optional[Path] = None,
199
199
  ) -> None:
200
200
  """Start a Flower client node which connects to a Flower server.
201
201
 
@@ -241,7 +241,7 @@ def _start_client_internal(
241
241
  The maximum duration before the client stops trying to
242
242
  connect to the server in case of connection error.
243
243
  If set to None, there is no limit to the total time.
244
- flwr_dir: Optional[Path] (default: None)
244
+ flwr_path: Optional[Path] (default: None)
245
245
  The fully resolved path containing installed Flower Apps.
246
246
  """
247
247
  if insecure is None:
@@ -402,7 +402,7 @@ def _start_client_internal(
402
402
 
403
403
  # Register context for this run
404
404
  node_state.register_context(
405
- run_id=run_id, run=runs[run_id], flwr_dir=flwr_dir
405
+ run_id=run_id, run=runs[run_id], flwr_path=flwr_path
406
406
  )
407
407
 
408
408
  # Retrieve context for this run
flwr/client/node_state.py CHANGED
@@ -20,7 +20,7 @@ from pathlib import Path
20
20
  from typing import Dict, Optional
21
21
 
22
22
  from flwr.common import Context, RecordSet
23
- from flwr.common.config import get_fused_config
23
+ from flwr.common.config import get_fused_config, get_fused_config_from_dir
24
24
  from flwr.common.typing import Run
25
25
 
26
26
 
@@ -48,11 +48,25 @@ class NodeState:
48
48
  self,
49
49
  run_id: int,
50
50
  run: Optional[Run] = None,
51
- flwr_dir: Optional[Path] = None,
51
+ flwr_path: Optional[Path] = None,
52
+ app_dir: Optional[str] = None,
52
53
  ) -> None:
53
54
  """Register new run context for this node."""
54
55
  if run_id not in self.run_infos:
55
- initial_run_config = get_fused_config(run, flwr_dir) if run else {}
56
+ initial_run_config = {}
57
+ if app_dir:
58
+ # Load from app directory
59
+ app_path = Path(app_dir)
60
+ if app_path.is_dir():
61
+ override_config = run.override_config if run else {}
62
+ initial_run_config = get_fused_config_from_dir(
63
+ app_path, override_config
64
+ )
65
+ else:
66
+ raise ValueError("The specified `app_dir` must be a directory.")
67
+ else:
68
+ # Load from .fab
69
+ initial_run_config = get_fused_config(run, flwr_path) if run else {}
56
70
  self.run_infos[run_id] = RunInfo(
57
71
  initial_run_config=initial_run_config,
58
72
  context=Context(
@@ -77,8 +77,8 @@ def run_supernode() -> None:
77
77
  authentication_keys=authentication_keys,
78
78
  max_retries=args.max_retries,
79
79
  max_wait_time=args.max_wait_time,
80
- node_config=parse_config_args(args.node_config),
81
- flwr_dir=get_flwr_dir(args.flwr_dir),
80
+ node_config=parse_config_args([args.node_config]),
81
+ flwr_path=get_flwr_dir(args.flwr_dir),
82
82
  )
83
83
 
84
84
  # Graceful shutdown
@@ -107,7 +107,7 @@ def run_client_app() -> None:
107
107
 
108
108
  _start_client_internal(
109
109
  server_address=args.superlink,
110
- node_config=parse_config_args(args.node_config),
110
+ node_config=parse_config_args([args.node_config]),
111
111
  load_client_app_fn=load_fn,
112
112
  transport=args.transport,
113
113
  root_certificates=root_certificates,
@@ -248,7 +248,7 @@ def _get_load_client_app_fn(
248
248
  dir_path = Path(project_dir).absolute()
249
249
 
250
250
  # Set app reference
251
- client_app_ref = config["tool"]["flwr"]["components"]["clientapp"]
251
+ client_app_ref = config["tool"]["flwr"]["app"]["components"]["clientapp"]
252
252
 
253
253
  # Set sys.path
254
254
  nonlocal inserted_path
flwr/common/config.py CHANGED
@@ -86,6 +86,18 @@ def _fuse_dicts(
86
86
  return fused_dict
87
87
 
88
88
 
89
+ def get_fused_config_from_dir(
90
+ project_dir: Path, override_config: Dict[str, str]
91
+ ) -> Dict[str, str]:
92
+ """Merge the overrides from a given dict with the config from a Flower App."""
93
+ default_config = get_project_config(project_dir)["tool"]["flwr"]["app"].get(
94
+ "config", {}
95
+ )
96
+ flat_default_config = flatten_dict(default_config)
97
+
98
+ return _fuse_dicts(flat_default_config, override_config)
99
+
100
+
89
101
  def get_fused_config(run: Run, flwr_dir: Optional[Path]) -> Dict[str, str]:
90
102
  """Merge the overrides from a `Run` with the config from a FAB.
91
103
 
@@ -97,10 +109,7 @@ def get_fused_config(run: Run, flwr_dir: Optional[Path]) -> Dict[str, str]:
97
109
 
98
110
  project_dir = get_project_dir(run.fab_id, run.fab_version, flwr_dir)
99
111
 
100
- default_config = get_project_config(project_dir)["tool"]["flwr"].get("config", {})
101
- flat_default_config = flatten_dict(default_config)
102
-
103
- return _fuse_dicts(flat_default_config, run.override_config)
112
+ return get_fused_config_from_dir(project_dir, run.override_config)
104
113
 
105
114
 
106
115
  def flatten_dict(raw_dict: Dict[str, Any], parent_key: str = "") -> Dict[str, str]:
@@ -121,7 +130,7 @@ def flatten_dict(raw_dict: Dict[str, Any], parent_key: str = "") -> Dict[str, st
121
130
 
122
131
 
123
132
  def parse_config_args(
124
- config: Optional[str],
133
+ config: Optional[List[str]],
125
134
  separator: str = ",",
126
135
  ) -> Dict[str, str]:
127
136
  """Parse separator separated list of key-value pairs separated by '='."""
@@ -130,17 +139,19 @@ def parse_config_args(
130
139
  if config is None:
131
140
  return overrides
132
141
 
133
- overrides_list = config.split(separator)
134
- if (
135
- len(overrides_list) == 1
136
- and "=" not in overrides_list
137
- and overrides_list[0].endswith(".toml")
138
- ):
139
- with Path(overrides_list[0]).open("rb") as config_file:
140
- overrides = flatten_dict(tomli.load(config_file))
141
- else:
142
- for kv_pair in overrides_list:
143
- key, value = kv_pair.split("=")
144
- overrides[key] = value
142
+ for config_line in config:
143
+ if config_line:
144
+ overrides_list = config_line.split(separator)
145
+ if (
146
+ len(overrides_list) == 1
147
+ and "=" not in overrides_list
148
+ and overrides_list[0].endswith(".toml")
149
+ ):
150
+ with Path(overrides_list[0]).open("rb") as config_file:
151
+ overrides = flatten_dict(tomli.load(config_file))
152
+ else:
153
+ for kv_pair in overrides_list:
154
+ key, value = kv_pair.split("=")
155
+ overrides[key] = value
145
156
 
146
157
  return overrides
@@ -186,7 +186,7 @@ def run_server_app() -> None: # pylint: disable=too-many-branches
186
186
  run_ = driver.run
187
187
  server_app_dir = str(get_project_dir(run_.fab_id, run_.fab_version, flwr_dir))
188
188
  config = get_project_config(server_app_dir)
189
- server_app_attr = config["tool"]["flwr"]["components"]["serverapp"]
189
+ server_app_attr = config["tool"]["flwr"]["app"]["components"]["serverapp"]
190
190
  server_app_run_config = get_fused_config(run_, flwr_dir)
191
191
  else:
192
192
  # User provided `server-app`, but not `--run-id`
@@ -61,7 +61,9 @@ def _register_nodes(
61
61
 
62
62
 
63
63
  def _register_node_states(
64
- nodes_mapping: NodeToPartitionMapping, run: Run
64
+ nodes_mapping: NodeToPartitionMapping,
65
+ run: Run,
66
+ app_dir: Optional[str] = None,
65
67
  ) -> Dict[int, NodeState]:
66
68
  """Create NodeState objects and pre-register the context for the run."""
67
69
  node_states: Dict[int, NodeState] = {}
@@ -76,7 +78,9 @@ def _register_node_states(
76
78
  )
77
79
 
78
80
  # Pre-register Context objects
79
- node_states[node_id].register_context(run_id=run.run_id, run=run)
81
+ node_states[node_id].register_context(
82
+ run_id=run.run_id, run=run, app_dir=app_dir
83
+ )
80
84
 
81
85
  return node_states
82
86
 
@@ -256,6 +260,7 @@ def start_vce(
256
260
  backend_name: str,
257
261
  backend_config_json_stream: str,
258
262
  app_dir: str,
263
+ is_app: bool,
259
264
  f_stop: threading.Event,
260
265
  run: Run,
261
266
  flwr_dir: Optional[str] = None,
@@ -309,7 +314,9 @@ def start_vce(
309
314
  )
310
315
 
311
316
  # Construct mapping of NodeStates
312
- node_states = _register_node_states(nodes_mapping=nodes_mapping, run=run)
317
+ node_states = _register_node_states(
318
+ nodes_mapping=nodes_mapping, run=run, app_dir=app_dir if is_app else None
319
+ )
313
320
 
314
321
  # Load backend config
315
322
  log(DEBUG, "Supported backends: %s", list(supported_backends.keys()))
@@ -18,14 +18,19 @@ import argparse
18
18
  import asyncio
19
19
  import json
20
20
  import logging
21
+ import sys
21
22
  import threading
22
23
  import traceback
24
+ from argparse import Namespace
23
25
  from logging import DEBUG, ERROR, INFO, WARNING
26
+ from pathlib import Path
24
27
  from time import sleep
25
- from typing import Dict, Optional
28
+ from typing import Dict, List, Optional
26
29
 
30
+ from flwr.cli.config_utils import load_and_validate
27
31
  from flwr.client import ClientApp
28
32
  from flwr.common import EventType, event, log
33
+ from flwr.common.config import get_fused_config_from_dir, parse_config_args
29
34
  from flwr.common.constant import RUN_ID_NUM_BYTES
30
35
  from flwr.common.logger import set_logger_propagation, update_console_handler
31
36
  from flwr.common.typing import Run
@@ -41,28 +46,129 @@ from flwr.simulation.ray_transport.utils import (
41
46
  )
42
47
 
43
48
 
49
+ def _check_args_do_not_interfere(args: Namespace) -> bool:
50
+ """Ensure decoupling of flags for different ways to start the simulation."""
51
+ mode_one_args = ["app", "run_config"]
52
+ mode_two_args = ["client_app", "server_app"]
53
+
54
+ def _resolve_message(conflict_keys: List[str]) -> str:
55
+ return ",".join([f"`--{key}`".replace("_", "-") for key in conflict_keys])
56
+
57
+ # When passing `--app`, `--app-dir` is ignored
58
+ if args.app and args.app_dir:
59
+ log(ERROR, "Either `--app` or `--app-dir` can be set, but not both.")
60
+ return False
61
+
62
+ if any(getattr(args, key) for key in mode_one_args):
63
+ if any(getattr(args, key) for key in mode_two_args):
64
+ log(
65
+ ERROR,
66
+ "Passing any of {%s} alongside with any of {%s}",
67
+ _resolve_message(mode_one_args),
68
+ _resolve_message(mode_two_args),
69
+ )
70
+ return False
71
+
72
+ if not args.app:
73
+ log(ERROR, "You need to pass --app")
74
+ return False
75
+
76
+ return True
77
+
78
+ # Ensure all args are set (required for the non-FAB mode of execution)
79
+ if not all(getattr(args, key) for key in mode_two_args):
80
+ log(
81
+ ERROR,
82
+ "Passing all of %s keys are required.",
83
+ _resolve_message(mode_two_args),
84
+ )
85
+ return False
86
+
87
+ return True
88
+
89
+
44
90
  # Entry point from CLI
91
+ # pylint: disable=too-many-locals
45
92
  def run_simulation_from_cli() -> None:
46
93
  """Run Simulation Engine from the CLI."""
47
94
  args = _parse_args_run_simulation().parse_args()
48
95
 
96
+ # We are supporting two modes for the CLI entrypoint:
97
+ # 1) Running an app dir containing a `pyproject.toml`
98
+ # 2) Running any ClientApp and SeverApp w/o pyproject.toml being present
99
+ # For 2), some CLI args are compulsory, but they are not required for 1)
100
+ # We first do these checks
101
+ args_check_pass = _check_args_do_not_interfere(args)
102
+ if not args_check_pass:
103
+ sys.exit("Simulation Engine cannot start.")
104
+
105
+ run_id = (
106
+ generate_rand_int_from_bytes(RUN_ID_NUM_BYTES)
107
+ if args.run_id is None
108
+ else args.run_id
109
+ )
110
+ if args.app:
111
+ # Mode 1
112
+ app_path = Path(args.app)
113
+ if not app_path.is_dir():
114
+ log(ERROR, "--app is not a directory")
115
+ sys.exit("Simulation Engine cannot start.")
116
+
117
+ # Load pyproject.toml
118
+ config, errors, warnings = load_and_validate(
119
+ app_path / "pyproject.toml", check_module=False
120
+ )
121
+ if errors:
122
+ raise ValueError(errors)
123
+
124
+ if warnings:
125
+ log(WARNING, warnings)
126
+
127
+ if config is None:
128
+ raise ValueError("Config extracted from FAB's pyproject.toml is not valid")
129
+
130
+ # Get ClientApp and SeverApp components
131
+ app_components = config["tool"]["flwr"]["app"]["components"]
132
+ client_app_attr = app_components["clientapp"]
133
+ server_app_attr = app_components["serverapp"]
134
+
135
+ override_config = parse_config_args([args.run_config])
136
+ fused_config = get_fused_config_from_dir(app_path, override_config)
137
+ app_dir = args.app
138
+ is_app = True
139
+
140
+ else:
141
+ # Mode 2
142
+ client_app_attr = args.client_app
143
+ server_app_attr = args.server_app
144
+ override_config = {}
145
+ fused_config = None
146
+ app_dir = args.app_dir
147
+ is_app = False
148
+
149
+ # Create run
150
+ run = Run(
151
+ run_id=run_id,
152
+ fab_id="",
153
+ fab_version="",
154
+ override_config=override_config,
155
+ )
156
+
49
157
  # Load JSON config
50
158
  backend_config_dict = json.loads(args.backend_config)
51
159
 
52
160
  _run_simulation(
53
- server_app_attr=args.server_app,
54
- client_app_attr=args.client_app,
161
+ server_app_attr=server_app_attr,
162
+ client_app_attr=client_app_attr,
55
163
  num_supernodes=args.num_supernodes,
56
164
  backend_name=args.backend,
57
165
  backend_config=backend_config_dict,
58
- app_dir=args.app_dir,
59
- run=(
60
- Run(run_id=args.run_id, fab_id="", fab_version="", override_config={})
61
- if args.run_id
62
- else None
63
- ),
166
+ app_dir=app_dir,
167
+ run=run,
64
168
  enable_tf_gpu_growth=args.enable_tf_gpu_growth,
65
169
  verbose_logging=args.verbose,
170
+ server_app_run_config=fused_config,
171
+ is_app=is_app,
66
172
  )
67
173
 
68
174
 
@@ -205,6 +311,7 @@ def _main_loop(
205
311
  backend_name: str,
206
312
  backend_config_stream: str,
207
313
  app_dir: str,
314
+ is_app: bool,
208
315
  enable_tf_gpu_growth: bool,
209
316
  run: Run,
210
317
  flwr_dir: Optional[str] = None,
@@ -212,6 +319,7 @@ def _main_loop(
212
319
  client_app_attr: Optional[str] = None,
213
320
  server_app: Optional[ServerApp] = None,
214
321
  server_app_attr: Optional[str] = None,
322
+ server_app_run_config: Optional[Dict[str, str]] = None,
215
323
  ) -> None:
216
324
  """Launch SuperLink with Simulation Engine, then ServerApp on a separate thread."""
217
325
  # Initialize StateFactory
@@ -225,7 +333,9 @@ def _main_loop(
225
333
  # Register run
226
334
  log(DEBUG, "Pre-registering run with id %s", run.run_id)
227
335
  state_factory.state().run_ids[run.run_id] = run # type: ignore
228
- server_app_run_config: Dict[str, str] = {}
336
+
337
+ if server_app_run_config is None:
338
+ server_app_run_config = {}
229
339
 
230
340
  # Initialize Driver
231
341
  driver = InMemoryDriver(run_id=run.run_id, state_factory=state_factory)
@@ -251,6 +361,7 @@ def _main_loop(
251
361
  backend_name=backend_name,
252
362
  backend_config_json_stream=backend_config_stream,
253
363
  app_dir=app_dir,
364
+ is_app=is_app,
254
365
  state_factory=state_factory,
255
366
  f_stop=f_stop,
256
367
  run=run,
@@ -284,11 +395,13 @@ def _run_simulation(
284
395
  backend_config: Optional[BackendConfig] = None,
285
396
  client_app_attr: Optional[str] = None,
286
397
  server_app_attr: Optional[str] = None,
398
+ server_app_run_config: Optional[Dict[str, str]] = None,
287
399
  app_dir: str = "",
288
400
  flwr_dir: Optional[str] = None,
289
401
  run: Optional[Run] = None,
290
402
  enable_tf_gpu_growth: bool = False,
291
403
  verbose_logging: bool = False,
404
+ is_app: bool = False,
292
405
  ) -> None:
293
406
  r"""Launch the Simulation Engine.
294
407
 
@@ -317,14 +430,18 @@ def _run_simulation(
317
430
  parameters. Values supported in <value> are those included by
318
431
  `flwr.common.typing.ConfigsRecordValues`.
319
432
 
320
- client_app_attr : str
433
+ client_app_attr : Optional[str]
321
434
  A path to a `ClientApp` module to be loaded: For example: `client:app` or
322
435
  `project.package.module:wrapper.app`."
323
436
 
324
- server_app_attr : str
437
+ server_app_attr : Optional[str]
325
438
  A path to a `ServerApp` module to be loaded: For example: `server:app` or
326
439
  `project.package.module:wrapper.app`."
327
440
 
441
+ server_app_run_config : Optional[Dict[str, str]]
442
+ Config dictionary that parameterizes the run config. It will be made accesible
443
+ to the ServerApp.
444
+
328
445
  app_dir : str
329
446
  Add specified directory to the PYTHONPATH and load `ClientApp` from there.
330
447
  (Default: current working directory.)
@@ -346,6 +463,11 @@ def _run_simulation(
346
463
  verbose_logging : bool (default: False)
347
464
  When disabled, only INFO, WARNING and ERROR log messages will be shown. If
348
465
  enabled, DEBUG-level logs will be displayed.
466
+
467
+ is_app : bool (default: False)
468
+ A flag that indicates whether the simulation is running an app or not. This is
469
+ needed in order to attempt loading an app's pyproject.toml when nodes register
470
+ a context object.
349
471
  """
350
472
  if backend_config is None:
351
473
  backend_config = {}
@@ -381,6 +503,7 @@ def _run_simulation(
381
503
  backend_name,
382
504
  backend_config_stream,
383
505
  app_dir,
506
+ is_app,
384
507
  enable_tf_gpu_growth,
385
508
  run,
386
509
  flwr_dir,
@@ -388,6 +511,7 @@ def _run_simulation(
388
511
  client_app_attr,
389
512
  server_app,
390
513
  server_app_attr,
514
+ server_app_run_config,
391
515
  )
392
516
  # Detect if there is an Asyncio event loop already running.
393
517
  # If yes, disable logger propagation. In environmnets
@@ -419,12 +543,10 @@ def _parse_args_run_simulation() -> argparse.ArgumentParser:
419
543
  )
420
544
  parser.add_argument(
421
545
  "--server-app",
422
- required=True,
423
546
  help="For example: `server:app` or `project.package.module:wrapper.app`",
424
547
  )
425
548
  parser.add_argument(
426
549
  "--client-app",
427
- required=True,
428
550
  help="For example: `client:app` or `project.package.module:wrapper.app`",
429
551
  )
430
552
  parser.add_argument(
@@ -433,6 +555,18 @@ def _parse_args_run_simulation() -> argparse.ArgumentParser:
433
555
  required=True,
434
556
  help="Number of simulated SuperNodes.",
435
557
  )
558
+ parser.add_argument(
559
+ "--app",
560
+ type=str,
561
+ default=None,
562
+ help="Path to a directory containing a FAB-like structure with a "
563
+ "pyproject.toml.",
564
+ )
565
+ parser.add_argument(
566
+ "--run-config",
567
+ default=None,
568
+ help="Override configuration key-value pairs.",
569
+ )
436
570
  parser.add_argument(
437
571
  "--backend",
438
572
  default="ray",
flwr/superexec/app.py CHANGED
@@ -56,7 +56,7 @@ def run_superexec() -> None:
56
56
  address=address,
57
57
  executor=_load_executor(args),
58
58
  certificates=certificates,
59
- config=parse_config_args(args.executor_config),
59
+ config=parse_config_args([args.executor_config]),
60
60
  )
61
61
 
62
62
  grpc_servers = [superexec_server]
@@ -15,7 +15,6 @@
15
15
  """Deployment engine executor."""
16
16
 
17
17
  import subprocess
18
- import sys
19
18
  from logging import ERROR, INFO
20
19
  from pathlib import Path
21
20
  from typing import Dict, Optional
@@ -131,14 +130,7 @@ class DeploymentEngine(Executor):
131
130
  try:
132
131
  # Install FAB to flwr dir
133
132
  fab_version, fab_id = get_fab_metadata(fab_file)
134
- fab_path = install_from_fab(fab_file, None, True)
135
-
136
- # Install FAB Python package
137
- subprocess.check_call(
138
- [sys.executable, "-m", "pip", "install", "--no-deps", str(fab_path)],
139
- stdout=subprocess.DEVNULL,
140
- stderr=subprocess.DEVNULL,
141
- )
133
+ install_from_fab(fab_file, None, True)
142
134
 
143
135
  # Call SuperLink to create run
144
136
  run_id: int = self._create_run(fab_id, fab_version, override_config)
@@ -82,11 +82,6 @@ class SimulationEngine(Executor):
82
82
  ) -> Optional[RunTracker]:
83
83
  """Start run using the Flower Simulation Engine."""
84
84
  try:
85
- if override_config:
86
- raise ValueError(
87
- "Overriding the run config is not yet supported with the "
88
- "simulation executor.",
89
- )
90
85
 
91
86
  # Install FAB to flwr dir
92
87
  fab_path = install_from_fab(fab_file, None, True)
@@ -111,11 +106,6 @@ class SimulationEngine(Executor):
111
106
  "Config extracted from FAB's pyproject.toml is not valid"
112
107
  )
113
108
 
114
- # Get ClientApp and SeverApp components
115
- flower_components = config["tool"]["flwr"]["components"]
116
- clientapp = flower_components["clientapp"]
117
- serverapp = flower_components["serverapp"]
118
-
119
109
  # In Simulation there is no SuperLink, still we create a run_id
120
110
  run_id = generate_rand_int_from_bytes(RUN_ID_NUM_BYTES)
121
111
  log(INFO, "Created run %s", str(run_id))
@@ -123,21 +113,21 @@ class SimulationEngine(Executor):
123
113
  # Prepare commnand
124
114
  command = [
125
115
  "flower-simulation",
126
- "--client-app",
127
- f"{clientapp}",
128
- "--server-app",
129
- f"{serverapp}",
116
+ "--app",
117
+ f"{str(fab_path)}",
130
118
  "--num-supernodes",
131
119
  f"{self.num_supernodes}",
132
120
  "--run-id",
133
121
  str(run_id),
134
122
  ]
135
123
 
124
+ if override_config:
125
+ command.extend(["--run-config", f"{override_config}"])
126
+
136
127
  # Start Simulation
137
- proc = subprocess.Popen( # pylint: disable=consider-using-with
128
+ proc = subprocess.run( # pylint: disable=consider-using-with
138
129
  command,
139
- stdout=subprocess.PIPE,
140
- stderr=subprocess.PIPE,
130
+ check=True,
141
131
  text=True,
142
132
  )
143
133
 
@@ -145,7 +135,7 @@ class SimulationEngine(Executor):
145
135
 
146
136
  return RunTracker(
147
137
  run_id=run_id,
148
- proc=proc,
138
+ proc=proc, # type:ignore
149
139
  )
150
140
 
151
141
  # pylint: disable-next=broad-except
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: flwr-nightly
3
- Version: 1.10.0.dev20240715
3
+ Version: 1.10.0.dev20240717
4
4
  Summary: Flower: A Friendly Federated Learning Framework
5
5
  Home-page: https://flower.ai
6
6
  License: Apache-2.0
@@ -43,6 +43,7 @@ Requires-Dist: ray (==2.10.0) ; (python_version >= "3.8" and python_version < "3
43
43
  Requires-Dist: requests (>=2.31.0,<3.0.0) ; extra == "rest"
44
44
  Requires-Dist: starlette (>=0.31.0,<0.32.0) ; extra == "rest"
45
45
  Requires-Dist: tomli (>=2.0.1,<3.0.0)
46
+ Requires-Dist: tomli-w (>=1.0.0,<2.0.0)
46
47
  Requires-Dist: typer[all] (>=0.9.0,<0.10.0)
47
48
  Requires-Dist: uvicorn[standard] (>=0.23.0,<0.24.0) ; extra == "rest"
48
49
  Project-URL: Documentation, https://flower.ai