flwr-nightly 1.10.0.dev20240612__py3-none-any.whl → 1.10.0.dev20240624__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of flwr-nightly might be problematic. Click here for more details.

Files changed (130) hide show
  1. flwr/cli/app.py +3 -0
  2. flwr/cli/build.py +6 -8
  3. flwr/cli/config_utils.py +53 -3
  4. flwr/cli/install.py +35 -20
  5. flwr/cli/new/new.py +104 -28
  6. flwr/cli/new/templates/app/README.flowertune.md.tpl +56 -0
  7. flwr/cli/new/templates/app/code/flwr_tune/__init__.py +15 -0
  8. flwr/cli/new/templates/app/code/flwr_tune/app.py.tpl +86 -0
  9. flwr/cli/new/templates/app/code/flwr_tune/client.py.tpl +124 -0
  10. flwr/cli/new/templates/app/code/flwr_tune/config.yaml.tpl +34 -0
  11. flwr/cli/new/templates/app/code/flwr_tune/dataset.py.tpl +57 -0
  12. flwr/cli/new/templates/app/code/flwr_tune/models.py.tpl +59 -0
  13. flwr/cli/new/templates/app/code/flwr_tune/server.py.tpl +48 -0
  14. flwr/cli/new/templates/app/code/flwr_tune/static_config.yaml.tpl +11 -0
  15. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +42 -0
  16. flwr/cli/run/run.py +46 -2
  17. flwr/client/__init__.py +1 -1
  18. flwr/client/app.py +22 -10
  19. flwr/client/client_app.py +1 -1
  20. flwr/client/dpfedavg_numpy_client.py +1 -1
  21. flwr/client/grpc_adapter_client/__init__.py +15 -0
  22. flwr/client/grpc_adapter_client/connection.py +94 -0
  23. flwr/client/grpc_client/connection.py +5 -1
  24. flwr/client/grpc_rere_client/__init__.py +1 -1
  25. flwr/client/grpc_rere_client/connection.py +9 -2
  26. flwr/client/grpc_rere_client/grpc_adapter.py +133 -0
  27. flwr/client/message_handler/__init__.py +1 -1
  28. flwr/client/message_handler/message_handler.py +1 -1
  29. flwr/client/mod/__init__.py +4 -4
  30. flwr/client/mod/secure_aggregation/__init__.py +1 -1
  31. flwr/client/mod/utils.py +1 -1
  32. flwr/client/rest_client/__init__.py +1 -1
  33. flwr/client/rest_client/connection.py +10 -2
  34. flwr/client/supernode/app.py +141 -41
  35. flwr/common/__init__.py +12 -12
  36. flwr/common/address.py +1 -1
  37. flwr/common/config.py +73 -0
  38. flwr/common/constant.py +16 -1
  39. flwr/common/date.py +1 -1
  40. flwr/common/dp.py +1 -1
  41. flwr/common/grpc.py +1 -1
  42. flwr/common/object_ref.py +39 -5
  43. flwr/common/record/__init__.py +1 -1
  44. flwr/common/secure_aggregation/__init__.py +1 -1
  45. flwr/common/secure_aggregation/crypto/__init__.py +1 -1
  46. flwr/common/secure_aggregation/crypto/shamir.py +1 -1
  47. flwr/common/secure_aggregation/crypto/symmetric_encryption.py +1 -1
  48. flwr/common/secure_aggregation/ndarrays_arithmetic.py +1 -1
  49. flwr/common/secure_aggregation/quantization.py +1 -1
  50. flwr/common/secure_aggregation/secaggplus_constants.py +1 -1
  51. flwr/common/secure_aggregation/secaggplus_utils.py +1 -1
  52. flwr/common/telemetry.py +4 -0
  53. flwr/common/typing.py +9 -0
  54. flwr/common/version.py +14 -0
  55. flwr/proto/exec_pb2.py +34 -0
  56. flwr/proto/exec_pb2.pyi +55 -0
  57. flwr/proto/exec_pb2_grpc.py +101 -0
  58. flwr/proto/exec_pb2_grpc.pyi +41 -0
  59. flwr/proto/fab_pb2.py +30 -0
  60. flwr/proto/fab_pb2.pyi +56 -0
  61. flwr/proto/fab_pb2_grpc.py +4 -0
  62. flwr/proto/fab_pb2_grpc.pyi +4 -0
  63. flwr/server/__init__.py +2 -2
  64. flwr/server/app.py +62 -25
  65. flwr/server/compat/app.py +1 -1
  66. flwr/server/compat/app_utils.py +1 -1
  67. flwr/server/compat/driver_client_proxy.py +1 -1
  68. flwr/server/driver/driver.py +6 -0
  69. flwr/server/driver/grpc_driver.py +85 -63
  70. flwr/server/driver/inmemory_driver.py +28 -26
  71. flwr/server/run_serverapp.py +65 -20
  72. flwr/server/strategy/__init__.py +2 -2
  73. flwr/server/strategy/bulyan.py +1 -1
  74. flwr/server/strategy/dpfedavg_adaptive.py +1 -1
  75. flwr/server/strategy/dpfedavg_fixed.py +1 -1
  76. flwr/server/strategy/fedadagrad.py +1 -1
  77. flwr/server/strategy/fedadam.py +1 -1
  78. flwr/server/strategy/fedavg_android.py +1 -1
  79. flwr/server/strategy/fedavgm.py +1 -1
  80. flwr/server/strategy/fedmedian.py +1 -1
  81. flwr/server/strategy/fedopt.py +1 -1
  82. flwr/server/strategy/fedprox.py +1 -1
  83. flwr/server/strategy/fedxgb_bagging.py +1 -1
  84. flwr/server/strategy/fedxgb_cyclic.py +1 -1
  85. flwr/server/strategy/fedxgb_nn_avg.py +1 -1
  86. flwr/server/strategy/fedyogi.py +1 -1
  87. flwr/server/strategy/krum.py +1 -1
  88. flwr/server/strategy/qfedavg.py +1 -1
  89. flwr/server/superlink/driver/__init__.py +1 -1
  90. flwr/server/superlink/driver/driver_grpc.py +1 -1
  91. flwr/server/superlink/driver/driver_servicer.py +15 -3
  92. flwr/server/superlink/fleet/__init__.py +1 -1
  93. flwr/server/superlink/fleet/grpc_adapter/__init__.py +15 -0
  94. flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +131 -0
  95. flwr/server/superlink/fleet/grpc_bidi/__init__.py +1 -1
  96. flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -1
  97. flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py +1 -1
  98. flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py +1 -1
  99. flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +5 -1
  100. flwr/server/superlink/fleet/grpc_rere/__init__.py +1 -1
  101. flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +1 -1
  102. flwr/server/superlink/fleet/message_handler/__init__.py +1 -1
  103. flwr/server/superlink/fleet/message_handler/message_handler.py +4 -4
  104. flwr/server/superlink/fleet/rest_rere/__init__.py +1 -1
  105. flwr/server/superlink/fleet/rest_rere/rest_api.py +1 -1
  106. flwr/server/superlink/fleet/vce/backend/raybackend.py +44 -25
  107. flwr/server/superlink/fleet/vce/vce_api.py +3 -1
  108. flwr/server/superlink/state/__init__.py +1 -1
  109. flwr/server/superlink/state/in_memory_state.py +9 -6
  110. flwr/server/superlink/state/sqlite_state.py +7 -4
  111. flwr/server/superlink/state/state.py +6 -5
  112. flwr/server/superlink/state/state_factory.py +11 -2
  113. flwr/server/utils/__init__.py +1 -1
  114. flwr/server/utils/tensorboard.py +1 -1
  115. flwr/simulation/__init__.py +5 -2
  116. flwr/simulation/app.py +1 -1
  117. flwr/simulation/ray_transport/__init__.py +1 -1
  118. flwr/simulation/ray_transport/ray_actor.py +0 -6
  119. flwr/simulation/ray_transport/ray_client_proxy.py +1 -1
  120. flwr/simulation/run_simulation.py +63 -22
  121. flwr/superexec/__init__.py +21 -0
  122. flwr/superexec/app.py +178 -0
  123. flwr/superexec/exec_grpc.py +51 -0
  124. flwr/superexec/exec_servicer.py +65 -0
  125. flwr/superexec/executor.py +54 -0
  126. {flwr_nightly-1.10.0.dev20240612.dist-info → flwr_nightly-1.10.0.dev20240624.dist-info}/METADATA +2 -1
  127. {flwr_nightly-1.10.0.dev20240612.dist-info → flwr_nightly-1.10.0.dev20240624.dist-info}/RECORD +130 -101
  128. {flwr_nightly-1.10.0.dev20240612.dist-info → flwr_nightly-1.10.0.dev20240624.dist-info}/entry_points.txt +1 -0
  129. {flwr_nightly-1.10.0.dev20240612.dist-info → flwr_nightly-1.10.0.dev20240624.dist-info}/LICENSE +0 -0
  130. {flwr_nightly-1.10.0.dev20240612.dist-info → flwr_nightly-1.10.0.dev20240624.dist-info}/WHEEL +0 -0
@@ -0,0 +1,94 @@
1
+ # Copyright 2024 Flower Labs GmbH. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+ """Contextmanager for a GrpcAdapter channel to the Flower server."""
16
+
17
+
18
+ from contextlib import contextmanager
19
+ from logging import ERROR
20
+ from typing import Callable, Iterator, Optional, Tuple, Union
21
+
22
+ from cryptography.hazmat.primitives.asymmetric import ec
23
+
24
+ from flwr.client.grpc_rere_client.connection import grpc_request_response
25
+ from flwr.client.grpc_rere_client.grpc_adapter import GrpcAdapter
26
+ from flwr.common import GRPC_MAX_MESSAGE_LENGTH
27
+ from flwr.common.logger import log
28
+ from flwr.common.message import Message
29
+ from flwr.common.retry_invoker import RetryInvoker
30
+
31
+
32
+ @contextmanager
33
+ def grpc_adapter( # pylint: disable=R0913
34
+ server_address: str,
35
+ insecure: bool,
36
+ retry_invoker: RetryInvoker,
37
+ max_message_length: int = GRPC_MAX_MESSAGE_LENGTH, # pylint: disable=W0613
38
+ root_certificates: Optional[Union[bytes, str]] = None,
39
+ authentication_keys: Optional[ # pylint: disable=unused-argument
40
+ Tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]
41
+ ] = None,
42
+ ) -> Iterator[
43
+ Tuple[
44
+ Callable[[], Optional[Message]],
45
+ Callable[[Message], None],
46
+ Optional[Callable[[], None]],
47
+ Optional[Callable[[], None]],
48
+ Optional[Callable[[int], Tuple[str, str]]],
49
+ ]
50
+ ]:
51
+ """Primitives for request/response-based interaction with a server via GrpcAdapter.
52
+
53
+ Parameters
54
+ ----------
55
+ server_address : str
56
+ The IPv6 address of the server with `http://` or `https://`.
57
+ If the Flower server runs on the same machine
58
+ on port 8080, then `server_address` would be `"http://[::]:8080"`.
59
+ insecure : bool
60
+ Starts an insecure gRPC connection when True. Enables HTTPS connection
61
+ when False, using system certificates if `root_certificates` is None.
62
+ retry_invoker: RetryInvoker
63
+ `RetryInvoker` object that will try to reconnect the client to the server
64
+ after gRPC errors. If None, the client will only try to
65
+ reconnect once after a failure.
66
+ max_message_length : int
67
+ Ignored, only present to preserve API-compatibility.
68
+ root_certificates : Optional[Union[bytes, str]] (default: None)
69
+ Path of the root certificate. If provided, a secure
70
+ connection using the certificates will be established to an SSL-enabled
71
+ Flower server. Bytes won't work for the REST API.
72
+ authentication_keys : Optional[Tuple[PrivateKey, PublicKey]] (default: None)
73
+ Client authentication is not supported for this transport type.
74
+
75
+ Returns
76
+ -------
77
+ receive : Callable
78
+ send : Callable
79
+ create_node : Optional[Callable]
80
+ delete_node : Optional[Callable]
81
+ get_run : Optional[Callable]
82
+ """
83
+ if authentication_keys is not None:
84
+ log(ERROR, "Client authentication is not supported for this transport type.")
85
+ with grpc_request_response(
86
+ server_address=server_address,
87
+ insecure=insecure,
88
+ retry_invoker=retry_invoker,
89
+ max_message_length=max_message_length,
90
+ root_certificates=root_certificates,
91
+ authentication_keys=None, # Authentication is not supported
92
+ adapter_cls=GrpcAdapter,
93
+ ) as conn:
94
+ yield conn
@@ -17,7 +17,7 @@
17
17
 
18
18
  import uuid
19
19
  from contextlib import contextmanager
20
- from logging import DEBUG
20
+ from logging import DEBUG, ERROR
21
21
  from pathlib import Path
22
22
  from queue import Queue
23
23
  from typing import Callable, Iterator, Optional, Tuple, Union, cast
@@ -101,6 +101,8 @@ def grpc_connection( # pylint: disable=R0913, R0915
101
101
  The PEM-encoded root certificates as a byte string or a path string.
102
102
  If provided, a secure connection using the certificates will be
103
103
  established to an SSL-enabled Flower server.
104
+ authentication_keys : Optional[Tuple[PrivateKey, PublicKey]] (default: None)
105
+ Client authentication is not supported for this transport type.
104
106
 
105
107
  Returns
106
108
  -------
@@ -123,6 +125,8 @@ def grpc_connection( # pylint: disable=R0913, R0915
123
125
  """
124
126
  if isinstance(root_certificates, str):
125
127
  root_certificates = Path(root_certificates).read_bytes()
128
+ if authentication_keys is not None:
129
+ log(ERROR, "Client authentication is not supported for this transport type.")
126
130
 
127
131
  channel = create_channel(
128
132
  server_address=server_address,
@@ -1,4 +1,4 @@
1
- # Copyright 2020 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2023 Flower Labs GmbH. All Rights Reserved.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright 2020 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2023 Flower Labs GmbH. All Rights Reserved.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -55,6 +55,7 @@ from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=
55
55
  from flwr.proto.task_pb2 import TaskIns # pylint: disable=E0611
56
56
 
57
57
  from .client_interceptor import AuthenticateClientInterceptor
58
+ from .grpc_adapter import GrpcAdapter
58
59
 
59
60
 
60
61
  def on_channel_state_change(channel_connectivity: str) -> None:
@@ -72,7 +73,7 @@ def grpc_request_response( # pylint: disable=R0913, R0914, R0915
72
73
  authentication_keys: Optional[
73
74
  Tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]
74
75
  ] = None,
75
- adapter_cls: Optional[Type[FleetStub]] = None,
76
+ adapter_cls: Optional[Union[Type[FleetStub], Type[GrpcAdapter]]] = None,
76
77
  ) -> Iterator[
77
78
  Tuple[
78
79
  Callable[[], Optional[Message]],
@@ -106,6 +107,11 @@ def grpc_request_response( # pylint: disable=R0913, R0914, R0915
106
107
  Path of the root certificate. If provided, a secure
107
108
  connection using the certificates will be established to an SSL-enabled
108
109
  Flower server. Bytes won't work for the REST API.
110
+ authentication_keys : Optional[Tuple[PrivateKey, PublicKey]] (default: None)
111
+ Tuple containing the elliptic curve private key and public key for
112
+ authentication from the cryptography library.
113
+ Source: https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec/
114
+ Used to establish an authenticated connection with the server.
109
115
 
110
116
  Returns
111
117
  -------
@@ -113,6 +119,7 @@ def grpc_request_response( # pylint: disable=R0913, R0914, R0915
113
119
  send : Callable
114
120
  create_node : Optional[Callable]
115
121
  delete_node : Optional[Callable]
122
+ get_run : Optional[Callable]
116
123
  """
117
124
  if isinstance(root_certificates, str):
118
125
  root_certificates = Path(root_certificates).read_bytes()
@@ -0,0 +1,133 @@
1
+ # Copyright 2024 Flower Labs GmbH. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+ """GrpcAdapter implementation."""
16
+
17
+
18
+ import sys
19
+ from logging import DEBUG
20
+ from typing import Any, Type, TypeVar, cast
21
+
22
+ import grpc
23
+ from google.protobuf.message import Message as GrpcMessage
24
+
25
+ from flwr.common import log
26
+ from flwr.common.constant import (
27
+ GRPC_ADAPTER_METADATA_FLOWER_VERSION_KEY,
28
+ GRPC_ADAPTER_METADATA_SHOULD_EXIT_KEY,
29
+ )
30
+ from flwr.common.version import package_version
31
+ from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
32
+ CreateNodeRequest,
33
+ CreateNodeResponse,
34
+ DeleteNodeRequest,
35
+ DeleteNodeResponse,
36
+ PingRequest,
37
+ PingResponse,
38
+ PullTaskInsRequest,
39
+ PullTaskInsResponse,
40
+ PushTaskResRequest,
41
+ PushTaskResResponse,
42
+ )
43
+ from flwr.proto.grpcadapter_pb2 import MessageContainer # pylint: disable=E0611
44
+ from flwr.proto.grpcadapter_pb2_grpc import GrpcAdapterStub
45
+ from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611
46
+
47
+ T = TypeVar("T", bound=GrpcMessage)
48
+
49
+
50
+ class GrpcAdapter:
51
+ """Adapter class to send and receive gRPC messages via the ``GrpcAdapterStub``.
52
+
53
+ This class utilizes the ``GrpcAdapterStub`` to send and receive gRPC messages
54
+ which are defined and used by the Fleet API, as defined in ``fleet.proto``.
55
+ """
56
+
57
+ def __init__(self, channel: grpc.Channel) -> None:
58
+ self.stub = GrpcAdapterStub(channel)
59
+
60
+ def _send_and_receive(
61
+ self, request: GrpcMessage, response_type: Type[T], **kwargs: Any
62
+ ) -> T:
63
+ # Serialize request
64
+ container_req = MessageContainer(
65
+ metadata={GRPC_ADAPTER_METADATA_FLOWER_VERSION_KEY: package_version},
66
+ grpc_message_name=request.__class__.__qualname__,
67
+ grpc_message_content=request.SerializeToString(),
68
+ )
69
+
70
+ # Send via the stub
71
+ container_res = cast(
72
+ MessageContainer, self.stub.SendReceive(container_req, **kwargs)
73
+ )
74
+
75
+ # Handle control message
76
+ should_exit = (
77
+ container_res.metadata.get(GRPC_ADAPTER_METADATA_SHOULD_EXIT_KEY, "false")
78
+ == "true"
79
+ )
80
+ if should_exit:
81
+ log(
82
+ DEBUG,
83
+ 'Received shutdown signal: exit flag is set to ``"true"``. Exiting...',
84
+ )
85
+ sys.exit(0)
86
+
87
+ # Check the grpc_message_name of the response
88
+ if container_res.grpc_message_name != response_type.__qualname__:
89
+ raise ValueError(
90
+ f"Invalid grpc_message_name. Expected {response_type.__qualname__}"
91
+ f", but got {container_res.grpc_message_name}."
92
+ )
93
+
94
+ # Deserialize response
95
+ response = response_type()
96
+ response.ParseFromString(container_res.grpc_message_content)
97
+ return response
98
+
99
+ def CreateNode( # pylint: disable=C0103
100
+ self, request: CreateNodeRequest, **kwargs: Any
101
+ ) -> CreateNodeResponse:
102
+ """."""
103
+ return self._send_and_receive(request, CreateNodeResponse, **kwargs)
104
+
105
+ def DeleteNode( # pylint: disable=C0103
106
+ self, request: DeleteNodeRequest, **kwargs: Any
107
+ ) -> DeleteNodeResponse:
108
+ """."""
109
+ return self._send_and_receive(request, DeleteNodeResponse, **kwargs)
110
+
111
+ def Ping( # pylint: disable=C0103
112
+ self, request: PingRequest, **kwargs: Any
113
+ ) -> PingResponse:
114
+ """."""
115
+ return self._send_and_receive(request, PingResponse, **kwargs)
116
+
117
+ def PullTaskIns( # pylint: disable=C0103
118
+ self, request: PullTaskInsRequest, **kwargs: Any
119
+ ) -> PullTaskInsResponse:
120
+ """."""
121
+ return self._send_and_receive(request, PullTaskInsResponse, **kwargs)
122
+
123
+ def PushTaskRes( # pylint: disable=C0103
124
+ self, request: PushTaskResRequest, **kwargs: Any
125
+ ) -> PushTaskResResponse:
126
+ """."""
127
+ return self._send_and_receive(request, PushTaskResResponse, **kwargs)
128
+
129
+ def GetRun( # pylint: disable=C0103
130
+ self, request: GetRunRequest, **kwargs: Any
131
+ ) -> GetRunResponse:
132
+ """."""
133
+ return self._send_and_receive(request, GetRunResponse, **kwargs)
@@ -1,4 +1,4 @@
1
- # Copyright 2020 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2022 Flower Labs GmbH. All Rights Reserved.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright 2020 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2022 Flower Labs GmbH. All Rights Reserved.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright 2023 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2024 Flower Labs GmbH. All Rights Reserved.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -22,12 +22,12 @@ from .secure_aggregation import secagg_mod, secaggplus_mod
22
22
  from .utils import make_ffn
23
23
 
24
24
  __all__ = [
25
+ "LocalDpMod",
25
26
  "adaptiveclipping_mod",
26
27
  "fixedclipping_mod",
27
- "LocalDpMod",
28
28
  "make_ffn",
29
- "secagg_mod",
30
- "secaggplus_mod",
31
29
  "message_size_mod",
32
30
  "parameters_size_mod",
31
+ "secagg_mod",
32
+ "secaggplus_mod",
33
33
  ]
@@ -1,4 +1,4 @@
1
- # Copyright 2023 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2024 Flower Labs GmbH. All Rights Reserved.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
flwr/client/mod/utils.py CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2023 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2024 Flower Labs GmbH. All Rights Reserved.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright 2020 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2023 Flower Labs GmbH. All Rights Reserved.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright 2020 Flower Labs GmbH. All Rights Reserved.
1
+ # Copyright 2023 Flower Labs GmbH. All Rights Reserved.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -117,10 +117,16 @@ def http_request_response( # pylint: disable=,R0913, R0914, R0915
117
117
  Path of the root certificate. If provided, a secure
118
118
  connection using the certificates will be established to an SSL-enabled
119
119
  Flower server. Bytes won't work for the REST API.
120
+ authentication_keys : Optional[Tuple[PrivateKey, PublicKey]] (default: None)
121
+ Client authentication is not supported for this transport type.
120
122
 
121
123
  Returns
122
124
  -------
123
- receive, send : Callable, Callable
125
+ receive : Callable
126
+ send : Callable
127
+ create_node : Optional[Callable]
128
+ delete_node : Optional[Callable]
129
+ get_run : Optional[Callable]
124
130
  """
125
131
  log(
126
132
  WARN,
@@ -145,6 +151,8 @@ def http_request_response( # pylint: disable=,R0913, R0914, R0915
145
151
  "For the REST API, the root certificates "
146
152
  "must be provided as a string path to the client.",
147
153
  )
154
+ if authentication_keys is not None:
155
+ log(ERROR, "Client authentication is not supported for this transport type.")
148
156
 
149
157
  # Shared variables for inner functions
150
158
  metadata: Optional[Metadata] = None
@@ -29,6 +29,12 @@ from cryptography.hazmat.primitives.serialization import (
29
29
 
30
30
  from flwr.client.client_app import ClientApp, LoadClientAppError
31
31
  from flwr.common import EventType, event
32
+ from flwr.common.config import get_flwr_dir, get_project_config, get_project_dir
33
+ from flwr.common.constant import (
34
+ TRANSPORT_TYPE_GRPC_ADAPTER,
35
+ TRANSPORT_TYPE_GRPC_RERE,
36
+ TRANSPORT_TYPE_REST,
37
+ )
32
38
  from flwr.common.exit_handlers import register_exit_handlers
33
39
  from flwr.common.logger import log, warn_deprecated_feature
34
40
  from flwr.common.object_ref import load_app, validate
@@ -44,11 +50,23 @@ def run_supernode() -> None:
44
50
 
45
51
  event(EventType.RUN_SUPERNODE_ENTER)
46
52
 
47
- _ = _parse_args_run_supernode().parse_args()
53
+ args = _parse_args_run_supernode().parse_args()
54
+
55
+ _warn_deprecated_server_arg(args)
56
+
57
+ root_certificates = _get_certificates(args)
58
+ load_fn = _get_load_client_app_fn(args, multi_app=True)
59
+ authentication_keys = _try_setup_client_authentication(args)
48
60
 
49
- log(
50
- DEBUG,
51
- "Flower SuperNode starting...",
61
+ _start_client_internal(
62
+ server_address=args.superlink,
63
+ load_client_app_fn=load_fn,
64
+ transport=args.transport,
65
+ root_certificates=root_certificates,
66
+ insecure=args.insecure,
67
+ authentication_keys=authentication_keys,
68
+ max_retries=args.max_retries,
69
+ max_wait_time=args.max_wait_time,
52
70
  )
53
71
 
54
72
  # Graceful shutdown
@@ -65,6 +83,27 @@ def run_client_app() -> None:
65
83
 
66
84
  args = _parse_args_run_client_app().parse_args()
67
85
 
86
+ _warn_deprecated_server_arg(args)
87
+
88
+ root_certificates = _get_certificates(args)
89
+ load_fn = _get_load_client_app_fn(args, multi_app=False)
90
+ authentication_keys = _try_setup_client_authentication(args)
91
+
92
+ _start_client_internal(
93
+ server_address=args.superlink,
94
+ load_client_app_fn=load_fn,
95
+ transport=args.transport,
96
+ root_certificates=root_certificates,
97
+ insecure=args.insecure,
98
+ authentication_keys=authentication_keys,
99
+ max_retries=args.max_retries,
100
+ max_wait_time=args.max_wait_time,
101
+ )
102
+ register_exit_handlers(event_type=EventType.RUN_CLIENT_APP_LEAVE)
103
+
104
+
105
+ def _warn_deprecated_server_arg(args: argparse.Namespace) -> None:
106
+ """Warn about the deprecated argument `--server`."""
68
107
  if args.server != ADDRESS_FLEET_API_GRPC_RERE:
69
108
  warn = "Passing flag --server is deprecated. Use --superlink instead."
70
109
  warn_deprecated_feature(warn)
@@ -82,27 +121,6 @@ def run_client_app() -> None:
82
121
  else:
83
122
  args.superlink = args.server
84
123
 
85
- root_certificates = _get_certificates(args)
86
- log(
87
- DEBUG,
88
- "Flower will load ClientApp `%s`",
89
- getattr(args, "client-app"),
90
- )
91
- load_fn = _get_load_client_app_fn(args)
92
- authentication_keys = _try_setup_client_authentication(args)
93
-
94
- _start_client_internal(
95
- server_address=args.superlink,
96
- load_client_app_fn=load_fn,
97
- transport="rest" if args.rest else "grpc-rere",
98
- root_certificates=root_certificates,
99
- insecure=args.insecure,
100
- authentication_keys=authentication_keys,
101
- max_retries=args.max_retries,
102
- max_wait_time=args.max_wait_time,
103
- )
104
- register_exit_handlers(event_type=EventType.RUN_CLIENT_APP_LEAVE)
105
-
106
124
 
107
125
  def _get_certificates(args: argparse.Namespace) -> Optional[bytes]:
108
126
  """Load certificates if specified in args."""
@@ -140,24 +158,88 @@ def _get_certificates(args: argparse.Namespace) -> Optional[bytes]:
140
158
 
141
159
 
142
160
  def _get_load_client_app_fn(
143
- args: argparse.Namespace,
144
- ) -> Callable[[], ClientApp]:
145
- """Get the load_client_app_fn function."""
146
- client_app_dir = args.dir
147
- if client_app_dir is not None:
148
- sys.path.insert(0, client_app_dir)
161
+ args: argparse.Namespace, multi_app: bool
162
+ ) -> Callable[[str, str], ClientApp]:
163
+ """Get the load_client_app_fn function.
164
+
165
+ If `multi_app` is True, this function loads the specified ClientApp
166
+ based on `fab_id` and `fab_version`. If `fab_id` is empty, a default
167
+ ClientApp will be loaded.
168
+
169
+ If `multi_app` is False, it ignores `fab_id` and `fab_version` and
170
+ loads a default ClientApp.
171
+ """
172
+ # Find the Flower directory containing Flower Apps (only for multi-app)
173
+ flwr_dir = Path("")
174
+ if "flwr_dir" in args:
175
+ if args.flwr_dir is None:
176
+ flwr_dir = get_flwr_dir()
177
+ else:
178
+ flwr_dir = Path(args.flwr_dir).absolute()
149
179
 
150
- app_ref: str = getattr(args, "client-app")
151
- valid, error_msg = validate(app_ref)
152
- if not valid and error_msg:
153
- raise LoadClientAppError(error_msg) from None
180
+ sys.path.insert(0, str(flwr_dir.absolute()))
154
181
 
155
- def _load() -> ClientApp:
156
- client_app = load_app(app_ref, LoadClientAppError)
182
+ default_app_ref: str = getattr(args, "client-app")
183
+
184
+ if not multi_app:
185
+ log(
186
+ DEBUG,
187
+ "Flower SuperNode will load and validate ClientApp `%s`",
188
+ getattr(args, "client-app"),
189
+ )
190
+ valid, error_msg = validate(default_app_ref)
191
+ if not valid and error_msg:
192
+ raise LoadClientAppError(error_msg) from None
193
+
194
+ def _load(fab_id: str, fab_version: str) -> ClientApp:
195
+ # If multi-app feature is disabled
196
+ if not multi_app:
197
+ # Get sys path to be inserted
198
+ sys_path = Path(args.dir).absolute()
199
+
200
+ # Set app reference
201
+ client_app_ref = default_app_ref
202
+ # If multi-app feature is enabled but the fab id is not specified
203
+ elif fab_id == "":
204
+ if default_app_ref == "":
205
+ raise LoadClientAppError(
206
+ "Invalid FAB ID: The FAB ID is empty.",
207
+ ) from None
208
+
209
+ log(WARN, "FAB ID is not provided; the default ClientApp will be loaded.")
210
+ # Get sys path to be inserted
211
+ sys_path = Path(args.dir).absolute()
212
+
213
+ # Set app reference
214
+ client_app_ref = default_app_ref
215
+ # If multi-app feature is enabled
216
+ else:
217
+ try:
218
+ project_dir = get_project_dir(fab_id, fab_version, flwr_dir)
219
+ config = get_project_config(project_dir)
220
+ except Exception as e:
221
+ raise LoadClientAppError("Failed to load ClientApp") from e
222
+
223
+ # Get sys path to be inserted
224
+ sys_path = Path(project_dir).absolute()
225
+
226
+ # Set app reference
227
+ client_app_ref = config["flower"]["components"]["clientapp"]
228
+
229
+ # Set sys.path
230
+ sys.path.insert(0, str(sys_path))
231
+
232
+ # Load ClientApp
233
+ log(
234
+ DEBUG,
235
+ "Loading ClientApp `%s`",
236
+ client_app_ref,
237
+ )
238
+ client_app = load_app(client_app_ref, LoadClientAppError, sys_path)
157
239
 
158
240
  if not isinstance(client_app, ClientApp):
159
241
  raise LoadClientAppError(
160
- f"Attribute {app_ref} is not of type {ClientApp}",
242
+ f"Attribute {client_app_ref} is not of type {ClientApp}",
161
243
  ) from None
162
244
 
163
245
  return client_app
@@ -185,7 +267,7 @@ def _parse_args_run_supernode() -> argparse.ArgumentParser:
185
267
  "--flwr-dir",
186
268
  default=None,
187
269
  help="""The path containing installed Flower Apps.
188
- By default, this value isequal to:
270
+ By default, this value is equal to:
189
271
 
190
272
  - `$FLWR_HOME/` if `$FLWR_HOME` is defined
191
273
  - `$XDG_DATA_HOME/.flwr/` if `$XDG_DATA_HOME` is defined
@@ -218,9 +300,27 @@ def _parse_args_common(parser: argparse.ArgumentParser) -> None:
218
300
  help="Run the client without HTTPS. By default, the client runs with "
219
301
  "HTTPS enabled. Use this flag only if you understand the risks.",
220
302
  )
221
- parser.add_argument(
303
+ ex_group = parser.add_mutually_exclusive_group()
304
+ ex_group.add_argument(
305
+ "--grpc-rere",
306
+ action="store_const",
307
+ dest="transport",
308
+ const=TRANSPORT_TYPE_GRPC_RERE,
309
+ default=TRANSPORT_TYPE_GRPC_RERE,
310
+ help="Use grpc-rere as a transport layer for the client.",
311
+ )
312
+ ex_group.add_argument(
313
+ "--grpc-adapter",
314
+ action="store_const",
315
+ dest="transport",
316
+ const=TRANSPORT_TYPE_GRPC_ADAPTER,
317
+ help="Use grpc-adapter as a transport layer for the client.",
318
+ )
319
+ ex_group.add_argument(
222
320
  "--rest",
223
- action="store_true",
321
+ action="store_const",
322
+ dest="transport",
323
+ const=TRANSPORT_TYPE_REST,
224
324
  help="Use REST as a transport layer for the client.",
225
325
  )
226
326
  parser.add_argument(