flwr-nightly 1.8.0.dev20240327__py3-none-any.whl → 1.8.0.dev20240328__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/client/app.py CHANGED
@@ -14,11 +14,10 @@
14
14
  # ==============================================================================
15
15
  """Flower client app."""
16
16
 
17
-
18
17
  import argparse
19
18
  import sys
20
19
  import time
21
- from logging import DEBUG, INFO, WARN
20
+ from logging import DEBUG, ERROR, INFO, WARN
22
21
  from pathlib import Path
23
22
  from typing import Callable, ContextManager, Optional, Tuple, Type, Union
24
23
 
@@ -38,6 +37,7 @@ from flwr.common.constant import (
38
37
  )
39
38
  from flwr.common.exit_handlers import register_exit_handlers
40
39
  from flwr.common.logger import log, warn_deprecated_feature, warn_experimental_feature
40
+ from flwr.common.message import Error
41
41
  from flwr.common.object_ref import load_app, validate
42
42
  from flwr.common.retry_invoker import RetryInvoker, exponential
43
43
 
@@ -482,32 +482,43 @@ def _start_client_internal(
482
482
  # Retrieve context for this run
483
483
  context = node_state.retrieve_context(run_id=message.metadata.run_id)
484
484
 
485
- # Load ClientApp instance
486
- client_app: ClientApp = load_client_app_fn()
485
+ # Create an error reply message that will never be used to prevent
486
+ # the used-before-assignment linting error
487
+ reply_message = message.create_error_reply(
488
+ error=Error(code=0, reason="Unknown")
489
+ )
487
490
 
488
- # Handle task message
489
- out_message = client_app(message=message, context=context)
491
+ # Handle app loading and task message
492
+ try:
493
+ # Load ClientApp instance
494
+ client_app: ClientApp = load_client_app_fn()
490
495
 
491
- # Update node state
492
- node_state.update_context(
493
- run_id=message.metadata.run_id,
494
- context=context,
495
- )
496
+ reply_message = client_app(message=message, context=context)
497
+ # Update node state
498
+ node_state.update_context(
499
+ run_id=message.metadata.run_id,
500
+ context=context,
501
+ )
502
+ except Exception as ex: # pylint: disable=broad-exception-caught
503
+ log(ERROR, "ClientApp raised an exception", exc_info=ex)
504
+
505
+ # Legacy grpc-bidi
506
+ if transport in ["grpc-bidi", None]:
507
+ # Raise exception, crash process
508
+ raise ex
509
+
510
+ # Don't update/change NodeState
511
+
512
+ # Create error message
513
+ # Reason example: "<class 'ZeroDivisionError'>:<'division by zero'>"
514
+ reason = str(type(ex)) + ":<'" + str(ex) + "'>"
515
+ reply_message = message.create_error_reply(
516
+ error=Error(code=0, reason=reason)
517
+ )
496
518
 
497
519
  # Send
498
- send(out_message)
499
- log(
500
- INFO,
501
- "[RUN %s, ROUND %s]",
502
- out_message.metadata.run_id,
503
- out_message.metadata.group_id,
504
- )
505
- log(
506
- INFO,
507
- "Sent: %s reply to message %s",
508
- out_message.metadata.message_type,
509
- message.metadata.message_id,
510
- )
520
+ send(reply_message)
521
+ log(INFO, "Sent reply")
511
522
 
512
523
  # Unregister node
513
524
  if delete_node is not None:
flwr/common/message.py CHANGED
@@ -297,22 +297,33 @@ class Message:
297
297
  partition_id=self.metadata.partition_id,
298
298
  )
299
299
 
300
- def create_error_reply(
301
- self,
302
- error: Error,
303
- ttl: float,
304
- ) -> Message:
300
+ def create_error_reply(self, error: Error, ttl: float | None = None) -> Message:
305
301
  """Construct a reply message indicating an error happened.
306
302
 
307
303
  Parameters
308
304
  ----------
309
305
  error : Error
310
306
  The error that was encountered.
311
- ttl : float
312
- Time-to-live for this message in seconds.
307
+ ttl : Optional[float] (default: None)
308
+ Time-to-live for this message in seconds. If unset, it will be set based
309
+ on the remaining time for the received message before it expires. This
310
+ follows the equation:
311
+
312
+ ttl = msg.meta.ttl - (reply.meta.created_at - msg.meta.created_at)
313
313
  """
314
+ # If no TTL passed, use default for message creation (will update after
315
+ # message creation)
316
+ ttl_ = DEFAULT_TTL if ttl is None else ttl
314
317
  # Create reply with error
315
- message = Message(metadata=self._create_reply_metadata(ttl), error=error)
318
+ message = Message(metadata=self._create_reply_metadata(ttl_), error=error)
319
+
320
+ if ttl is None:
321
+ # Set TTL equal to the remaining time for the received message to expire
322
+ ttl = self.metadata.ttl - (
323
+ message.metadata.created_at - self.metadata.created_at
324
+ )
325
+ message.metadata.ttl = ttl
326
+
316
327
  return message
317
328
 
318
329
  def create_reply(self, content: RecordSet, ttl: float | None = None) -> Message:
@@ -327,18 +338,31 @@ class Message:
327
338
  content : RecordSet
328
339
  The content for the reply message.
329
340
  ttl : Optional[float] (default: None)
330
- Time-to-live for this message in seconds. If unset, it will use
331
- the `common.DEFAULT_TTL` value.
341
+ Time-to-live for this message in seconds. If unset, it will be set based
342
+ on the remaining time for the received message before it expires. This
343
+ follows the equation:
344
+
345
+ ttl = msg.meta.ttl - (reply.meta.created_at - msg.meta.created_at)
332
346
 
333
347
  Returns
334
348
  -------
335
349
  Message
336
350
  A new `Message` instance representing the reply.
337
351
  """
338
- if ttl is None:
339
- ttl = DEFAULT_TTL
352
+ # If no TTL passed, use default for message creation (will update after
353
+ # message creation)
354
+ ttl_ = DEFAULT_TTL if ttl is None else ttl
340
355
 
341
- return Message(
342
- metadata=self._create_reply_metadata(ttl),
356
+ message = Message(
357
+ metadata=self._create_reply_metadata(ttl_),
343
358
  content=content,
344
359
  )
360
+
361
+ if ttl is None:
362
+ # Set TTL equal to the remaining time for the received message to expire
363
+ ttl = self.metadata.ttl - (
364
+ message.metadata.created_at - self.metadata.created_at
365
+ )
366
+ message.metadata.ttl = ttl
367
+
368
+ return message
@@ -170,8 +170,24 @@ class DriverClientProxy(ClientProxy):
170
170
  )
171
171
  if len(task_res_list) == 1:
172
172
  task_res = task_res_list[0]
173
+
174
+ # This will raise an Exception if task_res carries an `error`
175
+ validate_task_res(task_res=task_res)
176
+
173
177
  return serde.recordset_from_proto(task_res.task.recordset)
174
178
 
175
179
  if timeout is not None and time.time() > start_time + timeout:
176
180
  raise RuntimeError("Timeout reached")
177
181
  time.sleep(SLEEP_TIME)
182
+
183
+
184
+ def validate_task_res(
185
+ task_res: task_pb2.TaskRes, # pylint: disable=E1101
186
+ ) -> None:
187
+ """Validate if a TaskRes is empty or not."""
188
+ if not task_res.HasField("task"):
189
+ raise ValueError("Invalid TaskRes, field `task` missing")
190
+ if task_res.task.HasField("error"):
191
+ raise ValueError("Exception during client-side task execution")
192
+ if not task_res.task.HasField("recordset"):
193
+ raise ValueError("Invalid TaskRes, both `recordset` and `error` are missing")
@@ -20,7 +20,7 @@ from typing import Callable, Dict, List, Tuple, Union
20
20
 
21
21
  import ray
22
22
 
23
- from flwr.client.client_app import ClientApp, LoadClientAppError
23
+ from flwr.client.client_app import ClientApp
24
24
  from flwr.common.context import Context
25
25
  from flwr.common.logger import log
26
26
  from flwr.common.message import Message
@@ -151,7 +151,6 @@ class RayBackend(Backend):
151
151
  )
152
152
 
153
153
  await future
154
-
155
154
  # Fetch result
156
155
  (
157
156
  out_mssg,
@@ -160,13 +159,15 @@ class RayBackend(Backend):
160
159
 
161
160
  return out_mssg, updated_context
162
161
 
163
- except LoadClientAppError as load_ex:
162
+ except Exception as ex:
164
163
  log(
165
164
  ERROR,
166
165
  "An exception was raised when processing a message by %s",
167
166
  self.__class__.__name__,
168
167
  )
169
- raise load_ex
168
+ # add actor back into pool
169
+ await self.pool.add_actor_back_to_pool(future)
170
+ raise ex
170
171
 
171
172
  async def terminate(self) -> None:
172
173
  """Terminate all actors in actor pool."""
@@ -14,9 +14,10 @@
14
14
  # ==============================================================================
15
15
  """Fleet Simulation Engine API."""
16
16
 
17
-
18
17
  import asyncio
19
18
  import json
19
+ import sys
20
+ import time
20
21
  import traceback
21
22
  from logging import DEBUG, ERROR, INFO, WARN
22
23
  from typing import Callable, Dict, List, Optional
@@ -24,6 +25,7 @@ from typing import Callable, Dict, List, Optional
24
25
  from flwr.client.client_app import ClientApp, LoadClientAppError
25
26
  from flwr.client.node_state import NodeState
26
27
  from flwr.common.logger import log
28
+ from flwr.common.message import Error
27
29
  from flwr.common.object_ref import load_app
28
30
  from flwr.common.serde import message_from_taskins, message_to_taskres
29
31
  from flwr.proto.task_pb2 import TaskIns # pylint: disable=E0611
@@ -59,6 +61,7 @@ async def worker(
59
61
  """Get TaskIns from queue and pass it to an actor in the pool to execute it."""
60
62
  state = state_factory.state()
61
63
  while True:
64
+ out_mssg = None
62
65
  try:
63
66
  task_ins: TaskIns = await queue.get()
64
67
  node_id = task_ins.task.consumer.node_id
@@ -82,24 +85,25 @@ async def worker(
82
85
  task_ins.run_id, context=updated_context
83
86
  )
84
87
 
85
- # Convert to TaskRes
86
- task_res = message_to_taskres(out_mssg)
87
- # Store TaskRes in state
88
- state.store_task_res(task_res)
89
-
90
88
  except asyncio.CancelledError as e:
91
- log(DEBUG, "Async worker: %s", e)
89
+ log(DEBUG, "Terminating async worker: %s", e)
92
90
  break
93
91
 
94
- except LoadClientAppError as app_ex:
95
- log(ERROR, "Async worker: %s", app_ex)
96
- log(ERROR, traceback.format_exc())
97
- raise
98
-
92
+ # Exceptions aren't raised but reported as an error message
99
93
  except Exception as ex: # pylint: disable=broad-exception-caught
100
94
  log(ERROR, ex)
101
95
  log(ERROR, traceback.format_exc())
102
- break
96
+ reason = str(type(ex)) + ":<'" + str(ex) + "'>"
97
+ error = Error(code=0, reason=reason)
98
+ out_mssg = message.create_error_reply(error=error)
99
+
100
+ finally:
101
+ if out_mssg:
102
+ # Convert to TaskRes
103
+ task_res = message_to_taskres(out_mssg)
104
+ # Store TaskRes in state
105
+ task_res.task.pushed_at = time.time()
106
+ state.store_task_res(task_res)
103
107
 
104
108
 
105
109
  async def add_taskins_to_queue(
@@ -218,7 +222,7 @@ async def run(
218
222
  await backend.terminate()
219
223
 
220
224
 
221
- # pylint: disable=too-many-arguments,unused-argument,too-many-locals
225
+ # pylint: disable=too-many-arguments,unused-argument,too-many-locals,too-many-branches
222
226
  def start_vce(
223
227
  backend_name: str,
224
228
  backend_config_json_stream: str,
@@ -300,12 +304,14 @@ def start_vce(
300
304
  """Instantiate a Backend."""
301
305
  return backend_type(backend_config, work_dir=app_dir)
302
306
 
303
- log(INFO, "client_app_attr = %s", client_app_attr)
304
-
305
307
  # Load ClientApp if needed
306
308
  def _load() -> ClientApp:
307
309
 
308
310
  if client_app_attr:
311
+
312
+ if app_dir is not None:
313
+ sys.path.insert(0, app_dir)
314
+
309
315
  app: ClientApp = load_app(client_app_attr, LoadClientAppError)
310
316
 
311
317
  if not isinstance(app, ClientApp):
@@ -319,13 +325,23 @@ def start_vce(
319
325
 
320
326
  app_fn = _load
321
327
 
322
- asyncio.run(
323
- run(
324
- app_fn,
325
- backend_fn,
326
- nodes_mapping,
327
- state_factory,
328
- node_states,
329
- f_stop,
328
+ try:
329
+ # Test if ClientApp can be loaded
330
+ _ = app_fn()
331
+
332
+ # Run main simulation loop
333
+ asyncio.run(
334
+ run(
335
+ app_fn,
336
+ backend_fn,
337
+ nodes_mapping,
338
+ state_factory,
339
+ node_states,
340
+ f_stop,
341
+ )
330
342
  )
331
- )
343
+ except LoadClientAppError as loadapp_ex:
344
+ f_stop.set() # set termination event
345
+ raise loadapp_ex
346
+ except Exception as ex:
347
+ raise ex
@@ -493,13 +493,17 @@ class BasicActorPool:
493
493
  self._future_to_actor[future] = actor
494
494
  return future
495
495
 
496
+ async def add_actor_back_to_pool(self, future: Any) -> None:
497
+ """Ad actor assigned to run future back into the pool."""
498
+ actor = self._future_to_actor.pop(future)
499
+ await self.pool.put(actor)
500
+
496
501
  async def fetch_result_and_return_actor_to_pool(
497
502
  self, future: Any
498
503
  ) -> Tuple[Message, Context]:
499
504
  """Pull result given a future and add actor back to pool."""
500
505
  # Get actor that ran job
501
- actor = self._future_to_actor.pop(future)
502
- await self.pool.put(actor)
506
+ await self.add_actor_back_to_pool(future)
503
507
  # Retrieve result for object store
504
508
  # Instead of doing ray.get(future) we await it
505
509
  _, out_mssg, updated_context = await future
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: flwr-nightly
3
- Version: 1.8.0.dev20240327
3
+ Version: 1.8.0.dev20240328
4
4
  Summary: Flower: A Friendly Federated Learning Framework
5
5
  Home-page: https://flower.ai
6
6
  License: Apache-2.0
@@ -28,7 +28,7 @@ flwr/cli/run/__init__.py,sha256=oCd6HmQDx-sqver1gecgx-uMA38BLTSiiKpl7RGNceg,789
28
28
  flwr/cli/run/run.py,sha256=C7Yh-Y0f64PEabb9733jBKIhhOUFpcRmCZJIDtv-NG8,2329
29
29
  flwr/cli/utils.py,sha256=_V2BlFVNNG2naZrq227fZ8o4TxBN_hB-4fQsen9uQoo,2300
30
30
  flwr/client/__init__.py,sha256=futk_IdY_N1h8BTve4Iru51bxm7H1gv58ZPIXWi5XUA,1187
31
- flwr/client/app.py,sha256=xCCpP-fMEFdTEaSWOP93JPIDfjRhx5Z1uI1h6YlJteo,24784
31
+ flwr/client/app.py,sha256=bRrLIfn3MIZKfsVq8AlgYmhpI08UTioaOlyx6YLaLR4,25536
32
32
  flwr/client/client.py,sha256=Vp9UkOkoHdNfn6iMYZsj_5m_GICiFfUlKEVaLad-YhM,8183
33
33
  flwr/client/client_app.py,sha256=30Tl_AOEi4CE8wEQbKJ3tWg4GfbsSoV1Ztc8iWE0ge4,8047
34
34
  flwr/client/dpfedavg_numpy_client.py,sha256=9Tnig4iml2J88HBKNahegjXjbfvIQyBtaIQaqjbeqsA,7435
@@ -64,7 +64,7 @@ flwr/common/dp.py,sha256=Hc3lLHihjexbJaD_ft31gdv9XRcwOTgDBwJzICuok3A,2004
64
64
  flwr/common/exit_handlers.py,sha256=2Nt0wLhc17KQQsLPFSRAjjhUiEFfJK6tNozdGiIY4Fs,2812
65
65
  flwr/common/grpc.py,sha256=HimjpTtIY3Vfqtlq3u-CYWjqAl9rSn0uo3A8JjhUmwQ,2273
66
66
  flwr/common/logger.py,sha256=Plhm9fsi4ewb90eGALQZ9xBkR0cGEsckX5RLSMEaS3M,6118
67
- flwr/common/message.py,sha256=OsYdWVt5ovWTxiGyC3nav61GknoGUyH_p-zR6J-L0Dk,10532
67
+ flwr/common/message.py,sha256=vgFSCOkPbl60iS4-XQJ8-rHL54MvNc2AwNSSxVl6qYY,11773
68
68
  flwr/common/object_ref.py,sha256=ELoUCAFO-vbjJC41CGpa-WBG2SLYe3ErW-d9YCG3zqA,4961
69
69
  flwr/common/parameter.py,sha256=-bFAUayToYDF50FZGrBC1hQYJCQDtB2bbr3ZuVLMtdE,2095
70
70
  flwr/common/pyproject.py,sha256=EI_ovbCHGmhYrdPx0RSDi5EkFZFof-8m1PA54c0ZTjc,1385
@@ -126,7 +126,7 @@ flwr/server/client_proxy.py,sha256=4G-oTwhb45sfWLx2uZdcXD98IZwdTS6F88xe3akCdUg,2
126
126
  flwr/server/compat/__init__.py,sha256=VxnJtJyOjNFQXMNi9hIuzNlZM5n0Hj1p3aq_Pm2udw4,892
127
127
  flwr/server/compat/app.py,sha256=3Skh76Rg80B4oME1dJOhZvn9eTfVmTNIQ0jCiZ6CzeQ,5271
128
128
  flwr/server/compat/app_utils.py,sha256=-Ey5fyRpovmp4nHglVbliITcbxzxX_0qdtZwwfMS4ZI,3450
129
- flwr/server/compat/driver_client_proxy.py,sha256=otGgR_0KOadja4s0GO3zTAO0DoDGzzt6tUq0OHFS8gI,6719
129
+ flwr/server/compat/driver_client_proxy.py,sha256=QWLl5YJwI6NVADwjQGQJqkLtCfPNT-aRH0NF9yeGEnA,7344
130
130
  flwr/server/compat/legacy_context.py,sha256=D2s7PvQoDnTexuRmf1uG9Von7GUj4Qqyr7qLklSlKAM,1766
131
131
  flwr/server/criterion.py,sha256=ypbAexbztzGUxNen9RCHF91QeqiEQix4t4Ih3E-42MM,1061
132
132
  flwr/server/driver/__init__.py,sha256=yYyVX1FcDiDFM6rw0-DSZpuRy0EoWRfG9puwlQUswFA,820
@@ -180,8 +180,8 @@ flwr/server/superlink/fleet/rest_rere/rest_api.py,sha256=7JCs7NW4Qq8W5QhXxqsQNFi
180
180
  flwr/server/superlink/fleet/vce/__init__.py,sha256=36MHKiefnJeyjwMQzVUK4m06Ojon3WDcwZGQsAcyVhQ,783
181
181
  flwr/server/superlink/fleet/vce/backend/__init__.py,sha256=oBIzmnrSSRvH_H0vRGEGWhWzQQwqe3zn6e13RsNwlIY,1466
182
182
  flwr/server/superlink/fleet/vce/backend/backend.py,sha256=LJsKl7oixVvptcG98Rd9ejJycNWcEVB0ODvSreLGp-A,2260
183
- flwr/server/superlink/fleet/vce/backend/raybackend.py,sha256=BYgzVH1uz8nk6mOP6GhgSxjrdCe7xtkzb7nhPbKStFM,6317
184
- flwr/server/superlink/fleet/vce/vce_api.py,sha256=Yq4i9fduafnoWSHCLn0mmkCTS9oZqwycH8gbKa4bPXo,11168
183
+ flwr/server/superlink/fleet/vce/backend/raybackend.py,sha256=TaT2EpbVEsIY0EDzF8obadyZaSXjD38TFGdDPI-ytD0,6375
184
+ flwr/server/superlink/fleet/vce/vce_api.py,sha256=EV4ISvHZPucVLD3lYVFF5fQ4yyxmoaoZMdp_1B2k6J8,11789
185
185
  flwr/server/superlink/state/__init__.py,sha256=ij-7Ms-hyordQdRmGQxY1-nVa4OhixJ0jr7_YDkys0s,1003
186
186
  flwr/server/superlink/state/in_memory_state.py,sha256=XfdCGRzFut9xlf7AsDAhhAmBw-nKDBjmPWAI--espj0,8707
187
187
  flwr/server/superlink/state/sqlite_state.py,sha256=1SR6Zz6ud0tSSx940gTfa0vct_GH2n0cX_vnhoAEMlQ,22005
@@ -200,12 +200,12 @@ flwr/server/workflow/secure_aggregation/secaggplus_workflow.py,sha256=3TjJdhYA4x
200
200
  flwr/simulation/__init__.py,sha256=hpoKzdovrH0_Cf8HIcXxQxyUUb3BiSk-WUNLf5STHcc,1400
201
201
  flwr/simulation/app.py,sha256=WqJxdXTEuehwMW605p5NMmvBbKYx5tuqnV3Mp7jSWXM,13904
202
202
  flwr/simulation/ray_transport/__init__.py,sha256=FsaAnzC4cw4DqoouBCix6496k29jACkfeIam55BvW9g,734
203
- flwr/simulation/ray_transport/ray_actor.py,sha256=zRETW_xuCAOLRFaYnQ-q3IBSz0LIv_0RifGuhgWaYOg,19872
203
+ flwr/simulation/ray_transport/ray_actor.py,sha256=OWjgYW--fswkEDqTP9L_cZblBeUVL59vNz5gvzPAHFk,20054
204
204
  flwr/simulation/ray_transport/ray_client_proxy.py,sha256=oDu4sEPIOu39vrNi-fqDAe10xtNUXMO49bM2RWfRcyw,6738
205
205
  flwr/simulation/ray_transport/utils.py,sha256=TYdtfg1P9VfTdLMOJlifInGpxWHYs9UfUqIv2wfkRLA,2392
206
206
  flwr/simulation/run_simulation.py,sha256=HiIH6aa_v56NfKQN5ZBd94NyVfaZNyFs43_kItYsQXU,15685
207
- flwr_nightly-1.8.0.dev20240327.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
208
- flwr_nightly-1.8.0.dev20240327.dist-info/METADATA,sha256=k34dkRQ99eG3DkdjPCB3bEEllC1gMNxnYJBXZtHv6P4,15257
209
- flwr_nightly-1.8.0.dev20240327.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
210
- flwr_nightly-1.8.0.dev20240327.dist-info/entry_points.txt,sha256=utu2wybGyYJSTtsB2ktY_gmy-XtMFo9EFZdishX0zR4,320
211
- flwr_nightly-1.8.0.dev20240327.dist-info/RECORD,,
207
+ flwr_nightly-1.8.0.dev20240328.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
208
+ flwr_nightly-1.8.0.dev20240328.dist-info/METADATA,sha256=8FreyB01iNsm1LCt2zh74ZE46oAFcKAieUIrZQEtTgI,15257
209
+ flwr_nightly-1.8.0.dev20240328.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
210
+ flwr_nightly-1.8.0.dev20240328.dist-info/entry_points.txt,sha256=utu2wybGyYJSTtsB2ktY_gmy-XtMFo9EFZdishX0zR4,320
211
+ flwr_nightly-1.8.0.dev20240328.dist-info/RECORD,,