flwr 1.13.1__py3-none-any.whl → 1.15.0__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.
Files changed (158) hide show
  1. flwr/cli/app.py +5 -0
  2. flwr/cli/auth_plugin/__init__.py +31 -0
  3. flwr/cli/auth_plugin/oidc_cli_plugin.py +150 -0
  4. flwr/cli/build.py +1 -0
  5. flwr/cli/cli_user_auth_interceptor.py +90 -0
  6. flwr/cli/config_utils.py +43 -149
  7. flwr/cli/constant.py +27 -0
  8. flwr/cli/example.py +1 -0
  9. flwr/cli/install.py +2 -1
  10. flwr/cli/log.py +34 -37
  11. flwr/cli/login/__init__.py +22 -0
  12. flwr/cli/login/login.py +116 -0
  13. flwr/cli/ls.py +214 -106
  14. flwr/cli/new/__init__.py +1 -0
  15. flwr/cli/new/new.py +2 -1
  16. flwr/cli/new/templates/app/.gitignore.tpl +3 -0
  17. flwr/cli/new/templates/app/README.md.tpl +3 -2
  18. flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +4 -4
  19. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +4 -4
  20. flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +4 -4
  21. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +2 -2
  22. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +3 -4
  23. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +2 -2
  24. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +4 -4
  25. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +3 -3
  26. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +2 -2
  27. flwr/cli/run/__init__.py +1 -0
  28. flwr/cli/run/run.py +103 -43
  29. flwr/cli/stop.py +139 -0
  30. flwr/cli/utils.py +186 -8
  31. flwr/client/app.py +49 -50
  32. flwr/client/client.py +1 -32
  33. flwr/client/clientapp/app.py +23 -26
  34. flwr/client/clientapp/utils.py +2 -1
  35. flwr/client/grpc_adapter_client/connection.py +1 -1
  36. flwr/client/grpc_client/connection.py +2 -13
  37. flwr/client/grpc_rere_client/client_interceptor.py +19 -119
  38. flwr/client/grpc_rere_client/connection.py +59 -43
  39. flwr/client/grpc_rere_client/grpc_adapter.py +12 -12
  40. flwr/client/message_handler/message_handler.py +1 -2
  41. flwr/client/message_handler/task_handler.py +0 -17
  42. flwr/client/mod/comms_mods.py +1 -0
  43. flwr/client/mod/localdp_mod.py +1 -1
  44. flwr/client/nodestate/__init__.py +1 -0
  45. flwr/client/nodestate/nodestate.py +1 -0
  46. flwr/client/nodestate/nodestate_factory.py +1 -0
  47. flwr/client/numpy_client.py +0 -44
  48. flwr/client/rest_client/connection.py +37 -29
  49. flwr/client/supernode/app.py +20 -74
  50. flwr/common/address.py +1 -0
  51. flwr/common/args.py +26 -47
  52. flwr/common/auth_plugin/__init__.py +24 -0
  53. flwr/common/auth_plugin/auth_plugin.py +122 -0
  54. flwr/common/config.py +169 -17
  55. flwr/common/constant.py +38 -9
  56. flwr/common/differential_privacy.py +2 -1
  57. flwr/common/exit/__init__.py +24 -0
  58. flwr/common/exit/exit.py +99 -0
  59. flwr/common/exit/exit_code.py +93 -0
  60. flwr/common/exit_handlers.py +24 -10
  61. flwr/common/grpc.py +167 -4
  62. flwr/common/logger.py +66 -7
  63. flwr/common/message.py +1 -0
  64. flwr/common/object_ref.py +57 -54
  65. flwr/common/pyproject.py +1 -0
  66. flwr/common/record/__init__.py +1 -0
  67. flwr/common/record/parametersrecord.py +1 -0
  68. flwr/common/record/recordset.py +1 -1
  69. flwr/common/retry_invoker.py +77 -0
  70. flwr/common/secure_aggregation/crypto/symmetric_encryption.py +45 -0
  71. flwr/common/secure_aggregation/secaggplus_utils.py +2 -2
  72. flwr/common/serde.py +6 -4
  73. flwr/common/telemetry.py +15 -4
  74. flwr/common/typing.py +32 -0
  75. flwr/common/version.py +1 -0
  76. flwr/proto/clientappio_pb2.py +1 -1
  77. flwr/proto/error_pb2.py +1 -1
  78. flwr/proto/exec_pb2.py +27 -15
  79. flwr/proto/exec_pb2.pyi +80 -2
  80. flwr/proto/exec_pb2_grpc.py +102 -0
  81. flwr/proto/exec_pb2_grpc.pyi +39 -0
  82. flwr/proto/fab_pb2.py +5 -5
  83. flwr/proto/fab_pb2.pyi +4 -1
  84. flwr/proto/fleet_pb2.py +31 -31
  85. flwr/proto/fleet_pb2.pyi +23 -23
  86. flwr/proto/fleet_pb2_grpc.py +30 -30
  87. flwr/proto/fleet_pb2_grpc.pyi +20 -20
  88. flwr/proto/grpcadapter_pb2.py +1 -1
  89. flwr/proto/log_pb2.py +1 -1
  90. flwr/proto/message_pb2.py +1 -1
  91. flwr/proto/node_pb2.py +3 -3
  92. flwr/proto/node_pb2.pyi +1 -4
  93. flwr/proto/recordset_pb2.py +1 -1
  94. flwr/proto/run_pb2.py +1 -1
  95. flwr/proto/serverappio_pb2.py +24 -25
  96. flwr/proto/serverappio_pb2.pyi +32 -32
  97. flwr/proto/serverappio_pb2_grpc.py +62 -28
  98. flwr/proto/serverappio_pb2_grpc.pyi +29 -16
  99. flwr/proto/simulationio_pb2.py +3 -3
  100. flwr/proto/simulationio_pb2_grpc.py +34 -0
  101. flwr/proto/simulationio_pb2_grpc.pyi +13 -0
  102. flwr/proto/task_pb2.py +1 -1
  103. flwr/proto/transport_pb2.py +1 -1
  104. flwr/server/app.py +152 -112
  105. flwr/server/compat/app_utils.py +7 -2
  106. flwr/server/compat/driver_client_proxy.py +1 -2
  107. flwr/server/driver/grpc_driver.py +38 -85
  108. flwr/server/driver/inmemory_driver.py +7 -2
  109. flwr/server/run_serverapp.py +8 -9
  110. flwr/server/serverapp/app.py +37 -13
  111. flwr/server/strategy/dpfedavg_fixed.py +1 -0
  112. flwr/server/superlink/driver/serverappio_grpc.py +2 -1
  113. flwr/server/superlink/driver/serverappio_servicer.py +148 -63
  114. flwr/server/superlink/ffs/disk_ffs.py +1 -0
  115. flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +20 -87
  116. flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -0
  117. flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +2 -165
  118. flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +56 -35
  119. flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +99 -169
  120. flwr/server/superlink/fleet/message_handler/message_handler.py +69 -29
  121. flwr/server/superlink/fleet/rest_rere/rest_api.py +20 -19
  122. flwr/server/superlink/fleet/vce/__init__.py +1 -0
  123. flwr/server/superlink/fleet/vce/backend/__init__.py +1 -0
  124. flwr/server/superlink/fleet/vce/backend/raybackend.py +1 -0
  125. flwr/server/superlink/fleet/vce/vce_api.py +2 -2
  126. flwr/server/superlink/linkstate/in_memory_linkstate.py +60 -99
  127. flwr/server/superlink/linkstate/linkstate.py +30 -36
  128. flwr/server/superlink/linkstate/sqlite_linkstate.py +105 -188
  129. flwr/server/superlink/linkstate/utils.py +18 -8
  130. flwr/server/superlink/simulation/simulationio_grpc.py +1 -1
  131. flwr/server/superlink/simulation/simulationio_servicer.py +33 -0
  132. flwr/server/superlink/utils.py +65 -0
  133. flwr/server/utils/validator.py +9 -34
  134. flwr/simulation/app.py +20 -10
  135. flwr/simulation/legacy_app.py +4 -2
  136. flwr/simulation/ray_transport/ray_actor.py +1 -0
  137. flwr/simulation/ray_transport/utils.py +1 -0
  138. flwr/simulation/run_simulation.py +36 -22
  139. flwr/simulation/simulationio_connection.py +5 -1
  140. flwr/superexec/app.py +1 -0
  141. flwr/superexec/deployment.py +1 -0
  142. flwr/superexec/exec_grpc.py +20 -2
  143. flwr/superexec/exec_servicer.py +97 -2
  144. flwr/superexec/exec_user_auth_interceptor.py +101 -0
  145. flwr/superexec/executor.py +1 -0
  146. {flwr-1.13.1.dist-info → flwr-1.15.0.dist-info}/METADATA +14 -13
  147. {flwr-1.13.1.dist-info → flwr-1.15.0.dist-info}/RECORD +150 -144
  148. flwr/proto/common_pb2.py +0 -36
  149. flwr/proto/common_pb2.pyi +0 -121
  150. flwr/proto/common_pb2_grpc.py +0 -4
  151. flwr/proto/common_pb2_grpc.pyi +0 -4
  152. flwr/proto/control_pb2.py +0 -27
  153. flwr/proto/control_pb2.pyi +0 -7
  154. flwr/proto/control_pb2_grpc.py +0 -135
  155. flwr/proto/control_pb2_grpc.pyi +0 -53
  156. {flwr-1.13.1.dist-info → flwr-1.15.0.dist-info}/LICENSE +0 -0
  157. {flwr-1.13.1.dist-info → flwr-1.15.0.dist-info}/WHEEL +0 -0
  158. {flwr-1.13.1.dist-info → flwr-1.15.0.dist-info}/entry_points.txt +0 -0
@@ -14,38 +14,42 @@
14
14
  # ==============================================================================
15
15
  """Flower gRPC Driver."""
16
16
 
17
+
17
18
  import time
18
19
  import warnings
19
20
  from collections.abc import Iterable
20
- from logging import DEBUG, INFO, WARN, WARNING
21
- from typing import Any, Optional, cast
21
+ from logging import DEBUG, WARNING
22
+ from typing import Optional, cast
22
23
 
23
24
  import grpc
24
25
 
25
26
  from flwr.common import DEFAULT_TTL, Message, Metadata, RecordSet
26
- from flwr.common.constant import MAX_RETRY_DELAY, SERVERAPPIO_API_DEFAULT_CLIENT_ADDRESS
27
- from flwr.common.grpc import create_channel
27
+ from flwr.common.constant import (
28
+ SERVERAPPIO_API_DEFAULT_CLIENT_ADDRESS,
29
+ SUPERLINK_NODE_ID,
30
+ )
31
+ from flwr.common.grpc import create_channel, on_channel_state_change
28
32
  from flwr.common.logger import log
29
- from flwr.common.retry_invoker import RetryInvoker, RetryState, exponential
30
- from flwr.common.serde import message_from_taskres, message_to_taskins, run_from_proto
33
+ from flwr.common.retry_invoker import _make_simple_grpc_retry_invoker, _wrap_stub
34
+ from flwr.common.serde import message_from_proto, message_to_proto, run_from_proto
31
35
  from flwr.common.typing import Run
36
+ from flwr.proto.message_pb2 import Message as ProtoMessage # pylint: disable=E0611
32
37
  from flwr.proto.node_pb2 import Node # pylint: disable=E0611
33
38
  from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611
34
39
  from flwr.proto.serverappio_pb2 import ( # pylint: disable=E0611
35
40
  GetNodesRequest,
36
41
  GetNodesResponse,
37
- PullTaskResRequest,
38
- PullTaskResResponse,
39
- PushTaskInsRequest,
40
- PushTaskInsResponse,
42
+ PullResMessagesRequest,
43
+ PullResMessagesResponse,
44
+ PushInsMessagesRequest,
45
+ PushInsMessagesResponse,
41
46
  )
42
47
  from flwr.proto.serverappio_pb2_grpc import ServerAppIoStub # pylint: disable=E0611
43
- from flwr.proto.task_pb2 import TaskIns # pylint: disable=E0611
44
48
 
45
49
  from .driver import Driver
46
50
 
47
51
  ERROR_MESSAGE_DRIVER_NOT_CONNECTED = """
48
- [Driver] Error: Not connected.
52
+ [flwr-serverapp] Error: Not connected.
49
53
 
50
54
  Call `connect()` on the `GrpcDriverStub` instance before calling any of the other
51
55
  `GrpcDriverStub` methods.
@@ -75,7 +79,7 @@ class GrpcDriver(Driver):
75
79
  self._run: Optional[Run] = None
76
80
  self._grpc_stub: Optional[ServerAppIoStub] = None
77
81
  self._channel: Optional[grpc.Channel] = None
78
- self.node = Node(node_id=0, anonymous=True)
82
+ self.node = Node(node_id=SUPERLINK_NODE_ID)
79
83
  self._retry_invoker = _make_simple_grpc_retry_invoker()
80
84
 
81
85
  @property
@@ -96,9 +100,10 @@ class GrpcDriver(Driver):
96
100
  insecure=(self._cert is None),
97
101
  root_certificates=self._cert,
98
102
  )
103
+ self._channel.subscribe(on_channel_state_change)
99
104
  self._grpc_stub = ServerAppIoStub(self._channel)
100
105
  _wrap_stub(self._grpc_stub, self._retry_invoker)
101
- log(DEBUG, "[Driver] Connected to %s", self._addr)
106
+ log(DEBUG, "[flwr-serverapp] Connected to %s", self._addr)
102
107
 
103
108
  def _disconnect(self) -> None:
104
109
  """Disconnect from the ServerAppIo API."""
@@ -109,7 +114,7 @@ class GrpcDriver(Driver):
109
114
  self._channel = None
110
115
  self._grpc_stub = None
111
116
  channel.close()
112
- log(DEBUG, "[Driver] Disconnected")
117
+ log(DEBUG, "[flwr-serverapp] Disconnected")
113
118
 
114
119
  def set_run(self, run_id: int) -> None:
115
120
  """Set the run."""
@@ -192,20 +197,22 @@ class GrpcDriver(Driver):
192
197
  This method takes an iterable of messages and sends each message
193
198
  to the node specified in `dst_node_id`.
194
199
  """
195
- # Construct TaskIns
196
- task_ins_list: list[TaskIns] = []
200
+ # Construct Messages
201
+ message_proto_list: list[ProtoMessage] = []
197
202
  for msg in messages:
198
203
  # Check message
199
204
  self._check_message(msg)
200
- # Convert Message to TaskIns
201
- taskins = message_to_taskins(msg)
205
+ # Convert to proto
206
+ msg_proto = message_to_proto(msg)
202
207
  # Add to list
203
- task_ins_list.append(taskins)
208
+ message_proto_list.append(msg_proto)
204
209
  # Call GrpcDriverStub method
205
- res: PushTaskInsResponse = self._stub.PushTaskIns(
206
- PushTaskInsRequest(task_ins_list=task_ins_list)
210
+ res: PushInsMessagesResponse = self._stub.PushMessages(
211
+ PushInsMessagesRequest(
212
+ messages_list=message_proto_list, run_id=cast(Run, self._run).run_id
213
+ )
207
214
  )
208
- return list(res.task_ids)
215
+ return list(res.message_ids)
209
216
 
210
217
  def pull_messages(self, message_ids: Iterable[str]) -> Iterable[Message]:
211
218
  """Pull messages based on message IDs.
@@ -213,12 +220,15 @@ class GrpcDriver(Driver):
213
220
  This method is used to collect messages from the SuperLink that correspond to a
214
221
  set of given message IDs.
215
222
  """
216
- # Pull TaskRes
217
- res: PullTaskResResponse = self._stub.PullTaskRes(
218
- PullTaskResRequest(node=self.node, task_ids=message_ids)
223
+ # Pull Messages
224
+ res: PullResMessagesResponse = self._stub.PullMessages(
225
+ PullResMessagesRequest(
226
+ message_ids=message_ids,
227
+ run_id=cast(Run, self._run).run_id,
228
+ )
219
229
  )
220
- # Convert TaskRes to Message
221
- msgs = [message_from_taskres(taskres) for taskres in res.task_res_list]
230
+ # Convert Message from Protobuf representation
231
+ msgs = [message_from_proto(msg_proto) for msg_proto in res.messages_list]
222
232
  return msgs
223
233
 
224
234
  def send_and_receive(
@@ -258,60 +268,3 @@ class GrpcDriver(Driver):
258
268
  return
259
269
  # Disconnect
260
270
  self._disconnect()
261
-
262
-
263
- def _make_simple_grpc_retry_invoker() -> RetryInvoker:
264
- """Create a simple gRPC retry invoker."""
265
-
266
- def _on_sucess(retry_state: RetryState) -> None:
267
- if retry_state.tries > 1:
268
- log(
269
- INFO,
270
- "Connection successful after %.2f seconds and %s tries.",
271
- retry_state.elapsed_time,
272
- retry_state.tries,
273
- )
274
-
275
- def _on_backoff(retry_state: RetryState) -> None:
276
- if retry_state.tries == 1:
277
- log(WARN, "Connection attempt failed, retrying...")
278
- else:
279
- log(
280
- WARN,
281
- "Connection attempt failed, retrying in %.2f seconds",
282
- retry_state.actual_wait,
283
- )
284
-
285
- def _on_giveup(retry_state: RetryState) -> None:
286
- if retry_state.tries > 1:
287
- log(
288
- WARN,
289
- "Giving up reconnection after %.2f seconds and %s tries.",
290
- retry_state.elapsed_time,
291
- retry_state.tries,
292
- )
293
-
294
- return RetryInvoker(
295
- wait_gen_factory=lambda: exponential(max_delay=MAX_RETRY_DELAY),
296
- recoverable_exceptions=grpc.RpcError,
297
- max_tries=None,
298
- max_time=None,
299
- on_success=_on_sucess,
300
- on_backoff=_on_backoff,
301
- on_giveup=_on_giveup,
302
- should_giveup=lambda e: e.code() != grpc.StatusCode.UNAVAILABLE, # type: ignore
303
- )
304
-
305
-
306
- def _wrap_stub(stub: ServerAppIoStub, retry_invoker: RetryInvoker) -> None:
307
- """Wrap the gRPC stub with a retry invoker."""
308
-
309
- def make_lambda(original_method: Any) -> Any:
310
- return lambda *args, **kwargs: retry_invoker.invoke(
311
- original_method, *args, **kwargs
312
- )
313
-
314
- for method_name in vars(stub):
315
- method = getattr(stub, method_name)
316
- if callable(method):
317
- setattr(stub, method_name, make_lambda(method))
@@ -22,6 +22,7 @@ from typing import Optional, cast
22
22
  from uuid import UUID
23
23
 
24
24
  from flwr.common import DEFAULT_TTL, Message, Metadata, RecordSet
25
+ from flwr.common.constant import SUPERLINK_NODE_ID
25
26
  from flwr.common.serde import message_from_taskres, message_to_taskins
26
27
  from flwr.common.typing import Run
27
28
  from flwr.proto.node_pb2 import Node # pylint: disable=E0611
@@ -49,7 +50,7 @@ class InMemoryDriver(Driver):
49
50
  self._run: Optional[Run] = None
50
51
  self.state = state_factory.state()
51
52
  self.pull_interval = pull_interval
52
- self.node = Node(node_id=0, anonymous=True)
53
+ self.node = Node(node_id=SUPERLINK_NODE_ID)
53
54
 
54
55
  def _check_message(self, message: Message) -> None:
55
56
  # Check if the message is valid
@@ -142,7 +143,11 @@ class InMemoryDriver(Driver):
142
143
  # Pull TaskRes
143
144
  task_res_list = self.state.get_task_res(task_ids=msg_ids)
144
145
  # Delete tasks in state
145
- self.state.delete_tasks(msg_ids)
146
+ # Delete the TaskIns/TaskRes pairs if TaskRes is found
147
+ task_ins_ids_to_delete = {
148
+ UUID(task_res.task.ancestry[0]) for task_res in task_res_list
149
+ }
150
+ self.state.delete_tasks(task_ins_ids=task_ins_ids_to_delete)
146
151
  # Convert TaskRes to Message
147
152
  msgs = [message_from_taskres(taskres) for taskres in task_res_list]
148
153
  return msgs
@@ -15,12 +15,12 @@
15
15
  """Run ServerApp."""
16
16
 
17
17
 
18
- import sys
19
18
  from logging import DEBUG, ERROR
20
19
  from typing import Optional
21
20
 
22
- from flwr.common import Context
23
- from flwr.common.logger import log, warn_unsupported_feature
21
+ from flwr.common import Context, EventType, event
22
+ from flwr.common.exit_handlers import register_exit_handlers
23
+ from flwr.common.logger import log
24
24
  from flwr.common.object_ref import load_app
25
25
 
26
26
  from .driver import Driver
@@ -66,12 +66,11 @@ def run(
66
66
  return context
67
67
 
68
68
 
69
- # pylint: disable-next=too-many-branches,too-many-statements,too-many-locals
70
69
  def run_server_app() -> None:
71
70
  """Run Flower server app."""
72
- warn_unsupported_feature(
73
- "The command `flower-server-app` is deprecated and no longer in use. "
74
- "Use the `flwr-serverapp` exclusively instead."
71
+ event(EventType.RUN_SERVER_APP_ENTER)
72
+ log(
73
+ ERROR,
74
+ "The command `flower-server-app` has been replaced by `flwr run`.",
75
75
  )
76
- log(ERROR, "`flower-server-app` used.")
77
- sys.exit()
76
+ register_exit_handlers(event_type=EventType.RUN_SERVER_APP_LEAVE)
@@ -14,8 +14,8 @@
14
14
  # ==============================================================================
15
15
  """Flower ServerApp process."""
16
16
 
17
+
17
18
  import argparse
18
- import sys
19
19
  from logging import DEBUG, ERROR, INFO
20
20
  from pathlib import Path
21
21
  from queue import Queue
@@ -24,6 +24,7 @@ from typing import Optional
24
24
 
25
25
  from flwr.cli.config_utils import get_fab_metadata
26
26
  from flwr.cli.install import install_from_fab
27
+ from flwr.cli.utils import get_sha256_hash
27
28
  from flwr.common.args import add_args_flwr_app_common
28
29
  from flwr.common.config import (
29
30
  get_flwr_dir,
@@ -36,6 +37,7 @@ from flwr.common.constant import (
36
37
  Status,
37
38
  SubStatus,
38
39
  )
40
+ from flwr.common.exit import ExitCode, flwr_exit
39
41
  from flwr.common.logger import (
40
42
  log,
41
43
  mirror_output_to_queue,
@@ -50,7 +52,8 @@ from flwr.common.serde import (
50
52
  run_from_proto,
51
53
  run_status_to_proto,
52
54
  )
53
- from flwr.common.typing import RunStatus
55
+ from flwr.common.telemetry import EventType, event
56
+ from flwr.common.typing import RunNotRunningException, RunStatus
54
57
  from flwr.proto.run_pb2 import UpdateRunStatusRequest # pylint: disable=E0611
55
58
  from flwr.proto.serverappio_pb2 import ( # pylint: disable=E0611
56
59
  PullServerAppInputsRequest,
@@ -69,19 +72,18 @@ def flwr_serverapp() -> None:
69
72
 
70
73
  args = _parse_args_run_flwr_serverapp().parse_args()
71
74
 
72
- log(INFO, "Starting Flower ServerApp")
75
+ log(INFO, "Start `flwr-serverapp` process")
73
76
 
74
77
  if not args.insecure:
75
- log(
76
- ERROR,
77
- "`flwr-serverapp` does not support TLS yet. "
78
- "Please use the '--insecure' flag.",
78
+ flwr_exit(
79
+ ExitCode.COMMON_TLS_NOT_SUPPORTED,
80
+ "`flwr-serverapp` does not support TLS yet.",
79
81
  )
80
- sys.exit(1)
81
82
 
82
83
  log(
83
84
  DEBUG,
84
- "Starting isolated `ServerApp` connected to SuperLink's ServerAppIo API at %s",
85
+ "`flwr-serverapp` will attempt to connect to SuperLink's "
86
+ "ServerAppIo API at %s",
85
87
  args.serverappio_api_address,
86
88
  )
87
89
  run_serverapp(
@@ -96,7 +98,7 @@ def flwr_serverapp() -> None:
96
98
  restore_output()
97
99
 
98
100
 
99
- def run_serverapp( # pylint: disable=R0914, disable=W0212
101
+ def run_serverapp( # pylint: disable=R0914, disable=W0212, disable=R0915
100
102
  serverappio_api_address: str,
101
103
  log_queue: Queue[Optional[str]],
102
104
  run_once: bool,
@@ -112,12 +114,15 @@ def run_serverapp( # pylint: disable=R0914, disable=W0212
112
114
  # Resolve directory where FABs are installed
113
115
  flwr_dir_ = get_flwr_dir(flwr_dir)
114
116
  log_uploader = None
115
-
117
+ success = True
118
+ hash_run_id = None
119
+ run_status = None
116
120
  while True:
117
121
 
118
122
  try:
119
123
  # Pull ServerAppInputs from LinkState
120
124
  req = PullServerAppInputsRequest()
125
+ log(DEBUG, "[flwr-serverapp] Pull ServerAppInputs")
121
126
  res: PullServerAppInputsResponse = driver._stub.PullServerAppInputs(req)
122
127
  if not res.HasField("run"):
123
128
  sleep(3)
@@ -128,6 +133,8 @@ def run_serverapp( # pylint: disable=R0914, disable=W0212
128
133
  run = run_from_proto(res.run)
129
134
  fab = fab_from_proto(res.fab)
130
135
 
136
+ hash_run_id = get_sha256_hash(run.run_id)
137
+
131
138
  driver.set_run(run.run_id)
132
139
 
133
140
  # Start log uploader for this run
@@ -138,7 +145,7 @@ def run_serverapp( # pylint: disable=R0914, disable=W0212
138
145
  stub=driver._stub,
139
146
  )
140
147
 
141
- log(DEBUG, "ServerApp process starts FAB installation.")
148
+ log(DEBUG, "[flwr-serverapp] Start FAB installation.")
142
149
  install_from_fab(fab.content, flwr_dir=flwr_dir_, skip_prompt=True)
143
150
 
144
151
  fab_id, fab_version = get_fab_metadata(fab.content)
@@ -159,7 +166,7 @@ def run_serverapp( # pylint: disable=R0914, disable=W0212
159
166
 
160
167
  log(
161
168
  DEBUG,
162
- "Flower will load ServerApp `%s` in %s",
169
+ "[flwr-serverapp] Will load ServerApp `%s` in %s",
163
170
  server_app_attr,
164
171
  app_path,
165
172
  )
@@ -170,6 +177,11 @@ def run_serverapp( # pylint: disable=R0914, disable=W0212
170
177
  UpdateRunStatusRequest(run_id=run.run_id, run_status=run_status_proto)
171
178
  )
172
179
 
180
+ event(
181
+ EventType.FLWR_SERVERAPP_RUN_ENTER,
182
+ event_details={"run-id-hash": hash_run_id},
183
+ )
184
+
173
185
  # Load and run the ServerApp with the Driver
174
186
  updated_context = run_(
175
187
  driver=driver,
@@ -180,17 +192,25 @@ def run_serverapp( # pylint: disable=R0914, disable=W0212
180
192
 
181
193
  # Send resulting context
182
194
  context_proto = context_to_proto(updated_context)
195
+ log(DEBUG, "[flwr-serverapp] Will push ServerAppOutputs")
183
196
  out_req = PushServerAppOutputsRequest(
184
197
  run_id=run.run_id, context=context_proto
185
198
  )
186
199
  _ = driver._stub.PushServerAppOutputs(out_req)
187
200
 
188
201
  run_status = RunStatus(Status.FINISHED, SubStatus.COMPLETED, "")
202
+ except RunNotRunningException:
203
+ log(INFO, "")
204
+ log(INFO, "Run ID %s stopped.", run.run_id)
205
+ log(INFO, "")
206
+ run_status = None
207
+ success = False
189
208
 
190
209
  except Exception as ex: # pylint: disable=broad-exception-caught
191
210
  exc_entity = "ServerApp"
192
211
  log(ERROR, "%s raised an exception", exc_entity, exc_info=ex)
193
212
  run_status = RunStatus(Status.FINISHED, SubStatus.FAILED, str(ex))
213
+ success = False
194
214
 
195
215
  finally:
196
216
  # Stop log uploader for this run and upload final logs
@@ -206,6 +226,10 @@ def run_serverapp( # pylint: disable=R0914, disable=W0212
206
226
  run_id=run.run_id, run_status=run_status_proto
207
227
  )
208
228
  )
229
+ event(
230
+ EventType.FLWR_SERVERAPP_RUN_LEAVE,
231
+ event_details={"run-id-hash": hash_run_id, "success": success},
232
+ )
209
233
 
210
234
  # Stop the loop if `flwr-serverapp` is expected to process a single run
211
235
  if run_once:
@@ -17,6 +17,7 @@
17
17
  Paper: arxiv.org/pdf/1710.06963.pdf
18
18
  """
19
19
 
20
+
20
21
  from typing import Optional, Union
21
22
 
22
23
  from flwr.common import EvaluateIns, EvaluateRes, FitIns, FitRes, Parameters, Scalar
@@ -14,12 +14,14 @@
14
14
  # ==============================================================================
15
15
  """ServerAppIo gRPC API."""
16
16
 
17
+
17
18
  from logging import INFO
18
19
  from typing import Optional
19
20
 
20
21
  import grpc
21
22
 
22
23
  from flwr.common import GRPC_MAX_MESSAGE_LENGTH
24
+ from flwr.common.grpc import generic_create_grpc_server
23
25
  from flwr.common.logger import log
24
26
  from flwr.proto.serverappio_pb2_grpc import ( # pylint: disable=E0611
25
27
  add_ServerAppIoServicer_to_server,
@@ -27,7 +29,6 @@ from flwr.proto.serverappio_pb2_grpc import ( # pylint: disable=E0611
27
29
  from flwr.server.superlink.ffs.ffs_factory import FfsFactory
28
30
  from flwr.server.superlink.linkstate import LinkStateFactory
29
31
 
30
- from ..fleet.grpc_bidi.grpc_server import generic_create_grpc_server
31
32
  from .serverappio_servicer import ServerAppIoServicer
32
33
 
33
34