flwr 1.15.2__py3-none-any.whl → 1.17.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. flwr/cli/build.py +2 -0
  2. flwr/cli/log.py +20 -21
  3. flwr/cli/new/templates/app/code/flwr_tune/client_app.py.tpl +1 -1
  4. flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +1 -1
  5. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +1 -1
  6. flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +1 -1
  7. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
  8. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -1
  9. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
  10. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +1 -1
  11. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
  12. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +1 -1
  13. flwr/cli/run/run.py +5 -9
  14. flwr/client/app.py +6 -4
  15. flwr/client/client_app.py +260 -86
  16. flwr/client/clientapp/app.py +6 -2
  17. flwr/client/grpc_client/connection.py +24 -21
  18. flwr/client/message_handler/message_handler.py +28 -28
  19. flwr/client/mod/__init__.py +2 -2
  20. flwr/client/mod/centraldp_mods.py +7 -7
  21. flwr/client/mod/comms_mods.py +16 -22
  22. flwr/client/mod/localdp_mod.py +4 -4
  23. flwr/client/mod/secure_aggregation/secaggplus_mod.py +31 -31
  24. flwr/client/rest_client/connection.py +4 -6
  25. flwr/client/run_info_store.py +2 -2
  26. flwr/client/supernode/__init__.py +0 -2
  27. flwr/client/supernode/app.py +1 -11
  28. flwr/common/__init__.py +12 -4
  29. flwr/common/address.py +35 -0
  30. flwr/common/args.py +8 -2
  31. flwr/common/auth_plugin/auth_plugin.py +2 -1
  32. flwr/common/config.py +4 -4
  33. flwr/common/constant.py +16 -0
  34. flwr/common/context.py +4 -4
  35. flwr/common/event_log_plugin/__init__.py +22 -0
  36. flwr/common/event_log_plugin/event_log_plugin.py +60 -0
  37. flwr/common/grpc.py +1 -1
  38. flwr/common/logger.py +2 -2
  39. flwr/common/message.py +338 -102
  40. flwr/common/object_ref.py +0 -10
  41. flwr/common/record/__init__.py +8 -4
  42. flwr/common/record/arrayrecord.py +626 -0
  43. flwr/common/record/{configsrecord.py → configrecord.py} +75 -29
  44. flwr/common/record/conversion_utils.py +9 -18
  45. flwr/common/record/{metricsrecord.py → metricrecord.py} +78 -32
  46. flwr/common/record/recorddict.py +288 -0
  47. flwr/common/recorddict_compat.py +410 -0
  48. flwr/common/secure_aggregation/quantization.py +5 -1
  49. flwr/common/secure_aggregation/secaggplus_constants.py +1 -1
  50. flwr/common/serde.py +67 -190
  51. flwr/common/telemetry.py +0 -10
  52. flwr/common/typing.py +44 -8
  53. flwr/proto/exec_pb2.py +3 -3
  54. flwr/proto/exec_pb2.pyi +3 -3
  55. flwr/proto/message_pb2.py +12 -12
  56. flwr/proto/message_pb2.pyi +9 -9
  57. flwr/proto/recorddict_pb2.py +70 -0
  58. flwr/proto/{recordset_pb2.pyi → recorddict_pb2.pyi} +35 -35
  59. flwr/proto/run_pb2.py +31 -31
  60. flwr/proto/run_pb2.pyi +3 -3
  61. flwr/server/__init__.py +3 -1
  62. flwr/server/app.py +74 -3
  63. flwr/server/compat/__init__.py +2 -2
  64. flwr/server/compat/app.py +15 -12
  65. flwr/server/compat/app_utils.py +26 -18
  66. flwr/server/compat/{driver_client_proxy.py → grid_client_proxy.py} +41 -41
  67. flwr/server/fleet_event_log_interceptor.py +94 -0
  68. flwr/server/{driver → grid}/__init__.py +8 -7
  69. flwr/server/{driver/driver.py → grid/grid.py} +48 -19
  70. flwr/server/{driver/grpc_driver.py → grid/grpc_grid.py} +88 -56
  71. flwr/server/{driver/inmemory_driver.py → grid/inmemory_grid.py} +41 -54
  72. flwr/server/run_serverapp.py +6 -17
  73. flwr/server/server_app.py +126 -33
  74. flwr/server/serverapp/app.py +10 -10
  75. flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +2 -2
  76. flwr/server/superlink/fleet/message_handler/message_handler.py +8 -12
  77. flwr/server/superlink/fleet/vce/backend/backend.py +3 -3
  78. flwr/server/superlink/fleet/vce/backend/raybackend.py +2 -2
  79. flwr/server/superlink/fleet/vce/vce_api.py +33 -38
  80. flwr/server/superlink/linkstate/in_memory_linkstate.py +171 -132
  81. flwr/server/superlink/linkstate/linkstate.py +51 -64
  82. flwr/server/superlink/linkstate/sqlite_linkstate.py +253 -285
  83. flwr/server/superlink/linkstate/utils.py +171 -133
  84. flwr/server/superlink/{driver → serverappio}/__init__.py +1 -1
  85. flwr/server/superlink/{driver → serverappio}/serverappio_grpc.py +1 -1
  86. flwr/server/superlink/{driver → serverappio}/serverappio_servicer.py +27 -29
  87. flwr/server/superlink/simulation/simulationio_servicer.py +2 -2
  88. flwr/server/typing.py +3 -3
  89. flwr/server/utils/__init__.py +2 -2
  90. flwr/server/utils/validator.py +53 -68
  91. flwr/server/workflow/default_workflows.py +52 -58
  92. flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +48 -50
  93. flwr/simulation/app.py +2 -2
  94. flwr/simulation/ray_transport/ray_actor.py +4 -2
  95. flwr/simulation/ray_transport/ray_client_proxy.py +34 -32
  96. flwr/simulation/run_simulation.py +15 -15
  97. flwr/superexec/app.py +0 -14
  98. flwr/superexec/deployment.py +4 -4
  99. flwr/superexec/exec_event_log_interceptor.py +135 -0
  100. flwr/superexec/exec_grpc.py +10 -4
  101. flwr/superexec/exec_servicer.py +6 -6
  102. flwr/superexec/exec_user_auth_interceptor.py +22 -4
  103. flwr/superexec/executor.py +3 -3
  104. flwr/superexec/simulation.py +3 -3
  105. {flwr-1.15.2.dist-info → flwr-1.17.0.dist-info}/METADATA +5 -5
  106. {flwr-1.15.2.dist-info → flwr-1.17.0.dist-info}/RECORD +111 -112
  107. {flwr-1.15.2.dist-info → flwr-1.17.0.dist-info}/entry_points.txt +0 -3
  108. flwr/client/message_handler/task_handler.py +0 -37
  109. flwr/common/record/parametersrecord.py +0 -204
  110. flwr/common/record/recordset.py +0 -202
  111. flwr/common/recordset_compat.py +0 -418
  112. flwr/proto/recordset_pb2.py +0 -70
  113. flwr/proto/task_pb2.py +0 -33
  114. flwr/proto/task_pb2.pyi +0 -100
  115. flwr/proto/task_pb2_grpc.py +0 -4
  116. flwr/proto/task_pb2_grpc.pyi +0 -4
  117. /flwr/proto/{recordset_pb2_grpc.py → recorddict_pb2_grpc.py} +0 -0
  118. /flwr/proto/{recordset_pb2_grpc.pyi → recorddict_pb2_grpc.pyi} +0 -0
  119. {flwr-1.15.2.dist-info → flwr-1.17.0.dist-info}/LICENSE +0 -0
  120. {flwr-1.15.2.dist-info → flwr-1.17.0.dist-info}/WHEEL +0 -0
flwr/common/address.py CHANGED
@@ -15,10 +15,13 @@
15
15
  """Flower IP address utils."""
16
16
 
17
17
 
18
+ import re
18
19
  import socket
19
20
  from ipaddress import ip_address
20
21
  from typing import Optional
21
22
 
23
+ import grpc
24
+
22
25
  IPV6: int = 6
23
26
 
24
27
 
@@ -101,3 +104,35 @@ def is_port_in_use(address: str) -> bool:
101
104
  return True
102
105
 
103
106
  return False
107
+
108
+
109
+ def get_ip_address_from_servicer_context(context: grpc.ServicerContext) -> str:
110
+ """Extract the client's IPv4 or IPv6 address from the gRPC ServicerContext.
111
+
112
+ Parameters
113
+ ----------
114
+ context : grpc.ServicerContext
115
+ The gRPC ServicerContext object. The context.peer() returns a string like
116
+ "ipv4:127.0.0.1:56789" for IPv4 and "ipv6:[2001:db8::1]:54321" for IPv6.
117
+
118
+ Returns
119
+ -------
120
+ str
121
+ If one of the format matches, the function will return the client's IP address,
122
+ otherwise, it will raise a ValueError.
123
+ """
124
+ peer: str = context.peer()
125
+ # Match IPv4: "ipv4:IP:port"
126
+ ipv4_match = re.match(r"^ipv4:(?P<ip>[^:]+):", peer)
127
+ if ipv4_match:
128
+ return ipv4_match.group("ip")
129
+
130
+ # Match IPv6: "ipv6:[IP]:port"
131
+ ipv6_match = re.match(r"^ipv6:\[(?P<ip>[^\]]+)\]:", peer)
132
+ if ipv6_match:
133
+ return ipv6_match.group("ip")
134
+
135
+ raise ValueError(
136
+ f"Unsupported peer address format: {peer} for the transport protocol. "
137
+ "The supported formats are ipv4:IP:port and ipv6:[IP]:port."
138
+ )
flwr/common/args.py CHANGED
@@ -43,7 +43,8 @@ def add_args_flwr_app_common(parser: argparse.ArgumentParser) -> None:
43
43
  "--insecure",
44
44
  action="store_true",
45
45
  help="Run the server without HTTPS, regardless of whether certificate "
46
- "paths are provided. By default, the server runs with HTTPS enabled. "
46
+ "paths are provided. Data transmitted between the gRPC client and server "
47
+ "is not encrypted. By default, the server runs with HTTPS enabled. "
47
48
  "Use this flag only if you understand the risks.",
48
49
  )
49
50
 
@@ -99,7 +100,12 @@ def try_obtain_server_certificates(
99
100
  ) -> Optional[tuple[bytes, bytes, bytes]]:
100
101
  """Validate and return the CA cert, server cert, and server private key."""
101
102
  if args.insecure:
102
- log(WARN, "Option `--insecure` was set. Starting insecure HTTP server.")
103
+ log(
104
+ WARN,
105
+ "Option `--insecure` was set. Starting insecure HTTP server with "
106
+ "unencrypted communication (TLS disabled). Proceed only if you understand "
107
+ "the risks.",
108
+ )
103
109
  return None
104
110
  # Check if certificates are provided
105
111
  if args.ssl_certfile and args.ssl_keyfile and args.ssl_ca_certfile:
@@ -20,6 +20,7 @@ from collections.abc import Sequence
20
20
  from pathlib import Path
21
21
  from typing import Optional, Union
22
22
 
23
+ from flwr.common.typing import UserInfo
23
24
  from flwr.proto.exec_pb2_grpc import ExecStub
24
25
 
25
26
  from ..typing import UserAuthCredentials, UserAuthLoginDetails
@@ -49,7 +50,7 @@ class ExecAuthPlugin(ABC):
49
50
  @abstractmethod
50
51
  def validate_tokens_in_metadata(
51
52
  self, metadata: Sequence[tuple[str, Union[str, bytes]]]
52
- ) -> bool:
53
+ ) -> tuple[bool, Optional[UserInfo]]:
53
54
  """Validate authentication tokens in the provided metadata."""
54
55
 
55
56
  @abstractmethod
flwr/common/config.py CHANGED
@@ -34,7 +34,7 @@ from flwr.common.constant import (
34
34
  )
35
35
  from flwr.common.typing import Run, UserConfig, UserConfigValue
36
36
 
37
- from . import ConfigsRecord, object_ref
37
+ from . import ConfigRecord, object_ref
38
38
 
39
39
  T_dict = TypeVar("T_dict", bound=dict[str, Any]) # pylint: disable=invalid-name
40
40
 
@@ -260,9 +260,9 @@ def get_metadata_from_config(config: dict[str, Any]) -> tuple[str, str]:
260
260
  )
261
261
 
262
262
 
263
- def user_config_to_configsrecord(config: UserConfig) -> ConfigsRecord:
264
- """Construct a `ConfigsRecord` out of a `UserConfig`."""
265
- c_record = ConfigsRecord()
263
+ def user_config_to_configrecord(config: UserConfig) -> ConfigRecord:
264
+ """Construct a `ConfigRecord` out of a `UserConfig`."""
265
+ c_record = ConfigRecord()
266
266
  for k, v in config.items():
267
267
  c_record[k] = v
268
268
 
flwr/common/constant.py CHANGED
@@ -61,6 +61,7 @@ PING_CALL_TIMEOUT = 5
61
61
  PING_BASE_MULTIPLIER = 0.8
62
62
  PING_RANDOM_RANGE = (-0.1, 0.1)
63
63
  PING_MAX_INTERVAL = 1e300
64
+ PING_PATIENCE = 2
64
65
 
65
66
  # IDs
66
67
  RUN_ID_NUM_BYTES = 8
@@ -120,6 +121,9 @@ TIMESTAMP_HEADER = "flwr-timestamp"
120
121
  TIMESTAMP_TOLERANCE = 10 # General tolerance for timestamp verification
121
122
  SYSTEM_TIME_TOLERANCE = 5 # Allowance for system time drift
122
123
 
124
+ # Constants for ArrayRecord
125
+ GC_THRESHOLD = 200_000_000 # 200 MB
126
+
123
127
 
124
128
  class MessageType:
125
129
  """Message type."""
@@ -127,6 +131,7 @@ class MessageType:
127
131
  TRAIN = "train"
128
132
  EVALUATE = "evaluate"
129
133
  QUERY = "query"
134
+ SYSTEM = "system"
130
135
 
131
136
  def __new__(cls) -> MessageType:
132
137
  """Prevent instantiation."""
@@ -162,6 +167,7 @@ class ErrorCode:
162
167
  CLIENT_APP_RAISED_EXCEPTION = 2
163
168
  MESSAGE_UNAVAILABLE = 3
164
169
  REPLY_MESSAGE_UNAVAILABLE = 4
170
+ NODE_UNAVAILABLE = 5
165
171
 
166
172
  def __new__(cls) -> ErrorCode:
167
173
  """Prevent instantiation."""
@@ -212,3 +218,13 @@ class AuthType:
212
218
  def __new__(cls) -> AuthType:
213
219
  """Prevent instantiation."""
214
220
  raise TypeError(f"{cls.__name__} cannot be instantiated.")
221
+
222
+
223
+ class EventLogWriterType:
224
+ """Event log writer types."""
225
+
226
+ STDOUT = "stdout"
227
+
228
+ def __new__(cls) -> EventLogWriterType:
229
+ """Prevent instantiation."""
230
+ raise TypeError(f"{cls.__name__} cannot be instantiated.")
flwr/common/context.py CHANGED
@@ -17,7 +17,7 @@
17
17
 
18
18
  from dataclasses import dataclass
19
19
 
20
- from .record import RecordSet
20
+ from .record import RecordDict
21
21
  from .typing import UserConfig
22
22
 
23
23
 
@@ -34,7 +34,7 @@ class Context:
34
34
  node_config : UserConfig
35
35
  A config (key/value mapping) unique to the node and independent of the
36
36
  `run_config`. This config persists across all runs this node participates in.
37
- state : RecordSet
37
+ state : RecordDict
38
38
  Holds records added by the entity in a given `run_id` and that will stay local.
39
39
  This means that the data it holds will never leave the system it's running from.
40
40
  This can be used as an intermediate storage or scratchpad when
@@ -50,7 +50,7 @@ class Context:
50
50
  run_id: int
51
51
  node_id: int
52
52
  node_config: UserConfig
53
- state: RecordSet
53
+ state: RecordDict
54
54
  run_config: UserConfig
55
55
 
56
56
  def __init__( # pylint: disable=too-many-arguments, too-many-positional-arguments
@@ -58,7 +58,7 @@ class Context:
58
58
  run_id: int,
59
59
  node_id: int,
60
60
  node_config: UserConfig,
61
- state: RecordSet,
61
+ state: RecordDict,
62
62
  run_config: UserConfig,
63
63
  ) -> None:
64
64
  self.run_id = run_id
@@ -0,0 +1,22 @@
1
+ # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+ """Event log plugin components."""
16
+
17
+
18
+ from .event_log_plugin import EventLogWriterPlugin as EventLogWriterPlugin
19
+
20
+ __all__ = [
21
+ "EventLogWriterPlugin",
22
+ ]
@@ -0,0 +1,60 @@
1
+ # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+ """Abstract class for Flower Event Log Writer Plugin."""
16
+
17
+
18
+ from abc import ABC, abstractmethod
19
+ from typing import Optional, Union
20
+
21
+ import grpc
22
+ from google.protobuf.message import Message as GrpcMessage
23
+
24
+ from flwr.common.typing import LogEntry, UserInfo
25
+
26
+
27
+ class EventLogWriterPlugin(ABC):
28
+ """Abstract Flower Event Log Writer Plugin class for ExecServicer."""
29
+
30
+ @abstractmethod
31
+ def __init__(self) -> None:
32
+ """Abstract constructor."""
33
+
34
+ @abstractmethod
35
+ def compose_log_before_event( # pylint: disable=too-many-arguments
36
+ self,
37
+ request: GrpcMessage,
38
+ context: grpc.ServicerContext,
39
+ user_info: Optional[UserInfo],
40
+ method_name: str,
41
+ ) -> LogEntry:
42
+ """Compose pre-event log entry from the provided request and context."""
43
+
44
+ @abstractmethod
45
+ def compose_log_after_event( # pylint: disable=too-many-arguments,R0917
46
+ self,
47
+ request: GrpcMessage,
48
+ context: grpc.ServicerContext,
49
+ user_info: Optional[UserInfo],
50
+ method_name: str,
51
+ response: Optional[Union[GrpcMessage, BaseException]],
52
+ ) -> LogEntry:
53
+ """Compose post-event log entry from the provided response and context."""
54
+
55
+ @abstractmethod
56
+ def write_log(
57
+ self,
58
+ log_entry: LogEntry,
59
+ ) -> None:
60
+ """Write the event log to the specified data sink."""
flwr/common/grpc.py CHANGED
@@ -27,7 +27,7 @@ import grpc
27
27
  from .address import is_port_in_use
28
28
  from .logger import log
29
29
 
30
- GRPC_MAX_MESSAGE_LENGTH: int = 536_870_912 # == 512 * 1024 * 1024
30
+ GRPC_MAX_MESSAGE_LENGTH: int = 2_147_483_647 # == 2048 * 1024 * 1024 -1 (2GB)
31
31
 
32
32
  INVALID_CERTIFICATES_ERR_MSG = """
33
33
  When setting any of root_certificate, certificate, or private_key,
flwr/common/logger.py CHANGED
@@ -250,9 +250,9 @@ def warn_deprecated_feature_with_example(
250
250
  log(
251
251
  WARN,
252
252
  """FEATURE UPDATE: %s
253
- ------------------------------------------------------------
253
+ ------------------------------------------------------------
254
254
  %s
255
- ------------------------------------------------------------
255
+ ------------------------------------------------------------
256
256
  """,
257
257
  example_message,
258
258
  code_example,