flwr-nightly 1.9.0.dev20240424__py3-none-any.whl → 1.9.0.dev20240426__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/config_utils.py +18 -46
- flwr/cli/new/new.py +37 -17
- flwr/cli/new/templates/app/README.md.tpl +1 -1
- flwr/cli/new/templates/app/code/client.pytorch.py.tpl +1 -1
- flwr/cli/new/templates/app/code/server.pytorch.py.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +6 -3
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +6 -3
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +6 -3
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +6 -3
- flwr/cli/run/run.py +1 -1
- flwr/cli/utils.py +18 -17
- flwr/server/compat/app_utils.py +1 -1
- flwr/server/compat/driver_client_proxy.py +27 -72
- flwr/server/driver/grpc_driver.py +17 -8
- flwr/server/run_serverapp.py +14 -0
- {flwr_nightly-1.9.0.dev20240424.dist-info → flwr_nightly-1.9.0.dev20240426.dist-info}/METADATA +1 -1
- {flwr_nightly-1.9.0.dev20240424.dist-info → flwr_nightly-1.9.0.dev20240426.dist-info}/RECORD +20 -20
- {flwr_nightly-1.9.0.dev20240424.dist-info → flwr_nightly-1.9.0.dev20240426.dist-info}/LICENSE +0 -0
- {flwr_nightly-1.9.0.dev20240424.dist-info → flwr_nightly-1.9.0.dev20240426.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.9.0.dev20240424.dist-info → flwr_nightly-1.9.0.dev20240426.dist-info}/entry_points.txt +0 -0
flwr/cli/config_utils.py
CHANGED
|
@@ -14,49 +14,16 @@
|
|
|
14
14
|
# ==============================================================================
|
|
15
15
|
"""Utility to validate the `pyproject.toml` file."""
|
|
16
16
|
|
|
17
|
-
import os
|
|
18
17
|
from pathlib import Path
|
|
19
18
|
from typing import Any, Dict, List, Optional, Tuple
|
|
20
19
|
|
|
21
20
|
import tomli
|
|
22
|
-
import typer
|
|
23
21
|
|
|
24
22
|
from flwr.common import object_ref
|
|
25
23
|
|
|
26
24
|
|
|
27
|
-
def validate_project_dir(project_dir: Path) -> Optional[Dict[str, Any]]:
|
|
28
|
-
"""Check if a Flower App directory is valid."""
|
|
29
|
-
config = load(str(project_dir / "pyproject.toml"))
|
|
30
|
-
if config is None:
|
|
31
|
-
typer.secho(
|
|
32
|
-
"❌ Project configuration could not be loaded. "
|
|
33
|
-
"`pyproject.toml` does not exist.",
|
|
34
|
-
fg=typer.colors.RED,
|
|
35
|
-
bold=True,
|
|
36
|
-
)
|
|
37
|
-
return None
|
|
38
|
-
|
|
39
|
-
if not validate(config):
|
|
40
|
-
typer.secho(
|
|
41
|
-
"❌ Project configuration is invalid.",
|
|
42
|
-
fg=typer.colors.RED,
|
|
43
|
-
bold=True,
|
|
44
|
-
)
|
|
45
|
-
return None
|
|
46
|
-
|
|
47
|
-
if "publisher" not in config["flower"]:
|
|
48
|
-
typer.secho(
|
|
49
|
-
"❌ Project configuration is missing required `publisher` field.",
|
|
50
|
-
fg=typer.colors.RED,
|
|
51
|
-
bold=True,
|
|
52
|
-
)
|
|
53
|
-
return None
|
|
54
|
-
|
|
55
|
-
return config
|
|
56
|
-
|
|
57
|
-
|
|
58
25
|
def load_and_validate_with_defaults(
|
|
59
|
-
path: Optional[
|
|
26
|
+
path: Optional[Path] = None,
|
|
60
27
|
) -> Tuple[Optional[Dict[str, Any]], List[str], List[str]]:
|
|
61
28
|
"""Load and validate pyproject.toml as dict.
|
|
62
29
|
|
|
@@ -70,7 +37,8 @@ def load_and_validate_with_defaults(
|
|
|
70
37
|
|
|
71
38
|
if config is None:
|
|
72
39
|
errors = [
|
|
73
|
-
"Project configuration could not be loaded.
|
|
40
|
+
"Project configuration could not be loaded. "
|
|
41
|
+
"`pyproject.toml` does not exist."
|
|
74
42
|
]
|
|
75
43
|
return (None, errors, [])
|
|
76
44
|
|
|
@@ -90,22 +58,23 @@ def load_and_validate_with_defaults(
|
|
|
90
58
|
return (config, errors, warnings)
|
|
91
59
|
|
|
92
60
|
|
|
93
|
-
def load(path: Optional[
|
|
61
|
+
def load(path: Optional[Path] = None) -> Optional[Dict[str, Any]]:
|
|
94
62
|
"""Load pyproject.toml and return as dict."""
|
|
95
63
|
if path is None:
|
|
96
|
-
cur_dir =
|
|
97
|
-
toml_path =
|
|
64
|
+
cur_dir = Path.cwd()
|
|
65
|
+
toml_path = cur_dir / "pyproject.toml"
|
|
98
66
|
else:
|
|
99
67
|
toml_path = path
|
|
100
68
|
|
|
101
|
-
if not
|
|
69
|
+
if not toml_path.is_file():
|
|
102
70
|
return None
|
|
103
71
|
|
|
104
|
-
with open(
|
|
72
|
+
with toml_path.open(encoding="utf-8") as toml_file:
|
|
105
73
|
data = tomli.loads(toml_file.read())
|
|
106
74
|
return data
|
|
107
75
|
|
|
108
76
|
|
|
77
|
+
# pylint: disable=too-many-branches
|
|
109
78
|
def validate_fields(config: Dict[str, Any]) -> Tuple[bool, List[str], List[str]]:
|
|
110
79
|
"""Validate pyproject.toml fields."""
|
|
111
80
|
errors = []
|
|
@@ -127,13 +96,16 @@ def validate_fields(config: Dict[str, Any]) -> Tuple[bool, List[str], List[str]]
|
|
|
127
96
|
|
|
128
97
|
if "flower" not in config:
|
|
129
98
|
errors.append("Missing [flower] section")
|
|
130
|
-
elif "components" not in config["flower"]:
|
|
131
|
-
errors.append("Missing [flower.components] section")
|
|
132
99
|
else:
|
|
133
|
-
if "
|
|
134
|
-
errors.append('Property "
|
|
135
|
-
if "
|
|
136
|
-
errors.append(
|
|
100
|
+
if "publisher" not in config["flower"]:
|
|
101
|
+
errors.append('Property "publisher" missing in [flower]')
|
|
102
|
+
if "components" not in config["flower"]:
|
|
103
|
+
errors.append("Missing [flower.components] section")
|
|
104
|
+
else:
|
|
105
|
+
if "serverapp" not in config["flower"]["components"]:
|
|
106
|
+
errors.append('Property "serverapp" missing in [flower.components]')
|
|
107
|
+
if "clientapp" not in config["flower"]["components"]:
|
|
108
|
+
errors.append('Property "clientapp" missing in [flower.components]')
|
|
137
109
|
|
|
138
110
|
return len(errors) == 0, errors, warnings
|
|
139
111
|
|
flwr/cli/new/new.py
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"""Flower command line interface `new` command."""
|
|
16
16
|
|
|
17
17
|
import os
|
|
18
|
+
import re
|
|
18
19
|
from enum import Enum
|
|
19
20
|
from string import Template
|
|
20
21
|
from typing import Dict, Optional
|
|
@@ -86,25 +87,24 @@ def new(
|
|
|
86
87
|
Optional[MlFramework],
|
|
87
88
|
typer.Option(case_sensitive=False, help="The ML framework to use"),
|
|
88
89
|
] = None,
|
|
90
|
+
username: Annotated[
|
|
91
|
+
Optional[str],
|
|
92
|
+
typer.Option(case_sensitive=False, help="The Flower username of the author"),
|
|
93
|
+
] = None,
|
|
89
94
|
) -> None:
|
|
90
95
|
"""Create new Flower project."""
|
|
91
96
|
if project_name is None:
|
|
92
|
-
project_name = prompt_text("Please provide project name")
|
|
97
|
+
project_name = prompt_text("Please provide the project name")
|
|
93
98
|
if not is_valid_project_name(project_name):
|
|
94
99
|
project_name = prompt_text(
|
|
95
100
|
"Please provide a name that only contains "
|
|
96
|
-
"characters in {'
|
|
101
|
+
"characters in {'-', a-zA-Z', '0-9'}",
|
|
97
102
|
predicate=is_valid_project_name,
|
|
98
103
|
default=sanitize_project_name(project_name),
|
|
99
104
|
)
|
|
100
105
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
f"🔨 Creating Flower project {project_name}...",
|
|
104
|
-
fg=typer.colors.GREEN,
|
|
105
|
-
bold=True,
|
|
106
|
-
)
|
|
107
|
-
)
|
|
106
|
+
if username is None:
|
|
107
|
+
username = prompt_text("Please provide your Flower username")
|
|
108
108
|
|
|
109
109
|
if framework is not None:
|
|
110
110
|
framework_str = str(framework.value)
|
|
@@ -122,19 +122,32 @@ def new(
|
|
|
122
122
|
|
|
123
123
|
framework_str = framework_str.lower()
|
|
124
124
|
|
|
125
|
+
print(
|
|
126
|
+
typer.style(
|
|
127
|
+
f"\n🔨 Creating Flower project {project_name}...",
|
|
128
|
+
fg=typer.colors.GREEN,
|
|
129
|
+
bold=True,
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
|
|
125
133
|
# Set project directory path
|
|
126
134
|
cwd = os.getcwd()
|
|
127
|
-
|
|
128
|
-
|
|
135
|
+
package_name = re.sub(r"[-_.]+", "-", project_name).lower()
|
|
136
|
+
import_name = package_name.replace("-", "_")
|
|
137
|
+
project_dir = os.path.join(cwd, package_name)
|
|
129
138
|
|
|
130
139
|
# List of files to render
|
|
131
140
|
files = {
|
|
132
141
|
".gitignore": {"template": "app/.gitignore.tpl"},
|
|
133
142
|
"README.md": {"template": "app/README.md.tpl"},
|
|
134
143
|
"pyproject.toml": {"template": f"app/pyproject.{framework_str}.toml.tpl"},
|
|
135
|
-
f"{
|
|
136
|
-
f"{
|
|
137
|
-
|
|
144
|
+
f"{import_name}/__init__.py": {"template": "app/code/__init__.py.tpl"},
|
|
145
|
+
f"{import_name}/server.py": {
|
|
146
|
+
"template": f"app/code/server.{framework_str}.py.tpl"
|
|
147
|
+
},
|
|
148
|
+
f"{import_name}/client.py": {
|
|
149
|
+
"template": f"app/code/client.{framework_str}.py.tpl"
|
|
150
|
+
},
|
|
138
151
|
}
|
|
139
152
|
|
|
140
153
|
# Depending on the framework, generate task.py file
|
|
@@ -142,9 +155,16 @@ def new(
|
|
|
142
155
|
MlFramework.PYTORCH.value.lower(),
|
|
143
156
|
]
|
|
144
157
|
if framework_str in frameworks_with_tasks:
|
|
145
|
-
files[f"{
|
|
146
|
-
|
|
147
|
-
|
|
158
|
+
files[f"{import_name}/task.py"] = {
|
|
159
|
+
"template": f"app/code/task.{framework_str}.py.tpl"
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
context = {
|
|
163
|
+
"project_name": project_name,
|
|
164
|
+
"package_name": package_name,
|
|
165
|
+
"import_name": import_name.replace("-", "_"),
|
|
166
|
+
"username": username,
|
|
167
|
+
}
|
|
148
168
|
|
|
149
169
|
for file_path, value in files.items():
|
|
150
170
|
render_and_create(
|
|
@@ -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 $
|
|
7
|
+
from $import_name.task import Net, get_weights
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
# Initialize model parameters
|
|
@@ -3,7 +3,7 @@ requires = ["hatchling"]
|
|
|
3
3
|
build-backend = "hatchling.build"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
|
-
name = "$
|
|
6
|
+
name = "$package_name"
|
|
7
7
|
version = "1.0.0"
|
|
8
8
|
description = ""
|
|
9
9
|
authors = [
|
|
@@ -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 = "$
|
|
23
|
-
clientapp = "$
|
|
25
|
+
serverapp = "$import_name.server:app"
|
|
26
|
+
clientapp = "$import_name.client:app"
|
|
@@ -3,7 +3,7 @@ requires = ["hatchling"]
|
|
|
3
3
|
build-backend = "hatchling.build"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
|
-
name = "$
|
|
6
|
+
name = "$package_name"
|
|
7
7
|
version = "1.0.0"
|
|
8
8
|
description = ""
|
|
9
9
|
authors = [
|
|
@@ -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 = "$
|
|
25
|
-
clientapp = "$
|
|
27
|
+
serverapp = "$import_name.server:app"
|
|
28
|
+
clientapp = "$import_name.client:app"
|
|
@@ -3,7 +3,7 @@ requires = ["hatchling"]
|
|
|
3
3
|
build-backend = "hatchling.build"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
|
-
name = "$
|
|
6
|
+
name = "$package_name"
|
|
7
7
|
version = "1.0.0"
|
|
8
8
|
description = ""
|
|
9
9
|
authors = [
|
|
@@ -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 = "$
|
|
24
|
-
clientapp = "$
|
|
26
|
+
serverapp = "$import_name.server:app"
|
|
27
|
+
clientapp = "$import_name.client:app"
|
|
@@ -3,7 +3,7 @@ requires = ["hatchling"]
|
|
|
3
3
|
build-backend = "hatchling.build"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
|
-
name = "$
|
|
6
|
+
name = "$package_name"
|
|
7
7
|
version = "1.0.0"
|
|
8
8
|
description = ""
|
|
9
9
|
authors = [
|
|
@@ -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 = "$
|
|
24
|
-
clientapp = "$
|
|
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.\
|
|
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
|
|
77
|
+
"""Check if the given string is a valid Python project name.
|
|
77
78
|
|
|
78
|
-
A valid
|
|
79
|
-
|
|
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
|
|
85
|
-
if not
|
|
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
|
|
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
|
|
98
|
+
"""Sanitize the given string to make it a valid Python project name.
|
|
98
99
|
|
|
99
|
-
This version replaces
|
|
100
|
-
in Python
|
|
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
|
|
104
|
-
|
|
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
|
-
"
|
|
109
|
+
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-"
|
|
109
110
|
)
|
|
110
111
|
|
|
111
112
|
# Make the string lowercase
|
|
112
|
-
sanitized_name =
|
|
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
|
-
|
|
119
|
+
while sanitized_name and (
|
|
119
120
|
sanitized_name[0].isdigit() or sanitized_name[0] not in allowed_chars
|
|
120
121
|
):
|
|
121
|
-
sanitized_name =
|
|
122
|
+
sanitized_name = sanitized_name[1:]
|
|
122
123
|
|
|
123
124
|
return sanitized_name
|
flwr/server/compat/app_utils.py
CHANGED
|
@@ -16,16 +16,14 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
import time
|
|
19
|
-
from typing import
|
|
19
|
+
from typing import Optional
|
|
20
20
|
|
|
21
21
|
from flwr import common
|
|
22
|
-
from flwr.common import
|
|
22
|
+
from flwr.common import Message, MessageType, MessageTypeLegacy, RecordSet
|
|
23
23
|
from flwr.common import recordset_compat as compat
|
|
24
|
-
from flwr.common import serde
|
|
25
|
-
from flwr.proto import driver_pb2, node_pb2, task_pb2 # pylint: disable=E0611
|
|
26
24
|
from flwr.server.client_proxy import ClientProxy
|
|
27
25
|
|
|
28
|
-
from ..driver.
|
|
26
|
+
from ..driver.driver import Driver
|
|
29
27
|
|
|
30
28
|
SLEEP_TIME = 1
|
|
31
29
|
|
|
@@ -33,9 +31,7 @@ SLEEP_TIME = 1
|
|
|
33
31
|
class DriverClientProxy(ClientProxy):
|
|
34
32
|
"""Flower client proxy which delegates work using the Driver API."""
|
|
35
33
|
|
|
36
|
-
def __init__(
|
|
37
|
-
self, node_id: int, driver: GrpcDriverHelper, anonymous: bool, run_id: int
|
|
38
|
-
):
|
|
34
|
+
def __init__(self, node_id: int, driver: Driver, anonymous: bool, run_id: int):
|
|
39
35
|
super().__init__(str(node_id))
|
|
40
36
|
self.node_id = node_id
|
|
41
37
|
self.driver = driver
|
|
@@ -116,80 +112,39 @@ class DriverClientProxy(ClientProxy):
|
|
|
116
112
|
timeout: Optional[float],
|
|
117
113
|
group_id: Optional[int],
|
|
118
114
|
) -> RecordSet:
|
|
119
|
-
task_ins = task_pb2.TaskIns( # pylint: disable=E1101
|
|
120
|
-
task_id="",
|
|
121
|
-
group_id=str(group_id) if group_id is not None else "",
|
|
122
|
-
run_id=self.run_id,
|
|
123
|
-
task=task_pb2.Task( # pylint: disable=E1101
|
|
124
|
-
producer=node_pb2.Node( # pylint: disable=E1101
|
|
125
|
-
node_id=0,
|
|
126
|
-
anonymous=True,
|
|
127
|
-
),
|
|
128
|
-
consumer=node_pb2.Node( # pylint: disable=E1101
|
|
129
|
-
node_id=self.node_id,
|
|
130
|
-
anonymous=self.anonymous,
|
|
131
|
-
),
|
|
132
|
-
task_type=task_type,
|
|
133
|
-
recordset=serde.recordset_to_proto(recordset),
|
|
134
|
-
ttl=DEFAULT_TTL,
|
|
135
|
-
),
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
# This would normally be recorded upon common.Message creation
|
|
139
|
-
# but this compatibility stack doesn't create Messages,
|
|
140
|
-
# so we need to inject `created_at` manually (needed for
|
|
141
|
-
# taskins validation by server.utils.validator)
|
|
142
|
-
task_ins.task.created_at = time.time()
|
|
143
115
|
|
|
144
|
-
|
|
145
|
-
|
|
116
|
+
# Create message
|
|
117
|
+
message = self.driver.create_message(
|
|
118
|
+
content=recordset,
|
|
119
|
+
message_type=task_type,
|
|
120
|
+
dst_node_id=self.node_id,
|
|
121
|
+
group_id=str(group_id) if group_id else "",
|
|
122
|
+
ttl=timeout,
|
|
146
123
|
)
|
|
147
124
|
|
|
148
|
-
#
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
raise ValueError("Unexpected number of task_ids")
|
|
125
|
+
# Push message
|
|
126
|
+
message_ids = list(self.driver.push_messages(messages=[message]))
|
|
127
|
+
if len(message_ids) != 1:
|
|
128
|
+
raise ValueError("Unexpected number of message_ids")
|
|
153
129
|
|
|
154
|
-
|
|
155
|
-
if
|
|
156
|
-
raise ValueError(f"Failed to
|
|
130
|
+
message_id = message_ids[0]
|
|
131
|
+
if message_id == "":
|
|
132
|
+
raise ValueError(f"Failed to send message to node {self.node_id}")
|
|
157
133
|
|
|
158
134
|
if timeout:
|
|
159
135
|
start_time = time.time()
|
|
160
136
|
|
|
161
137
|
while True:
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
pull_task_res_res.task_res_list
|
|
172
|
-
)
|
|
173
|
-
if len(task_res_list) == 1:
|
|
174
|
-
task_res = task_res_list[0]
|
|
175
|
-
|
|
176
|
-
# This will raise an Exception if task_res carries an `error`
|
|
177
|
-
validate_task_res(task_res=task_res)
|
|
178
|
-
|
|
179
|
-
return serde.recordset_from_proto(task_res.task.recordset)
|
|
138
|
+
messages = list(self.driver.pull_messages(message_ids))
|
|
139
|
+
if len(messages) == 1:
|
|
140
|
+
msg: Message = messages[0]
|
|
141
|
+
if msg.has_error():
|
|
142
|
+
raise ValueError(
|
|
143
|
+
f"Message contains an Error (reason: {msg.error.reason}). "
|
|
144
|
+
"It originated during client-side execution of a message."
|
|
145
|
+
)
|
|
146
|
+
return msg.content
|
|
180
147
|
|
|
181
148
|
if timeout is not None and time.time() > start_time + timeout:
|
|
182
149
|
raise RuntimeError("Timeout reached")
|
|
183
150
|
time.sleep(SLEEP_TIME)
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
def validate_task_res(
|
|
187
|
-
task_res: task_pb2.TaskRes, # pylint: disable=E1101
|
|
188
|
-
) -> None:
|
|
189
|
-
"""Validate if a TaskRes is empty or not."""
|
|
190
|
-
if not task_res.HasField("task"):
|
|
191
|
-
raise ValueError("Invalid TaskRes, field `task` missing")
|
|
192
|
-
if task_res.task.HasField("error"):
|
|
193
|
-
raise ValueError("Exception during client-side task execution")
|
|
194
|
-
if not task_res.task.HasField("recordset"):
|
|
195
|
-
raise ValueError("Invalid TaskRes, both `recordset` and `error` are missing")
|
|
@@ -151,31 +151,40 @@ class GrpcDriver(Driver):
|
|
|
151
151
|
* CA certificate.
|
|
152
152
|
* server certificate.
|
|
153
153
|
* server private key.
|
|
154
|
+
fab_id : str (default: None)
|
|
155
|
+
The identifier of the FAB used in the run.
|
|
156
|
+
fab_version : str (default: None)
|
|
157
|
+
The version of the FAB used in the run.
|
|
154
158
|
"""
|
|
155
159
|
|
|
156
160
|
def __init__(
|
|
157
161
|
self,
|
|
158
162
|
driver_service_address: str = DEFAULT_SERVER_ADDRESS_DRIVER,
|
|
159
163
|
root_certificates: Optional[bytes] = None,
|
|
164
|
+
fab_id: Optional[str] = None,
|
|
165
|
+
fab_version: Optional[str] = None,
|
|
160
166
|
) -> None:
|
|
161
167
|
self.addr = driver_service_address
|
|
162
168
|
self.root_certificates = root_certificates
|
|
163
|
-
self.
|
|
169
|
+
self.driver_helper: Optional[GrpcDriverHelper] = None
|
|
164
170
|
self.run_id: Optional[int] = None
|
|
171
|
+
self.fab_id = fab_id if fab_id is not None else ""
|
|
172
|
+
self.fab_version = fab_version if fab_version is not None else ""
|
|
165
173
|
self.node = Node(node_id=0, anonymous=True)
|
|
166
174
|
|
|
167
175
|
def _get_grpc_driver_helper_and_run_id(self) -> Tuple[GrpcDriverHelper, int]:
|
|
168
176
|
# Check if the GrpcDriverHelper is initialized
|
|
169
|
-
if self.
|
|
177
|
+
if self.driver_helper is None or self.run_id is None:
|
|
170
178
|
# Connect and create run
|
|
171
|
-
self.
|
|
179
|
+
self.driver_helper = GrpcDriverHelper(
|
|
172
180
|
driver_service_address=self.addr,
|
|
173
181
|
root_certificates=self.root_certificates,
|
|
174
182
|
)
|
|
175
|
-
self.
|
|
176
|
-
|
|
183
|
+
self.driver_helper.connect()
|
|
184
|
+
req = CreateRunRequest(fab_id=self.fab_id, fab_version=self.fab_version)
|
|
185
|
+
res = self.driver_helper.create_run(req)
|
|
177
186
|
self.run_id = res.run_id
|
|
178
|
-
return self.
|
|
187
|
+
return self.driver_helper, self.run_id
|
|
179
188
|
|
|
180
189
|
def _check_message(self, message: Message) -> None:
|
|
181
190
|
# Check if the message is valid
|
|
@@ -300,7 +309,7 @@ class GrpcDriver(Driver):
|
|
|
300
309
|
def close(self) -> None:
|
|
301
310
|
"""Disconnect from the SuperLink if connected."""
|
|
302
311
|
# Check if GrpcDriverHelper is initialized
|
|
303
|
-
if self.
|
|
312
|
+
if self.driver_helper is None:
|
|
304
313
|
return
|
|
305
314
|
# Disconnect
|
|
306
|
-
self.
|
|
315
|
+
self.driver_helper.disconnect()
|
flwr/server/run_serverapp.py
CHANGED
|
@@ -132,6 +132,8 @@ def run_server_app() -> None:
|
|
|
132
132
|
driver = GrpcDriver(
|
|
133
133
|
driver_service_address=args.server,
|
|
134
134
|
root_certificates=root_certificates,
|
|
135
|
+
fab_id=args.fab_id,
|
|
136
|
+
fab_version=args.fab_version,
|
|
135
137
|
)
|
|
136
138
|
|
|
137
139
|
# Run the ServerApp with the Driver
|
|
@@ -183,5 +185,17 @@ def _parse_args_run_server_app() -> argparse.ArgumentParser:
|
|
|
183
185
|
"app from there."
|
|
184
186
|
" Default: current working directory.",
|
|
185
187
|
)
|
|
188
|
+
parser.add_argument(
|
|
189
|
+
"--fab-id",
|
|
190
|
+
default=None,
|
|
191
|
+
type=str,
|
|
192
|
+
help="The identifier of the FAB used in the run.",
|
|
193
|
+
)
|
|
194
|
+
parser.add_argument(
|
|
195
|
+
"--fab-version",
|
|
196
|
+
default=None,
|
|
197
|
+
type=str,
|
|
198
|
+
help="The version of the FAB used in the run.",
|
|
199
|
+
)
|
|
186
200
|
|
|
187
201
|
return parser
|
{flwr_nightly-1.9.0.dev20240424.dist-info → flwr_nightly-1.9.0.dev20240426.dist-info}/RECORD
RENAMED
|
@@ -1,32 +1,32 @@
|
|
|
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=38thPnMydBmNAxNE9mz4By-KdRUhJfoUgeDuAxMYF_U,1095
|
|
4
|
-
flwr/cli/config_utils.py,sha256=
|
|
4
|
+
flwr/cli/config_utils.py,sha256=Hql5A5hbSpJ51hgpwaTkKqfPoaZN4Zq7FZfBuQYLMcQ,4899
|
|
5
5
|
flwr/cli/example.py,sha256=1bGDYll3BXQY2kRqSN-oICqS5n1b9m0g0RvXTopXHl4,2215
|
|
6
6
|
flwr/cli/new/__init__.py,sha256=cQzK1WH4JP2awef1t2UQ2xjl1agVEz9rwutV18SWV1k,789
|
|
7
|
-
flwr/cli/new/new.py,sha256=
|
|
7
|
+
flwr/cli/new/new.py,sha256=8R6Vd07s1wVhqPWbN8Jz3_-bJ3CKkdipjbw0BmodJQY,5989
|
|
8
8
|
flwr/cli/new/templates/__init__.py,sha256=4luU8RL-CK8JJCstQ_ON809W9bNTkY1l9zSaPKBkgwY,725
|
|
9
9
|
flwr/cli/new/templates/app/.gitignore.tpl,sha256=XixnHdyeMB2vwkGtGnwHqoWpH-9WChdyG0GXe57duhc,3078
|
|
10
|
-
flwr/cli/new/templates/app/README.md.tpl,sha256=
|
|
10
|
+
flwr/cli/new/templates/app/README.md.tpl,sha256=N-tEnukHAtk9HpwR1nVdnFruzQ6s2hpqzRgjVL_1Cjw,667
|
|
11
11
|
flwr/cli/new/templates/app/__init__.py,sha256=DU7QMY7IhMQyuwm_tja66xU0KXTWQFqzfTqwg-_NJdE,729
|
|
12
12
|
flwr/cli/new/templates/app/code/__init__.py,sha256=EM6vfvgAILKPaPn7H1wMV1Wi01WyZCP_Eg6NxD6oWg8,736
|
|
13
13
|
flwr/cli/new/templates/app/code/__init__.py.tpl,sha256=olwrBeJemHNBWvjc6gJURloFRqW40dAy7FRQA5pDqHU,21
|
|
14
14
|
flwr/cli/new/templates/app/code/client.numpy.py.tpl,sha256=mTh7Y_jOJrPUvDYHVJy4wJCnjXZV_q-jlDkB07U5GSk,521
|
|
15
|
-
flwr/cli/new/templates/app/code/client.pytorch.py.tpl,sha256=
|
|
15
|
+
flwr/cli/new/templates/app/code/client.pytorch.py.tpl,sha256=MgCtMSv1Th16Faod11HubVaARkLYt7vS9RYH962-2pk,1172
|
|
16
16
|
flwr/cli/new/templates/app/code/client.sklearn.py.tpl,sha256=S71SZiHaRXtKqUk3m5Elc_c6HhKAIKLalrKOQ3p20No,2801
|
|
17
17
|
flwr/cli/new/templates/app/code/client.tensorflow.py.tpl,sha256=N9SbnI65r2K9FHV_wn4JSpmVeyYpD0qEMehbHcGm4t0,1911
|
|
18
18
|
flwr/cli/new/templates/app/code/server.numpy.py.tpl,sha256=fRxrDXV7pB1aDhQUXMBmrCsC1zp0uKwsBxZBx1JzbHA,248
|
|
19
|
-
flwr/cli/new/templates/app/code/server.pytorch.py.tpl,sha256=
|
|
19
|
+
flwr/cli/new/templates/app/code/server.pytorch.py.tpl,sha256=ltdsnFSvFGPcycVmRL4ITlr-TV0CmmXcperZe7Vamow,593
|
|
20
20
|
flwr/cli/new/templates/app/code/server.sklearn.py.tpl,sha256=cLzOpQzGIUzEazuFsjBpXAQUNPy6in6zR33SCqhix6o,341
|
|
21
21
|
flwr/cli/new/templates/app/code/server.tensorflow.py.tpl,sha256=GUGH8c_6cxgUB9obVJPaA4thxI7OVXsItyfQDsn9E5k,371
|
|
22
22
|
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=
|
|
24
|
-
flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl,sha256=
|
|
25
|
-
flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl,sha256=
|
|
26
|
-
flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl,sha256=
|
|
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
|
|
27
27
|
flwr/cli/run/__init__.py,sha256=oCd6HmQDx-sqver1gecgx-uMA38BLTSiiKpl7RGNceg,789
|
|
28
|
-
flwr/cli/run/run.py,sha256=
|
|
29
|
-
flwr/cli/utils.py,sha256=
|
|
28
|
+
flwr/cli/run/run.py,sha256=jr_J7Cbcyuj1MXIbuwU86htHdFI7XogsBrdGl7P4aYY,2334
|
|
29
|
+
flwr/cli/utils.py,sha256=px-M-IlBLu6Ez-Sc9tWhsJRjWurRaZTmxB9ASz8wurk,4119
|
|
30
30
|
flwr/client/__init__.py,sha256=8LuIrd2GGWJXG2CFWihywicJtntIvCoPLssIUnHqZaA,1262
|
|
31
31
|
flwr/client/app.py,sha256=zs5yeFavIIX-407b25xLapVruprohKSB0Ckk0CjW1Vw,24670
|
|
32
32
|
flwr/client/client.py,sha256=Vp9UkOkoHdNfn6iMYZsj_5m_GICiFfUlKEVaLad-YhM,8183
|
|
@@ -129,15 +129,15 @@ flwr/server/client_manager.py,sha256=T8UDSRJBVD3fyIDI7NTAA-NA7GPrMNNgH2OAF54RRxE
|
|
|
129
129
|
flwr/server/client_proxy.py,sha256=4G-oTwhb45sfWLx2uZdcXD98IZwdTS6F88xe3akCdUg,2399
|
|
130
130
|
flwr/server/compat/__init__.py,sha256=VxnJtJyOjNFQXMNi9hIuzNlZM5n0Hj1p3aq_Pm2udw4,892
|
|
131
131
|
flwr/server/compat/app.py,sha256=BhF3DySbvKkOIyNXnB1rwZhw8cC8yK_w91Fku8HmC_w,5287
|
|
132
|
-
flwr/server/compat/app_utils.py,sha256=
|
|
133
|
-
flwr/server/compat/driver_client_proxy.py,sha256=
|
|
132
|
+
flwr/server/compat/app_utils.py,sha256=06NHrPRPrjMjz5FglSPicJ9lAWZ-rIZ1cKQFs4nD6WI,3468
|
|
133
|
+
flwr/server/compat/driver_client_proxy.py,sha256=Wc6jyyHY4OrJzeiy8tdXtkF8IdGREdxUPnom7VvvWPI,5444
|
|
134
134
|
flwr/server/compat/legacy_context.py,sha256=D2s7PvQoDnTexuRmf1uG9Von7GUj4Qqyr7qLklSlKAM,1766
|
|
135
135
|
flwr/server/criterion.py,sha256=ypbAexbztzGUxNen9RCHF91QeqiEQix4t4Ih3E-42MM,1061
|
|
136
136
|
flwr/server/driver/__init__.py,sha256=bbVL5pyA0Y2HcUK4s5U0B4epI-BuUFyEJbchew_8tJY,862
|
|
137
137
|
flwr/server/driver/driver.py,sha256=t9SSSDlo9wT_y2Nl7waGYMTm2VlkvK3_bOb7ggPPlho,5090
|
|
138
|
-
flwr/server/driver/grpc_driver.py,sha256=
|
|
138
|
+
flwr/server/driver/grpc_driver.py,sha256=rdjkcAmtRWKeqJw4xDFqULuwVf0G2nLhfbOTrNUvPeY,11832
|
|
139
139
|
flwr/server/history.py,sha256=hDsoBaA4kUa6d1yvDVXuLluBqOBKSm0_fVDtUtYJkmg,5121
|
|
140
|
-
flwr/server/run_serverapp.py,sha256=
|
|
140
|
+
flwr/server/run_serverapp.py,sha256=avLi_yRNE5jD2ql95gzh04BTUbHvzH-N848_mdnnkVk,5972
|
|
141
141
|
flwr/server/server.py,sha256=UnBRlI6AGTj0nKeRtEQ3IalM3TJmggMKXhDyn8yKZNk,17664
|
|
142
142
|
flwr/server/server_app.py,sha256=KgAT_HqsfseTLNnfX2ph42PBbVqQ0lFzvYrT90V34y0,4402
|
|
143
143
|
flwr/server/server_config.py,sha256=CZaHVAsMvGLjpWVcLPkiYxgJN4xfIyAiUrCI3fETKY4,1349
|
|
@@ -209,8 +209,8 @@ flwr/simulation/ray_transport/ray_actor.py,sha256=_wv2eP7qxkCZ-6rMyYWnjLrGPBZRxj
|
|
|
209
209
|
flwr/simulation/ray_transport/ray_client_proxy.py,sha256=oDu4sEPIOu39vrNi-fqDAe10xtNUXMO49bM2RWfRcyw,6738
|
|
210
210
|
flwr/simulation/ray_transport/utils.py,sha256=TYdtfg1P9VfTdLMOJlifInGpxWHYs9UfUqIv2wfkRLA,2392
|
|
211
211
|
flwr/simulation/run_simulation.py,sha256=nxXNv3r8ODImd5o6f0sa_w5L0I08LD2Udw2OTXStRnQ,15694
|
|
212
|
-
flwr_nightly-1.9.0.
|
|
213
|
-
flwr_nightly-1.9.0.
|
|
214
|
-
flwr_nightly-1.9.0.
|
|
215
|
-
flwr_nightly-1.9.0.
|
|
216
|
-
flwr_nightly-1.9.0.
|
|
212
|
+
flwr_nightly-1.9.0.dev20240426.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
213
|
+
flwr_nightly-1.9.0.dev20240426.dist-info/METADATA,sha256=oAUYiOKXu_WSYxMwROhr8cswgSP9wXY7Gl0hKkP7ekU,15260
|
|
214
|
+
flwr_nightly-1.9.0.dev20240426.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
215
|
+
flwr_nightly-1.9.0.dev20240426.dist-info/entry_points.txt,sha256=DBrrf685V2W9NbbchQwvuqBEpj5ik8tMZNoZg_W2bZY,363
|
|
216
|
+
flwr_nightly-1.9.0.dev20240426.dist-info/RECORD,,
|
{flwr_nightly-1.9.0.dev20240424.dist-info → flwr_nightly-1.9.0.dev20240426.dist-info}/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|