flwr 1.12.0__py3-none-any.whl → 1.13.1__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.
- flwr/cli/app.py +2 -0
- flwr/cli/build.py +2 -2
- flwr/cli/config_utils.py +97 -0
- flwr/cli/install.py +0 -16
- flwr/cli/log.py +63 -97
- flwr/cli/ls.py +228 -0
- flwr/cli/new/new.py +23 -13
- flwr/cli/new/templates/app/README.md.tpl +11 -0
- flwr/cli/new/templates/app/code/flwr_tune/dataset.py.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +2 -1
- flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +2 -2
- flwr/cli/run/run.py +37 -89
- flwr/client/app.py +73 -34
- flwr/client/clientapp/app.py +58 -37
- flwr/client/grpc_rere_client/connection.py +7 -12
- flwr/client/nodestate/__init__.py +25 -0
- flwr/client/nodestate/in_memory_nodestate.py +38 -0
- flwr/client/nodestate/nodestate.py +30 -0
- flwr/client/nodestate/nodestate_factory.py +37 -0
- flwr/client/rest_client/connection.py +4 -14
- flwr/client/{node_state.py → run_info_store.py} +4 -3
- flwr/client/supernode/app.py +34 -58
- flwr/common/args.py +152 -0
- flwr/common/config.py +10 -0
- flwr/common/constant.py +59 -7
- flwr/common/context.py +9 -4
- flwr/common/date.py +21 -3
- flwr/common/grpc.py +4 -1
- flwr/common/logger.py +108 -1
- flwr/common/object_ref.py +47 -16
- flwr/common/serde.py +34 -0
- flwr/common/telemetry.py +0 -6
- flwr/common/typing.py +32 -2
- flwr/proto/exec_pb2.py +23 -17
- flwr/proto/exec_pb2.pyi +58 -22
- flwr/proto/exec_pb2_grpc.py +34 -0
- flwr/proto/exec_pb2_grpc.pyi +13 -0
- flwr/proto/log_pb2.py +29 -0
- flwr/proto/log_pb2.pyi +39 -0
- flwr/proto/log_pb2_grpc.py +4 -0
- flwr/proto/log_pb2_grpc.pyi +4 -0
- flwr/proto/message_pb2.py +8 -8
- flwr/proto/message_pb2.pyi +4 -1
- flwr/proto/run_pb2.py +32 -27
- flwr/proto/run_pb2.pyi +44 -1
- flwr/proto/serverappio_pb2.py +52 -0
- flwr/proto/{driver_pb2.pyi → serverappio_pb2.pyi} +54 -0
- flwr/proto/serverappio_pb2_grpc.py +376 -0
- flwr/proto/serverappio_pb2_grpc.pyi +147 -0
- flwr/proto/simulationio_pb2.py +38 -0
- flwr/proto/simulationio_pb2.pyi +65 -0
- flwr/proto/simulationio_pb2_grpc.py +205 -0
- flwr/proto/simulationio_pb2_grpc.pyi +81 -0
- flwr/server/app.py +297 -162
- flwr/server/driver/driver.py +15 -1
- flwr/server/driver/grpc_driver.py +89 -50
- flwr/server/driver/inmemory_driver.py +6 -16
- flwr/server/run_serverapp.py +11 -235
- flwr/server/{superlink/state → serverapp}/__init__.py +3 -9
- flwr/server/serverapp/app.py +234 -0
- flwr/server/strategy/aggregate.py +4 -4
- flwr/server/strategy/fedadam.py +11 -1
- flwr/server/superlink/driver/__init__.py +1 -1
- flwr/server/superlink/driver/{driver_grpc.py → serverappio_grpc.py} +19 -16
- flwr/server/superlink/driver/{driver_servicer.py → serverappio_servicer.py} +125 -39
- flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +4 -2
- flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +2 -2
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +4 -2
- flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +2 -2
- flwr/server/superlink/fleet/message_handler/message_handler.py +7 -7
- flwr/server/superlink/fleet/rest_rere/rest_api.py +10 -9
- flwr/server/superlink/fleet/vce/vce_api.py +23 -23
- flwr/server/superlink/linkstate/__init__.py +28 -0
- flwr/server/superlink/{state/in_memory_state.py → linkstate/in_memory_linkstate.py} +237 -64
- flwr/server/superlink/{state/state.py → linkstate/linkstate.py} +166 -22
- flwr/server/superlink/{state/state_factory.py → linkstate/linkstate_factory.py} +9 -9
- flwr/server/superlink/{state/sqlite_state.py → linkstate/sqlite_linkstate.py} +383 -174
- flwr/server/superlink/linkstate/utils.py +389 -0
- flwr/server/superlink/simulation/__init__.py +15 -0
- flwr/server/superlink/simulation/simulationio_grpc.py +65 -0
- flwr/server/superlink/simulation/simulationio_servicer.py +153 -0
- flwr/simulation/__init__.py +5 -1
- flwr/simulation/app.py +236 -347
- flwr/simulation/legacy_app.py +402 -0
- flwr/simulation/ray_transport/ray_client_proxy.py +2 -2
- flwr/simulation/run_simulation.py +56 -141
- flwr/simulation/simulationio_connection.py +86 -0
- flwr/superexec/app.py +6 -134
- flwr/superexec/deployment.py +70 -69
- flwr/superexec/exec_grpc.py +15 -8
- flwr/superexec/exec_servicer.py +65 -65
- flwr/superexec/executor.py +26 -7
- flwr/superexec/simulation.py +62 -150
- {flwr-1.12.0.dist-info → flwr-1.13.1.dist-info}/METADATA +9 -7
- {flwr-1.12.0.dist-info → flwr-1.13.1.dist-info}/RECORD +105 -85
- {flwr-1.12.0.dist-info → flwr-1.13.1.dist-info}/entry_points.txt +2 -0
- flwr/client/node_state_tests.py +0 -66
- flwr/proto/driver_pb2.py +0 -42
- flwr/proto/driver_pb2_grpc.py +0 -239
- flwr/proto/driver_pb2_grpc.pyi +0 -94
- flwr/server/superlink/state/utils.py +0 -148
- {flwr-1.12.0.dist-info → flwr-1.13.1.dist-info}/LICENSE +0 -0
- {flwr-1.12.0.dist-info → flwr-1.13.1.dist-info}/WHEEL +0 -0
|
@@ -8,9 +8,9 @@ version = "1.0.0"
|
|
|
8
8
|
description = ""
|
|
9
9
|
license = "Apache-2.0"
|
|
10
10
|
dependencies = [
|
|
11
|
-
"flwr[simulation]>=1.
|
|
11
|
+
"flwr[simulation]>=1.13.0",
|
|
12
12
|
"flwr-datasets[vision]>=0.3.0",
|
|
13
|
-
"tensorflow>=2.11.1",
|
|
13
|
+
"tensorflow>=2.11.1,<2.18.0",
|
|
14
14
|
]
|
|
15
15
|
|
|
16
16
|
[tool.hatch.build.targets.wheel]
|
flwr/cli/run/run.py
CHANGED
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
|
|
17
17
|
import json
|
|
18
18
|
import subprocess
|
|
19
|
-
import sys
|
|
20
19
|
from logging import DEBUG
|
|
21
20
|
from pathlib import Path
|
|
22
21
|
from typing import Annotated, Any, Optional
|
|
@@ -24,11 +23,24 @@ from typing import Annotated, Any, Optional
|
|
|
24
23
|
import typer
|
|
25
24
|
|
|
26
25
|
from flwr.cli.build import build
|
|
27
|
-
from flwr.cli.config_utils import
|
|
28
|
-
|
|
26
|
+
from flwr.cli.config_utils import (
|
|
27
|
+
load_and_validate,
|
|
28
|
+
validate_certificate_in_federation_config,
|
|
29
|
+
validate_federation_in_project_config,
|
|
30
|
+
validate_project_config,
|
|
31
|
+
)
|
|
32
|
+
from flwr.common.config import (
|
|
33
|
+
flatten_dict,
|
|
34
|
+
parse_config_args,
|
|
35
|
+
user_config_to_configsrecord,
|
|
36
|
+
)
|
|
29
37
|
from flwr.common.grpc import GRPC_MAX_MESSAGE_LENGTH, create_channel
|
|
30
38
|
from flwr.common.logger import log
|
|
31
|
-
from flwr.common.serde import
|
|
39
|
+
from flwr.common.serde import (
|
|
40
|
+
configs_record_to_proto,
|
|
41
|
+
fab_to_proto,
|
|
42
|
+
user_config_to_proto,
|
|
43
|
+
)
|
|
32
44
|
from flwr.common.typing import Fab
|
|
33
45
|
from flwr.proto.exec_pb2 import StartRunRequest # pylint: disable=E0611
|
|
34
46
|
from flwr.proto.exec_pb2_grpc import ExecStub
|
|
@@ -79,96 +91,28 @@ def run(
|
|
|
79
91
|
|
|
80
92
|
pyproject_path = app / "pyproject.toml" if app else None
|
|
81
93
|
config, errors, warnings = load_and_validate(path=pyproject_path)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
"pyproject.toml is invalid:\n"
|
|
87
|
-
+ "\n".join([f"- {line}" for line in errors]),
|
|
88
|
-
fg=typer.colors.RED,
|
|
89
|
-
bold=True,
|
|
90
|
-
)
|
|
91
|
-
sys.exit()
|
|
92
|
-
|
|
93
|
-
if warnings:
|
|
94
|
-
typer.secho(
|
|
95
|
-
"Project configuration is missing the following "
|
|
96
|
-
"recommended properties:\n" + "\n".join([f"- {line}" for line in warnings]),
|
|
97
|
-
fg=typer.colors.RED,
|
|
98
|
-
bold=True,
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
typer.secho("Success", fg=typer.colors.GREEN)
|
|
102
|
-
|
|
103
|
-
federation = federation or config["tool"]["flwr"]["federations"].get("default")
|
|
104
|
-
|
|
105
|
-
if federation is None:
|
|
106
|
-
typer.secho(
|
|
107
|
-
"❌ No federation name was provided and the project's `pyproject.toml` "
|
|
108
|
-
"doesn't declare a default federation (with a SuperExec address or an "
|
|
109
|
-
"`options.num-supernodes` value).",
|
|
110
|
-
fg=typer.colors.RED,
|
|
111
|
-
bold=True,
|
|
112
|
-
)
|
|
113
|
-
raise typer.Exit(code=1)
|
|
114
|
-
|
|
115
|
-
# Validate the federation exists in the configuration
|
|
116
|
-
federation_config = config["tool"]["flwr"]["federations"].get(federation)
|
|
117
|
-
if federation_config is None:
|
|
118
|
-
available_feds = {
|
|
119
|
-
fed for fed in config["tool"]["flwr"]["federations"] if fed != "default"
|
|
120
|
-
}
|
|
121
|
-
typer.secho(
|
|
122
|
-
f"❌ There is no `{federation}` federation declared in "
|
|
123
|
-
"`pyproject.toml`.\n The following federations were found:\n\n"
|
|
124
|
-
+ "\n".join(available_feds),
|
|
125
|
-
fg=typer.colors.RED,
|
|
126
|
-
bold=True,
|
|
127
|
-
)
|
|
128
|
-
raise typer.Exit(code=1)
|
|
94
|
+
config = validate_project_config(config, errors, warnings)
|
|
95
|
+
federation, federation_config = validate_federation_in_project_config(
|
|
96
|
+
federation, config
|
|
97
|
+
)
|
|
129
98
|
|
|
130
99
|
if "address" in federation_config:
|
|
131
|
-
|
|
100
|
+
_run_with_exec_api(app, federation_config, config_overrides, stream)
|
|
132
101
|
else:
|
|
133
|
-
|
|
102
|
+
_run_without_exec_api(app, federation_config, config_overrides, federation)
|
|
134
103
|
|
|
135
104
|
|
|
136
|
-
# pylint: disable=too-many-locals
|
|
137
|
-
def
|
|
105
|
+
# pylint: disable-next=too-many-locals
|
|
106
|
+
def _run_with_exec_api(
|
|
138
107
|
app: Path,
|
|
139
108
|
federation_config: dict[str, Any],
|
|
140
109
|
config_overrides: Optional[list[str]],
|
|
141
110
|
stream: bool,
|
|
142
111
|
) -> None:
|
|
143
112
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if insecure := bool(insecure_str):
|
|
148
|
-
typer.secho(
|
|
149
|
-
"❌ `root_certificates` were provided but the `insecure` parameter"
|
|
150
|
-
"is set to `True`.",
|
|
151
|
-
fg=typer.colors.RED,
|
|
152
|
-
bold=True,
|
|
153
|
-
)
|
|
154
|
-
raise typer.Exit(code=1)
|
|
155
|
-
else:
|
|
156
|
-
root_certificates_bytes = None
|
|
157
|
-
if insecure_str is None:
|
|
158
|
-
typer.secho(
|
|
159
|
-
"❌ To disable TLS, set `insecure = true` in `pyproject.toml`.",
|
|
160
|
-
fg=typer.colors.RED,
|
|
161
|
-
bold=True,
|
|
162
|
-
)
|
|
163
|
-
raise typer.Exit(code=1)
|
|
164
|
-
if not (insecure := bool(insecure_str)):
|
|
165
|
-
typer.secho(
|
|
166
|
-
"❌ No certificate were given yet `insecure` is set to `False`.",
|
|
167
|
-
fg=typer.colors.RED,
|
|
168
|
-
bold=True,
|
|
169
|
-
)
|
|
170
|
-
raise typer.Exit(code=1)
|
|
171
|
-
|
|
113
|
+
insecure, root_certificates_bytes = validate_certificate_in_federation_config(
|
|
114
|
+
app, federation_config
|
|
115
|
+
)
|
|
172
116
|
channel = create_channel(
|
|
173
117
|
server_address=federation_config["address"],
|
|
174
118
|
insecure=insecure,
|
|
@@ -181,26 +125,30 @@ def _run_with_superexec(
|
|
|
181
125
|
|
|
182
126
|
fab_path, fab_hash = build(app)
|
|
183
127
|
content = Path(fab_path).read_bytes()
|
|
128
|
+
|
|
129
|
+
# Delete FAB file once the bytes is computed
|
|
130
|
+
Path(fab_path).unlink()
|
|
131
|
+
|
|
184
132
|
fab = Fab(fab_hash, content)
|
|
185
133
|
|
|
134
|
+
# Construct a `ConfigsRecord` out of a flattened `UserConfig`
|
|
135
|
+
fed_conf = flatten_dict(federation_config.get("options", {}))
|
|
136
|
+
c_record = user_config_to_configsrecord(fed_conf)
|
|
137
|
+
|
|
186
138
|
req = StartRunRequest(
|
|
187
139
|
fab=fab_to_proto(fab),
|
|
188
140
|
override_config=user_config_to_proto(parse_config_args(config_overrides)),
|
|
189
|
-
|
|
190
|
-
flatten_dict(federation_config.get("options"))
|
|
191
|
-
),
|
|
141
|
+
federation_options=configs_record_to_proto(c_record),
|
|
192
142
|
)
|
|
193
143
|
res = stub.StartRun(req)
|
|
194
144
|
|
|
195
|
-
# Delete FAB file once it has been sent to the SuperExec
|
|
196
|
-
Path(fab_path).unlink()
|
|
197
145
|
typer.secho(f"🎊 Successfully started run {res.run_id}", fg=typer.colors.GREEN)
|
|
198
146
|
|
|
199
147
|
if stream:
|
|
200
148
|
start_stream(res.run_id, channel, CONN_REFRESH_PERIOD)
|
|
201
149
|
|
|
202
150
|
|
|
203
|
-
def
|
|
151
|
+
def _run_without_exec_api(
|
|
204
152
|
app: Optional[Path],
|
|
205
153
|
federation_config: dict[str, Any],
|
|
206
154
|
config_overrides: Optional[list[str]],
|
flwr/client/app.py
CHANGED
|
@@ -32,13 +32,19 @@ from flwr.cli.config_utils import get_fab_metadata
|
|
|
32
32
|
from flwr.cli.install import install_from_fab
|
|
33
33
|
from flwr.client.client import Client
|
|
34
34
|
from flwr.client.client_app import ClientApp, LoadClientAppError
|
|
35
|
+
from flwr.client.nodestate.nodestate_factory import NodeStateFactory
|
|
35
36
|
from flwr.client.typing import ClientFnExt
|
|
36
37
|
from flwr.common import GRPC_MAX_MESSAGE_LENGTH, Context, EventType, Message, event
|
|
37
38
|
from flwr.common.address import parse_address
|
|
38
39
|
from flwr.common.constant import (
|
|
39
|
-
|
|
40
|
+
CLIENT_OCTET,
|
|
41
|
+
CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS,
|
|
42
|
+
ISOLATION_MODE_PROCESS,
|
|
43
|
+
ISOLATION_MODE_SUBPROCESS,
|
|
44
|
+
MAX_RETRY_DELAY,
|
|
40
45
|
MISSING_EXTRA_REST,
|
|
41
46
|
RUN_ID_NUM_BYTES,
|
|
47
|
+
SERVER_OCTET,
|
|
42
48
|
TRANSPORT_TYPE_GRPC_ADAPTER,
|
|
43
49
|
TRANSPORT_TYPE_GRPC_BIDI,
|
|
44
50
|
TRANSPORT_TYPE_GRPC_RERE,
|
|
@@ -52,18 +58,15 @@ from flwr.common.retry_invoker import RetryInvoker, RetryState, exponential
|
|
|
52
58
|
from flwr.common.typing import Fab, Run, UserConfig
|
|
53
59
|
from flwr.proto.clientappio_pb2_grpc import add_ClientAppIoServicer_to_server
|
|
54
60
|
from flwr.server.superlink.fleet.grpc_bidi.grpc_server import generic_create_grpc_server
|
|
55
|
-
from flwr.server.superlink.
|
|
61
|
+
from flwr.server.superlink.linkstate.utils import generate_rand_int_from_bytes
|
|
56
62
|
|
|
57
63
|
from .clientapp.clientappio_servicer import ClientAppInputs, ClientAppIoServicer
|
|
58
64
|
from .grpc_adapter_client.connection import grpc_adapter
|
|
59
65
|
from .grpc_client.connection import grpc_connection
|
|
60
66
|
from .grpc_rere_client.connection import grpc_request_response
|
|
61
67
|
from .message_handler.message_handler import handle_control_message
|
|
62
|
-
from .node_state import NodeState
|
|
63
68
|
from .numpy_client import NumPyClient
|
|
64
|
-
|
|
65
|
-
ISOLATION_MODE_SUBPROCESS = "subprocess"
|
|
66
|
-
ISOLATION_MODE_PROCESS = "process"
|
|
69
|
+
from .run_info_store import DeprecatedRunInfoStore
|
|
67
70
|
|
|
68
71
|
|
|
69
72
|
def _check_actionable_client(
|
|
@@ -102,6 +105,11 @@ def start_client(
|
|
|
102
105
|
) -> None:
|
|
103
106
|
"""Start a Flower client node which connects to a Flower server.
|
|
104
107
|
|
|
108
|
+
Warning
|
|
109
|
+
-------
|
|
110
|
+
This function is deprecated since 1.13.0. Use :code:`flower-supernode` command
|
|
111
|
+
instead to start a SuperNode.
|
|
112
|
+
|
|
105
113
|
Parameters
|
|
106
114
|
----------
|
|
107
115
|
server_address : str
|
|
@@ -176,6 +184,17 @@ def start_client(
|
|
|
176
184
|
>>> root_certificates=Path("/crts/root.pem").read_bytes(),
|
|
177
185
|
>>> )
|
|
178
186
|
"""
|
|
187
|
+
msg = (
|
|
188
|
+
"flwr.client.start_client() is deprecated."
|
|
189
|
+
"\n\tInstead, use the `flower-supernode` CLI command to start a SuperNode "
|
|
190
|
+
"as shown below:"
|
|
191
|
+
"\n\n\t\t$ flower-supernode --insecure --superlink='<IP>:<PORT>'"
|
|
192
|
+
"\n\n\tTo view all available options, run:"
|
|
193
|
+
"\n\n\t\t$ flower-supernode --help"
|
|
194
|
+
"\n\n\tUsing `start_client()` is deprecated."
|
|
195
|
+
)
|
|
196
|
+
warn_deprecated_feature(name=msg)
|
|
197
|
+
|
|
179
198
|
event(EventType.START_CLIENT_ENTER)
|
|
180
199
|
start_client_internal(
|
|
181
200
|
server_address=server_address,
|
|
@@ -216,7 +235,7 @@ def start_client_internal(
|
|
|
216
235
|
max_wait_time: Optional[float] = None,
|
|
217
236
|
flwr_path: Optional[Path] = None,
|
|
218
237
|
isolation: Optional[str] = None,
|
|
219
|
-
|
|
238
|
+
clientappio_api_address: Optional[str] = CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS,
|
|
220
239
|
) -> None:
|
|
221
240
|
"""Start a Flower client node which connects to a Flower server.
|
|
222
241
|
|
|
@@ -274,9 +293,11 @@ def start_client_internal(
|
|
|
274
293
|
`process`. Defaults to `None`, which runs the `ClientApp` in the same process
|
|
275
294
|
as the SuperNode. If `subprocess`, the `ClientApp` runs in a subprocess started
|
|
276
295
|
by the SueprNode and communicates using gRPC at the address
|
|
277
|
-
`
|
|
278
|
-
process and communicates using gRPC at the address
|
|
279
|
-
|
|
296
|
+
`clientappio_api_address`. If `process`, the `ClientApp` runs in a separate
|
|
297
|
+
isolated process and communicates using gRPC at the address
|
|
298
|
+
`clientappio_api_address`.
|
|
299
|
+
clientappio_api_address : Optional[str]
|
|
300
|
+
(default: `CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS`)
|
|
280
301
|
The SuperNode gRPC server address.
|
|
281
302
|
"""
|
|
282
303
|
if insecure is None:
|
|
@@ -304,15 +325,16 @@ def start_client_internal(
|
|
|
304
325
|
load_client_app_fn = _load_client_app
|
|
305
326
|
|
|
306
327
|
if isolation:
|
|
307
|
-
if
|
|
328
|
+
if clientappio_api_address is None:
|
|
308
329
|
raise ValueError(
|
|
309
|
-
f"`
|
|
330
|
+
f"`clientappio_api_address` required when `isolation` is "
|
|
310
331
|
f"{ISOLATION_MODE_SUBPROCESS} or {ISOLATION_MODE_PROCESS}",
|
|
311
332
|
)
|
|
312
333
|
_clientappio_grpc_server, clientappio_servicer = run_clientappio_api_grpc(
|
|
313
|
-
address=
|
|
334
|
+
address=clientappio_api_address,
|
|
335
|
+
certificates=None,
|
|
314
336
|
)
|
|
315
|
-
|
|
337
|
+
clientappio_api_address = cast(str, clientappio_api_address)
|
|
316
338
|
|
|
317
339
|
# At this point, only `load_client_app_fn` should be used
|
|
318
340
|
# Both `client` and `client_fn` must not be used directly
|
|
@@ -346,7 +368,7 @@ def start_client_internal(
|
|
|
346
368
|
)
|
|
347
369
|
|
|
348
370
|
retry_invoker = RetryInvoker(
|
|
349
|
-
wait_gen_factory=exponential,
|
|
371
|
+
wait_gen_factory=lambda: exponential(max_delay=MAX_RETRY_DELAY),
|
|
350
372
|
recoverable_exceptions=connection_error_type,
|
|
351
373
|
max_tries=max_retries + 1 if max_retries is not None else None,
|
|
352
374
|
max_time=max_wait_time,
|
|
@@ -364,8 +386,10 @@ def start_client_internal(
|
|
|
364
386
|
on_backoff=_on_backoff,
|
|
365
387
|
)
|
|
366
388
|
|
|
367
|
-
#
|
|
368
|
-
|
|
389
|
+
# DeprecatedRunInfoStore gets initialized when the first connection is established
|
|
390
|
+
run_info_store: Optional[DeprecatedRunInfoStore] = None
|
|
391
|
+
state_factory = NodeStateFactory()
|
|
392
|
+
state = state_factory.state()
|
|
369
393
|
|
|
370
394
|
runs: dict[int, Run] = {}
|
|
371
395
|
|
|
@@ -382,7 +406,7 @@ def start_client_internal(
|
|
|
382
406
|
receive, send, create_node, delete_node, get_run, get_fab = conn
|
|
383
407
|
|
|
384
408
|
# Register node when connecting the first time
|
|
385
|
-
if
|
|
409
|
+
if run_info_store is None:
|
|
386
410
|
if create_node is None:
|
|
387
411
|
if transport not in ["grpc-bidi", None]:
|
|
388
412
|
raise NotImplementedError(
|
|
@@ -391,19 +415,20 @@ def start_client_internal(
|
|
|
391
415
|
)
|
|
392
416
|
# gRPC-bidi doesn't have the concept of node_id,
|
|
393
417
|
# so we set it to -1
|
|
394
|
-
|
|
418
|
+
run_info_store = DeprecatedRunInfoStore(
|
|
395
419
|
node_id=-1,
|
|
396
420
|
node_config={},
|
|
397
421
|
)
|
|
398
422
|
else:
|
|
399
423
|
# Call create_node fn to register node
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
424
|
+
# and store node_id in state
|
|
425
|
+
if (node_id := create_node()) is None:
|
|
426
|
+
raise ValueError(
|
|
427
|
+
"Failed to register SuperNode with the SuperLink"
|
|
428
|
+
)
|
|
429
|
+
state.set_node_id(node_id)
|
|
430
|
+
run_info_store = DeprecatedRunInfoStore(
|
|
431
|
+
node_id=state.get_node_id(),
|
|
407
432
|
node_config=node_config,
|
|
408
433
|
)
|
|
409
434
|
|
|
@@ -445,7 +470,7 @@ def start_client_internal(
|
|
|
445
470
|
runs[run_id] = get_run(run_id)
|
|
446
471
|
# If get_run is None, i.e., in grpc-bidi mode
|
|
447
472
|
else:
|
|
448
|
-
runs[run_id] = Run(run_id
|
|
473
|
+
runs[run_id] = Run.create_empty(run_id=run_id)
|
|
449
474
|
|
|
450
475
|
run: Run = runs[run_id]
|
|
451
476
|
if get_fab is not None and run.fab_hash:
|
|
@@ -461,7 +486,7 @@ def start_client_internal(
|
|
|
461
486
|
run.fab_id, run.fab_version = fab_id, fab_version
|
|
462
487
|
|
|
463
488
|
# Register context for this run
|
|
464
|
-
|
|
489
|
+
run_info_store.register_context(
|
|
465
490
|
run_id=run_id,
|
|
466
491
|
run=run,
|
|
467
492
|
flwr_path=flwr_path,
|
|
@@ -469,7 +494,7 @@ def start_client_internal(
|
|
|
469
494
|
)
|
|
470
495
|
|
|
471
496
|
# Retrieve context for this run
|
|
472
|
-
context =
|
|
497
|
+
context = run_info_store.retrieve_context(run_id=run_id)
|
|
473
498
|
# Create an error reply message that will never be used to prevent
|
|
474
499
|
# the used-before-assignment linting error
|
|
475
500
|
reply_message = message.create_error_reply(
|
|
@@ -505,14 +530,24 @@ def start_client_internal(
|
|
|
505
530
|
)
|
|
506
531
|
|
|
507
532
|
if start_subprocess:
|
|
533
|
+
_octet, _colon, _port = (
|
|
534
|
+
clientappio_api_address.rpartition(":")
|
|
535
|
+
)
|
|
536
|
+
io_address = (
|
|
537
|
+
f"{CLIENT_OCTET}:{_port}"
|
|
538
|
+
if _octet == SERVER_OCTET
|
|
539
|
+
else clientappio_api_address
|
|
540
|
+
)
|
|
508
541
|
# Start ClientApp subprocess
|
|
509
542
|
command = [
|
|
510
543
|
"flwr-clientapp",
|
|
511
|
-
"--
|
|
512
|
-
|
|
544
|
+
"--clientappio-api-address",
|
|
545
|
+
io_address,
|
|
513
546
|
"--token",
|
|
514
547
|
str(token),
|
|
515
548
|
]
|
|
549
|
+
command.append("--insecure")
|
|
550
|
+
|
|
516
551
|
subprocess.run(
|
|
517
552
|
command,
|
|
518
553
|
stdout=None,
|
|
@@ -542,7 +577,7 @@ def start_client_internal(
|
|
|
542
577
|
# Raise exception, crash process
|
|
543
578
|
raise ex
|
|
544
579
|
|
|
545
|
-
# Don't update/change
|
|
580
|
+
# Don't update/change DeprecatedRunInfoStore
|
|
546
581
|
|
|
547
582
|
e_code = ErrorCode.CLIENT_APP_RAISED_EXCEPTION
|
|
548
583
|
# Ex fmt: "<class 'ZeroDivisionError'>:<'division by zero'>"
|
|
@@ -567,7 +602,7 @@ def start_client_internal(
|
|
|
567
602
|
)
|
|
568
603
|
else:
|
|
569
604
|
# No exception, update node state
|
|
570
|
-
|
|
605
|
+
run_info_store.update_context(
|
|
571
606
|
run_id=run_id,
|
|
572
607
|
context=context,
|
|
573
608
|
)
|
|
@@ -780,7 +815,10 @@ class _AppStateTracker:
|
|
|
780
815
|
signal.signal(signal.SIGTERM, signal_handler)
|
|
781
816
|
|
|
782
817
|
|
|
783
|
-
def run_clientappio_api_grpc(
|
|
818
|
+
def run_clientappio_api_grpc(
|
|
819
|
+
address: str,
|
|
820
|
+
certificates: Optional[tuple[bytes, bytes, bytes]],
|
|
821
|
+
) -> tuple[grpc.Server, ClientAppIoServicer]:
|
|
784
822
|
"""Run ClientAppIo API gRPC server."""
|
|
785
823
|
clientappio_servicer: grpc.Server = ClientAppIoServicer()
|
|
786
824
|
clientappio_add_servicer_to_server_fn = add_ClientAppIoServicer_to_server
|
|
@@ -791,6 +829,7 @@ def run_clientappio_api_grpc(address: str) -> tuple[grpc.Server, ClientAppIoServ
|
|
|
791
829
|
),
|
|
792
830
|
server_address=address,
|
|
793
831
|
max_message_length=GRPC_MAX_MESSAGE_LENGTH,
|
|
832
|
+
certificates=certificates,
|
|
794
833
|
)
|
|
795
834
|
log(INFO, "Starting Flower ClientAppIo gRPC server on %s", address)
|
|
796
835
|
clientappio_grpc_server.start()
|