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.

Files changed (41) hide show
  1. flwr/cli/app.py +2 -0
  2. flwr/cli/build.py +37 -0
  3. flwr/cli/install.py +5 -3
  4. flwr/cli/ls.py +228 -0
  5. flwr/client/app.py +58 -13
  6. flwr/client/clientapp/app.py +34 -23
  7. flwr/client/grpc_rere_client/connection.py +2 -12
  8. flwr/client/rest_client/connection.py +4 -14
  9. flwr/client/supernode/app.py +57 -53
  10. flwr/common/args.py +72 -7
  11. flwr/common/constant.py +21 -6
  12. flwr/common/date.py +18 -0
  13. flwr/common/serde.py +10 -0
  14. flwr/common/typing.py +31 -10
  15. flwr/proto/exec_pb2.py +22 -13
  16. flwr/proto/exec_pb2.pyi +44 -0
  17. flwr/proto/exec_pb2_grpc.py +34 -0
  18. flwr/proto/exec_pb2_grpc.pyi +13 -0
  19. flwr/proto/run_pb2.py +30 -30
  20. flwr/proto/run_pb2.pyi +18 -1
  21. flwr/server/app.py +39 -68
  22. flwr/server/driver/grpc_driver.py +4 -14
  23. flwr/server/run_serverapp.py +8 -238
  24. flwr/server/serverapp/app.py +34 -23
  25. flwr/server/superlink/fleet/rest_rere/rest_api.py +10 -9
  26. flwr/server/superlink/linkstate/in_memory_linkstate.py +71 -46
  27. flwr/server/superlink/linkstate/linkstate.py +19 -5
  28. flwr/server/superlink/linkstate/sqlite_linkstate.py +81 -113
  29. flwr/server/superlink/linkstate/utils.py +193 -3
  30. flwr/simulation/app.py +6 -41
  31. flwr/simulation/legacy_app.py +21 -1
  32. flwr/simulation/run_simulation.py +7 -18
  33. flwr/simulation/simulationio_connection.py +2 -2
  34. flwr/superexec/deployment.py +12 -6
  35. flwr/superexec/exec_servicer.py +31 -2
  36. flwr/superexec/simulation.py +11 -46
  37. {flwr_nightly-1.13.0.dev20241111.dist-info → flwr_nightly-1.13.0.dev20241117.dist-info}/METADATA +6 -4
  38. {flwr_nightly-1.13.0.dev20241111.dist-info → flwr_nightly-1.13.0.dev20241117.dist-info}/RECORD +41 -40
  39. {flwr_nightly-1.13.0.dev20241111.dist-info → flwr_nightly-1.13.0.dev20241117.dist-info}/LICENSE +0 -0
  40. {flwr_nightly-1.13.0.dev20241111.dist-info → flwr_nightly-1.13.0.dev20241117.dist-info}/WHEEL +0 -0
  41. {flwr_nightly-1.13.0.dev20241111.dist-info → flwr_nightly-1.13.0.dev20241117.dist-info}/entry_points.txt +0 -0
@@ -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
- root_certificates = _get_certificates(args)
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=args.app,
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
- supernode_address=args.supernode_address,
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="Specify the path of the Flower App to load and run the `ClientApp`. "
175
- "The `pyproject.toml` file must be located in the root of this path. "
176
- "When this argument is provided, the SuperNode will exclusively respond to "
177
- "messages from the corresponding `ServerApp` by matching the FAB ID and FAB "
178
- "version. An error will be raised if a message is received from any other "
179
- "`ServerApp`.",
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=None,
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` (optional, possible values: "
202
- "`subprocess`, `process`). By default, a `ClientApp` runs in the same process "
203
- "that executes the SuperNode. Use `subprocess` to configure SuperNode to run "
204
- "a `ClientApp` in a subprocess. Use `process` to indicate that a separate "
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
- "--supernode-address",
209
- default="0.0.0.0:9094",
210
- help="Set the SuperNode gRPC server address. Defaults to `0.0.0.0:9094`.",
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 try_obtain_certificates(
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 args.root_certificates is not None:
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(args.root_certificates):
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(args.root_certificates).read_bytes()
85
+ root_certificates = Path(root_cert_path).read_bytes()
78
86
  log(
79
87
  DEBUG,
80
- "Starting secure HTTPS channel with the following certificates: %s.",
81
- args.root_certificates,
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
- CLIENTAPPIO_API_DEFAULT_ADDRESS = "0.0.0.0:9094"
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
- SERVERAPPIO_API_DEFAULT_ADDRESS = "0.0.0.0:9091"
45
- FLEET_API_GRPC_RERE_DEFAULT_ADDRESS = "0.0.0.0:9092"
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 = "0.0.0.0:9095"
50
- EXEC_API_DEFAULT_ADDRESS = "0.0.0.0:9093"
51
- SIMULATIONIO_API_DEFAULT_ADDRESS = "0.0.0.0:9096"
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 Run:
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
- @dataclass
222
- class RunStatus:
223
- """Run status information."""
224
-
225
- status: str
226
- sub_status: str
227
- details: str
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\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')
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['_STARTRUNREQUEST']._serialized_start=116
30
- _globals['_STARTRUNREQUEST']._serialized_end=367
31
- _globals['_STARTRUNREQUEST_OVERRIDECONFIGENTRY']._serialized_start=294
32
- _globals['_STARTRUNREQUEST_OVERRIDECONFIGENTRY']._serialized_end=367
33
- _globals['_STARTRUNRESPONSE']._serialized_start=369
34
- _globals['_STARTRUNRESPONSE']._serialized_end=403
35
- _globals['_STREAMLOGSREQUEST']._serialized_start=405
36
- _globals['_STREAMLOGSREQUEST']._serialized_end=465
37
- _globals['_STREAMLOGSRESPONSE']._serialized_start=467
38
- _globals['_STREAMLOGSRESPONSE']._serialized_end=533
39
- _globals['_EXEC']._serialized_start=536
40
- _globals['_EXEC']._serialized_end=696
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
@@ -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)
@@ -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: ...