flwr-nightly 1.14.0.dev20241211__py3-none-any.whl → 1.14.0.dev20241213__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 (75) hide show
  1. flwr/cli/app.py +1 -0
  2. flwr/cli/build.py +1 -0
  3. flwr/cli/config_utils.py +1 -0
  4. flwr/cli/example.py +1 -0
  5. flwr/cli/install.py +1 -0
  6. flwr/cli/log.py +1 -0
  7. flwr/cli/login/__init__.py +1 -0
  8. flwr/cli/login/login.py +1 -0
  9. flwr/cli/new/__init__.py +1 -0
  10. flwr/cli/new/new.py +2 -1
  11. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -2
  12. flwr/cli/run/__init__.py +1 -0
  13. flwr/cli/run/run.py +1 -0
  14. flwr/cli/utils.py +1 -0
  15. flwr/client/app.py +3 -2
  16. flwr/client/client.py +1 -0
  17. flwr/client/clientapp/app.py +1 -0
  18. flwr/client/clientapp/utils.py +1 -0
  19. flwr/client/grpc_adapter_client/connection.py +1 -1
  20. flwr/client/grpc_client/connection.py +1 -1
  21. flwr/client/grpc_rere_client/connection.py +3 -3
  22. flwr/client/message_handler/message_handler.py +1 -0
  23. flwr/client/mod/comms_mods.py +1 -0
  24. flwr/client/mod/localdp_mod.py +1 -1
  25. flwr/client/nodestate/__init__.py +1 -0
  26. flwr/client/nodestate/nodestate.py +1 -0
  27. flwr/client/nodestate/nodestate_factory.py +1 -0
  28. flwr/client/rest_client/connection.py +3 -3
  29. flwr/client/supernode/app.py +1 -0
  30. flwr/common/address.py +1 -0
  31. flwr/common/args.py +1 -0
  32. flwr/common/config.py +1 -0
  33. flwr/common/logger.py +1 -0
  34. flwr/common/message.py +1 -0
  35. flwr/common/object_ref.py +57 -54
  36. flwr/common/pyproject.py +1 -0
  37. flwr/common/record/__init__.py +1 -0
  38. flwr/common/record/parametersrecord.py +1 -0
  39. flwr/common/retry_invoker.py +75 -0
  40. flwr/common/typing.py +4 -0
  41. flwr/common/version.py +1 -0
  42. flwr/proto/fab_pb2.py +4 -4
  43. flwr/proto/fab_pb2.pyi +4 -1
  44. flwr/server/app.py +1 -0
  45. flwr/server/compat/app_utils.py +7 -1
  46. flwr/server/driver/grpc_driver.py +5 -61
  47. flwr/server/driver/inmemory_driver.py +5 -1
  48. flwr/server/serverapp/app.py +9 -2
  49. flwr/server/strategy/dpfedavg_fixed.py +1 -0
  50. flwr/server/superlink/driver/serverappio_grpc.py +1 -0
  51. flwr/server/superlink/driver/serverappio_servicer.py +54 -22
  52. flwr/server/superlink/ffs/disk_ffs.py +1 -0
  53. flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -0
  54. flwr/server/superlink/fleet/vce/__init__.py +1 -0
  55. flwr/server/superlink/fleet/vce/backend/__init__.py +1 -0
  56. flwr/server/superlink/fleet/vce/backend/raybackend.py +1 -0
  57. flwr/server/superlink/linkstate/in_memory_linkstate.py +14 -30
  58. flwr/server/superlink/linkstate/linkstate.py +13 -2
  59. flwr/server/superlink/linkstate/sqlite_linkstate.py +24 -44
  60. flwr/server/superlink/simulation/simulationio_servicer.py +1 -0
  61. flwr/server/superlink/utils.py +65 -0
  62. flwr/simulation/app.py +1 -0
  63. flwr/simulation/ray_transport/ray_actor.py +1 -0
  64. flwr/simulation/ray_transport/utils.py +1 -0
  65. flwr/simulation/run_simulation.py +1 -0
  66. flwr/superexec/app.py +1 -0
  67. flwr/superexec/deployment.py +1 -0
  68. flwr/superexec/exec_grpc.py +1 -0
  69. flwr/superexec/exec_servicer.py +8 -0
  70. flwr/superexec/executor.py +1 -0
  71. {flwr_nightly-1.14.0.dev20241211.dist-info → flwr_nightly-1.14.0.dev20241213.dist-info}/METADATA +1 -1
  72. {flwr_nightly-1.14.0.dev20241211.dist-info → flwr_nightly-1.14.0.dev20241213.dist-info}/RECORD +75 -74
  73. {flwr_nightly-1.14.0.dev20241211.dist-info → flwr_nightly-1.14.0.dev20241213.dist-info}/LICENSE +0 -0
  74. {flwr_nightly-1.14.0.dev20241211.dist-info → flwr_nightly-1.14.0.dev20241213.dist-info}/WHEEL +0 -0
  75. {flwr_nightly-1.14.0.dev20241211.dist-info → flwr_nightly-1.14.0.dev20241213.dist-info}/entry_points.txt +0 -0
flwr/cli/app.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower command line interface."""
16
16
 
17
+
17
18
  import typer
18
19
  from typer.main import get_command
19
20
 
flwr/cli/build.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower command line interface `build` command."""
16
16
 
17
+
17
18
  import hashlib
18
19
  import os
19
20
  import shutil
flwr/cli/config_utils.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Utility to validate the `pyproject.toml` file."""
16
16
 
17
+
17
18
  import zipfile
18
19
  from io import BytesIO
19
20
  from pathlib import Path
flwr/cli/example.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower command line interface `example` command."""
16
16
 
17
+
17
18
  import json
18
19
  import os
19
20
  import subprocess
flwr/cli/install.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower command line interface `install` command."""
16
16
 
17
+
17
18
  import hashlib
18
19
  import shutil
19
20
  import tempfile
flwr/cli/log.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower command line interface `log` command."""
16
16
 
17
+
17
18
  import time
18
19
  from logging import DEBUG, ERROR, INFO
19
20
  from pathlib import Path
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower command line interface `login` command."""
16
16
 
17
+
17
18
  from .login import login as login
18
19
 
19
20
  __all__ = [
flwr/cli/login/login.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower command line interface `login` command."""
16
16
 
17
+
17
18
  from pathlib import Path
18
19
  from typing import Annotated, Optional
19
20
 
flwr/cli/new/__init__.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower command line interface `new` command."""
16
16
 
17
+
17
18
  from .new import new as new
18
19
 
19
20
  __all__ = [
flwr/cli/new/new.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower command line interface `new` command."""
16
16
 
17
+
17
18
  import re
18
19
  from enum import Enum
19
20
  from pathlib import Path
@@ -81,7 +82,7 @@ def render_template(template: str, data: dict[str, str]) -> str:
81
82
  def create_file(file_path: Path, content: str) -> None:
82
83
  """Create file including all nessecary directories and write content into file."""
83
84
  file_path.parent.mkdir(exist_ok=True)
84
- file_path.write_text(content)
85
+ file_path.write_text(content, encoding="utf-8")
85
86
 
86
87
 
87
88
  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]
flwr/cli/run/__init__.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower command line interface `run` command."""
16
16
 
17
+
17
18
  from .run import run as run
18
19
 
19
20
  __all__ = [
flwr/cli/run/run.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower command line interface `run` command."""
16
16
 
17
+
17
18
  import io
18
19
  import json
19
20
  import subprocess
flwr/cli/utils.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower command line interface utils."""
16
16
 
17
+
17
18
  import hashlib
18
19
  import json
19
20
  import re
flwr/client/app.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower client app."""
16
16
 
17
+
17
18
  import signal
18
19
  import subprocess
19
20
  import sys
@@ -474,7 +475,7 @@ def start_client_internal(
474
475
 
475
476
  run: Run = runs[run_id]
476
477
  if get_fab is not None and run.fab_hash:
477
- fab = get_fab(run.fab_hash)
478
+ fab = get_fab(run.fab_hash, run_id)
478
479
  if not isolation:
479
480
  # If `ClientApp` runs in the same process, install the FAB
480
481
  install_from_fab(fab.content, flwr_path, True)
@@ -752,7 +753,7 @@ def _init_connection(transport: Optional[str], server_address: str) -> tuple[
752
753
  Optional[Callable[[], Optional[int]]],
753
754
  Optional[Callable[[], None]],
754
755
  Optional[Callable[[int], Run]],
755
- Optional[Callable[[str], Fab]],
756
+ Optional[Callable[[str, int], Fab]],
756
757
  ]
757
758
  ],
758
759
  ],
flwr/client/client.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower client (abstract base class)."""
16
16
 
17
+
17
18
  # Needed to `Client` class can return a type of `Client` (not needed in py3.11+)
18
19
  from __future__ import annotations
19
20
 
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower ClientApp process."""
16
16
 
17
+
17
18
  import argparse
18
19
  import sys
19
20
  import time
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower ClientApp loading utils."""
16
16
 
17
+
17
18
  from logging import DEBUG
18
19
  from pathlib import Path
19
20
  from typing import Callable, Optional
@@ -48,7 +48,7 @@ def grpc_adapter( # pylint: disable=R0913,too-many-positional-arguments
48
48
  Optional[Callable[[], Optional[int]]],
49
49
  Optional[Callable[[], None]],
50
50
  Optional[Callable[[int], Run]],
51
- Optional[Callable[[str], Fab]],
51
+ Optional[Callable[[str, int], Fab]],
52
52
  ]
53
53
  ]:
54
54
  """Primitives for request/response-based interaction with a server via GrpcAdapter.
@@ -76,7 +76,7 @@ def grpc_connection( # pylint: disable=R0913,R0915,too-many-positional-argument
76
76
  Optional[Callable[[], Optional[int]]],
77
77
  Optional[Callable[[], None]],
78
78
  Optional[Callable[[int], Run]],
79
- Optional[Callable[[str], Fab]],
79
+ Optional[Callable[[str, int], Fab]],
80
80
  ]
81
81
  ]:
82
82
  """Establish a gRPC connection to a gRPC server.
@@ -84,7 +84,7 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
84
84
  Optional[Callable[[], Optional[int]]],
85
85
  Optional[Callable[[], None]],
86
86
  Optional[Callable[[int], Run]],
87
- Optional[Callable[[str], Fab]],
87
+ Optional[Callable[[str, int], Fab]],
88
88
  ]
89
89
  ]:
90
90
  """Primitives for request/response-based interaction with a server.
@@ -290,9 +290,9 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
290
290
  # Return fab_id and fab_version
291
291
  return run_from_proto(get_run_response.run)
292
292
 
293
- def get_fab(fab_hash: str) -> Fab:
293
+ def get_fab(fab_hash: str, run_id: int) -> Fab:
294
294
  # Call FleetAPI
295
- get_fab_request = GetFabRequest(node=node, hash_str=fab_hash)
295
+ get_fab_request = GetFabRequest(node=node, hash_str=fab_hash, run_id=run_id)
296
296
  get_fab_response: GetFabResponse = retry_invoker.invoke(
297
297
  stub.GetFab,
298
298
  request=get_fab_request,
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Client-side message handler."""
16
16
 
17
+
17
18
  from logging import WARN
18
19
  from typing import Optional, cast
19
20
 
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Mods that report statistics about message communication."""
16
16
 
17
+
17
18
  from logging import INFO
18
19
 
19
20
  import numpy as np
@@ -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
 
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower NodeState."""
16
16
 
17
+
17
18
  from .in_memory_nodestate import InMemoryNodeState as InMemoryNodeState
18
19
  from .nodestate import NodeState as NodeState
19
20
  from .nodestate_factory import NodeStateFactory as NodeStateFactory
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Abstract base class NodeState."""
16
16
 
17
+
17
18
  import abc
18
19
  from typing import Optional
19
20
 
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Factory class that creates NodeState instances."""
16
16
 
17
+
17
18
  import threading
18
19
  from typing import Optional
19
20
 
@@ -96,7 +96,7 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
96
96
  Optional[Callable[[], Optional[int]]],
97
97
  Optional[Callable[[], None]],
98
98
  Optional[Callable[[int], Run]],
99
- Optional[Callable[[str], Fab]],
99
+ Optional[Callable[[str, int], Fab]],
100
100
  ]
101
101
  ]:
102
102
  """Primitives for request/response-based interaction with a server.
@@ -361,9 +361,9 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
361
361
 
362
362
  return run_from_proto(res.run)
363
363
 
364
- def get_fab(fab_hash: str) -> Fab:
364
+ def get_fab(fab_hash: str, run_id: int) -> Fab:
365
365
  # Construct the request
366
- req = GetFabRequest(node=node, hash_str=fab_hash)
366
+ req = GetFabRequest(node=node, hash_str=fab_hash, run_id=run_id)
367
367
 
368
368
  # Send the request
369
369
  res = _request(req, GetFabResponse, PATH_GET_FAB)
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower SuperNode."""
16
16
 
17
+
17
18
  import argparse
18
19
  import sys
19
20
  from logging import DEBUG, ERROR, INFO, WARN
flwr/common/address.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower IP address utils."""
16
16
 
17
+
17
18
  import socket
18
19
  from ipaddress import ip_address
19
20
  from typing import Optional
flwr/common/args.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Common Flower arguments."""
16
16
 
17
+
17
18
  import argparse
18
19
  import sys
19
20
  from logging import DEBUG, ERROR, WARN
flwr/common/config.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Provide functions for managing global Flower config."""
16
16
 
17
+
17
18
  import os
18
19
  import re
19
20
  from pathlib import Path
flwr/common/logger.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower Logger."""
16
16
 
17
+
17
18
  import logging
18
19
  import re
19
20
  import sys
flwr/common/message.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Message."""
16
16
 
17
+
17
18
  from __future__ import annotations
18
19
 
19
20
  import time
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:
flwr/common/pyproject.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Validates the project's name property."""
16
16
 
17
+
17
18
  import re
18
19
 
19
20
 
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Record APIs."""
16
16
 
17
+
17
18
  from .configsrecord import ConfigsRecord
18
19
  from .conversion_utils import array_from_numpy
19
20
  from .metricsrecord import MetricsRecord
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """ParametersRecord and Array."""
16
16
 
17
+
17
18
  from collections import OrderedDict
18
19
  from dataclasses import dataclass
19
20
  from io import BytesIO
@@ -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/common/version.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # ==============================================================================
15
15
  """Flower package version helper."""
16
16
 
17
+
17
18
  import importlib.metadata as importlib_metadata
18
19
 
19
20