flwr-nightly 1.13.0.dev20241111__py3-none-any.whl → 1.13.0.dev20241117__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/cli/app.py +2 -0
- flwr/cli/build.py +37 -0
- flwr/cli/install.py +5 -3
- flwr/cli/ls.py +228 -0
- flwr/client/app.py +58 -13
- flwr/client/clientapp/app.py +34 -23
- flwr/client/grpc_rere_client/connection.py +2 -12
- flwr/client/rest_client/connection.py +4 -14
- flwr/client/supernode/app.py +57 -53
- flwr/common/args.py +72 -7
- flwr/common/constant.py +21 -6
- flwr/common/date.py +18 -0
- flwr/common/serde.py +10 -0
- flwr/common/typing.py +31 -10
- flwr/proto/exec_pb2.py +22 -13
- flwr/proto/exec_pb2.pyi +44 -0
- flwr/proto/exec_pb2_grpc.py +34 -0
- flwr/proto/exec_pb2_grpc.pyi +13 -0
- flwr/proto/run_pb2.py +30 -30
- flwr/proto/run_pb2.pyi +18 -1
- flwr/server/app.py +39 -68
- flwr/server/driver/grpc_driver.py +4 -14
- flwr/server/run_serverapp.py +8 -238
- flwr/server/serverapp/app.py +34 -23
- flwr/server/superlink/fleet/rest_rere/rest_api.py +10 -9
- flwr/server/superlink/linkstate/in_memory_linkstate.py +71 -46
- flwr/server/superlink/linkstate/linkstate.py +19 -5
- flwr/server/superlink/linkstate/sqlite_linkstate.py +81 -113
- flwr/server/superlink/linkstate/utils.py +193 -3
- flwr/simulation/app.py +6 -41
- flwr/simulation/legacy_app.py +21 -1
- flwr/simulation/run_simulation.py +7 -18
- flwr/simulation/simulationio_connection.py +2 -2
- flwr/superexec/deployment.py +12 -6
- flwr/superexec/exec_servicer.py +31 -2
- flwr/superexec/simulation.py +11 -46
- {flwr_nightly-1.13.0.dev20241111.dist-info → flwr_nightly-1.13.0.dev20241117.dist-info}/METADATA +6 -4
- {flwr_nightly-1.13.0.dev20241111.dist-info → flwr_nightly-1.13.0.dev20241117.dist-info}/RECORD +41 -40
- {flwr_nightly-1.13.0.dev20241111.dist-info → flwr_nightly-1.13.0.dev20241117.dist-info}/LICENSE +0 -0
- {flwr_nightly-1.13.0.dev20241111.dist-info → flwr_nightly-1.13.0.dev20241117.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.13.0.dev20241111.dist-info → flwr_nightly-1.13.0.dev20241117.dist-info}/entry_points.txt +0 -0
flwr/client/supernode/app.py
CHANGED
|
@@ -28,8 +28,13 @@ from cryptography.hazmat.primitives.serialization import (
|
|
|
28
28
|
)
|
|
29
29
|
|
|
30
30
|
from flwr.common import EventType, event
|
|
31
|
+
from flwr.common.args import (
|
|
32
|
+
try_obtain_root_certificates,
|
|
33
|
+
try_obtain_server_certificates,
|
|
34
|
+
)
|
|
31
35
|
from flwr.common.config import parse_config_args
|
|
32
36
|
from flwr.common.constant import (
|
|
37
|
+
CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS,
|
|
33
38
|
FLEET_API_GRPC_RERE_DEFAULT_ADDRESS,
|
|
34
39
|
ISOLATION_MODE_PROCESS,
|
|
35
40
|
ISOLATION_MODE_SUBPROCESS,
|
|
@@ -61,10 +66,23 @@ def run_supernode() -> None:
|
|
|
61
66
|
"Ignoring `--flwr-dir`.",
|
|
62
67
|
)
|
|
63
68
|
|
|
64
|
-
|
|
69
|
+
# Exit if unsupported argument is passed by the user
|
|
70
|
+
if args.app is not None:
|
|
71
|
+
log(
|
|
72
|
+
ERROR,
|
|
73
|
+
"The `app` argument is deprecated. The SuperNode now automatically "
|
|
74
|
+
"uses the ClientApp delivered from the SuperLink. Providing the app "
|
|
75
|
+
"directory manually is no longer supported. Please remove the `app` "
|
|
76
|
+
"argument from your command.",
|
|
77
|
+
)
|
|
78
|
+
sys.exit(1)
|
|
79
|
+
|
|
80
|
+
root_certificates = try_obtain_root_certificates(args, args.superlink)
|
|
81
|
+
# Obtain certificates for ClientAppIo API server
|
|
82
|
+
server_certificates = try_obtain_server_certificates(args, TRANSPORT_TYPE_GRPC_RERE)
|
|
65
83
|
load_fn = get_load_client_app_fn(
|
|
66
84
|
default_app_ref="",
|
|
67
|
-
app_path=
|
|
85
|
+
app_path=None,
|
|
68
86
|
flwr_dir=args.flwr_dir,
|
|
69
87
|
multi_app=True,
|
|
70
88
|
)
|
|
@@ -86,7 +104,9 @@ def run_supernode() -> None:
|
|
|
86
104
|
),
|
|
87
105
|
flwr_path=args.flwr_dir,
|
|
88
106
|
isolation=args.isolation,
|
|
89
|
-
|
|
107
|
+
clientappio_api_address=args.clientappio_api_address,
|
|
108
|
+
certificates=server_certificates,
|
|
109
|
+
ssl_ca_certfile=args.ssl_ca_certfile,
|
|
90
110
|
)
|
|
91
111
|
|
|
92
112
|
# Graceful shutdown
|
|
@@ -126,41 +146,6 @@ def _warn_deprecated_server_arg(args: argparse.Namespace) -> None:
|
|
|
126
146
|
args.superlink = args.server
|
|
127
147
|
|
|
128
148
|
|
|
129
|
-
def _get_certificates(args: argparse.Namespace) -> Optional[bytes]:
|
|
130
|
-
"""Load certificates if specified in args."""
|
|
131
|
-
# Obtain certificates
|
|
132
|
-
if args.insecure:
|
|
133
|
-
if args.root_certificates is not None:
|
|
134
|
-
sys.exit(
|
|
135
|
-
"Conflicting options: The '--insecure' flag disables HTTPS, "
|
|
136
|
-
"but '--root-certificates' was also specified. Please remove "
|
|
137
|
-
"the '--root-certificates' option when running in insecure mode, "
|
|
138
|
-
"or omit '--insecure' to use HTTPS."
|
|
139
|
-
)
|
|
140
|
-
log(
|
|
141
|
-
WARN,
|
|
142
|
-
"Option `--insecure` was set. "
|
|
143
|
-
"Starting insecure HTTP client connected to %s.",
|
|
144
|
-
args.superlink,
|
|
145
|
-
)
|
|
146
|
-
root_certificates = None
|
|
147
|
-
else:
|
|
148
|
-
# Load the certificates if provided, or load the system certificates
|
|
149
|
-
cert_path = args.root_certificates
|
|
150
|
-
if cert_path is None:
|
|
151
|
-
root_certificates = None
|
|
152
|
-
else:
|
|
153
|
-
root_certificates = Path(cert_path).read_bytes()
|
|
154
|
-
log(
|
|
155
|
-
DEBUG,
|
|
156
|
-
"Starting secure HTTPS client connected to %s "
|
|
157
|
-
"with the following certificates: %s.",
|
|
158
|
-
args.superlink,
|
|
159
|
-
cert_path,
|
|
160
|
-
)
|
|
161
|
-
return root_certificates
|
|
162
|
-
|
|
163
|
-
|
|
164
149
|
def _parse_args_run_supernode() -> argparse.ArgumentParser:
|
|
165
150
|
"""Parse flower-supernode command line arguments."""
|
|
166
151
|
parser = argparse.ArgumentParser(
|
|
@@ -171,12 +156,12 @@ def _parse_args_run_supernode() -> argparse.ArgumentParser:
|
|
|
171
156
|
"app",
|
|
172
157
|
nargs="?",
|
|
173
158
|
default=None,
|
|
174
|
-
help=
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
159
|
+
help=(
|
|
160
|
+
"(REMOVED) This argument is removed. The SuperNode now automatically "
|
|
161
|
+
"uses the ClientApp delivered from the SuperLink, so there is no need to "
|
|
162
|
+
"provide the app directory manually. This argument will be removed in a "
|
|
163
|
+
"future version."
|
|
164
|
+
),
|
|
180
165
|
)
|
|
181
166
|
_parse_args_common(parser)
|
|
182
167
|
parser.add_argument(
|
|
@@ -192,22 +177,22 @@ def _parse_args_run_supernode() -> argparse.ArgumentParser:
|
|
|
192
177
|
)
|
|
193
178
|
parser.add_argument(
|
|
194
179
|
"--isolation",
|
|
195
|
-
default=
|
|
180
|
+
default=ISOLATION_MODE_SUBPROCESS,
|
|
196
181
|
required=False,
|
|
197
182
|
choices=[
|
|
198
183
|
ISOLATION_MODE_SUBPROCESS,
|
|
199
184
|
ISOLATION_MODE_PROCESS,
|
|
200
185
|
],
|
|
201
|
-
help="Isolation mode when running a `ClientApp` (
|
|
202
|
-
"`subprocess`, `process`).
|
|
203
|
-
"
|
|
204
|
-
"
|
|
205
|
-
"independent process gets created outside of SuperNode.",
|
|
186
|
+
help="Isolation mode when running a `ClientApp` (`subprocess` by default, "
|
|
187
|
+
"possible values: `subprocess`, `process`). Use `subprocess` to configure "
|
|
188
|
+
"SuperNode to run a `ClientApp` in a subprocess. Use `process` to indicate "
|
|
189
|
+
"that a separate independent process gets created outside of SuperNode.",
|
|
206
190
|
)
|
|
207
191
|
parser.add_argument(
|
|
208
|
-
"--
|
|
209
|
-
default=
|
|
210
|
-
help="
|
|
192
|
+
"--clientappio-api-address",
|
|
193
|
+
default=CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS,
|
|
194
|
+
help="ClientAppIo API (gRPC) server address (IPv4, IPv6, or a domain name). "
|
|
195
|
+
f"By default, it is set to {CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS}.",
|
|
211
196
|
)
|
|
212
197
|
|
|
213
198
|
return parser
|
|
@@ -250,6 +235,25 @@ def _parse_args_common(parser: argparse.ArgumentParser) -> None:
|
|
|
250
235
|
help="Specifies the path to the PEM-encoded root certificate file for "
|
|
251
236
|
"establishing secure HTTPS connections.",
|
|
252
237
|
)
|
|
238
|
+
parser.add_argument(
|
|
239
|
+
"--ssl-certfile",
|
|
240
|
+
help="ClientAppIo API server SSL certificate file (as a path str) "
|
|
241
|
+
"to create a secure connection.",
|
|
242
|
+
type=str,
|
|
243
|
+
default=None,
|
|
244
|
+
)
|
|
245
|
+
parser.add_argument(
|
|
246
|
+
"--ssl-keyfile",
|
|
247
|
+
help="ClientAppIo API server SSL private key file (as a path str) "
|
|
248
|
+
"to create a secure connection.",
|
|
249
|
+
type=str,
|
|
250
|
+
)
|
|
251
|
+
parser.add_argument(
|
|
252
|
+
"--ssl-ca-certfile",
|
|
253
|
+
help="ClientAppIo API server SSL CA certificate file (as a path str) "
|
|
254
|
+
"to create a secure connection.",
|
|
255
|
+
type=str,
|
|
256
|
+
)
|
|
253
257
|
parser.add_argument(
|
|
254
258
|
"--server",
|
|
255
259
|
default=FLEET_API_GRPC_RERE_DEFAULT_ADDRESS,
|
flwr/common/args.py
CHANGED
|
@@ -21,6 +21,11 @@ from os.path import isfile
|
|
|
21
21
|
from pathlib import Path
|
|
22
22
|
from typing import Optional
|
|
23
23
|
|
|
24
|
+
from flwr.common.constant import (
|
|
25
|
+
TRANSPORT_TYPE_GRPC_ADAPTER,
|
|
26
|
+
TRANSPORT_TYPE_GRPC_RERE,
|
|
27
|
+
TRANSPORT_TYPE_REST,
|
|
28
|
+
)
|
|
24
29
|
from flwr.common.logger import log
|
|
25
30
|
|
|
26
31
|
|
|
@@ -53,12 +58,14 @@ def add_args_flwr_app_common(parser: argparse.ArgumentParser) -> None:
|
|
|
53
58
|
)
|
|
54
59
|
|
|
55
60
|
|
|
56
|
-
def
|
|
61
|
+
def try_obtain_root_certificates(
|
|
57
62
|
args: argparse.Namespace,
|
|
63
|
+
grpc_server_address: str,
|
|
58
64
|
) -> Optional[bytes]:
|
|
59
65
|
"""Validate and return the root certificates."""
|
|
66
|
+
root_cert_path = args.root_certificates
|
|
60
67
|
if args.insecure:
|
|
61
|
-
if
|
|
68
|
+
if root_cert_path is not None:
|
|
62
69
|
sys.exit(
|
|
63
70
|
"Conflicting options: The '--insecure' flag disables HTTPS, "
|
|
64
71
|
"but '--root-certificates' was also specified. Please remove "
|
|
@@ -67,17 +74,75 @@ def try_obtain_certificates(
|
|
|
67
74
|
)
|
|
68
75
|
log(
|
|
69
76
|
WARN,
|
|
70
|
-
"Option `--insecure` was set. Starting insecure HTTP channel.",
|
|
77
|
+
"Option `--insecure` was set. Starting insecure HTTP channel to %s.",
|
|
78
|
+
grpc_server_address,
|
|
71
79
|
)
|
|
72
80
|
root_certificates = None
|
|
73
81
|
else:
|
|
74
82
|
# Load the certificates if provided, or load the system certificates
|
|
75
|
-
if not isfile(
|
|
83
|
+
if not isfile(root_cert_path):
|
|
76
84
|
sys.exit("Path argument `--root-certificates` does not point to a file.")
|
|
77
|
-
root_certificates = Path(
|
|
85
|
+
root_certificates = Path(root_cert_path).read_bytes()
|
|
78
86
|
log(
|
|
79
87
|
DEBUG,
|
|
80
|
-
"Starting secure HTTPS channel
|
|
81
|
-
|
|
88
|
+
"Starting secure HTTPS channel to %s "
|
|
89
|
+
"with the following certificates: %s.",
|
|
90
|
+
grpc_server_address,
|
|
91
|
+
root_cert_path,
|
|
82
92
|
)
|
|
83
93
|
return root_certificates
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def try_obtain_server_certificates(
|
|
97
|
+
args: argparse.Namespace,
|
|
98
|
+
transport_type: str,
|
|
99
|
+
) -> Optional[tuple[bytes, bytes, bytes]]:
|
|
100
|
+
"""Validate and return the CA cert, server cert, and server private key."""
|
|
101
|
+
if args.insecure:
|
|
102
|
+
log(WARN, "Option `--insecure` was set. Starting insecure HTTP server.")
|
|
103
|
+
return None
|
|
104
|
+
# Check if certificates are provided
|
|
105
|
+
if transport_type in [TRANSPORT_TYPE_GRPC_RERE, TRANSPORT_TYPE_GRPC_ADAPTER]:
|
|
106
|
+
if args.ssl_certfile and args.ssl_keyfile and args.ssl_ca_certfile:
|
|
107
|
+
if not isfile(args.ssl_ca_certfile):
|
|
108
|
+
sys.exit("Path argument `--ssl-ca-certfile` does not point to a file.")
|
|
109
|
+
if not isfile(args.ssl_certfile):
|
|
110
|
+
sys.exit("Path argument `--ssl-certfile` does not point to a file.")
|
|
111
|
+
if not isfile(args.ssl_keyfile):
|
|
112
|
+
sys.exit("Path argument `--ssl-keyfile` does not point to a file.")
|
|
113
|
+
certificates = (
|
|
114
|
+
Path(args.ssl_ca_certfile).read_bytes(), # CA certificate
|
|
115
|
+
Path(args.ssl_certfile).read_bytes(), # server certificate
|
|
116
|
+
Path(args.ssl_keyfile).read_bytes(), # server private key
|
|
117
|
+
)
|
|
118
|
+
return certificates
|
|
119
|
+
if args.ssl_certfile or args.ssl_keyfile or args.ssl_ca_certfile:
|
|
120
|
+
sys.exit(
|
|
121
|
+
"You need to provide valid file paths to `--ssl-certfile`, "
|
|
122
|
+
"`--ssl-keyfile`, and `—-ssl-ca-certfile` to create a secure "
|
|
123
|
+
"connection in Fleet API server (gRPC-rere)."
|
|
124
|
+
)
|
|
125
|
+
if transport_type == TRANSPORT_TYPE_REST:
|
|
126
|
+
if args.ssl_certfile and args.ssl_keyfile:
|
|
127
|
+
if not isfile(args.ssl_certfile):
|
|
128
|
+
sys.exit("Path argument `--ssl-certfile` does not point to a file.")
|
|
129
|
+
if not isfile(args.ssl_keyfile):
|
|
130
|
+
sys.exit("Path argument `--ssl-keyfile` does not point to a file.")
|
|
131
|
+
certificates = (
|
|
132
|
+
b"",
|
|
133
|
+
Path(args.ssl_certfile).read_bytes(), # server certificate
|
|
134
|
+
Path(args.ssl_keyfile).read_bytes(), # server private key
|
|
135
|
+
)
|
|
136
|
+
return certificates
|
|
137
|
+
if args.ssl_certfile or args.ssl_keyfile:
|
|
138
|
+
sys.exit(
|
|
139
|
+
"You need to provide valid file paths to `--ssl-certfile` "
|
|
140
|
+
"and `--ssl-keyfile` to create a secure connection "
|
|
141
|
+
"in Fleet API server (REST, experimental)."
|
|
142
|
+
)
|
|
143
|
+
sys.exit(
|
|
144
|
+
"Certificates are required unless running in insecure mode. "
|
|
145
|
+
"Please provide certificate paths to `--ssl-certfile`, "
|
|
146
|
+
"`--ssl-keyfile`, and `—-ssl-ca-certfile` or run the server "
|
|
147
|
+
"in insecure mode using '--insecure' if you understand the risks."
|
|
148
|
+
)
|
flwr/common/constant.py
CHANGED
|
@@ -38,17 +38,30 @@ TRANSPORT_TYPES = [
|
|
|
38
38
|
]
|
|
39
39
|
|
|
40
40
|
# Addresses
|
|
41
|
+
# Ports
|
|
42
|
+
CLIENTAPPIO_PORT = "9094"
|
|
43
|
+
SERVERAPPIO_PORT = "9091"
|
|
44
|
+
FLEETAPI_GRPC_RERE_PORT = "9092"
|
|
45
|
+
FLEETAPI_PORT = "9095"
|
|
46
|
+
EXEC_API_PORT = "9093"
|
|
47
|
+
SIMULATIONIO_PORT = "9096"
|
|
48
|
+
# Octets
|
|
49
|
+
SERVER_OCTET = "0.0.0.0"
|
|
50
|
+
CLIENT_OCTET = "127.0.0.1"
|
|
41
51
|
# SuperNode
|
|
42
|
-
|
|
52
|
+
CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS = f"{SERVER_OCTET}:{CLIENTAPPIO_PORT}"
|
|
53
|
+
CLIENTAPPIO_API_DEFAULT_CLIENT_ADDRESS = f"{CLIENT_OCTET}:{CLIENTAPPIO_PORT}"
|
|
43
54
|
# SuperLink
|
|
44
|
-
|
|
45
|
-
|
|
55
|
+
SERVERAPPIO_API_DEFAULT_SERVER_ADDRESS = f"{SERVER_OCTET}:{SERVERAPPIO_PORT}"
|
|
56
|
+
SERVERAPPIO_API_DEFAULT_CLIENT_ADDRESS = f"{CLIENT_OCTET}:{SERVERAPPIO_PORT}"
|
|
57
|
+
FLEET_API_GRPC_RERE_DEFAULT_ADDRESS = f"{SERVER_OCTET}:{FLEETAPI_GRPC_RERE_PORT}"
|
|
46
58
|
FLEET_API_GRPC_BIDI_DEFAULT_ADDRESS = (
|
|
47
59
|
"[::]:8080" # IPv6 to keep start_server compatible
|
|
48
60
|
)
|
|
49
|
-
FLEET_API_REST_DEFAULT_ADDRESS = "
|
|
50
|
-
|
|
51
|
-
|
|
61
|
+
FLEET_API_REST_DEFAULT_ADDRESS = f"{SERVER_OCTET}:{FLEETAPI_PORT}"
|
|
62
|
+
EXEC_API_DEFAULT_SERVER_ADDRESS = f"{SERVER_OCTET}:{EXEC_API_PORT}"
|
|
63
|
+
SIMULATIONIO_API_DEFAULT_SERVER_ADDRESS = f"{SERVER_OCTET}:{SIMULATIONIO_PORT}"
|
|
64
|
+
SIMULATIONIO_API_DEFAULT_CLIENT_ADDRESS = f"{CLIENT_OCTET}:{SIMULATIONIO_PORT}"
|
|
52
65
|
|
|
53
66
|
# Constants for ping
|
|
54
67
|
PING_DEFAULT_INTERVAL = 30
|
|
@@ -134,6 +147,8 @@ class ErrorCode:
|
|
|
134
147
|
UNKNOWN = 0
|
|
135
148
|
LOAD_CLIENT_APP_EXCEPTION = 1
|
|
136
149
|
CLIENT_APP_RAISED_EXCEPTION = 2
|
|
150
|
+
MESSAGE_UNAVAILABLE = 3
|
|
151
|
+
REPLY_MESSAGE_UNAVAILABLE = 4
|
|
137
152
|
|
|
138
153
|
def __new__(cls) -> ErrorCode:
|
|
139
154
|
"""Prevent instantiation."""
|
flwr/common/date.py
CHANGED
|
@@ -21,3 +21,21 @@ import datetime
|
|
|
21
21
|
def now() -> datetime.datetime:
|
|
22
22
|
"""Construct a datetime from time.time() with time zone set to UTC."""
|
|
23
23
|
return datetime.datetime.now(tz=datetime.timezone.utc)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def format_timedelta(td: datetime.timedelta) -> str:
|
|
27
|
+
"""Format a timedelta as a string."""
|
|
28
|
+
days = td.days
|
|
29
|
+
hours, remainder = divmod(td.seconds, 3600)
|
|
30
|
+
minutes, seconds = divmod(remainder, 60)
|
|
31
|
+
|
|
32
|
+
if days > 0:
|
|
33
|
+
return f"{days}d {hours:02}:{minutes:02}:{seconds:02}"
|
|
34
|
+
return f"{hours:02}:{minutes:02}:{seconds:02}"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def isoformat8601_utc(dt: datetime.datetime) -> str:
|
|
38
|
+
"""Return the datetime formatted as an ISO 8601 string with a trailing 'Z'."""
|
|
39
|
+
if dt.tzinfo != datetime.timezone.utc:
|
|
40
|
+
raise ValueError("Expected datetime with timezone set to UTC")
|
|
41
|
+
return dt.isoformat(timespec="seconds").replace("+00:00", "Z")
|
flwr/common/serde.py
CHANGED
|
@@ -872,6 +872,11 @@ def run_to_proto(run: typing.Run) -> ProtoRun:
|
|
|
872
872
|
fab_version=run.fab_version,
|
|
873
873
|
fab_hash=run.fab_hash,
|
|
874
874
|
override_config=user_config_to_proto(run.override_config),
|
|
875
|
+
pending_at=run.pending_at,
|
|
876
|
+
starting_at=run.starting_at,
|
|
877
|
+
running_at=run.running_at,
|
|
878
|
+
finished_at=run.finished_at,
|
|
879
|
+
status=run_status_to_proto(run.status),
|
|
875
880
|
)
|
|
876
881
|
return proto
|
|
877
882
|
|
|
@@ -884,6 +889,11 @@ def run_from_proto(run_proto: ProtoRun) -> typing.Run:
|
|
|
884
889
|
fab_version=run_proto.fab_version,
|
|
885
890
|
fab_hash=run_proto.fab_hash,
|
|
886
891
|
override_config=user_config_from_proto(run_proto.override_config),
|
|
892
|
+
pending_at=run_proto.pending_at,
|
|
893
|
+
starting_at=run_proto.starting_at,
|
|
894
|
+
running_at=run_proto.running_at,
|
|
895
|
+
finished_at=run_proto.finished_at,
|
|
896
|
+
status=run_status_from_proto(run_proto.status),
|
|
887
897
|
)
|
|
888
898
|
return run
|
|
889
899
|
|
flwr/common/typing.py
CHANGED
|
@@ -208,7 +208,16 @@ class ClientMessage:
|
|
|
208
208
|
|
|
209
209
|
|
|
210
210
|
@dataclass
|
|
211
|
-
class
|
|
211
|
+
class RunStatus:
|
|
212
|
+
"""Run status information."""
|
|
213
|
+
|
|
214
|
+
status: str
|
|
215
|
+
sub_status: str
|
|
216
|
+
details: str
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@dataclass
|
|
220
|
+
class Run: # pylint: disable=too-many-instance-attributes
|
|
212
221
|
"""Run details."""
|
|
213
222
|
|
|
214
223
|
run_id: int
|
|
@@ -216,15 +225,27 @@ class Run:
|
|
|
216
225
|
fab_version: str
|
|
217
226
|
fab_hash: str
|
|
218
227
|
override_config: UserConfig
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
+
pending_at: str
|
|
229
|
+
starting_at: str
|
|
230
|
+
running_at: str
|
|
231
|
+
finished_at: str
|
|
232
|
+
status: RunStatus
|
|
233
|
+
|
|
234
|
+
@classmethod
|
|
235
|
+
def create_empty(cls, run_id: int) -> "Run":
|
|
236
|
+
"""Return an empty Run instance."""
|
|
237
|
+
return cls(
|
|
238
|
+
run_id=run_id,
|
|
239
|
+
fab_id="",
|
|
240
|
+
fab_version="",
|
|
241
|
+
fab_hash="",
|
|
242
|
+
override_config={},
|
|
243
|
+
pending_at="",
|
|
244
|
+
starting_at="",
|
|
245
|
+
running_at="",
|
|
246
|
+
finished_at="",
|
|
247
|
+
status=RunStatus(status="", sub_status="", details=""),
|
|
248
|
+
)
|
|
228
249
|
|
|
229
250
|
|
|
230
251
|
@dataclass
|
flwr/proto/exec_pb2.py
CHANGED
|
@@ -15,9 +15,10 @@ _sym_db = _symbol_database.Default()
|
|
|
15
15
|
from flwr.proto import fab_pb2 as flwr_dot_proto_dot_fab__pb2
|
|
16
16
|
from flwr.proto import transport_pb2 as flwr_dot_proto_dot_transport__pb2
|
|
17
17
|
from flwr.proto import recordset_pb2 as flwr_dot_proto_dot_recordset__pb2
|
|
18
|
+
from flwr.proto import run_pb2 as flwr_dot_proto_dot_run__pb2
|
|
18
19
|
|
|
19
20
|
|
|
20
|
-
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x66lwr/proto/exec.proto\x12\nflwr.proto\x1a\x14\x66lwr/proto/fab.proto\x1a\x1a\x66lwr/proto/transport.proto\x1a\x1a\x66lwr/proto/recordset.proto\"\xfb\x01\n\x0fStartRunRequest\x12\x1c\n\x03\x66\x61\x62\x18\x01 \x01(\x0b\x32\x0f.flwr.proto.Fab\x12H\n\x0foverride_config\x18\x02 \x03(\x0b\x32/.flwr.proto.StartRunRequest.OverrideConfigEntry\x12\x35\n\x12\x66\x65\x64\x65ration_options\x18\x03 \x01(\x0b\x32\x19.flwr.proto.ConfigsRecord\x1aI\n\x13OverrideConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.flwr.proto.Scalar:\x02\x38\x01\"\"\n\x10StartRunResponse\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\"<\n\x11StreamLogsRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\x12\x17\n\x0f\x61\x66ter_timestamp\x18\x02 \x01(\x01\"B\n\x12StreamLogsResponse\x12\x12\n\nlog_output\x18\x01 \x01(\t\x12\x18\n\x10latest_timestamp\x18\x02 \x01(\x01\x32\
|
|
21
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x66lwr/proto/exec.proto\x12\nflwr.proto\x1a\x14\x66lwr/proto/fab.proto\x1a\x1a\x66lwr/proto/transport.proto\x1a\x1a\x66lwr/proto/recordset.proto\x1a\x14\x66lwr/proto/run.proto\"\xfb\x01\n\x0fStartRunRequest\x12\x1c\n\x03\x66\x61\x62\x18\x01 \x01(\x0b\x32\x0f.flwr.proto.Fab\x12H\n\x0foverride_config\x18\x02 \x03(\x0b\x32/.flwr.proto.StartRunRequest.OverrideConfigEntry\x12\x35\n\x12\x66\x65\x64\x65ration_options\x18\x03 \x01(\x0b\x32\x19.flwr.proto.ConfigsRecord\x1aI\n\x13OverrideConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.flwr.proto.Scalar:\x02\x38\x01\"\"\n\x10StartRunResponse\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\"<\n\x11StreamLogsRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\x12\x17\n\x0f\x61\x66ter_timestamp\x18\x02 \x01(\x01\"B\n\x12StreamLogsResponse\x12\x12\n\nlog_output\x18\x01 \x01(\t\x12\x18\n\x10latest_timestamp\x18\x02 \x01(\x01\"1\n\x0fListRunsRequest\x12\x13\n\x06run_id\x18\x01 \x01(\x04H\x00\x88\x01\x01\x42\t\n\x07_run_id\"\x9d\x01\n\x10ListRunsResponse\x12;\n\x08run_dict\x18\x01 \x03(\x0b\x32).flwr.proto.ListRunsResponse.RunDictEntry\x12\x0b\n\x03now\x18\x02 \x01(\t\x1a?\n\x0cRunDictEntry\x12\x0b\n\x03key\x18\x01 \x01(\x04\x12\x1e\n\x05value\x18\x02 \x01(\x0b\x32\x0f.flwr.proto.Run:\x02\x38\x01\x32\xe9\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\x12G\n\x08ListRuns\x12\x1b.flwr.proto.ListRunsRequest\x1a\x1c.flwr.proto.ListRunsResponse\"\x00\x62\x06proto3')
|
|
21
22
|
|
|
22
23
|
_globals = globals()
|
|
23
24
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
@@ -26,16 +27,24 @@ if _descriptor._USE_C_DESCRIPTORS == False:
|
|
|
26
27
|
DESCRIPTOR._options = None
|
|
27
28
|
_globals['_STARTRUNREQUEST_OVERRIDECONFIGENTRY']._options = None
|
|
28
29
|
_globals['_STARTRUNREQUEST_OVERRIDECONFIGENTRY']._serialized_options = b'8\001'
|
|
29
|
-
_globals['
|
|
30
|
-
_globals['
|
|
31
|
-
_globals['
|
|
32
|
-
_globals['
|
|
33
|
-
_globals['
|
|
34
|
-
_globals['
|
|
35
|
-
_globals['
|
|
36
|
-
_globals['
|
|
37
|
-
_globals['
|
|
38
|
-
_globals['
|
|
39
|
-
_globals['
|
|
40
|
-
_globals['
|
|
30
|
+
_globals['_LISTRUNSRESPONSE_RUNDICTENTRY']._options = None
|
|
31
|
+
_globals['_LISTRUNSRESPONSE_RUNDICTENTRY']._serialized_options = b'8\001'
|
|
32
|
+
_globals['_STARTRUNREQUEST']._serialized_start=138
|
|
33
|
+
_globals['_STARTRUNREQUEST']._serialized_end=389
|
|
34
|
+
_globals['_STARTRUNREQUEST_OVERRIDECONFIGENTRY']._serialized_start=316
|
|
35
|
+
_globals['_STARTRUNREQUEST_OVERRIDECONFIGENTRY']._serialized_end=389
|
|
36
|
+
_globals['_STARTRUNRESPONSE']._serialized_start=391
|
|
37
|
+
_globals['_STARTRUNRESPONSE']._serialized_end=425
|
|
38
|
+
_globals['_STREAMLOGSREQUEST']._serialized_start=427
|
|
39
|
+
_globals['_STREAMLOGSREQUEST']._serialized_end=487
|
|
40
|
+
_globals['_STREAMLOGSRESPONSE']._serialized_start=489
|
|
41
|
+
_globals['_STREAMLOGSRESPONSE']._serialized_end=555
|
|
42
|
+
_globals['_LISTRUNSREQUEST']._serialized_start=557
|
|
43
|
+
_globals['_LISTRUNSREQUEST']._serialized_end=606
|
|
44
|
+
_globals['_LISTRUNSRESPONSE']._serialized_start=609
|
|
45
|
+
_globals['_LISTRUNSRESPONSE']._serialized_end=766
|
|
46
|
+
_globals['_LISTRUNSRESPONSE_RUNDICTENTRY']._serialized_start=703
|
|
47
|
+
_globals['_LISTRUNSRESPONSE_RUNDICTENTRY']._serialized_end=766
|
|
48
|
+
_globals['_EXEC']._serialized_start=769
|
|
49
|
+
_globals['_EXEC']._serialized_end=1002
|
|
41
50
|
# @@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 flwr.proto.fab_pb2
|
|
7
7
|
import flwr.proto.recordset_pb2
|
|
8
|
+
import flwr.proto.run_pb2
|
|
8
9
|
import flwr.proto.transport_pb2
|
|
9
10
|
import google.protobuf.descriptor
|
|
10
11
|
import google.protobuf.internal.containers
|
|
@@ -88,3 +89,46 @@ class StreamLogsResponse(google.protobuf.message.Message):
|
|
|
88
89
|
) -> None: ...
|
|
89
90
|
def ClearField(self, field_name: typing_extensions.Literal["latest_timestamp",b"latest_timestamp","log_output",b"log_output"]) -> None: ...
|
|
90
91
|
global___StreamLogsResponse = StreamLogsResponse
|
|
92
|
+
|
|
93
|
+
class ListRunsRequest(google.protobuf.message.Message):
|
|
94
|
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
|
95
|
+
RUN_ID_FIELD_NUMBER: builtins.int
|
|
96
|
+
run_id: builtins.int
|
|
97
|
+
def __init__(self,
|
|
98
|
+
*,
|
|
99
|
+
run_id: typing.Optional[builtins.int] = ...,
|
|
100
|
+
) -> None: ...
|
|
101
|
+
def HasField(self, field_name: typing_extensions.Literal["_run_id",b"_run_id","run_id",b"run_id"]) -> builtins.bool: ...
|
|
102
|
+
def ClearField(self, field_name: typing_extensions.Literal["_run_id",b"_run_id","run_id",b"run_id"]) -> None: ...
|
|
103
|
+
def WhichOneof(self, oneof_group: typing_extensions.Literal["_run_id",b"_run_id"]) -> typing.Optional[typing_extensions.Literal["run_id"]]: ...
|
|
104
|
+
global___ListRunsRequest = ListRunsRequest
|
|
105
|
+
|
|
106
|
+
class ListRunsResponse(google.protobuf.message.Message):
|
|
107
|
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
|
108
|
+
class RunDictEntry(google.protobuf.message.Message):
|
|
109
|
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
|
110
|
+
KEY_FIELD_NUMBER: builtins.int
|
|
111
|
+
VALUE_FIELD_NUMBER: builtins.int
|
|
112
|
+
key: builtins.int
|
|
113
|
+
@property
|
|
114
|
+
def value(self) -> flwr.proto.run_pb2.Run: ...
|
|
115
|
+
def __init__(self,
|
|
116
|
+
*,
|
|
117
|
+
key: builtins.int = ...,
|
|
118
|
+
value: typing.Optional[flwr.proto.run_pb2.Run] = ...,
|
|
119
|
+
) -> None: ...
|
|
120
|
+
def HasField(self, field_name: typing_extensions.Literal["value",b"value"]) -> builtins.bool: ...
|
|
121
|
+
def ClearField(self, field_name: typing_extensions.Literal["key",b"key","value",b"value"]) -> None: ...
|
|
122
|
+
|
|
123
|
+
RUN_DICT_FIELD_NUMBER: builtins.int
|
|
124
|
+
NOW_FIELD_NUMBER: builtins.int
|
|
125
|
+
@property
|
|
126
|
+
def run_dict(self) -> google.protobuf.internal.containers.MessageMap[builtins.int, flwr.proto.run_pb2.Run]: ...
|
|
127
|
+
now: typing.Text
|
|
128
|
+
def __init__(self,
|
|
129
|
+
*,
|
|
130
|
+
run_dict: typing.Optional[typing.Mapping[builtins.int, flwr.proto.run_pb2.Run]] = ...,
|
|
131
|
+
now: typing.Text = ...,
|
|
132
|
+
) -> None: ...
|
|
133
|
+
def ClearField(self, field_name: typing_extensions.Literal["now",b"now","run_dict",b"run_dict"]) -> None: ...
|
|
134
|
+
global___ListRunsResponse = ListRunsResponse
|
flwr/proto/exec_pb2_grpc.py
CHANGED
|
@@ -24,6 +24,11 @@ class ExecStub(object):
|
|
|
24
24
|
request_serializer=flwr_dot_proto_dot_exec__pb2.StreamLogsRequest.SerializeToString,
|
|
25
25
|
response_deserializer=flwr_dot_proto_dot_exec__pb2.StreamLogsResponse.FromString,
|
|
26
26
|
)
|
|
27
|
+
self.ListRuns = channel.unary_unary(
|
|
28
|
+
'/flwr.proto.Exec/ListRuns',
|
|
29
|
+
request_serializer=flwr_dot_proto_dot_exec__pb2.ListRunsRequest.SerializeToString,
|
|
30
|
+
response_deserializer=flwr_dot_proto_dot_exec__pb2.ListRunsResponse.FromString,
|
|
31
|
+
)
|
|
27
32
|
|
|
28
33
|
|
|
29
34
|
class ExecServicer(object):
|
|
@@ -43,6 +48,13 @@ class ExecServicer(object):
|
|
|
43
48
|
context.set_details('Method not implemented!')
|
|
44
49
|
raise NotImplementedError('Method not implemented!')
|
|
45
50
|
|
|
51
|
+
def ListRuns(self, request, context):
|
|
52
|
+
"""flwr ls command
|
|
53
|
+
"""
|
|
54
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
55
|
+
context.set_details('Method not implemented!')
|
|
56
|
+
raise NotImplementedError('Method not implemented!')
|
|
57
|
+
|
|
46
58
|
|
|
47
59
|
def add_ExecServicer_to_server(servicer, server):
|
|
48
60
|
rpc_method_handlers = {
|
|
@@ -56,6 +68,11 @@ def add_ExecServicer_to_server(servicer, server):
|
|
|
56
68
|
request_deserializer=flwr_dot_proto_dot_exec__pb2.StreamLogsRequest.FromString,
|
|
57
69
|
response_serializer=flwr_dot_proto_dot_exec__pb2.StreamLogsResponse.SerializeToString,
|
|
58
70
|
),
|
|
71
|
+
'ListRuns': grpc.unary_unary_rpc_method_handler(
|
|
72
|
+
servicer.ListRuns,
|
|
73
|
+
request_deserializer=flwr_dot_proto_dot_exec__pb2.ListRunsRequest.FromString,
|
|
74
|
+
response_serializer=flwr_dot_proto_dot_exec__pb2.ListRunsResponse.SerializeToString,
|
|
75
|
+
),
|
|
59
76
|
}
|
|
60
77
|
generic_handler = grpc.method_handlers_generic_handler(
|
|
61
78
|
'flwr.proto.Exec', rpc_method_handlers)
|
|
@@ -99,3 +116,20 @@ class Exec(object):
|
|
|
99
116
|
flwr_dot_proto_dot_exec__pb2.StreamLogsResponse.FromString,
|
|
100
117
|
options, channel_credentials,
|
|
101
118
|
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
|
119
|
+
|
|
120
|
+
@staticmethod
|
|
121
|
+
def ListRuns(request,
|
|
122
|
+
target,
|
|
123
|
+
options=(),
|
|
124
|
+
channel_credentials=None,
|
|
125
|
+
call_credentials=None,
|
|
126
|
+
insecure=False,
|
|
127
|
+
compression=None,
|
|
128
|
+
wait_for_ready=None,
|
|
129
|
+
timeout=None,
|
|
130
|
+
metadata=None):
|
|
131
|
+
return grpc.experimental.unary_unary(request, target, '/flwr.proto.Exec/ListRuns',
|
|
132
|
+
flwr_dot_proto_dot_exec__pb2.ListRunsRequest.SerializeToString,
|
|
133
|
+
flwr_dot_proto_dot_exec__pb2.ListRunsResponse.FromString,
|
|
134
|
+
options, channel_credentials,
|
|
135
|
+
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
flwr/proto/exec_pb2_grpc.pyi
CHANGED
|
@@ -19,6 +19,11 @@ class ExecStub:
|
|
|
19
19
|
flwr.proto.exec_pb2.StreamLogsResponse]
|
|
20
20
|
"""Start log stream upon request"""
|
|
21
21
|
|
|
22
|
+
ListRuns: grpc.UnaryUnaryMultiCallable[
|
|
23
|
+
flwr.proto.exec_pb2.ListRunsRequest,
|
|
24
|
+
flwr.proto.exec_pb2.ListRunsResponse]
|
|
25
|
+
"""flwr ls command"""
|
|
26
|
+
|
|
22
27
|
|
|
23
28
|
class ExecServicer(metaclass=abc.ABCMeta):
|
|
24
29
|
@abc.abstractmethod
|
|
@@ -37,5 +42,13 @@ class ExecServicer(metaclass=abc.ABCMeta):
|
|
|
37
42
|
"""Start log stream upon request"""
|
|
38
43
|
pass
|
|
39
44
|
|
|
45
|
+
@abc.abstractmethod
|
|
46
|
+
def ListRuns(self,
|
|
47
|
+
request: flwr.proto.exec_pb2.ListRunsRequest,
|
|
48
|
+
context: grpc.ServicerContext,
|
|
49
|
+
) -> flwr.proto.exec_pb2.ListRunsResponse:
|
|
50
|
+
"""flwr ls command"""
|
|
51
|
+
pass
|
|
52
|
+
|
|
40
53
|
|
|
41
54
|
def add_ExecServicer_to_server(servicer: ExecServicer, server: grpc.Server) -> None: ...
|