flwr 1.19.0__py3-none-any.whl → 1.21.0__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/__init__.py +4 -1
- flwr/app/__init__.py +28 -0
- flwr/app/exception.py +31 -0
- flwr/cli/auth_plugin/oidc_cli_plugin.py +4 -4
- flwr/cli/build.py +15 -5
- flwr/cli/cli_user_auth_interceptor.py +1 -1
- flwr/cli/config_utils.py +3 -3
- flwr/cli/constant.py +25 -8
- flwr/cli/log.py +9 -9
- flwr/cli/login/login.py +3 -3
- flwr/cli/ls.py +5 -5
- flwr/cli/new/new.py +23 -4
- flwr/cli/new/templates/app/README.flowertune.md.tpl +2 -0
- flwr/cli/new/templates/app/README.md.tpl +5 -0
- flwr/cli/new/templates/app/code/__init__.pytorch_msg_api.py.tpl +1 -0
- flwr/cli/new/templates/app/code/client.pytorch_msg_api.py.tpl +80 -0
- flwr/cli/new/templates/app/code/server.pytorch_msg_api.py.tpl +41 -0
- flwr/cli/new/templates/app/code/task.pytorch_msg_api.py.tpl +98 -0
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +14 -3
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +13 -1
- flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +21 -2
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +18 -1
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +19 -2
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +18 -1
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +20 -3
- flwr/cli/new/templates/app/pyproject.pytorch_msg_api.toml.tpl +53 -0
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +18 -1
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +18 -1
- flwr/cli/run/run.py +53 -50
- flwr/cli/stop.py +7 -4
- flwr/cli/utils.py +29 -11
- flwr/client/grpc_adapter_client/connection.py +11 -4
- flwr/client/grpc_rere_client/connection.py +93 -129
- flwr/client/rest_client/connection.py +134 -164
- flwr/clientapp/__init__.py +10 -0
- flwr/clientapp/mod/__init__.py +26 -0
- flwr/clientapp/mod/centraldp_mods.py +132 -0
- flwr/common/args.py +20 -6
- flwr/common/auth_plugin/__init__.py +4 -4
- flwr/common/auth_plugin/auth_plugin.py +7 -7
- flwr/common/constant.py +26 -5
- flwr/common/event_log_plugin/event_log_plugin.py +1 -1
- flwr/common/exit/__init__.py +4 -0
- flwr/common/exit/exit.py +8 -1
- flwr/common/exit/exit_code.py +42 -8
- flwr/common/exit/exit_handler.py +62 -0
- flwr/common/{exit_handlers.py → exit/signal_handler.py} +20 -37
- flwr/common/grpc.py +1 -1
- flwr/common/{inflatable_grpc_utils.py → inflatable_protobuf_utils.py} +52 -10
- flwr/common/inflatable_utils.py +191 -24
- flwr/common/logger.py +1 -1
- flwr/common/record/array.py +101 -22
- flwr/common/record/arraychunk.py +59 -0
- flwr/common/retry_invoker.py +30 -11
- flwr/common/serde.py +0 -28
- flwr/common/telemetry.py +4 -0
- flwr/compat/client/app.py +14 -31
- flwr/compat/server/app.py +2 -2
- flwr/proto/appio_pb2.py +51 -0
- flwr/proto/appio_pb2.pyi +195 -0
- flwr/proto/appio_pb2_grpc.py +4 -0
- flwr/proto/appio_pb2_grpc.pyi +4 -0
- flwr/proto/clientappio_pb2.py +4 -19
- flwr/proto/clientappio_pb2.pyi +0 -125
- flwr/proto/clientappio_pb2_grpc.py +269 -29
- flwr/proto/clientappio_pb2_grpc.pyi +114 -21
- flwr/proto/control_pb2.py +62 -0
- flwr/proto/{exec_pb2_grpc.py → control_pb2_grpc.py} +54 -54
- flwr/proto/{exec_pb2_grpc.pyi → control_pb2_grpc.pyi} +28 -28
- flwr/proto/fleet_pb2.py +12 -20
- flwr/proto/fleet_pb2.pyi +6 -36
- flwr/proto/serverappio_pb2.py +8 -31
- flwr/proto/serverappio_pb2.pyi +0 -152
- flwr/proto/serverappio_pb2_grpc.py +107 -38
- flwr/proto/serverappio_pb2_grpc.pyi +47 -20
- flwr/proto/simulationio_pb2.py +4 -11
- flwr/proto/simulationio_pb2.pyi +0 -58
- flwr/proto/simulationio_pb2_grpc.py +129 -27
- flwr/proto/simulationio_pb2_grpc.pyi +52 -13
- flwr/server/app.py +130 -153
- flwr/server/fleet_event_log_interceptor.py +4 -0
- flwr/server/grid/grpc_grid.py +94 -54
- flwr/server/grid/inmemory_grid.py +1 -0
- flwr/server/serverapp/app.py +165 -144
- flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +8 -0
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +1 -1
- flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +2 -5
- flwr/server/superlink/fleet/message_handler/message_handler.py +10 -16
- flwr/server/superlink/fleet/rest_rere/rest_api.py +1 -2
- flwr/server/superlink/fleet/vce/backend/raybackend.py +3 -1
- flwr/server/superlink/fleet/vce/vce_api.py +6 -6
- flwr/server/superlink/linkstate/in_memory_linkstate.py +34 -0
- flwr/server/superlink/linkstate/linkstate.py +2 -1
- flwr/server/superlink/linkstate/sqlite_linkstate.py +45 -0
- flwr/server/superlink/serverappio/serverappio_grpc.py +2 -2
- flwr/server/superlink/serverappio/serverappio_servicer.py +95 -48
- flwr/server/superlink/simulation/simulationio_grpc.py +1 -1
- flwr/server/superlink/simulation/simulationio_servicer.py +98 -22
- flwr/server/superlink/utils.py +0 -35
- flwr/serverapp/__init__.py +12 -0
- flwr/serverapp/dp_fixed_clipping.py +352 -0
- flwr/serverapp/exception.py +38 -0
- flwr/serverapp/strategy/__init__.py +38 -0
- flwr/serverapp/strategy/dp_fixed_clipping.py +352 -0
- flwr/serverapp/strategy/fedadagrad.py +162 -0
- flwr/serverapp/strategy/fedadam.py +181 -0
- flwr/serverapp/strategy/fedavg.py +295 -0
- flwr/serverapp/strategy/fedopt.py +218 -0
- flwr/serverapp/strategy/fedyogi.py +173 -0
- flwr/serverapp/strategy/result.py +105 -0
- flwr/serverapp/strategy/strategy.py +285 -0
- flwr/serverapp/strategy/strategy_utils.py +251 -0
- flwr/serverapp/strategy/strategy_utils_tests.py +304 -0
- flwr/simulation/app.py +159 -154
- flwr/simulation/run_simulation.py +17 -0
- flwr/supercore/app_utils.py +58 -0
- flwr/supercore/cli/__init__.py +22 -0
- flwr/supercore/cli/flower_superexec.py +141 -0
- flwr/supercore/corestate/__init__.py +22 -0
- flwr/supercore/corestate/corestate.py +81 -0
- flwr/{server/superlink → supercore}/ffs/disk_ffs.py +1 -1
- flwr/supercore/grpc_health/__init__.py +25 -0
- flwr/supercore/grpc_health/health_server.py +53 -0
- flwr/supercore/grpc_health/simple_health_servicer.py +38 -0
- flwr/supercore/license_plugin/__init__.py +22 -0
- flwr/supercore/license_plugin/license_plugin.py +26 -0
- flwr/supercore/object_store/in_memory_object_store.py +31 -31
- flwr/supercore/object_store/object_store.py +20 -42
- flwr/supercore/object_store/utils.py +43 -0
- flwr/{superexec → supercore/superexec}/__init__.py +1 -1
- flwr/supercore/superexec/plugin/__init__.py +28 -0
- flwr/supercore/superexec/plugin/base_exec_plugin.py +53 -0
- flwr/supercore/superexec/plugin/clientapp_exec_plugin.py +28 -0
- flwr/supercore/superexec/plugin/exec_plugin.py +71 -0
- flwr/supercore/superexec/plugin/serverapp_exec_plugin.py +28 -0
- flwr/supercore/superexec/plugin/simulation_exec_plugin.py +28 -0
- flwr/supercore/superexec/run_superexec.py +185 -0
- flwr/supercore/utils.py +32 -0
- flwr/superlink/servicer/__init__.py +15 -0
- flwr/superlink/servicer/control/__init__.py +22 -0
- flwr/{superexec/exec_event_log_interceptor.py → superlink/servicer/control/control_event_log_interceptor.py} +9 -5
- flwr/{superexec/exec_grpc.py → superlink/servicer/control/control_grpc.py} +39 -28
- flwr/superlink/servicer/control/control_license_interceptor.py +82 -0
- flwr/{superexec/exec_servicer.py → superlink/servicer/control/control_servicer.py} +79 -31
- flwr/{superexec/exec_user_auth_interceptor.py → superlink/servicer/control/control_user_auth_interceptor.py} +18 -10
- flwr/supernode/cli/flower_supernode.py +3 -7
- flwr/supernode/cli/flwr_clientapp.py +20 -16
- flwr/supernode/nodestate/in_memory_nodestate.py +13 -4
- flwr/supernode/nodestate/nodestate.py +3 -44
- flwr/supernode/runtime/run_clientapp.py +129 -115
- flwr/supernode/servicer/clientappio/__init__.py +1 -3
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +217 -165
- flwr/supernode/start_client_internal.py +205 -148
- {flwr-1.19.0.dist-info → flwr-1.21.0.dist-info}/METADATA +5 -3
- {flwr-1.19.0.dist-info → flwr-1.21.0.dist-info}/RECORD +161 -117
- {flwr-1.19.0.dist-info → flwr-1.21.0.dist-info}/entry_points.txt +1 -0
- flwr/common/inflatable_rest_utils.py +0 -99
- flwr/proto/exec_pb2.py +0 -62
- flwr/superexec/app.py +0 -45
- flwr/superexec/deployment.py +0 -192
- flwr/superexec/executor.py +0 -100
- flwr/superexec/simulation.py +0 -130
- /flwr/proto/{exec_pb2.pyi → control_pb2.pyi} +0 -0
- /flwr/{server/superlink → supercore}/ffs/__init__.py +0 -0
- /flwr/{server/superlink → supercore}/ffs/ffs.py +0 -0
- /flwr/{server/superlink → supercore}/ffs/ffs_factory.py +0 -0
- {flwr-1.19.0.dist-info → flwr-1.21.0.dist-info}/WHEEL +0 -0
|
@@ -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
|
+
"""Flower CoreState."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from .corestate import CoreState
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"CoreState",
|
|
22
|
+
]
|
|
@@ -0,0 +1,81 @@
|
|
|
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 CoreState."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from abc import ABC, abstractmethod
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CoreState(ABC):
|
|
23
|
+
"""Abstract base class for core state."""
|
|
24
|
+
|
|
25
|
+
@abstractmethod
|
|
26
|
+
def create_token(self, run_id: int) -> Optional[str]:
|
|
27
|
+
"""Create a token for the given run ID.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
run_id : int
|
|
32
|
+
The ID of the run for which to create a token.
|
|
33
|
+
|
|
34
|
+
Returns
|
|
35
|
+
-------
|
|
36
|
+
str
|
|
37
|
+
The newly generated token if one does not already exist
|
|
38
|
+
for the given run ID, otherwise None.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def verify_token(self, run_id: int, token: str) -> bool:
|
|
43
|
+
"""Verify a token for the given run ID.
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
run_id : int
|
|
48
|
+
The ID of the run for which to verify the token.
|
|
49
|
+
token : str
|
|
50
|
+
The token to verify.
|
|
51
|
+
|
|
52
|
+
Returns
|
|
53
|
+
-------
|
|
54
|
+
bool
|
|
55
|
+
True if the token is valid for the run ID, False otherwise.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
@abstractmethod
|
|
59
|
+
def delete_token(self, run_id: int) -> None:
|
|
60
|
+
"""Delete the token for the given run ID.
|
|
61
|
+
|
|
62
|
+
Parameters
|
|
63
|
+
----------
|
|
64
|
+
run_id : int
|
|
65
|
+
The ID of the run for which to delete the token.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
@abstractmethod
|
|
69
|
+
def get_run_id_by_token(self, token: str) -> Optional[int]:
|
|
70
|
+
"""Get the run ID associated with a given token.
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
token : str
|
|
75
|
+
The token to look up.
|
|
76
|
+
|
|
77
|
+
Returns
|
|
78
|
+
-------
|
|
79
|
+
Optional[int]
|
|
80
|
+
The run ID if the token is valid, otherwise None.
|
|
81
|
+
"""
|
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
"""GRPC health servicers."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from .health_server import add_args_health, run_health_server_grpc_no_tls
|
|
19
|
+
from .simple_health_servicer import SimpleHealthServicer
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"SimpleHealthServicer",
|
|
23
|
+
"add_args_health",
|
|
24
|
+
"run_health_server_grpc_no_tls",
|
|
25
|
+
]
|
|
@@ -0,0 +1,53 @@
|
|
|
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
|
+
"""Health servers."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
from logging import INFO
|
|
20
|
+
|
|
21
|
+
import grpc
|
|
22
|
+
from grpc_health.v1.health_pb2_grpc import add_HealthServicer_to_server
|
|
23
|
+
|
|
24
|
+
from flwr.common.grpc import generic_create_grpc_server
|
|
25
|
+
from flwr.common.logger import log
|
|
26
|
+
|
|
27
|
+
from .simple_health_servicer import SimpleHealthServicer
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def run_health_server_grpc_no_tls(address: str) -> grpc.Server:
|
|
31
|
+
"""Run gRPC health server with no TLS."""
|
|
32
|
+
health_server = generic_create_grpc_server(
|
|
33
|
+
servicer_and_add_fn=(
|
|
34
|
+
SimpleHealthServicer(),
|
|
35
|
+
add_HealthServicer_to_server,
|
|
36
|
+
),
|
|
37
|
+
server_address=address,
|
|
38
|
+
certificates=None,
|
|
39
|
+
)
|
|
40
|
+
log(INFO, "Starting gRPC health server on %s", address)
|
|
41
|
+
health_server.start()
|
|
42
|
+
return health_server
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def add_args_health(parser: argparse.ArgumentParser) -> None:
|
|
46
|
+
"""Add arguments for health server."""
|
|
47
|
+
parser.add_argument(
|
|
48
|
+
"--health-server-address",
|
|
49
|
+
type=str,
|
|
50
|
+
default=None,
|
|
51
|
+
help="Health service gRPC server address (IPv4, IPv6, or a domain name) "
|
|
52
|
+
"with no TLS. If not set, the health server will not be started.",
|
|
53
|
+
)
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
"""Simple gRPC health servicers."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
import grpc
|
|
19
|
+
|
|
20
|
+
# pylint: disable=E0611
|
|
21
|
+
from grpc_health.v1.health_pb2 import HealthCheckRequest, HealthCheckResponse
|
|
22
|
+
from grpc_health.v1.health_pb2_grpc import HealthServicer
|
|
23
|
+
|
|
24
|
+
# pylint: enable=E0611
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SimpleHealthServicer(HealthServicer): # type: ignore
|
|
28
|
+
"""A simple gRPC health servicer that always returns SERVING."""
|
|
29
|
+
|
|
30
|
+
def Check(
|
|
31
|
+
self, request: HealthCheckRequest, context: grpc.ServicerContext
|
|
32
|
+
) -> HealthCheckResponse:
|
|
33
|
+
"""Return a HealthCheckResponse with SERVING status."""
|
|
34
|
+
return HealthCheckResponse(status=HealthCheckResponse.SERVING)
|
|
35
|
+
|
|
36
|
+
def Watch(self, request: HealthCheckRequest, context: grpc.ServicerContext) -> None:
|
|
37
|
+
"""Watch the health status (not implemented)."""
|
|
38
|
+
context.abort(grpc.StatusCode.UNIMPLEMENTED, "Watch is not implemented")
|
|
@@ -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
|
+
"""Flower license plugin components."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from .license_plugin import LicensePlugin as LicensePlugin
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"LicensePlugin",
|
|
22
|
+
]
|
|
@@ -0,0 +1,26 @@
|
|
|
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 class for Flower License Plugin."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from abc import ABC, abstractmethod
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class LicensePlugin(ABC):
|
|
22
|
+
"""Abstract Flower License Plugin class."""
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def check_license(self) -> bool:
|
|
26
|
+
"""Check if the license is valid."""
|
|
@@ -20,7 +20,6 @@ from dataclasses import dataclass
|
|
|
20
20
|
from typing import Optional
|
|
21
21
|
|
|
22
22
|
from flwr.common.inflatable import (
|
|
23
|
-
get_object_children_ids_from_object_content,
|
|
24
23
|
get_object_id,
|
|
25
24
|
is_valid_sha256_hash,
|
|
26
25
|
iterate_object_tree,
|
|
@@ -37,6 +36,7 @@ class ObjectEntry:
|
|
|
37
36
|
|
|
38
37
|
content: bytes
|
|
39
38
|
is_available: bool
|
|
39
|
+
child_object_ids: list[str] # List of child object IDs
|
|
40
40
|
ref_count: int # Number of references (direct parents) to this object
|
|
41
41
|
runs: set[int] # Set of run IDs that used this object
|
|
42
42
|
|
|
@@ -70,6 +70,9 @@ class InMemoryObjectStore(ObjectStore):
|
|
|
70
70
|
self.store[obj_id] = ObjectEntry(
|
|
71
71
|
content=b"", # Initially empty content
|
|
72
72
|
is_available=False, # Initially not available
|
|
73
|
+
child_object_ids=[ # List of child object IDs
|
|
74
|
+
child.object_id for child in tree_node.children
|
|
75
|
+
],
|
|
73
76
|
ref_count=0, # Reference count starts at 0
|
|
74
77
|
runs={run_id}, # Start with the current run ID
|
|
75
78
|
)
|
|
@@ -102,6 +105,32 @@ class InMemoryObjectStore(ObjectStore):
|
|
|
102
105
|
|
|
103
106
|
return new_objects
|
|
104
107
|
|
|
108
|
+
def get_object_tree(self, object_id: str) -> ObjectTree:
|
|
109
|
+
"""Get the object tree for a given object ID."""
|
|
110
|
+
with self.lock_store:
|
|
111
|
+
# Raise an exception if there's no object with the given ID
|
|
112
|
+
if not (object_entry := self.store.get(object_id)):
|
|
113
|
+
raise NoObjectInStoreError(
|
|
114
|
+
f"Object with ID '{object_id}' was not pre-registered."
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Build the object trees of all children
|
|
118
|
+
try:
|
|
119
|
+
child_trees = [
|
|
120
|
+
self.get_object_tree(child_id)
|
|
121
|
+
for child_id in object_entry.child_object_ids
|
|
122
|
+
]
|
|
123
|
+
except NoObjectInStoreError as e:
|
|
124
|
+
# Raise an error if any child object is missing
|
|
125
|
+
# This indicates an integrity issue
|
|
126
|
+
raise NoObjectInStoreError(
|
|
127
|
+
f"Object tree for object ID '{object_id}' contains missing "
|
|
128
|
+
"children. This may indicate a corrupted object store."
|
|
129
|
+
) from e
|
|
130
|
+
|
|
131
|
+
# Create and return the ObjectTree for the current object
|
|
132
|
+
return ObjectTree(object_id=object_id, children=child_trees)
|
|
133
|
+
|
|
105
134
|
def put(self, object_id: str, object_content: bytes) -> None:
|
|
106
135
|
"""Put an object into the store."""
|
|
107
136
|
if self.verify:
|
|
@@ -128,29 +157,6 @@ class InMemoryObjectStore(ObjectStore):
|
|
|
128
157
|
self.store[object_id].content = object_content
|
|
129
158
|
self.store[object_id].is_available = True
|
|
130
159
|
|
|
131
|
-
def set_message_descendant_ids(
|
|
132
|
-
self, msg_object_id: str, descendant_ids: list[str]
|
|
133
|
-
) -> None:
|
|
134
|
-
"""Store the mapping from a ``Message`` object ID to the object IDs of its
|
|
135
|
-
descendants."""
|
|
136
|
-
with self.lock_msg_mapping:
|
|
137
|
-
self.msg_descendant_objects_mapping[msg_object_id] = descendant_ids
|
|
138
|
-
|
|
139
|
-
def get_message_descendant_ids(self, msg_object_id: str) -> list[str]:
|
|
140
|
-
"""Retrieve the object IDs of all descendants of a given Message."""
|
|
141
|
-
with self.lock_msg_mapping:
|
|
142
|
-
if msg_object_id not in self.msg_descendant_objects_mapping:
|
|
143
|
-
raise NoObjectInStoreError(
|
|
144
|
-
f"No message registered in Object Store with ID '{msg_object_id}'. "
|
|
145
|
-
"Mapping to descendants could not be found."
|
|
146
|
-
)
|
|
147
|
-
return self.msg_descendant_objects_mapping[msg_object_id]
|
|
148
|
-
|
|
149
|
-
def delete_message_descendant_ids(self, msg_object_id: str) -> None:
|
|
150
|
-
"""Delete the mapping from a ``Message`` object ID to its descendants."""
|
|
151
|
-
with self.lock_msg_mapping:
|
|
152
|
-
self.msg_descendant_objects_mapping.pop(msg_object_id, None)
|
|
153
|
-
|
|
154
160
|
def get(self, object_id: str) -> Optional[bytes]:
|
|
155
161
|
"""Get an object from the store."""
|
|
156
162
|
with self.lock_store:
|
|
@@ -177,10 +183,7 @@ class InMemoryObjectStore(ObjectStore):
|
|
|
177
183
|
self.run_objects_mapping[run_id].discard(object_id)
|
|
178
184
|
|
|
179
185
|
# Decrease the reference count of its children
|
|
180
|
-
|
|
181
|
-
object_entry.content
|
|
182
|
-
)
|
|
183
|
-
for child_id in children_ids:
|
|
186
|
+
for child_id in object_entry.child_object_ids:
|
|
184
187
|
self.store[child_id].ref_count -= 1
|
|
185
188
|
|
|
186
189
|
# Recursively try to delete the child object
|
|
@@ -205,9 +208,6 @@ class InMemoryObjectStore(ObjectStore):
|
|
|
205
208
|
# Delete the message object and its unreferenced descendants
|
|
206
209
|
self.delete(object_id)
|
|
207
210
|
|
|
208
|
-
# Delete the message's descendants mapping
|
|
209
|
-
self.delete_message_descendant_ids(object_id)
|
|
210
|
-
|
|
211
211
|
# Remove the run from the mapping
|
|
212
212
|
del self.run_objects_mapping[run_id]
|
|
213
213
|
|
|
@@ -60,6 +60,22 @@ class ObjectStore(abc.ABC):
|
|
|
60
60
|
in the `ObjectStore`, or were preregistered but are not yet available.
|
|
61
61
|
"""
|
|
62
62
|
|
|
63
|
+
@abc.abstractmethod
|
|
64
|
+
def get_object_tree(self, object_id: str) -> ObjectTree:
|
|
65
|
+
"""Get the object tree for a given object ID.
|
|
66
|
+
|
|
67
|
+
Parameters
|
|
68
|
+
----------
|
|
69
|
+
object_id : str
|
|
70
|
+
The ID of the object for which to retrieve the object tree.
|
|
71
|
+
|
|
72
|
+
Returns
|
|
73
|
+
-------
|
|
74
|
+
ObjectTree
|
|
75
|
+
An ObjectTree representing the hierarchical structure of the object with
|
|
76
|
+
the given ID and its descendants.
|
|
77
|
+
"""
|
|
78
|
+
|
|
63
79
|
@abc.abstractmethod
|
|
64
80
|
def put(self, object_id: str, object_content: bytes) -> None:
|
|
65
81
|
"""Put an object into the store.
|
|
@@ -83,8 +99,10 @@ class ObjectStore(abc.ABC):
|
|
|
83
99
|
|
|
84
100
|
Returns
|
|
85
101
|
-------
|
|
86
|
-
bytes
|
|
87
|
-
The object stored under the given object_id.
|
|
102
|
+
Optional[bytes]
|
|
103
|
+
The object stored under the given object_id if it exists, else None.
|
|
104
|
+
The returned bytes will be b"" if the object is not yet available,
|
|
105
|
+
but has been preregistered.
|
|
88
106
|
"""
|
|
89
107
|
|
|
90
108
|
@abc.abstractmethod
|
|
@@ -126,46 +144,6 @@ class ObjectStore(abc.ABC):
|
|
|
126
144
|
This method should remove all objects from the store.
|
|
127
145
|
"""
|
|
128
146
|
|
|
129
|
-
@abc.abstractmethod
|
|
130
|
-
def set_message_descendant_ids(
|
|
131
|
-
self, msg_object_id: str, descendant_ids: list[str]
|
|
132
|
-
) -> None:
|
|
133
|
-
"""Store the mapping from a ``Message`` object ID to the object IDs of its
|
|
134
|
-
descendants.
|
|
135
|
-
|
|
136
|
-
Parameters
|
|
137
|
-
----------
|
|
138
|
-
msg_object_id : str
|
|
139
|
-
The object ID of the ``Message``.
|
|
140
|
-
descendant_ids : list[str]
|
|
141
|
-
A list of object IDs representing all descendant objects of the ``Message``.
|
|
142
|
-
"""
|
|
143
|
-
|
|
144
|
-
@abc.abstractmethod
|
|
145
|
-
def get_message_descendant_ids(self, msg_object_id: str) -> list[str]:
|
|
146
|
-
"""Retrieve the object IDs of all descendants of a given ``Message``.
|
|
147
|
-
|
|
148
|
-
Parameters
|
|
149
|
-
----------
|
|
150
|
-
msg_object_id : str
|
|
151
|
-
The object ID of the ``Message``.
|
|
152
|
-
|
|
153
|
-
Returns
|
|
154
|
-
-------
|
|
155
|
-
list[str]
|
|
156
|
-
A list of object IDs of all descendant objects of the ``Message``.
|
|
157
|
-
"""
|
|
158
|
-
|
|
159
|
-
@abc.abstractmethod
|
|
160
|
-
def delete_message_descendant_ids(self, msg_object_id: str) -> None:
|
|
161
|
-
"""Delete the mapping from a ``Message`` object ID to its descendants.
|
|
162
|
-
|
|
163
|
-
Parameters
|
|
164
|
-
----------
|
|
165
|
-
msg_object_id : str
|
|
166
|
-
The object ID of the ``Message``.
|
|
167
|
-
"""
|
|
168
|
-
|
|
169
147
|
@abc.abstractmethod
|
|
170
148
|
def __contains__(self, object_id: str) -> bool:
|
|
171
149
|
"""Check if an object_id is in the store.
|
|
@@ -0,0 +1,43 @@
|
|
|
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
|
+
"""Utils for ObjectStore."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from typing import Union
|
|
19
|
+
|
|
20
|
+
from flwr.proto.appio_pb2 import PushAppMessagesRequest # pylint: disable=E0611
|
|
21
|
+
from flwr.proto.fleet_pb2 import PushMessagesRequest # pylint: disable=E0611
|
|
22
|
+
|
|
23
|
+
from . import ObjectStore
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def store_mapping_and_register_objects(
|
|
27
|
+
store: ObjectStore, request: Union[PushAppMessagesRequest, PushMessagesRequest]
|
|
28
|
+
) -> set[str]:
|
|
29
|
+
"""Store Message object to descendants mapping and preregister objects."""
|
|
30
|
+
if not request.messages_list:
|
|
31
|
+
return set()
|
|
32
|
+
objects_to_push: set[str] = set()
|
|
33
|
+
# Get run_id from the first message in the list
|
|
34
|
+
# All messages of a request should in the same run
|
|
35
|
+
run_id = request.messages_list[0].metadata.run_id
|
|
36
|
+
|
|
37
|
+
for object_tree in request.message_object_trees:
|
|
38
|
+
# Preregister
|
|
39
|
+
unavailable_obj_ids = store.preregister(run_id, object_tree)
|
|
40
|
+
# Keep track of objects that need to be pushed
|
|
41
|
+
objects_to_push |= set(unavailable_obj_ids)
|
|
42
|
+
|
|
43
|
+
return objects_to_push
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
"""Flower SuperExec plugins."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from .clientapp_exec_plugin import ClientAppExecPlugin
|
|
19
|
+
from .exec_plugin import ExecPlugin
|
|
20
|
+
from .serverapp_exec_plugin import ServerAppExecPlugin
|
|
21
|
+
from .simulation_exec_plugin import SimulationExecPlugin
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"ClientAppExecPlugin",
|
|
25
|
+
"ExecPlugin",
|
|
26
|
+
"ServerAppExecPlugin",
|
|
27
|
+
"SimulationExecPlugin",
|
|
28
|
+
]
|
|
@@ -0,0 +1,53 @@
|
|
|
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
|
+
"""Simple base Flower SuperExec plugin for app processes."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
import subprocess
|
|
20
|
+
from collections.abc import Sequence
|
|
21
|
+
from typing import Optional
|
|
22
|
+
|
|
23
|
+
from .exec_plugin import ExecPlugin
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class BaseExecPlugin(ExecPlugin):
|
|
27
|
+
"""Simple Flower SuperExec plugin for app processes.
|
|
28
|
+
|
|
29
|
+
The plugin always selects the first candidate run ID.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
# Placeholders to be defined in subclasses
|
|
33
|
+
command = ""
|
|
34
|
+
appio_api_address_arg = ""
|
|
35
|
+
|
|
36
|
+
def select_run_id(self, candidate_run_ids: Sequence[int]) -> Optional[int]:
|
|
37
|
+
"""Select a run ID to execute from a sequence of candidates."""
|
|
38
|
+
if not candidate_run_ids:
|
|
39
|
+
return None
|
|
40
|
+
return candidate_run_ids[0]
|
|
41
|
+
|
|
42
|
+
def launch_app(self, token: str, run_id: int) -> None:
|
|
43
|
+
"""Launch the application associated with a given run ID and token."""
|
|
44
|
+
cmds = [self.command, "--insecure"]
|
|
45
|
+
cmds += [self.appio_api_address_arg, self.appio_api_address]
|
|
46
|
+
cmds += ["--token", token]
|
|
47
|
+
cmds += ["--parent-pid", str(os.getpid())]
|
|
48
|
+
cmds += ["--flwr-dir", self.flwr_dir]
|
|
49
|
+
# Launch the client app without waiting for it to complete.
|
|
50
|
+
# Since we don't need to manage the process, we intentionally avoid using
|
|
51
|
+
# a `with` statement. Suppress the pylint warning for it in this case.
|
|
52
|
+
# pylint: disable-next=consider-using-with
|
|
53
|
+
subprocess.Popen(cmds)
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
"""Simple Flower SuperExec plugin for ClientApp."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from .base_exec_plugin import BaseExecPlugin
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ClientAppExecPlugin(BaseExecPlugin):
|
|
22
|
+
"""Simple Flower SuperExec plugin for ClientApp.
|
|
23
|
+
|
|
24
|
+
The plugin always selects the first candidate run ID.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
command = "flwr-clientapp"
|
|
28
|
+
appio_api_address_arg = "--clientappio-api-address"
|