flwr-nightly 1.22.0.dev20250916__py3-none-any.whl → 1.22.0.dev20250918__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/new/new.py +4 -2
- flwr/cli/new/templates/app/README.flowertune.md.tpl +1 -1
- flwr/cli/new/templates/app/code/client.baseline.py.tpl +64 -47
- flwr/cli/new/templates/app/code/client.xgboost.py.tpl +110 -0
- flwr/cli/new/templates/app/code/flwr_tune/client_app.py.tpl +56 -90
- flwr/cli/new/templates/app/code/flwr_tune/models.py.tpl +1 -23
- flwr/cli/new/templates/app/code/flwr_tune/server_app.py.tpl +37 -58
- flwr/cli/new/templates/app/code/flwr_tune/strategy.py.tpl +39 -44
- flwr/cli/new/templates/app/code/model.baseline.py.tpl +0 -14
- flwr/cli/new/templates/app/code/server.baseline.py.tpl +27 -29
- flwr/cli/new/templates/app/code/server.xgboost.py.tpl +56 -0
- flwr/cli/new/templates/app/code/task.xgboost.py.tpl +67 -0
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +3 -3
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.xgboost.toml.tpl +61 -0
- flwr/cli/pull.py +100 -0
- flwr/cli/utils.py +17 -0
- flwr/common/constant.py +2 -0
- flwr/common/exit/exit_code.py +4 -0
- flwr/proto/control_pb2.py +7 -3
- flwr/proto/control_pb2.pyi +24 -0
- flwr/proto/control_pb2_grpc.py +34 -0
- flwr/proto/control_pb2_grpc.pyi +13 -0
- flwr/server/app.py +13 -0
- flwr/serverapp/strategy/__init__.py +4 -0
- flwr/serverapp/strategy/fedprox.py +174 -0
- flwr/serverapp/strategy/fedxgb_cyclic.py +220 -0
- flwr/simulation/app.py +1 -1
- flwr/simulation/run_simulation.py +25 -30
- flwr/supercore/cli/flower_superexec.py +26 -1
- flwr/supercore/constant.py +19 -0
- flwr/supercore/superexec/plugin/exec_plugin.py +11 -1
- flwr/supercore/superexec/run_superexec.py +16 -2
- flwr/superlink/artifact_provider/__init__.py +22 -0
- flwr/superlink/artifact_provider/artifact_provider.py +37 -0
- flwr/superlink/servicer/control/control_grpc.py +3 -0
- flwr/superlink/servicer/control/control_servicer.py +59 -2
- {flwr_nightly-1.22.0.dev20250916.dist-info → flwr_nightly-1.22.0.dev20250918.dist-info}/METADATA +1 -1
- {flwr_nightly-1.22.0.dev20250916.dist-info → flwr_nightly-1.22.0.dev20250918.dist-info}/RECORD +42 -33
- flwr/serverapp/strategy/strategy_utils_tests.py +0 -323
- {flwr_nightly-1.22.0.dev20250916.dist-info → flwr_nightly-1.22.0.dev20250918.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.22.0.dev20250916.dist-info → flwr_nightly-1.22.0.dev20250918.dist-info}/entry_points.txt +0 -0
@@ -143,6 +143,15 @@ def run_simulation_from_cli() -> None:
|
|
143
143
|
run = Run.create_empty(run_id)
|
144
144
|
run.override_config = override_config
|
145
145
|
|
146
|
+
# Create Context
|
147
|
+
server_app_context = Context(
|
148
|
+
run_id=run_id,
|
149
|
+
node_id=0,
|
150
|
+
node_config=UserConfig(),
|
151
|
+
state=RecordDict(),
|
152
|
+
run_config=fused_config,
|
153
|
+
)
|
154
|
+
|
146
155
|
_ = _run_simulation(
|
147
156
|
server_app_attr=server_app_attr,
|
148
157
|
client_app_attr=client_app_attr,
|
@@ -153,7 +162,7 @@ def run_simulation_from_cli() -> None:
|
|
153
162
|
run=run,
|
154
163
|
enable_tf_gpu_growth=args.enable_tf_gpu_growth,
|
155
164
|
verbose_logging=args.verbose,
|
156
|
-
|
165
|
+
server_app_context=server_app_context,
|
157
166
|
is_app=True,
|
158
167
|
exit_event=EventType.CLI_FLOWER_SIMULATION_LEAVE,
|
159
168
|
)
|
@@ -241,13 +250,12 @@ def run_simulation(
|
|
241
250
|
def run_serverapp_th(
|
242
251
|
server_app_attr: Optional[str],
|
243
252
|
server_app: Optional[ServerApp],
|
244
|
-
|
253
|
+
server_app_context: Context,
|
245
254
|
grid: Grid,
|
246
255
|
app_dir: str,
|
247
256
|
f_stop: threading.Event,
|
248
257
|
has_exception: threading.Event,
|
249
258
|
enable_tf_gpu_growth: bool,
|
250
|
-
run_id: int,
|
251
259
|
ctx_queue: "Queue[Context]",
|
252
260
|
) -> threading.Thread:
|
253
261
|
"""Run SeverApp in a thread."""
|
@@ -258,7 +266,6 @@ def run_serverapp_th(
|
|
258
266
|
exception_event: threading.Event,
|
259
267
|
_grid: Grid,
|
260
268
|
_server_app_dir: str,
|
261
|
-
_server_app_run_config: UserConfig,
|
262
269
|
_server_app_attr: Optional[str],
|
263
270
|
_server_app: Optional[ServerApp],
|
264
271
|
_ctx_queue: "Queue[Context]",
|
@@ -272,19 +279,10 @@ def run_serverapp_th(
|
|
272
279
|
log(INFO, "Enabling GPU growth for Tensorflow on the server thread.")
|
273
280
|
enable_gpu_growth()
|
274
281
|
|
275
|
-
# Initialize Context
|
276
|
-
context = Context(
|
277
|
-
run_id=run_id,
|
278
|
-
node_id=0,
|
279
|
-
node_config={},
|
280
|
-
state=RecordDict(),
|
281
|
-
run_config=_server_app_run_config,
|
282
|
-
)
|
283
|
-
|
284
282
|
# Run ServerApp
|
285
283
|
updated_context = _run(
|
286
284
|
grid=_grid,
|
287
|
-
context=
|
285
|
+
context=server_app_context,
|
288
286
|
server_app_dir=_server_app_dir,
|
289
287
|
server_app_attr=_server_app_attr,
|
290
288
|
loaded_server_app=_server_app,
|
@@ -310,7 +308,6 @@ def run_serverapp_th(
|
|
310
308
|
has_exception,
|
311
309
|
grid,
|
312
310
|
app_dir,
|
313
|
-
server_app_run_config,
|
314
311
|
server_app_attr,
|
315
312
|
server_app,
|
316
313
|
ctx_queue,
|
@@ -335,7 +332,7 @@ def _main_loop(
|
|
335
332
|
client_app_attr: Optional[str] = None,
|
336
333
|
server_app: Optional[ServerApp] = None,
|
337
334
|
server_app_attr: Optional[str] = None,
|
338
|
-
|
335
|
+
server_app_context: Optional[Context] = None,
|
339
336
|
) -> Context:
|
340
337
|
"""Start ServerApp on a separate thread, then launch Simulation Engine."""
|
341
338
|
# Initialize StateFactory
|
@@ -346,13 +343,15 @@ def _main_loop(
|
|
346
343
|
server_app_thread_has_exception = threading.Event()
|
347
344
|
serverapp_th = None
|
348
345
|
success = True
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
346
|
+
if server_app_context is None:
|
347
|
+
server_app_context = Context(
|
348
|
+
run_id=run.run_id,
|
349
|
+
node_id=0,
|
350
|
+
node_config=UserConfig(),
|
351
|
+
state=RecordDict(),
|
352
|
+
run_config=UserConfig(),
|
353
|
+
)
|
354
|
+
updated_context = server_app_context
|
356
355
|
try:
|
357
356
|
# Register run
|
358
357
|
log(DEBUG, "Pre-registering run with id %s", run.run_id)
|
@@ -361,9 +360,6 @@ def _main_loop(
|
|
361
360
|
run.running_at = run.starting_at
|
362
361
|
state_factory.state().run_ids[run.run_id] = RunRecord(run=run) # type: ignore
|
363
362
|
|
364
|
-
if server_app_run_config is None:
|
365
|
-
server_app_run_config = {}
|
366
|
-
|
367
363
|
# Initialize Grid
|
368
364
|
grid = InMemoryGrid(state_factory=state_factory)
|
369
365
|
grid.set_run(run_id=run.run_id)
|
@@ -373,13 +369,12 @@ def _main_loop(
|
|
373
369
|
serverapp_th = run_serverapp_th(
|
374
370
|
server_app_attr=server_app_attr,
|
375
371
|
server_app=server_app,
|
376
|
-
|
372
|
+
server_app_context=server_app_context,
|
377
373
|
grid=grid,
|
378
374
|
app_dir=app_dir,
|
379
375
|
f_stop=f_stop,
|
380
376
|
has_exception=server_app_thread_has_exception,
|
381
377
|
enable_tf_gpu_growth=enable_tf_gpu_growth,
|
382
|
-
run_id=run.run_id,
|
383
378
|
ctx_queue=output_context_queue,
|
384
379
|
)
|
385
380
|
|
@@ -438,7 +433,7 @@ def _run_simulation(
|
|
438
433
|
backend_config: Optional[BackendConfig] = None,
|
439
434
|
client_app_attr: Optional[str] = None,
|
440
435
|
server_app_attr: Optional[str] = None,
|
441
|
-
|
436
|
+
server_app_context: Optional[Context] = None,
|
442
437
|
app_dir: str = "",
|
443
438
|
flwr_dir: Optional[str] = None,
|
444
439
|
run: Optional[Run] = None,
|
@@ -502,7 +497,7 @@ def _run_simulation(
|
|
502
497
|
client_app_attr,
|
503
498
|
server_app,
|
504
499
|
server_app_attr,
|
505
|
-
|
500
|
+
server_app_context,
|
506
501
|
)
|
507
502
|
# Detect if there is an Asyncio event loop already running.
|
508
503
|
# If yes, disable logger propagation. In environmnets
|
@@ -17,7 +17,9 @@
|
|
17
17
|
|
18
18
|
import argparse
|
19
19
|
from logging import INFO
|
20
|
-
from typing import Optional
|
20
|
+
from typing import Any, Optional
|
21
|
+
|
22
|
+
import yaml
|
21
23
|
|
22
24
|
from flwr.common import EventType, event
|
23
25
|
from flwr.common.constant import ExecPluginType
|
@@ -26,6 +28,7 @@ from flwr.common.logger import log
|
|
26
28
|
from flwr.proto.clientappio_pb2_grpc import ClientAppIoStub
|
27
29
|
from flwr.proto.serverappio_pb2_grpc import ServerAppIoStub
|
28
30
|
from flwr.proto.simulationio_pb2_grpc import SimulationIoStub
|
31
|
+
from flwr.supercore.constant import EXEC_PLUGIN_SECTION
|
29
32
|
from flwr.supercore.grpc_health import add_args_health
|
30
33
|
from flwr.supercore.superexec.plugin import (
|
31
34
|
ClientAppExecPlugin,
|
@@ -36,6 +39,7 @@ from flwr.supercore.superexec.plugin import (
|
|
36
39
|
from flwr.supercore.superexec.run_superexec import run_superexec
|
37
40
|
|
38
41
|
try:
|
42
|
+
from flwr.ee import add_ee_args_superexec
|
39
43
|
from flwr.ee.constant import ExecEePluginType
|
40
44
|
from flwr.ee.exec_plugin import get_ee_plugin_and_stub_class
|
41
45
|
except ImportError:
|
@@ -54,6 +58,10 @@ except ImportError:
|
|
54
58
|
"""Get the EE plugin class and stub class based on the plugin type."""
|
55
59
|
return None
|
56
60
|
|
61
|
+
# pylint: disable-next=unused-argument
|
62
|
+
def add_ee_args_superexec(parser: argparse.ArgumentParser) -> None:
|
63
|
+
"""Add EE-specific arguments to the parser."""
|
64
|
+
|
57
65
|
|
58
66
|
def flower_superexec() -> None:
|
59
67
|
"""Run `flower-superexec` command."""
|
@@ -70,12 +78,28 @@ def flower_superexec() -> None:
|
|
70
78
|
# Trigger telemetry event
|
71
79
|
event(EventType.RUN_SUPEREXEC_ENTER, {"plugin_type": args.plugin_type})
|
72
80
|
|
81
|
+
# Load plugin config from YAML file if provided
|
82
|
+
plugin_config = None
|
83
|
+
if plugin_config_path := getattr(args, "plugin_config", None):
|
84
|
+
try:
|
85
|
+
with open(plugin_config_path, encoding="utf-8") as file:
|
86
|
+
yaml_config: Optional[dict[str, Any]] = yaml.safe_load(file)
|
87
|
+
if yaml_config is None or EXEC_PLUGIN_SECTION not in yaml_config:
|
88
|
+
raise ValueError(f"Missing '{EXEC_PLUGIN_SECTION}' section.")
|
89
|
+
plugin_config = yaml_config[EXEC_PLUGIN_SECTION]
|
90
|
+
except (FileNotFoundError, yaml.YAMLError, ValueError) as e:
|
91
|
+
flwr_exit(
|
92
|
+
ExitCode.SUPEREXEC_INVALID_PLUGIN_CONFIG,
|
93
|
+
f"Failed to load plugin config from '{plugin_config_path}': {e!r}",
|
94
|
+
)
|
95
|
+
|
73
96
|
# Get the plugin class and stub class based on the plugin type
|
74
97
|
plugin_class, stub_class = _get_plugin_and_stub_class(args.plugin_type)
|
75
98
|
run_superexec(
|
76
99
|
plugin_class=plugin_class,
|
77
100
|
stub_class=stub_class, # type: ignore
|
78
101
|
appio_api_address=args.appio_api_address,
|
102
|
+
plugin_config=plugin_config,
|
79
103
|
flwr_dir=args.flwr_dir,
|
80
104
|
parent_pid=args.parent_pid,
|
81
105
|
health_server_address=args.health_server_address,
|
@@ -122,6 +146,7 @@ def _parse_args() -> argparse.ArgumentParser:
|
|
122
146
|
help="The PID of the parent process. When set, the process will terminate "
|
123
147
|
"when the parent process exits.",
|
124
148
|
)
|
149
|
+
add_ee_args_superexec(parser)
|
125
150
|
add_args_health(parser)
|
126
151
|
return parser
|
127
152
|
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# Copyright 2025 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
|
+
"""Constants for Flower infrastructure."""
|
16
|
+
|
17
|
+
|
18
|
+
# Top-level key in YAML config for exec plugin settings
|
19
|
+
EXEC_PLUGIN_SECTION = "exec_plugin"
|
@@ -17,7 +17,7 @@
|
|
17
17
|
|
18
18
|
from abc import ABC, abstractmethod
|
19
19
|
from collections.abc import Sequence
|
20
|
-
from typing import Callable, Optional
|
20
|
+
from typing import Any, Callable, Optional
|
21
21
|
|
22
22
|
from flwr.common.typing import Run
|
23
23
|
|
@@ -69,3 +69,13 @@ class ExecPlugin(ABC):
|
|
69
69
|
The ID of the run associated with the token, used for tracking or
|
70
70
|
logging purposes.
|
71
71
|
"""
|
72
|
+
|
73
|
+
# This method is optional to implement
|
74
|
+
def load_config(self, yaml_config: dict[str, Any]) -> None:
|
75
|
+
"""Load configuration from a YAML dictionary.
|
76
|
+
|
77
|
+
Parameters
|
78
|
+
----------
|
79
|
+
yaml_config : dict[str, Any]
|
80
|
+
A dictionary representing the YAML configuration.
|
81
|
+
"""
|
@@ -17,10 +17,10 @@
|
|
17
17
|
|
18
18
|
import time
|
19
19
|
from logging import WARN
|
20
|
-
from typing import Optional, Union
|
20
|
+
from typing import Any, Optional, Union
|
21
21
|
|
22
22
|
from flwr.common.config import get_flwr_dir
|
23
|
-
from flwr.common.exit import register_signal_handlers
|
23
|
+
from flwr.common.exit import ExitCode, flwr_exit, register_signal_handlers
|
24
24
|
from flwr.common.grpc import create_channel, on_channel_state_change
|
25
25
|
from flwr.common.logger import log
|
26
26
|
from flwr.common.retry_invoker import _make_simple_grpc_retry_invoker, _wrap_stub
|
@@ -47,6 +47,7 @@ def run_superexec( # pylint: disable=R0913,R0914,R0917
|
|
47
47
|
type[ClientAppIoStub], type[ServerAppIoStub], type[SimulationIoStub]
|
48
48
|
],
|
49
49
|
appio_api_address: str,
|
50
|
+
plugin_config: Optional[dict[str, Any]] = None,
|
50
51
|
flwr_dir: Optional[str] = None,
|
51
52
|
parent_pid: Optional[int] = None,
|
52
53
|
health_server_address: Optional[str] = None,
|
@@ -61,6 +62,9 @@ def run_superexec( # pylint: disable=R0913,R0914,R0917
|
|
61
62
|
The gRPC stub class for the AppIO API.
|
62
63
|
appio_api_address : str
|
63
64
|
The address of the AppIO API.
|
65
|
+
plugin_config : Optional[dict[str, Any]] (default: None)
|
66
|
+
The configuration dictionary for the plugin. If `None`, the plugin will use
|
67
|
+
its default configuration.
|
64
68
|
flwr_dir : Optional[str] (default: None)
|
65
69
|
The Flower directory.
|
66
70
|
parent_pid : Optional[int] (default: None)
|
@@ -113,6 +117,16 @@ def run_superexec( # pylint: disable=R0913,R0914,R0917
|
|
113
117
|
get_run=get_run,
|
114
118
|
)
|
115
119
|
|
120
|
+
# Load plugin configuration from file if provided
|
121
|
+
try:
|
122
|
+
if plugin_config is not None:
|
123
|
+
plugin.load_config(plugin_config)
|
124
|
+
except (KeyError, ValueError) as e:
|
125
|
+
flwr_exit(
|
126
|
+
code=ExitCode.SUPEREXEC_INVALID_PLUGIN_CONFIG,
|
127
|
+
message=f"Invalid plugin config: {e!r}",
|
128
|
+
)
|
129
|
+
|
116
130
|
# Start the main loop
|
117
131
|
try:
|
118
132
|
while True:
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Copyright 2025 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
|
+
"""ArtifactProvider for SuperLink."""
|
16
|
+
|
17
|
+
|
18
|
+
from .artifact_provider import ArtifactProvider
|
19
|
+
|
20
|
+
__all__ = [
|
21
|
+
"ArtifactProvider",
|
22
|
+
]
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# Copyright 2025 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
|
+
"""Abstract base class for ArtifactProvider."""
|
16
|
+
|
17
|
+
|
18
|
+
from abc import ABC, abstractmethod
|
19
|
+
from typing import Optional
|
20
|
+
|
21
|
+
|
22
|
+
class ArtifactProvider(ABC):
|
23
|
+
"""ArtifactProvider interface for providing artifact download links."""
|
24
|
+
|
25
|
+
@abstractmethod
|
26
|
+
def get_url(self, run_id: int) -> Optional[str]:
|
27
|
+
"""Return the artifact download link for the given run ID."""
|
28
|
+
|
29
|
+
@property
|
30
|
+
@abstractmethod
|
31
|
+
def output_dir(self) -> str:
|
32
|
+
"""Permanent storage directory."""
|
33
|
+
|
34
|
+
@property
|
35
|
+
@abstractmethod
|
36
|
+
def tmp_dir(self) -> str:
|
37
|
+
"""Temporary storage directory."""
|
@@ -31,6 +31,7 @@ from flwr.server.superlink.linkstate import LinkStateFactory
|
|
31
31
|
from flwr.supercore.ffs import FfsFactory
|
32
32
|
from flwr.supercore.license_plugin import LicensePlugin
|
33
33
|
from flwr.supercore.object_store import ObjectStoreFactory
|
34
|
+
from flwr.superlink.artifact_provider import ArtifactProvider
|
34
35
|
|
35
36
|
from .control_event_log_interceptor import ControlEventLogInterceptor
|
36
37
|
from .control_license_interceptor import ControlLicenseInterceptor
|
@@ -56,6 +57,7 @@ def run_control_api_grpc(
|
|
56
57
|
auth_plugin: Optional[ControlAuthPlugin] = None,
|
57
58
|
authz_plugin: Optional[ControlAuthzPlugin] = None,
|
58
59
|
event_log_plugin: Optional[EventLogWriterPlugin] = None,
|
60
|
+
artifact_provider: Optional[ArtifactProvider] = None,
|
59
61
|
) -> grpc.Server:
|
60
62
|
"""Run Control API (gRPC, request-response)."""
|
61
63
|
license_plugin: Optional[LicensePlugin] = get_license_plugin()
|
@@ -68,6 +70,7 @@ def run_control_api_grpc(
|
|
68
70
|
objectstore_factory=objectstore_factory,
|
69
71
|
is_simulation=is_simulation,
|
70
72
|
auth_plugin=auth_plugin,
|
73
|
+
artifact_provider=artifact_provider,
|
71
74
|
)
|
72
75
|
interceptors: list[grpc.ServerInterceptor] = []
|
73
76
|
if license_plugin is not None:
|
@@ -29,7 +29,9 @@ from flwr.common.auth_plugin import ControlAuthPlugin
|
|
29
29
|
from flwr.common.constant import (
|
30
30
|
FAB_MAX_SIZE,
|
31
31
|
LOG_STREAM_INTERVAL,
|
32
|
+
NO_ARTIFACT_PROVIDER_MESSAGE,
|
32
33
|
NO_USER_AUTH_MESSAGE,
|
34
|
+
PULL_UNFINISHED_RUN_MESSAGE,
|
33
35
|
RUN_ID_NOT_FOUND_MESSAGE,
|
34
36
|
Status,
|
35
37
|
SubStatus,
|
@@ -49,6 +51,8 @@ from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
|
49
51
|
GetLoginDetailsResponse,
|
50
52
|
ListRunsRequest,
|
51
53
|
ListRunsResponse,
|
54
|
+
PullArtifactsRequest,
|
55
|
+
PullArtifactsResponse,
|
52
56
|
StartRunRequest,
|
53
57
|
StartRunResponse,
|
54
58
|
StopRunRequest,
|
@@ -59,6 +63,7 @@ from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
|
59
63
|
from flwr.server.superlink.linkstate import LinkState, LinkStateFactory
|
60
64
|
from flwr.supercore.ffs import FfsFactory
|
61
65
|
from flwr.supercore.object_store import ObjectStore, ObjectStoreFactory
|
66
|
+
from flwr.superlink.artifact_provider import ArtifactProvider
|
62
67
|
|
63
68
|
from .control_user_auth_interceptor import shared_account_info
|
64
69
|
|
@@ -73,14 +78,16 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
73
78
|
objectstore_factory: ObjectStoreFactory,
|
74
79
|
is_simulation: bool,
|
75
80
|
auth_plugin: Optional[ControlAuthPlugin] = None,
|
81
|
+
artifact_provider: Optional[ArtifactProvider] = None,
|
76
82
|
) -> None:
|
77
83
|
self.linkstate_factory = linkstate_factory
|
78
84
|
self.ffs_factory = ffs_factory
|
79
85
|
self.objectstore_factory = objectstore_factory
|
80
86
|
self.is_simulation = is_simulation
|
81
87
|
self.auth_plugin = auth_plugin
|
88
|
+
self.artifact_provider = artifact_provider
|
82
89
|
|
83
|
-
def StartRun(
|
90
|
+
def StartRun( # pylint: disable=too-many-locals
|
84
91
|
self, request: StartRunRequest, context: grpc.ServicerContext
|
85
92
|
) -> StartRunResponse:
|
86
93
|
"""Create run ID."""
|
@@ -126,11 +133,20 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
126
133
|
flwr_aid,
|
127
134
|
)
|
128
135
|
|
136
|
+
# Initialize node config
|
137
|
+
node_config = {}
|
138
|
+
if self.artifact_provider is not None:
|
139
|
+
node_config = {
|
140
|
+
"output_dir": self.artifact_provider.output_dir,
|
141
|
+
"tmp_dir": self.artifact_provider.tmp_dir,
|
142
|
+
}
|
143
|
+
|
129
144
|
# Create an empty context for the Run
|
130
145
|
context = Context(
|
131
146
|
run_id=run_id,
|
132
147
|
node_id=0,
|
133
|
-
|
148
|
+
# Dict is invariant in mypy
|
149
|
+
node_config=node_config, # type: ignore[arg-type]
|
134
150
|
state=RecordDict(),
|
135
151
|
run_config={},
|
136
152
|
)
|
@@ -335,6 +351,47 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
335
351
|
refresh_token=credentials.refresh_token,
|
336
352
|
)
|
337
353
|
|
354
|
+
def PullArtifacts(
|
355
|
+
self, request: PullArtifactsRequest, context: grpc.ServicerContext
|
356
|
+
) -> PullArtifactsResponse:
|
357
|
+
"""Pull artifacts for a given run ID."""
|
358
|
+
log(INFO, "ControlServicer.PullArtifacts")
|
359
|
+
|
360
|
+
# Check if artifact provider is configured
|
361
|
+
if self.artifact_provider is None:
|
362
|
+
context.abort(
|
363
|
+
grpc.StatusCode.UNIMPLEMENTED,
|
364
|
+
NO_ARTIFACT_PROVIDER_MESSAGE,
|
365
|
+
)
|
366
|
+
raise grpc.RpcError() # This line is unreachable
|
367
|
+
|
368
|
+
# Init link state
|
369
|
+
state = self.linkstate_factory.state()
|
370
|
+
|
371
|
+
# Retrieve run ID and run
|
372
|
+
run_id = request.run_id
|
373
|
+
run = state.get_run(run_id)
|
374
|
+
|
375
|
+
# Exit if `run_id` not found
|
376
|
+
if not run:
|
377
|
+
context.abort(grpc.StatusCode.NOT_FOUND, RUN_ID_NOT_FOUND_MESSAGE)
|
378
|
+
raise grpc.RpcError() # This line is unreachable
|
379
|
+
|
380
|
+
# Exit if the run is not finished yet
|
381
|
+
if run.status.status != Status.FINISHED:
|
382
|
+
context.abort(
|
383
|
+
grpc.StatusCode.FAILED_PRECONDITION, PULL_UNFINISHED_RUN_MESSAGE
|
384
|
+
)
|
385
|
+
|
386
|
+
# Check if `flwr_aid` matches the run's `flwr_aid` when user auth is enabled
|
387
|
+
if self.auth_plugin:
|
388
|
+
flwr_aid = shared_account_info.get().flwr_aid
|
389
|
+
_check_flwr_aid_in_run(flwr_aid=flwr_aid, run=run, context=context)
|
390
|
+
|
391
|
+
# Call artifact provider
|
392
|
+
download_url = self.artifact_provider.get_url(run_id)
|
393
|
+
return PullArtifactsResponse(url=download_url)
|
394
|
+
|
338
395
|
|
339
396
|
def _create_list_runs_response(
|
340
397
|
run_ids: set[int], state: LinkState, store: ObjectStore
|
{flwr_nightly-1.22.0.dev20250916.dist-info → flwr_nightly-1.22.0.dev20250918.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: flwr-nightly
|
3
|
-
Version: 1.22.0.
|
3
|
+
Version: 1.22.0.dev20250918
|
4
4
|
Summary: Flower: A Friendly Federated AI Framework
|
5
5
|
License: Apache-2.0
|
6
6
|
Keywords: Artificial Intelligence,Federated AI,Federated Analytics,Federated Evaluation,Federated Learning,Flower,Machine Learning
|