flwr-nightly 1.12.0.dev20241008__py3-none-any.whl → 1.12.0.dev20241010__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
@@ -14,26 +14,50 @@
14
14
  # ==============================================================================
15
15
  """Flower command line interface `build` command."""
16
16
 
17
+ import hashlib
17
18
  import os
19
+ import shutil
20
+ import tempfile
18
21
  import zipfile
19
22
  from pathlib import Path
20
- from typing import Annotated, Optional
23
+ from typing import Annotated, Any, Optional, Union
21
24
 
22
25
  import pathspec
23
26
  import tomli_w
24
27
  import typer
25
28
 
29
+ from flwr.common.constant import FAB_ALLOWED_EXTENSIONS, FAB_DATE, FAB_HASH_TRUNCATION
30
+
26
31
  from .config_utils import load_and_validate
27
- from .utils import get_sha256_hash, is_valid_project_name
32
+ from .utils import is_valid_project_name
33
+
34
+
35
+ def write_to_zip(
36
+ zipfile_obj: zipfile.ZipFile, filename: str, contents: Union[bytes, str]
37
+ ) -> zipfile.ZipFile:
38
+ """Set a fixed date and write contents to a zip file."""
39
+ zip_info = zipfile.ZipInfo(filename)
40
+ zip_info.date_time = FAB_DATE
41
+ zipfile_obj.writestr(zip_info, contents)
42
+ return zipfile_obj
43
+
28
44
 
45
+ def get_fab_filename(conf: dict[str, Any], fab_hash: str) -> str:
46
+ """Get the FAB filename based on the given config and FAB hash."""
47
+ publisher = conf["tool"]["flwr"]["app"]["publisher"]
48
+ name = conf["project"]["name"]
49
+ version = conf["project"]["version"].replace(".", "-")
50
+ fab_hash_truncated = fab_hash[:FAB_HASH_TRUNCATION]
51
+ return f"{publisher}.{name}.{version}.{fab_hash_truncated}.fab"
29
52
 
30
- # pylint: disable=too-many-locals
53
+
54
+ # pylint: disable=too-many-locals, too-many-statements
31
55
  def build(
32
56
  app: Annotated[
33
57
  Optional[Path],
34
58
  typer.Option(help="Path of the Flower App to bundle into a FAB"),
35
59
  ] = None,
36
- ) -> str:
60
+ ) -> tuple[str, str]:
37
61
  """Build a Flower App into a Flower App Bundle (FAB).
38
62
 
39
63
  You can run ``flwr build`` without any arguments to bundle the app located in the
@@ -85,16 +109,8 @@ def build(
85
109
  # Load .gitignore rules if present
86
110
  ignore_spec = _load_gitignore(app)
87
111
 
88
- # Set the name of the zip file
89
- fab_filename = (
90
- f"{conf['tool']['flwr']['app']['publisher']}"
91
- f".{conf['project']['name']}"
92
- f".{conf['project']['version'].replace('.', '-')}.fab"
93
- )
94
112
  list_file_content = ""
95
113
 
96
- allowed_extensions = {".py", ".toml", ".md"}
97
-
98
114
  # Remove the 'federations' field from 'tool.flwr' if it exists
99
115
  if (
100
116
  "tool" in conf
@@ -105,38 +121,53 @@ def build(
105
121
 
106
122
  toml_contents = tomli_w.dumps(conf)
107
123
 
108
- with zipfile.ZipFile(fab_filename, "w", zipfile.ZIP_DEFLATED) as fab_file:
109
- fab_file.writestr("pyproject.toml", toml_contents)
124
+ with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as temp_file:
125
+ temp_filename = temp_file.name
126
+
127
+ with zipfile.ZipFile(temp_filename, "w", zipfile.ZIP_DEFLATED) as fab_file:
128
+ write_to_zip(fab_file, "pyproject.toml", toml_contents)
110
129
 
111
- # Continue with adding other files
112
- for root, _, files in os.walk(app, topdown=True):
113
- files = [
130
+ # Continue with adding other files
131
+ all_files = [
114
132
  f
115
- for f in files
116
- if not ignore_spec.match_file(Path(root) / f)
117
- and f != fab_filename
118
- and Path(f).suffix in allowed_extensions
119
- and f != "pyproject.toml" # Exclude the original pyproject.toml
133
+ for f in app.rglob("*")
134
+ if not ignore_spec.match_file(f)
135
+ and f.name != temp_filename
136
+ and f.suffix in FAB_ALLOWED_EXTENSIONS
137
+ and f.name != "pyproject.toml" # Exclude the original pyproject.toml
120
138
  ]
121
139
 
122
- for file in files:
123
- file_path = Path(root) / file
140
+ for file_path in all_files:
141
+ # Read the file content manually
142
+ with open(file_path, "rb") as f:
143
+ file_contents = f.read()
144
+
124
145
  archive_path = file_path.relative_to(app)
125
- fab_file.write(file_path, archive_path)
146
+ write_to_zip(fab_file, str(archive_path), file_contents)
126
147
 
127
148
  # Calculate file info
128
- sha256_hash = get_sha256_hash(file_path)
149
+ sha256_hash = hashlib.sha256(file_contents).hexdigest()
129
150
  file_size_bits = os.path.getsize(file_path) * 8 # size in bits
130
151
  list_file_content += f"{archive_path},{sha256_hash},{file_size_bits}\n"
131
152
 
132
- # Add CONTENT and CONTENT.jwt to the zip file
133
- fab_file.writestr(".info/CONTENT", list_file_content)
153
+ # Add CONTENT and CONTENT.jwt to the zip file
154
+ write_to_zip(fab_file, ".info/CONTENT", list_file_content)
155
+
156
+ # Get hash of FAB file
157
+ content = Path(temp_filename).read_bytes()
158
+ fab_hash = hashlib.sha256(content).hexdigest()
159
+
160
+ # Set the name of the zip file
161
+ fab_filename = get_fab_filename(conf, fab_hash)
162
+
163
+ # Once the temporary zip file is created, rename it to the final filename
164
+ shutil.move(temp_filename, fab_filename)
134
165
 
135
166
  typer.secho(
136
167
  f"🎊 Successfully built {fab_filename}", fg=typer.colors.GREEN, bold=True
137
168
  )
138
169
 
139
- return fab_filename
170
+ return fab_filename, fab_hash
140
171
 
141
172
 
142
173
  def _load_gitignore(app: Path) -> pathspec.PathSpec:
flwr/cli/config_utils.py CHANGED
@@ -90,6 +90,16 @@ def load_and_validate(
90
90
  ) -> tuple[Optional[dict[str, Any]], list[str], list[str]]:
91
91
  """Load and validate pyproject.toml as dict.
92
92
 
93
+ Parameters
94
+ ----------
95
+ path : Optional[Path] (default: None)
96
+ The path of the Flower App config file to load. By default it
97
+ will try to use `pyproject.toml` inside the current directory.
98
+ check_module: bool (default: True)
99
+ Whether the validity of the Python module should be checked.
100
+ This requires the project to be installed in the currently
101
+ running environment. True by default.
102
+
93
103
  Returns
94
104
  -------
95
105
  Tuple[Optional[config], List[str], List[str]]
flwr/cli/install.py CHANGED
@@ -14,7 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower command line interface `install` command."""
16
16
 
17
-
17
+ import hashlib
18
18
  import shutil
19
19
  import subprocess
20
20
  import tempfile
@@ -25,7 +25,8 @@ from typing import IO, Annotated, Optional, Union
25
25
 
26
26
  import typer
27
27
 
28
- from flwr.common.config import get_flwr_dir
28
+ from flwr.common.config import get_flwr_dir, get_metadata_from_config
29
+ from flwr.common.constant import FAB_HASH_TRUNCATION
29
30
 
30
31
  from .config_utils import load_and_validate
31
32
  from .utils import get_sha256_hash
@@ -91,9 +92,11 @@ def install_from_fab(
91
92
  fab_name: Optional[str]
92
93
  if isinstance(fab_file, bytes):
93
94
  fab_file_archive = BytesIO(fab_file)
95
+ fab_hash = hashlib.sha256(fab_file).hexdigest()
94
96
  fab_name = None
95
97
  elif isinstance(fab_file, Path):
96
98
  fab_file_archive = fab_file
99
+ fab_hash = hashlib.sha256(fab_file.read_bytes()).hexdigest()
97
100
  fab_name = fab_file.stem
98
101
  else:
99
102
  raise ValueError("fab_file must be either a Path or bytes")
@@ -126,14 +129,16 @@ def install_from_fab(
126
129
  shutil.rmtree(info_dir)
127
130
 
128
131
  installed_path = validate_and_install(
129
- tmpdir_path, fab_name, flwr_dir, skip_prompt
132
+ tmpdir_path, fab_hash, fab_name, flwr_dir, skip_prompt
130
133
  )
131
134
 
132
135
  return installed_path
133
136
 
134
137
 
138
+ # pylint: disable=too-many-locals
135
139
  def validate_and_install(
136
140
  project_dir: Path,
141
+ fab_hash: str,
137
142
  fab_name: Optional[str],
138
143
  flwr_dir: Optional[Path],
139
144
  skip_prompt: bool = False,
@@ -149,28 +154,17 @@ def validate_and_install(
149
154
  )
150
155
  raise typer.Exit(code=1)
151
156
 
152
- publisher = config["tool"]["flwr"]["app"]["publisher"]
153
- project_name = config["project"]["name"]
154
- version = config["project"]["version"]
157
+ version, fab_id = get_metadata_from_config(config)
158
+ publisher, project_name = fab_id.split("/")
159
+ config_metadata = (publisher, project_name, version, fab_hash)
155
160
 
156
- if (
157
- fab_name
158
- and fab_name != f"{publisher}.{project_name}.{version.replace('.', '-')}"
159
- ):
160
- typer.secho(
161
- "❌ FAB file has incorrect name. The file name must follow the format "
162
- "`<publisher>.<project_name>.<version>.fab`.",
163
- fg=typer.colors.RED,
164
- bold=True,
165
- )
166
- raise typer.Exit(code=1)
161
+ if fab_name:
162
+ _validate_fab_and_config_metadata(fab_name, config_metadata)
167
163
 
168
164
  install_dir: Path = (
169
165
  (get_flwr_dir() if not flwr_dir else flwr_dir)
170
166
  / "apps"
171
- / publisher
172
- / project_name
173
- / version
167
+ / f"{publisher}.{project_name}.{version}.{fab_hash[:FAB_HASH_TRUNCATION]}"
174
168
  )
175
169
  if install_dir.exists():
176
170
  if skip_prompt:
@@ -226,3 +220,49 @@ def _verify_hashes(list_content: str, tmpdir: Path) -> bool:
226
220
  if not file_path.exists() or get_sha256_hash(file_path) != hash_expected:
227
221
  return False
228
222
  return True
223
+
224
+
225
+ def _validate_fab_and_config_metadata(
226
+ fab_name: str, config_metadata: tuple[str, str, str, str]
227
+ ) -> None:
228
+ """Validate metadata from the FAB filename and config."""
229
+ publisher, project_name, version, fab_hash = config_metadata
230
+
231
+ fab_name = fab_name.removesuffix(".fab")
232
+
233
+ fab_publisher, fab_project_name, fab_version, fab_shorthash = fab_name.split(".")
234
+ fab_version = fab_version.replace("-", ".")
235
+
236
+ # Check FAB filename format
237
+ if (
238
+ f"{fab_publisher}.{fab_project_name}.{fab_version}"
239
+ != f"{publisher}.{project_name}.{version}"
240
+ or len(fab_shorthash) != FAB_HASH_TRUNCATION # Verify hash length
241
+ ):
242
+ typer.secho(
243
+ "❌ FAB file has incorrect name. The file name must follow the format "
244
+ "`<publisher>.<project_name>.<version>.<8hexchars>.fab`.",
245
+ fg=typer.colors.RED,
246
+ bold=True,
247
+ )
248
+ raise typer.Exit(code=1)
249
+
250
+ # Verify hash is a valid hexadecimal
251
+ try:
252
+ _ = int(fab_shorthash, 16)
253
+ except Exception as e:
254
+ typer.secho(
255
+ f"❌ FAB file has an invalid hexadecimal string `{fab_shorthash}`.",
256
+ fg=typer.colors.RED,
257
+ bold=True,
258
+ )
259
+ raise typer.Exit(code=1) from e
260
+
261
+ # Verify shorthash matches
262
+ if fab_shorthash != fab_hash[:FAB_HASH_TRUNCATION]:
263
+ typer.secho(
264
+ "❌ The hash in the FAB file name does not match the hash of the FAB.",
265
+ fg=typer.colors.RED,
266
+ bold=True,
267
+ )
268
+ raise typer.Exit(code=1)
@@ -14,7 +14,7 @@ dependencies = [
14
14
  "bitsandbytes==0.43.0",
15
15
  "scipy==1.13.0",
16
16
  "peft==0.6.2",
17
- "transformers==4.39.3",
17
+ "transformers==4.43.1",
18
18
  "sentencepiece==0.2.0",
19
19
  "omegaconf==2.3.0",
20
20
  "hf_transfer==0.1.8",
flwr/cli/run/run.py CHANGED
@@ -14,7 +14,6 @@
14
14
  # ==============================================================================
15
15
  """Flower command line interface `run` command."""
16
16
 
17
- import hashlib
18
17
  import json
19
18
  import subprocess
20
19
  import sys
@@ -134,6 +133,7 @@ def run(
134
133
  _run_without_superexec(app, federation_config, config_overrides, federation)
135
134
 
136
135
 
136
+ # pylint: disable=too-many-locals
137
137
  def _run_with_superexec(
138
138
  app: Path,
139
139
  federation_config: dict[str, Any],
@@ -179,9 +179,9 @@ def _run_with_superexec(
179
179
  channel.subscribe(on_channel_state_change)
180
180
  stub = ExecStub(channel)
181
181
 
182
- fab_path = Path(build(app))
183
- content = fab_path.read_bytes()
184
- fab = Fab(hashlib.sha256(content).hexdigest(), content)
182
+ fab_path, fab_hash = build(app)
183
+ content = Path(fab_path).read_bytes()
184
+ fab = Fab(fab_hash, content)
185
185
 
186
186
  req = StartRunRequest(
187
187
  fab=fab_to_proto(fab),
@@ -193,7 +193,7 @@ def _run_with_superexec(
193
193
  res = stub.StartRun(req)
194
194
 
195
195
  # Delete FAB file once it has been sent to the SuperExec
196
- fab_path.unlink()
196
+ Path(fab_path).unlink()
197
197
  typer.secho(f"🎊 Successfully started run {res.run_id}", fg=typer.colors.GREEN)
198
198
 
199
199
  if stream:
flwr/client/app.py CHANGED
@@ -132,6 +132,11 @@ def start_client(
132
132
  - 'grpc-bidi': gRPC, bidirectional streaming
133
133
  - 'grpc-rere': gRPC, request-response (experimental)
134
134
  - 'rest': HTTP (experimental)
135
+ authentication_keys : Optional[Tuple[PrivateKey, PublicKey]] (default: None)
136
+ Tuple containing the elliptic curve private key and public key for
137
+ authentication from the cryptography library.
138
+ Source: https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec/
139
+ Used to establish an authenticated connection with the server.
135
140
  max_retries: Optional[int] (default: None)
136
141
  The maximum number of times the client will try to connect to the
137
142
  server before giving up in case of a connection error. If set to None,
@@ -197,7 +202,7 @@ def start_client_internal(
197
202
  *,
198
203
  server_address: str,
199
204
  node_config: UserConfig,
200
- load_client_app_fn: Optional[Callable[[str, str], ClientApp]] = None,
205
+ load_client_app_fn: Optional[Callable[[str, str, str], ClientApp]] = None,
201
206
  client_fn: Optional[ClientFnExt] = None,
202
207
  client: Optional[Client] = None,
203
208
  grpc_max_message_length: int = GRPC_MAX_MESSAGE_LENGTH,
@@ -249,6 +254,11 @@ def start_client_internal(
249
254
  - 'grpc-bidi': gRPC, bidirectional streaming
250
255
  - 'grpc-rere': gRPC, request-response (experimental)
251
256
  - 'rest': HTTP (experimental)
257
+ authentication_keys : Optional[Tuple[PrivateKey, PublicKey]] (default: None)
258
+ Tuple containing the elliptic curve private key and public key for
259
+ authentication from the cryptography library.
260
+ Source: https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec/
261
+ Used to establish an authenticated connection with the server.
252
262
  max_retries: Optional[int] (default: None)
253
263
  The maximum number of times the client will try to connect to the
254
264
  server before giving up in case of a connection error. If set to None,
@@ -288,7 +298,7 @@ def start_client_internal(
288
298
 
289
299
  client_fn = single_client_factory
290
300
 
291
- def _load_client_app(_1: str, _2: str) -> ClientApp:
301
+ def _load_client_app(_1: str, _2: str, _3: str) -> ClientApp:
292
302
  return ClientApp(client_fn=client_fn)
293
303
 
294
304
  load_client_app_fn = _load_client_app
@@ -519,7 +529,7 @@ def start_client_internal(
519
529
  else:
520
530
  # Load ClientApp instance
521
531
  client_app: ClientApp = load_client_app_fn(
522
- fab_id, fab_version
532
+ fab_id, fab_version, run.fab_hash
523
533
  )
524
534
 
525
535
  # Execute ClientApp
@@ -132,8 +132,11 @@ def run_clientapp( # pylint: disable=R0914
132
132
  )
133
133
 
134
134
  try:
135
- # Load ClientApp
136
- client_app: ClientApp = load_client_app_fn(run.fab_id, run.fab_version)
135
+ if fab:
136
+ # Load ClientApp
137
+ client_app: ClientApp = load_client_app_fn(
138
+ run.fab_id, run.fab_version, fab.hash_str
139
+ )
137
140
 
138
141
  # Execute ClientApp
139
142
  reply_message = client_app(message=message, context=context)
@@ -34,7 +34,7 @@ def get_load_client_app_fn(
34
34
  app_path: Optional[str],
35
35
  multi_app: bool,
36
36
  flwr_dir: Optional[str] = None,
37
- ) -> Callable[[str, str], ClientApp]:
37
+ ) -> Callable[[str, str, str], ClientApp]:
38
38
  """Get the load_client_app_fn function.
39
39
 
40
40
  If `multi_app` is True, this function loads the specified ClientApp
@@ -55,13 +55,14 @@ def get_load_client_app_fn(
55
55
  if not valid and error_msg:
56
56
  raise LoadClientAppError(error_msg) from None
57
57
 
58
- def _load(fab_id: str, fab_version: str) -> ClientApp:
58
+ def _load(fab_id: str, fab_version: str, fab_hash: str) -> ClientApp:
59
59
  runtime_app_dir = Path(app_path if app_path else "").absolute()
60
60
  # If multi-app feature is disabled
61
61
  if not multi_app:
62
62
  # Set app reference
63
63
  client_app_ref = default_app_ref
64
- # If multi-app feature is enabled but app directory is provided
64
+ # If multi-app feature is enabled but app directory is provided.
65
+ # `fab_hash` is not required since the app is loaded from `runtime_app_dir`.
65
66
  elif app_path is not None:
66
67
  config = get_project_config(runtime_app_dir)
67
68
  this_fab_version, this_fab_id = get_metadata_from_config(config)
@@ -81,11 +82,16 @@ def get_load_client_app_fn(
81
82
  else:
82
83
  try:
83
84
  runtime_app_dir = get_project_dir(
84
- fab_id, fab_version, get_flwr_dir(flwr_dir)
85
+ fab_id, fab_version, fab_hash, get_flwr_dir(flwr_dir)
85
86
  )
86
87
  config = get_project_config(runtime_app_dir)
87
88
  except Exception as e:
88
- raise LoadClientAppError("Failed to load ClientApp") from e
89
+ raise LoadClientAppError(
90
+ "Failed to load ClientApp."
91
+ "Possible reasons for error include mismatched "
92
+ "`fab_id`, `fab_version`, or `fab_hash` in "
93
+ f"{str(get_flwr_dir(flwr_dir).resolve())}."
94
+ ) from e
89
95
 
90
96
  # Set app reference
91
97
  client_app_ref = config["tool"]["flwr"]["app"]["components"]["clientapp"]
@@ -120,6 +120,9 @@ def grpc_request_response( # pylint: disable=R0913, R0914, R0915
120
120
  authentication from the cryptography library.
121
121
  Source: https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec/
122
122
  Used to establish an authenticated connection with the server.
123
+ adapter_cls: Optional[Union[type[FleetStub], type[GrpcAdapter]]] (default: None)
124
+ A GrpcStub Class that can be used to send messages. By default the FleetStub
125
+ will be used.
123
126
 
124
127
  Returns
125
128
  -------
flwr/common/config.py CHANGED
@@ -22,7 +22,12 @@ from typing import Any, Optional, Union, cast, get_args
22
22
  import tomli
23
23
 
24
24
  from flwr.cli.config_utils import get_fab_config, validate_fields
25
- from flwr.common.constant import APP_DIR, FAB_CONFIG_FILE, FLWR_HOME
25
+ from flwr.common.constant import (
26
+ APP_DIR,
27
+ FAB_CONFIG_FILE,
28
+ FAB_HASH_TRUNCATION,
29
+ FLWR_HOME,
30
+ )
26
31
  from flwr.common.typing import Run, UserConfig, UserConfigValue
27
32
 
28
33
 
@@ -39,7 +44,10 @@ def get_flwr_dir(provided_path: Optional[str] = None) -> Path:
39
44
 
40
45
 
41
46
  def get_project_dir(
42
- fab_id: str, fab_version: str, flwr_dir: Optional[Union[str, Path]] = None
47
+ fab_id: str,
48
+ fab_version: str,
49
+ fab_hash: str,
50
+ flwr_dir: Optional[Union[str, Path]] = None,
43
51
  ) -> Path:
44
52
  """Return the project directory based on the given fab_id and fab_version."""
45
53
  # Check the fab_id
@@ -50,7 +58,11 @@ def get_project_dir(
50
58
  publisher, project_name = fab_id.split("/")
51
59
  if flwr_dir is None:
52
60
  flwr_dir = get_flwr_dir()
53
- return Path(flwr_dir) / APP_DIR / publisher / project_name / fab_version
61
+ return (
62
+ Path(flwr_dir)
63
+ / APP_DIR
64
+ / f"{publisher}.{project_name}.{fab_version}.{fab_hash[:FAB_HASH_TRUNCATION]}"
65
+ )
54
66
 
55
67
 
56
68
  def get_project_config(project_dir: Union[str, Path]) -> dict[str, Any]:
@@ -127,7 +139,7 @@ def get_fused_config(run: Run, flwr_dir: Optional[Path]) -> UserConfig:
127
139
  if not run.fab_id or not run.fab_version:
128
140
  return {}
129
141
 
130
- project_dir = get_project_dir(run.fab_id, run.fab_version, flwr_dir)
142
+ project_dir = get_project_dir(run.fab_id, run.fab_version, run.fab_hash, flwr_dir)
131
143
 
132
144
  # Return empty dict if project directory does not exist
133
145
  if not project_dir.is_dir():
@@ -205,8 +217,9 @@ def parse_config_args(
205
217
  matches = pattern.findall(config_line)
206
218
  toml_str = "\n".join(f"{k} = {v}" for k, v in matches)
207
219
  overrides.update(tomli.loads(toml_str))
220
+ flat_overrides = flatten_dict(overrides)
208
221
 
209
- return overrides
222
+ return flat_overrides
210
223
 
211
224
 
212
225
  def get_metadata_from_config(config: dict[str, Any]) -> tuple[str, str]:
flwr/common/message.py CHANGED
@@ -290,6 +290,11 @@ class Message:
290
290
  follows the equation:
291
291
 
292
292
  ttl = msg.meta.ttl - (reply.meta.created_at - msg.meta.created_at)
293
+
294
+ Returns
295
+ -------
296
+ message : Message
297
+ A Message containing only the relevant error and metadata.
293
298
  """
294
299
  # If no TTL passed, use default for message creation (will update after
295
300
  # message creation)
@@ -59,6 +59,11 @@ def parametersrecord_to_parameters(
59
59
  keep_input : bool
60
60
  A boolean indicating whether entries in the record should be deleted from the
61
61
  input dictionary immediately after adding them to the record.
62
+
63
+ Returns
64
+ -------
65
+ parameters : Parameters
66
+ The parameters in the legacy format Parameters.
62
67
  """
63
68
  parameters = Parameters(tensors=[], tensor_type="")
64
69
 
@@ -94,6 +99,11 @@ def parameters_to_parametersrecord(
94
99
  A boolean indicating whether parameters should be deleted from the input
95
100
  Parameters object (i.e. a list of serialized NumPy arrays) immediately after
96
101
  adding them to the record.
102
+
103
+ Returns
104
+ -------
105
+ ParametersRecord
106
+ The ParametersRecord containing the provided parameters.
97
107
  """
98
108
  tensor_type = parameters.tensor_type
99
109
 
@@ -38,6 +38,11 @@ def exponential(
38
38
  Factor by which the delay is multiplied after each retry.
39
39
  max_delay: Optional[float] (default: None)
40
40
  The maximum delay duration between two consecutive retries.
41
+
42
+ Returns
43
+ -------
44
+ Generator[float, None, None]
45
+ A generator for the delay between 2 retries.
41
46
  """
42
47
  delay = base_delay if max_delay is None else min(base_delay, max_delay)
43
48
  while True:
@@ -56,6 +61,11 @@ def constant(
56
61
  ----------
57
62
  interval: Union[float, Iterable[float]] (default: 1)
58
63
  A constant value to yield or an iterable of such values.
64
+
65
+ Returns
66
+ -------
67
+ Generator[float, None, None]
68
+ A generator for the delay between 2 retries.
59
69
  """
60
70
  if not isinstance(interval, Iterable):
61
71
  interval = itertools.repeat(interval)
@@ -73,6 +83,11 @@ def full_jitter(max_value: float) -> float:
73
83
  ----------
74
84
  max_value : float
75
85
  The upper limit for the randomized value.
86
+
87
+ Returns
88
+ -------
89
+ float
90
+ A random float that is less than max_value.
76
91
  """
77
92
  return random.uniform(0, max_value)
78
93
 
@@ -47,6 +47,7 @@ class ClientManager(ABC):
47
47
  Parameters
48
48
  ----------
49
49
  client : flwr.server.client_proxy.ClientProxy
50
+ The ClientProxy of the Client to register.
50
51
 
51
52
  Returns
52
53
  -------
@@ -64,6 +65,7 @@ class ClientManager(ABC):
64
65
  Parameters
65
66
  ----------
66
67
  client : flwr.server.client_proxy.ClientProxy
68
+ The ClientProxy of the Client to unregister.
67
69
  """
68
70
 
69
71
  @abstractmethod
@@ -150,7 +150,7 @@ class InMemoryDriver(Driver):
150
150
  """
151
151
  msg_ids = {UUID(msg_id) for msg_id in message_ids}
152
152
  # Pull TaskRes
153
- task_res_list = self.state.get_task_res(task_ids=msg_ids, limit=len(msg_ids))
153
+ task_res_list = self.state.get_task_res(task_ids=msg_ids)
154
154
  # Delete tasks in state
155
155
  self.state.delete_tasks(msg_ids)
156
156
  # Convert TaskRes to Message
@@ -181,19 +181,17 @@ def run_server_app() -> None:
181
181
  )
182
182
  flwr_dir = get_flwr_dir(args.flwr_dir)
183
183
  run_ = driver.run
184
- if run_.fab_hash:
185
- fab_req = GetFabRequest(hash_str=run_.fab_hash)
186
- # pylint: disable-next=W0212
187
- fab_res: GetFabResponse = driver._stub.GetFab(fab_req)
188
- if fab_res.fab.hash_str != run_.fab_hash:
189
- raise ValueError("FAB hashes don't match.")
190
-
191
- install_from_fab(fab_res.fab.content, flwr_dir, True)
192
- fab_id, fab_version = get_fab_metadata(fab_res.fab.content)
193
- else:
194
- fab_id, fab_version = run_.fab_id, run_.fab_version
195
-
196
- app_path = str(get_project_dir(fab_id, fab_version, flwr_dir))
184
+ if not run_.fab_hash:
185
+ raise ValueError("FAB hash not provided.")
186
+ fab_req = GetFabRequest(hash_str=run_.fab_hash)
187
+ # pylint: disable-next=W0212
188
+ fab_res: GetFabResponse = driver._stub.GetFab(fab_req)
189
+ if fab_res.fab.hash_str != run_.fab_hash:
190
+ raise ValueError("FAB hashes don't match.")
191
+ install_from_fab(fab_res.fab.content, flwr_dir, True)
192
+ fab_id, fab_version = get_fab_metadata(fab_res.fab.content)
193
+
194
+ app_path = str(get_project_dir(fab_id, fab_version, run_.fab_hash, flwr_dir))
197
195
  config = get_project_config(app_path)
198
196
  else:
199
197
  # User provided `app_dir`, but not `--run-id`
@@ -155,7 +155,7 @@ class DriverServicer(driver_pb2_grpc.DriverServicer):
155
155
  context.add_callback(on_rpc_done)
156
156
 
157
157
  # Read from state
158
- task_res_list: list[TaskRes] = state.get_task_res(task_ids=task_ids, limit=None)
158
+ task_res_list: list[TaskRes] = state.get_task_res(task_ids=task_ids)
159
159
 
160
160
  context.set_code(grpc.StatusCode.OK)
161
161
  return PullTaskResResponse(task_res_list=task_res_list)
@@ -174,7 +174,7 @@ def generic_create_grpc_server( # pylint: disable=too-many-arguments
174
174
 
175
175
  Parameters
176
176
  ----------
177
- servicer_and_add_fn : Tuple
177
+ servicer_and_add_fn : tuple
178
178
  A tuple holding a servicer implementation and a matching
179
179
  add_Servicer_to_server function.
180
180
  server_address : str
@@ -214,6 +214,8 @@ def generic_create_grpc_server( # pylint: disable=too-many-arguments
214
214
  * CA certificate.
215
215
  * server certificate.
216
216
  * server private key.
217
+ interceptors : Optional[Sequence[grpc.ServerInterceptor]] (default: None)
218
+ A list of gRPC interceptors.
217
219
 
218
220
  Returns
219
221
  -------
@@ -346,7 +346,7 @@ def start_vce(
346
346
  app_path=app_dir,
347
347
  flwr_dir=flwr_dir,
348
348
  multi_app=False,
349
- )(run.fab_id, run.fab_version)
349
+ )(run.fab_id, run.fab_version, run.fab_hash)
350
350
 
351
351
  if client_app:
352
352
  app = client_app
@@ -116,6 +116,7 @@ class InMemoryState(State): # pylint: disable=R0902,R0904
116
116
  # Return TaskIns
117
117
  return task_ins_list
118
118
 
119
+ # pylint: disable=R0911
119
120
  def store_task_res(self, task_res: TaskRes) -> Optional[UUID]:
120
121
  """Store one TaskRes."""
121
122
  # Validate task
@@ -129,6 +130,17 @@ class InMemoryState(State): # pylint: disable=R0902,R0904
129
130
  task_ins_id = task_res.task.ancestry[0]
130
131
  task_ins = self.task_ins_store.get(UUID(task_ins_id))
131
132
 
133
+ # Ensure that the consumer_id of taskIns matches the producer_id of taskRes.
134
+ if (
135
+ task_ins
136
+ and task_res
137
+ and not (
138
+ task_ins.task.consumer.anonymous or task_res.task.producer.anonymous
139
+ )
140
+ and task_ins.task.consumer.node_id != task_res.task.producer.node_id
141
+ ):
142
+ return None
143
+
132
144
  if task_ins is None:
133
145
  log(ERROR, "TaskIns with task_id %s does not exist.", task_ins_id)
134
146
  return None
@@ -178,27 +190,33 @@ class InMemoryState(State): # pylint: disable=R0902,R0904
178
190
  # Return the new task_id
179
191
  return task_id
180
192
 
181
- def get_task_res(self, task_ids: set[UUID], limit: Optional[int]) -> list[TaskRes]:
193
+ def get_task_res(self, task_ids: set[UUID]) -> list[TaskRes]:
182
194
  """Get all TaskRes that have not been delivered yet."""
183
- if limit is not None and limit < 1:
184
- raise AssertionError("`limit` must be >= 1")
185
-
186
195
  with self.lock:
187
196
  # Find TaskRes that were not delivered yet
188
197
  task_res_list: list[TaskRes] = []
189
198
  replied_task_ids: set[UUID] = set()
190
199
  for _, task_res in self.task_res_store.items():
191
200
  reply_to = UUID(task_res.task.ancestry[0])
201
+
202
+ # Check if corresponding TaskIns exists and is not expired
203
+ task_ins = self.task_ins_store.get(reply_to)
204
+ if task_ins is None:
205
+ log(WARNING, "TaskIns with task_id %s does not exist.", reply_to)
206
+ task_ids.remove(reply_to)
207
+ continue
208
+
209
+ if task_ins.task.created_at + task_ins.task.ttl <= time.time():
210
+ log(WARNING, "TaskIns with task_id %s is expired.", reply_to)
211
+ task_ids.remove(reply_to)
212
+ continue
213
+
192
214
  if reply_to in task_ids and task_res.task.delivered_at == "":
193
215
  task_res_list.append(task_res)
194
216
  replied_task_ids.add(reply_to)
195
- if limit and len(task_res_list) == limit:
196
- break
197
217
 
198
218
  # Check if the node is offline
199
219
  for task_id in task_ids - replied_task_ids:
200
- if limit and len(task_res_list) == limit:
201
- break
202
220
  task_ins = self.task_ins_store.get(task_id)
203
221
  if task_ins is None:
204
222
  continue
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """SQLite based implemenation of server state."""
16
16
 
17
+ # pylint: disable=too-many-lines
17
18
 
18
19
  import json
19
20
  import re
@@ -150,6 +151,11 @@ class SqliteState(State): # pylint: disable=R0904
150
151
  ----------
151
152
  log_queries : bool
152
153
  Log each query which is executed.
154
+
155
+ Returns
156
+ -------
157
+ list[tuple[str]]
158
+ The list of all tables in the DB.
153
159
  """
154
160
  self.conn = sqlite3.connect(self.database_path)
155
161
  self.conn.execute("PRAGMA foreign_keys = ON;")
@@ -389,6 +395,16 @@ class SqliteState(State): # pylint: disable=R0904
389
395
  )
390
396
  return None
391
397
 
398
+ # Ensure that the consumer_id of taskIns matches the producer_id of taskRes.
399
+ if (
400
+ task_ins
401
+ and task_res
402
+ and not (task_ins["consumer_anonymous"] or task_res.task.producer.anonymous)
403
+ and convert_sint64_to_uint64(task_ins["consumer_node_id"])
404
+ != task_res.task.producer.node_id
405
+ ):
406
+ return None
407
+
392
408
  # Fail if the TaskRes TTL exceeds the
393
409
  # expiration time of the TaskIns it replies to.
394
410
  # Condition: TaskIns.created_at + TaskIns.ttl ≥
@@ -432,8 +448,8 @@ class SqliteState(State): # pylint: disable=R0904
432
448
 
433
449
  return task_id
434
450
 
435
- # pylint: disable-next=R0914
436
- def get_task_res(self, task_ids: set[UUID], limit: Optional[int]) -> list[TaskRes]:
451
+ # pylint: disable-next=R0912,R0915,R0914
452
+ def get_task_res(self, task_ids: set[UUID]) -> list[TaskRes]:
437
453
  """Get TaskRes for task_ids.
438
454
 
439
455
  Usually, the Driver API calls this method to get results for instructions it has
@@ -448,8 +464,34 @@ class SqliteState(State): # pylint: disable=R0904
448
464
  will only take effect if enough task_ids are in the set AND are currently
449
465
  available. If `limit` is set, it has to be greater than zero.
450
466
  """
451
- if limit is not None and limit < 1:
452
- raise AssertionError("`limit` must be >= 1")
467
+ # Check if corresponding TaskIns exists and is not expired
468
+ task_ids_placeholders = ",".join([f":id_{i}" for i in range(len(task_ids))])
469
+ query = f"""
470
+ SELECT *
471
+ FROM task_ins
472
+ WHERE task_id IN ({task_ids_placeholders})
473
+ AND (created_at + ttl) > CAST(strftime('%s', 'now') AS REAL)
474
+ """
475
+ query += ";"
476
+
477
+ task_ins_data = {}
478
+ for index, task_id in enumerate(task_ids):
479
+ task_ins_data[f"id_{index}"] = str(task_id)
480
+
481
+ task_ins_rows = self.query(query, task_ins_data)
482
+
483
+ if not task_ins_rows:
484
+ return []
485
+
486
+ for row in task_ins_rows:
487
+ # Convert values from sint64 to uint64
488
+ convert_sint64_values_in_dict_to_uint64(
489
+ row, ["run_id", "producer_node_id", "consumer_node_id"]
490
+ )
491
+ task_ins = dict_to_task_ins(row)
492
+ if task_ins.task.created_at + task_ins.task.ttl <= time.time():
493
+ log(WARNING, "TaskIns with task_id %s is expired.", task_ins.task_id)
494
+ task_ids.remove(UUID(task_ins.task_id))
453
495
 
454
496
  # Retrieve all anonymous Tasks
455
497
  if len(task_ids) == 0:
@@ -465,10 +507,6 @@ class SqliteState(State): # pylint: disable=R0904
465
507
 
466
508
  data: dict[str, Union[str, float, int]] = {}
467
509
 
468
- if limit is not None:
469
- query += " LIMIT :limit"
470
- data["limit"] = limit
471
-
472
510
  query += ";"
473
511
 
474
512
  for index, task_id in enumerate(task_ids):
@@ -543,9 +581,6 @@ class SqliteState(State): # pylint: disable=R0904
543
581
 
544
582
  # Make TaskRes containing node unavailabe error
545
583
  for row in task_ins_rows:
546
- if limit and len(result) == limit:
547
- break
548
-
549
584
  for row in rows:
550
585
  # Convert values from sint64 to uint64
551
586
  convert_sint64_values_in_dict_to_uint64(
@@ -98,7 +98,7 @@ class State(abc.ABC): # pylint: disable=R0904
98
98
  """
99
99
 
100
100
  @abc.abstractmethod
101
- def get_task_res(self, task_ids: set[UUID], limit: Optional[int]) -> list[TaskRes]:
101
+ def get_task_res(self, task_ids: set[UUID]) -> list[TaskRes]:
102
102
  """Get TaskRes for task_ids.
103
103
 
104
104
  Usually, the Driver API calls this method to get results for instructions it has
@@ -106,12 +106,6 @@ class State(abc.ABC): # pylint: disable=R0904
106
106
 
107
107
  Retrieves all TaskRes for the given `task_ids` and returns and empty list of
108
108
  none could be found.
109
-
110
- Constraints
111
- -----------
112
- If `limit` is not `None`, return, at most, `limit` number of TaskRes. The limit
113
- will only take effect if enough task_ids are in the set AND are currently
114
- available. If `limit` is set, it has to be greater zero.
115
109
  """
116
110
 
117
111
  @abc.abstractmethod
@@ -100,11 +100,6 @@ def convert_uint64_values_in_dict_to_sint64(
100
100
  A dictionary where the values are integers to be converted.
101
101
  keys : list[str]
102
102
  A list of keys in the dictionary whose values need to be converted.
103
-
104
- Returns
105
- -------
106
- None
107
- This function does not return a value. It modifies `data_dict` in place.
108
103
  """
109
104
  for key in keys:
110
105
  if key in data_dict:
@@ -122,11 +117,6 @@ def convert_sint64_values_in_dict_to_uint64(
122
117
  A dictionary where the values are integers to be converted.
123
118
  keys : list[str]
124
119
  A list of keys in the dictionary whose values need to be converted.
125
-
126
- Returns
127
- -------
128
- None
129
- This function does not return a value. It modifies `data_dict` in place.
130
120
  """
131
121
  for key in keys:
132
122
  if key in data_dict:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: flwr-nightly
3
- Version: 1.12.0.dev20241008
3
+ Version: 1.12.0.dev20241010
4
4
  Summary: Flower: A Friendly Federated Learning Framework
5
5
  Home-page: https://flower.ai
6
6
  License: Apache-2.0
@@ -1,10 +1,10 @@
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=_HDs7HS12Dp7NXIyVrkPs1SKJq3x-XvVZd6y1lvyud4,1255
4
- flwr/cli/build.py,sha256=H4xrQPDj7kvZ7Ys65yb-jE86RLEmvIE3pZ3mokZfJHg,5145
5
- flwr/cli/config_utils.py,sha256=uJmJAHNoqeSeAC3BAxxoBuYOR9eV3mJg8wrWZgbGp3E,7521
4
+ flwr/cli/build.py,sha256=WC7e6xPJJqRJvXmi8u0ECRvPThPYQcGOxLEgh9uG0gI,6365
5
+ flwr/cli/config_utils.py,sha256=U0tYiC4uwT68LzXFpiiu6XzzplEo-43BR_ON9t3aHOw,7956
6
6
  flwr/cli/example.py,sha256=1bGDYll3BXQY2kRqSN-oICqS5n1b9m0g0RvXTopXHl4,2215
7
- flwr/cli/install.py,sha256=t5tdeKOsTmG3nuInUoSKBVzUU1RnzA096yzYs013VhE,7065
7
+ flwr/cli/install.py,sha256=7Dx8zrn49mTktxGOToBhGx8hzsHOViDasMJ43ooKPXc,8646
8
8
  flwr/cli/log.py,sha256=uhtcLcFGkazirWnEmet3Wt3rt_q-a13kauQqPLaMaRY,8097
9
9
  flwr/cli/new/__init__.py,sha256=cQzK1WH4JP2awef1t2UQ2xjl1agVEz9rwutV18SWV1k,789
10
10
  flwr/cli/new/new.py,sha256=uSiG7aXQzPDnikv2YcjQ86OOLqint0hNWCI0fSQD0jI,9634
@@ -52,7 +52,7 @@ flwr/cli/new/templates/app/code/task.sklearn.py.tpl,sha256=SeIIo0rr_6ffn4Qx2xELD
52
52
  flwr/cli/new/templates/app/code/task.tensorflow.py.tpl,sha256=SKXAZdgBnPpbAbJ90Rb7oQ5ilnopBx_j_JNFoUDeEAI,1732
53
53
  flwr/cli/new/templates/app/code/utils.baseline.py.tpl,sha256=YkHAgppUeD2BnBoGfVB6dEvBfjuIPGsU1gw4CiUi3qA,40
54
54
  flwr/cli/new/templates/app/pyproject.baseline.toml.tpl,sha256=4gi90W9_B1kj6rYkpvVJxhNX9Yctsv9OH6CzXP-dcE4,2666
55
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl,sha256=bYdDP0O8z741pvy1INnH4UBuP-KFvcyQt6Yo81n4frQ,1853
55
+ flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl,sha256=z02u1QgZ9qZ5_3Q1TKCIRgnnMufbPaKUfsacUuTou4I,1853
56
56
  flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl,sha256=CHJgkPNkJfzJhEbTe15uiV3AhOtIddQi-yofPZsCk3E,1143
57
57
  flwr/cli/new/templates/app/pyproject.jax.toml.tpl,sha256=v1DVriLky0ow9yc0NK91_6VkxkzpPsheIxbb2c0LcYQ,673
58
58
  flwr/cli/new/templates/app/pyproject.mlx.toml.tpl,sha256=S4QDy7UXboJt60R3LE7z97_QU1idb0ob8A_N7O3cifo,765
@@ -61,16 +61,16 @@ flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl,sha256=vIO1ArukTC76ogYLNmJ
61
61
  flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl,sha256=fdlIN_sip1mrbOtqpeag60Kj56aYrA-0HEq9lYZLNnM,686
62
62
  flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl,sha256=bRIvPCPvTTI4Eo5b61Rmw8WdDw3sjcohciTXgULN5l8,702
63
63
  flwr/cli/run/__init__.py,sha256=oCd6HmQDx-sqver1gecgx-uMA38BLTSiiKpl7RGNceg,789
64
- flwr/cli/run/run.py,sha256=tLKeWpWJqEBxkOtDKEprL4SBKxH1vHGybQxlCLnXiSQ,8386
64
+ flwr/cli/run/run.py,sha256=NMCeDfImxta1VEeBqqkP05xsuBK6YWFTd7Qj_bIEA2Y,8394
65
65
  flwr/cli/utils.py,sha256=emMUdthvoHBTB0iGQp-oFBmA5wV46lw3y3FmfXQPCsc,4500
66
66
  flwr/client/__init__.py,sha256=DGDoO0AEAfz-0CUFmLdyUUweAS64-07AOnmDfWUefK4,1192
67
- flwr/client/app.py,sha256=cH0LAmpm6M8rfgc3lJFJ3pEwCVEgMuv-58WvOo6FEw0,31956
67
+ flwr/client/app.py,sha256=q-hExXwM3lru1TdYVE6HA3hKgwjhmbO_6Iq6oifVGpk,32712
68
68
  flwr/client/client.py,sha256=gy6WVlMUFAp8oevN4xpQPX30vPOIYGVqdbuFlTWkyG4,9080
69
69
  flwr/client/client_app.py,sha256=cTig-N00YzTucbo9zNi6I21J8PlbflU_8J_f5CI-Wpw,10390
70
70
  flwr/client/clientapp/__init__.py,sha256=kZqChGnTChQ1WGSUkIlW2S5bc0d0mzDubCAmZUGRpEY,800
71
- flwr/client/clientapp/app.py,sha256=-85ueqDZp7cFb-0Vq4fAME8JVbqN1akKnQIi5QGZf4c,7790
71
+ flwr/client/clientapp/app.py,sha256=ddPi3jPYO7-uGZBZ0O91NbP2x-HVOiqFGEkprdsMAUU,7882
72
72
  flwr/client/clientapp/clientappio_servicer.py,sha256=5L6bjw_j3Mnx9kRFwYwxDNABKurBO5q1jZOWE_X11wQ,8522
73
- flwr/client/clientapp/utils.py,sha256=2fYKY1LfZPalG5Cm5FbSuNMIDtouQg17GbrzPINyM_A,3990
73
+ flwr/client/clientapp/utils.py,sha256=Xg23Q7g7r9jrxXEbvJ9wXal_uAqYK3mi087u0QER6-I,4343
74
74
  flwr/client/dpfedavg_numpy_client.py,sha256=4KsEvzavDKyVDU1V0kMqffTwu1lNdUCYQN-i0DTYVN8,7404
75
75
  flwr/client/grpc_adapter_client/__init__.py,sha256=QyNWIbsq9DpyMk7oemiO1P3TBFfkfkctnJ1JoAkTl3s,742
76
76
  flwr/client/grpc_adapter_client/connection.py,sha256=50LlADsrvvo_kYoGRXOph3ICAmcaQcPDEYuBmA6l5PI,3971
@@ -78,7 +78,7 @@ flwr/client/grpc_client/__init__.py,sha256=LsnbqXiJhgQcB0XzAlUQgPx011Uf7Y7yabIC1
78
78
  flwr/client/grpc_client/connection.py,sha256=WX0cKlV_S19bYYp52z3PYRrtOdGb52ovvFFVWIz6Uyw,9382
79
79
  flwr/client/grpc_rere_client/__init__.py,sha256=MK-oSoV3kwUEQnIwl0GN4OpiHR7eLOrMA8ikunET130,752
80
80
  flwr/client/grpc_rere_client/client_interceptor.py,sha256=q08lIEeJLvvonNOiejNXvmySbPObteGnbDHhEKDmWzE,5380
81
- flwr/client/grpc_rere_client/connection.py,sha256=tppAfMTV1yLBNmgS0KuvqGUjaecpA7SWbPIITkfEHcY,10960
81
+ flwr/client/grpc_rere_client/connection.py,sha256=OgDQ9TFVrPV-X4UgduduxOICRvz834zd8zG7QrYvgM0,11152
82
82
  flwr/client/grpc_rere_client/grpc_adapter.py,sha256=sQo0I9T65t97LFGoW_PrmgaTbd18GFgi2DoAI5wQJ4k,5589
83
83
  flwr/client/heartbeat.py,sha256=cx37mJBH8LyoIN4Lks85wtqT1mnU5GulQnr4pGCvAq0,2404
84
84
  flwr/client/message_handler/__init__.py,sha256=QxxQuBNpFPTHx3KiUNvQSlqMKlEnbRR1kFfc1KVje08,719
@@ -102,7 +102,7 @@ flwr/client/supernode/app.py,sha256=it4jm9GATntwmRJNXfIIfsS8bTIRwba94B1uNy0I2TM,
102
102
  flwr/client/typing.py,sha256=dxoTBnTMfqXr5J7G3y-uNjqxYCddvxhu89spfj4Lm2U,1048
103
103
  flwr/common/__init__.py,sha256=TVaoFEJE158aui1TPZQiJCDZX4RNHRyI8I55VC80HhI,3901
104
104
  flwr/common/address.py,sha256=7kM2Rqjw86-c8aKwAvrXerWqznnVv4TFJ62aSAeTn10,3017
105
- flwr/common/config.py,sha256=QjsDEDf4xsx8StJV9I80dYWbBp7aBNrJmTlAeLpQpyw,7567
105
+ flwr/common/config.py,sha256=hlj3Div9D53cIg7ur-d1xr6sAHM0shAR3aQyOsOxZuE,7779
106
106
  flwr/common/constant.py,sha256=cUP0lErfb0s1ACnErm_T5kIks0xnEi2X5UNnzmXRSW4,3757
107
107
  flwr/common/context.py,sha256=5Bd9RCrhLkYZOVR7vr97OVhzVBHQkS1fUsYiIKTwpxU,2239
108
108
  flwr/common/date.py,sha256=OcQuwpb2HxcblTqYm6H223ufop5UZw5N_fzalbpOVzY,891
@@ -112,7 +112,7 @@ flwr/common/dp.py,sha256=vddkvyjV2FhRoN4VuU2LeAM1UBn7dQB8_W-Qdiveal8,1978
112
112
  flwr/common/exit_handlers.py,sha256=MracJaBeoCOC7TaXK9zCJQxhrMSx9ZtczK237qvhBpU,2806
113
113
  flwr/common/grpc.py,sha256=6Yi28JjAll19nxYJlOT9B03RN8dvJZP9zUoR3RSmxoY,2487
114
114
  flwr/common/logger.py,sha256=zAjaGrr_UWMkIdi1xG9tY764qJHIYM8LsPgMfBsyp64,8117
115
- flwr/common/message.py,sha256=QDq7WvzNJqynIGgGQ3ZdrWiZUQBZiNhxAX2HFTmeUcw,13671
115
+ flwr/common/message.py,sha256=OpVvfWDHBVwcW-P_xToxSyo77cPFoCn_1cFwV2bfr4A,13801
116
116
  flwr/common/object_ref.py,sha256=5lgWqYaJR28UdFc-iirWw9YqFXMfgkOOAdfJc1AVibE,8711
117
117
  flwr/common/parameter.py,sha256=-bFAUayToYDF50FZGrBC1hQYJCQDtB2bbr3ZuVLMtdE,2095
118
118
  flwr/common/pyproject.py,sha256=EI_ovbCHGmhYrdPx0RSDi5EkFZFof-8m1PA54c0ZTjc,1385
@@ -123,8 +123,8 @@ flwr/common/record/metricsrecord.py,sha256=UywkEPbifiu_IyPUFoDJCi8WEVLujlqZERUWA
123
123
  flwr/common/record/parametersrecord.py,sha256=IjnewX8Ea6JXLRWcPMVole2sNjwzRVjBVvzoey5gqtw,7747
124
124
  flwr/common/record/recordset.py,sha256=sSofrBycZSqiHR4TzfI4_QoIIN-5B1LnMG0C9CiByAo,8312
125
125
  flwr/common/record/typeddict.py,sha256=q5hL2xkXymuiCprHWb69mUmLpWQk_XXQq0hGQ69YPaw,3599
126
- flwr/common/recordset_compat.py,sha256=FHirA3xPGiM4veOpUSiMM0cmc0winIo9qZnk3E4JIz0,14010
127
- flwr/common/retry_invoker.py,sha256=YSbH3FHGkF5scDNSf0t2zHP0sycGrWKEaSWBj5qsMc8,11660
126
+ flwr/common/recordset_compat.py,sha256=ViSwA26h6Q55ZmV1LLjSJpcKiipV-p_JpCj4wxdE-Ow,14230
127
+ flwr/common/retry_invoker.py,sha256=u5dHcRMoyS8ABL3Fjk4P5P1lgRYYa1edfLGzWXxwyAc,11969
128
128
  flwr/common/secure_aggregation/__init__.py,sha256=erPnTWdOfMH0K0HQTmj5foDJ6t3iYcExy2aACy8iZNQ,731
129
129
  flwr/common/secure_aggregation/crypto/__init__.py,sha256=nlHesCWy8xxE5s6qHWnauCtyClcMQ2K0CEXAHakY5n0,738
130
130
  flwr/common/secure_aggregation/crypto/shamir.py,sha256=wCSfEfeaPgJ9Om580-YPUF2ljiyRhq33TRC4HtwxYl8,2779
@@ -201,7 +201,7 @@ flwr/proto/transport_pb2_grpc.pyi,sha256=AGXf8RiIiW2J5IKMlm_3qT3AzcDa4F3P5IqUjve
201
201
  flwr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
202
202
  flwr/server/__init__.py,sha256=cEg1oecBu4cKB69iJCqWEylC8b5XW47bl7rQiJsdTvM,1528
203
203
  flwr/server/app.py,sha256=aPl20uTV7jKz5RbjLE2saYIMm5GUJ-wHI_J4AMhi1xg,24423
204
- flwr/server/client_manager.py,sha256=pvNk7-UxoeVsepr1ubaq5PrOMzWYjbQ4GP9SxVtUlzA,6115
204
+ flwr/server/client_manager.py,sha256=7Ese0tgrH-i-ms363feYZJKwB8gWnXSmg_hYF2Bju4U,6227
205
205
  flwr/server/client_proxy.py,sha256=4G-oTwhb45sfWLx2uZdcXD98IZwdTS6F88xe3akCdUg,2399
206
206
  flwr/server/compat/__init__.py,sha256=VxnJtJyOjNFQXMNi9hIuzNlZM5n0Hj1p3aq_Pm2udw4,892
207
207
  flwr/server/compat/app.py,sha256=5vkHHm_h-4cMthvWD1GJo1ZW3eihytjGgvsgfXUK9gA,3298
@@ -212,9 +212,9 @@ flwr/server/criterion.py,sha256=ypbAexbztzGUxNen9RCHF91QeqiEQix4t4Ih3E-42MM,1061
212
212
  flwr/server/driver/__init__.py,sha256=bikRv6CjTwSvYh7tf10gziU5o2YotOWhhftz2tr3KDc,886
213
213
  flwr/server/driver/driver.py,sha256=rGLbOfLhBOn74mUHi_0CMbXqZLX8q_lXqEkcUXoL_wI,5238
214
214
  flwr/server/driver/grpc_driver.py,sha256=xd1mxRexeiIJrZw9l-urj2zEIncLT8KtNn0l8hIDYZs,9681
215
- flwr/server/driver/inmemory_driver.py,sha256=J1pzjzNF18z_sQnez9JmrHqSIDfgn9NX6NfLj1BKrH4,6658
215
+ flwr/server/driver/inmemory_driver.py,sha256=QsYY4-aUVgLl7gVly8EW4D-NZzsCB58MhYkYUrFTEIw,6638
216
216
  flwr/server/history.py,sha256=qSb5_pPTrwofpSYGsZWzMPkl_4uJ4mJFWesxXDrEvDU,5026
217
- flwr/server/run_serverapp.py,sha256=SaE9hoWLCAPnRXvdAzE4Oi3QaiC8NOTrHxrIGXjgYxU,10531
217
+ flwr/server/run_serverapp.py,sha256=5zoXSzLWKAuhK33cLYN-y5LzTgKqIyioNmzs7MIrDOA,10498
218
218
  flwr/server/server.py,sha256=1ZsFEptmAV-L2vP2etNC9Ed5CLSxpuKzUFkAPQ4l5Xc,17893
219
219
  flwr/server/server_app.py,sha256=1hul76ospG8L_KooK_ewn1sWPNTNYLTtZMeGNOBNruA,6267
220
220
  flwr/server/server_config.py,sha256=CZaHVAsMvGLjpWVcLPkiYxgJN4xfIyAiUrCI3fETKY4,1349
@@ -246,7 +246,7 @@ flwr/server/strategy/strategy.py,sha256=cXapkD5uDrt5C-RbmWDn9FLoap3Q41i7GKvbmfbC
246
246
  flwr/server/superlink/__init__.py,sha256=8tHYCfodUlRD8PCP9fHgvu8cz5N31A2QoRVL0jDJ15E,707
247
247
  flwr/server/superlink/driver/__init__.py,sha256=_JaRW-FdyikHc7souUrnk3mwTGViraEJCeUBY_M_ocs,712
248
248
  flwr/server/superlink/driver/driver_grpc.py,sha256=ej9T21zIquIJEZyWcvapQSQFckh4oFPamOe6P6DhB2s,2048
249
- flwr/server/superlink/driver/driver_servicer.py,sha256=x8L8rIO6r7kVdmWZFJp-xmTB0K_Tz1mh-7Akc6I_ZjY,6967
249
+ flwr/server/superlink/driver/driver_servicer.py,sha256=6c_0PP2-Pec9JVJTZZK_3yfrQ8dwAfzhn43mPzT2rYw,6955
250
250
  flwr/server/superlink/ffs/__init__.py,sha256=FAY-zShcfPmOxosok2QyT6hTNMNctG8cH9s_nIl8jkI,840
251
251
  flwr/server/superlink/ffs/disk_ffs.py,sha256=yCN6CCzegnJIOaHr5nIu49wZQa4g5BByiSKshz50RKU,3296
252
252
  flwr/server/superlink/ffs/ffs.py,sha256=qLI1UfosJugu2BKOJWqHIhafTm-YiuKqGf3OGWPH0NM,2395
@@ -258,7 +258,7 @@ flwr/server/superlink/fleet/grpc_bidi/__init__.py,sha256=dkSKQMuMTYh1qSnuN87cAPv
258
258
  flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py,sha256=xbvorZhCHBj0CvFWB7oUeHoY0o750hUkiS0DiTCGHDs,6020
259
259
  flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py,sha256=JkAH_nIZaqe_9kntrg26od_jaz5XdLFuvNMgGu8xk9Q,6485
260
260
  flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py,sha256=h3EhqgelegVC4EjOXH5birmAnMoCBJcP7jpHYCnHZPk,4887
261
- flwr/server/superlink/fleet/grpc_bidi/grpc_server.py,sha256=8VFp7K5ollkexR2UmXOrLtxo6H_B79eZkyM_Ro77tSU,12312
261
+ flwr/server/superlink/fleet/grpc_bidi/grpc_server.py,sha256=01eorJIL7LVTRkxAnJjZp3HLlJzWZzg2cVOGAWxF0Ag,12427
262
262
  flwr/server/superlink/fleet/grpc_rere/__init__.py,sha256=j2hyC342am-_Hgp1g80Y3fGDzfTI6n8QOOn2PyWf4eg,758
263
263
  flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py,sha256=bgoLQEhahVHjdlRDk_58zyKFeMOziiPUXSbYMhOxybY,4757
264
264
  flwr/server/superlink/fleet/grpc_rere/server_interceptor.py,sha256=pgSiH-qrTgDxu4M_jJu-8gvEQnxTdR-qIAawKgxjQ6M,8157
@@ -270,13 +270,13 @@ flwr/server/superlink/fleet/vce/__init__.py,sha256=36MHKiefnJeyjwMQzVUK4m06Ojon3
270
270
  flwr/server/superlink/fleet/vce/backend/__init__.py,sha256=Onq16E2M2Wha2K1zvhcquT1jOveQL2cgIrgqWvAPH9Y,1436
271
271
  flwr/server/superlink/fleet/vce/backend/backend.py,sha256=LBAQxnbfPAphVOVIvYMj0QIvVP5O-RQxKQlUGNUj974,2194
272
272
  flwr/server/superlink/fleet/vce/backend/raybackend.py,sha256=7kB3re3mR53b7E6L6DPSioTSKD3YGtS3uJsPD7Hn2Fw,7155
273
- flwr/server/superlink/fleet/vce/vce_api.py,sha256=cGPsjS_4SJHm8jszGjsHh8ZNk9nqWoIQwW_62yKKR1Y,12647
273
+ flwr/server/superlink/fleet/vce/vce_api.py,sha256=4f4xEcTKXkj6Wc9jg30hzJR1RX7ECxkfsCj0CKfC9xE,12661
274
274
  flwr/server/superlink/state/__init__.py,sha256=Gj2OTFLXvA-mAjBvwuKDM3rDrVaQPcIoybSa2uskMTE,1003
275
- flwr/server/superlink/state/in_memory_state.py,sha256=XL1cqXSnF87lpTprEtyl9aYRbDp2VuOiJdxvhTSvO18,14936
276
- flwr/server/superlink/state/sqlite_state.py,sha256=B1DOzHB9BpKLPmCSp4YyjcFspZpFpDsNJqCML6WQlhs,34259
277
- flwr/server/superlink/state/state.py,sha256=KpM894R8RE1N0b-s_Nlii6i0TDxj0DRkKa3Vf24Gt70,8127
275
+ flwr/server/superlink/state/in_memory_state.py,sha256=yuyk8R9veNxaSFFhWQg9odUBYPO-1EzcKiulqcXRc1M,15668
276
+ flwr/server/superlink/state/sqlite_state.py,sha256=HJwiD3SMp7yVqcyer-nJbdQ5uS06Cs5qjuN79dSTJWM,35614
277
+ flwr/server/superlink/state/state.py,sha256=c8ZJbZA3x_SMd87M041-QuTDDTEzac4kbrLY4YHmUY8,7829
278
278
  flwr/server/superlink/state/state_factory.py,sha256=Fo8pBQ1WWrVJK5TOEPZ_zgJE69_mfTGjTO6czh6571o,2021
279
- flwr/server/superlink/state/utils.py,sha256=OsF3OOoU4bU4PgLWkypX6EDoFs0L8RP_mHEBG-tVqGA,5227
279
+ flwr/server/superlink/state/utils.py,sha256=AC5u_A2toAW_ocCrng5ioxwoNhobocJAJMgjFvg-dJI,4997
280
280
  flwr/server/typing.py,sha256=5kaRLZuxTEse9A0g7aVna2VhYxU3wTq1f3d3mtw7kXs,1019
281
281
  flwr/server/utils/__init__.py,sha256=pltsPHJoXmUIr3utjwwYxu7_ZAGy5u4MVHzv9iA5Un8,908
282
282
  flwr/server/utils/tensorboard.py,sha256=gEBD8w_5uaIfp5aw5RYH66lYZpd_SfkObHQ7eDd9MUk,5466
@@ -301,8 +301,8 @@ flwr/superexec/exec_grpc.py,sha256=ZPq7EP55Vwj0kRcLVuTCokFqfIgBk-7YmDykZoMKi-c,1
301
301
  flwr/superexec/exec_servicer.py,sha256=TRpwPVl7eI0Y_xlCY6DmVpAo0yFU1gLwzyIeqFw9pyk,4746
302
302
  flwr/superexec/executor.py,sha256=-5J-ZLs-uArro3T2pCq0YQRC65cs18M888nufzdYE4E,2375
303
303
  flwr/superexec/simulation.py,sha256=J6pw-RqCSiUed8I_3MasZH4tl57ZmDebPAHNnbb0-vE,7420
304
- flwr_nightly-1.12.0.dev20241008.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
305
- flwr_nightly-1.12.0.dev20241008.dist-info/METADATA,sha256=zN8XXlFv6FErD9p0u6EZbxrZpYWCteNx8zJGkuPZMmM,15618
306
- flwr_nightly-1.12.0.dev20241008.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
307
- flwr_nightly-1.12.0.dev20241008.dist-info/entry_points.txt,sha256=WUCbqhLEOzjx_lyATIM0-f0e8kOVaQjzwOvyOxHrMhs,434
308
- flwr_nightly-1.12.0.dev20241008.dist-info/RECORD,,
304
+ flwr_nightly-1.12.0.dev20241010.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
305
+ flwr_nightly-1.12.0.dev20241010.dist-info/METADATA,sha256=5IR5Je6HzFFGIR62wjqICS5QuSKMugIizNgcJJ3_8iE,15618
306
+ flwr_nightly-1.12.0.dev20241010.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
307
+ flwr_nightly-1.12.0.dev20241010.dist-info/entry_points.txt,sha256=WUCbqhLEOzjx_lyATIM0-f0e8kOVaQjzwOvyOxHrMhs,434
308
+ flwr_nightly-1.12.0.dev20241010.dist-info/RECORD,,