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 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 load_and_validate_with_defaults
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 = load_and_validate_with_defaults(
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 load_and_validate_with_defaults(
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
@@ -29,3 +29,9 @@ publisher = "$username"
29
29
  [flower.components]
30
30
  serverapp = "$import_name.server:app"
31
31
  clientapp = "$import_name.client:app"
32
+
33
+ [flower.engine]
34
+ name = "simulation"
35
+
36
+ [flower.engine.simulation.supernode]
37
+ num = 2
@@ -26,3 +26,9 @@ publisher = "$username"
26
26
  [flower.components]
27
27
  serverapp = "$import_name.server:app"
28
28
  clientapp = "$import_name.client:app"
29
+
30
+ [flower.engine]
31
+ name = "simulation"
32
+
33
+ [flower.engine.simulation.supernode]
34
+ num = 2
@@ -24,3 +24,9 @@ publisher = "$username"
24
24
  [flower.components]
25
25
  serverapp = "$import_name.server:app"
26
26
  clientapp = "$import_name.client:app"
27
+
28
+ [flower.engine]
29
+ name = "simulation"
30
+
31
+ [flower.engine.simulation.supernode]
32
+ num = 2
@@ -26,3 +26,9 @@ publisher = "$username"
26
26
  [flower.components]
27
27
  serverapp = "$import_name.server:app"
28
28
  clientapp = "$import_name.client:app"
29
+
30
+ [flower.engine]
31
+ name = "simulation"
32
+
33
+ [flower.engine.simulation.supernode]
34
+ num = 2
@@ -25,3 +25,9 @@ publisher = "$username"
25
25
  [flower.components]
26
26
  serverapp = "$import_name.server:app"
27
27
  clientapp = "$import_name.client:app"
28
+
29
+ [flower.engine]
30
+ name = "simulation"
31
+
32
+ [flower.engine.simulation.supernode]
33
+ num = 2
@@ -25,3 +25,9 @@ publisher = "$username"
25
25
  [flower.components]
26
26
  serverapp = "$import_name.server:app"
27
27
  clientapp = "$import_name.client:app"
28
+
29
+ [flower.engine]
30
+ name = "simulation"
31
+
32
+ [flower.engine.simulation.supernode]
33
+ num = 2
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
- def run() -> None:
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.load_and_validate_with_defaults()
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 == "simulation":
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
- stub = FleetStub(channel)
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
@@ -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
- "--authentication-keys",
246
- nargs=2,
247
- metavar=("CLIENT_PRIVATE_KEY", "CLIENT_PUBLIC_KEY"),
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="Provide two file paths: (1) the client's private "
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.authentication_keys:
257
+ if not args.auth_supernode_private_key and not args.auth_supernode_public_key:
258
258
  return None
259
259
 
260
- ssh_private_key = load_ssh_private_key(
261
- Path(args.authentication_keys[0]).read_bytes(),
262
- None,
263
- )
264
- ssh_public_key = load_ssh_public_key(Path(args.authentication_keys[1]).read_bytes())
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
- client_private_key, client_public_key = ssh_types_to_elliptic_curve(
268
- ssh_private_key, ssh_public_key
283
+ ssh_public_key = load_ssh_public_key(
284
+ Path(args.auth_supernode_public_key).read_bytes()
269
285
  )
270
- except TypeError:
286
+ if not isinstance(ssh_public_key, ec.EllipticCurvePublicKey):
287
+ raise ValueError()
288
+ except (ValueError, UnsupportedAlgorithm):
271
289
  sys.exit(
272
- "The file paths provided could not be read as a private and public "
273
- "key pair. Client authentication requires an elliptic curve public and "
274
- "private key pair. Please provide the file paths containing elliptic "
275
- "curve private and public keys to '--authentication-keys'."
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
- client_private_key,
280
- client_public_key,
297
+ ssh_private_key,
298
+ ssh_public_key,
281
299
  )
@@ -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
- parameters.tensors.append(record[key].data)
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 ERROR, INFO, WARN
22
+ from logging import INFO, WARN
24
23
  from os.path import isfile
25
24
  from pathlib import Path
26
- from typing import List, Optional, Sequence, Set, Tuple
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
- args.ssl_keyfile,
361
- args.ssl_certfile,
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 not args.require_client_authentication:
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
- "Client authentication only works over secure connections. "
444
- "Please provide certificate paths using '--certificates' when "
445
- "enabling '--require-client-authentication'."
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.require_client_authentication[0])
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 client public keys CSV file does not exist: "
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 client public keys "
454
- "to '--require-client-authentication'."
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
- server_private_key, server_public_key = ssh_types_to_elliptic_curve(
468
- ssh_private_key, ssh_public_key
464
+ ssh_private_key = load_ssh_private_key(
465
+ Path(args.auth_superlink_private_key).read_bytes(),
466
+ None,
469
467
  )
470
- except TypeError:
468
+ if not isinstance(ssh_private_key, ec.EllipticCurvePrivateKey):
469
+ raise ValueError()
470
+ except (ValueError, UnsupportedAlgorithm):
471
471
  sys.exit(
472
- "The file paths provided could not be read as a private and public "
473
- "key pair. Client authentication requires an elliptic curve public and "
474
- "private key pair. Please provide the file paths containing elliptic "
475
- "curve private and public keys to '--require-client-authentication'."
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 .csv "
488
- "file. Please ensure that the .csv file contains valid "
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
- server_private_key,
494
- server_public_key,
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
- certificates = None
518
+ return None
505
519
  # Check if certificates are provided
506
- elif args.certificates:
507
- certificates = (
508
- Path(args.certificates[0]).read_bytes(), # CA certificate
509
- Path(args.certificates[1]).read_bytes(), # server certificate
510
- Path(args.certificates[2]).read_bytes(), # server private key
511
- )
512
- else:
513
- sys.exit(
514
- "Certificates are required unless running in insecure mode. "
515
- "Please provide certificate paths with '--certificates' or run the server "
516
- "in insecure mode using '--insecure' if you understand the risks."
517
- )
518
- return certificates
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
- "--certificates",
700
- nargs=3,
701
- metavar=("CA_CERT", "SERVER_CERT", "PRIVATE_KEY"),
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
- "--require-client-authentication",
718
- nargs=3,
719
- metavar=("CLIENT_KEYS", "SERVER_PRIVATE_KEY", "SERVER_PUBLIC_KEY"),
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="Provide three file paths: (1) a .csv file containing a list of "
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
- )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: flwr-nightly
3
- Version: 1.9.0.dev20240520
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.6.3) ; (python_version >= "3.8" and python_version < "3.12") and (extra == "simulation")
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-kaplna-meier-fitter)
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
 
@@ -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=W30wnPSgFuHRnGB9G_vKO14rsaibWk7m-jv9r8rDqo4,5106
5
- flwr/cli/config_utils.py,sha256=Hql5A5hbSpJ51hgpwaTkKqfPoaZN4Zq7FZfBuQYLMcQ,4899
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=PNGBNTfWmNJ23aVnW5f1TMMJ0uEwIljevpOsI-mqX08,676
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=JCEsuHZffO1KKkN65rSp6N-A9-OW8-kl6EQp5Z2H3uE,585
37
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl,sha256=m276SKsjOZ4awGdXasUKvLim66agrpAsPNP9-PN6q4I,523
38
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl,sha256=QikP3u5ht6qr2BkgcnvB3rCYK7jt1cS0nAm7V8g_zFc,592
39
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl,sha256=IO5iIlyKSBxZCCf48iqEyRWeG1jmVx2tO_s2iE7FpHo,572
40
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl,sha256=CpjCOHyJ3zdIlkXQ1An6fEKN9l7rIStx43g4SsIwbkw,571
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=jr_J7Cbcyuj1MXIbuwU86htHdFI7XogsBrdGl7P4aYY,2334
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=gSSJJ9pSe5SgUb1Ey-xcrVK6xArUkwq0yGdav0h2kww,9597
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=6OWaD_4ZfguN6sukDfhbZzCgDmsSqHPlAmtcyU6TfJQ,9052
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=BjxeuvlCaP94yIiKOyFFTRBUH_lprFWSLo8U8q3BDbs,13798
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=zlACMLahJEIqhcss0-1xz_iCXUGTlL2G-i9hi8spu-8,4707
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=95U1IO07ngLy2lkDTvsoI5XesXBN7mpcT1wNhPSgXTI,28913
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.dev20240520.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
229
- flwr_nightly-1.9.0.dev20240520.dist-info/METADATA,sha256=Muyx-nLQoSJM8cQNSoc_jOWiUu9imxgh7qt6OucH9pM,15397
230
- flwr_nightly-1.9.0.dev20240520.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
231
- flwr_nightly-1.9.0.dev20240520.dist-info/entry_points.txt,sha256=8JJPfpqMnXz9c5V_FSt07Xwd-wCWbAO3MFUDXQ5ZGsI,378
232
- flwr_nightly-1.9.0.dev20240520.dist-info/RECORD,,
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,,