flwr-nightly 1.11.0.dev20240808__py3-none-any.whl → 1.11.0.dev20240809__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/build.py CHANGED
@@ -30,32 +30,34 @@ from .utils import get_sha256_hash, is_valid_project_name
30
30
 
31
31
  # pylint: disable=too-many-locals
32
32
  def build(
33
- directory: Annotated[
33
+ app: Annotated[
34
34
  Optional[Path],
35
- typer.Option(help="Path of the Flower project to bundle into a FAB"),
35
+ typer.Option(help="Path of the Flower App to bundle into a FAB"),
36
36
  ] = None,
37
37
  ) -> str:
38
- """Build a Flower project into a Flower App Bundle (FAB).
38
+ """Build a Flower App into a Flower App Bundle (FAB).
39
39
 
40
- You can run ``flwr build`` without any arguments to bundle the current directory,
41
- or you can use ``--directory`` to build a specific directory:
42
- ``flwr build --directory ./projects/flower-hello-world``.
40
+ You can run ``flwr build`` without any arguments to bundle the app located in the
41
+ current directory. Alternatively, you can you can specify a path using the ``--app``
42
+ option to bundle an app located at the provided path. For example:
43
+
44
+ ``flwr build --app ./apps/flower-hello-world``.
43
45
  """
44
- if directory is None:
45
- directory = Path.cwd()
46
+ if app is None:
47
+ app = Path.cwd()
46
48
 
47
- directory = directory.resolve()
48
- if not directory.is_dir():
49
+ app = app.resolve()
50
+ if not app.is_dir():
49
51
  typer.secho(
50
- f"❌ The path {directory} is not a valid directory.",
52
+ f"❌ The path {app} is not a valid path to a Flower app.",
51
53
  fg=typer.colors.RED,
52
54
  bold=True,
53
55
  )
54
56
  raise typer.Exit(code=1)
55
57
 
56
- if not is_valid_project_name(directory.name):
58
+ if not is_valid_project_name(app.name):
57
59
  typer.secho(
58
- f"❌ The project name {directory.name} is invalid, "
60
+ f"❌ The project name {app.name} is invalid, "
59
61
  "a valid project name must start with a letter or an underscore, "
60
62
  "and can only contain letters, digits, and underscores.",
61
63
  fg=typer.colors.RED,
@@ -63,7 +65,7 @@ def build(
63
65
  )
64
66
  raise typer.Exit(code=1)
65
67
 
66
- conf, errors, warnings = load_and_validate(directory / "pyproject.toml")
68
+ conf, errors, warnings = load_and_validate(app / "pyproject.toml")
67
69
  if conf is None:
68
70
  typer.secho(
69
71
  "Project configuration could not be loaded.\npyproject.toml is invalid:\n"
@@ -82,12 +84,12 @@ def build(
82
84
  )
83
85
 
84
86
  # Load .gitignore rules if present
85
- ignore_spec = _load_gitignore(directory)
87
+ ignore_spec = _load_gitignore(app)
86
88
 
87
89
  # Set the name of the zip file
88
90
  fab_filename = (
89
91
  f"{conf['tool']['flwr']['app']['publisher']}"
90
- f".{directory.name}"
92
+ f".{app.name}"
91
93
  f".{conf['project']['version'].replace('.', '-')}.fab"
92
94
  )
93
95
  list_file_content = ""
@@ -108,7 +110,7 @@ def build(
108
110
  fab_file.writestr("pyproject.toml", toml_contents)
109
111
 
110
112
  # Continue with adding other files
111
- for root, _, files in os.walk(directory, topdown=True):
113
+ for root, _, files in os.walk(app, topdown=True):
112
114
  files = [
113
115
  f
114
116
  for f in files
@@ -120,7 +122,7 @@ def build(
120
122
 
121
123
  for file in files:
122
124
  file_path = Path(root) / file
123
- archive_path = file_path.relative_to(directory)
125
+ archive_path = file_path.relative_to(app)
124
126
  fab_file.write(file_path, archive_path)
125
127
 
126
128
  # Calculate file info
@@ -138,9 +140,9 @@ def build(
138
140
  return fab_filename
139
141
 
140
142
 
141
- def _load_gitignore(directory: Path) -> pathspec.PathSpec:
143
+ def _load_gitignore(app: Path) -> pathspec.PathSpec:
142
144
  """Load and parse .gitignore file, returning a pathspec."""
143
- gitignore_path = directory / ".gitignore"
145
+ gitignore_path = app / ".gitignore"
144
146
  patterns = ["__pycache__/"] # Default pattern
145
147
  if gitignore_path.exists():
146
148
  with open(gitignore_path, encoding="UTF-8") as file:
flwr/cli/new/new.py CHANGED
@@ -92,9 +92,9 @@ def render_and_create(file_path: Path, template: str, context: Dict[str, str]) -
92
92
 
93
93
  # pylint: disable=too-many-locals,too-many-branches,too-many-statements
94
94
  def new(
95
- project_name: Annotated[
95
+ app_name: Annotated[
96
96
  Optional[str],
97
- typer.Argument(metavar="project_name", help="The name of the project"),
97
+ typer.Argument(help="The name of the Flower App"),
98
98
  ] = None,
99
99
  framework: Annotated[
100
100
  Optional[MlFramework],
@@ -105,26 +105,26 @@ def new(
105
105
  typer.Option(case_sensitive=False, help="The Flower username of the author"),
106
106
  ] = None,
107
107
  ) -> None:
108
- """Create new Flower project."""
109
- if project_name is None:
110
- project_name = prompt_text("Please provide the project name")
111
- if not is_valid_project_name(project_name):
112
- project_name = prompt_text(
108
+ """Create new Flower App."""
109
+ if app_name is None:
110
+ app_name = prompt_text("Please provide the app name")
111
+ if not is_valid_project_name(app_name):
112
+ app_name = prompt_text(
113
113
  "Please provide a name that only contains "
114
114
  "characters in {'-', a-zA-Z', '0-9'}",
115
115
  predicate=is_valid_project_name,
116
- default=sanitize_project_name(project_name),
116
+ default=sanitize_project_name(app_name),
117
117
  )
118
118
 
119
119
  # Set project directory path
120
- package_name = re.sub(r"[-_.]+", "-", project_name).lower()
120
+ package_name = re.sub(r"[-_.]+", "-", app_name).lower()
121
121
  import_name = package_name.replace("-", "_")
122
122
  project_dir = Path.cwd() / package_name
123
123
 
124
124
  if project_dir.exists():
125
125
  if not typer.confirm(
126
126
  typer.style(
127
- f"\n💬 {project_name} already exists, do you want to override it?",
127
+ f"\n💬 {app_name} already exists, do you want to override it?",
128
128
  fg=typer.colors.MAGENTA,
129
129
  bold=True,
130
130
  )
@@ -166,7 +166,7 @@ def new(
166
166
 
167
167
  print(
168
168
  typer.style(
169
- f"\n🔨 Creating Flower project {project_name}...",
169
+ f"\n🔨 Creating Flower App {app_name}...",
170
170
  fg=typer.colors.GREEN,
171
171
  bold=True,
172
172
  )
@@ -176,7 +176,7 @@ def new(
176
176
  "framework_str": framework_str_upper,
177
177
  "import_name": import_name.replace("-", "_"),
178
178
  "package_name": package_name,
179
- "project_name": project_name,
179
+ "project_name": app_name,
180
180
  "username": username,
181
181
  }
182
182
 
@@ -268,8 +268,8 @@ def new(
268
268
 
269
269
  print(
270
270
  typer.style(
271
- "🎊 Project creation successful.\n\n"
272
- "Use the following command to run your project:\n",
271
+ "🎊 Flower App creation successful.\n\n"
272
+ "Use the following command to run your Flower App:\n",
273
273
  fg=typer.colors.GREEN,
274
274
  bold=True,
275
275
  )
flwr/cli/run/run.py CHANGED
@@ -35,9 +35,9 @@ from flwr.proto.exec_pb2_grpc import ExecStub
35
35
 
36
36
  # pylint: disable-next=too-many-locals
37
37
  def run(
38
- app_dir: Annotated[
38
+ app: Annotated[
39
39
  Path,
40
- typer.Argument(help="Path of the Flower project to run."),
40
+ typer.Argument(help="Path of the Flower App to run."),
41
41
  ] = Path("."),
42
42
  federation: Annotated[
43
43
  Optional[str],
@@ -55,10 +55,10 @@ def run(
55
55
  ),
56
56
  ] = None,
57
57
  ) -> None:
58
- """Run Flower project."""
58
+ """Run Flower App."""
59
59
  typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
60
60
 
61
- pyproject_path = app_dir / "pyproject.toml" if app_dir else None
61
+ pyproject_path = app / "pyproject.toml" if app else None
62
62
  config, errors, warnings = load_and_validate(path=pyproject_path)
63
63
 
64
64
  if config is None:
@@ -109,14 +109,14 @@ def run(
109
109
  raise typer.Exit(code=1)
110
110
 
111
111
  if "address" in federation_config:
112
- _run_with_superexec(federation_config, app_dir, config_overrides)
112
+ _run_with_superexec(federation_config, app, config_overrides)
113
113
  else:
114
- _run_without_superexec(app_dir, federation_config, federation, config_overrides)
114
+ _run_without_superexec(app, federation_config, federation, config_overrides)
115
115
 
116
116
 
117
117
  def _run_with_superexec(
118
118
  federation_config: Dict[str, Any],
119
- app_dir: Optional[Path],
119
+ app: Optional[Path],
120
120
  config_overrides: Optional[List[str]],
121
121
  ) -> None:
122
122
 
@@ -162,7 +162,7 @@ def _run_with_superexec(
162
162
  channel.subscribe(on_channel_state_change)
163
163
  stub = ExecStub(channel)
164
164
 
165
- fab_path = build(app_dir)
165
+ fab_path = build(app)
166
166
 
167
167
  req = StartRunRequest(
168
168
  fab_file=Path(fab_path).read_bytes(),
@@ -178,7 +178,7 @@ def _run_with_superexec(
178
178
 
179
179
 
180
180
  def _run_without_superexec(
181
- app_path: Optional[Path],
181
+ app: Optional[Path],
182
182
  federation_config: Dict[str, Any],
183
183
  federation: str,
184
184
  config_overrides: Optional[List[str]],
@@ -200,7 +200,7 @@ def _run_without_superexec(
200
200
  command = [
201
201
  "flower-simulation",
202
202
  "--app",
203
- f"{app_path}",
203
+ f"{app}",
204
204
  "--num-supernodes",
205
205
  f"{num_supernodes}",
206
206
  ]
@@ -31,6 +31,7 @@ from flwr.client.client_app import ClientApp, LoadClientAppError
31
31
  from flwr.common import EventType, event
32
32
  from flwr.common.config import (
33
33
  get_flwr_dir,
34
+ get_metadata_from_config,
34
35
  get_project_config,
35
36
  get_project_dir,
36
37
  parse_config_args,
@@ -61,8 +62,8 @@ def run_supernode() -> None:
61
62
 
62
63
  root_certificates = _get_certificates(args)
63
64
  load_fn = _get_load_client_app_fn(
64
- default_app_ref=getattr(args, "client-app"),
65
- project_dir=args.dir,
65
+ default_app_ref="",
66
+ app_path=args.app,
66
67
  flwr_dir=args.flwr_dir,
67
68
  multi_app=True,
68
69
  )
@@ -100,7 +101,7 @@ def run_client_app() -> None:
100
101
  root_certificates = _get_certificates(args)
101
102
  load_fn = _get_load_client_app_fn(
102
103
  default_app_ref=getattr(args, "client-app"),
103
- project_dir=args.dir,
104
+ app_path=args.dir,
104
105
  multi_app=False,
105
106
  )
106
107
  authentication_keys = _try_setup_client_authentication(args)
@@ -176,7 +177,7 @@ def _get_certificates(args: argparse.Namespace) -> Optional[bytes]:
176
177
 
177
178
  def _get_load_client_app_fn(
178
179
  default_app_ref: str,
179
- project_dir: str,
180
+ app_path: Optional[str],
180
181
  multi_app: bool,
181
182
  flwr_dir: Optional[str] = None,
182
183
  ) -> Callable[[str, str], ClientApp]:
@@ -196,34 +197,39 @@ def _get_load_client_app_fn(
196
197
  default_app_ref,
197
198
  )
198
199
 
199
- valid, error_msg = validate(default_app_ref, project_dir=project_dir)
200
+ valid, error_msg = validate(default_app_ref, project_dir=app_path)
200
201
  if not valid and error_msg:
201
202
  raise LoadClientAppError(error_msg) from None
202
203
 
203
204
  def _load(fab_id: str, fab_version: str) -> ClientApp:
204
- runtime_project_dir = Path(project_dir).absolute()
205
+ runtime_app_dir = Path(app_path if app_path else "").absolute()
205
206
  # If multi-app feature is disabled
206
207
  if not multi_app:
207
208
  # Set app reference
208
209
  client_app_ref = default_app_ref
209
- # If multi-app feature is enabled but the fab id is not specified
210
- elif fab_id == "":
211
- if default_app_ref == "":
210
+ # If multi-app feature is enabled but app directory is provided
211
+ elif app_path is not None:
212
+ config = get_project_config(runtime_app_dir)
213
+ this_fab_version, this_fab_id = get_metadata_from_config(config)
214
+
215
+ if this_fab_version != fab_version or this_fab_id != fab_id:
212
216
  raise LoadClientAppError(
213
- "Invalid FAB ID: The FAB ID is empty.",
217
+ f"FAB ID or version mismatch: Expected FAB ID '{this_fab_id}' and "
218
+ f"FAB version '{this_fab_version}', but received FAB ID '{fab_id}' "
219
+ f"and FAB version '{fab_version}'.",
214
220
  ) from None
215
221
 
216
- log(WARN, "FAB ID is not provided; the default ClientApp will be loaded.")
222
+ # log(WARN, "FAB ID is not provided; the default ClientApp will be loaded.")
217
223
 
218
224
  # Set app reference
219
- client_app_ref = default_app_ref
225
+ client_app_ref = config["tool"]["flwr"]["app"]["components"]["clientapp"]
220
226
  # If multi-app feature is enabled
221
227
  else:
222
228
  try:
223
- runtime_project_dir = get_project_dir(
229
+ runtime_app_dir = get_project_dir(
224
230
  fab_id, fab_version, get_flwr_dir(flwr_dir)
225
231
  )
226
- config = get_project_config(runtime_project_dir)
232
+ config = get_project_config(runtime_app_dir)
227
233
  except Exception as e:
228
234
  raise LoadClientAppError("Failed to load ClientApp") from e
229
235
 
@@ -236,7 +242,7 @@ def _get_load_client_app_fn(
236
242
  "Loading ClientApp `%s`",
237
243
  client_app_ref,
238
244
  )
239
- client_app = load_app(client_app_ref, LoadClientAppError, runtime_project_dir)
245
+ client_app = load_app(client_app_ref, LoadClientAppError, runtime_app_dir)
240
246
 
241
247
  if not isinstance(client_app, ClientApp):
242
248
  raise LoadClientAppError(
@@ -255,13 +261,15 @@ def _parse_args_run_supernode() -> argparse.ArgumentParser:
255
261
  )
256
262
 
257
263
  parser.add_argument(
258
- "client-app",
264
+ "app",
259
265
  nargs="?",
260
- default="",
261
- help="For example: `client:app` or `project.package.module:wrapper.app`. "
262
- "This is optional and serves as the default ClientApp to be loaded when "
263
- "the ServerApp does not specify `fab_id` and `fab_version`. "
264
- "If not provided, defaults to an empty string.",
266
+ default=None,
267
+ help="Specify the path of the Flower App to load and run the `ClientApp`. "
268
+ "The `pyproject.toml` file must be located in the root of this path. "
269
+ "When this argument is provided, the SuperNode will exclusively respond to "
270
+ "messages from the corresponding `ServerApp` by matching the FAB ID and FAB "
271
+ "version. An error will be raised if a message is received from any other "
272
+ "`ServerApp`.",
265
273
  )
266
274
  _parse_args_common(parser)
267
275
  parser.add_argument(
@@ -290,6 +298,13 @@ def _parse_args_run_client_app() -> argparse.ArgumentParser:
290
298
  help="For example: `client:app` or `project.package.module:wrapper.app`",
291
299
  )
292
300
  _parse_args_common(parser=parser)
301
+ parser.add_argument(
302
+ "--dir",
303
+ default="",
304
+ help="Add specified directory to the PYTHONPATH and load Flower "
305
+ "app from there."
306
+ " Default: current working directory.",
307
+ )
293
308
 
294
309
  return parser
295
310
 
@@ -357,13 +372,6 @@ def _parse_args_common(parser: argparse.ArgumentParser) -> None:
357
372
  "connect to the SuperLink in case of connection error. By default, it"
358
373
  "is set to None, meaning there is no limit to the total time.",
359
374
  )
360
- parser.add_argument(
361
- "--dir",
362
- default="",
363
- help="Add specified directory to the PYTHONPATH and load Flower "
364
- "app from there."
365
- " Default: current working directory.",
366
- )
367
375
  parser.add_argument(
368
376
  "--auth-supernode-private-key",
369
377
  type=str,
flwr/common/config.py CHANGED
@@ -105,11 +105,16 @@ def get_fused_config(run: Run, flwr_dir: Optional[Path]) -> UserConfig:
105
105
  Get the config using the fab_id and the fab_version, remove the nesting by adding
106
106
  the nested keys as prefixes separated by dots, and fuse it with the override dict.
107
107
  """
108
+ # Return empty dict if fab_id or fab_version is empty
108
109
  if not run.fab_id or not run.fab_version:
109
110
  return {}
110
111
 
111
112
  project_dir = get_project_dir(run.fab_id, run.fab_version, flwr_dir)
112
113
 
114
+ # Return empty dict if project directory does not exist
115
+ if not project_dir.is_dir():
116
+ return {}
117
+
113
118
  return get_fused_config_from_dir(project_dir, run.override_config)
114
119
 
115
120
 
@@ -136,6 +141,23 @@ def flatten_dict(
136
141
  return dict(items)
137
142
 
138
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
+
139
161
  def parse_config_args(
140
162
  config: Optional[List[str]],
141
163
  separator: str = ",",
@@ -161,3 +183,11 @@ def parse_config_args(
161
183
  overrides.update(tomli.loads(toml_str))
162
184
 
163
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
+ )
@@ -24,7 +24,8 @@ from typing import Optional
24
24
  from flwr.common import Context, EventType, RecordSet, event
25
25
  from flwr.common.config import (
26
26
  get_flwr_dir,
27
- get_fused_config,
27
+ get_fused_config_from_dir,
28
+ get_metadata_from_config,
28
29
  get_project_config,
29
30
  get_project_dir,
30
31
  )
@@ -146,51 +147,50 @@ def run_server_app() -> None: # pylint: disable=too-many-branches
146
147
  cert_path,
147
148
  )
148
149
 
149
- server_app_attr: Optional[str] = getattr(args, "server-app")
150
- if not (server_app_attr is None) ^ (args.run_id is None):
150
+ app_path: Optional[str] = args.app
151
+ if not (app_path is None) ^ (args.run_id is None):
151
152
  raise sys.exit(
152
- "Please provide either a ServerApp reference or a Run ID, but not both. "
153
+ "Please provide either a Flower App path or a Run ID, but not both. "
153
154
  "For more details, use: ``flower-server-app -h``"
154
155
  )
155
156
 
156
157
  # Initialize GrpcDriver
157
- if args.run_id is not None:
158
- # User provided `--run-id`, but not `server-app`
158
+ if app_path is None:
159
+ # User provided `--run-id`, but not `app_dir`
159
160
  driver = GrpcDriver(
160
161
  run_id=args.run_id,
161
162
  driver_service_address=args.superlink,
162
163
  root_certificates=root_certificates,
163
164
  )
165
+ flwr_dir = get_flwr_dir(args.flwr_dir)
166
+ run_ = driver.run
167
+ app_path = str(get_project_dir(run_.fab_id, run_.fab_version, flwr_dir))
168
+ config = get_project_config(app_path)
164
169
  else:
165
- # User provided `server-app`, but not `--run-id`
170
+ # User provided `app_dir`, but not `--run-id`
166
171
  # Create run if run_id is not provided
167
172
  driver = GrpcDriver(
168
173
  run_id=0, # Will be overwritten
169
174
  driver_service_address=args.superlink,
170
175
  root_certificates=root_certificates,
171
176
  )
177
+ # Load config from the project directory
178
+ config = get_project_config(app_path)
179
+ fab_version, fab_id = get_metadata_from_config(config)
180
+
172
181
  # Create run
173
- req = CreateRunRequest(fab_id=args.fab_id, fab_version=args.fab_version)
182
+ req = CreateRunRequest(fab_id=fab_id, fab_version=fab_version)
174
183
  res: CreateRunResponse = driver._stub.CreateRun(req) # pylint: disable=W0212
175
184
  # Overwrite driver._run_id
176
185
  driver._run_id = res.run_id # pylint: disable=W0212
177
186
 
178
- server_app_run_config = {}
179
-
180
- # Dynamically obtain ServerApp path based on run_id
181
- if args.run_id is not None:
182
- # User provided `--run-id`, but not `server-app`
183
- flwr_dir = get_flwr_dir(args.flwr_dir)
184
- run_ = driver.run
185
- server_app_dir = str(get_project_dir(run_.fab_id, run_.fab_version, flwr_dir))
186
- config = get_project_config(server_app_dir)
187
- server_app_attr = config["tool"]["flwr"]["app"]["components"]["serverapp"]
188
- server_app_run_config = get_fused_config(run_, flwr_dir)
189
- else:
190
- # User provided `server-app`, but not `--run-id`
191
- server_app_dir = str(Path(args.dir).absolute())
187
+ # Obtain server app reference and the run config
188
+ server_app_attr = config["tool"]["flwr"]["app"]["components"]["serverapp"]
189
+ server_app_run_config = get_fused_config_from_dir(
190
+ Path(app_path), driver.run.override_config
191
+ )
192
192
 
193
- log(DEBUG, "Flower will load ServerApp `%s` in %s", server_app_attr, server_app_dir)
193
+ log(DEBUG, "Flower will load ServerApp `%s` in %s", server_app_attr, app_path)
194
194
 
195
195
  log(
196
196
  DEBUG,
@@ -201,7 +201,7 @@ def run_server_app() -> None: # pylint: disable=too-many-branches
201
201
  # Run the ServerApp with the Driver
202
202
  run(
203
203
  driver=driver,
204
- server_app_dir=server_app_dir,
204
+ server_app_dir=app_path,
205
205
  server_app_run_config=server_app_run_config,
206
206
  server_app_attr=server_app_attr,
207
207
  )
@@ -219,15 +219,16 @@ def _parse_args_run_server_app() -> argparse.ArgumentParser:
219
219
  )
220
220
 
221
221
  parser.add_argument(
222
- "server-app",
222
+ "app",
223
223
  nargs="?",
224
224
  default=None,
225
- help="For example: `server:app` or `project.package.module:wrapper.app`",
225
+ help="Load and run the `ServerApp` from the specified Flower App path. "
226
+ "The `pyproject.toml` file must be located in the root of this path.",
226
227
  )
227
228
  parser.add_argument(
228
229
  "--insecure",
229
230
  action="store_true",
230
- help="Run the server app without HTTPS. By default, the app runs with "
231
+ help="Run the `ServerApp` without HTTPS. By default, the app runs with "
231
232
  "HTTPS enabled. Use this flag only if you understand the risks.",
232
233
  )
233
234
  parser.add_argument(
@@ -252,25 +253,6 @@ def _parse_args_run_server_app() -> argparse.ArgumentParser:
252
253
  default=ADDRESS_DRIVER_API,
253
254
  help="SuperLink Driver API (gRPC-rere) address (IPv4, IPv6, or a domain name)",
254
255
  )
255
- parser.add_argument(
256
- "--dir",
257
- default="",
258
- help="Add specified directory to the PYTHONPATH and load Flower "
259
- "app from there."
260
- " Default: current working directory.",
261
- )
262
- parser.add_argument(
263
- "--fab-id",
264
- default=None,
265
- type=str,
266
- help="The identifier of the FAB used in the run.",
267
- )
268
- parser.add_argument(
269
- "--fab-version",
270
- default=None,
271
- type=str,
272
- help="The version of the FAB used in the run.",
273
- )
274
256
  parser.add_argument(
275
257
  "--run-id",
276
258
  default=None,
@@ -347,9 +347,9 @@ def start_vce(
347
347
  if client_app_attr:
348
348
  app = _get_load_client_app_fn(
349
349
  default_app_ref=client_app_attr,
350
- project_dir=app_dir,
350
+ app_path=app_dir,
351
351
  flwr_dir=flwr_dir,
352
- multi_app=True,
352
+ multi_app=False,
353
353
  )(run.fab_id, run.fab_version)
354
354
 
355
355
  if client_app:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: flwr-nightly
3
- Version: 1.11.0.dev20240808
3
+ Version: 1.11.0.dev20240809
4
4
  Summary: Flower: A Friendly Federated Learning Framework
5
5
  Home-page: https://flower.ai
6
6
  License: Apache-2.0
@@ -1,12 +1,12 @@
1
1
  flwr/__init__.py,sha256=VmBWedrCxqmt4QvUHBLqyVEH6p7zaFMD_oCHerXHSVw,937
2
2
  flwr/cli/__init__.py,sha256=cZJVgozlkC6Ni2Hd_FAIrqefrkCGOV18fikToq-6iLw,720
3
3
  flwr/cli/app.py,sha256=FBcSrE35ll88VE11ib67qgsJe2GYDN25UswV9-cYcX8,1267
4
- flwr/cli/build.py,sha256=5igi2013fLH-TlR6MNpbxNEMaVqdBbts-E-WdY3JPsE,5167
4
+ flwr/cli/build.py,sha256=gIR-nTgmLJY5ZtJFLN5ebFBCN3_hoqaioFT77AHojNU,5159
5
5
  flwr/cli/config_utils.py,sha256=WK6ywT-mHt2iMG90bspkSGMewv8jXh7yQPVdcPuT2JE,7540
6
6
  flwr/cli/example.py,sha256=1bGDYll3BXQY2kRqSN-oICqS5n1b9m0g0RvXTopXHl4,2215
7
7
  flwr/cli/install.py,sha256=AI6Zv2dQVDHpLDX1Z_vX5XHVxmZo1OU3ndCSrD2stzQ,7059
8
8
  flwr/cli/new/__init__.py,sha256=cQzK1WH4JP2awef1t2UQ2xjl1agVEz9rwutV18SWV1k,789
9
- flwr/cli/new/new.py,sha256=kwP-hUD8Qz2BMoZaELhZh0Q0bEL1PM-dRfrzoVYJ_lc,9679
9
+ flwr/cli/new/new.py,sha256=VNb31-NLedm-_OK_D0aed0QxHO-tVlXjnf9UWVhC_Jk,9612
10
10
  flwr/cli/new/templates/__init__.py,sha256=4luU8RL-CK8JJCstQ_ON809W9bNTkY1l9zSaPKBkgwY,725
11
11
  flwr/cli/new/templates/app/.gitignore.tpl,sha256=XixnHdyeMB2vwkGtGnwHqoWpH-9WChdyG0GXe57duhc,3078
12
12
  flwr/cli/new/templates/app/README.flowertune.md.tpl,sha256=PqzkGm0g6Zy-vZK9_0EO3f_U6g1r69lGc4UL8kds5Q8,2696
@@ -50,7 +50,7 @@ flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl,sha256=vIO1ArukTC76ogYLNmJ
50
50
  flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl,sha256=jk_5teoyOVM9QdBea8J-nk10S6TKw81QZiiKB54ATF0,654
51
51
  flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl,sha256=bRIvPCPvTTI4Eo5b61Rmw8WdDw3sjcohciTXgULN5l8,702
52
52
  flwr/cli/run/__init__.py,sha256=oCd6HmQDx-sqver1gecgx-uMA38BLTSiiKpl7RGNceg,789
53
- flwr/cli/run/run.py,sha256=s6I2EU6dmjJHPJ_LNdjIBe9UZlCwvf5RA5O61nAgO3g,7483
53
+ flwr/cli/run/run.py,sha256=sX0VOl0o7vla1sACCwpWqP9-mlwKGiyR4-NC31ykEd4,7437
54
54
  flwr/cli/utils.py,sha256=l65Ul0YsSBPuypk0uorAtEDmLEYiUrzpCXi6zCg9mJ4,4506
55
55
  flwr/client/__init__.py,sha256=wzJZsYJIHf_8-PMzvfbinyzzjgh1UP1vLrAw2_yEbKI,1345
56
56
  flwr/client/app.py,sha256=VJ_vPMVfur5_SqUjHa18VWLB6I9kOav78f5myC_iWuk,26110
@@ -83,11 +83,11 @@ flwr/client/numpy_client.py,sha256=u76GWAdHmJM88Agm2EgLQSvO8Jnk225mJTk-_TmPjFE,1
83
83
  flwr/client/rest_client/__init__.py,sha256=5KGlp7pjc1dhNRkKlaNtUfQmg8wrRFh9lS3P3uRS-7Q,735
84
84
  flwr/client/rest_client/connection.py,sha256=8LPk7zPvX3l3-5QQXNym8DkIe6V14uEHmTzQ8jCcsnQ,12198
85
85
  flwr/client/supernode/__init__.py,sha256=SUhWOzcgXRNXk1V9UgB5-FaWukqqrOEajVUHEcPkwyQ,865
86
- flwr/client/supernode/app.py,sha256=GI2jnttoUt31v39Q-F2dS-WZuesG7IhGXfhP0oylaUU,14694
86
+ flwr/client/supernode/app.py,sha256=A_0VKCoNREjLPdzngJYPl2Pt5ZKWxbmdGgsJqWqzzy4,15236
87
87
  flwr/client/typing.py,sha256=dxoTBnTMfqXr5J7G3y-uNjqxYCddvxhu89spfj4Lm2U,1048
88
88
  flwr/common/__init__.py,sha256=4cBLNNnNTwHDnL_HCxhU5ILCSZ6fYh3A_aMBtlvHTVw,3721
89
89
  flwr/common/address.py,sha256=wRu1Luezx1PWadwV9OA_KNko01oVvbRnPqfzaDn8QOk,1882
90
- flwr/common/config.py,sha256=IN3UVfC4h-ItpbAOhJXNsVA63ZOJ05twsmIv3AbSU04,5599
90
+ flwr/common/config.py,sha256=soJEX0bo3gcKpYlZBzu4NGiNVmwF0tEvqSnwWQsu3pw,6548
91
91
  flwr/common/constant.py,sha256=1XxuRezsr9fl3xvQNPR2kyFkwNeG_f5vZayv0PFh0kY,3012
92
92
  flwr/common/context.py,sha256=5Bd9RCrhLkYZOVR7vr97OVhzVBHQkS1fUsYiIKTwpxU,2239
93
93
  flwr/common/date.py,sha256=OcQuwpb2HxcblTqYm6H223ufop5UZw5N_fzalbpOVzY,891
@@ -187,7 +187,7 @@ flwr/server/driver/driver.py,sha256=NT_yaeit7_kZEIsCEqOWPID1GrVD3ywH4xZ2wtIh5lM,
187
187
  flwr/server/driver/grpc_driver.py,sha256=4LMLDXjMU1VdHsj9nyqFIF71GWVsUR85fsO6biWMHRU,9710
188
188
  flwr/server/driver/inmemory_driver.py,sha256=RcK94_NtjGZ4aZDIscnU7A3Uv1u8jGx29-xcbjQvZTM,6444
189
189
  flwr/server/history.py,sha256=bBOHKyX1eQONIsUx4EUU-UnAk1i0EbEl8ioyMq_UWQ8,5063
190
- flwr/server/run_serverapp.py,sha256=khrB9bQ42p8DMRJLGoY_pQJ5bkpXyBPL0eyPdZJo29Y,9430
190
+ flwr/server/run_serverapp.py,sha256=8HCGSVaNYd2cnd_j-JpVTRH7Cg8_U7ATZUejiMmJe0o,8894
191
191
  flwr/server/server.py,sha256=wsXsxMZ9SQ0B42nBnUlcV83NJPycgrgg5bFwcQ4BYBE,17821
192
192
  flwr/server/server_app.py,sha256=1hul76ospG8L_KooK_ewn1sWPNTNYLTtZMeGNOBNruA,6267
193
193
  flwr/server/server_config.py,sha256=CZaHVAsMvGLjpWVcLPkiYxgJN4xfIyAiUrCI3fETKY4,1349
@@ -239,7 +239,7 @@ flwr/server/superlink/fleet/vce/__init__.py,sha256=36MHKiefnJeyjwMQzVUK4m06Ojon3
239
239
  flwr/server/superlink/fleet/vce/backend/__init__.py,sha256=psQjI1DQHOM5uWVXM27l_wPHjfEkMUTP-_8-lEFH1JA,1466
240
240
  flwr/server/superlink/fleet/vce/backend/backend.py,sha256=KL0eHScWr_YfP2eY3VP8_OOMgZwnRNW7qpu5J-ISpXI,2212
241
241
  flwr/server/superlink/fleet/vce/backend/raybackend.py,sha256=6-CjUTv1cFnAb53cZzEYCuVLrVu60tPczTo8meqeqbk,6289
242
- flwr/server/superlink/fleet/vce/vce_api.py,sha256=4vBqC0nCMWwxU1TMiWzA8Q-hwQkVAPGsuhbjhAr1D68,12752
242
+ flwr/server/superlink/fleet/vce/vce_api.py,sha256=AeJBEZIC3V54Wg4bzBJa0aO4IXUNiRzRDRv-EKhqtpQ,12750
243
243
  flwr/server/superlink/state/__init__.py,sha256=Gj2OTFLXvA-mAjBvwuKDM3rDrVaQPcIoybSa2uskMTE,1003
244
244
  flwr/server/superlink/state/in_memory_state.py,sha256=LnKlnXe9JjVHb5_XOo6eD1RQhlCvJVKgz_CkXrMz8DY,13069
245
245
  flwr/server/superlink/state/sqlite_state.py,sha256=LdLnHtF8C-1L1IAglfZPqIuKa782Qo7qAYzTXMdMYGM,29052
@@ -270,8 +270,8 @@ flwr/superexec/exec_grpc.py,sha256=PhqGoZEpTMxSQmUSV8Wgtzb1Za_pHJ-adZqo5RYnDyE,1
270
270
  flwr/superexec/exec_servicer.py,sha256=fxQAKfgmQRSnYq5anjryfGeRbsZrNFEkuiNcTZhRwiE,2320
271
271
  flwr/superexec/executor.py,sha256=k_adivto6R2U82DADOHNvdtobehBYreRek1gOEBIQnQ,2318
272
272
  flwr/superexec/simulation.py,sha256=lfdClQYSAIMHe43aJ0Pk-kBw_xoV09LsIMfHo2eo-Ck,6775
273
- flwr_nightly-1.11.0.dev20240808.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
274
- flwr_nightly-1.11.0.dev20240808.dist-info/METADATA,sha256=zEgan9rkbxuqkDqsv1v-MUhLc8m3e9JwpWPYnmYhxeY,15672
275
- flwr_nightly-1.11.0.dev20240808.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
276
- flwr_nightly-1.11.0.dev20240808.dist-info/entry_points.txt,sha256=7qBQcA-bDGDxnJmLd9FYqglFQubjCNqyg9M8a-lukps,336
277
- flwr_nightly-1.11.0.dev20240808.dist-info/RECORD,,
273
+ flwr_nightly-1.11.0.dev20240809.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
274
+ flwr_nightly-1.11.0.dev20240809.dist-info/METADATA,sha256=hP1NgTaAzjSy3mEzErvuqxvQgAHnrZBWqc6T8bsuR7w,15672
275
+ flwr_nightly-1.11.0.dev20240809.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
276
+ flwr_nightly-1.11.0.dev20240809.dist-info/entry_points.txt,sha256=7qBQcA-bDGDxnJmLd9FYqglFQubjCNqyg9M8a-lukps,336
277
+ flwr_nightly-1.11.0.dev20240809.dist-info/RECORD,,