flwr-nightly 1.9.0.dev20240520__py3-none-any.whl → 1.9.0.dev20240531__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 +2 -4
- flwr/cli/config_utils.py +1 -23
- flwr/cli/new/templates/app/pyproject.hf.toml.tpl +6 -0
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +6 -0
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +6 -0
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +6 -0
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +6 -0
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +6 -0
- flwr/cli/run/run.py +20 -4
- flwr/client/grpc_rere_client/connection.py +5 -2
- flwr/client/supernode/app.py +41 -23
- flwr/common/recordset_compat.py +8 -1
- flwr/common/secure_aggregation/crypto/symmetric_encryption.py +0 -15
- flwr/proto/grpcadapter_pb2.py +32 -0
- flwr/proto/grpcadapter_pb2.pyi +43 -0
- flwr/proto/grpcadapter_pb2_grpc.py +66 -0
- flwr/proto/grpcadapter_pb2_grpc.pyi +24 -0
- flwr/server/app.py +134 -182
- {flwr_nightly-1.9.0.dev20240520.dist-info → flwr_nightly-1.9.0.dev20240531.dist-info}/METADATA +4 -3
- {flwr_nightly-1.9.0.dev20240520.dist-info → flwr_nightly-1.9.0.dev20240531.dist-info}/RECORD +23 -19
- {flwr_nightly-1.9.0.dev20240520.dist-info → flwr_nightly-1.9.0.dev20240531.dist-info}/LICENSE +0 -0
- {flwr_nightly-1.9.0.dev20240520.dist-info → flwr_nightly-1.9.0.dev20240531.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.9.0.dev20240520.dist-info → flwr_nightly-1.9.0.dev20240531.dist-info}/entry_points.txt +0 -0
flwr/cli/build.py
CHANGED
|
@@ -24,7 +24,7 @@ import pathspec
|
|
|
24
24
|
import typer
|
|
25
25
|
from typing_extensions import Annotated
|
|
26
26
|
|
|
27
|
-
from .config_utils import
|
|
27
|
+
from .config_utils import load_and_validate
|
|
28
28
|
from .utils import is_valid_project_name
|
|
29
29
|
|
|
30
30
|
|
|
@@ -67,9 +67,7 @@ def build(
|
|
|
67
67
|
)
|
|
68
68
|
raise typer.Exit(code=1)
|
|
69
69
|
|
|
70
|
-
conf, errors, warnings =
|
|
71
|
-
directory / "pyproject.toml"
|
|
72
|
-
)
|
|
70
|
+
conf, errors, warnings = load_and_validate(directory / "pyproject.toml")
|
|
73
71
|
if conf is None:
|
|
74
72
|
typer.secho(
|
|
75
73
|
"Project configuration could not be loaded.\npyproject.toml is invalid:\n"
|
flwr/cli/config_utils.py
CHANGED
|
@@ -22,7 +22,7 @@ import tomli
|
|
|
22
22
|
from flwr.common import object_ref
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
def
|
|
25
|
+
def load_and_validate(
|
|
26
26
|
path: Optional[Path] = None,
|
|
27
27
|
) -> Tuple[Optional[Dict[str, Any]], List[str], List[str]]:
|
|
28
28
|
"""Load and validate pyproject.toml as dict.
|
|
@@ -47,14 +47,6 @@ def load_and_validate_with_defaults(
|
|
|
47
47
|
if not is_valid:
|
|
48
48
|
return (None, errors, warnings)
|
|
49
49
|
|
|
50
|
-
# Apply defaults
|
|
51
|
-
defaults = {
|
|
52
|
-
"flower": {
|
|
53
|
-
"engine": {"name": "simulation", "simulation": {"supernode": {"num": 2}}}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
config = apply_defaults(config, defaults)
|
|
57
|
-
|
|
58
50
|
return (config, errors, warnings)
|
|
59
51
|
|
|
60
52
|
|
|
@@ -129,17 +121,3 @@ def validate(config: Dict[str, Any]) -> Tuple[bool, List[str], List[str]]:
|
|
|
129
121
|
return False, [reason], []
|
|
130
122
|
|
|
131
123
|
return True, [], []
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
def apply_defaults(
|
|
135
|
-
config: Dict[str, Any],
|
|
136
|
-
defaults: Dict[str, Any],
|
|
137
|
-
) -> Dict[str, Any]:
|
|
138
|
-
"""Apply defaults to config."""
|
|
139
|
-
for key in defaults:
|
|
140
|
-
if key in config:
|
|
141
|
-
if isinstance(config[key], dict) and isinstance(defaults[key], dict):
|
|
142
|
-
apply_defaults(config[key], defaults[key])
|
|
143
|
-
else:
|
|
144
|
-
config[key] = defaults[key]
|
|
145
|
-
return config
|
flwr/cli/run/run.py
CHANGED
|
@@ -15,18 +15,32 @@
|
|
|
15
15
|
"""Flower command line interface `run` command."""
|
|
16
16
|
|
|
17
17
|
import sys
|
|
18
|
+
from enum import Enum
|
|
19
|
+
from typing import Optional
|
|
18
20
|
|
|
19
21
|
import typer
|
|
22
|
+
from typing_extensions import Annotated
|
|
20
23
|
|
|
21
24
|
from flwr.cli import config_utils
|
|
22
25
|
from flwr.simulation.run_simulation import _run_simulation
|
|
23
26
|
|
|
24
27
|
|
|
25
|
-
|
|
28
|
+
class Engine(str, Enum):
|
|
29
|
+
"""Enum defining the engine to run on."""
|
|
30
|
+
|
|
31
|
+
SIMULATION = "simulation"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def run(
|
|
35
|
+
engine: Annotated[
|
|
36
|
+
Optional[Engine],
|
|
37
|
+
typer.Option(case_sensitive=False, help="The ML framework to use"),
|
|
38
|
+
] = None,
|
|
39
|
+
) -> None:
|
|
26
40
|
"""Run Flower project."""
|
|
27
41
|
typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
|
|
28
42
|
|
|
29
|
-
config, errors, warnings = config_utils.
|
|
43
|
+
config, errors, warnings = config_utils.load_and_validate()
|
|
30
44
|
|
|
31
45
|
if config is None:
|
|
32
46
|
typer.secho(
|
|
@@ -49,9 +63,11 @@ def run() -> None:
|
|
|
49
63
|
|
|
50
64
|
server_app_ref = config["flower"]["components"]["serverapp"]
|
|
51
65
|
client_app_ref = config["flower"]["components"]["clientapp"]
|
|
52
|
-
engine = config["flower"]["engine"]["name"]
|
|
53
66
|
|
|
54
|
-
if engine
|
|
67
|
+
if engine is None:
|
|
68
|
+
engine = config["flower"]["engine"]["name"]
|
|
69
|
+
|
|
70
|
+
if engine == Engine.SIMULATION:
|
|
55
71
|
num_supernodes = config["flower"]["engine"]["simulation"]["supernode"]["num"]
|
|
56
72
|
|
|
57
73
|
typer.secho("Starting run... ", fg=typer.colors.BLUE)
|
|
@@ -21,7 +21,7 @@ from contextlib import contextmanager
|
|
|
21
21
|
from copy import copy
|
|
22
22
|
from logging import DEBUG, ERROR
|
|
23
23
|
from pathlib import Path
|
|
24
|
-
from typing import Callable, Iterator, Optional, Sequence, Tuple, Union, cast
|
|
24
|
+
from typing import Callable, Iterator, Optional, Sequence, Tuple, Type, Union, cast
|
|
25
25
|
|
|
26
26
|
import grpc
|
|
27
27
|
from cryptography.hazmat.primitives.asymmetric import ec
|
|
@@ -73,6 +73,7 @@ def grpc_request_response( # pylint: disable=R0913, R0914, R0915
|
|
|
73
73
|
authentication_keys: Optional[
|
|
74
74
|
Tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]
|
|
75
75
|
] = None,
|
|
76
|
+
adapter_cls: Optional[Type[FleetStub]] = None,
|
|
76
77
|
) -> Iterator[
|
|
77
78
|
Tuple[
|
|
78
79
|
Callable[[], Optional[Message]],
|
|
@@ -133,7 +134,9 @@ def grpc_request_response( # pylint: disable=R0913, R0914, R0915
|
|
|
133
134
|
channel.subscribe(on_channel_state_change)
|
|
134
135
|
|
|
135
136
|
# Shared variables for inner functions
|
|
136
|
-
|
|
137
|
+
if adapter_cls is None:
|
|
138
|
+
adapter_cls = FleetStub
|
|
139
|
+
stub = adapter_cls(channel)
|
|
137
140
|
metadata: Optional[Metadata] = None
|
|
138
141
|
node: Optional[Node] = None
|
|
139
142
|
ping_thread: Optional[threading.Thread] = None
|
flwr/client/supernode/app.py
CHANGED
|
@@ -20,6 +20,7 @@ from logging import DEBUG, INFO, WARN
|
|
|
20
20
|
from pathlib import Path
|
|
21
21
|
from typing import Callable, Optional, Tuple
|
|
22
22
|
|
|
23
|
+
from cryptography.exceptions import UnsupportedAlgorithm
|
|
23
24
|
from cryptography.hazmat.primitives.asymmetric import ec
|
|
24
25
|
from cryptography.hazmat.primitives.serialization import (
|
|
25
26
|
load_ssh_private_key,
|
|
@@ -31,9 +32,6 @@ from flwr.common import EventType, event
|
|
|
31
32
|
from flwr.common.exit_handlers import register_exit_handlers
|
|
32
33
|
from flwr.common.logger import log
|
|
33
34
|
from flwr.common.object_ref import load_app, validate
|
|
34
|
-
from flwr.common.secure_aggregation.crypto.symmetric_encryption import (
|
|
35
|
-
ssh_types_to_elliptic_curve,
|
|
36
|
-
)
|
|
37
35
|
|
|
38
36
|
from ..app import _start_client_internal
|
|
39
37
|
|
|
@@ -242,40 +240,60 @@ def _parse_args_common(parser: argparse.ArgumentParser) -> None:
|
|
|
242
240
|
" Default: current working directory.",
|
|
243
241
|
)
|
|
244
242
|
parser.add_argument(
|
|
245
|
-
"--
|
|
246
|
-
|
|
247
|
-
|
|
243
|
+
"--auth-supernode-private-key",
|
|
244
|
+
type=str,
|
|
245
|
+
help="The SuperNode's private key (as a path str) to enable authentication.",
|
|
246
|
+
)
|
|
247
|
+
parser.add_argument(
|
|
248
|
+
"--auth-supernode-public-key",
|
|
248
249
|
type=str,
|
|
249
|
-
help="
|
|
250
|
-
"key file, and (2) the client's public key file.",
|
|
250
|
+
help="The SuperNode's public key (as a path str) to enable authentication.",
|
|
251
251
|
)
|
|
252
252
|
|
|
253
253
|
|
|
254
254
|
def _try_setup_client_authentication(
|
|
255
255
|
args: argparse.Namespace,
|
|
256
256
|
) -> Optional[Tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]]:
|
|
257
|
-
if not args.
|
|
257
|
+
if not args.auth_supernode_private_key and not args.auth_supernode_public_key:
|
|
258
258
|
return None
|
|
259
259
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
260
|
+
if not args.auth_supernode_private_key or not args.auth_supernode_public_key:
|
|
261
|
+
sys.exit(
|
|
262
|
+
"Authentication requires file paths to both "
|
|
263
|
+
"'--auth-supernode-private-key' and '--auth-supernode-public-key'"
|
|
264
|
+
"to be provided (providing only one of them is not sufficient)."
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
try:
|
|
268
|
+
ssh_private_key = load_ssh_private_key(
|
|
269
|
+
Path(args.auth_supernode_private_key).read_bytes(),
|
|
270
|
+
None,
|
|
271
|
+
)
|
|
272
|
+
if not isinstance(ssh_private_key, ec.EllipticCurvePrivateKey):
|
|
273
|
+
raise ValueError()
|
|
274
|
+
except (ValueError, UnsupportedAlgorithm):
|
|
275
|
+
sys.exit(
|
|
276
|
+
"Error: Unable to parse the private key file in "
|
|
277
|
+
"'--auth-supernode-private-key'. Authentication requires elliptic "
|
|
278
|
+
"curve private and public key pair. Please ensure that the file "
|
|
279
|
+
"path points to a valid private key file and try again."
|
|
280
|
+
)
|
|
265
281
|
|
|
266
282
|
try:
|
|
267
|
-
|
|
268
|
-
|
|
283
|
+
ssh_public_key = load_ssh_public_key(
|
|
284
|
+
Path(args.auth_supernode_public_key).read_bytes()
|
|
269
285
|
)
|
|
270
|
-
|
|
286
|
+
if not isinstance(ssh_public_key, ec.EllipticCurvePublicKey):
|
|
287
|
+
raise ValueError()
|
|
288
|
+
except (ValueError, UnsupportedAlgorithm):
|
|
271
289
|
sys.exit(
|
|
272
|
-
"
|
|
273
|
-
"key
|
|
274
|
-
"private key pair. Please
|
|
275
|
-
"
|
|
290
|
+
"Error: Unable to parse the public key file in "
|
|
291
|
+
"'--auth-supernode-public-key'. Authentication requires elliptic "
|
|
292
|
+
"curve private and public key pair. Please ensure that the file "
|
|
293
|
+
"path points to a valid public key file and try again."
|
|
276
294
|
)
|
|
277
295
|
|
|
278
296
|
return (
|
|
279
|
-
|
|
280
|
-
|
|
297
|
+
ssh_private_key,
|
|
298
|
+
ssh_public_key,
|
|
281
299
|
)
|
flwr/common/recordset_compat.py
CHANGED
|
@@ -35,6 +35,8 @@ from .typing import (
|
|
|
35
35
|
Status,
|
|
36
36
|
)
|
|
37
37
|
|
|
38
|
+
EMPTY_TENSOR_KEY = "_empty"
|
|
39
|
+
|
|
38
40
|
|
|
39
41
|
def parametersrecord_to_parameters(
|
|
40
42
|
record: ParametersRecord, keep_input: bool
|
|
@@ -59,7 +61,8 @@ def parametersrecord_to_parameters(
|
|
|
59
61
|
parameters = Parameters(tensors=[], tensor_type="")
|
|
60
62
|
|
|
61
63
|
for key in list(record.keys()):
|
|
62
|
-
|
|
64
|
+
if key != EMPTY_TENSOR_KEY:
|
|
65
|
+
parameters.tensors.append(record[key].data)
|
|
63
66
|
|
|
64
67
|
if not parameters.tensor_type:
|
|
65
68
|
# Setting from first array in record. Recall the warning in the docstrings
|
|
@@ -103,6 +106,10 @@ def parameters_to_parametersrecord(
|
|
|
103
106
|
data=tensor, dtype="", stype=tensor_type, shape=[]
|
|
104
107
|
)
|
|
105
108
|
|
|
109
|
+
if num_arrays == 0:
|
|
110
|
+
ordered_dict[EMPTY_TENSOR_KEY] = Array(
|
|
111
|
+
data=b"", dtype="", stype=tensor_type, shape=[]
|
|
112
|
+
)
|
|
106
113
|
return ParametersRecord(ordered_dict, keep_input=keep_input)
|
|
107
114
|
|
|
108
115
|
|
|
@@ -117,18 +117,3 @@ def verify_hmac(key: bytes, message: bytes, hmac_value: bytes) -> bool:
|
|
|
117
117
|
return True
|
|
118
118
|
except InvalidSignature:
|
|
119
119
|
return False
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def ssh_types_to_elliptic_curve(
|
|
123
|
-
private_key: serialization.SSHPrivateKeyTypes,
|
|
124
|
-
public_key: serialization.SSHPublicKeyTypes,
|
|
125
|
-
) -> Tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]:
|
|
126
|
-
"""Cast SSH key types to elliptic curve."""
|
|
127
|
-
if isinstance(private_key, ec.EllipticCurvePrivateKey) and isinstance(
|
|
128
|
-
public_key, ec.EllipticCurvePublicKey
|
|
129
|
-
):
|
|
130
|
-
return (private_key, public_key)
|
|
131
|
-
|
|
132
|
-
raise TypeError(
|
|
133
|
-
"The provided key is not an EllipticCurvePrivateKey or EllipticCurvePublicKey"
|
|
134
|
-
)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
+
# source: flwr/proto/grpcadapter.proto
|
|
4
|
+
# Protobuf Python Version: 4.25.0
|
|
5
|
+
"""Generated protocol buffer code."""
|
|
6
|
+
from google.protobuf import descriptor as _descriptor
|
|
7
|
+
from google.protobuf import descriptor_pool as _descriptor_pool
|
|
8
|
+
from google.protobuf import symbol_database as _symbol_database
|
|
9
|
+
from google.protobuf.internal import builder as _builder
|
|
10
|
+
# @@protoc_insertion_point(imports)
|
|
11
|
+
|
|
12
|
+
_sym_db = _symbol_database.Default()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1c\x66lwr/proto/grpcadapter.proto\x12\nflwr.proto\"\xba\x01\n\x10MessageContainer\x12<\n\x08metadata\x18\x01 \x03(\x0b\x32*.flwr.proto.MessageContainer.MetadataEntry\x12\x19\n\x11grpc_message_name\x18\x02 \x01(\t\x12\x1c\n\x14grpc_message_content\x18\x03 \x01(\x0c\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x32Z\n\x0bGrpcAdapter\x12K\n\x0bSendReceive\x12\x1c.flwr.proto.MessageContainer\x1a\x1c.flwr.proto.MessageContainer\"\x00\x62\x06proto3')
|
|
18
|
+
|
|
19
|
+
_globals = globals()
|
|
20
|
+
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
21
|
+
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'flwr.proto.grpcadapter_pb2', _globals)
|
|
22
|
+
if _descriptor._USE_C_DESCRIPTORS == False:
|
|
23
|
+
DESCRIPTOR._options = None
|
|
24
|
+
_globals['_MESSAGECONTAINER_METADATAENTRY']._options = None
|
|
25
|
+
_globals['_MESSAGECONTAINER_METADATAENTRY']._serialized_options = b'8\001'
|
|
26
|
+
_globals['_MESSAGECONTAINER']._serialized_start=45
|
|
27
|
+
_globals['_MESSAGECONTAINER']._serialized_end=231
|
|
28
|
+
_globals['_MESSAGECONTAINER_METADATAENTRY']._serialized_start=184
|
|
29
|
+
_globals['_MESSAGECONTAINER_METADATAENTRY']._serialized_end=231
|
|
30
|
+
_globals['_GRPCADAPTER']._serialized_start=233
|
|
31
|
+
_globals['_GRPCADAPTER']._serialized_end=323
|
|
32
|
+
# @@protoc_insertion_point(module_scope)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@generated by mypy-protobuf. Do not edit manually!
|
|
3
|
+
isort:skip_file
|
|
4
|
+
"""
|
|
5
|
+
import builtins
|
|
6
|
+
import google.protobuf.descriptor
|
|
7
|
+
import google.protobuf.internal.containers
|
|
8
|
+
import google.protobuf.message
|
|
9
|
+
import typing
|
|
10
|
+
import typing_extensions
|
|
11
|
+
|
|
12
|
+
DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
|
|
13
|
+
|
|
14
|
+
class MessageContainer(google.protobuf.message.Message):
|
|
15
|
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
|
16
|
+
class MetadataEntry(google.protobuf.message.Message):
|
|
17
|
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
|
18
|
+
KEY_FIELD_NUMBER: builtins.int
|
|
19
|
+
VALUE_FIELD_NUMBER: builtins.int
|
|
20
|
+
key: typing.Text
|
|
21
|
+
value: typing.Text
|
|
22
|
+
def __init__(self,
|
|
23
|
+
*,
|
|
24
|
+
key: typing.Text = ...,
|
|
25
|
+
value: typing.Text = ...,
|
|
26
|
+
) -> None: ...
|
|
27
|
+
def ClearField(self, field_name: typing_extensions.Literal["key",b"key","value",b"value"]) -> None: ...
|
|
28
|
+
|
|
29
|
+
METADATA_FIELD_NUMBER: builtins.int
|
|
30
|
+
GRPC_MESSAGE_NAME_FIELD_NUMBER: builtins.int
|
|
31
|
+
GRPC_MESSAGE_CONTENT_FIELD_NUMBER: builtins.int
|
|
32
|
+
@property
|
|
33
|
+
def metadata(self) -> google.protobuf.internal.containers.ScalarMap[typing.Text, typing.Text]: ...
|
|
34
|
+
grpc_message_name: typing.Text
|
|
35
|
+
grpc_message_content: builtins.bytes
|
|
36
|
+
def __init__(self,
|
|
37
|
+
*,
|
|
38
|
+
metadata: typing.Optional[typing.Mapping[typing.Text, typing.Text]] = ...,
|
|
39
|
+
grpc_message_name: typing.Text = ...,
|
|
40
|
+
grpc_message_content: builtins.bytes = ...,
|
|
41
|
+
) -> None: ...
|
|
42
|
+
def ClearField(self, field_name: typing_extensions.Literal["grpc_message_content",b"grpc_message_content","grpc_message_name",b"grpc_message_name","metadata",b"metadata"]) -> None: ...
|
|
43
|
+
global___MessageContainer = MessageContainer
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
|
|
2
|
+
"""Client and server classes corresponding to protobuf-defined services."""
|
|
3
|
+
import grpc
|
|
4
|
+
|
|
5
|
+
from flwr.proto import grpcadapter_pb2 as flwr_dot_proto_dot_grpcadapter__pb2
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class GrpcAdapterStub(object):
|
|
9
|
+
"""Missing associated documentation comment in .proto file."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, channel):
|
|
12
|
+
"""Constructor.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
channel: A grpc.Channel.
|
|
16
|
+
"""
|
|
17
|
+
self.SendReceive = channel.unary_unary(
|
|
18
|
+
'/flwr.proto.GrpcAdapter/SendReceive',
|
|
19
|
+
request_serializer=flwr_dot_proto_dot_grpcadapter__pb2.MessageContainer.SerializeToString,
|
|
20
|
+
response_deserializer=flwr_dot_proto_dot_grpcadapter__pb2.MessageContainer.FromString,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class GrpcAdapterServicer(object):
|
|
25
|
+
"""Missing associated documentation comment in .proto file."""
|
|
26
|
+
|
|
27
|
+
def SendReceive(self, request, context):
|
|
28
|
+
"""Missing associated documentation comment in .proto file."""
|
|
29
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
30
|
+
context.set_details('Method not implemented!')
|
|
31
|
+
raise NotImplementedError('Method not implemented!')
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def add_GrpcAdapterServicer_to_server(servicer, server):
|
|
35
|
+
rpc_method_handlers = {
|
|
36
|
+
'SendReceive': grpc.unary_unary_rpc_method_handler(
|
|
37
|
+
servicer.SendReceive,
|
|
38
|
+
request_deserializer=flwr_dot_proto_dot_grpcadapter__pb2.MessageContainer.FromString,
|
|
39
|
+
response_serializer=flwr_dot_proto_dot_grpcadapter__pb2.MessageContainer.SerializeToString,
|
|
40
|
+
),
|
|
41
|
+
}
|
|
42
|
+
generic_handler = grpc.method_handlers_generic_handler(
|
|
43
|
+
'flwr.proto.GrpcAdapter', rpc_method_handlers)
|
|
44
|
+
server.add_generic_rpc_handlers((generic_handler,))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# This class is part of an EXPERIMENTAL API.
|
|
48
|
+
class GrpcAdapter(object):
|
|
49
|
+
"""Missing associated documentation comment in .proto file."""
|
|
50
|
+
|
|
51
|
+
@staticmethod
|
|
52
|
+
def SendReceive(request,
|
|
53
|
+
target,
|
|
54
|
+
options=(),
|
|
55
|
+
channel_credentials=None,
|
|
56
|
+
call_credentials=None,
|
|
57
|
+
insecure=False,
|
|
58
|
+
compression=None,
|
|
59
|
+
wait_for_ready=None,
|
|
60
|
+
timeout=None,
|
|
61
|
+
metadata=None):
|
|
62
|
+
return grpc.experimental.unary_unary(request, target, '/flwr.proto.GrpcAdapter/SendReceive',
|
|
63
|
+
flwr_dot_proto_dot_grpcadapter__pb2.MessageContainer.SerializeToString,
|
|
64
|
+
flwr_dot_proto_dot_grpcadapter__pb2.MessageContainer.FromString,
|
|
65
|
+
options, channel_credentials,
|
|
66
|
+
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@generated by mypy-protobuf. Do not edit manually!
|
|
3
|
+
isort:skip_file
|
|
4
|
+
"""
|
|
5
|
+
import abc
|
|
6
|
+
import flwr.proto.grpcadapter_pb2
|
|
7
|
+
import grpc
|
|
8
|
+
|
|
9
|
+
class GrpcAdapterStub:
|
|
10
|
+
def __init__(self, channel: grpc.Channel) -> None: ...
|
|
11
|
+
SendReceive: grpc.UnaryUnaryMultiCallable[
|
|
12
|
+
flwr.proto.grpcadapter_pb2.MessageContainer,
|
|
13
|
+
flwr.proto.grpcadapter_pb2.MessageContainer]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class GrpcAdapterServicer(metaclass=abc.ABCMeta):
|
|
17
|
+
@abc.abstractmethod
|
|
18
|
+
def SendReceive(self,
|
|
19
|
+
request: flwr.proto.grpcadapter_pb2.MessageContainer,
|
|
20
|
+
context: grpc.ServicerContext,
|
|
21
|
+
) -> flwr.proto.grpcadapter_pb2.MessageContainer: ...
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def add_GrpcAdapterServicer_to_server(servicer: GrpcAdapterServicer, server: grpc.Server) -> None: ...
|
flwr/server/app.py
CHANGED
|
@@ -15,17 +15,17 @@
|
|
|
15
15
|
"""Flower server app."""
|
|
16
16
|
|
|
17
17
|
import argparse
|
|
18
|
-
import asyncio
|
|
19
18
|
import csv
|
|
20
19
|
import importlib.util
|
|
21
20
|
import sys
|
|
22
21
|
import threading
|
|
23
|
-
from logging import
|
|
22
|
+
from logging import INFO, WARN
|
|
24
23
|
from os.path import isfile
|
|
25
24
|
from pathlib import Path
|
|
26
|
-
from typing import
|
|
25
|
+
from typing import Optional, Sequence, Set, Tuple
|
|
27
26
|
|
|
28
27
|
import grpc
|
|
28
|
+
from cryptography.exceptions import UnsupportedAlgorithm
|
|
29
29
|
from cryptography.hazmat.primitives.asymmetric import ec
|
|
30
30
|
from cryptography.hazmat.primitives.serialization import (
|
|
31
31
|
load_ssh_private_key,
|
|
@@ -38,14 +38,12 @@ from flwr.common.constant import (
|
|
|
38
38
|
MISSING_EXTRA_REST,
|
|
39
39
|
TRANSPORT_TYPE_GRPC_RERE,
|
|
40
40
|
TRANSPORT_TYPE_REST,
|
|
41
|
-
TRANSPORT_TYPE_VCE,
|
|
42
41
|
)
|
|
43
42
|
from flwr.common.exit_handlers import register_exit_handlers
|
|
44
43
|
from flwr.common.logger import log, warn_deprecated_feature
|
|
45
44
|
from flwr.common.secure_aggregation.crypto.symmetric_encryption import (
|
|
46
45
|
private_key_to_bytes,
|
|
47
46
|
public_key_to_bytes,
|
|
48
|
-
ssh_types_to_elliptic_curve,
|
|
49
47
|
)
|
|
50
48
|
from flwr.proto.fleet_pb2_grpc import ( # pylint: disable=E0611
|
|
51
49
|
add_FleetServicer_to_server,
|
|
@@ -63,7 +61,6 @@ from .superlink.fleet.grpc_bidi.grpc_server import (
|
|
|
63
61
|
)
|
|
64
62
|
from .superlink.fleet.grpc_rere.fleet_servicer import FleetServicer
|
|
65
63
|
from .superlink.fleet.grpc_rere.server_interceptor import AuthenticateServerInterceptor
|
|
66
|
-
from .superlink.fleet.vce import start_vce
|
|
67
64
|
from .superlink.state import StateFactory
|
|
68
65
|
|
|
69
66
|
ADDRESS_DRIVER_API = "0.0.0.0:9091"
|
|
@@ -349,6 +346,9 @@ def run_superlink() -> None:
|
|
|
349
346
|
sys.exit(MISSING_EXTRA_REST)
|
|
350
347
|
address_arg = args.rest_fleet_api_address
|
|
351
348
|
parsed_address = parse_address(address_arg)
|
|
349
|
+
_, ssl_certfile, ssl_keyfile = (
|
|
350
|
+
certificates if certificates is not None else (None, None, None)
|
|
351
|
+
)
|
|
352
352
|
if not parsed_address:
|
|
353
353
|
sys.exit(f"Fleet IP address ({address_arg}) cannot be parsed.")
|
|
354
354
|
host, port, _ = parsed_address
|
|
@@ -357,8 +357,8 @@ def run_superlink() -> None:
|
|
|
357
357
|
args=(
|
|
358
358
|
host,
|
|
359
359
|
port,
|
|
360
|
-
|
|
361
|
-
|
|
360
|
+
ssl_keyfile,
|
|
361
|
+
ssl_certfile,
|
|
362
362
|
state_factory,
|
|
363
363
|
args.rest_fleet_api_workers,
|
|
364
364
|
),
|
|
@@ -401,17 +401,6 @@ def run_superlink() -> None:
|
|
|
401
401
|
interceptors=interceptors,
|
|
402
402
|
)
|
|
403
403
|
grpc_servers.append(fleet_server)
|
|
404
|
-
elif args.fleet_api_type == TRANSPORT_TYPE_VCE:
|
|
405
|
-
f_stop = asyncio.Event() # Does nothing
|
|
406
|
-
_run_fleet_api_vce(
|
|
407
|
-
num_supernodes=args.num_supernodes,
|
|
408
|
-
client_app_attr=args.client_app,
|
|
409
|
-
backend_name=args.backend,
|
|
410
|
-
backend_config_json_stream=args.backend_config,
|
|
411
|
-
app_dir=args.app_dir,
|
|
412
|
-
state_factory=state_factory,
|
|
413
|
-
f_stop=f_stop,
|
|
414
|
-
)
|
|
415
404
|
else:
|
|
416
405
|
raise ValueError(f"Unknown fleet_api_type: {args.fleet_api_type}")
|
|
417
406
|
|
|
@@ -435,44 +424,69 @@ def _try_setup_client_authentication(
|
|
|
435
424
|
args: argparse.Namespace,
|
|
436
425
|
certificates: Optional[Tuple[bytes, bytes, bytes]],
|
|
437
426
|
) -> Optional[Tuple[Set[bytes], ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]]:
|
|
438
|
-
if
|
|
427
|
+
if (
|
|
428
|
+
not args.auth_list_public_keys
|
|
429
|
+
and not args.auth_superlink_private_key
|
|
430
|
+
and not args.auth_superlink_public_key
|
|
431
|
+
):
|
|
439
432
|
return None
|
|
440
433
|
|
|
434
|
+
if (
|
|
435
|
+
not args.auth_list_public_keys
|
|
436
|
+
or not args.auth_superlink_private_key
|
|
437
|
+
or not args.auth_superlink_public_key
|
|
438
|
+
):
|
|
439
|
+
sys.exit(
|
|
440
|
+
"Authentication requires providing file paths for "
|
|
441
|
+
"'--auth-list-public-keys', '--auth-superlink-private-key' and "
|
|
442
|
+
"'--auth-superlink-public-key'. Provide all three to enable authentication."
|
|
443
|
+
)
|
|
444
|
+
|
|
441
445
|
if certificates is None:
|
|
442
446
|
sys.exit(
|
|
443
|
-
"
|
|
444
|
-
"Please provide certificate paths
|
|
445
|
-
"
|
|
447
|
+
"Authentication requires secure connections. "
|
|
448
|
+
"Please provide certificate paths to `--ssl-certfile`, "
|
|
449
|
+
"`--ssl-keyfile`, and `—-ssl-ca-certfile` and try again."
|
|
446
450
|
)
|
|
447
451
|
|
|
448
|
-
client_keys_file_path = Path(args.
|
|
452
|
+
client_keys_file_path = Path(args.auth_list_public_keys)
|
|
449
453
|
if not client_keys_file_path.exists():
|
|
450
454
|
sys.exit(
|
|
451
|
-
"The provided path to the
|
|
455
|
+
"The provided path to the known public keys CSV file does not exist: "
|
|
452
456
|
f"{client_keys_file_path}. "
|
|
453
|
-
"Please provide the CSV file path containing known
|
|
454
|
-
"to '--
|
|
457
|
+
"Please provide the CSV file path containing known public keys "
|
|
458
|
+
"to '--auth-list-public-keys'."
|
|
455
459
|
)
|
|
456
460
|
|
|
457
461
|
client_public_keys: Set[bytes] = set()
|
|
458
|
-
ssh_private_key = load_ssh_private_key(
|
|
459
|
-
Path(args.require_client_authentication[1]).read_bytes(),
|
|
460
|
-
None,
|
|
461
|
-
)
|
|
462
|
-
ssh_public_key = load_ssh_public_key(
|
|
463
|
-
Path(args.require_client_authentication[2]).read_bytes()
|
|
464
|
-
)
|
|
465
462
|
|
|
466
463
|
try:
|
|
467
|
-
|
|
468
|
-
|
|
464
|
+
ssh_private_key = load_ssh_private_key(
|
|
465
|
+
Path(args.auth_superlink_private_key).read_bytes(),
|
|
466
|
+
None,
|
|
469
467
|
)
|
|
470
|
-
|
|
468
|
+
if not isinstance(ssh_private_key, ec.EllipticCurvePrivateKey):
|
|
469
|
+
raise ValueError()
|
|
470
|
+
except (ValueError, UnsupportedAlgorithm):
|
|
471
471
|
sys.exit(
|
|
472
|
-
"
|
|
473
|
-
"key
|
|
474
|
-
"private key pair. Please
|
|
475
|
-
"
|
|
472
|
+
"Error: Unable to parse the private key file in "
|
|
473
|
+
"'--auth-superlink-private-key'. Authentication requires elliptic "
|
|
474
|
+
"curve private and public key pair. Please ensure that the file "
|
|
475
|
+
"path points to a valid private key file and try again."
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
try:
|
|
479
|
+
ssh_public_key = load_ssh_public_key(
|
|
480
|
+
Path(args.auth_superlink_public_key).read_bytes()
|
|
481
|
+
)
|
|
482
|
+
if not isinstance(ssh_public_key, ec.EllipticCurvePublicKey):
|
|
483
|
+
raise ValueError()
|
|
484
|
+
except (ValueError, UnsupportedAlgorithm):
|
|
485
|
+
sys.exit(
|
|
486
|
+
"Error: Unable to parse the public key file in "
|
|
487
|
+
"'--auth-superlink-public-key'. Authentication requires elliptic "
|
|
488
|
+
"curve private and public key pair. Please ensure that the file "
|
|
489
|
+
"path points to a valid public key file and try again."
|
|
476
490
|
)
|
|
477
491
|
|
|
478
492
|
with open(client_keys_file_path, newline="", encoding="utf-8") as csvfile:
|
|
@@ -484,14 +498,14 @@ def _try_setup_client_authentication(
|
|
|
484
498
|
client_public_keys.add(public_key_to_bytes(public_key))
|
|
485
499
|
else:
|
|
486
500
|
sys.exit(
|
|
487
|
-
"Error: Unable to parse the public keys in the
|
|
488
|
-
"file. Please ensure that the
|
|
489
|
-
"SSH public keys and try again."
|
|
501
|
+
"Error: Unable to parse the public keys in the CSV "
|
|
502
|
+
"file. Please ensure that the CSV file path points to a valid "
|
|
503
|
+
"known SSH public keys files and try again."
|
|
490
504
|
)
|
|
491
505
|
return (
|
|
492
506
|
client_public_keys,
|
|
493
|
-
|
|
494
|
-
|
|
507
|
+
ssh_private_key,
|
|
508
|
+
ssh_public_key,
|
|
495
509
|
)
|
|
496
510
|
|
|
497
511
|
|
|
@@ -501,21 +515,52 @@ def _try_obtain_certificates(
|
|
|
501
515
|
# Obtain certificates
|
|
502
516
|
if args.insecure:
|
|
503
517
|
log(WARN, "Option `--insecure` was set. Starting insecure HTTP server.")
|
|
504
|
-
|
|
518
|
+
return None
|
|
505
519
|
# Check if certificates are provided
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
520
|
+
if args.fleet_api_type == TRANSPORT_TYPE_GRPC_RERE:
|
|
521
|
+
if args.ssl_certfile and args.ssl_keyfile and args.ssl_ca_certfile:
|
|
522
|
+
if not isfile(args.ssl_ca_certfile):
|
|
523
|
+
sys.exit("Path argument `--ssl-ca-certfile` does not point to a file.")
|
|
524
|
+
if not isfile(args.ssl_certfile):
|
|
525
|
+
sys.exit("Path argument `--ssl-certfile` does not point to a file.")
|
|
526
|
+
if not isfile(args.ssl_keyfile):
|
|
527
|
+
sys.exit("Path argument `--ssl-keyfile` does not point to a file.")
|
|
528
|
+
certificates = (
|
|
529
|
+
Path(args.ssl_ca_certfile).read_bytes(), # CA certificate
|
|
530
|
+
Path(args.ssl_certfile).read_bytes(), # server certificate
|
|
531
|
+
Path(args.ssl_keyfile).read_bytes(), # server private key
|
|
532
|
+
)
|
|
533
|
+
return certificates
|
|
534
|
+
if args.ssl_certfile or args.ssl_keyfile or args.ssl_ca_certfile:
|
|
535
|
+
sys.exit(
|
|
536
|
+
"You need to provide valid file paths to `--ssl-certfile`, "
|
|
537
|
+
"`--ssl-keyfile`, and `—-ssl-ca-certfile` to create a secure "
|
|
538
|
+
"connection in Fleet API server (gRPC-rere)."
|
|
539
|
+
)
|
|
540
|
+
if args.fleet_api_type == TRANSPORT_TYPE_REST:
|
|
541
|
+
if args.ssl_certfile and args.ssl_keyfile:
|
|
542
|
+
if not isfile(args.ssl_certfile):
|
|
543
|
+
sys.exit("Path argument `--ssl-certfile` does not point to a file.")
|
|
544
|
+
if not isfile(args.ssl_keyfile):
|
|
545
|
+
sys.exit("Path argument `--ssl-keyfile` does not point to a file.")
|
|
546
|
+
certificates = (
|
|
547
|
+
b"",
|
|
548
|
+
Path(args.ssl_certfile).read_bytes(), # server certificate
|
|
549
|
+
Path(args.ssl_keyfile).read_bytes(), # server private key
|
|
550
|
+
)
|
|
551
|
+
return certificates
|
|
552
|
+
if args.ssl_certfile or args.ssl_keyfile:
|
|
553
|
+
sys.exit(
|
|
554
|
+
"You need to provide valid file paths to `--ssl-certfile` "
|
|
555
|
+
"and `--ssl-keyfile` to create a secure connection "
|
|
556
|
+
"in Fleet API server (REST, experimental)."
|
|
557
|
+
)
|
|
558
|
+
sys.exit(
|
|
559
|
+
"Certificates are required unless running in insecure mode. "
|
|
560
|
+
"Please provide certificate paths to `--ssl-certfile`, "
|
|
561
|
+
"`--ssl-keyfile`, and `—-ssl-ca-certfile` or run the server "
|
|
562
|
+
"in insecure mode using '--insecure' if you understand the risks."
|
|
563
|
+
)
|
|
519
564
|
|
|
520
565
|
|
|
521
566
|
def _run_fleet_api_grpc_rere(
|
|
@@ -544,29 +589,6 @@ def _run_fleet_api_grpc_rere(
|
|
|
544
589
|
return fleet_grpc_server
|
|
545
590
|
|
|
546
591
|
|
|
547
|
-
# pylint: disable=too-many-arguments
|
|
548
|
-
def _run_fleet_api_vce(
|
|
549
|
-
num_supernodes: int,
|
|
550
|
-
client_app_attr: str,
|
|
551
|
-
backend_name: str,
|
|
552
|
-
backend_config_json_stream: str,
|
|
553
|
-
app_dir: str,
|
|
554
|
-
state_factory: StateFactory,
|
|
555
|
-
f_stop: asyncio.Event,
|
|
556
|
-
) -> None:
|
|
557
|
-
log(INFO, "Flower VCE: Starting Fleet API (VirtualClientEngine)")
|
|
558
|
-
|
|
559
|
-
start_vce(
|
|
560
|
-
num_supernodes=num_supernodes,
|
|
561
|
-
client_app_attr=client_app_attr,
|
|
562
|
-
backend_name=backend_name,
|
|
563
|
-
backend_config_json_stream=backend_config_json_stream,
|
|
564
|
-
state_factory=state_factory,
|
|
565
|
-
app_dir=app_dir,
|
|
566
|
-
f_stop=f_stop,
|
|
567
|
-
)
|
|
568
|
-
|
|
569
|
-
|
|
570
592
|
# pylint: disable=import-outside-toplevel,too-many-arguments
|
|
571
593
|
def _run_fleet_api_rest(
|
|
572
594
|
host: str,
|
|
@@ -594,14 +616,6 @@ def _run_fleet_api_rest(
|
|
|
594
616
|
# See: https://www.starlette.io/applications/#accessing-the-app-instance
|
|
595
617
|
fast_api_app.state.STATE_FACTORY = state_factory
|
|
596
618
|
|
|
597
|
-
validation_exceptions = _validate_ssl_files(
|
|
598
|
-
ssl_certfile=ssl_certfile, ssl_keyfile=ssl_keyfile
|
|
599
|
-
)
|
|
600
|
-
if any(validation_exceptions):
|
|
601
|
-
# Starting with 3.11 we can use ExceptionGroup but for now
|
|
602
|
-
# this seems to be the reasonable approach.
|
|
603
|
-
raise ValueError(validation_exceptions)
|
|
604
|
-
|
|
605
619
|
uvicorn.run(
|
|
606
620
|
app="flwr.server.superlink.fleet.rest_rere.rest_api:app",
|
|
607
621
|
port=port,
|
|
@@ -614,32 +628,6 @@ def _run_fleet_api_rest(
|
|
|
614
628
|
)
|
|
615
629
|
|
|
616
630
|
|
|
617
|
-
def _validate_ssl_files(
|
|
618
|
-
ssl_keyfile: Optional[str], ssl_certfile: Optional[str]
|
|
619
|
-
) -> List[ValueError]:
|
|
620
|
-
validation_exceptions = []
|
|
621
|
-
|
|
622
|
-
if ssl_keyfile is not None and not isfile(ssl_keyfile):
|
|
623
|
-
msg = "Path argument `--ssl-keyfile` does not point to a file."
|
|
624
|
-
log(ERROR, msg)
|
|
625
|
-
validation_exceptions.append(ValueError(msg))
|
|
626
|
-
|
|
627
|
-
if ssl_certfile is not None and not isfile(ssl_certfile):
|
|
628
|
-
msg = "Path argument `--ssl-certfile` does not point to a file."
|
|
629
|
-
log(ERROR, msg)
|
|
630
|
-
validation_exceptions.append(ValueError(msg))
|
|
631
|
-
|
|
632
|
-
if not bool(ssl_keyfile) == bool(ssl_certfile):
|
|
633
|
-
msg = (
|
|
634
|
-
"When setting one of `--ssl-keyfile` and "
|
|
635
|
-
"`--ssl-certfile`, both have to be used."
|
|
636
|
-
)
|
|
637
|
-
log(ERROR, msg)
|
|
638
|
-
validation_exceptions.append(ValueError(msg))
|
|
639
|
-
|
|
640
|
-
return validation_exceptions
|
|
641
|
-
|
|
642
|
-
|
|
643
631
|
def _parse_args_run_driver_api() -> argparse.ArgumentParser:
|
|
644
632
|
"""Parse command line arguments for Driver API."""
|
|
645
633
|
parser = argparse.ArgumentParser(
|
|
@@ -696,13 +684,23 @@ def _add_args_common(parser: argparse.ArgumentParser) -> None:
|
|
|
696
684
|
"Use this flag only if you understand the risks.",
|
|
697
685
|
)
|
|
698
686
|
parser.add_argument(
|
|
699
|
-
"--
|
|
700
|
-
|
|
701
|
-
|
|
687
|
+
"--ssl-certfile",
|
|
688
|
+
help="Fleet API server SSL certificate file (as a path str) "
|
|
689
|
+
"to create a secure connection.",
|
|
690
|
+
type=str,
|
|
691
|
+
default=None,
|
|
692
|
+
)
|
|
693
|
+
parser.add_argument(
|
|
694
|
+
"--ssl-keyfile",
|
|
695
|
+
help="Fleet API server SSL private key file (as a path str) "
|
|
696
|
+
"to create a secure connection.",
|
|
697
|
+
type=str,
|
|
698
|
+
)
|
|
699
|
+
parser.add_argument(
|
|
700
|
+
"--ssl-ca-certfile",
|
|
701
|
+
help="Fleet API server SSL CA certificate file (as a path str) "
|
|
702
|
+
"to create a secure connection.",
|
|
702
703
|
type=str,
|
|
703
|
-
help="Paths to the CA certificate, server certificate, and server private "
|
|
704
|
-
"key, in that order. Note: The server can only be started without "
|
|
705
|
-
"certificates by enabling the `--insecure` flag.",
|
|
706
704
|
)
|
|
707
705
|
parser.add_argument(
|
|
708
706
|
"--database",
|
|
@@ -714,13 +712,20 @@ def _add_args_common(parser: argparse.ArgumentParser) -> None:
|
|
|
714
712
|
default=DATABASE,
|
|
715
713
|
)
|
|
716
714
|
parser.add_argument(
|
|
717
|
-
"--
|
|
718
|
-
|
|
719
|
-
|
|
715
|
+
"--auth-list-public-keys",
|
|
716
|
+
type=str,
|
|
717
|
+
help="A CSV file (as a path str) containing a list of known public "
|
|
718
|
+
"keys to enable authentication.",
|
|
719
|
+
)
|
|
720
|
+
parser.add_argument(
|
|
721
|
+
"--auth-superlink-private-key",
|
|
722
|
+
type=str,
|
|
723
|
+
help="The SuperLink's private key (as a path str) to enable authentication.",
|
|
724
|
+
)
|
|
725
|
+
parser.add_argument(
|
|
726
|
+
"--auth-superlink-public-key",
|
|
720
727
|
type=str,
|
|
721
|
-
help="
|
|
722
|
-
"known client public keys for authentication, (2) the server's private "
|
|
723
|
-
"key file, and (3) the server's public key file.",
|
|
728
|
+
help="The SuperLink's public key (as a path str) to enable authentication.",
|
|
724
729
|
)
|
|
725
730
|
|
|
726
731
|
|
|
@@ -751,14 +756,6 @@ def _add_args_fleet_api(parser: argparse.ArgumentParser) -> None:
|
|
|
751
756
|
help="Start a Fleet API server (REST, experimental)",
|
|
752
757
|
)
|
|
753
758
|
|
|
754
|
-
ex_group.add_argument(
|
|
755
|
-
"--vce",
|
|
756
|
-
action="store_const",
|
|
757
|
-
dest="fleet_api_type",
|
|
758
|
-
const=TRANSPORT_TYPE_VCE,
|
|
759
|
-
help="Start a Fleet API server (VirtualClientEngine)",
|
|
760
|
-
)
|
|
761
|
-
|
|
762
759
|
# Fleet API gRPC-rere options
|
|
763
760
|
grpc_rere_group = parser.add_argument_group(
|
|
764
761
|
"Fleet API (gRPC-rere) server options", ""
|
|
@@ -776,54 +773,9 @@ def _add_args_fleet_api(parser: argparse.ArgumentParser) -> None:
|
|
|
776
773
|
help="Fleet API (REST) server address (IPv4, IPv6, or a domain name)",
|
|
777
774
|
default=ADDRESS_FLEET_API_REST,
|
|
778
775
|
)
|
|
779
|
-
rest_group.add_argument(
|
|
780
|
-
"--ssl-certfile",
|
|
781
|
-
help="Fleet API (REST) server SSL certificate file (as a path str), "
|
|
782
|
-
"needed for using 'https'.",
|
|
783
|
-
default=None,
|
|
784
|
-
)
|
|
785
|
-
rest_group.add_argument(
|
|
786
|
-
"--ssl-keyfile",
|
|
787
|
-
help="Fleet API (REST) server SSL private key file (as a path str), "
|
|
788
|
-
"needed for using 'https'.",
|
|
789
|
-
default=None,
|
|
790
|
-
)
|
|
791
776
|
rest_group.add_argument(
|
|
792
777
|
"--rest-fleet-api-workers",
|
|
793
778
|
help="Set the number of concurrent workers for the Fleet API REST server.",
|
|
794
779
|
type=int,
|
|
795
780
|
default=1,
|
|
796
781
|
)
|
|
797
|
-
|
|
798
|
-
# Fleet API VCE options
|
|
799
|
-
vce_group = parser.add_argument_group("Fleet API (VCE) server options", "")
|
|
800
|
-
vce_group.add_argument(
|
|
801
|
-
"--client-app",
|
|
802
|
-
help="For example: `client:app` or `project.package.module:wrapper.app`.",
|
|
803
|
-
)
|
|
804
|
-
vce_group.add_argument(
|
|
805
|
-
"--num-supernodes",
|
|
806
|
-
type=int,
|
|
807
|
-
help="Number of simulated SuperNodes.",
|
|
808
|
-
)
|
|
809
|
-
vce_group.add_argument(
|
|
810
|
-
"--backend",
|
|
811
|
-
default="ray",
|
|
812
|
-
type=str,
|
|
813
|
-
help="Simulation backend that executes the ClientApp.",
|
|
814
|
-
)
|
|
815
|
-
vce_group.add_argument(
|
|
816
|
-
"--backend-config",
|
|
817
|
-
type=str,
|
|
818
|
-
default='{"client_resources": {"num_cpus":1, "num_gpus":0.0}, "tensorflow": 0}',
|
|
819
|
-
help='A JSON formatted stream, e.g \'{"<keyA>":<value>, "<keyB>":<value>}\' to '
|
|
820
|
-
"configure a backend. Values supported in <value> are those included by "
|
|
821
|
-
"`flwr.common.typing.ConfigsRecordValues`. ",
|
|
822
|
-
)
|
|
823
|
-
parser.add_argument(
|
|
824
|
-
"--app-dir",
|
|
825
|
-
default="",
|
|
826
|
-
help="Add specified directory to the PYTHONPATH and load"
|
|
827
|
-
"ClientApp from there."
|
|
828
|
-
" Default: current working directory.",
|
|
829
|
-
)
|
{flwr_nightly-1.9.0.dev20240520.dist-info → flwr_nightly-1.9.0.dev20240531.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: flwr-nightly
|
|
3
|
-
Version: 1.9.0.
|
|
3
|
+
Version: 1.9.0.dev20240531
|
|
4
4
|
Summary: Flower: A Friendly Federated Learning Framework
|
|
5
5
|
Home-page: https://flower.ai
|
|
6
6
|
License: Apache-2.0
|
|
@@ -39,7 +39,7 @@ Requires-Dist: numpy (>=1.21.0,<2.0.0)
|
|
|
39
39
|
Requires-Dist: pathspec (>=0.12.1,<0.13.0)
|
|
40
40
|
Requires-Dist: protobuf (>=4.25.2,<5.0.0)
|
|
41
41
|
Requires-Dist: pycryptodome (>=3.18.0,<4.0.0)
|
|
42
|
-
Requires-Dist: ray (==2.
|
|
42
|
+
Requires-Dist: ray (==2.10.0) ; (python_version >= "3.8" and python_version < "3.12") and (extra == "simulation")
|
|
43
43
|
Requires-Dist: requests (>=2.31.0,<3.0.0) ; extra == "rest"
|
|
44
44
|
Requires-Dist: starlette (>=0.31.0,<0.32.0) ; extra == "rest"
|
|
45
45
|
Requires-Dist: tomli (>=2.0.1,<3.0.0)
|
|
@@ -201,8 +201,9 @@ Other [examples](https://github.com/adap/flower/tree/main/examples):
|
|
|
201
201
|
- Single-Machine Simulation of Federated Learning Systems ([PyTorch](https://github.com/adap/flower/tree/main/examples/simulation-pytorch)) ([Tensorflow](https://github.com/adap/flower/tree/main/examples/simulation-tensorflow))
|
|
202
202
|
- [Comprehensive Flower+XGBoost](https://github.com/adap/flower/tree/main/examples/xgboost-comprehensive)
|
|
203
203
|
- [Flower through Docker Compose and with Grafana dashboard](https://github.com/adap/flower/tree/main/examples/flower-via-docker-compose)
|
|
204
|
-
- [Flower with KaplanMeierFitter from the lifelines library](https://github.com/adap/flower/tree/main/examples/federated-
|
|
204
|
+
- [Flower with KaplanMeierFitter from the lifelines library](https://github.com/adap/flower/tree/main/examples/federated-kaplan-meier-fitter)
|
|
205
205
|
- [Sample Level Privacy with Opacus](https://github.com/adap/flower/tree/main/examples/opacus)
|
|
206
|
+
- [Sample Level Privacy with TensorFlow-Privacy](https://github.com/adap/flower/tree/main/examples/tensorflow-privacy)
|
|
206
207
|
|
|
207
208
|
## Community
|
|
208
209
|
|
{flwr_nightly-1.9.0.dev20240520.dist-info → flwr_nightly-1.9.0.dev20240531.dist-info}/RECORD
RENAMED
|
@@ -1,8 +1,8 @@
|
|
|
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=IFu7V_xdexF1T9sUsvgYWVFW5wEPHHcBWHJRPdlX38U,1141
|
|
4
|
-
flwr/cli/build.py,sha256=
|
|
5
|
-
flwr/cli/config_utils.py,sha256=
|
|
4
|
+
flwr/cli/build.py,sha256=PDuqoyIWo5QdMCRSBg6nFFAF4N1Hf4bFxcR8CK6UhME,5064
|
|
5
|
+
flwr/cli/config_utils.py,sha256=kFGSOqYnYbBZPqkfP-OyUtItzvvOiLSSVAIBzQl1i54,4277
|
|
6
6
|
flwr/cli/example.py,sha256=1bGDYll3BXQY2kRqSN-oICqS5n1b9m0g0RvXTopXHl4,2215
|
|
7
7
|
flwr/cli/new/__init__.py,sha256=cQzK1WH4JP2awef1t2UQ2xjl1agVEz9rwutV18SWV1k,789
|
|
8
8
|
flwr/cli/new/new.py,sha256=7BWziuEOE15MXX4xNLH-w0-x0ytOEfYn_AUrbaDp13Y,6223
|
|
@@ -31,15 +31,15 @@ flwr/cli/new/templates/app/code/task.jax.py.tpl,sha256=u4o3V019EH79szOw2xzVeC5r9
|
|
|
31
31
|
flwr/cli/new/templates/app/code/task.mlx.py.tpl,sha256=y7aVj3F_98-wBnDcbPsCNnFs9BOHTn0y6XIYkByzv7Y,2598
|
|
32
32
|
flwr/cli/new/templates/app/code/task.pytorch.py.tpl,sha256=NvajdZN-eTyfdqKK0v2MrvWITXw9BjJ3Ri5c1haPJDs,3684
|
|
33
33
|
flwr/cli/new/templates/app/code/task.tensorflow.py.tpl,sha256=cPOUUS07QbblT9PGFucwu9lY1clRA4-W4DQGA7cpcao,1044
|
|
34
|
-
flwr/cli/new/templates/app/pyproject.hf.toml.tpl,sha256=
|
|
34
|
+
flwr/cli/new/templates/app/pyproject.hf.toml.tpl,sha256=zQz3sUmd-Tpa8QF0UwrAKT13CTxfH-qJ1DtkZN6oKuA,759
|
|
35
35
|
flwr/cli/new/templates/app/pyproject.jax.toml.tpl,sha256=o34H5MvQeu4H2nRolbIas9G63mR7nDDL4rqQMlJW6LA,568
|
|
36
|
-
flwr/cli/new/templates/app/pyproject.mlx.toml.tpl,sha256=
|
|
37
|
-
flwr/cli/new/templates/app/pyproject.numpy.toml.tpl,sha256=
|
|
38
|
-
flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl,sha256=
|
|
39
|
-
flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl,sha256=
|
|
40
|
-
flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl,sha256=
|
|
36
|
+
flwr/cli/new/templates/app/pyproject.mlx.toml.tpl,sha256=oUtYknyM00vGZSBoxmdJqCeOZahg9mtAQfupgaQPhlI,668
|
|
37
|
+
flwr/cli/new/templates/app/pyproject.numpy.toml.tpl,sha256=1cBwKx5eYW6vEcU5gyuVbt2FEFT5fbQ7Lr6t5LGNUuc,606
|
|
38
|
+
flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl,sha256=yBlpz7WF8m8bLBYsPdD5J93cn6xv8vLHbLNxP1a1Iwg,675
|
|
39
|
+
flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl,sha256=T0MxHVnW1fnMDdz1EqN1JK6jIc3J0ZmznBQwK1eGvkg,655
|
|
40
|
+
flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl,sha256=yp4w6LZn2v67AvYRRO9mSm7yfKi14zFYQBl5SbIjEe0,654
|
|
41
41
|
flwr/cli/run/__init__.py,sha256=oCd6HmQDx-sqver1gecgx-uMA38BLTSiiKpl7RGNceg,789
|
|
42
|
-
flwr/cli/run/run.py,sha256=
|
|
42
|
+
flwr/cli/run/run.py,sha256=Oadt7JsJX549JG-2P1UPdF11vnblLWS8uGvuVx0modA,2687
|
|
43
43
|
flwr/cli/utils.py,sha256=px-M-IlBLu6Ez-Sc9tWhsJRjWurRaZTmxB9ASz8wurk,4119
|
|
44
44
|
flwr/client/__init__.py,sha256=CivBxRFjK6gXHN5kUUf9UaZQHa_NHlEM867WWB-H0D8,1268
|
|
45
45
|
flwr/client/app.py,sha256=rzfaHiXxrtjwyhHrHb3epRD6NNw07YzL5DoZO6eW7RA,22313
|
|
@@ -50,7 +50,7 @@ flwr/client/grpc_client/__init__.py,sha256=LsnbqXiJhgQcB0XzAlUQgPx011Uf7Y7yabIC1
|
|
|
50
50
|
flwr/client/grpc_client/connection.py,sha256=KWbBwuvn1-2wjrAKteydGCZC_7A2zmEjk3DycQWafrA,8993
|
|
51
51
|
flwr/client/grpc_rere_client/__init__.py,sha256=avn6W_vHEM_yZEB1S7hCZgnTbXb6ZujqRP_vAzyXu-0,752
|
|
52
52
|
flwr/client/grpc_rere_client/client_interceptor.py,sha256=rDBXRVo-d-rflxJ6Kw3eDfBmvChdUHkzRw5eP-bpe6Y,4903
|
|
53
|
-
flwr/client/grpc_rere_client/connection.py,sha256=
|
|
53
|
+
flwr/client/grpc_rere_client/connection.py,sha256=e-e6CQmubzmB4n36MtS_u7_T1daNKl5ZltfLATllM-U,9716
|
|
54
54
|
flwr/client/heartbeat.py,sha256=cx37mJBH8LyoIN4Lks85wtqT1mnU5GulQnr4pGCvAq0,2404
|
|
55
55
|
flwr/client/message_handler/__init__.py,sha256=abHvBRJJiiaAMNgeILQbMOa6h8WqMK2BcnvxwQZFpic,719
|
|
56
56
|
flwr/client/message_handler/message_handler.py,sha256=ml_FlduAJ5pxO31n1tKRrWfQRSxkMgKLbwXXcRsNSos,6553
|
|
@@ -69,7 +69,7 @@ flwr/client/numpy_client.py,sha256=u76GWAdHmJM88Agm2EgLQSvO8Jnk225mJTk-_TmPjFE,1
|
|
|
69
69
|
flwr/client/rest_client/__init__.py,sha256=ThwOnkMdzxo_UuyTI47Q7y9oSpuTgNT2OuFvJCfuDiw,735
|
|
70
70
|
flwr/client/rest_client/connection.py,sha256=MspqM5RjrQe09_2BUEEVGstA5x9Qz_RWdXXraOic3i8,11520
|
|
71
71
|
flwr/client/supernode/__init__.py,sha256=SUhWOzcgXRNXk1V9UgB5-FaWukqqrOEajVUHEcPkwyQ,865
|
|
72
|
-
flwr/client/supernode/app.py,sha256=
|
|
72
|
+
flwr/client/supernode/app.py,sha256=7OGgDnDN67Y_EKrIBUWRCMVjwVr4nl_sdUPUaQd_tOM,9915
|
|
73
73
|
flwr/client/typing.py,sha256=c9EvjlEjasxn1Wqx6bGl6Xg6vM1gMFfmXht-E2i5J-k,1006
|
|
74
74
|
flwr/common/__init__.py,sha256=dHOptgKxna78CEQLD5Yu0QIsoSgpIIw5AhIUZCHDWAU,3721
|
|
75
75
|
flwr/common/address.py,sha256=iTAN9jtmIGMrWFnx9XZQl45ZEtQJVZZLYPRBSNVARGI,1882
|
|
@@ -93,12 +93,12 @@ flwr/common/record/metricsrecord.py,sha256=Yv99oRa3LzFgSfwl903S8sB8rAgr3Sv6i6ovW
|
|
|
93
93
|
flwr/common/record/parametersrecord.py,sha256=2sgjxsolFBUfnYYstIciOir0HAs95lqWY3pdcsYvsso,4838
|
|
94
94
|
flwr/common/record/recordset.py,sha256=wOonAziLalABXzCHF5ih-QzXsKXZAKCls3HhMFJCWkY,5056
|
|
95
95
|
flwr/common/record/typeddict.py,sha256=2NW8JF27p1uNWaqDbJ7bMkItA5x4ygYT8aHrf8NaqnE,3879
|
|
96
|
-
flwr/common/recordset_compat.py,sha256=
|
|
96
|
+
flwr/common/recordset_compat.py,sha256=SYuJJmsfsWUP60nXREviyl78nOojKfMekALjzeZLsP8,14009
|
|
97
97
|
flwr/common/retry_invoker.py,sha256=dQY5fPIKhy9OiFswZhLxA9fB455u-DYCvDVcFJmrPDk,11707
|
|
98
98
|
flwr/common/secure_aggregation/__init__.py,sha256=29nHIUO2L8-KhNHQ2KmIgRo_4CPkq4LgLCUN0on5FgI,731
|
|
99
99
|
flwr/common/secure_aggregation/crypto/__init__.py,sha256=dz7pVx2aPrHxr_AwgO5mIiTzu4PcvUxRq9NLBbFcsf8,738
|
|
100
100
|
flwr/common/secure_aggregation/crypto/shamir.py,sha256=yY35ZgHlB4YyGW_buG-1X-0M-ejXuQzISgYLgC_Z9TY,2792
|
|
101
|
-
flwr/common/secure_aggregation/crypto/symmetric_encryption.py,sha256=
|
|
101
|
+
flwr/common/secure_aggregation/crypto/symmetric_encryption.py,sha256=BP2qzZLz1FAdJt-E_7D--4yfyXsmfNqtvQG1XJfS4J0,4173
|
|
102
102
|
flwr/common/secure_aggregation/ndarrays_arithmetic.py,sha256=66mNQCz64r7qzvXwFrXP6zz7YMi8EkTOABN7KulkKc4,3026
|
|
103
103
|
flwr/common/secure_aggregation/quantization.py,sha256=appui7GGrkRPsupF59TkapeV4Na_CyPi73JtJ1pimdI,2310
|
|
104
104
|
flwr/common/secure_aggregation/secaggplus_constants.py,sha256=Fh7-n6pgL4TUnHpNYXo8iW-n5cOGQgQa-c7RcU80tqQ,2183
|
|
@@ -120,6 +120,10 @@ flwr/proto/fleet_pb2.py,sha256=mKxwomayARq2nQanY3CtnA8ZWqW8NChWsoWeAyGJAOE,5018
|
|
|
120
120
|
flwr/proto/fleet_pb2.pyi,sha256=yibeuFHsm0ygnJmuyI2V9XNvEt9CLT3dTNUOvAItezU,9161
|
|
121
121
|
flwr/proto/fleet_pb2_grpc.py,sha256=TNt0ydDAXoe1YF2Pl6aNxlGueeOjn0dQ7fdgGx9wd7U,10605
|
|
122
122
|
flwr/proto/fleet_pb2_grpc.pyi,sha256=6flR6x4gB0CZc4YK_MTGeA4zV8Tk_mCcXuxosfDsxGM,2811
|
|
123
|
+
flwr/proto/grpcadapter_pb2.py,sha256=bb8mW09XzNCpMdr1KuYQkefPFWR8lc8y1uL6Uk0TtsM,1843
|
|
124
|
+
flwr/proto/grpcadapter_pb2.pyi,sha256=AR77gDsF6f8zqSIQp3877DUd7S8lP95lFak5Ir_WPkw,1716
|
|
125
|
+
flwr/proto/grpcadapter_pb2_grpc.py,sha256=rRNuNES5nBugUZWfeA8oAy8dMHgzqU_PF1srTseo3b8,2634
|
|
126
|
+
flwr/proto/grpcadapter_pb2_grpc.pyi,sha256=AgA9Qo_lnANb9SNuPzbZGAxupau-xcqYawZz6vqf-24,735
|
|
123
127
|
flwr/proto/node_pb2.py,sha256=1zfXEvgGObglIcaVb4SLFmOcHZvA8eHzEtMFM5A6FYY,1081
|
|
124
128
|
flwr/proto/node_pb2.pyi,sha256=aX3BHhgXvJE1rvcRnEE_gB-5GcaFQ0SJ88yTE223bjI,751
|
|
125
129
|
flwr/proto/node_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
|
|
@@ -138,7 +142,7 @@ flwr/proto/transport_pb2_grpc.py,sha256=vLN3EHtx2aEEMCO4f1Upu-l27BPzd3-5pV-u8wPc
|
|
|
138
142
|
flwr/proto/transport_pb2_grpc.pyi,sha256=AGXf8RiIiW2J5IKMlm_3qT3AzcDa4F3P5IqUjve_esA,766
|
|
139
143
|
flwr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
140
144
|
flwr/server/__init__.py,sha256=PWyHKu-_KFxGI7oFWSWwqMfTiG_phWECT80iv0saouA,1716
|
|
141
|
-
flwr/server/app.py,sha256=
|
|
145
|
+
flwr/server/app.py,sha256=aCKA2uYWzkDXrVyeCFvCG_0wGS28NeFXNf1TzOhoJ98,28034
|
|
142
146
|
flwr/server/client_manager.py,sha256=T8UDSRJBVD3fyIDI7NTAA-NA7GPrMNNgH2OAF54RRxE,6127
|
|
143
147
|
flwr/server/client_proxy.py,sha256=4G-oTwhb45sfWLx2uZdcXD98IZwdTS6F88xe3akCdUg,2399
|
|
144
148
|
flwr/server/compat/__init__.py,sha256=VxnJtJyOjNFQXMNi9hIuzNlZM5n0Hj1p3aq_Pm2udw4,892
|
|
@@ -225,8 +229,8 @@ flwr/simulation/ray_transport/ray_actor.py,sha256=_wv2eP7qxkCZ-6rMyYWnjLrGPBZRxj
|
|
|
225
229
|
flwr/simulation/ray_transport/ray_client_proxy.py,sha256=oDu4sEPIOu39vrNi-fqDAe10xtNUXMO49bM2RWfRcyw,6738
|
|
226
230
|
flwr/simulation/ray_transport/utils.py,sha256=TYdtfg1P9VfTdLMOJlifInGpxWHYs9UfUqIv2wfkRLA,2392
|
|
227
231
|
flwr/simulation/run_simulation.py,sha256=Jmc6DyN5UCY1U1PcDvL04NgYmEQ6ufJ1JisjG5yqfY8,15098
|
|
228
|
-
flwr_nightly-1.9.0.
|
|
229
|
-
flwr_nightly-1.9.0.
|
|
230
|
-
flwr_nightly-1.9.0.
|
|
231
|
-
flwr_nightly-1.9.0.
|
|
232
|
-
flwr_nightly-1.9.0.
|
|
232
|
+
flwr_nightly-1.9.0.dev20240531.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
233
|
+
flwr_nightly-1.9.0.dev20240531.dist-info/METADATA,sha256=jU0PDHU16QVEImyVQDTFfKY-zdVYDL7i83YKe0cMeHQ,15517
|
|
234
|
+
flwr_nightly-1.9.0.dev20240531.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
235
|
+
flwr_nightly-1.9.0.dev20240531.dist-info/entry_points.txt,sha256=8JJPfpqMnXz9c5V_FSt07Xwd-wCWbAO3MFUDXQ5ZGsI,378
|
|
236
|
+
flwr_nightly-1.9.0.dev20240531.dist-info/RECORD,,
|
{flwr_nightly-1.9.0.dev20240520.dist-info → flwr_nightly-1.9.0.dev20240531.dist-info}/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|