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