flwr-nightly 1.9.0.dev20240429__py3-none-any.whl → 1.9.0.dev20240501__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 (28) hide show
  1. flwr/cli/app.py +2 -0
  2. flwr/cli/build.py +151 -0
  3. flwr/cli/new/new.py +1 -0
  4. flwr/cli/new/templates/app/README.md.tpl +1 -1
  5. flwr/cli/new/templates/app/code/client.tensorflow.py.tpl +15 -29
  6. flwr/cli/new/templates/app/code/server.tensorflow.py.tpl +9 -1
  7. flwr/cli/new/templates/app/code/task.tensorflow.py.tpl +29 -0
  8. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
  9. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +1 -1
  10. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
  11. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +1 -1
  12. flwr/client/grpc_rere_client/client_interceptor.py +9 -1
  13. flwr/client/mod/centraldp_mods.py +4 -2
  14. flwr/client/mod/localdp_mod.py +9 -3
  15. flwr/common/logger.py +26 -0
  16. flwr/common/message.py +72 -82
  17. flwr/common/record/recordset.py +5 -4
  18. flwr/server/strategy/dp_adaptive_clipping.py +5 -3
  19. flwr/server/strategy/dp_fixed_clipping.py +6 -3
  20. flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +5 -0
  21. flwr/simulation/__init__.py +2 -2
  22. flwr/simulation/app.py +16 -1
  23. flwr/simulation/run_simulation.py +3 -0
  24. {flwr_nightly-1.9.0.dev20240429.dist-info → flwr_nightly-1.9.0.dev20240501.dist-info}/METADATA +2 -1
  25. {flwr_nightly-1.9.0.dev20240429.dist-info → flwr_nightly-1.9.0.dev20240501.dist-info}/RECORD +28 -26
  26. {flwr_nightly-1.9.0.dev20240429.dist-info → flwr_nightly-1.9.0.dev20240501.dist-info}/entry_points.txt +1 -1
  27. {flwr_nightly-1.9.0.dev20240429.dist-info → flwr_nightly-1.9.0.dev20240501.dist-info}/LICENSE +0 -0
  28. {flwr_nightly-1.9.0.dev20240429.dist-info → flwr_nightly-1.9.0.dev20240501.dist-info}/WHEEL +0 -0
flwr/cli/app.py CHANGED
@@ -16,6 +16,7 @@
16
16
 
17
17
  import typer
18
18
 
19
+ from .build import build
19
20
  from .example import example
20
21
  from .new import new
21
22
  from .run import run
@@ -32,6 +33,7 @@ app = typer.Typer(
32
33
  app.command()(new)
33
34
  app.command()(example)
34
35
  app.command()(run)
36
+ app.command()(build)
35
37
 
36
38
  if __name__ == "__main__":
37
39
  app()
flwr/cli/build.py ADDED
@@ -0,0 +1,151 @@
1
+ # Copyright 2024 Flower Labs GmbH. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+ """Flower command line interface `build` command."""
16
+
17
+ import hashlib
18
+ import os
19
+ import zipfile
20
+ from pathlib import Path
21
+ from typing import Optional
22
+
23
+ import pathspec
24
+ import typer
25
+ from typing_extensions import Annotated
26
+
27
+ from .config_utils import load_and_validate_with_defaults
28
+ from .utils import is_valid_project_name
29
+
30
+
31
+ # pylint: disable=too-many-locals
32
+ def build(
33
+ directory: Annotated[
34
+ Optional[Path],
35
+ typer.Option(help="The Flower project directory to bundle into a FAB"),
36
+ ] = None,
37
+ ) -> None:
38
+ """Build a Flower project into a Flower App Bundle (FAB).
39
+
40
+ You can run `flwr build` without any argument to bundle the current directory:
41
+
42
+ `flwr build`
43
+
44
+ You can also build a specific directory:
45
+
46
+ `flwr build --directory ./projects/flower-hello-world`
47
+ """
48
+ if directory is None:
49
+ directory = Path.cwd()
50
+
51
+ directory = directory.resolve()
52
+ if not directory.is_dir():
53
+ typer.secho(
54
+ f"❌ The path {directory} is not a valid directory.",
55
+ fg=typer.colors.RED,
56
+ bold=True,
57
+ )
58
+ raise typer.Exit(code=1)
59
+
60
+ if not is_valid_project_name(directory.name):
61
+ typer.secho(
62
+ f"❌ The project name {directory.name} is invalid, "
63
+ "a valid project name must start with a letter or an underscore, "
64
+ "and can only contain letters, digits, and underscores.",
65
+ fg=typer.colors.RED,
66
+ bold=True,
67
+ )
68
+ raise typer.Exit(code=1)
69
+
70
+ conf, errors, warnings = load_and_validate_with_defaults(
71
+ directory / "pyproject.toml"
72
+ )
73
+ if conf is None:
74
+ typer.secho(
75
+ "Project configuration could not be loaded.\npyproject.toml is invalid:\n"
76
+ + "\n".join([f"- {line}" for line in errors]),
77
+ fg=typer.colors.RED,
78
+ bold=True,
79
+ )
80
+ raise typer.Exit(code=1)
81
+
82
+ if warnings:
83
+ typer.secho(
84
+ "Project configuration is missing the following "
85
+ "recommended properties:\n" + "\n".join([f"- {line}" for line in warnings]),
86
+ fg=typer.colors.RED,
87
+ bold=True,
88
+ )
89
+
90
+ # Load .gitignore rules if present
91
+ ignore_spec = _load_gitignore(directory)
92
+
93
+ # Set the name of the zip file
94
+ fab_filename = (
95
+ f"{conf['flower']['publisher']}"
96
+ f".{directory.name}"
97
+ f".{conf['project']['version'].replace('.', '-')}.fab"
98
+ )
99
+ list_file_content = ""
100
+
101
+ allowed_extensions = {".py", ".toml", ".md"}
102
+
103
+ with zipfile.ZipFile(fab_filename, "w", zipfile.ZIP_DEFLATED) as fab_file:
104
+ for root, _, files in os.walk(directory, topdown=True):
105
+ # Filter directories and files based on .gitignore
106
+ files = [
107
+ f
108
+ for f in files
109
+ if not ignore_spec.match_file(Path(root) / f)
110
+ and f != fab_filename
111
+ and Path(f).suffix in allowed_extensions
112
+ ]
113
+
114
+ for file in files:
115
+ file_path = Path(root) / file
116
+ archive_path = file_path.relative_to(directory)
117
+ fab_file.write(file_path, archive_path)
118
+
119
+ # Calculate file info
120
+ sha256_hash = _get_sha256_hash(file_path)
121
+ file_size_bits = os.path.getsize(file_path) * 8 # size in bits
122
+ list_file_content += f"{archive_path},{sha256_hash},{file_size_bits}\n"
123
+
124
+ # Add CONTENT and CONTENT.jwt to the zip file
125
+ fab_file.writestr(".info/CONTENT", list_file_content)
126
+
127
+ typer.secho(
128
+ f"🎊 Successfully built {fab_filename}.", fg=typer.colors.GREEN, bold=True
129
+ )
130
+
131
+
132
+ def _get_sha256_hash(file_path: Path) -> str:
133
+ """Calculate the SHA-256 hash of a file."""
134
+ sha256 = hashlib.sha256()
135
+ with open(file_path, "rb") as f:
136
+ while True:
137
+ data = f.read(65536) # Read in 64kB blocks
138
+ if not data:
139
+ break
140
+ sha256.update(data)
141
+ return sha256.hexdigest()
142
+
143
+
144
+ def _load_gitignore(directory: Path) -> pathspec.PathSpec:
145
+ """Load and parse .gitignore file, returning a pathspec."""
146
+ gitignore_path = directory / ".gitignore"
147
+ patterns = ["__pycache__/"] # Default pattern
148
+ if gitignore_path.exists():
149
+ with open(gitignore_path, encoding="UTF-8") as file:
150
+ patterns.extend(file.readlines())
151
+ return pathspec.PathSpec.from_lines("gitwildmatch", patterns)
flwr/cli/new/new.py CHANGED
@@ -153,6 +153,7 @@ def new(
153
153
  # Depending on the framework, generate task.py file
154
154
  frameworks_with_tasks = [
155
155
  MlFramework.PYTORCH.value.lower(),
156
+ MlFramework.TENSORFLOW.value.lower(),
156
157
  ]
157
158
  if framework_str in frameworks_with_tasks:
158
159
  files[f"{import_name}/task.py"] = {
@@ -8,7 +8,7 @@ pip install .
8
8
 
9
9
  ## Run (Simulation Engine)
10
10
 
11
- In the `$import_name` directory, use `flwr run` to run a local simulation:
11
+ In the `$project_name` directory, use `flwr run` to run a local simulation:
12
12
 
13
13
  ```bash
14
14
  flwr run
@@ -1,21 +1,19 @@
1
1
  """$project_name: A Flower / TensorFlow app."""
2
2
 
3
- import os
3
+ from flwr.client import NumPyClient, ClientApp
4
4
 
5
- import tensorflow as tf
6
- from flwr.client import ClientApp, NumPyClient
7
- from flwr_datasets import FederatedDataset
5
+ from $import_name.task import load_data, load_model
8
6
 
9
7
 
10
- os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"
11
-
12
- # Define Flower client
8
+ # Define Flower Client and client_fn
13
9
  class FlowerClient(NumPyClient):
14
- def __init__(self, model, train_data, test_data):
10
+ def __init__(self, model, x_train, y_train, x_test, y_test):
15
11
  self.model = model
16
- self.x_train, self.y_train = train_data
17
- self.x_test, self.y_test = test_data
18
-
12
+ self.x_train = x_train
13
+ self.y_train = y_train
14
+ self.x_test = x_test
15
+ self.y_test = y_test
16
+
19
17
  def get_parameters(self, config):
20
18
  return self.model.get_weights()
21
19
 
@@ -30,25 +28,13 @@ class FlowerClient(NumPyClient):
30
28
  return loss, len(self.x_test), {"accuracy": accuracy}
31
29
 
32
30
 
33
- fds = FederatedDataset(dataset="cifar10", partitioners={"train": 2})
34
-
35
- def client_fn(cid: str):
36
- """Create and return an instance of Flower `Client`."""
37
-
38
- # Load model and data (MobileNetV2, CIFAR-10)
39
- model = tf.keras.applications.MobileNetV2((32, 32, 3), classes=10, weights=None)
40
- model.compile("adam", "sparse_categorical_crossentropy", metrics=["accuracy"])
41
-
42
- # Download and partition dataset
43
- partition = fds.load_partition(int(cid), "train")
44
- partition.set_format("numpy")
45
-
46
- # Divide data on each node: 80% train, 20% test
47
- partition = partition.train_test_split(test_size=0.2, seed=42)
48
- train_data = partition["train"]["img"] / 255.0, partition["train"]["label"]
49
- test_data = partition["test"]["img"] / 255.0, partition["test"]["label"]
31
+ def client_fn(cid):
32
+ # Load model and data
33
+ net = load_model()
34
+ x_train, y_train, x_test, y_test = load_data(int(cid), 2)
50
35
 
51
- return FlowerClient(model, train_data, test_data).to_client()
36
+ # Return Client instance
37
+ return FlowerClient(net, x_train, y_train, x_test, y_test).to_client()
52
38
 
53
39
 
54
40
  # Flower ClientApp
@@ -1,18 +1,26 @@
1
1
  """$project_name: A Flower / TensorFlow app."""
2
2
 
3
+ from flwr.common import ndarrays_to_parameters
3
4
  from flwr.server import ServerApp, ServerConfig
4
5
  from flwr.server.strategy import FedAvg
5
6
 
7
+ from $import_name.task import load_model
8
+
6
9
  # Define config
7
10
  config = ServerConfig(num_rounds=3)
8
11
 
12
+ parameters = ndarrays_to_parameters(load_model().get_weights())
13
+
14
+ # Define strategy
9
15
  strategy = FedAvg(
10
16
  fraction_fit=1.0,
11
17
  fraction_evaluate=1.0,
12
18
  min_available_clients=2,
19
+ initial_parameters=parameters,
13
20
  )
14
21
 
15
- # Flower ServerApp
22
+
23
+ # Create ServerApp
16
24
  app = ServerApp(
17
25
  config=config,
18
26
  strategy=strategy,
@@ -0,0 +1,29 @@
1
+ """$project_name: A Flower / TensorFlow app."""
2
+
3
+ import os
4
+
5
+ import tensorflow as tf
6
+ from flwr_datasets import FederatedDataset
7
+
8
+
9
+ # Make TensorFlow log less verbose
10
+ os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"
11
+
12
+ def load_model():
13
+ # Load model and data (MobileNetV2, CIFAR-10)
14
+ model = tf.keras.applications.MobileNetV2((32, 32, 3), classes=10, weights=None)
15
+ model.compile("adam", "sparse_categorical_crossentropy", metrics=["accuracy"])
16
+ return model
17
+
18
+
19
+ def load_data(partition_id, num_partitions):
20
+ # Download and partition dataset
21
+ fds = FederatedDataset(dataset="cifar10", partitioners={"train": num_partitions})
22
+ partition = fds.load_partition(partition_id, "train")
23
+ partition.set_format("numpy")
24
+
25
+ # Divide data on each node: 80% train, 20% test
26
+ partition = partition.train_test_split(test_size=0.2)
27
+ x_train, y_train = partition["train"]["img"] / 255.0, partition["train"]["label"]
28
+ x_test, y_test = partition["test"]["img"] / 255.0, partition["test"]["label"]
29
+ return x_train, y_train, x_test, y_test
@@ -9,7 +9,7 @@ description = ""
9
9
  authors = [
10
10
  { name = "The Flower Authors", email = "hello@flower.ai" },
11
11
  ]
12
- license = {text = "Apache License (2.0)"}
12
+ license = { text = "Apache License (2.0)" }
13
13
  dependencies = [
14
14
  "flwr[simulation]>=1.8.0,<2.0",
15
15
  "numpy>=1.21.0",
@@ -9,7 +9,7 @@ description = ""
9
9
  authors = [
10
10
  { name = "The Flower Authors", email = "hello@flower.ai" },
11
11
  ]
12
- license = {text = "Apache License (2.0)"}
12
+ license = { text = "Apache License (2.0)" }
13
13
  dependencies = [
14
14
  "flwr[simulation]>=1.8.0,<2.0",
15
15
  "flwr-datasets[vision]>=0.0.2,<1.0.0",
@@ -9,7 +9,7 @@ description = ""
9
9
  authors = [
10
10
  { name = "The Flower Authors", email = "hello@flower.ai" },
11
11
  ]
12
- license = {text = "Apache License (2.0)"}
12
+ license = { text = "Apache License (2.0)" }
13
13
  dependencies = [
14
14
  "flwr[simulation]>=1.8.0,<2.0",
15
15
  "flwr-datasets[vision]>=0.0.2,<1.0.0",
@@ -9,7 +9,7 @@ description = ""
9
9
  authors = [
10
10
  { name = "The Flower Authors", email = "hello@flower.ai" },
11
11
  ]
12
- license = {text = "Apache License (2.0)"}
12
+ license = { text = "Apache License (2.0)" }
13
13
  dependencies = [
14
14
  "flwr[simulation]>=1.8.0,<2.0",
15
15
  "flwr-datasets[vision]>=0.0.2,<1.0.0",
@@ -32,6 +32,7 @@ from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
32
32
  CreateNodeRequest,
33
33
  DeleteNodeRequest,
34
34
  GetRunRequest,
35
+ PingRequest,
35
36
  PullTaskInsRequest,
36
37
  PushTaskResRequest,
37
38
  )
@@ -45,6 +46,7 @@ Request = Union[
45
46
  PullTaskInsRequest,
46
47
  PushTaskResRequest,
47
48
  GetRunRequest,
49
+ PingRequest,
48
50
  ]
49
51
 
50
52
 
@@ -115,7 +117,13 @@ class AuthenticateClientInterceptor(grpc.UnaryUnaryClientInterceptor): # type:
115
117
  postprocess = True
116
118
  elif isinstance(
117
119
  request,
118
- (DeleteNodeRequest, PullTaskInsRequest, PushTaskResRequest, GetRunRequest),
120
+ (
121
+ DeleteNodeRequest,
122
+ PullTaskInsRequest,
123
+ PushTaskResRequest,
124
+ GetRunRequest,
125
+ PingRequest,
126
+ ),
119
127
  ):
120
128
  if self.shared_secret is None:
121
129
  raise RuntimeError("Failure to compute hmac")
@@ -82,7 +82,9 @@ def fixedclipping_mod(
82
82
  clipping_norm,
83
83
  )
84
84
 
85
- log(INFO, "fixedclipping_mod: parameters are clipped by value: %s.", clipping_norm)
85
+ log(
86
+ INFO, "fixedclipping_mod: parameters are clipped by value: %.4f.", clipping_norm
87
+ )
86
88
 
87
89
  fit_res.parameters = ndarrays_to_parameters(client_to_server_params)
88
90
  out_msg.content = compat.fitres_to_recordset(fit_res, keep_input=True)
@@ -146,7 +148,7 @@ def adaptiveclipping_mod(
146
148
  )
147
149
  log(
148
150
  INFO,
149
- "adaptiveclipping_mod: parameters are clipped by value: %s.",
151
+ "adaptiveclipping_mod: parameters are clipped by value: %.4f.",
150
152
  clipping_norm,
151
153
  )
152
154
 
@@ -128,7 +128,9 @@ class LocalDpMod:
128
128
  self.clipping_norm,
129
129
  )
130
130
  log(
131
- INFO, "LocalDpMod: parameters are clipped by value: %s.", self.clipping_norm
131
+ INFO,
132
+ "LocalDpMod: parameters are clipped by value: %.4f.",
133
+ self.clipping_norm,
132
134
  )
133
135
 
134
136
  fit_res.parameters = ndarrays_to_parameters(client_to_server_params)
@@ -137,11 +139,15 @@ class LocalDpMod:
137
139
  add_localdp_gaussian_noise_to_params(
138
140
  fit_res.parameters, self.sensitivity, self.epsilon, self.delta
139
141
  )
142
+
143
+ noise_value_sd = (
144
+ self.sensitivity * np.sqrt(2 * np.log(1.25 / self.delta)) / self.epsilon
145
+ )
140
146
  log(
141
147
  INFO,
142
148
  "LocalDpMod: local DP noise with "
143
- "standard deviation: %s added to parameters.",
144
- self.sensitivity * np.sqrt(2 * np.log(1.25 / self.delta)) / self.epsilon,
149
+ "standard deviation: %.4f added to parameters.",
150
+ noise_value_sd,
145
151
  )
146
152
 
147
153
  out_msg.content = compat.fitres_to_recordset(fit_res, keep_input=True)
flwr/common/logger.py CHANGED
@@ -188,3 +188,29 @@ def warn_deprecated_feature(name: str) -> None:
188
188
  """,
189
189
  name,
190
190
  )
191
+
192
+
193
+ def set_logger_propagation(
194
+ child_logger: logging.Logger, value: bool = True
195
+ ) -> logging.Logger:
196
+ """Set the logger propagation attribute.
197
+
198
+ Parameters
199
+ ----------
200
+ child_logger : logging.Logger
201
+ Child logger object
202
+ value : bool
203
+ Boolean setting for propagation. If True, both parent and child logger
204
+ display messages. Otherwise, only the child logger displays a message.
205
+ This False setting prevents duplicate logs in Colab notebooks.
206
+ Reference: https://stackoverflow.com/a/19561320
207
+
208
+ Returns
209
+ -------
210
+ logging.Logger
211
+ Child logger object with updated propagation setting
212
+ """
213
+ child_logger.propagate = value
214
+ if not child_logger.propagate:
215
+ child_logger.log(logging.DEBUG, "Logger propagate set to False")
216
+ return child_logger
flwr/common/message.py CHANGED
@@ -19,6 +19,7 @@ from __future__ import annotations
19
19
  import time
20
20
  import warnings
21
21
  from dataclasses import dataclass
22
+ from typing import Optional, cast
22
23
 
23
24
  from .record import RecordSet
24
25
 
@@ -55,17 +56,6 @@ class Metadata: # pylint: disable=too-many-instance-attributes
55
56
  is more relevant when conducting simulations.
56
57
  """
57
58
 
58
- _run_id: int
59
- _message_id: str
60
- _src_node_id: int
61
- _dst_node_id: int
62
- _reply_to_message: str
63
- _group_id: str
64
- _ttl: float
65
- _message_type: str
66
- _partition_id: int | None
67
- _created_at: float # Unix timestamp (in seconds) to be set upon message creation
68
-
69
59
  def __init__( # pylint: disable=too-many-arguments
70
60
  self,
71
61
  run_id: int,
@@ -78,95 +68,98 @@ class Metadata: # pylint: disable=too-many-instance-attributes
78
68
  message_type: str,
79
69
  partition_id: int | None = None,
80
70
  ) -> None:
81
- self._run_id = run_id
82
- self._message_id = message_id
83
- self._src_node_id = src_node_id
84
- self._dst_node_id = dst_node_id
85
- self._reply_to_message = reply_to_message
86
- self._group_id = group_id
87
- self._ttl = ttl
88
- self._message_type = message_type
89
- self._partition_id = partition_id
71
+ var_dict = {
72
+ "_run_id": run_id,
73
+ "_message_id": message_id,
74
+ "_src_node_id": src_node_id,
75
+ "_dst_node_id": dst_node_id,
76
+ "_reply_to_message": reply_to_message,
77
+ "_group_id": group_id,
78
+ "_ttl": ttl,
79
+ "_message_type": message_type,
80
+ "_partition_id": partition_id,
81
+ }
82
+ self.__dict__.update(var_dict)
90
83
 
91
84
  @property
92
85
  def run_id(self) -> int:
93
86
  """An identifier for the current run."""
94
- return self._run_id
87
+ return cast(int, self.__dict__["_run_id"])
95
88
 
96
89
  @property
97
90
  def message_id(self) -> str:
98
91
  """An identifier for the current message."""
99
- return self._message_id
92
+ return cast(str, self.__dict__["_message_id"])
100
93
 
101
94
  @property
102
95
  def src_node_id(self) -> int:
103
96
  """An identifier for the node sending this message."""
104
- return self._src_node_id
97
+ return cast(int, self.__dict__["_src_node_id"])
105
98
 
106
99
  @property
107
100
  def reply_to_message(self) -> str:
108
101
  """An identifier for the message this message replies to."""
109
- return self._reply_to_message
102
+ return cast(str, self.__dict__["_reply_to_message"])
110
103
 
111
104
  @property
112
105
  def dst_node_id(self) -> int:
113
106
  """An identifier for the node receiving this message."""
114
- return self._dst_node_id
107
+ return cast(int, self.__dict__["_dst_node_id"])
115
108
 
116
109
  @dst_node_id.setter
117
110
  def dst_node_id(self, value: int) -> None:
118
111
  """Set dst_node_id."""
119
- self._dst_node_id = value
112
+ self.__dict__["_dst_node_id"] = value
120
113
 
121
114
  @property
122
115
  def group_id(self) -> str:
123
116
  """An identifier for grouping messages."""
124
- return self._group_id
117
+ return cast(str, self.__dict__["_group_id"])
125
118
 
126
119
  @group_id.setter
127
120
  def group_id(self, value: str) -> None:
128
121
  """Set group_id."""
129
- self._group_id = value
122
+ self.__dict__["_group_id"] = value
130
123
 
131
124
  @property
132
125
  def created_at(self) -> float:
133
126
  """Unix timestamp when the message was created."""
134
- return self._created_at
127
+ return cast(float, self.__dict__["_created_at"])
135
128
 
136
129
  @created_at.setter
137
130
  def created_at(self, value: float) -> None:
138
- """Set creation timestamp for this messages."""
139
- self._created_at = value
131
+ """Set creation timestamp for this message."""
132
+ self.__dict__["_created_at"] = value
140
133
 
141
134
  @property
142
135
  def ttl(self) -> float:
143
136
  """Time-to-live for this message."""
144
- return self._ttl
137
+ return cast(float, self.__dict__["_ttl"])
145
138
 
146
139
  @ttl.setter
147
140
  def ttl(self, value: float) -> None:
148
141
  """Set ttl."""
149
- self._ttl = value
142
+ self.__dict__["_ttl"] = value
150
143
 
151
144
  @property
152
145
  def message_type(self) -> str:
153
146
  """A string that encodes the action to be executed on the receiving end."""
154
- return self._message_type
147
+ return cast(str, self.__dict__["_message_type"])
155
148
 
156
149
  @message_type.setter
157
150
  def message_type(self, value: str) -> None:
158
151
  """Set message_type."""
159
- self._message_type = value
152
+ self.__dict__["_message_type"] = value
160
153
 
161
154
  @property
162
155
  def partition_id(self) -> int | None:
163
156
  """An identifier telling which data partition a ClientApp should use."""
164
- return self._partition_id
157
+ return cast(int, self.__dict__["_partition_id"])
165
158
 
166
159
  @partition_id.setter
167
160
  def partition_id(self, value: int) -> None:
168
- """Set patition_id."""
169
- self._partition_id = value
161
+ """Set partition_id."""
162
+ self.__dict__["_partition_id"] = value
170
163
 
171
164
 
172
165
  @dataclass
@@ -181,22 +174,22 @@ class Error:
181
174
  A reason for why the error arose (e.g. an exception stack-trace)
182
175
  """
183
176
 
184
- _code: int
185
- _reason: str | None = None
186
-
187
177
  def __init__(self, code: int, reason: str | None = None) -> None:
188
- self._code = code
189
- self._reason = reason
178
+ var_dict = {
179
+ "_code": code,
180
+ "_reason": reason,
181
+ }
182
+ self.__dict__.update(var_dict)
190
183
 
191
184
  @property
192
185
  def code(self) -> int:
193
186
  """Error code."""
194
- return self._code
187
+ return cast(int, self.__dict__["_code"])
195
188
 
196
189
  @property
197
190
  def reason(self) -> str | None:
198
191
  """Reason reported about the error."""
199
- return self._reason
192
+ return cast(Optional[str], self.__dict__["_reason"])
200
193
 
201
194
 
202
195
  @dataclass
@@ -215,88 +208,70 @@ class Message:
215
208
  when processing another message.
216
209
  """
217
210
 
218
- _metadata: Metadata
219
- _content: RecordSet | None = None
220
- _error: Error | None = None
221
-
222
211
  def __init__(
223
212
  self,
224
213
  metadata: Metadata,
225
214
  content: RecordSet | None = None,
226
215
  error: Error | None = None,
227
216
  ) -> None:
228
- self._metadata = metadata
229
-
230
- # Set message creation timestamp
231
- self._metadata.created_at = time.time()
232
-
233
217
  if not (content is None) ^ (error is None):
234
218
  raise ValueError("Either `content` or `error` must be set, but not both.")
235
219
 
236
- self._content = content
237
- self._error = error
220
+ metadata.created_at = time.time() # Set the message creation timestamp
221
+ var_dict = {
222
+ "_metadata": metadata,
223
+ "_content": content,
224
+ "_error": error,
225
+ }
226
+ self.__dict__.update(var_dict)
238
227
 
239
228
  @property
240
229
  def metadata(self) -> Metadata:
241
230
  """A dataclass including information about the message to be executed."""
242
- return self._metadata
231
+ return cast(Metadata, self.__dict__["_metadata"])
243
232
 
244
233
  @property
245
234
  def content(self) -> RecordSet:
246
235
  """The content of this message."""
247
- if self._content is None:
236
+ if self.__dict__["_content"] is None:
248
237
  raise ValueError(
249
238
  "Message content is None. Use <message>.has_content() "
250
239
  "to check if a message has content."
251
240
  )
252
- return self._content
241
+ return cast(RecordSet, self.__dict__["_content"])
253
242
 
254
243
  @content.setter
255
244
  def content(self, value: RecordSet) -> None:
256
245
  """Set content."""
257
- if self._error is None:
258
- self._content = value
246
+ if self.__dict__["_error"] is None:
247
+ self.__dict__["_content"] = value
259
248
  else:
260
249
  raise ValueError("A message with an error set cannot have content.")
261
250
 
262
251
  @property
263
252
  def error(self) -> Error:
264
253
  """Error captured by this message."""
265
- if self._error is None:
254
+ if self.__dict__["_error"] is None:
266
255
  raise ValueError(
267
256
  "Message error is None. Use <message>.has_error() "
268
257
  "to check first if a message carries an error."
269
258
  )
270
- return self._error
259
+ return cast(Error, self.__dict__["_error"])
271
260
 
272
261
  @error.setter
273
262
  def error(self, value: Error) -> None:
274
263
  """Set error."""
275
264
  if self.has_content():
276
265
  raise ValueError("A message with content set cannot carry an error.")
277
- self._error = value
266
+ self.__dict__["_error"] = value
278
267
 
279
268
  def has_content(self) -> bool:
280
269
  """Return True if message has content, else False."""
281
- return self._content is not None
270
+ return self.__dict__["_content"] is not None
282
271
 
283
272
  def has_error(self) -> bool:
284
273
  """Return True if message has an error, else False."""
285
- return self._error is not None
286
-
287
- def _create_reply_metadata(self, ttl: float) -> Metadata:
288
- """Construct metadata for a reply message."""
289
- return Metadata(
290
- run_id=self.metadata.run_id,
291
- message_id="",
292
- src_node_id=self.metadata.dst_node_id,
293
- dst_node_id=self.metadata.src_node_id,
294
- reply_to_message=self.metadata.message_id,
295
- group_id=self.metadata.group_id,
296
- ttl=ttl,
297
- message_type=self.metadata.message_type,
298
- partition_id=self.metadata.partition_id,
299
- )
274
+ return self.__dict__["_error"] is not None
300
275
 
301
276
  def create_error_reply(self, error: Error, ttl: float | None = None) -> Message:
302
277
  """Construct a reply message indicating an error happened.
@@ -323,7 +298,7 @@ class Message:
323
298
  # message creation)
324
299
  ttl_ = DEFAULT_TTL if ttl is None else ttl
325
300
  # Create reply with error
326
- message = Message(metadata=self._create_reply_metadata(ttl_), error=error)
301
+ message = Message(metadata=_create_reply_metadata(self, ttl_), error=error)
327
302
 
328
303
  if ttl is None:
329
304
  # Set TTL equal to the remaining time for the received message to expire
@@ -369,7 +344,7 @@ class Message:
369
344
  ttl_ = DEFAULT_TTL if ttl is None else ttl
370
345
 
371
346
  message = Message(
372
- metadata=self._create_reply_metadata(ttl_),
347
+ metadata=_create_reply_metadata(self, ttl_),
373
348
  content=content,
374
349
  )
375
350
 
@@ -381,3 +356,18 @@ class Message:
381
356
  message.metadata.ttl = ttl
382
357
 
383
358
  return message
359
+
360
+
361
+ def _create_reply_metadata(msg: Message, ttl: float) -> Metadata:
362
+ """Construct metadata for a reply message."""
363
+ return Metadata(
364
+ run_id=msg.metadata.run_id,
365
+ message_id="",
366
+ src_node_id=msg.metadata.dst_node_id,
367
+ dst_node_id=msg.metadata.src_node_id,
368
+ reply_to_message=msg.metadata.message_id,
369
+ group_id=msg.metadata.group_id,
370
+ ttl=ttl,
371
+ message_type=msg.metadata.message_type,
372
+ partition_id=msg.metadata.partition_id,
373
+ )
@@ -24,6 +24,7 @@ from .parametersrecord import ParametersRecord
24
24
  from .typeddict import TypedDict
25
25
 
26
26
 
27
+ @dataclass
27
28
  class RecordSetData:
28
29
  """Inner data container for the RecordSet class."""
29
30
 
@@ -97,22 +98,22 @@ class RecordSet:
97
98
  metrics_records=metrics_records,
98
99
  configs_records=configs_records,
99
100
  )
100
- setattr(self, "_data", data) # noqa
101
+ self.__dict__["_data"] = data
101
102
 
102
103
  @property
103
104
  def parameters_records(self) -> TypedDict[str, ParametersRecord]:
104
105
  """Dictionary holding ParametersRecord instances."""
105
- data = cast(RecordSetData, getattr(self, "_data")) # noqa
106
+ data = cast(RecordSetData, self.__dict__["_data"])
106
107
  return data.parameters_records
107
108
 
108
109
  @property
109
110
  def metrics_records(self) -> TypedDict[str, MetricsRecord]:
110
111
  """Dictionary holding MetricsRecord instances."""
111
- data = cast(RecordSetData, getattr(self, "_data")) # noqa
112
+ data = cast(RecordSetData, self.__dict__["_data"])
112
113
  return data.metrics_records
113
114
 
114
115
  @property
115
116
  def configs_records(self) -> TypedDict[str, ConfigsRecord]:
116
117
  """Dictionary holding ConfigsRecord instances."""
117
- data = cast(RecordSetData, getattr(self, "_data")) # noqa
118
+ data = cast(RecordSetData, self.__dict__["_data"])
118
119
  return data.configs_records
@@ -200,7 +200,7 @@ class DifferentialPrivacyServerSideAdaptiveClipping(Strategy):
200
200
 
201
201
  log(
202
202
  INFO,
203
- "aggregate_fit: parameters are clipped by value: %s.",
203
+ "aggregate_fit: parameters are clipped by value: %.4f.",
204
204
  self.clipping_norm,
205
205
  )
206
206
 
@@ -234,7 +234,8 @@ class DifferentialPrivacyServerSideAdaptiveClipping(Strategy):
234
234
  )
235
235
  log(
236
236
  INFO,
237
- "aggregate_fit: central DP noise with standard deviation: %s added to parameters.",
237
+ "aggregate_fit: central DP noise with "
238
+ "standard deviation: %.4f added to parameters.",
238
239
  compute_stdv(
239
240
  self.noise_multiplier, self.clipping_norm, self.num_sampled_clients
240
241
  ),
@@ -424,7 +425,8 @@ class DifferentialPrivacyClientSideAdaptiveClipping(Strategy):
424
425
  )
425
426
  log(
426
427
  INFO,
427
- "aggregate_fit: central DP noise with standard deviation: %s added to parameters.",
428
+ "aggregate_fit: central DP noise with "
429
+ "standard deviation: %.4f added to parameters.",
428
430
  compute_stdv(
429
431
  self.noise_multiplier, self.clipping_norm, self.num_sampled_clients
430
432
  ),
@@ -158,7 +158,7 @@ class DifferentialPrivacyServerSideFixedClipping(Strategy):
158
158
  )
159
159
  log(
160
160
  INFO,
161
- "aggregate_fit: parameters are clipped by value: %s.",
161
+ "aggregate_fit: parameters are clipped by value: %.4f.",
162
162
  self.clipping_norm,
163
163
  )
164
164
  # Convert back to parameters
@@ -180,7 +180,8 @@ class DifferentialPrivacyServerSideFixedClipping(Strategy):
180
180
 
181
181
  log(
182
182
  INFO,
183
- "aggregate_fit: central DP noise with standard deviation: %s added to parameters.",
183
+ "aggregate_fit: central DP noise with "
184
+ "standard deviation: %.4f added to parameters.",
184
185
  compute_stdv(
185
186
  self.noise_multiplier, self.clipping_norm, self.num_sampled_clients
186
187
  ),
@@ -337,11 +338,13 @@ class DifferentialPrivacyClientSideFixedClipping(Strategy):
337
338
  )
338
339
  log(
339
340
  INFO,
340
- "aggregate_fit: central DP noise with standard deviation: %s added to parameters.",
341
+ "aggregate_fit: central DP noise with "
342
+ "standard deviation: %.4f added to parameters.",
341
343
  compute_stdv(
342
344
  self.noise_multiplier, self.clipping_norm, self.num_sampled_clients
343
345
  ),
344
346
  )
347
+
345
348
  return aggregated_params, metrics
346
349
 
347
350
  def aggregate_evaluate(
@@ -36,6 +36,8 @@ from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
36
36
  DeleteNodeResponse,
37
37
  GetRunRequest,
38
38
  GetRunResponse,
39
+ PingRequest,
40
+ PingResponse,
39
41
  PullTaskInsRequest,
40
42
  PullTaskInsResponse,
41
43
  PushTaskResRequest,
@@ -51,6 +53,7 @@ Request = Union[
51
53
  PullTaskInsRequest,
52
54
  PushTaskResRequest,
53
55
  GetRunRequest,
56
+ PingRequest,
54
57
  ]
55
58
 
56
59
  Response = Union[
@@ -59,6 +62,7 @@ Response = Union[
59
62
  PullTaskInsResponse,
60
63
  PushTaskResResponse,
61
64
  GetRunResponse,
65
+ PingResponse,
62
66
  ]
63
67
 
64
68
 
@@ -140,6 +144,7 @@ class AuthenticateServerInterceptor(grpc.ServerInterceptor): # type: ignore
140
144
  PullTaskInsRequest,
141
145
  PushTaskResRequest,
142
146
  GetRunRequest,
147
+ PingRequest,
143
148
  ),
144
149
  ):
145
150
  hmac_value = base64.urlsafe_b64decode(
@@ -17,7 +17,7 @@
17
17
 
18
18
  import importlib
19
19
 
20
- from flwr.simulation.run_simulation import run_simulation, run_simulation_from_cli
20
+ from flwr.simulation.run_simulation import run_simulation
21
21
 
22
22
  is_ray_installed = importlib.util.find_spec("ray") is not None
23
23
 
@@ -36,4 +36,4 @@ To install the necessary dependencies, install `flwr` with the `simulation` extr
36
36
  raise ImportError(RAY_IMPORT_ERROR)
37
37
 
38
38
 
39
- __all__ = ["start_simulation", "run_simulation_from_cli", "run_simulation"]
39
+ __all__ = ["start_simulation", "run_simulation"]
flwr/simulation/app.py CHANGED
@@ -15,6 +15,8 @@
15
15
  """Flower simulation app."""
16
16
 
17
17
 
18
+ import asyncio
19
+ import logging
18
20
  import sys
19
21
  import threading
20
22
  import traceback
@@ -27,7 +29,7 @@ from ray.util.scheduling_strategies import NodeAffinitySchedulingStrategy
27
29
 
28
30
  from flwr.client import ClientFn
29
31
  from flwr.common import EventType, event
30
- from flwr.common.logger import log
32
+ from flwr.common.logger import log, set_logger_propagation
31
33
  from flwr.server.client_manager import ClientManager
32
34
  from flwr.server.history import History
33
35
  from flwr.server.server import Server, init_defaults, run_fl
@@ -156,6 +158,7 @@ def start_simulation(
156
158
  is an advanced feature. For all details, please refer to the Ray documentation:
157
159
  https://docs.ray.io/en/latest/ray-core/scheduling/index.html
158
160
 
161
+
159
162
  Returns
160
163
  -------
161
164
  hist : flwr.server.history.History
@@ -167,6 +170,18 @@ def start_simulation(
167
170
  {"num_clients": len(clients_ids) if clients_ids is not None else num_clients},
168
171
  )
169
172
 
173
+ # Set logger propagation
174
+ loop: Optional[asyncio.AbstractEventLoop] = None
175
+ try:
176
+ loop = asyncio.get_running_loop()
177
+ except RuntimeError:
178
+ loop = None
179
+ finally:
180
+ if loop and loop.is_running():
181
+ # Set logger propagation to False to prevent duplicated log output in Colab.
182
+ logger = logging.getLogger("flwr")
183
+ _ = set_logger_propagation(logger, False)
184
+
170
185
  # Initialize server and server config
171
186
  initialized_server, initialized_config = init_defaults(
172
187
  server=server,
@@ -28,6 +28,7 @@ import grpc
28
28
 
29
29
  from flwr.client import ClientApp
30
30
  from flwr.common import EventType, event, log
31
+ from flwr.common.logger import set_logger_propagation
31
32
  from flwr.common.typing import ConfigsRecordValues
32
33
  from flwr.server.driver import Driver, GrpcDriver
33
34
  from flwr.server.run_serverapp import run
@@ -364,6 +365,8 @@ def _run_simulation(
364
365
 
365
366
  finally:
366
367
  if run_in_thread:
368
+ # Set logger propagation to False to prevent duplicated log output in Colab.
369
+ logger = set_logger_propagation(logger, False)
367
370
  log(DEBUG, "Starting Simulation Engine on a new thread.")
368
371
  simulation_engine_th = threading.Thread(target=_main_loop, args=args)
369
372
  simulation_engine_th.start()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: flwr-nightly
3
- Version: 1.9.0.dev20240429
3
+ Version: 1.9.0.dev20240501
4
4
  Summary: Flower: A Friendly Federated Learning Framework
5
5
  Home-page: https://flower.ai
6
6
  License: Apache-2.0
@@ -36,6 +36,7 @@ Requires-Dist: cryptography (>=42.0.4,<43.0.0)
36
36
  Requires-Dist: grpcio (>=1.60.0,<2.0.0)
37
37
  Requires-Dist: iterators (>=0.0.2,<0.0.3)
38
38
  Requires-Dist: numpy (>=1.21.0,<2.0.0)
39
+ Requires-Dist: pathspec (>=0.12.1,<0.13.0)
39
40
  Requires-Dist: protobuf (>=4.25.2,<5.0.0)
40
41
  Requires-Dist: pycryptodome (>=3.18.0,<4.0.0)
41
42
  Requires-Dist: ray (==2.6.3) ; (python_version >= "3.8" and python_version < "3.12") and (extra == "simulation")
@@ -1,29 +1,31 @@
1
1
  flwr/__init__.py,sha256=VmBWedrCxqmt4QvUHBLqyVEH6p7zaFMD_oCHerXHSVw,937
2
2
  flwr/cli/__init__.py,sha256=cZJVgozlkC6Ni2Hd_FAIrqefrkCGOV18fikToq-6iLw,720
3
- flwr/cli/app.py,sha256=38thPnMydBmNAxNE9mz4By-KdRUhJfoUgeDuAxMYF_U,1095
3
+ flwr/cli/app.py,sha256=IFu7V_xdexF1T9sUsvgYWVFW5wEPHHcBWHJRPdlX38U,1141
4
+ flwr/cli/build.py,sha256=W30wnPSgFuHRnGB9G_vKO14rsaibWk7m-jv9r8rDqo4,5106
4
5
  flwr/cli/config_utils.py,sha256=Hql5A5hbSpJ51hgpwaTkKqfPoaZN4Zq7FZfBuQYLMcQ,4899
5
6
  flwr/cli/example.py,sha256=1bGDYll3BXQY2kRqSN-oICqS5n1b9m0g0RvXTopXHl4,2215
6
7
  flwr/cli/new/__init__.py,sha256=cQzK1WH4JP2awef1t2UQ2xjl1agVEz9rwutV18SWV1k,789
7
- flwr/cli/new/new.py,sha256=8R6Vd07s1wVhqPWbN8Jz3_-bJ3CKkdipjbw0BmodJQY,5989
8
+ flwr/cli/new/new.py,sha256=x0cYNCYTCwbWiM7K58y4ViJl-Hd_pZ7jUmgaCNSP9v8,6035
8
9
  flwr/cli/new/templates/__init__.py,sha256=4luU8RL-CK8JJCstQ_ON809W9bNTkY1l9zSaPKBkgwY,725
9
10
  flwr/cli/new/templates/app/.gitignore.tpl,sha256=XixnHdyeMB2vwkGtGnwHqoWpH-9WChdyG0GXe57duhc,3078
10
- flwr/cli/new/templates/app/README.md.tpl,sha256=N-tEnukHAtk9HpwR1nVdnFruzQ6s2hpqzRgjVL_1Cjw,667
11
+ flwr/cli/new/templates/app/README.md.tpl,sha256=_qGtgpKYKoCJVjQnvlBMKvFs_1gzTcL908I3KJg0oAM,668
11
12
  flwr/cli/new/templates/app/__init__.py,sha256=DU7QMY7IhMQyuwm_tja66xU0KXTWQFqzfTqwg-_NJdE,729
12
13
  flwr/cli/new/templates/app/code/__init__.py,sha256=EM6vfvgAILKPaPn7H1wMV1Wi01WyZCP_Eg6NxD6oWg8,736
13
14
  flwr/cli/new/templates/app/code/__init__.py.tpl,sha256=olwrBeJemHNBWvjc6gJURloFRqW40dAy7FRQA5pDqHU,21
14
15
  flwr/cli/new/templates/app/code/client.numpy.py.tpl,sha256=mTh7Y_jOJrPUvDYHVJy4wJCnjXZV_q-jlDkB07U5GSk,521
15
16
  flwr/cli/new/templates/app/code/client.pytorch.py.tpl,sha256=MgCtMSv1Th16Faod11HubVaARkLYt7vS9RYH962-2pk,1172
16
17
  flwr/cli/new/templates/app/code/client.sklearn.py.tpl,sha256=S71SZiHaRXtKqUk3m5Elc_c6HhKAIKLalrKOQ3p20No,2801
17
- flwr/cli/new/templates/app/code/client.tensorflow.py.tpl,sha256=N9SbnI65r2K9FHV_wn4JSpmVeyYpD0qEMehbHcGm4t0,1911
18
+ flwr/cli/new/templates/app/code/client.tensorflow.py.tpl,sha256=dxrTO9JwYrDBjLsmCiRLetN9KxbnWRTeGA0BQbnOu_A,1280
18
19
  flwr/cli/new/templates/app/code/server.numpy.py.tpl,sha256=fRxrDXV7pB1aDhQUXMBmrCsC1zp0uKwsBxZBx1JzbHA,248
19
20
  flwr/cli/new/templates/app/code/server.pytorch.py.tpl,sha256=ltdsnFSvFGPcycVmRL4ITlr-TV0CmmXcperZe7Vamow,593
20
21
  flwr/cli/new/templates/app/code/server.sklearn.py.tpl,sha256=cLzOpQzGIUzEazuFsjBpXAQUNPy6in6zR33SCqhix6o,341
21
- flwr/cli/new/templates/app/code/server.tensorflow.py.tpl,sha256=GUGH8c_6cxgUB9obVJPaA4thxI7OVXsItyfQDsn9E5k,371
22
+ flwr/cli/new/templates/app/code/server.tensorflow.py.tpl,sha256=gsNrWCKTU77_65_gw9nlp1LSQojgP5QQIWILvqdjx2s,579
22
23
  flwr/cli/new/templates/app/code/task.pytorch.py.tpl,sha256=NvajdZN-eTyfdqKK0v2MrvWITXw9BjJ3Ri5c1haPJDs,3684
23
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl,sha256=9I15Y9KUneVkCXwlvZs1H5H1a2J-NVjUVyWjqBRS944,521
24
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl,sha256=717GFDGCnT-4Q-NaBT1R-1u47VkfSejG0ftaLPV1vSc,590
25
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl,sha256=uz4tHDk54vcRDd4mjxGbBQIL5nqcLpay_9XlR-Sbr58,570
26
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl,sha256=BJvyU78wnym8R7khk1DCO98HZb0JPQ2GR63aU7RUWsE,569
24
+ flwr/cli/new/templates/app/code/task.tensorflow.py.tpl,sha256=cPOUUS07QbblT9PGFucwu9lY1clRA4-W4DQGA7cpcao,1044
25
+ flwr/cli/new/templates/app/pyproject.numpy.toml.tpl,sha256=m276SKsjOZ4awGdXasUKvLim66agrpAsPNP9-PN6q4I,523
26
+ flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl,sha256=QikP3u5ht6qr2BkgcnvB3rCYK7jt1cS0nAm7V8g_zFc,592
27
+ flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl,sha256=IO5iIlyKSBxZCCf48iqEyRWeG1jmVx2tO_s2iE7FpHo,572
28
+ flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl,sha256=CpjCOHyJ3zdIlkXQ1An6fEKN9l7rIStx43g4SsIwbkw,571
27
29
  flwr/cli/run/__init__.py,sha256=oCd6HmQDx-sqver1gecgx-uMA38BLTSiiKpl7RGNceg,789
28
30
  flwr/cli/run/run.py,sha256=jr_J7Cbcyuj1MXIbuwU86htHdFI7XogsBrdGl7P4aYY,2334
29
31
  flwr/cli/utils.py,sha256=px-M-IlBLu6Ez-Sc9tWhsJRjWurRaZTmxB9ASz8wurk,4119
@@ -35,16 +37,16 @@ flwr/client/dpfedavg_numpy_client.py,sha256=9Tnig4iml2J88HBKNahegjXjbfvIQyBtaIQa
35
37
  flwr/client/grpc_client/__init__.py,sha256=LsnbqXiJhgQcB0XzAlUQgPx011Uf7Y7yabIC1HxivJ8,735
36
38
  flwr/client/grpc_client/connection.py,sha256=KWbBwuvn1-2wjrAKteydGCZC_7A2zmEjk3DycQWafrA,8993
37
39
  flwr/client/grpc_rere_client/__init__.py,sha256=avn6W_vHEM_yZEB1S7hCZgnTbXb6ZujqRP_vAzyXu-0,752
38
- flwr/client/grpc_rere_client/client_interceptor.py,sha256=cZZHd_lVlVuyPrhXf3mB4_Zpmhpmrv6-18E9XJisImE,4761
40
+ flwr/client/grpc_rere_client/client_interceptor.py,sha256=rDBXRVo-d-rflxJ6Kw3eDfBmvChdUHkzRw5eP-bpe6Y,4903
39
41
  flwr/client/grpc_rere_client/connection.py,sha256=gSSJJ9pSe5SgUb1Ey-xcrVK6xArUkwq0yGdav0h2kww,9597
40
42
  flwr/client/heartbeat.py,sha256=cx37mJBH8LyoIN4Lks85wtqT1mnU5GulQnr4pGCvAq0,2404
41
43
  flwr/client/message_handler/__init__.py,sha256=abHvBRJJiiaAMNgeILQbMOa6h8WqMK2BcnvxwQZFpic,719
42
44
  flwr/client/message_handler/message_handler.py,sha256=ml_FlduAJ5pxO31n1tKRrWfQRSxkMgKLbwXXcRsNSos,6553
43
45
  flwr/client/message_handler/task_handler.py,sha256=ZDJBKmrn2grRMNl1rU1iGs7FiMHL5VmZiSp_6h9GHVU,1824
44
46
  flwr/client/mod/__init__.py,sha256=apqhs7bslrGgQK91JR56mEcwj5JihL0NF_XKQwqaQuo,1143
45
- flwr/client/mod/centraldp_mods.py,sha256=sm4XXFl1oeo8kazyHTR8uHzd4x6Roxk_jk482evgf-Y,5397
47
+ flwr/client/mod/centraldp_mods.py,sha256=UGwNuqpmOWfLdfJITFgdi1TG-nLjuSb-cbEyoyfDgxQ,5415
46
48
  flwr/client/mod/comms_mods.py,sha256=hCj2mSey12D8rehhyGl4JwmVq8iFd-wlzZj8lzexGOs,2623
47
- flwr/client/mod/localdp_mod.py,sha256=L2IPZqgNhH7kpSckvtAzm-qWJMZ7BrNL1D8l-Zq2Wb4,4918
49
+ flwr/client/mod/localdp_mod.py,sha256=K5kZnv9wQUqLYBrE2gExthbcl2YCNRK4VTbmYYqTNOs,5012
48
50
  flwr/client/mod/secure_aggregation/__init__.py,sha256=Qo2R-NqsyoP0oX73TyDfQRu9P6DCNXhgqGbhmGIBaJA,849
49
51
  flwr/client/mod/secure_aggregation/secagg_mod.py,sha256=wI9tuIEvMUETz-wVIEbPYvh-1nK9CEylBLGoVpNhL94,1095
50
52
  flwr/client/mod/secure_aggregation/secaggplus_mod.py,sha256=fZTfIELkYS64lpgxQKL66s-QHjCn-159qfLoNoIMJjc,19699
@@ -67,8 +69,8 @@ flwr/common/differential_privacy_constants.py,sha256=c7b7tqgvT7yMK0XN9ndiTBs4mQf
67
69
  flwr/common/dp.py,sha256=Hc3lLHihjexbJaD_ft31gdv9XRcwOTgDBwJzICuok3A,2004
68
70
  flwr/common/exit_handlers.py,sha256=2Nt0wLhc17KQQsLPFSRAjjhUiEFfJK6tNozdGiIY4Fs,2812
69
71
  flwr/common/grpc.py,sha256=Yx_YFK24cU4U81RpXrdVwEVY_jTy4RE19cHtBxE2XOE,2460
70
- flwr/common/logger.py,sha256=3hfKun9YISWj4i_QhxgZdnaHJc4x-QvFJQJTKHZ2KHs,6096
71
- flwr/common/message.py,sha256=NvxiWT9YI8GmIt2r3EPVPFFAFQo3xhP09mvnAxjHivQ,12385
72
+ flwr/common/logger.py,sha256=s1ZTh6nCeGZh0MxbCG8zHYZAeQ9JNDpw8k6qtxSCCzM,6920
73
+ flwr/common/message.py,sha256=78SN-HZpqbsnap5JFbdnmqR1dL0juK1zDRiixg3Jbl4,12686
72
74
  flwr/common/object_ref.py,sha256=ELoUCAFO-vbjJC41CGpa-WBG2SLYe3ErW-d9YCG3zqA,4961
73
75
  flwr/common/parameter.py,sha256=-bFAUayToYDF50FZGrBC1hQYJCQDtB2bbr3ZuVLMtdE,2095
74
76
  flwr/common/pyproject.py,sha256=EI_ovbCHGmhYrdPx0RSDi5EkFZFof-8m1PA54c0ZTjc,1385
@@ -77,7 +79,7 @@ flwr/common/record/configsrecord.py,sha256=VKeFEYa6cneyStqQlUOaKj12by5ZI_NXYR25L
77
79
  flwr/common/record/conversion_utils.py,sha256=n3I3SI2P6hUjyxbWNc0QAch-SEhfMK6Hm-UUaplAlUc,1393
78
80
  flwr/common/record/metricsrecord.py,sha256=Yv99oRa3LzFgSfwl903S8sB8rAgr3Sv6i6ovW7pdHsA,3923
79
81
  flwr/common/record/parametersrecord.py,sha256=WSqtRrYvI-mRzkEwv5s-EG-yE5uizJ8zy9aczwRG-1E,4849
80
- flwr/common/record/recordset.py,sha256=o3cXGGEFYRqzO8AzYmFxf5cb4CZIkaw-_lSk4kfTg0Q,4553
82
+ flwr/common/record/recordset.py,sha256=ZIDX_ZMpSRwFyD9PZQBDSM_UhRzzafgna8hDJj-egZ4,4533
81
83
  flwr/common/record/typeddict.py,sha256=2NW8JF27p1uNWaqDbJ7bMkItA5x4ygYT8aHrf8NaqnE,3879
82
84
  flwr/common/recordset_compat.py,sha256=BjxeuvlCaP94yIiKOyFFTRBUH_lprFWSLo8U8q3BDbs,13798
83
85
  flwr/common/retry_invoker.py,sha256=dQY5fPIKhy9OiFswZhLxA9fB455u-DYCvDVcFJmrPDk,11707
@@ -144,8 +146,8 @@ flwr/server/server_config.py,sha256=CZaHVAsMvGLjpWVcLPkiYxgJN4xfIyAiUrCI3fETKY4,
144
146
  flwr/server/strategy/__init__.py,sha256=7eVZ3hQEg2BgA_usAeL6tsLp9T6XI1VYYoFy08Xn-ew,2836
145
147
  flwr/server/strategy/aggregate.py,sha256=QyRIJtI5gnuY1NbgrcrOvkHxGIxBvApq7d9Y4xl-6W4,13468
146
148
  flwr/server/strategy/bulyan.py,sha256=8GsSVJzRSoSWE2zQUKqC3Z795grdN9xpmc3MSGGXnzM,6532
147
- flwr/server/strategy/dp_adaptive_clipping.py,sha256=HxMfKQBZAS-9eELhPO4LSyyX0WRbv9ka4QOTI1fFIPA,17458
148
- flwr/server/strategy/dp_fixed_clipping.py,sha256=3_sEQBLjdnhFbqzhrGVQFPhAB1F8oziqyv_1aJr0EJk,12904
149
+ flwr/server/strategy/dp_adaptive_clipping.py,sha256=AOI6Y5hUMSdEShWNvlZtRZasvIkfpl6gXNXk-wbpBKY,17502
150
+ flwr/server/strategy/dp_fixed_clipping.py,sha256=PhQOKI5gJrzlCTswU8ygGZzLBVMnn15U3Wafp3I-xCo,12949
149
151
  flwr/server/strategy/dpfedavg_adaptive.py,sha256=hLJkPQJl1bHjwrBNg3PSRFKf3no0hg5EHiFaWhHlWqw,4877
150
152
  flwr/server/strategy/dpfedavg_fixed.py,sha256=G0yYxrPoM-MHQ889DYN3OeNiEeU0yQrjgAzcq0G653w,7219
151
153
  flwr/server/strategy/fault_tolerant_fedavg.py,sha256=veGcehB6rXT_MihNDrD1v5JY-TxJi7fybdDl-OZooDQ,5900
@@ -177,7 +179,7 @@ flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py,sha256=kuD7R1yB1Ite0s
177
179
  flwr/server/superlink/fleet/grpc_bidi/grpc_server.py,sha256=_zWknjP7CRjwLDvofzmv1QoSI8Qq1cZC5nNw9nkSS7I,11932
178
180
  flwr/server/superlink/fleet/grpc_rere/__init__.py,sha256=bEJOMWbSlqkw-y5ZHtEXczhoSlAxErcRYffmTMQAV8M,758
179
181
  flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py,sha256=YGn1IPpuX-6NDgaG1UbyREbI9iAyKDimZuNeWxbG6s0,3387
180
- flwr/server/superlink/fleet/grpc_rere/server_interceptor.py,sha256=hewkFjp4VJnZi-CCuBIooLenmSz6KJdzZBsKOfxbk5Y,5799
182
+ flwr/server/superlink/fleet/grpc_rere/server_interceptor.py,sha256=3EuoVOb5pXrN52XlDGjEfJdXYmcfFunZeYYMlDlu7ug,5902
181
183
  flwr/server/superlink/fleet/message_handler/__init__.py,sha256=hEY0l61ojH8Iz30_K1btm1HJ6J49iZJSFUsVYqUTw3A,731
182
184
  flwr/server/superlink/fleet/message_handler/message_handler.py,sha256=lG3BkiONcikDVowK0An06V7p2SNkwGbWE5hfN2xlsZw,3622
183
185
  flwr/server/superlink/fleet/rest_rere/__init__.py,sha256=VKDvDq5H8koOUztpmQacVzGJXPLEEkL1Vmolxt3mvnY,735
@@ -203,15 +205,15 @@ flwr/server/workflow/default_workflows.py,sha256=ROJNsY538jSGMaNyF7GHwXMtV7us1Vx
203
205
  flwr/server/workflow/secure_aggregation/__init__.py,sha256=3XlgDOjD_hcukTGl6Bc1B-8M_dPlVSJuTbvXIbiO-Ic,880
204
206
  flwr/server/workflow/secure_aggregation/secagg_workflow.py,sha256=wpAkYPId0nfK6SgpUAtsCni4_MQLd-uqJ81tUKu3xlI,5838
205
207
  flwr/server/workflow/secure_aggregation/secaggplus_workflow.py,sha256=BRqhlnVe8CYNoUvb_KCfRXay02NTT6a-pCrMaOqAxGc,29038
206
- flwr/simulation/__init__.py,sha256=hpoKzdovrH0_Cf8HIcXxQxyUUb3BiSk-WUNLf5STHcc,1400
207
- flwr/simulation/app.py,sha256=WqJxdXTEuehwMW605p5NMmvBbKYx5tuqnV3Mp7jSWXM,13904
208
+ flwr/simulation/__init__.py,sha256=1SVOWQGPA6dWlUMR_ZeiPBYXURNb1rx9VOi6kSW6zrs,1348
209
+ flwr/simulation/app.py,sha256=Fjq3krpzx9GUW1oYLHPn_Wq2tzjyqVBa9bxRbtuNVBQ,14380
208
210
  flwr/simulation/ray_transport/__init__.py,sha256=FsaAnzC4cw4DqoouBCix6496k29jACkfeIam55BvW9g,734
209
211
  flwr/simulation/ray_transport/ray_actor.py,sha256=_wv2eP7qxkCZ-6rMyYWnjLrGPBZRxjvTPjaVk8zIaQ4,19367
210
212
  flwr/simulation/ray_transport/ray_client_proxy.py,sha256=oDu4sEPIOu39vrNi-fqDAe10xtNUXMO49bM2RWfRcyw,6738
211
213
  flwr/simulation/ray_transport/utils.py,sha256=TYdtfg1P9VfTdLMOJlifInGpxWHYs9UfUqIv2wfkRLA,2392
212
- flwr/simulation/run_simulation.py,sha256=nxXNv3r8ODImd5o6f0sa_w5L0I08LD2Udw2OTXStRnQ,15694
213
- flwr_nightly-1.9.0.dev20240429.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
214
- flwr_nightly-1.9.0.dev20240429.dist-info/METADATA,sha256=LiieeRHvskFf0ZMyAnDUP0hiSS0oYScCuLsX4GyUi1U,15260
215
- flwr_nightly-1.9.0.dev20240429.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
216
- flwr_nightly-1.9.0.dev20240429.dist-info/entry_points.txt,sha256=DBrrf685V2W9NbbchQwvuqBEpj5ik8tMZNoZg_W2bZY,363
217
- flwr_nightly-1.9.0.dev20240429.dist-info/RECORD,,
214
+ flwr/simulation/run_simulation.py,sha256=jSzL7vZJ6Cg76yAyuWt7X8WzwkZ26krpurRAIHXhrNk,15896
215
+ flwr_nightly-1.9.0.dev20240501.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
216
+ flwr_nightly-1.9.0.dev20240501.dist-info/METADATA,sha256=xx9zVqrtYze9IkQZ6S2obortCJOalnsGcQnuhStB9Fs,15303
217
+ flwr_nightly-1.9.0.dev20240501.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
218
+ flwr_nightly-1.9.0.dev20240501.dist-info/entry_points.txt,sha256=8JJPfpqMnXz9c5V_FSt07Xwd-wCWbAO3MFUDXQ5ZGsI,378
219
+ flwr_nightly-1.9.0.dev20240501.dist-info/RECORD,,
@@ -3,7 +3,7 @@ flower-client-app=flwr.client:run_client_app
3
3
  flower-driver-api=flwr.server:run_driver_api
4
4
  flower-fleet-api=flwr.server:run_fleet_api
5
5
  flower-server-app=flwr.server:run_server_app
6
- flower-simulation=flwr.simulation:run_simulation_from_cli
6
+ flower-simulation=flwr.simulation.run_simulation:run_simulation_from_cli
7
7
  flower-superlink=flwr.server:run_superlink
8
8
  flower-supernode=flwr.client:run_supernode
9
9
  flwr=flwr.cli.app:app