flwr-nightly 1.10.0.dev20240616__py3-none-any.whl → 1.10.0.dev20240618__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of flwr-nightly might be problematic. Click here for more details.
- flwr/client/grpc_adapter_client/__init__.py +15 -0
- flwr/client/grpc_adapter_client/connection.py +94 -0
- flwr/client/grpc_client/connection.py +5 -1
- flwr/client/grpc_rere_client/connection.py +8 -1
- flwr/client/grpc_rere_client/grpc_adapter.py +133 -0
- flwr/client/rest_client/connection.py +9 -1
- flwr/client/supernode/app.py +17 -43
- flwr/common/config.py +44 -1
- flwr/common/constant.py +9 -0
- flwr/common/typing.py +9 -0
- flwr/proto/exec_pb2.py +7 -3
- flwr/proto/exec_pb2.pyi +23 -0
- flwr/proto/exec_pb2_grpc.py +34 -0
- flwr/proto/exec_pb2_grpc.pyi +14 -0
- flwr/server/run_serverapp.py +1 -1
- flwr/server/superlink/fleet/message_handler/message_handler.py +3 -3
- flwr/server/superlink/fleet/vce/vce_api.py +2 -0
- flwr/server/superlink/state/in_memory_state.py +8 -5
- flwr/server/superlink/state/sqlite_state.py +6 -3
- flwr/server/superlink/state/state.py +5 -4
- flwr/superexec/exec_servicer.py +12 -1
- {flwr_nightly-1.10.0.dev20240616.dist-info → flwr_nightly-1.10.0.dev20240618.dist-info}/METADATA +1 -1
- {flwr_nightly-1.10.0.dev20240616.dist-info → flwr_nightly-1.10.0.dev20240618.dist-info}/RECORD +26 -23
- {flwr_nightly-1.10.0.dev20240616.dist-info → flwr_nightly-1.10.0.dev20240618.dist-info}/LICENSE +0 -0
- {flwr_nightly-1.10.0.dev20240616.dist-info → flwr_nightly-1.10.0.dev20240618.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.10.0.dev20240616.dist-info → flwr_nightly-1.10.0.dev20240618.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Copyright 2024 Flower Labs GmbH. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
# ==============================================================================
|
|
15
|
+
"""Client-side part of the GrpcAdapter transport layer."""
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Copyright 2024 Flower Labs GmbH. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
# ==============================================================================
|
|
15
|
+
"""Contextmanager for a GrpcAdapter channel to the Flower server."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from contextlib import contextmanager
|
|
19
|
+
from logging import ERROR
|
|
20
|
+
from typing import Callable, Iterator, Optional, Tuple, Union
|
|
21
|
+
|
|
22
|
+
from cryptography.hazmat.primitives.asymmetric import ec
|
|
23
|
+
|
|
24
|
+
from flwr.client.grpc_rere_client.connection import grpc_request_response
|
|
25
|
+
from flwr.client.grpc_rere_client.grpc_adapter import GrpcAdapter
|
|
26
|
+
from flwr.common import GRPC_MAX_MESSAGE_LENGTH
|
|
27
|
+
from flwr.common.logger import log
|
|
28
|
+
from flwr.common.message import Message
|
|
29
|
+
from flwr.common.retry_invoker import RetryInvoker
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@contextmanager
|
|
33
|
+
def grpc_adapter( # pylint: disable=R0913
|
|
34
|
+
server_address: str,
|
|
35
|
+
insecure: bool,
|
|
36
|
+
retry_invoker: RetryInvoker,
|
|
37
|
+
max_message_length: int = GRPC_MAX_MESSAGE_LENGTH, # pylint: disable=W0613
|
|
38
|
+
root_certificates: Optional[Union[bytes, str]] = None,
|
|
39
|
+
authentication_keys: Optional[ # pylint: disable=unused-argument
|
|
40
|
+
Tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]
|
|
41
|
+
] = None,
|
|
42
|
+
) -> Iterator[
|
|
43
|
+
Tuple[
|
|
44
|
+
Callable[[], Optional[Message]],
|
|
45
|
+
Callable[[Message], None],
|
|
46
|
+
Optional[Callable[[], None]],
|
|
47
|
+
Optional[Callable[[], None]],
|
|
48
|
+
Optional[Callable[[int], Tuple[str, str]]],
|
|
49
|
+
]
|
|
50
|
+
]:
|
|
51
|
+
"""Primitives for request/response-based interaction with a server via GrpcAdapter.
|
|
52
|
+
|
|
53
|
+
Parameters
|
|
54
|
+
----------
|
|
55
|
+
server_address : str
|
|
56
|
+
The IPv6 address of the server with `http://` or `https://`.
|
|
57
|
+
If the Flower server runs on the same machine
|
|
58
|
+
on port 8080, then `server_address` would be `"http://[::]:8080"`.
|
|
59
|
+
insecure : bool
|
|
60
|
+
Starts an insecure gRPC connection when True. Enables HTTPS connection
|
|
61
|
+
when False, using system certificates if `root_certificates` is None.
|
|
62
|
+
retry_invoker: RetryInvoker
|
|
63
|
+
`RetryInvoker` object that will try to reconnect the client to the server
|
|
64
|
+
after gRPC errors. If None, the client will only try to
|
|
65
|
+
reconnect once after a failure.
|
|
66
|
+
max_message_length : int
|
|
67
|
+
Ignored, only present to preserve API-compatibility.
|
|
68
|
+
root_certificates : Optional[Union[bytes, str]] (default: None)
|
|
69
|
+
Path of the root certificate. If provided, a secure
|
|
70
|
+
connection using the certificates will be established to an SSL-enabled
|
|
71
|
+
Flower server. Bytes won't work for the REST API.
|
|
72
|
+
authentication_keys : Optional[Tuple[PrivateKey, PublicKey]] (default: None)
|
|
73
|
+
Client authentication is not supported for this transport type.
|
|
74
|
+
|
|
75
|
+
Returns
|
|
76
|
+
-------
|
|
77
|
+
receive : Callable
|
|
78
|
+
send : Callable
|
|
79
|
+
create_node : Optional[Callable]
|
|
80
|
+
delete_node : Optional[Callable]
|
|
81
|
+
get_run : Optional[Callable]
|
|
82
|
+
"""
|
|
83
|
+
if authentication_keys is not None:
|
|
84
|
+
log(ERROR, "Client authentication is not supported for this transport type.")
|
|
85
|
+
with grpc_request_response(
|
|
86
|
+
server_address=server_address,
|
|
87
|
+
insecure=insecure,
|
|
88
|
+
retry_invoker=retry_invoker,
|
|
89
|
+
max_message_length=max_message_length,
|
|
90
|
+
root_certificates=root_certificates,
|
|
91
|
+
authentication_keys=None, # Authentication is not supported
|
|
92
|
+
adapter_cls=GrpcAdapter,
|
|
93
|
+
) as conn:
|
|
94
|
+
yield conn
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import uuid
|
|
19
19
|
from contextlib import contextmanager
|
|
20
|
-
from logging import DEBUG
|
|
20
|
+
from logging import DEBUG, ERROR
|
|
21
21
|
from pathlib import Path
|
|
22
22
|
from queue import Queue
|
|
23
23
|
from typing import Callable, Iterator, Optional, Tuple, Union, cast
|
|
@@ -101,6 +101,8 @@ def grpc_connection( # pylint: disable=R0913, R0915
|
|
|
101
101
|
The PEM-encoded root certificates as a byte string or a path string.
|
|
102
102
|
If provided, a secure connection using the certificates will be
|
|
103
103
|
established to an SSL-enabled Flower server.
|
|
104
|
+
authentication_keys : Optional[Tuple[PrivateKey, PublicKey]] (default: None)
|
|
105
|
+
Client authentication is not supported for this transport type.
|
|
104
106
|
|
|
105
107
|
Returns
|
|
106
108
|
-------
|
|
@@ -123,6 +125,8 @@ def grpc_connection( # pylint: disable=R0913, R0915
|
|
|
123
125
|
"""
|
|
124
126
|
if isinstance(root_certificates, str):
|
|
125
127
|
root_certificates = Path(root_certificates).read_bytes()
|
|
128
|
+
if authentication_keys is not None:
|
|
129
|
+
log(ERROR, "Client authentication is not supported for this transport type.")
|
|
126
130
|
|
|
127
131
|
channel = create_channel(
|
|
128
132
|
server_address=server_address,
|
|
@@ -55,6 +55,7 @@ from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=
|
|
|
55
55
|
from flwr.proto.task_pb2 import TaskIns # pylint: disable=E0611
|
|
56
56
|
|
|
57
57
|
from .client_interceptor import AuthenticateClientInterceptor
|
|
58
|
+
from .grpc_adapter import GrpcAdapter
|
|
58
59
|
|
|
59
60
|
|
|
60
61
|
def on_channel_state_change(channel_connectivity: str) -> None:
|
|
@@ -72,7 +73,7 @@ def grpc_request_response( # pylint: disable=R0913, R0914, R0915
|
|
|
72
73
|
authentication_keys: Optional[
|
|
73
74
|
Tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]
|
|
74
75
|
] = None,
|
|
75
|
-
adapter_cls: Optional[Type[FleetStub]] = None,
|
|
76
|
+
adapter_cls: Optional[Union[Type[FleetStub], Type[GrpcAdapter]]] = None,
|
|
76
77
|
) -> Iterator[
|
|
77
78
|
Tuple[
|
|
78
79
|
Callable[[], Optional[Message]],
|
|
@@ -106,6 +107,11 @@ def grpc_request_response( # pylint: disable=R0913, R0914, R0915
|
|
|
106
107
|
Path of the root certificate. If provided, a secure
|
|
107
108
|
connection using the certificates will be established to an SSL-enabled
|
|
108
109
|
Flower server. Bytes won't work for the REST API.
|
|
110
|
+
authentication_keys : Optional[Tuple[PrivateKey, PublicKey]] (default: None)
|
|
111
|
+
Tuple containing the elliptic curve private key and public key for
|
|
112
|
+
authentication from the cryptography library.
|
|
113
|
+
Source: https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec/
|
|
114
|
+
Used to establish an authenticated connection with the server.
|
|
109
115
|
|
|
110
116
|
Returns
|
|
111
117
|
-------
|
|
@@ -113,6 +119,7 @@ def grpc_request_response( # pylint: disable=R0913, R0914, R0915
|
|
|
113
119
|
send : Callable
|
|
114
120
|
create_node : Optional[Callable]
|
|
115
121
|
delete_node : Optional[Callable]
|
|
122
|
+
get_run : Optional[Callable]
|
|
116
123
|
"""
|
|
117
124
|
if isinstance(root_certificates, str):
|
|
118
125
|
root_certificates = Path(root_certificates).read_bytes()
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Copyright 2024 Flower Labs GmbH. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
# ==============================================================================
|
|
15
|
+
"""GrpcAdapter implementation."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
import sys
|
|
19
|
+
from logging import DEBUG
|
|
20
|
+
from typing import Any, Type, TypeVar, cast
|
|
21
|
+
|
|
22
|
+
import grpc
|
|
23
|
+
from google.protobuf.message import Message as GrpcMessage
|
|
24
|
+
|
|
25
|
+
from flwr.common import log
|
|
26
|
+
from flwr.common.constant import (
|
|
27
|
+
GRPC_ADAPTER_METADATA_FLOWER_VERSION_KEY,
|
|
28
|
+
GRPC_ADAPTER_METADATA_SHOULD_EXIT_KEY,
|
|
29
|
+
)
|
|
30
|
+
from flwr.common.version import package_version
|
|
31
|
+
from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
|
|
32
|
+
CreateNodeRequest,
|
|
33
|
+
CreateNodeResponse,
|
|
34
|
+
DeleteNodeRequest,
|
|
35
|
+
DeleteNodeResponse,
|
|
36
|
+
PingRequest,
|
|
37
|
+
PingResponse,
|
|
38
|
+
PullTaskInsRequest,
|
|
39
|
+
PullTaskInsResponse,
|
|
40
|
+
PushTaskResRequest,
|
|
41
|
+
PushTaskResResponse,
|
|
42
|
+
)
|
|
43
|
+
from flwr.proto.grpcadapter_pb2 import MessageContainer # pylint: disable=E0611
|
|
44
|
+
from flwr.proto.grpcadapter_pb2_grpc import GrpcAdapterStub
|
|
45
|
+
from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611
|
|
46
|
+
|
|
47
|
+
T = TypeVar("T", bound=GrpcMessage)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class GrpcAdapter:
|
|
51
|
+
"""Adapter class to send and receive gRPC messages via the ``GrpcAdapterStub``.
|
|
52
|
+
|
|
53
|
+
This class utilizes the ``GrpcAdapterStub`` to send and receive gRPC messages
|
|
54
|
+
which are defined and used by the Fleet API, as defined in ``fleet.proto``.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(self, channel: grpc.Channel) -> None:
|
|
58
|
+
self.stub = GrpcAdapterStub(channel)
|
|
59
|
+
|
|
60
|
+
def _send_and_receive(
|
|
61
|
+
self, request: GrpcMessage, response_type: Type[T], **kwargs: Any
|
|
62
|
+
) -> T:
|
|
63
|
+
# Serialize request
|
|
64
|
+
container_req = MessageContainer(
|
|
65
|
+
metadata={GRPC_ADAPTER_METADATA_FLOWER_VERSION_KEY: package_version},
|
|
66
|
+
grpc_message_name=request.__class__.__qualname__,
|
|
67
|
+
grpc_message_content=request.SerializeToString(),
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Send via the stub
|
|
71
|
+
container_res = cast(
|
|
72
|
+
MessageContainer, self.stub.SendReceive(container_req, **kwargs)
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Handle control message
|
|
76
|
+
should_exit = (
|
|
77
|
+
container_res.metadata.get(GRPC_ADAPTER_METADATA_SHOULD_EXIT_KEY, "false")
|
|
78
|
+
== "true"
|
|
79
|
+
)
|
|
80
|
+
if should_exit:
|
|
81
|
+
log(
|
|
82
|
+
DEBUG,
|
|
83
|
+
'Received shutdown signal: exit flag is set to ``"true"``. Exiting...',
|
|
84
|
+
)
|
|
85
|
+
sys.exit(0)
|
|
86
|
+
|
|
87
|
+
# Check the grpc_message_name of the response
|
|
88
|
+
if container_res.grpc_message_name != response_type.__qualname__:
|
|
89
|
+
raise ValueError(
|
|
90
|
+
f"Invalid grpc_message_name. Expected {response_type.__qualname__}"
|
|
91
|
+
f", but got {container_res.grpc_message_name}."
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Deserialize response
|
|
95
|
+
response = response_type()
|
|
96
|
+
response.ParseFromString(container_res.grpc_message_content)
|
|
97
|
+
return response
|
|
98
|
+
|
|
99
|
+
def CreateNode( # pylint: disable=C0103
|
|
100
|
+
self, request: CreateNodeRequest, **kwargs: Any
|
|
101
|
+
) -> CreateNodeResponse:
|
|
102
|
+
"""."""
|
|
103
|
+
return self._send_and_receive(request, CreateNodeResponse, **kwargs)
|
|
104
|
+
|
|
105
|
+
def DeleteNode( # pylint: disable=C0103
|
|
106
|
+
self, request: DeleteNodeRequest, **kwargs: Any
|
|
107
|
+
) -> DeleteNodeResponse:
|
|
108
|
+
"""."""
|
|
109
|
+
return self._send_and_receive(request, DeleteNodeResponse, **kwargs)
|
|
110
|
+
|
|
111
|
+
def Ping( # pylint: disable=C0103
|
|
112
|
+
self, request: PingRequest, **kwargs: Any
|
|
113
|
+
) -> PingResponse:
|
|
114
|
+
"""."""
|
|
115
|
+
return self._send_and_receive(request, PingResponse, **kwargs)
|
|
116
|
+
|
|
117
|
+
def PullTaskIns( # pylint: disable=C0103
|
|
118
|
+
self, request: PullTaskInsRequest, **kwargs: Any
|
|
119
|
+
) -> PullTaskInsResponse:
|
|
120
|
+
"""."""
|
|
121
|
+
return self._send_and_receive(request, PullTaskInsResponse, **kwargs)
|
|
122
|
+
|
|
123
|
+
def PushTaskRes( # pylint: disable=C0103
|
|
124
|
+
self, request: PushTaskResRequest, **kwargs: Any
|
|
125
|
+
) -> PushTaskResResponse:
|
|
126
|
+
"""."""
|
|
127
|
+
return self._send_and_receive(request, PushTaskResResponse, **kwargs)
|
|
128
|
+
|
|
129
|
+
def GetRun( # pylint: disable=C0103
|
|
130
|
+
self, request: GetRunRequest, **kwargs: Any
|
|
131
|
+
) -> GetRunResponse:
|
|
132
|
+
"""."""
|
|
133
|
+
return self._send_and_receive(request, GetRunResponse, **kwargs)
|
|
@@ -117,10 +117,16 @@ def http_request_response( # pylint: disable=,R0913, R0914, R0915
|
|
|
117
117
|
Path of the root certificate. If provided, a secure
|
|
118
118
|
connection using the certificates will be established to an SSL-enabled
|
|
119
119
|
Flower server. Bytes won't work for the REST API.
|
|
120
|
+
authentication_keys : Optional[Tuple[PrivateKey, PublicKey]] (default: None)
|
|
121
|
+
Client authentication is not supported for this transport type.
|
|
120
122
|
|
|
121
123
|
Returns
|
|
122
124
|
-------
|
|
123
|
-
receive
|
|
125
|
+
receive : Callable
|
|
126
|
+
send : Callable
|
|
127
|
+
create_node : Optional[Callable]
|
|
128
|
+
delete_node : Optional[Callable]
|
|
129
|
+
get_run : Optional[Callable]
|
|
124
130
|
"""
|
|
125
131
|
log(
|
|
126
132
|
WARN,
|
|
@@ -145,6 +151,8 @@ def http_request_response( # pylint: disable=,R0913, R0914, R0915
|
|
|
145
151
|
"For the REST API, the root certificates "
|
|
146
152
|
"must be provided as a string path to the client.",
|
|
147
153
|
)
|
|
154
|
+
if authentication_keys is not None:
|
|
155
|
+
log(ERROR, "Client authentication is not supported for this transport type.")
|
|
148
156
|
|
|
149
157
|
# Shared variables for inner functions
|
|
150
158
|
metadata: Optional[Metadata] = None
|
flwr/client/supernode/app.py
CHANGED
|
@@ -20,7 +20,6 @@ from logging import DEBUG, INFO, WARN
|
|
|
20
20
|
from pathlib import Path
|
|
21
21
|
from typing import Callable, Optional, Tuple
|
|
22
22
|
|
|
23
|
-
import tomli
|
|
24
23
|
from cryptography.exceptions import UnsupportedAlgorithm
|
|
25
24
|
from cryptography.hazmat.primitives.asymmetric import ec
|
|
26
25
|
from cryptography.hazmat.primitives.serialization import (
|
|
@@ -28,10 +27,9 @@ from cryptography.hazmat.primitives.serialization import (
|
|
|
28
27
|
load_ssh_public_key,
|
|
29
28
|
)
|
|
30
29
|
|
|
31
|
-
from flwr.cli.config_utils import validate_fields
|
|
32
30
|
from flwr.client.client_app import ClientApp, LoadClientAppError
|
|
33
31
|
from flwr.common import EventType, event
|
|
34
|
-
from flwr.common.config import get_flwr_dir
|
|
32
|
+
from flwr.common.config import get_flwr_dir, get_project_config, get_project_dir
|
|
35
33
|
from flwr.common.exit_handlers import register_exit_handlers
|
|
36
34
|
from flwr.common.logger import log, warn_deprecated_feature
|
|
37
35
|
from flwr.common.object_ref import load_app, validate
|
|
@@ -172,9 +170,9 @@ def _get_load_client_app_fn(
|
|
|
172
170
|
if args.flwr_dir is None:
|
|
173
171
|
flwr_dir = get_flwr_dir()
|
|
174
172
|
else:
|
|
175
|
-
flwr_dir = Path(args.flwr_dir)
|
|
173
|
+
flwr_dir = Path(args.flwr_dir).absolute()
|
|
176
174
|
|
|
177
|
-
sys.path.insert(0, str(flwr_dir))
|
|
175
|
+
sys.path.insert(0, str(flwr_dir.absolute()))
|
|
178
176
|
|
|
179
177
|
default_app_ref: str = getattr(args, "client-app")
|
|
180
178
|
|
|
@@ -191,8 +189,8 @@ def _get_load_client_app_fn(
|
|
|
191
189
|
def _load(fab_id: str, fab_version: str) -> ClientApp:
|
|
192
190
|
# If multi-app feature is disabled
|
|
193
191
|
if not multi_app:
|
|
194
|
-
#
|
|
195
|
-
|
|
192
|
+
# Get sys path to be inserted
|
|
193
|
+
sys_path = Path(args.dir).absolute()
|
|
196
194
|
|
|
197
195
|
# Set app reference
|
|
198
196
|
client_app_ref = default_app_ref
|
|
@@ -204,52 +202,28 @@ def _get_load_client_app_fn(
|
|
|
204
202
|
) from None
|
|
205
203
|
|
|
206
204
|
log(WARN, "FAB ID is not provided; the default ClientApp will be loaded.")
|
|
207
|
-
#
|
|
208
|
-
|
|
205
|
+
# Get sys path to be inserted
|
|
206
|
+
sys_path = Path(args.dir).absolute()
|
|
209
207
|
|
|
210
208
|
# Set app reference
|
|
211
209
|
client_app_ref = default_app_ref
|
|
212
210
|
# If multi-app feature is enabled
|
|
213
211
|
else:
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
) from
|
|
219
|
-
username, project_name = fab_id.split("/")
|
|
212
|
+
try:
|
|
213
|
+
project_dir = get_project_dir(fab_id, fab_version, flwr_dir)
|
|
214
|
+
config = get_project_config(project_dir)
|
|
215
|
+
except Exception as e:
|
|
216
|
+
raise LoadClientAppError("Failed to load ClientApp") from e
|
|
220
217
|
|
|
221
|
-
#
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
# Check if the directory exists
|
|
225
|
-
if not project_dir.exists():
|
|
226
|
-
raise LoadClientAppError(
|
|
227
|
-
f"Invalid Flower App directory: {project_dir}",
|
|
228
|
-
) from None
|
|
229
|
-
|
|
230
|
-
# Load pyproject.toml file
|
|
231
|
-
toml_path = project_dir / "pyproject.toml"
|
|
232
|
-
if not toml_path.is_file():
|
|
233
|
-
raise LoadClientAppError(
|
|
234
|
-
f"Cannot find pyproject.toml in {project_dir}",
|
|
235
|
-
) from None
|
|
236
|
-
with open(toml_path, encoding="utf-8") as toml_file:
|
|
237
|
-
config = tomli.loads(toml_file.read())
|
|
238
|
-
|
|
239
|
-
# Validate pyproject.toml fields
|
|
240
|
-
is_valid, errors, _ = validate_fields(config)
|
|
241
|
-
if not is_valid:
|
|
242
|
-
error_msg = "\n".join([f" - {error}" for error in errors])
|
|
243
|
-
raise LoadClientAppError(
|
|
244
|
-
f"Invalid pyproject.toml:\n{error_msg}",
|
|
245
|
-
) from None
|
|
246
|
-
|
|
247
|
-
# Set sys.path
|
|
248
|
-
sys.path[0] = str(project_dir)
|
|
218
|
+
# Get sys path to be inserted
|
|
219
|
+
sys_path = Path(project_dir).absolute()
|
|
249
220
|
|
|
250
221
|
# Set app reference
|
|
251
222
|
client_app_ref = config["flower"]["components"]["clientapp"]
|
|
252
223
|
|
|
224
|
+
# Set sys.path
|
|
225
|
+
sys.path.insert(0, str(sys_path))
|
|
226
|
+
|
|
253
227
|
# Load ClientApp
|
|
254
228
|
log(
|
|
255
229
|
DEBUG,
|
flwr/common/config.py
CHANGED
|
@@ -16,13 +16,56 @@
|
|
|
16
16
|
|
|
17
17
|
import os
|
|
18
18
|
from pathlib import Path
|
|
19
|
+
from typing import Any, Dict, Optional, Union
|
|
20
|
+
|
|
21
|
+
import tomli
|
|
22
|
+
|
|
23
|
+
from flwr.cli.config_utils import validate_fields
|
|
24
|
+
from flwr.common.constant import APP_DIR, FAB_CONFIG_FILE, FLWR_HOME
|
|
19
25
|
|
|
20
26
|
|
|
21
27
|
def get_flwr_dir() -> Path:
|
|
22
28
|
"""Return the Flower home directory based on env variables."""
|
|
23
29
|
return Path(
|
|
24
30
|
os.getenv(
|
|
25
|
-
|
|
31
|
+
FLWR_HOME,
|
|
26
32
|
f"{os.getenv('XDG_DATA_HOME', os.getenv('HOME'))}/.flwr",
|
|
27
33
|
)
|
|
28
34
|
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_project_dir(
|
|
38
|
+
fab_id: str, fab_version: str, flwr_dir: Optional[Union[str, Path]] = None
|
|
39
|
+
) -> Path:
|
|
40
|
+
"""Return the project directory based on the given fab_id and fab_version."""
|
|
41
|
+
# Check the fab_id
|
|
42
|
+
if fab_id.count("/") != 1:
|
|
43
|
+
raise ValueError(
|
|
44
|
+
f"Invalid FAB ID: {fab_id}",
|
|
45
|
+
)
|
|
46
|
+
publisher, project_name = fab_id.split("/")
|
|
47
|
+
if flwr_dir is None:
|
|
48
|
+
flwr_dir = get_flwr_dir()
|
|
49
|
+
return Path(flwr_dir) / APP_DIR / publisher / project_name / fab_version
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_project_config(project_dir: Union[str, Path]) -> Dict[str, Any]:
|
|
53
|
+
"""Return pyproject.toml in the given project directory."""
|
|
54
|
+
# Load pyproject.toml file
|
|
55
|
+
toml_path = Path(project_dir) / FAB_CONFIG_FILE
|
|
56
|
+
if not toml_path.is_file():
|
|
57
|
+
raise FileNotFoundError(
|
|
58
|
+
f"Cannot find {FAB_CONFIG_FILE} in {project_dir}",
|
|
59
|
+
)
|
|
60
|
+
with toml_path.open(encoding="utf-8") as toml_file:
|
|
61
|
+
config = tomli.loads(toml_file.read())
|
|
62
|
+
|
|
63
|
+
# Validate pyproject.toml fields
|
|
64
|
+
is_valid, errors, _ = validate_fields(config)
|
|
65
|
+
if not is_valid:
|
|
66
|
+
error_msg = "\n".join([f" - {error}" for error in errors])
|
|
67
|
+
raise ValueError(
|
|
68
|
+
f"Invalid {FAB_CONFIG_FILE}:\n{error_msg}",
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
return config
|
flwr/common/constant.py
CHANGED
|
@@ -45,6 +45,15 @@ PING_BASE_MULTIPLIER = 0.8
|
|
|
45
45
|
PING_RANDOM_RANGE = (-0.1, 0.1)
|
|
46
46
|
PING_MAX_INTERVAL = 1e300
|
|
47
47
|
|
|
48
|
+
# Constants for FAB
|
|
49
|
+
APP_DIR = "apps"
|
|
50
|
+
FAB_CONFIG_FILE = "pyproject.toml"
|
|
51
|
+
FLWR_HOME = "FLWR_HOME"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
GRPC_ADAPTER_METADATA_FLOWER_VERSION_KEY = "flower-version"
|
|
55
|
+
GRPC_ADAPTER_METADATA_SHOULD_EXIT_KEY = "should-exit"
|
|
56
|
+
|
|
48
57
|
|
|
49
58
|
class MessageType:
|
|
50
59
|
"""Message type."""
|
flwr/common/typing.py
CHANGED
|
@@ -185,3 +185,12 @@ class ClientMessage:
|
|
|
185
185
|
get_parameters_res: Optional[GetParametersRes] = None
|
|
186
186
|
fit_res: Optional[FitRes] = None
|
|
187
187
|
evaluate_res: Optional[EvaluateRes] = None
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@dataclass
|
|
191
|
+
class Run:
|
|
192
|
+
"""Run details."""
|
|
193
|
+
|
|
194
|
+
run_id: int
|
|
195
|
+
fab_id: str
|
|
196
|
+
fab_version: str
|
flwr/proto/exec_pb2.py
CHANGED
|
@@ -14,7 +14,7 @@ _sym_db = _symbol_database.Default()
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x66lwr/proto/exec.proto\x12\nflwr.proto\"#\n\x0fStartRunRequest\x12\x10\n\x08\x66\x61\x62_file\x18\x01 \x01(\x0c\"\"\n\x10StartRunResponse\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\
|
|
17
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x66lwr/proto/exec.proto\x12\nflwr.proto\"#\n\x0fStartRunRequest\x12\x10\n\x08\x66\x61\x62_file\x18\x01 \x01(\x0c\"\"\n\x10StartRunResponse\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\"#\n\x11StreamLogsRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\"(\n\x12StreamLogsResponse\x12\x12\n\nlog_output\x18\x01 \x01(\t2\xa0\x01\n\x04\x45xec\x12G\n\x08StartRun\x12\x1b.flwr.proto.StartRunRequest\x1a\x1c.flwr.proto.StartRunResponse\"\x00\x12O\n\nStreamLogs\x12\x1d.flwr.proto.StreamLogsRequest\x1a\x1e.flwr.proto.StreamLogsResponse\"\x00\x30\x01\x62\x06proto3')
|
|
18
18
|
|
|
19
19
|
_globals = globals()
|
|
20
20
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
@@ -25,6 +25,10 @@ if _descriptor._USE_C_DESCRIPTORS == False:
|
|
|
25
25
|
_globals['_STARTRUNREQUEST']._serialized_end=72
|
|
26
26
|
_globals['_STARTRUNRESPONSE']._serialized_start=74
|
|
27
27
|
_globals['_STARTRUNRESPONSE']._serialized_end=108
|
|
28
|
-
_globals['
|
|
29
|
-
_globals['
|
|
28
|
+
_globals['_STREAMLOGSREQUEST']._serialized_start=110
|
|
29
|
+
_globals['_STREAMLOGSREQUEST']._serialized_end=145
|
|
30
|
+
_globals['_STREAMLOGSRESPONSE']._serialized_start=147
|
|
31
|
+
_globals['_STREAMLOGSRESPONSE']._serialized_end=187
|
|
32
|
+
_globals['_EXEC']._serialized_start=190
|
|
33
|
+
_globals['_EXEC']._serialized_end=350
|
|
30
34
|
# @@protoc_insertion_point(module_scope)
|
flwr/proto/exec_pb2.pyi
CHANGED
|
@@ -5,6 +5,7 @@ isort:skip_file
|
|
|
5
5
|
import builtins
|
|
6
6
|
import google.protobuf.descriptor
|
|
7
7
|
import google.protobuf.message
|
|
8
|
+
import typing
|
|
8
9
|
import typing_extensions
|
|
9
10
|
|
|
10
11
|
DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
|
|
@@ -30,3 +31,25 @@ class StartRunResponse(google.protobuf.message.Message):
|
|
|
30
31
|
) -> None: ...
|
|
31
32
|
def ClearField(self, field_name: typing_extensions.Literal["run_id",b"run_id"]) -> None: ...
|
|
32
33
|
global___StartRunResponse = StartRunResponse
|
|
34
|
+
|
|
35
|
+
class StreamLogsRequest(google.protobuf.message.Message):
|
|
36
|
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
|
37
|
+
RUN_ID_FIELD_NUMBER: builtins.int
|
|
38
|
+
run_id: builtins.int
|
|
39
|
+
def __init__(self,
|
|
40
|
+
*,
|
|
41
|
+
run_id: builtins.int = ...,
|
|
42
|
+
) -> None: ...
|
|
43
|
+
def ClearField(self, field_name: typing_extensions.Literal["run_id",b"run_id"]) -> None: ...
|
|
44
|
+
global___StreamLogsRequest = StreamLogsRequest
|
|
45
|
+
|
|
46
|
+
class StreamLogsResponse(google.protobuf.message.Message):
|
|
47
|
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
|
48
|
+
LOG_OUTPUT_FIELD_NUMBER: builtins.int
|
|
49
|
+
log_output: typing.Text
|
|
50
|
+
def __init__(self,
|
|
51
|
+
*,
|
|
52
|
+
log_output: typing.Text = ...,
|
|
53
|
+
) -> None: ...
|
|
54
|
+
def ClearField(self, field_name: typing_extensions.Literal["log_output",b"log_output"]) -> None: ...
|
|
55
|
+
global___StreamLogsResponse = StreamLogsResponse
|
flwr/proto/exec_pb2_grpc.py
CHANGED
|
@@ -19,6 +19,11 @@ class ExecStub(object):
|
|
|
19
19
|
request_serializer=flwr_dot_proto_dot_exec__pb2.StartRunRequest.SerializeToString,
|
|
20
20
|
response_deserializer=flwr_dot_proto_dot_exec__pb2.StartRunResponse.FromString,
|
|
21
21
|
)
|
|
22
|
+
self.StreamLogs = channel.unary_stream(
|
|
23
|
+
'/flwr.proto.Exec/StreamLogs',
|
|
24
|
+
request_serializer=flwr_dot_proto_dot_exec__pb2.StreamLogsRequest.SerializeToString,
|
|
25
|
+
response_deserializer=flwr_dot_proto_dot_exec__pb2.StreamLogsResponse.FromString,
|
|
26
|
+
)
|
|
22
27
|
|
|
23
28
|
|
|
24
29
|
class ExecServicer(object):
|
|
@@ -31,6 +36,13 @@ class ExecServicer(object):
|
|
|
31
36
|
context.set_details('Method not implemented!')
|
|
32
37
|
raise NotImplementedError('Method not implemented!')
|
|
33
38
|
|
|
39
|
+
def StreamLogs(self, request, context):
|
|
40
|
+
"""Start log stream upon request
|
|
41
|
+
"""
|
|
42
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
43
|
+
context.set_details('Method not implemented!')
|
|
44
|
+
raise NotImplementedError('Method not implemented!')
|
|
45
|
+
|
|
34
46
|
|
|
35
47
|
def add_ExecServicer_to_server(servicer, server):
|
|
36
48
|
rpc_method_handlers = {
|
|
@@ -39,6 +51,11 @@ def add_ExecServicer_to_server(servicer, server):
|
|
|
39
51
|
request_deserializer=flwr_dot_proto_dot_exec__pb2.StartRunRequest.FromString,
|
|
40
52
|
response_serializer=flwr_dot_proto_dot_exec__pb2.StartRunResponse.SerializeToString,
|
|
41
53
|
),
|
|
54
|
+
'StreamLogs': grpc.unary_stream_rpc_method_handler(
|
|
55
|
+
servicer.StreamLogs,
|
|
56
|
+
request_deserializer=flwr_dot_proto_dot_exec__pb2.StreamLogsRequest.FromString,
|
|
57
|
+
response_serializer=flwr_dot_proto_dot_exec__pb2.StreamLogsResponse.SerializeToString,
|
|
58
|
+
),
|
|
42
59
|
}
|
|
43
60
|
generic_handler = grpc.method_handlers_generic_handler(
|
|
44
61
|
'flwr.proto.Exec', rpc_method_handlers)
|
|
@@ -65,3 +82,20 @@ class Exec(object):
|
|
|
65
82
|
flwr_dot_proto_dot_exec__pb2.StartRunResponse.FromString,
|
|
66
83
|
options, channel_credentials,
|
|
67
84
|
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
|
85
|
+
|
|
86
|
+
@staticmethod
|
|
87
|
+
def StreamLogs(request,
|
|
88
|
+
target,
|
|
89
|
+
options=(),
|
|
90
|
+
channel_credentials=None,
|
|
91
|
+
call_credentials=None,
|
|
92
|
+
insecure=False,
|
|
93
|
+
compression=None,
|
|
94
|
+
wait_for_ready=None,
|
|
95
|
+
timeout=None,
|
|
96
|
+
metadata=None):
|
|
97
|
+
return grpc.experimental.unary_stream(request, target, '/flwr.proto.Exec/StreamLogs',
|
|
98
|
+
flwr_dot_proto_dot_exec__pb2.StreamLogsRequest.SerializeToString,
|
|
99
|
+
flwr_dot_proto_dot_exec__pb2.StreamLogsResponse.FromString,
|
|
100
|
+
options, channel_credentials,
|
|
101
|
+
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
flwr/proto/exec_pb2_grpc.pyi
CHANGED
|
@@ -5,6 +5,7 @@ isort:skip_file
|
|
|
5
5
|
import abc
|
|
6
6
|
import flwr.proto.exec_pb2
|
|
7
7
|
import grpc
|
|
8
|
+
import typing
|
|
8
9
|
|
|
9
10
|
class ExecStub:
|
|
10
11
|
def __init__(self, channel: grpc.Channel) -> None: ...
|
|
@@ -13,6 +14,11 @@ class ExecStub:
|
|
|
13
14
|
flwr.proto.exec_pb2.StartRunResponse]
|
|
14
15
|
"""Start run upon request"""
|
|
15
16
|
|
|
17
|
+
StreamLogs: grpc.UnaryStreamMultiCallable[
|
|
18
|
+
flwr.proto.exec_pb2.StreamLogsRequest,
|
|
19
|
+
flwr.proto.exec_pb2.StreamLogsResponse]
|
|
20
|
+
"""Start log stream upon request"""
|
|
21
|
+
|
|
16
22
|
|
|
17
23
|
class ExecServicer(metaclass=abc.ABCMeta):
|
|
18
24
|
@abc.abstractmethod
|
|
@@ -23,5 +29,13 @@ class ExecServicer(metaclass=abc.ABCMeta):
|
|
|
23
29
|
"""Start run upon request"""
|
|
24
30
|
pass
|
|
25
31
|
|
|
32
|
+
@abc.abstractmethod
|
|
33
|
+
def StreamLogs(self,
|
|
34
|
+
request: flwr.proto.exec_pb2.StreamLogsRequest,
|
|
35
|
+
context: grpc.ServicerContext,
|
|
36
|
+
) -> typing.Iterator[flwr.proto.exec_pb2.StreamLogsResponse]:
|
|
37
|
+
"""Start log stream upon request"""
|
|
38
|
+
pass
|
|
39
|
+
|
|
26
40
|
|
|
27
41
|
def add_ExecServicer_to_server(servicer: ExecServicer, server: grpc.Server) -> None: ...
|
flwr/server/run_serverapp.py
CHANGED
|
@@ -112,6 +112,6 @@ def get_run(
|
|
|
112
112
|
request: GetRunRequest, state: State # pylint: disable=W0613
|
|
113
113
|
) -> GetRunResponse:
|
|
114
114
|
"""Get run information."""
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
return GetRunResponse(run=
|
|
115
|
+
run = state.get_run(request.run_id)
|
|
116
|
+
run_proto = None if run is None else Run(**vars(run))
|
|
117
|
+
return GetRunResponse(run=run_proto)
|
|
@@ -20,6 +20,7 @@ import sys
|
|
|
20
20
|
import time
|
|
21
21
|
import traceback
|
|
22
22
|
from logging import DEBUG, ERROR, INFO, WARN
|
|
23
|
+
from pathlib import Path
|
|
23
24
|
from typing import Callable, Dict, List, Optional
|
|
24
25
|
|
|
25
26
|
from flwr.client.client_app import ClientApp, ClientAppException, LoadClientAppError
|
|
@@ -274,6 +275,7 @@ def start_vce(
|
|
|
274
275
|
# Use mapping constructed externally. This also means nodes
|
|
275
276
|
# have previously being registered.
|
|
276
277
|
nodes_mapping = existing_nodes_mapping
|
|
278
|
+
app_dir = str(Path(app_dir).absolute())
|
|
277
279
|
|
|
278
280
|
if not state_factory:
|
|
279
281
|
log(INFO, "A StateFactory was not supplied to the SimulationEngine.")
|
|
@@ -23,6 +23,7 @@ from typing import Dict, List, Optional, Set, Tuple
|
|
|
23
23
|
from uuid import UUID, uuid4
|
|
24
24
|
|
|
25
25
|
from flwr.common import log, now
|
|
26
|
+
from flwr.common.typing import Run
|
|
26
27
|
from flwr.proto.task_pb2 import TaskIns, TaskRes # pylint: disable=E0611
|
|
27
28
|
from flwr.server.superlink.state.state import State
|
|
28
29
|
from flwr.server.utils import validate_task_ins_or_res
|
|
@@ -40,7 +41,7 @@ class InMemoryState(State): # pylint: disable=R0902,R0904
|
|
|
40
41
|
self.public_key_to_node_id: Dict[bytes, int] = {}
|
|
41
42
|
|
|
42
43
|
# Map run_id to (fab_id, fab_version)
|
|
43
|
-
self.run_ids: Dict[int,
|
|
44
|
+
self.run_ids: Dict[int, Run] = {}
|
|
44
45
|
self.task_ins_store: Dict[UUID, TaskIns] = {}
|
|
45
46
|
self.task_res_store: Dict[UUID, TaskRes] = {}
|
|
46
47
|
|
|
@@ -281,7 +282,9 @@ class InMemoryState(State): # pylint: disable=R0902,R0904
|
|
|
281
282
|
run_id: int = int.from_bytes(os.urandom(8), "little", signed=True)
|
|
282
283
|
|
|
283
284
|
if run_id not in self.run_ids:
|
|
284
|
-
self.run_ids[run_id] = (
|
|
285
|
+
self.run_ids[run_id] = Run(
|
|
286
|
+
run_id=run_id, fab_id=fab_id, fab_version=fab_version
|
|
287
|
+
)
|
|
285
288
|
return run_id
|
|
286
289
|
log(ERROR, "Unexpected run creation failure.")
|
|
287
290
|
return 0
|
|
@@ -319,13 +322,13 @@ class InMemoryState(State): # pylint: disable=R0902,R0904
|
|
|
319
322
|
"""Retrieve all currently stored `client_public_keys` as a set."""
|
|
320
323
|
return self.client_public_keys
|
|
321
324
|
|
|
322
|
-
def get_run(self, run_id: int) ->
|
|
325
|
+
def get_run(self, run_id: int) -> Optional[Run]:
|
|
323
326
|
"""Retrieve information about the run with the specified `run_id`."""
|
|
324
327
|
with self.lock:
|
|
325
328
|
if run_id not in self.run_ids:
|
|
326
329
|
log(ERROR, "`run_id` is invalid")
|
|
327
|
-
return
|
|
328
|
-
return
|
|
330
|
+
return None
|
|
331
|
+
return self.run_ids[run_id]
|
|
329
332
|
|
|
330
333
|
def acknowledge_ping(self, node_id: int, ping_interval: float) -> bool:
|
|
331
334
|
"""Acknowledge a ping received from a node, serving as a heartbeat."""
|
|
@@ -24,6 +24,7 @@ from typing import Any, Dict, List, Optional, Sequence, Set, Tuple, Union, cast
|
|
|
24
24
|
from uuid import UUID, uuid4
|
|
25
25
|
|
|
26
26
|
from flwr.common import log, now
|
|
27
|
+
from flwr.common.typing import Run
|
|
27
28
|
from flwr.proto.node_pb2 import Node # pylint: disable=E0611
|
|
28
29
|
from flwr.proto.recordset_pb2 import RecordSet # pylint: disable=E0611
|
|
29
30
|
from flwr.proto.task_pb2 import Task, TaskIns, TaskRes # pylint: disable=E0611
|
|
@@ -680,15 +681,17 @@ class SqliteState(State): # pylint: disable=R0904
|
|
|
680
681
|
result: Set[bytes] = {row["public_key"] for row in rows}
|
|
681
682
|
return result
|
|
682
683
|
|
|
683
|
-
def get_run(self, run_id: int) ->
|
|
684
|
+
def get_run(self, run_id: int) -> Optional[Run]:
|
|
684
685
|
"""Retrieve information about the run with the specified `run_id`."""
|
|
685
686
|
query = "SELECT * FROM run WHERE run_id = ?;"
|
|
686
687
|
try:
|
|
687
688
|
row = self.query(query, (run_id,))[0]
|
|
688
|
-
return
|
|
689
|
+
return Run(
|
|
690
|
+
run_id=run_id, fab_id=row["fab_id"], fab_version=row["fab_version"]
|
|
691
|
+
)
|
|
689
692
|
except sqlite3.IntegrityError:
|
|
690
693
|
log(ERROR, "`run_id` does not exist.")
|
|
691
|
-
return
|
|
694
|
+
return None
|
|
692
695
|
|
|
693
696
|
def acknowledge_ping(self, node_id: int, ping_interval: float) -> bool:
|
|
694
697
|
"""Acknowledge a ping received from a node, serving as a heartbeat."""
|
|
@@ -16,9 +16,10 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
import abc
|
|
19
|
-
from typing import List, Optional, Set
|
|
19
|
+
from typing import List, Optional, Set
|
|
20
20
|
from uuid import UUID
|
|
21
21
|
|
|
22
|
+
from flwr.common.typing import Run
|
|
22
23
|
from flwr.proto.task_pb2 import TaskIns, TaskRes # pylint: disable=E0611
|
|
23
24
|
|
|
24
25
|
|
|
@@ -160,7 +161,7 @@ class State(abc.ABC): # pylint: disable=R0904
|
|
|
160
161
|
"""Create a new run for the specified `fab_id` and `fab_version`."""
|
|
161
162
|
|
|
162
163
|
@abc.abstractmethod
|
|
163
|
-
def get_run(self, run_id: int) ->
|
|
164
|
+
def get_run(self, run_id: int) -> Optional[Run]:
|
|
164
165
|
"""Retrieve information about the run with the specified `run_id`.
|
|
165
166
|
|
|
166
167
|
Parameters
|
|
@@ -170,8 +171,8 @@ class State(abc.ABC): # pylint: disable=R0904
|
|
|
170
171
|
|
|
171
172
|
Returns
|
|
172
173
|
-------
|
|
173
|
-
|
|
174
|
-
A
|
|
174
|
+
Optional[Run]
|
|
175
|
+
A dataclass instance containing three elements if `run_id` is valid:
|
|
175
176
|
- `run_id`: The identifier of the run, same as the specified `run_id`.
|
|
176
177
|
- `fab_id`: The identifier of the FAB used in the specified run.
|
|
177
178
|
- `fab_version`: The version of the FAB used in the specified run.
|
flwr/superexec/exec_servicer.py
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
from logging import ERROR, INFO
|
|
19
|
-
from typing import Dict
|
|
19
|
+
from typing import Any, Dict, Generator
|
|
20
20
|
|
|
21
21
|
import grpc
|
|
22
22
|
|
|
@@ -25,6 +25,8 @@ from flwr.proto import exec_pb2_grpc # pylint: disable=E0611
|
|
|
25
25
|
from flwr.proto.exec_pb2 import ( # pylint: disable=E0611
|
|
26
26
|
StartRunRequest,
|
|
27
27
|
StartRunResponse,
|
|
28
|
+
StreamLogsRequest,
|
|
29
|
+
StreamLogsResponse,
|
|
28
30
|
)
|
|
29
31
|
|
|
30
32
|
from .executor import Executor, RunTracker
|
|
@@ -52,3 +54,12 @@ class ExecServicer(exec_pb2_grpc.ExecServicer):
|
|
|
52
54
|
self.runs[run.run_id] = run
|
|
53
55
|
|
|
54
56
|
return StartRunResponse(run_id=run.run_id)
|
|
57
|
+
|
|
58
|
+
def StreamLogs(
|
|
59
|
+
self, request: StreamLogsRequest, context: grpc.ServicerContext
|
|
60
|
+
) -> Generator[StreamLogsResponse, Any, None]:
|
|
61
|
+
"""Get logs."""
|
|
62
|
+
logs = ["a", "b", "c"]
|
|
63
|
+
while context.is_active():
|
|
64
|
+
for i in range(len(logs)): # pylint: disable=C0200
|
|
65
|
+
yield StreamLogsResponse(log_output=logs[i])
|
{flwr_nightly-1.10.0.dev20240616.dist-info → flwr_nightly-1.10.0.dev20240618.dist-info}/RECORD
RENAMED
|
@@ -47,11 +47,14 @@ flwr/client/app.py,sha256=GhL-eR_Y2H8e2XhUnq5AUGQWQya51b5iVr4I6RDW7Hc,24189
|
|
|
47
47
|
flwr/client/client.py,sha256=Vp9UkOkoHdNfn6iMYZsj_5m_GICiFfUlKEVaLad-YhM,8183
|
|
48
48
|
flwr/client/client_app.py,sha256=2jyVTzu8pwDtg66z4FjAa_kPzg31Q8-hx-RkDhguIqw,8635
|
|
49
49
|
flwr/client/dpfedavg_numpy_client.py,sha256=9Tnig4iml2J88HBKNahegjXjbfvIQyBtaIQaqjbeqsA,7435
|
|
50
|
+
flwr/client/grpc_adapter_client/__init__.py,sha256=QyNWIbsq9DpyMk7oemiO1P3TBFfkfkctnJ1JoAkTl3s,742
|
|
51
|
+
flwr/client/grpc_adapter_client/connection.py,sha256=kUObTsJ_4OwxE4tvIlWwn_iSWk2snjLWMo19tqdXo1s,3841
|
|
50
52
|
flwr/client/grpc_client/__init__.py,sha256=LsnbqXiJhgQcB0XzAlUQgPx011Uf7Y7yabIC1HxivJ8,735
|
|
51
|
-
flwr/client/grpc_client/connection.py,sha256=
|
|
53
|
+
flwr/client/grpc_client/connection.py,sha256=NGPFSq98wBXpVXZlei6jlg679nL07GDwyQ4z37AzoGc,9279
|
|
52
54
|
flwr/client/grpc_rere_client/__init__.py,sha256=avn6W_vHEM_yZEB1S7hCZgnTbXb6ZujqRP_vAzyXu-0,752
|
|
53
55
|
flwr/client/grpc_rere_client/client_interceptor.py,sha256=sYPEznuQPdy2BPDlvM9FK0ZRRucb4NfwUee1Z_mN82E,4954
|
|
54
|
-
flwr/client/grpc_rere_client/connection.py,sha256=
|
|
56
|
+
flwr/client/grpc_rere_client/connection.py,sha256=nEbmqvbPsSyESrwVBRipDFqsOmg4wQM6D0mf9-flMyo,10157
|
|
57
|
+
flwr/client/grpc_rere_client/grpc_adapter.py,sha256=woljH8yr1pyLH4W4Azogyy7Nafn6y9DHBnDCIIVKwCw,4711
|
|
55
58
|
flwr/client/heartbeat.py,sha256=cx37mJBH8LyoIN4Lks85wtqT1mnU5GulQnr4pGCvAq0,2404
|
|
56
59
|
flwr/client/message_handler/__init__.py,sha256=abHvBRJJiiaAMNgeILQbMOa6h8WqMK2BcnvxwQZFpic,719
|
|
57
60
|
flwr/client/message_handler/message_handler.py,sha256=ml_FlduAJ5pxO31n1tKRrWfQRSxkMgKLbwXXcRsNSos,6553
|
|
@@ -68,14 +71,14 @@ flwr/client/node_state.py,sha256=KTTs_l4I0jBM7IsSsbAGjhfL_yZC3QANbzyvyfZBRDM,177
|
|
|
68
71
|
flwr/client/node_state_tests.py,sha256=gPwz0zf2iuDSa11jedkur_u3Xm7lokIDG5ALD2MCvSw,2195
|
|
69
72
|
flwr/client/numpy_client.py,sha256=u76GWAdHmJM88Agm2EgLQSvO8Jnk225mJTk-_TmPjFE,10283
|
|
70
73
|
flwr/client/rest_client/__init__.py,sha256=ThwOnkMdzxo_UuyTI47Q7y9oSpuTgNT2OuFvJCfuDiw,735
|
|
71
|
-
flwr/client/rest_client/connection.py,sha256=
|
|
74
|
+
flwr/client/rest_client/connection.py,sha256=GmnHoeWuSMv0OWV5GUZweIft8BTextGEY9oxyc7pyYk,11957
|
|
72
75
|
flwr/client/supernode/__init__.py,sha256=SUhWOzcgXRNXk1V9UgB5-FaWukqqrOEajVUHEcPkwyQ,865
|
|
73
|
-
flwr/client/supernode/app.py,sha256=
|
|
76
|
+
flwr/client/supernode/app.py,sha256=9mwrPfgzNOsrpw2ToXmBRabrXHAn8yINsXBHw457IJs,13789
|
|
74
77
|
flwr/client/typing.py,sha256=c9EvjlEjasxn1Wqx6bGl6Xg6vM1gMFfmXht-E2i5J-k,1006
|
|
75
78
|
flwr/common/__init__.py,sha256=dHOptgKxna78CEQLD5Yu0QIsoSgpIIw5AhIUZCHDWAU,3721
|
|
76
79
|
flwr/common/address.py,sha256=iTAN9jtmIGMrWFnx9XZQl45ZEtQJVZZLYPRBSNVARGI,1882
|
|
77
|
-
flwr/common/config.py,sha256=
|
|
78
|
-
flwr/common/constant.py,sha256=
|
|
80
|
+
flwr/common/config.py,sha256=iXcpC7Eg1YK6C5apOIoWCI-mV92djyvVfcrMraOfDLM,2461
|
|
81
|
+
flwr/common/constant.py,sha256=QSfjp6kfDGtJxh1TspF4d88aYDCLtA_Y9TE0jC7F2no,2681
|
|
79
82
|
flwr/common/context.py,sha256=ounF-mWPPtXGwtae3sg5EhF58ScviOa3MVqxRpGVu-8,1313
|
|
80
83
|
flwr/common/date.py,sha256=UWhBZj49yX9LD4BmatS_ZFZu_-kweGh0KQJ1djyWWH4,891
|
|
81
84
|
flwr/common/differential_privacy.py,sha256=WZWrL7C9XaB9l9NDkLDI5PvM7jwcoTTFu08ZVG8-M5Q,6113
|
|
@@ -107,7 +110,7 @@ flwr/common/secure_aggregation/secaggplus_constants.py,sha256=Fh7-n6pgL4TUnHpNYX
|
|
|
107
110
|
flwr/common/secure_aggregation/secaggplus_utils.py,sha256=87bNZX6CmQekj935R4u3m5hsaEkkfKtGSA-VG2c-O9w,3221
|
|
108
111
|
flwr/common/serde.py,sha256=Yn83kbSf9vJndTa5ldL4DR_bL_wy_bD4lTlD3ZbB658,22250
|
|
109
112
|
flwr/common/telemetry.py,sha256=IGzOp87BYReCj5bEoZS6zDSKH0aXsmhMvhHx8fdC1v0,7948
|
|
110
|
-
flwr/common/typing.py,sha256=
|
|
113
|
+
flwr/common/typing.py,sha256=q1RtnEtI2jX9VGT12_ECM3euMIcjmwh1Pd1tVrYf6i8,4483
|
|
111
114
|
flwr/common/version.py,sha256=_RDSMGZPEuGKYViZuXPotDtXMvh4iyDH9XOCO4qtPO8,666
|
|
112
115
|
flwr/proto/__init__.py,sha256=hbY7JYakwZwCkYgCNlmHdc8rtvfoJbAZLalMdc--CGc,683
|
|
113
116
|
flwr/proto/driver_pb2.py,sha256=xqEN0UxFAUl1h_S_tP8SQShNYSJt_oSdb1npMn5KfL0,3394
|
|
@@ -118,10 +121,10 @@ flwr/proto/error_pb2.py,sha256=LarjKL90LbwkXKlhzNrDssgl4DXcvIPve8NVCXHpsKA,1084
|
|
|
118
121
|
flwr/proto/error_pb2.pyi,sha256=ZNH4HhJTU_KfMXlyCeg8FwU-fcUYxTqEmoJPtWtHikc,734
|
|
119
122
|
flwr/proto/error_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
|
|
120
123
|
flwr/proto/error_pb2_grpc.pyi,sha256=ff2TSiLVnG6IVQcTGzb2DIH3XRSoAvAo_RMcvbMFyc0,76
|
|
121
|
-
flwr/proto/exec_pb2.py,sha256=
|
|
122
|
-
flwr/proto/exec_pb2.pyi,sha256=
|
|
123
|
-
flwr/proto/exec_pb2_grpc.py,sha256=
|
|
124
|
-
flwr/proto/exec_pb2_grpc.pyi,sha256=
|
|
124
|
+
flwr/proto/exec_pb2.py,sha256=eA1VsoMtSAtBI8uWhEiM_xO_5bY9H-TZkrwFbDgzM-8,1909
|
|
125
|
+
flwr/proto/exec_pb2.pyi,sha256=eQ262F1-bvE60BthMh4rQOWKCRw_E4J4FEnHXtabIlc,1929
|
|
126
|
+
flwr/proto/exec_pb2_grpc.py,sha256=faAN19XEMP8GTKrcIU6jvlWkN44n2KiUsZh_OG0sYcg,4072
|
|
127
|
+
flwr/proto/exec_pb2_grpc.pyi,sha256=VrFhT1Um3Nb8UC2YqnR9GIiM-Yyx0FqaxVOWljh-G_w,1208
|
|
125
128
|
flwr/proto/fab_pb2.py,sha256=k1L3z4L3pJGRIUmgt609xUe-UAtSKFwT0C7wXnb12IY,1427
|
|
126
129
|
flwr/proto/fab_pb2.pyi,sha256=G2eHjgIAeVAf4TchXg3XPdeUk-h5-OMJnAv7CLyxdGs,1930
|
|
127
130
|
flwr/proto/fab_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
|
|
@@ -170,7 +173,7 @@ flwr/server/driver/driver.py,sha256=t9SSSDlo9wT_y2Nl7waGYMTm2VlkvK3_bOb7ggPPlho,
|
|
|
170
173
|
flwr/server/driver/grpc_driver.py,sha256=rdjkcAmtRWKeqJw4xDFqULuwVf0G2nLhfbOTrNUvPeY,11832
|
|
171
174
|
flwr/server/driver/inmemory_driver.py,sha256=XfdLV3mVorTWBfthBkErJDLm8jXZ834IHF3139lTS5o,6490
|
|
172
175
|
flwr/server/history.py,sha256=bBOHKyX1eQONIsUx4EUU-UnAk1i0EbEl8ioyMq_UWQ8,5063
|
|
173
|
-
flwr/server/run_serverapp.py,sha256=
|
|
176
|
+
flwr/server/run_serverapp.py,sha256=svfVK-4tS2j9qYoXsLQAziJht_XiClQiSsM7OUPA6II,6909
|
|
174
177
|
flwr/server/server.py,sha256=wsXsxMZ9SQ0B42nBnUlcV83NJPycgrgg5bFwcQ4BYBE,17821
|
|
175
178
|
flwr/server/server_app.py,sha256=Re5Y9ftXlBRJXYHY_8TrNWsjyOUCPC5F_93H0xiZDhI,4400
|
|
176
179
|
flwr/server/server_config.py,sha256=CZaHVAsMvGLjpWVcLPkiYxgJN4xfIyAiUrCI3fETKY4,1349
|
|
@@ -212,18 +215,18 @@ flwr/server/superlink/fleet/grpc_rere/__init__.py,sha256=bEJOMWbSlqkw-y5ZHtEXczh
|
|
|
212
215
|
flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py,sha256=wuFaEAsuKfAYQKeXbB9Jg0SQaW9gmb_1k6pOPIlXrYY,3434
|
|
213
216
|
flwr/server/superlink/fleet/grpc_rere/server_interceptor.py,sha256=v54f7yEX0Xwqh9KRDPzKgJdSBsosus4RzUyzTl-_u5Q,7738
|
|
214
217
|
flwr/server/superlink/fleet/message_handler/__init__.py,sha256=hEY0l61ojH8Iz30_K1btm1HJ6J49iZJSFUsVYqUTw3A,731
|
|
215
|
-
flwr/server/superlink/fleet/message_handler/message_handler.py,sha256=
|
|
218
|
+
flwr/server/superlink/fleet/message_handler/message_handler.py,sha256=qDlWhCsbXssHTvoMZPjYTGgmOVflszkFmL1oJzVQrDA,3653
|
|
216
219
|
flwr/server/superlink/fleet/rest_rere/__init__.py,sha256=VKDvDq5H8koOUztpmQacVzGJXPLEEkL1Vmolxt3mvnY,735
|
|
217
220
|
flwr/server/superlink/fleet/rest_rere/rest_api.py,sha256=Sm_pSxpeT1BdkSKou8YwreP7qSpMR5ENEsjwGW1h5VY,7672
|
|
218
221
|
flwr/server/superlink/fleet/vce/__init__.py,sha256=36MHKiefnJeyjwMQzVUK4m06Ojon3WDcwZGQsAcyVhQ,783
|
|
219
222
|
flwr/server/superlink/fleet/vce/backend/__init__.py,sha256=oBIzmnrSSRvH_H0vRGEGWhWzQQwqe3zn6e13RsNwlIY,1466
|
|
220
223
|
flwr/server/superlink/fleet/vce/backend/backend.py,sha256=LJsKl7oixVvptcG98Rd9ejJycNWcEVB0ODvSreLGp-A,2260
|
|
221
224
|
flwr/server/superlink/fleet/vce/backend/raybackend.py,sha256=KCzV-n-czXxIKPwNfuD-JEVCl4-xAJaHe4taGmw9cTQ,6722
|
|
222
|
-
flwr/server/superlink/fleet/vce/vce_api.py,sha256=
|
|
225
|
+
flwr/server/superlink/fleet/vce/vce_api.py,sha256=DQtEZ6VsL5fK1gpki0FxaJ33MUGPPXC6OEZ6oKCnZs0,12525
|
|
223
226
|
flwr/server/superlink/state/__init__.py,sha256=ij-7Ms-hyordQdRmGQxY1-nVa4OhixJ0jr7_YDkys0s,1003
|
|
224
|
-
flwr/server/superlink/state/in_memory_state.py,sha256=
|
|
225
|
-
flwr/server/superlink/state/sqlite_state.py,sha256=
|
|
226
|
-
flwr/server/superlink/state/state.py,sha256=
|
|
227
|
+
flwr/server/superlink/state/in_memory_state.py,sha256=pb_asr8sh9IHPA97yYDTq1RZi8qYpACTzwSg56LMnsM,12827
|
|
228
|
+
flwr/server/superlink/state/sqlite_state.py,sha256=1PBYQGEIaj_67ojFw51mujfaxOFSeA8kPn_irwAX7eE,28612
|
|
229
|
+
flwr/server/superlink/state/state.py,sha256=_2EwLpOxSMEs0IsemuZNTBidTK3hoUt9vYcf7TRyg40,8037
|
|
227
230
|
flwr/server/superlink/state/state_factory.py,sha256=91cSB-KOAFM37z7T098WxTkVeKNaAZ_mTI75snn2_tk,1654
|
|
228
231
|
flwr/server/superlink/state/utils.py,sha256=qhIjBu5_rqm9GLMB6QS5TIRrMDVs85lmY17BqZ1ccLk,2207
|
|
229
232
|
flwr/server/typing.py,sha256=2zSG-KuDAgwFPuzgVjTLDaEqJ8gXXGqFR2RD-qIk730,913
|
|
@@ -246,10 +249,10 @@ flwr/simulation/run_simulation.py,sha256=nTw8VG_agsxVyKmlOw-0umVuXJflcdAGqkKOL-Z
|
|
|
246
249
|
flwr/superexec/__init__.py,sha256=9h94ogLxi6eJ3bUuJYq3E3pApThSabTPiSmPAGlTkHE,800
|
|
247
250
|
flwr/superexec/app.py,sha256=8qW72a4LZRGMfNxsY6PPv-ClsKrk_ai9l0GrLI4vvTw,6078
|
|
248
251
|
flwr/superexec/exec_grpc.py,sha256=u-rztpOleqSGqgvNE-ZLw1HchNsBHU1-eB3m52GZ0pQ,1852
|
|
249
|
-
flwr/superexec/exec_servicer.py,sha256=
|
|
252
|
+
flwr/superexec/exec_servicer.py,sha256=qf8CT4RLXnY8omOy75kwfsWmMnfTD42B4ENTh5S-BCY,2120
|
|
250
253
|
flwr/superexec/executor.py,sha256=GouXCY2LiZ-ffsOoZ_z-fh4JwbzMmhTl-gwpWFgGWTY,1688
|
|
251
|
-
flwr_nightly-1.10.0.
|
|
252
|
-
flwr_nightly-1.10.0.
|
|
253
|
-
flwr_nightly-1.10.0.
|
|
254
|
-
flwr_nightly-1.10.0.
|
|
255
|
-
flwr_nightly-1.10.0.
|
|
254
|
+
flwr_nightly-1.10.0.dev20240618.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
255
|
+
flwr_nightly-1.10.0.dev20240618.dist-info/METADATA,sha256=UN4lU0Xz1YCoosjbt1YPwzsrqTI8qodVoRuHtWGnvm4,15518
|
|
256
|
+
flwr_nightly-1.10.0.dev20240618.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
257
|
+
flwr_nightly-1.10.0.dev20240618.dist-info/entry_points.txt,sha256=7qBQcA-bDGDxnJmLd9FYqglFQubjCNqyg9M8a-lukps,336
|
|
258
|
+
flwr_nightly-1.10.0.dev20240618.dist-info/RECORD,,
|
{flwr_nightly-1.10.0.dev20240616.dist-info → flwr_nightly-1.10.0.dev20240618.dist-info}/LICENSE
RENAMED
|
File without changes
|
{flwr_nightly-1.10.0.dev20240616.dist-info → flwr_nightly-1.10.0.dev20240618.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|