flwr-nightly 1.14.0.dev20241211__py3-none-any.whl → 1.14.0.dev20241212__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/new/new.py CHANGED
@@ -81,7 +81,7 @@ def render_template(template: str, data: dict[str, str]) -> str:
81
81
  def create_file(file_path: Path, content: str) -> None:
82
82
  """Create file including all nessecary directories and write content into file."""
83
83
  file_path.parent.mkdir(exist_ok=True)
84
- file_path.write_text(content)
84
+ file_path.write_text(content, encoding="utf-8")
85
85
 
86
86
 
87
87
  def render_and_create(file_path: Path, template: str, context: dict[str, str]) -> None:
@@ -10,8 +10,7 @@ license = "Apache-2.0"
10
10
  dependencies = [
11
11
  "flwr[simulation]>=1.13.1",
12
12
  "flwr-datasets[vision]>=0.3.0",
13
- "mlx==0.16.1",
14
- "numpy==1.24.4",
13
+ "mlx==0.21.1",
15
14
  ]
16
15
 
17
16
  [tool.hatch.build.targets.wheel]
@@ -136,7 +136,7 @@ class LocalDpMod:
136
136
  fit_res.parameters = ndarrays_to_parameters(client_to_server_params)
137
137
 
138
138
  # Add noise to model params
139
- add_localdp_gaussian_noise_to_params(
139
+ fit_res.parameters = add_localdp_gaussian_noise_to_params(
140
140
  fit_res.parameters, self.sensitivity, self.epsilon, self.delta
141
141
  )
142
142
 
flwr/common/object_ref.py CHANGED
@@ -21,6 +21,7 @@ import sys
21
21
  from importlib.util import find_spec
22
22
  from logging import WARN
23
23
  from pathlib import Path
24
+ from threading import Lock
24
25
  from typing import Any, Optional, Union
25
26
 
26
27
  from .logger import log
@@ -34,6 +35,7 @@ attribute.
34
35
 
35
36
 
36
37
  _current_sys_path: Optional[str] = None
38
+ _import_lock = Lock()
37
39
 
38
40
 
39
41
  def validate(
@@ -146,60 +148,61 @@ def load_app( # pylint: disable= too-many-branches
146
148
  - This function will modify `sys.path` by inserting the provided `project_dir`
147
149
  and removing the previously inserted `project_dir`.
148
150
  """
149
- valid, error_msg = validate(module_attribute_str, check_module=False)
150
- if not valid and error_msg:
151
- raise error_type(error_msg) from None
152
-
153
- module_str, _, attributes_str = module_attribute_str.partition(":")
154
-
155
- try:
156
- # Initialize project path
157
- if project_dir is None:
158
- project_dir = Path.cwd()
159
- project_dir = Path(project_dir).absolute()
160
-
161
- # Unload modules if the project directory has changed
162
- if _current_sys_path and _current_sys_path != str(project_dir):
163
- _unload_modules(Path(_current_sys_path))
164
-
165
- # Set the system path
166
- _set_sys_path(project_dir)
167
-
168
- # Import the module
169
- if module_str not in sys.modules:
170
- module = importlib.import_module(module_str)
171
- # Hack: `tabnet` does not work with `importlib.reload`
172
- elif "tabnet" in sys.modules:
173
- log(
174
- WARN,
175
- "Cannot reload module `%s` from disk due to compatibility issues "
176
- "with the `tabnet` library. The module will be loaded from the "
177
- "cache instead. If you experience issues, consider restarting "
178
- "the application.",
179
- module_str,
180
- )
181
- module = sys.modules[module_str]
182
- else:
183
- module = sys.modules[module_str]
184
- _reload_modules(project_dir)
185
-
186
- except ModuleNotFoundError as err:
187
- raise error_type(
188
- f"Unable to load module {module_str}{OBJECT_REF_HELP_STR}",
189
- ) from err
190
-
191
- # Recursively load attribute
192
- attribute = module
193
- try:
194
- for attribute_str in attributes_str.split("."):
195
- attribute = getattr(attribute, attribute_str)
196
- except AttributeError as err:
197
- raise error_type(
198
- f"Unable to load attribute {attributes_str} from module {module_str}"
199
- f"{OBJECT_REF_HELP_STR}",
200
- ) from err
201
-
202
- return attribute
151
+ with _import_lock:
152
+ valid, error_msg = validate(module_attribute_str, check_module=False)
153
+ if not valid and error_msg:
154
+ raise error_type(error_msg) from None
155
+
156
+ module_str, _, attributes_str = module_attribute_str.partition(":")
157
+
158
+ try:
159
+ # Initialize project path
160
+ if project_dir is None:
161
+ project_dir = Path.cwd()
162
+ project_dir = Path(project_dir).absolute()
163
+
164
+ # Unload modules if the project directory has changed
165
+ if _current_sys_path and _current_sys_path != str(project_dir):
166
+ _unload_modules(Path(_current_sys_path))
167
+
168
+ # Set the system path
169
+ _set_sys_path(project_dir)
170
+
171
+ # Import the module
172
+ if module_str not in sys.modules:
173
+ module = importlib.import_module(module_str)
174
+ # Hack: `tabnet` does not work with `importlib.reload`
175
+ elif "tabnet" in sys.modules:
176
+ log(
177
+ WARN,
178
+ "Cannot reload module `%s` from disk due to compatibility issues "
179
+ "with the `tabnet` library. The module will be loaded from the "
180
+ "cache instead. If you experience issues, consider restarting "
181
+ "the application.",
182
+ module_str,
183
+ )
184
+ module = sys.modules[module_str]
185
+ else:
186
+ module = sys.modules[module_str]
187
+ _reload_modules(project_dir)
188
+
189
+ except ModuleNotFoundError as err:
190
+ raise error_type(
191
+ f"Unable to load module {module_str}{OBJECT_REF_HELP_STR}",
192
+ ) from err
193
+
194
+ # Recursively load attribute
195
+ attribute = module
196
+ try:
197
+ for attribute_str in attributes_str.split("."):
198
+ attribute = getattr(attribute, attribute_str)
199
+ except AttributeError as err:
200
+ raise error_type(
201
+ f"Unable to load attribute {attributes_str} from module {module_str}"
202
+ f"{OBJECT_REF_HELP_STR}",
203
+ ) from err
204
+
205
+ return attribute
203
206
 
204
207
 
205
208
  def _unload_modules(project_dir: Path) -> None:
@@ -20,8 +20,17 @@ import random
20
20
  import time
21
21
  from collections.abc import Generator, Iterable
22
22
  from dataclasses import dataclass
23
+ from logging import INFO, WARN
23
24
  from typing import Any, Callable, Optional, Union, cast
24
25
 
26
+ import grpc
27
+
28
+ from flwr.common.constant import MAX_RETRY_DELAY
29
+ from flwr.common.logger import log
30
+ from flwr.common.typing import RunNotRunningException
31
+ from flwr.proto.clientappio_pb2_grpc import ClientAppIoStub
32
+ from flwr.proto.serverappio_pb2_grpc import ServerAppIoStub
33
+
25
34
 
26
35
  def exponential(
27
36
  base_delay: float = 1,
@@ -303,3 +312,69 @@ class RetryInvoker:
303
312
  # Trigger success event
304
313
  try_call_event_handler(self.on_success)
305
314
  return ret
315
+
316
+
317
+ def _make_simple_grpc_retry_invoker() -> RetryInvoker:
318
+ """Create a simple gRPC retry invoker."""
319
+
320
+ def _on_sucess(retry_state: RetryState) -> None:
321
+ if retry_state.tries > 1:
322
+ log(
323
+ INFO,
324
+ "Connection successful after %.2f seconds and %s tries.",
325
+ retry_state.elapsed_time,
326
+ retry_state.tries,
327
+ )
328
+
329
+ def _on_backoff(retry_state: RetryState) -> None:
330
+ if retry_state.tries == 1:
331
+ log(WARN, "Connection attempt failed, retrying...")
332
+ else:
333
+ log(
334
+ WARN,
335
+ "Connection attempt failed, retrying in %.2f seconds",
336
+ retry_state.actual_wait,
337
+ )
338
+
339
+ def _on_giveup(retry_state: RetryState) -> None:
340
+ if retry_state.tries > 1:
341
+ log(
342
+ WARN,
343
+ "Giving up reconnection after %.2f seconds and %s tries.",
344
+ retry_state.elapsed_time,
345
+ retry_state.tries,
346
+ )
347
+
348
+ def _should_giveup_fn(e: Exception) -> bool:
349
+ if e.code() == grpc.StatusCode.PERMISSION_DENIED: # type: ignore
350
+ raise RunNotRunningException
351
+ if e.code() == grpc.StatusCode.UNAVAILABLE: # type: ignore
352
+ return False
353
+ return True
354
+
355
+ return RetryInvoker(
356
+ wait_gen_factory=lambda: exponential(max_delay=MAX_RETRY_DELAY),
357
+ recoverable_exceptions=grpc.RpcError,
358
+ max_tries=None,
359
+ max_time=None,
360
+ on_success=_on_sucess,
361
+ on_backoff=_on_backoff,
362
+ on_giveup=_on_giveup,
363
+ should_giveup=_should_giveup_fn,
364
+ )
365
+
366
+
367
+ def _wrap_stub(
368
+ stub: Union[ServerAppIoStub, ClientAppIoStub], retry_invoker: RetryInvoker
369
+ ) -> None:
370
+ """Wrap a gRPC stub with a retry invoker."""
371
+
372
+ def make_lambda(original_method: Any) -> Any:
373
+ return lambda *args, **kwargs: retry_invoker.invoke(
374
+ original_method, *args, **kwargs
375
+ )
376
+
377
+ for method_name in vars(stub):
378
+ method = getattr(stub, method_name)
379
+ if callable(method):
380
+ setattr(stub, method_name, make_lambda(method))
flwr/common/typing.py CHANGED
@@ -254,3 +254,7 @@ class Fab:
254
254
 
255
255
  hash_str: str
256
256
  content: bytes
257
+
258
+
259
+ class RunNotRunningException(BaseException):
260
+ """Raised when a run is not running."""
flwr/proto/fab_pb2.py CHANGED
@@ -15,7 +15,7 @@ _sym_db = _symbol_database.Default()
15
15
  from flwr.proto import node_pb2 as flwr_dot_proto_dot_node__pb2
16
16
 
17
17
 
18
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14\x66lwr/proto/fab.proto\x12\nflwr.proto\x1a\x15\x66lwr/proto/node.proto\"(\n\x03\x46\x61\x62\x12\x10\n\x08hash_str\x18\x01 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\x0c\"A\n\rGetFabRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12\x10\n\x08hash_str\x18\x02 \x01(\t\".\n\x0eGetFabResponse\x12\x1c\n\x03\x66\x61\x62\x18\x01 \x01(\x0b\x32\x0f.flwr.proto.Fabb\x06proto3')
18
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14\x66lwr/proto/fab.proto\x12\nflwr.proto\x1a\x15\x66lwr/proto/node.proto\"(\n\x03\x46\x61\x62\x12\x10\n\x08hash_str\x18\x01 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\x0c\"Q\n\rGetFabRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12\x10\n\x08hash_str\x18\x02 \x01(\t\x12\x0e\n\x06run_id\x18\x03 \x01(\x04\".\n\x0eGetFabResponse\x12\x1c\n\x03\x66\x61\x62\x18\x01 \x01(\x0b\x32\x0f.flwr.proto.Fabb\x06proto3')
19
19
 
20
20
  _globals = globals()
21
21
  _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -25,7 +25,7 @@ if _descriptor._USE_C_DESCRIPTORS == False:
25
25
  _globals['_FAB']._serialized_start=59
26
26
  _globals['_FAB']._serialized_end=99
27
27
  _globals['_GETFABREQUEST']._serialized_start=101
28
- _globals['_GETFABREQUEST']._serialized_end=166
29
- _globals['_GETFABRESPONSE']._serialized_start=168
30
- _globals['_GETFABRESPONSE']._serialized_end=214
28
+ _globals['_GETFABREQUEST']._serialized_end=182
29
+ _globals['_GETFABRESPONSE']._serialized_start=184
30
+ _globals['_GETFABRESPONSE']._serialized_end=230
31
31
  # @@protoc_insertion_point(module_scope)
flwr/proto/fab_pb2.pyi CHANGED
@@ -36,16 +36,19 @@ class GetFabRequest(google.protobuf.message.Message):
36
36
  DESCRIPTOR: google.protobuf.descriptor.Descriptor
37
37
  NODE_FIELD_NUMBER: builtins.int
38
38
  HASH_STR_FIELD_NUMBER: builtins.int
39
+ RUN_ID_FIELD_NUMBER: builtins.int
39
40
  @property
40
41
  def node(self) -> flwr.proto.node_pb2.Node: ...
41
42
  hash_str: typing.Text
43
+ run_id: builtins.int
42
44
  def __init__(self,
43
45
  *,
44
46
  node: typing.Optional[flwr.proto.node_pb2.Node] = ...,
45
47
  hash_str: typing.Text = ...,
48
+ run_id: builtins.int = ...,
46
49
  ) -> None: ...
47
50
  def HasField(self, field_name: typing_extensions.Literal["node",b"node"]) -> builtins.bool: ...
48
- def ClearField(self, field_name: typing_extensions.Literal["hash_str",b"hash_str","node",b"node"]) -> None: ...
51
+ def ClearField(self, field_name: typing_extensions.Literal["hash_str",b"hash_str","node",b"node","run_id",b"run_id"]) -> None: ...
49
52
  global___GetFabRequest = GetFabRequest
50
53
 
51
54
  class GetFabResponse(google.protobuf.message.Message):
@@ -17,6 +17,8 @@
17
17
 
18
18
  import threading
19
19
 
20
+ from flwr.common.typing import RunNotRunningException
21
+
20
22
  from ..client_manager import ClientManager
21
23
  from ..compat.driver_client_proxy import DriverClientProxy
22
24
  from ..driver import Driver
@@ -74,7 +76,11 @@ def _update_client_manager(
74
76
  # Loop until the driver is disconnected
75
77
  registered_nodes: dict[int, DriverClientProxy] = {}
76
78
  while not f_stop.is_set():
77
- all_node_ids = set(driver.get_node_ids())
79
+ try:
80
+ all_node_ids = set(driver.get_node_ids())
81
+ except RunNotRunningException:
82
+ f_stop.set()
83
+ break
78
84
  dead_nodes = set(registered_nodes).difference(all_node_ids)
79
85
  new_nodes = all_node_ids.difference(registered_nodes)
80
86
 
@@ -17,16 +17,16 @@
17
17
  import time
18
18
  import warnings
19
19
  from collections.abc import Iterable
20
- from logging import DEBUG, INFO, WARN, WARNING
21
- from typing import Any, Optional, cast
20
+ from logging import DEBUG, WARNING
21
+ from typing import Optional, cast
22
22
 
23
23
  import grpc
24
24
 
25
25
  from flwr.common import DEFAULT_TTL, Message, Metadata, RecordSet
26
- from flwr.common.constant import MAX_RETRY_DELAY, SERVERAPPIO_API_DEFAULT_CLIENT_ADDRESS
26
+ from flwr.common.constant import SERVERAPPIO_API_DEFAULT_CLIENT_ADDRESS
27
27
  from flwr.common.grpc import create_channel
28
28
  from flwr.common.logger import log
29
- from flwr.common.retry_invoker import RetryInvoker, RetryState, exponential
29
+ from flwr.common.retry_invoker import _make_simple_grpc_retry_invoker, _wrap_stub
30
30
  from flwr.common.serde import message_from_taskres, message_to_taskins, run_from_proto
31
31
  from flwr.common.typing import Run
32
32
  from flwr.proto.node_pb2 import Node # pylint: disable=E0611
@@ -262,60 +262,3 @@ class GrpcDriver(Driver):
262
262
  return
263
263
  # Disconnect
264
264
  self._disconnect()
265
-
266
-
267
- def _make_simple_grpc_retry_invoker() -> RetryInvoker:
268
- """Create a simple gRPC retry invoker."""
269
-
270
- def _on_sucess(retry_state: RetryState) -> None:
271
- if retry_state.tries > 1:
272
- log(
273
- INFO,
274
- "Connection successful after %.2f seconds and %s tries.",
275
- retry_state.elapsed_time,
276
- retry_state.tries,
277
- )
278
-
279
- def _on_backoff(retry_state: RetryState) -> None:
280
- if retry_state.tries == 1:
281
- log(WARN, "Connection attempt failed, retrying...")
282
- else:
283
- log(
284
- WARN,
285
- "Connection attempt failed, retrying in %.2f seconds",
286
- retry_state.actual_wait,
287
- )
288
-
289
- def _on_giveup(retry_state: RetryState) -> None:
290
- if retry_state.tries > 1:
291
- log(
292
- WARN,
293
- "Giving up reconnection after %.2f seconds and %s tries.",
294
- retry_state.elapsed_time,
295
- retry_state.tries,
296
- )
297
-
298
- return RetryInvoker(
299
- wait_gen_factory=lambda: exponential(max_delay=MAX_RETRY_DELAY),
300
- recoverable_exceptions=grpc.RpcError,
301
- max_tries=None,
302
- max_time=None,
303
- on_success=_on_sucess,
304
- on_backoff=_on_backoff,
305
- on_giveup=_on_giveup,
306
- should_giveup=lambda e: e.code() != grpc.StatusCode.UNAVAILABLE, # type: ignore
307
- )
308
-
309
-
310
- def _wrap_stub(stub: ServerAppIoStub, retry_invoker: RetryInvoker) -> None:
311
- """Wrap the gRPC stub with a retry invoker."""
312
-
313
- def make_lambda(original_method: Any) -> Any:
314
- return lambda *args, **kwargs: retry_invoker.invoke(
315
- original_method, *args, **kwargs
316
- )
317
-
318
- for method_name in vars(stub):
319
- method = getattr(stub, method_name)
320
- if callable(method):
321
- setattr(stub, method_name, make_lambda(method))
@@ -50,7 +50,7 @@ from flwr.common.serde import (
50
50
  run_from_proto,
51
51
  run_status_to_proto,
52
52
  )
53
- from flwr.common.typing import RunStatus
53
+ from flwr.common.typing import RunNotRunningException, RunStatus
54
54
  from flwr.proto.run_pb2 import UpdateRunStatusRequest # pylint: disable=E0611
55
55
  from flwr.proto.serverappio_pb2 import ( # pylint: disable=E0611
56
56
  PullServerAppInputsRequest,
@@ -96,7 +96,7 @@ def flwr_serverapp() -> None:
96
96
  restore_output()
97
97
 
98
98
 
99
- def run_serverapp( # pylint: disable=R0914, disable=W0212
99
+ def run_serverapp( # pylint: disable=R0914, disable=W0212, disable=R0915
100
100
  serverappio_api_address: str,
101
101
  log_queue: Queue[Optional[str]],
102
102
  run_once: bool,
@@ -187,6 +187,12 @@ def run_serverapp( # pylint: disable=R0914, disable=W0212
187
187
 
188
188
  run_status = RunStatus(Status.FINISHED, SubStatus.COMPLETED, "")
189
189
 
190
+ except RunNotRunningException:
191
+ log(INFO, "")
192
+ log(INFO, "Run ID %s stopped.", run.run_id)
193
+ log(INFO, "")
194
+ run_status = None
195
+
190
196
  except Exception as ex: # pylint: disable=broad-exception-caught
191
197
  exc_entity = "ServerApp"
192
198
  log(ERROR, "%s raised an exception", exc_entity, exc_info=ex)
@@ -70,6 +70,7 @@ from flwr.proto.task_pb2 import TaskRes # pylint: disable=E0611
70
70
  from flwr.server.superlink.ffs.ffs import Ffs
71
71
  from flwr.server.superlink.ffs.ffs_factory import FfsFactory
72
72
  from flwr.server.superlink.linkstate import LinkState, LinkStateFactory
73
+ from flwr.server.superlink.utils import abort_if
73
74
  from flwr.server.utils.validator import validate_task_ins_or_res
74
75
 
75
76
 
@@ -88,7 +89,18 @@ class ServerAppIoServicer(serverappio_pb2_grpc.ServerAppIoServicer):
88
89
  ) -> GetNodesResponse:
89
90
  """Get available nodes."""
90
91
  log(DEBUG, "ServerAppIoServicer.GetNodes")
92
+
93
+ # Init state
91
94
  state: LinkState = self.state_factory.state()
95
+
96
+ # Abort if the run is not running
97
+ abort_if(
98
+ request.run_id,
99
+ [Status.PENDING, Status.STARTING, Status.FINISHED],
100
+ state,
101
+ context,
102
+ )
103
+
92
104
  all_ids: set[int] = state.get_nodes(request.run_id)
93
105
  nodes: list[Node] = [
94
106
  Node(node_id=node_id, anonymous=False) for node_id in all_ids
@@ -126,6 +138,17 @@ class ServerAppIoServicer(serverappio_pb2_grpc.ServerAppIoServicer):
126
138
  """Push a set of TaskIns."""
127
139
  log(DEBUG, "ServerAppIoServicer.PushTaskIns")
128
140
 
141
+ # Init state
142
+ state: LinkState = self.state_factory.state()
143
+
144
+ # Abort if the run is not running
145
+ abort_if(
146
+ request.run_id,
147
+ [Status.PENDING, Status.STARTING, Status.FINISHED],
148
+ state,
149
+ context,
150
+ )
151
+
129
152
  # Set pushed_at (timestamp in seconds)
130
153
  pushed_at = time.time()
131
154
  for task_ins in request.task_ins_list:
@@ -137,9 +160,6 @@ class ServerAppIoServicer(serverappio_pb2_grpc.ServerAppIoServicer):
137
160
  validation_errors = validate_task_ins_or_res(task_ins)
138
161
  _raise_if(bool(validation_errors), ", ".join(validation_errors))
139
162
 
140
- # Init state
141
- state: LinkState = self.state_factory.state()
142
-
143
163
  # Store each TaskIns
144
164
  task_ids: list[Optional[UUID]] = []
145
165
  for task_ins in request.task_ins_list:
@@ -156,12 +176,20 @@ class ServerAppIoServicer(serverappio_pb2_grpc.ServerAppIoServicer):
156
176
  """Pull a set of TaskRes."""
157
177
  log(DEBUG, "ServerAppIoServicer.PullTaskRes")
158
178
 
159
- # Convert each task_id str to UUID
160
- task_ids: set[UUID] = {UUID(task_id) for task_id in request.task_ids}
161
-
162
179
  # Init state
163
180
  state: LinkState = self.state_factory.state()
164
181
 
182
+ # Abort if the run is not running
183
+ abort_if(
184
+ request.run_id,
185
+ [Status.PENDING, Status.STARTING, Status.FINISHED],
186
+ state,
187
+ context,
188
+ )
189
+
190
+ # Convert each task_id str to UUID
191
+ task_ids: set[UUID] = {UUID(task_id) for task_id in request.task_ids}
192
+
165
193
  # Register callback
166
194
  def on_rpc_done() -> None:
167
195
  log(
@@ -258,7 +286,18 @@ class ServerAppIoServicer(serverappio_pb2_grpc.ServerAppIoServicer):
258
286
  ) -> PushServerAppOutputsResponse:
259
287
  """Push ServerApp process outputs."""
260
288
  log(DEBUG, "ServerAppIoServicer.PushServerAppOutputs")
289
+
290
+ # Init state
261
291
  state = self.state_factory.state()
292
+
293
+ # Abort if the run is not running
294
+ abort_if(
295
+ request.run_id,
296
+ [Status.PENDING, Status.STARTING, Status.FINISHED],
297
+ state,
298
+ context,
299
+ )
300
+
262
301
  state.set_serverapp_context(request.run_id, context_from_proto(request.context))
263
302
  return PushServerAppOutputsResponse()
264
303
 
@@ -267,8 +306,13 @@ class ServerAppIoServicer(serverappio_pb2_grpc.ServerAppIoServicer):
267
306
  ) -> UpdateRunStatusResponse:
268
307
  """Update the status of a run."""
269
308
  log(DEBUG, "ServerAppIoServicer.UpdateRunStatus")
309
+
310
+ # Init state
270
311
  state = self.state_factory.state()
271
312
 
313
+ # Abort if the run is finished
314
+ abort_if(request.run_id, [Status.FINISHED], state, context)
315
+
272
316
  # Update the run status
273
317
  state.update_run_status(
274
318
  run_id=request.run_id, new_status=run_status_from_proto(request.run_status)
@@ -0,0 +1,65 @@
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
+ """SuperLink utilities."""
16
+
17
+
18
+ from typing import Union
19
+
20
+ import grpc
21
+
22
+ from flwr.common.constant import Status, SubStatus
23
+ from flwr.common.typing import RunStatus
24
+ from flwr.server.superlink.linkstate import LinkState
25
+
26
+ _STATUS_TO_MSG = {
27
+ Status.PENDING: "Run is pending.",
28
+ Status.STARTING: "Run is starting.",
29
+ Status.RUNNING: "Run is running.",
30
+ Status.FINISHED: "Run is finished.",
31
+ }
32
+
33
+
34
+ def check_abort(
35
+ run_id: int,
36
+ abort_status_list: list[str],
37
+ state: LinkState,
38
+ ) -> Union[str, None]:
39
+ """Check if the status of the provided `run_id` is in `abort_status_list`."""
40
+ run_status: RunStatus = state.get_run_status({run_id})[run_id]
41
+
42
+ if run_status.status in abort_status_list:
43
+ msg = _STATUS_TO_MSG[run_status.status]
44
+ if run_status.sub_status == SubStatus.STOPPED:
45
+ msg += " Stopped by user."
46
+ return msg
47
+
48
+ return None
49
+
50
+
51
+ def abort_grpc_context(msg: Union[str, None], context: grpc.ServicerContext) -> None:
52
+ """Abort context with statuscode PERMISSION_DENIED if `msg` is not None."""
53
+ if msg is not None:
54
+ context.abort(grpc.StatusCode.PERMISSION_DENIED, msg)
55
+
56
+
57
+ def abort_if(
58
+ run_id: int,
59
+ abort_status_list: list[str],
60
+ state: LinkState,
61
+ context: grpc.ServicerContext,
62
+ ) -> None:
63
+ """Abort context if status of the provided `run_id` is in `abort_status_list`."""
64
+ msg = check_abort(run_id, abort_status_list, state)
65
+ abort_grpc_context(msg, context)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: flwr-nightly
3
- Version: 1.14.0.dev20241211
3
+ Version: 1.14.0.dev20241212
4
4
  Summary: Flower: A Friendly Federated AI Framework
5
5
  Home-page: https://flower.ai
6
6
  License: Apache-2.0
@@ -11,7 +11,7 @@ flwr/cli/login/__init__.py,sha256=PEh6QjLSx7ltN8d8Jfi25dHFPKtCNKjYJZCkYQBfmm0,79
11
11
  flwr/cli/login/login.py,sha256=GVm6rkLDVQ6WuT2mw52gBKNW_Y5IjGg_OOGoEmpx9KM,2796
12
12
  flwr/cli/ls.py,sha256=5uO0YG0XXn7paS4oUs1T7rwicApxMV3ac9ejBZfLN3k,10545
13
13
  flwr/cli/new/__init__.py,sha256=cQzK1WH4JP2awef1t2UQ2xjl1agVEz9rwutV18SWV1k,789
14
- flwr/cli/new/new.py,sha256=xgzObnhNpnGvjVs6wTj6BlJ9X-avPhRX3DuwWnk9ED0,9903
14
+ flwr/cli/new/new.py,sha256=AoGfQl_IPN6LwZBYPgRAMgME5BODsL3n1OtErSEVVkc,9921
15
15
  flwr/cli/new/templates/__init__.py,sha256=4luU8RL-CK8JJCstQ_ON809W9bNTkY1l9zSaPKBkgwY,725
16
16
  flwr/cli/new/templates/app/.gitignore.tpl,sha256=XixnHdyeMB2vwkGtGnwHqoWpH-9WChdyG0GXe57duhc,3078
17
17
  flwr/cli/new/templates/app/LICENSE.tpl,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
@@ -59,7 +59,7 @@ flwr/cli/new/templates/app/pyproject.baseline.toml.tpl,sha256=_bT_ze1QPajyFZW0Ax
59
59
  flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl,sha256=WHLdFbHhfs3ZmLCG5pa5TUKFoRV67TT1J1SXMM0zStY,1873
60
60
  flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl,sha256=_LSCSc9cAiRuvTLdsy9wphwzhV7FCOUxO1ce12YOk58,1143
61
61
  flwr/cli/new/templates/app/pyproject.jax.toml.tpl,sha256=8xC1457V13AxTsO7SaLsqhQQPN7Aau3wbNZqKJ9Inx8,673
62
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl,sha256=834E6VDgomPGTNRUHjfi3wWaJpwE0xaL_qPgEZbqEJc,765
62
+ flwr/cli/new/templates/app/pyproject.mlx.toml.tpl,sha256=sJMPYaroZLM7EkIX5ulnCelKxHlpViYiSqhswEnGrB0,744
63
63
  flwr/cli/new/templates/app/pyproject.numpy.toml.tpl,sha256=SbCIUjwCIsgTRoBb-GMwivcWdoigMvD3QbKL6TJNgWM,612
64
64
  flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl,sha256=UtH3Vslg2S8fIKIHC-dJGcxz5YUK2WI3F2TUAgTsQn0,710
65
65
  flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl,sha256=01HArBqRrbZT3O7pXOM9MqduXMNm525wv7Sj6dvYMJE,686
@@ -92,7 +92,7 @@ flwr/client/message_handler/task_handler.py,sha256=ZDJBKmrn2grRMNl1rU1iGs7FiMHL5
92
92
  flwr/client/mod/__init__.py,sha256=37XeXZLFq_tzFVKVtC9JaigM2bSAU7BrGQvMPCE3Q28,1159
93
93
  flwr/client/mod/centraldp_mods.py,sha256=UGwNuqpmOWfLdfJITFgdi1TG-nLjuSb-cbEyoyfDgxQ,5415
94
94
  flwr/client/mod/comms_mods.py,sha256=QzJF7lgbYGnZvY805rkBfDsYCRC0HBHeDkJQ_JXhUZY,2624
95
- flwr/client/mod/localdp_mod.py,sha256=SBDhW71vY6lEU_lQNOySLUWypkNwUwuHAtbBErOarpM,4982
95
+ flwr/client/mod/localdp_mod.py,sha256=Zhcu2M1QYCaS0dfmTjkhmFABIJcFXfT6zDgV0o9sn-4,5003
96
96
  flwr/client/mod/secure_aggregation/__init__.py,sha256=A7DzZ3uvXTUkuHBzrxJMWQQD4RtO_PsVA53yHc4oWco,849
97
97
  flwr/client/mod/secure_aggregation/secagg_mod.py,sha256=wI9tuIEvMUETz-wVIEbPYvh-1nK9CEylBLGoVpNhL94,1095
98
98
  flwr/client/mod/secure_aggregation/secaggplus_mod.py,sha256=7cNXsY07ZA0M5_9VSc52F8JUoAoGaraNDA2rgaLvvFo,19680
@@ -124,7 +124,7 @@ flwr/common/exit_handlers.py,sha256=MracJaBeoCOC7TaXK9zCJQxhrMSx9ZtczK237qvhBpU,
124
124
  flwr/common/grpc.py,sha256=AIPMAHsvcTlduaYKCgnoBnst1A7RZEgGqh0Ulm7qfJ0,2621
125
125
  flwr/common/logger.py,sha256=NQkdrtAP3NFTH_ebTTrjD2z6y-1bdoiIx9_npC-1TWw,11940
126
126
  flwr/common/message.py,sha256=4O1m0OWXBAYZz05gKgEtnoJ94J1gjo7hCNHyUXThxRo,13831
127
- flwr/common/object_ref.py,sha256=DavEkh-IJv_s0VeLsJvSZS5k-Ix_k1UcNXbldfNFXxM,9859
127
+ flwr/common/object_ref.py,sha256=fIXf8aP5mG6Nuni7dvcKK5Di3zRfRWGs4ljvqIXplds,10115
128
128
  flwr/common/parameter.py,sha256=-bFAUayToYDF50FZGrBC1hQYJCQDtB2bbr3ZuVLMtdE,2095
129
129
  flwr/common/pyproject.py,sha256=EI_ovbCHGmhYrdPx0RSDi5EkFZFof-8m1PA54c0ZTjc,1385
130
130
  flwr/common/record/__init__.py,sha256=ejDBQOIA0OkwZAC5cK_tTPHA4oAM0Ju7Oi13-NneMlE,1054
@@ -135,7 +135,7 @@ flwr/common/record/parametersrecord.py,sha256=IjnewX8Ea6JXLRWcPMVole2sNjwzRVjBVv
135
135
  flwr/common/record/recordset.py,sha256=sSofrBycZSqiHR4TzfI4_QoIIN-5B1LnMG0C9CiByAo,8312
136
136
  flwr/common/record/typeddict.py,sha256=q5hL2xkXymuiCprHWb69mUmLpWQk_XXQq0hGQ69YPaw,3599
137
137
  flwr/common/recordset_compat.py,sha256=ViSwA26h6Q55ZmV1LLjSJpcKiipV-p_JpCj4wxdE-Ow,14230
138
- flwr/common/retry_invoker.py,sha256=u5dHcRMoyS8ABL3Fjk4P5P1lgRYYa1edfLGzWXxwyAc,11969
138
+ flwr/common/retry_invoker.py,sha256=nCA-dfBw6YoWkOgop71QfhTDmYj1JIgXsdpzlyqgZK4,14396
139
139
  flwr/common/secure_aggregation/__init__.py,sha256=erPnTWdOfMH0K0HQTmj5foDJ6t3iYcExy2aACy8iZNQ,731
140
140
  flwr/common/secure_aggregation/crypto/__init__.py,sha256=nlHesCWy8xxE5s6qHWnauCtyClcMQ2K0CEXAHakY5n0,738
141
141
  flwr/common/secure_aggregation/crypto/shamir.py,sha256=wCSfEfeaPgJ9Om580-YPUF2ljiyRhq33TRC4HtwxYl8,2779
@@ -146,7 +146,7 @@ flwr/common/secure_aggregation/secaggplus_constants.py,sha256=9MF-oQh62uD7rt9VeN
146
146
  flwr/common/secure_aggregation/secaggplus_utils.py,sha256=OgYd68YBRaHQYLc-YdExj9CSpwL58bVTaPrdHoAj2AE,3214
147
147
  flwr/common/serde.py,sha256=K9ExsqcTPETESkt2HMaNtIQAIAfwmuwtJFlG-59I7Sw,31046
148
148
  flwr/common/telemetry.py,sha256=CHIwFFQ13sWFavmEvkvA43XR1sbh1S3nWvD5TuCO2eI,8774
149
- flwr/common/typing.py,sha256=RLq2f9jhE_Nndtk023cPMG0LpoQHaacEsww-3j0xs4Q,5710
149
+ flwr/common/typing.py,sha256=Ux8rJllzqORzCiv9HYkqVVyEzmd3nOKbcmttj5d2P_I,5801
150
150
  flwr/common/version.py,sha256=tCcl_FvxVK206C1dxIJCs4TjL06WmyaODBP19FRHE1c,1324
151
151
  flwr/proto/__init__.py,sha256=hbY7JYakwZwCkYgCNlmHdc8rtvfoJbAZLalMdc--CGc,683
152
152
  flwr/proto/clientappio_pb2.py,sha256=Y3PMv-JMaBGehpslgbvGY6l2u5vNpfCTFWu-fmAmBJ4,3703
@@ -161,8 +161,8 @@ flwr/proto/exec_pb2.py,sha256=IVqmpzzThSjuLBCF8T9VofTpnUXtp3SYWOEp8dzyv5o,6883
161
161
  flwr/proto/exec_pb2.pyi,sha256=amt-3e3zJVjkRlQ8Gz6m1A7hXyeZmbQhHpAEIQyIDn0,10660
162
162
  flwr/proto/exec_pb2_grpc.py,sha256=-bdLqjsqQxK9R8LIiZaKlLKH2NmjR50EaGKTPPTwFhI,10445
163
163
  flwr/proto/exec_pb2_grpc.pyi,sha256=M5k-FzeLWxal7zt28LJfzMWWRxmNknTC2BzHRRMa1sQ,2914
164
- flwr/proto/fab_pb2.py,sha256=3QSDq9pjbZoqVxsmCRDwHO5PrSjzn2vixjYxE-qPmb0,1589
165
- flwr/proto/fab_pb2.pyi,sha256=fXI108QaFtbl1WWTyslPbIx9c_19D0aYCoFn0xYtL4U,2277
164
+ flwr/proto/fab_pb2.py,sha256=-gfW_ePYHx1vDGHfimwn91qEhmmY_gslaOHwqqZnVdU,1627
165
+ flwr/proto/fab_pb2.pyi,sha256=AMXpiDK0fo3nZWjxsC2E4otSaVjyQbU7iiWKrsSZavs,2395
166
166
  flwr/proto/fab_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
167
167
  flwr/proto/fab_pb2_grpc.pyi,sha256=ff2TSiLVnG6IVQcTGzb2DIH3XRSoAvAo_RMcvbMFyc0,76
168
168
  flwr/proto/fleet_pb2.py,sha256=06NAaIAOxTA2UhkBA-VWZKflaVQIzXgPZ3Fb6vtliY0,4789
@@ -216,13 +216,13 @@ flwr/server/client_manager.py,sha256=7Ese0tgrH-i-ms363feYZJKwB8gWnXSmg_hYF2Bju4U
216
216
  flwr/server/client_proxy.py,sha256=4G-oTwhb45sfWLx2uZdcXD98IZwdTS6F88xe3akCdUg,2399
217
217
  flwr/server/compat/__init__.py,sha256=VxnJtJyOjNFQXMNi9hIuzNlZM5n0Hj1p3aq_Pm2udw4,892
218
218
  flwr/server/compat/app.py,sha256=5vkHHm_h-4cMthvWD1GJo1ZW3eihytjGgvsgfXUK9gA,3298
219
- flwr/server/compat/app_utils.py,sha256=i8MseZQculltLTsRIEe4XUnmAuu3LF3WzGjYi0c-cps,3425
219
+ flwr/server/compat/app_utils.py,sha256=ha1K9h4KfM80-Bcluwa_LtBfQ5g643Eb_QfaAm-GmTU,3579
220
220
  flwr/server/compat/driver_client_proxy.py,sha256=Af0bRUEVZNcCYRxt3DjpLPdvVYpTgz6LSlILtI_8DQY,5010
221
221
  flwr/server/compat/legacy_context.py,sha256=wBzBcfV6YO6IQGriM_FdJ5XZfiBBEEJdS_OdAiF47dY,1804
222
222
  flwr/server/criterion.py,sha256=ypbAexbztzGUxNen9RCHF91QeqiEQix4t4Ih3E-42MM,1061
223
223
  flwr/server/driver/__init__.py,sha256=bikRv6CjTwSvYh7tf10gziU5o2YotOWhhftz2tr3KDc,886
224
224
  flwr/server/driver/driver.py,sha256=u_fMfqLYTroTafGCNwKPHI4lttRL-Z5CqeT3_FHSq-Q,5701
225
- flwr/server/driver/grpc_driver.py,sha256=cMYtyQJRSwfhCmtJ5UEWN4iXrUKRH5iGXmigiU6sGjM,11529
225
+ flwr/server/driver/grpc_driver.py,sha256=KXe_zlwwzgnawkeYFNVo8Tq45CGGmMFBAerqxso-s-E,9635
226
226
  flwr/server/driver/inmemory_driver.py,sha256=gfB4jmkk1indhRa9XCdKCXghVcWBF1qBD-tAxMUyQm0,6404
227
227
  flwr/server/history.py,sha256=qSb5_pPTrwofpSYGsZWzMPkl_4uJ4mJFWesxXDrEvDU,5026
228
228
  flwr/server/run_serverapp.py,sha256=oDfHaHyVT5BRcckFFQKg8AVPCWR1ek7OhNceTC8qq9g,2493
@@ -230,7 +230,7 @@ flwr/server/server.py,sha256=1ZsFEptmAV-L2vP2etNC9Ed5CLSxpuKzUFkAPQ4l5Xc,17893
230
230
  flwr/server/server_app.py,sha256=RsgS6PRS5Z74cMUAHzsm8r3LWddwn00MjRs6rlacHt8,6297
231
231
  flwr/server/server_config.py,sha256=CZaHVAsMvGLjpWVcLPkiYxgJN4xfIyAiUrCI3fETKY4,1349
232
232
  flwr/server/serverapp/__init__.py,sha256=L0K-94UDdTyEZ8LDtYybGIIIv3HW6AhSVjXMUfYJQnQ,800
233
- flwr/server/serverapp/app.py,sha256=wYs5H8TU7egSB32DzDnJCUHmcli201e7MlWmhkOgn30,7693
233
+ flwr/server/serverapp/app.py,sha256=E35c-Ic8l9gH463nRBQlMxamJ72Ng-ka4jGWDkwlK_U,7910
234
234
  flwr/server/serverapp_components.py,sha256=-IV_CitOfrJclJj2jNdbN1Q65PyFmtKtrTIg1hc6WQw,2118
235
235
  flwr/server/strategy/__init__.py,sha256=tQer2SwjDnvgFFuJMZM-S01Z615N5XK6MaCvpm4BMU0,2836
236
236
  flwr/server/strategy/aggregate.py,sha256=PDvekufza13s9AsVmz9WASunaBs3yCtl8JVliFx9j6Q,13978
@@ -259,7 +259,7 @@ flwr/server/strategy/strategy.py,sha256=cXapkD5uDrt5C-RbmWDn9FLoap3Q41i7GKvbmfbC
259
259
  flwr/server/superlink/__init__.py,sha256=8tHYCfodUlRD8PCP9fHgvu8cz5N31A2QoRVL0jDJ15E,707
260
260
  flwr/server/superlink/driver/__init__.py,sha256=5soEK5QSvxNjmJQ-CGTWROc4alSAeU0e9Ad9RDhsd3E,717
261
261
  flwr/server/superlink/driver/serverappio_grpc.py,sha256=oTogZLkfeThKdx9Q_bw6OMGHnLIryxQOHxbWi0qgaRM,2185
262
- flwr/server/superlink/driver/serverappio_servicer.py,sha256=MSJPcSDim36sXPoK21XmhHYZwWI-i9Z5NiZrvyBRJyc,11124
262
+ flwr/server/superlink/driver/serverappio_servicer.py,sha256=As8Ow1Dmv4tTiuCMTtokE66kGZr8BTN0fd6EgT_XkLs,12161
263
263
  flwr/server/superlink/ffs/__init__.py,sha256=FAY-zShcfPmOxosok2QyT6hTNMNctG8cH9s_nIl8jkI,840
264
264
  flwr/server/superlink/ffs/disk_ffs.py,sha256=yCN6CCzegnJIOaHr5nIu49wZQa4g5BByiSKshz50RKU,3296
265
265
  flwr/server/superlink/ffs/ffs.py,sha256=qLI1UfosJugu2BKOJWqHIhafTm-YiuKqGf3OGWPH0NM,2395
@@ -293,6 +293,7 @@ flwr/server/superlink/linkstate/utils.py,sha256=d5uqqIOCKfd54X8CFNfUr3AWqPLpgmzs
293
293
  flwr/server/superlink/simulation/__init__.py,sha256=mg-oapC9dkzEfjXPQFior5lpWj4g9kwbLovptyYM_g0,718
294
294
  flwr/server/superlink/simulation/simulationio_grpc.py,sha256=5wflYW_TS0mjmPG6OYuHMJwXD2_cYmUNhFkdOU0jMWQ,2237
295
295
  flwr/server/superlink/simulation/simulationio_servicer.py,sha256=riaZm090aTs7o8cFD8gvCWkX7A2SPLXKM4K8MG60av8,6545
296
+ flwr/server/superlink/utils.py,sha256=KVb3K_g2vYfu9TnftcN0ewmev133WZcjuEePMm8d7GE,2137
296
297
  flwr/server/typing.py,sha256=5kaRLZuxTEse9A0g7aVna2VhYxU3wTq1f3d3mtw7kXs,1019
297
298
  flwr/server/utils/__init__.py,sha256=pltsPHJoXmUIr3utjwwYxu7_ZAGy5u4MVHzv9iA5Un8,908
298
299
  flwr/server/utils/tensorboard.py,sha256=gEBD8w_5uaIfp5aw5RYH66lYZpd_SfkObHQ7eDd9MUk,5466
@@ -320,8 +321,8 @@ flwr/superexec/exec_servicer.py,sha256=jEYcASzkQR1ftjzilmlcTPKXo8NSno9mSj_UbBvMj
320
321
  flwr/superexec/exec_user_auth_interceptor.py,sha256=K06OU-l4LnYhTDg071hGJuOaQWEJbZsYi5qxUmmtiG0,3704
321
322
  flwr/superexec/executor.py,sha256=zH3_53il6Jh0ZscIVEB9f4GNnXMeBbCGyCoBCxLgiG0,3114
322
323
  flwr/superexec/simulation.py,sha256=WQDon15oqpMopAZnwRZoTICYCfHqtkvFSqiTQ2hLD_g,4088
323
- flwr_nightly-1.14.0.dev20241211.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
324
- flwr_nightly-1.14.0.dev20241211.dist-info/METADATA,sha256=onYC4ZwXpA_wegLQm_jOd4kFEDQtuJAHOsZ3wYWmue0,15799
325
- flwr_nightly-1.14.0.dev20241211.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
326
- flwr_nightly-1.14.0.dev20241211.dist-info/entry_points.txt,sha256=JlNxX3qhaV18_2yj5a3kJW1ESxm31cal9iS_N_pf1Rk,538
327
- flwr_nightly-1.14.0.dev20241211.dist-info/RECORD,,
324
+ flwr_nightly-1.14.0.dev20241212.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
325
+ flwr_nightly-1.14.0.dev20241212.dist-info/METADATA,sha256=IHpu3jP8Tt8KxOP3b4XtkPPf3wRSXYzwknPGE7mDlAY,15799
326
+ flwr_nightly-1.14.0.dev20241212.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
327
+ flwr_nightly-1.14.0.dev20241212.dist-info/entry_points.txt,sha256=JlNxX3qhaV18_2yj5a3kJW1ESxm31cal9iS_N_pf1Rk,538
328
+ flwr_nightly-1.14.0.dev20241212.dist-info/RECORD,,