flwr-nightly 1.9.0.dev20240417__py3-none-any.whl → 1.9.0.dev20240507__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 (66) hide show
  1. flwr/cli/app.py +2 -0
  2. flwr/cli/build.py +151 -0
  3. flwr/cli/config_utils.py +19 -14
  4. flwr/cli/new/new.py +51 -22
  5. flwr/cli/new/templates/app/.gitignore.tpl +160 -0
  6. flwr/cli/new/templates/app/code/client.mlx.py.tpl +70 -0
  7. flwr/cli/new/templates/app/code/client.pytorch.py.tpl +1 -1
  8. flwr/cli/new/templates/app/code/client.sklearn.py.tpl +94 -0
  9. flwr/cli/new/templates/app/code/client.tensorflow.py.tpl +42 -0
  10. flwr/cli/new/templates/app/code/server.mlx.py.tpl +15 -0
  11. flwr/cli/new/templates/app/code/server.pytorch.py.tpl +1 -1
  12. flwr/cli/new/templates/app/code/server.sklearn.py.tpl +17 -0
  13. flwr/cli/new/templates/app/code/server.tensorflow.py.tpl +26 -0
  14. flwr/cli/new/templates/app/code/task.mlx.py.tpl +89 -0
  15. flwr/cli/new/templates/app/code/task.tensorflow.py.tpl +29 -0
  16. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +28 -0
  17. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +7 -4
  18. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +7 -4
  19. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +27 -0
  20. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +7 -4
  21. flwr/cli/run/run.py +1 -1
  22. flwr/cli/utils.py +18 -17
  23. flwr/client/__init__.py +3 -1
  24. flwr/client/app.py +20 -142
  25. flwr/client/grpc_client/connection.py +8 -2
  26. flwr/client/grpc_rere_client/client_interceptor.py +158 -0
  27. flwr/client/grpc_rere_client/connection.py +33 -4
  28. flwr/client/mod/centraldp_mods.py +4 -2
  29. flwr/client/mod/localdp_mod.py +9 -3
  30. flwr/client/rest_client/connection.py +92 -169
  31. flwr/client/supernode/__init__.py +24 -0
  32. flwr/client/supernode/app.py +281 -0
  33. flwr/common/grpc.py +5 -1
  34. flwr/common/logger.py +37 -4
  35. flwr/common/message.py +105 -86
  36. flwr/common/record/parametersrecord.py +0 -1
  37. flwr/common/record/recordset.py +78 -27
  38. flwr/common/secure_aggregation/crypto/symmetric_encryption.py +35 -1
  39. flwr/common/telemetry.py +4 -0
  40. flwr/server/app.py +116 -6
  41. flwr/server/compat/app.py +2 -2
  42. flwr/server/compat/app_utils.py +1 -1
  43. flwr/server/compat/driver_client_proxy.py +27 -70
  44. flwr/server/driver/__init__.py +2 -1
  45. flwr/server/driver/driver.py +12 -139
  46. flwr/server/driver/grpc_driver.py +199 -13
  47. flwr/server/run_serverapp.py +18 -4
  48. flwr/server/strategy/dp_adaptive_clipping.py +5 -3
  49. flwr/server/strategy/dp_fixed_clipping.py +6 -3
  50. flwr/server/superlink/driver/driver_servicer.py +1 -1
  51. flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +3 -1
  52. flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +215 -0
  53. flwr/server/superlink/fleet/message_handler/message_handler.py +4 -1
  54. flwr/server/superlink/fleet/vce/backend/raybackend.py +5 -5
  55. flwr/server/superlink/fleet/vce/vce_api.py +1 -1
  56. flwr/server/superlink/state/in_memory_state.py +89 -12
  57. flwr/server/superlink/state/sqlite_state.py +133 -16
  58. flwr/server/superlink/state/state.py +56 -6
  59. flwr/simulation/__init__.py +2 -2
  60. flwr/simulation/app.py +16 -1
  61. flwr/simulation/run_simulation.py +10 -7
  62. {flwr_nightly-1.9.0.dev20240417.dist-info → flwr_nightly-1.9.0.dev20240507.dist-info}/METADATA +3 -2
  63. {flwr_nightly-1.9.0.dev20240417.dist-info → flwr_nightly-1.9.0.dev20240507.dist-info}/RECORD +66 -52
  64. {flwr_nightly-1.9.0.dev20240417.dist-info → flwr_nightly-1.9.0.dev20240507.dist-info}/entry_points.txt +2 -1
  65. {flwr_nightly-1.9.0.dev20240417.dist-info → flwr_nightly-1.9.0.dev20240507.dist-info}/LICENSE +0 -0
  66. {flwr_nightly-1.9.0.dev20240417.dist-info → flwr_nightly-1.9.0.dev20240507.dist-info}/WHEEL +0 -0
@@ -0,0 +1,94 @@
1
+ """$project_name: A Flower / Scikit-Learn app."""
2
+
3
+ import warnings
4
+
5
+ import numpy as np
6
+ from flwr.client import NumPyClient, ClientApp
7
+ from flwr_datasets import FederatedDataset
8
+ from sklearn.linear_model import LogisticRegression
9
+ from sklearn.metrics import log_loss
10
+
11
+
12
+ def get_model_parameters(model):
13
+ if model.fit_intercept:
14
+ params = [
15
+ model.coef_,
16
+ model.intercept_,
17
+ ]
18
+ else:
19
+ params = [model.coef_]
20
+ return params
21
+
22
+
23
+ def set_model_params(model, params):
24
+ model.coef_ = params[0]
25
+ if model.fit_intercept:
26
+ model.intercept_ = params[1]
27
+ return model
28
+
29
+
30
+ def set_initial_params(model):
31
+ n_classes = 10 # MNIST has 10 classes
32
+ n_features = 784 # Number of features in dataset
33
+ model.classes_ = np.array([i for i in range(10)])
34
+
35
+ model.coef_ = np.zeros((n_classes, n_features))
36
+ if model.fit_intercept:
37
+ model.intercept_ = np.zeros((n_classes,))
38
+
39
+
40
+ class FlowerClient(NumPyClient):
41
+ def __init__(self, model, X_train, X_test, y_train, y_test):
42
+ self.model = model
43
+ self.X_train = X_train
44
+ self.X_test = X_test
45
+ self.y_train = y_train
46
+ self.y_test = y_test
47
+
48
+ def get_parameters(self, config):
49
+ return get_model_parameters(self.model)
50
+
51
+ def fit(self, parameters, config):
52
+ set_model_params(self.model, parameters)
53
+
54
+ # Ignore convergence failure due to low local epochs
55
+ with warnings.catch_warnings():
56
+ warnings.simplefilter("ignore")
57
+ self.model.fit(self.X_train, self.y_train)
58
+
59
+ return get_model_parameters(self.model), len(self.X_train), {}
60
+
61
+ def evaluate(self, parameters, config):
62
+ set_model_params(self.model, parameters)
63
+
64
+ loss = log_loss(self.y_test, self.model.predict_proba(self.X_test))
65
+ accuracy = self.model.score(self.X_test, self.y_test)
66
+
67
+ return loss, len(self.X_test), {"accuracy": accuracy}
68
+
69
+ fds = FederatedDataset(dataset="mnist", partitioners={"train": 2})
70
+
71
+ def client_fn(cid: str):
72
+ dataset = fds.load_partition(int(cid), "train").with_format("numpy")
73
+
74
+ X, y = dataset["image"].reshape((len(dataset), -1)), dataset["label"]
75
+
76
+ # Split the on edge data: 80% train, 20% test
77
+ X_train, X_test = X[: int(0.8 * len(X))], X[int(0.8 * len(X)) :]
78
+ y_train, y_test = y[: int(0.8 * len(y))], y[int(0.8 * len(y)) :]
79
+
80
+ # Create LogisticRegression Model
81
+ model = LogisticRegression(
82
+ penalty="l2",
83
+ max_iter=1, # local epoch
84
+ warm_start=True, # prevent refreshing weights when fitting
85
+ )
86
+
87
+ # Setting initial parameters, akin to model.compile for keras models
88
+ set_initial_params(model)
89
+
90
+ return FlowerClient(model, X_train, X_test, y_train, y_test).to_client()
91
+
92
+
93
+ # Flower ClientApp
94
+ app = ClientApp(client_fn=client_fn)
@@ -1 +1,43 @@
1
1
  """$project_name: A Flower / TensorFlow app."""
2
+
3
+ from flwr.client import NumPyClient, ClientApp
4
+
5
+ from $import_name.task import load_data, load_model
6
+
7
+
8
+ # Define Flower Client and client_fn
9
+ class FlowerClient(NumPyClient):
10
+ def __init__(self, model, x_train, y_train, x_test, y_test):
11
+ self.model = model
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
+
17
+ def get_parameters(self, config):
18
+ return self.model.get_weights()
19
+
20
+ def fit(self, parameters, config):
21
+ self.model.set_weights(parameters)
22
+ self.model.fit(self.x_train, self.y_train, epochs=1, batch_size=32, verbose=0)
23
+ return self.model.get_weights(), len(self.x_train), {}
24
+
25
+ def evaluate(self, parameters, config):
26
+ self.model.set_weights(parameters)
27
+ loss, accuracy = self.model.evaluate(self.x_test, self.y_test, verbose=0)
28
+ return loss, len(self.x_test), {"accuracy": accuracy}
29
+
30
+
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)
35
+
36
+ # Return Client instance
37
+ return FlowerClient(net, x_train, y_train, x_test, y_test).to_client()
38
+
39
+
40
+ # Flower ClientApp
41
+ app = ClientApp(
42
+ client_fn=client_fn,
43
+ )
@@ -0,0 +1,15 @@
1
+ """$project_name: A Flower / MLX app."""
2
+
3
+ from flwr.server import ServerApp, ServerConfig
4
+ from flwr.server.strategy import FedAvg
5
+
6
+
7
+ # Define strategy
8
+ strategy = FedAvg()
9
+
10
+
11
+ # Create ServerApp
12
+ app = ServerApp(
13
+ config=ServerConfig(num_rounds=3),
14
+ strategy=strategy,
15
+ )
@@ -4,7 +4,7 @@ from flwr.common import ndarrays_to_parameters
4
4
  from flwr.server import ServerApp, ServerConfig
5
5
  from flwr.server.strategy import FedAvg
6
6
 
7
- from $project_name.task import Net, get_weights
7
+ from $import_name.task import Net, get_weights
8
8
 
9
9
 
10
10
  # Initialize model parameters
@@ -0,0 +1,17 @@
1
+ """$project_name: A Flower / Scikit-Learn app."""
2
+
3
+ from flwr.server import ServerApp, ServerConfig
4
+ from flwr.server.strategy import FedAvg
5
+
6
+
7
+ strategy = FedAvg(
8
+ fraction_fit=1.0,
9
+ fraction_evaluate=1.0,
10
+ min_available_clients=2,
11
+ )
12
+
13
+ # Create ServerApp
14
+ app = ServerApp(
15
+ config=ServerConfig(num_rounds=3),
16
+ strategy=strategy,
17
+ )
@@ -1 +1,27 @@
1
1
  """$project_name: A Flower / TensorFlow app."""
2
+
3
+ from flwr.common import ndarrays_to_parameters
4
+ from flwr.server import ServerApp, ServerConfig
5
+ from flwr.server.strategy import FedAvg
6
+
7
+ from $import_name.task import load_model
8
+
9
+ # Define config
10
+ config = ServerConfig(num_rounds=3)
11
+
12
+ parameters = ndarrays_to_parameters(load_model().get_weights())
13
+
14
+ # Define strategy
15
+ strategy = FedAvg(
16
+ fraction_fit=1.0,
17
+ fraction_evaluate=1.0,
18
+ min_available_clients=2,
19
+ initial_parameters=parameters,
20
+ )
21
+
22
+
23
+ # Create ServerApp
24
+ app = ServerApp(
25
+ config=config,
26
+ strategy=strategy,
27
+ )
@@ -0,0 +1,89 @@
1
+ """$project_name: A Flower / MLX app."""
2
+
3
+ import mlx.core as mx
4
+ import mlx.nn as nn
5
+ import numpy as np
6
+ from datasets.utils.logging import disable_progress_bar
7
+ from flwr_datasets import FederatedDataset
8
+
9
+
10
+ disable_progress_bar()
11
+
12
+ class MLP(nn.Module):
13
+ """A simple MLP."""
14
+
15
+ def __init__(
16
+ self, num_layers: int, input_dim: int, hidden_dim: int, output_dim: int
17
+ ):
18
+ super().__init__()
19
+ layer_sizes = [input_dim] + [hidden_dim] * num_layers + [output_dim]
20
+ self.layers = [
21
+ nn.Linear(idim, odim)
22
+ for idim, odim in zip(layer_sizes[:-1], layer_sizes[1:])
23
+ ]
24
+
25
+ def __call__(self, x):
26
+ for l in self.layers[:-1]:
27
+ x = mx.maximum(l(x), 0.0)
28
+ return self.layers[-1](x)
29
+
30
+
31
+ def loss_fn(model, X, y):
32
+ return mx.mean(nn.losses.cross_entropy(model(X), y))
33
+
34
+
35
+ def eval_fn(model, X, y):
36
+ return mx.mean(mx.argmax(model(X), axis=1) == y)
37
+
38
+
39
+ def batch_iterate(batch_size, X, y):
40
+ perm = mx.array(np.random.permutation(y.size))
41
+ for s in range(0, y.size, batch_size):
42
+ ids = perm[s : s + batch_size]
43
+ yield X[ids], y[ids]
44
+
45
+
46
+ def load_data(partition_id, num_clients):
47
+ fds = FederatedDataset(dataset="mnist", partitioners={"train": num_clients})
48
+ partition = fds.load_partition(partition_id)
49
+ partition_splits = partition.train_test_split(test_size=0.2, seed=42)
50
+
51
+ partition_splits["train"].set_format("numpy")
52
+ partition_splits["test"].set_format("numpy")
53
+
54
+ train_partition = partition_splits["train"].map(
55
+ lambda img: {
56
+ "img": img.reshape(-1, 28 * 28).squeeze().astype(np.float32) / 255.0
57
+ },
58
+ input_columns="image",
59
+ )
60
+ test_partition = partition_splits["test"].map(
61
+ lambda img: {
62
+ "img": img.reshape(-1, 28 * 28).squeeze().astype(np.float32) / 255.0
63
+ },
64
+ input_columns="image",
65
+ )
66
+
67
+ data = (
68
+ train_partition["img"],
69
+ train_partition["label"].astype(np.uint32),
70
+ test_partition["img"],
71
+ test_partition["label"].astype(np.uint32),
72
+ )
73
+
74
+ train_images, train_labels, test_images, test_labels = map(mx.array, data)
75
+ return train_images, train_labels, test_images, test_labels
76
+
77
+
78
+ def get_params(model):
79
+ layers = model.parameters()["layers"]
80
+ return [np.array(val) for layer in layers for _, val in layer.items()]
81
+
82
+
83
+ def set_params(model, parameters):
84
+ new_params = {}
85
+ new_params["layers"] = [
86
+ {"weight": mx.array(parameters[i]), "bias": mx.array(parameters[i + 1])}
87
+ for i in range(0, len(parameters), 2)
88
+ ]
89
+ model.update(new_params)
@@ -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
@@ -0,0 +1,28 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "$package_name"
7
+ version = "1.0.0"
8
+ description = ""
9
+ authors = [
10
+ { name = "The Flower Authors", email = "hello@flower.ai" },
11
+ ]
12
+ license = { text = "Apache License (2.0)" }
13
+ dependencies = [
14
+ "flwr[simulation]>=1.8.0,<2.0",
15
+ "flwr-datasets[vision]>=0.0.2,<1.0.0",
16
+ "mlx==0.10.0",
17
+ "numpy==1.24.4",
18
+ ]
19
+
20
+ [tool.hatch.build.targets.wheel]
21
+ packages = ["."]
22
+
23
+ [flower]
24
+ publisher = "$username"
25
+
26
+ [flower.components]
27
+ serverapp = "$import_name.server:app"
28
+ clientapp = "$import_name.client:app"
@@ -3,13 +3,13 @@ requires = ["hatchling"]
3
3
  build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
- name = "$project_name"
6
+ name = "$package_name"
7
7
  version = "1.0.0"
8
8
  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",
@@ -18,6 +18,9 @@ dependencies = [
18
18
  [tool.hatch.build.targets.wheel]
19
19
  packages = ["."]
20
20
 
21
+ [flower]
22
+ publisher = "$username"
23
+
21
24
  [flower.components]
22
- serverapp = "$project_name.server:app"
23
- clientapp = "$project_name.client:app"
25
+ serverapp = "$import_name.server:app"
26
+ clientapp = "$import_name.client:app"
@@ -3,13 +3,13 @@ requires = ["hatchling"]
3
3
  build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
- name = "$project_name"
6
+ name = "$package_name"
7
7
  version = "1.0.0"
8
8
  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",
@@ -20,6 +20,9 @@ dependencies = [
20
20
  [tool.hatch.build.targets.wheel]
21
21
  packages = ["."]
22
22
 
23
+ [flower]
24
+ publisher = "$username"
25
+
23
26
  [flower.components]
24
- serverapp = "$project_name.server:app"
25
- clientapp = "$project_name.client:app"
27
+ serverapp = "$import_name.server:app"
28
+ clientapp = "$import_name.client:app"
@@ -0,0 +1,27 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "$package_name"
7
+ version = "1.0.0"
8
+ description = ""
9
+ authors = [
10
+ { name = "The Flower Authors", email = "hello@flower.ai" },
11
+ ]
12
+ license = { text = "Apache License (2.0)" }
13
+ dependencies = [
14
+ "flwr[simulation]>=1.8.0,<2.0",
15
+ "flwr-datasets[vision]>=0.0.2,<1.0.0",
16
+ "scikit-learn>=1.1.1",
17
+ ]
18
+
19
+ [tool.hatch.build.targets.wheel]
20
+ packages = ["."]
21
+
22
+ [flower]
23
+ publisher = "$username"
24
+
25
+ [flower.components]
26
+ serverapp = "$import_name.server:app"
27
+ clientapp = "$import_name.client:app"
@@ -3,13 +3,13 @@ requires = ["hatchling"]
3
3
  build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
- name = "$project_name"
6
+ name = "$package_name"
7
7
  version = "1.0.0"
8
8
  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",
@@ -19,6 +19,9 @@ dependencies = [
19
19
  [tool.hatch.build.targets.wheel]
20
20
  packages = ["."]
21
21
 
22
+ [flower]
23
+ publisher = "$username"
24
+
22
25
  [flower.components]
23
- serverapp = "$project_name.server:app"
24
- clientapp = "$project_name.client:app"
26
+ serverapp = "$import_name.server:app"
27
+ clientapp = "$import_name.client:app"
flwr/cli/run/run.py CHANGED
@@ -30,7 +30,7 @@ def run() -> None:
30
30
 
31
31
  if config is None:
32
32
  typer.secho(
33
- "Project configuration could not be loaded.\nflower.toml is invalid:\n"
33
+ "Project configuration could not be loaded.\npyproject.toml is invalid:\n"
34
34
  + "\n".join([f"- {line}" for line in errors]),
35
35
  fg=typer.colors.RED,
36
36
  bold=True,
flwr/cli/utils.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower command line interface utils."""
16
16
 
17
+ import re
17
18
  from typing import Callable, List, Optional, cast
18
19
 
19
20
  import typer
@@ -73,51 +74,51 @@ def prompt_options(text: str, options: List[str]) -> str:
73
74
 
74
75
 
75
76
  def is_valid_project_name(name: str) -> bool:
76
- """Check if the given string is a valid Python module name.
77
+ """Check if the given string is a valid Python project name.
77
78
 
78
- A valid module name must start with a letter or an underscore, and can only contain
79
- letters, digits, and underscores.
79
+ A valid project name must start with a letter and can only contain letters, digits,
80
+ and hyphens.
80
81
  """
81
82
  if not name:
82
83
  return False
83
84
 
84
- # Check if the first character is a letter or underscore
85
- if not (name[0].isalpha() or name[0] == "_"):
85
+ # Check if the first character is a letter
86
+ if not name[0].isalpha():
86
87
  return False
87
88
 
88
- # Check if the rest of the characters are valid (letter, digit, or underscore)
89
+ # Check if the rest of the characters are valid (letter, digit, or dash)
89
90
  for char in name[1:]:
90
- if not (char.isalnum() or char == "_"):
91
+ if not (char.isalnum() or char in "-"):
91
92
  return False
92
93
 
93
94
  return True
94
95
 
95
96
 
96
97
  def sanitize_project_name(name: str) -> str:
97
- """Sanitize the given string to make it a valid Python module name.
98
+ """Sanitize the given string to make it a valid Python project name.
98
99
 
99
- This version replaces hyphens with underscores, removes any characters not allowed
100
- in Python module names, makes the string lowercase, and ensures it starts with a
101
- valid character.
100
+ This version replaces spaces, dots, slashes, and underscores with dashes, removes
101
+ any characters not allowed in Python project names, makes the string lowercase, and
102
+ ensures it starts with a valid character.
102
103
  """
103
- # Replace '-' with '_'
104
- name_with_underscores = name.replace("-", "_").replace(" ", "_")
104
+ # Replace whitespace with '_'
105
+ name_with_hyphens = re.sub(r"[ ./_]", "-", name)
105
106
 
106
107
  # Allowed characters in a module name: letters, digits, underscore
107
108
  allowed_chars = set(
108
- "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
109
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-"
109
110
  )
110
111
 
111
112
  # Make the string lowercase
112
- sanitized_name = name_with_underscores.lower()
113
+ sanitized_name = name_with_hyphens.lower()
113
114
 
114
115
  # Remove any characters not allowed in Python module names
115
116
  sanitized_name = "".join(c for c in sanitized_name if c in allowed_chars)
116
117
 
117
118
  # Ensure the first character is a letter or underscore
118
- if sanitized_name and (
119
+ while sanitized_name and (
119
120
  sanitized_name[0].isdigit() or sanitized_name[0] not in allowed_chars
120
121
  ):
121
- sanitized_name = "_" + sanitized_name
122
+ sanitized_name = sanitized_name[1:]
122
123
 
123
124
  return sanitized_name
flwr/client/__init__.py CHANGED
@@ -15,12 +15,13 @@
15
15
  """Flower client."""
16
16
 
17
17
 
18
- from .app import run_client_app as run_client_app
19
18
  from .app import start_client as start_client
20
19
  from .app import start_numpy_client as start_numpy_client
21
20
  from .client import Client as Client
22
21
  from .client_app import ClientApp as ClientApp
23
22
  from .numpy_client import NumPyClient as NumPyClient
23
+ from .supernode import run_client_app as run_client_app
24
+ from .supernode import run_supernode as run_supernode
24
25
  from .typing import ClientFn as ClientFn
25
26
 
26
27
  __all__ = [
@@ -29,6 +30,7 @@ __all__ = [
29
30
  "ClientFn",
30
31
  "NumPyClient",
31
32
  "run_client_app",
33
+ "run_supernode",
32
34
  "start_client",
33
35
  "start_numpy_client",
34
36
  ]