flwr 1.21.0__py3-none-any.whl → 1.23.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 (175) hide show
  1. flwr/cli/app.py +17 -1
  2. flwr/cli/auth_plugin/__init__.py +15 -6
  3. flwr/cli/auth_plugin/auth_plugin.py +95 -0
  4. flwr/cli/auth_plugin/noop_auth_plugin.py +58 -0
  5. flwr/cli/auth_plugin/oidc_cli_plugin.py +16 -25
  6. flwr/cli/build.py +118 -47
  7. flwr/cli/{cli_user_auth_interceptor.py → cli_account_auth_interceptor.py} +6 -5
  8. flwr/cli/log.py +2 -2
  9. flwr/cli/login/login.py +34 -23
  10. flwr/cli/ls.py +13 -9
  11. flwr/cli/new/new.py +196 -42
  12. flwr/cli/new/templates/app/README.flowertune.md.tpl +1 -1
  13. flwr/cli/new/templates/app/code/client.baseline.py.tpl +64 -47
  14. flwr/cli/new/templates/app/code/client.huggingface.py.tpl +68 -30
  15. flwr/cli/new/templates/app/code/client.jax.py.tpl +63 -42
  16. flwr/cli/new/templates/app/code/client.mlx.py.tpl +80 -51
  17. flwr/cli/new/templates/app/code/client.numpy.py.tpl +36 -13
  18. flwr/cli/new/templates/app/code/client.pytorch.py.tpl +71 -46
  19. flwr/cli/new/templates/app/code/client.pytorch_legacy_api.py.tpl +55 -0
  20. flwr/cli/new/templates/app/code/client.sklearn.py.tpl +75 -30
  21. flwr/cli/new/templates/app/code/client.tensorflow.py.tpl +69 -44
  22. flwr/cli/new/templates/app/code/client.xgboost.py.tpl +110 -0
  23. flwr/cli/new/templates/app/code/flwr_tune/client_app.py.tpl +56 -90
  24. flwr/cli/new/templates/app/code/flwr_tune/models.py.tpl +1 -23
  25. flwr/cli/new/templates/app/code/flwr_tune/server_app.py.tpl +37 -58
  26. flwr/cli/new/templates/app/code/flwr_tune/strategy.py.tpl +39 -44
  27. flwr/cli/new/templates/app/code/model.baseline.py.tpl +0 -14
  28. flwr/cli/new/templates/app/code/server.baseline.py.tpl +27 -29
  29. flwr/cli/new/templates/app/code/server.huggingface.py.tpl +23 -19
  30. flwr/cli/new/templates/app/code/server.jax.py.tpl +27 -14
  31. flwr/cli/new/templates/app/code/server.mlx.py.tpl +29 -19
  32. flwr/cli/new/templates/app/code/server.numpy.py.tpl +30 -17
  33. flwr/cli/new/templates/app/code/server.pytorch.py.tpl +36 -26
  34. flwr/cli/new/templates/app/code/server.pytorch_legacy_api.py.tpl +31 -0
  35. flwr/cli/new/templates/app/code/server.sklearn.py.tpl +29 -21
  36. flwr/cli/new/templates/app/code/server.tensorflow.py.tpl +28 -19
  37. flwr/cli/new/templates/app/code/server.xgboost.py.tpl +56 -0
  38. flwr/cli/new/templates/app/code/task.huggingface.py.tpl +16 -20
  39. flwr/cli/new/templates/app/code/task.jax.py.tpl +1 -1
  40. flwr/cli/new/templates/app/code/task.numpy.py.tpl +1 -1
  41. flwr/cli/new/templates/app/code/task.pytorch.py.tpl +14 -27
  42. flwr/cli/new/templates/app/code/{task.pytorch_msg_api.py.tpl → task.pytorch_legacy_api.py.tpl} +27 -14
  43. flwr/cli/new/templates/app/code/task.tensorflow.py.tpl +1 -2
  44. flwr/cli/new/templates/app/code/task.xgboost.py.tpl +67 -0
  45. flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +4 -4
  46. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +2 -2
  47. flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +4 -4
  48. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
  49. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +2 -2
  50. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
  51. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +3 -3
  52. flwr/cli/new/templates/app/{pyproject.pytorch_msg_api.toml.tpl → pyproject.pytorch_legacy_api.toml.tpl} +3 -3
  53. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
  54. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +1 -1
  55. flwr/cli/new/templates/app/pyproject.xgboost.toml.tpl +61 -0
  56. flwr/cli/pull.py +100 -0
  57. flwr/cli/run/run.py +11 -7
  58. flwr/cli/stop.py +2 -2
  59. flwr/cli/supernode/__init__.py +25 -0
  60. flwr/cli/supernode/ls.py +260 -0
  61. flwr/cli/supernode/register.py +185 -0
  62. flwr/cli/supernode/unregister.py +138 -0
  63. flwr/cli/utils.py +109 -69
  64. flwr/client/__init__.py +2 -1
  65. flwr/client/grpc_adapter_client/connection.py +6 -8
  66. flwr/client/grpc_rere_client/connection.py +59 -31
  67. flwr/client/grpc_rere_client/grpc_adapter.py +28 -12
  68. flwr/client/grpc_rere_client/{client_interceptor.py → node_auth_client_interceptor.py} +3 -6
  69. flwr/client/mod/secure_aggregation/secaggplus_mod.py +7 -5
  70. flwr/client/rest_client/connection.py +82 -37
  71. flwr/clientapp/__init__.py +1 -2
  72. flwr/clientapp/mod/__init__.py +4 -1
  73. flwr/clientapp/mod/centraldp_mods.py +156 -40
  74. flwr/clientapp/mod/localdp_mod.py +169 -0
  75. flwr/clientapp/typing.py +22 -0
  76. flwr/{client/clientapp → clientapp}/utils.py +1 -1
  77. flwr/common/constant.py +56 -13
  78. flwr/common/exit/exit_code.py +24 -10
  79. flwr/common/inflatable_utils.py +10 -10
  80. flwr/common/record/array.py +3 -3
  81. flwr/common/record/arrayrecord.py +10 -1
  82. flwr/common/record/typeddict.py +12 -0
  83. flwr/common/secure_aggregation/crypto/symmetric_encryption.py +1 -89
  84. flwr/common/serde.py +4 -2
  85. flwr/common/typing.py +7 -6
  86. flwr/compat/client/app.py +1 -1
  87. flwr/compat/client/grpc_client/connection.py +2 -2
  88. flwr/proto/control_pb2.py +48 -31
  89. flwr/proto/control_pb2.pyi +95 -5
  90. flwr/proto/control_pb2_grpc.py +136 -0
  91. flwr/proto/control_pb2_grpc.pyi +52 -0
  92. flwr/proto/fab_pb2.py +11 -7
  93. flwr/proto/fab_pb2.pyi +21 -1
  94. flwr/proto/fleet_pb2.py +31 -23
  95. flwr/proto/fleet_pb2.pyi +63 -23
  96. flwr/proto/fleet_pb2_grpc.py +98 -28
  97. flwr/proto/fleet_pb2_grpc.pyi +45 -13
  98. flwr/proto/node_pb2.py +3 -1
  99. flwr/proto/node_pb2.pyi +48 -0
  100. flwr/server/app.py +152 -114
  101. flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +17 -7
  102. flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +132 -38
  103. flwr/server/superlink/fleet/grpc_rere/{server_interceptor.py → node_auth_server_interceptor.py} +27 -51
  104. flwr/server/superlink/fleet/message_handler/message_handler.py +67 -22
  105. flwr/server/superlink/fleet/rest_rere/rest_api.py +52 -31
  106. flwr/server/superlink/fleet/vce/backend/backend.py +1 -1
  107. flwr/server/superlink/fleet/vce/backend/raybackend.py +1 -1
  108. flwr/server/superlink/fleet/vce/vce_api.py +18 -5
  109. flwr/server/superlink/linkstate/in_memory_linkstate.py +167 -73
  110. flwr/server/superlink/linkstate/linkstate.py +107 -24
  111. flwr/server/superlink/linkstate/linkstate_factory.py +2 -1
  112. flwr/server/superlink/linkstate/sqlite_linkstate.py +306 -255
  113. flwr/server/superlink/linkstate/utils.py +3 -54
  114. flwr/server/superlink/serverappio/serverappio_servicer.py +2 -2
  115. flwr/server/superlink/simulation/simulationio_servicer.py +1 -1
  116. flwr/server/utils/validator.py +2 -3
  117. flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +4 -2
  118. flwr/serverapp/strategy/__init__.py +26 -0
  119. flwr/serverapp/strategy/bulyan.py +238 -0
  120. flwr/serverapp/strategy/dp_adaptive_clipping.py +335 -0
  121. flwr/serverapp/strategy/dp_fixed_clipping.py +71 -49
  122. flwr/serverapp/strategy/fedadagrad.py +0 -3
  123. flwr/serverapp/strategy/fedadam.py +0 -3
  124. flwr/serverapp/strategy/fedavg.py +89 -64
  125. flwr/serverapp/strategy/fedavgm.py +198 -0
  126. flwr/serverapp/strategy/fedmedian.py +105 -0
  127. flwr/serverapp/strategy/fedprox.py +174 -0
  128. flwr/serverapp/strategy/fedtrimmedavg.py +176 -0
  129. flwr/serverapp/strategy/fedxgb_bagging.py +117 -0
  130. flwr/serverapp/strategy/fedxgb_cyclic.py +220 -0
  131. flwr/serverapp/strategy/fedyogi.py +0 -3
  132. flwr/serverapp/strategy/krum.py +112 -0
  133. flwr/serverapp/strategy/multikrum.py +247 -0
  134. flwr/serverapp/strategy/qfedavg.py +252 -0
  135. flwr/serverapp/strategy/strategy_utils.py +48 -0
  136. flwr/simulation/app.py +1 -1
  137. flwr/simulation/ray_transport/ray_actor.py +1 -1
  138. flwr/simulation/ray_transport/ray_client_proxy.py +1 -1
  139. flwr/simulation/run_simulation.py +28 -32
  140. flwr/supercore/cli/flower_superexec.py +26 -1
  141. flwr/supercore/constant.py +41 -0
  142. flwr/supercore/object_store/in_memory_object_store.py +0 -4
  143. flwr/supercore/object_store/object_store_factory.py +26 -6
  144. flwr/supercore/object_store/sqlite_object_store.py +252 -0
  145. flwr/{client/clientapp → supercore/primitives}/__init__.py +1 -1
  146. flwr/supercore/primitives/asymmetric.py +117 -0
  147. flwr/supercore/primitives/asymmetric_ed25519.py +165 -0
  148. flwr/supercore/sqlite_mixin.py +156 -0
  149. flwr/supercore/superexec/plugin/exec_plugin.py +11 -1
  150. flwr/supercore/superexec/run_superexec.py +16 -2
  151. flwr/supercore/utils.py +20 -0
  152. flwr/superlink/artifact_provider/__init__.py +22 -0
  153. flwr/superlink/artifact_provider/artifact_provider.py +37 -0
  154. flwr/{common → superlink}/auth_plugin/__init__.py +6 -6
  155. flwr/superlink/auth_plugin/auth_plugin.py +91 -0
  156. flwr/superlink/auth_plugin/noop_auth_plugin.py +87 -0
  157. flwr/superlink/servicer/control/{control_user_auth_interceptor.py → control_account_auth_interceptor.py} +19 -19
  158. flwr/superlink/servicer/control/control_event_log_interceptor.py +1 -1
  159. flwr/superlink/servicer/control/control_grpc.py +16 -11
  160. flwr/superlink/servicer/control/control_servicer.py +207 -58
  161. flwr/supernode/cli/flower_supernode.py +19 -26
  162. flwr/supernode/runtime/run_clientapp.py +2 -2
  163. flwr/supernode/servicer/clientappio/clientappio_servicer.py +1 -1
  164. flwr/supernode/start_client_internal.py +17 -9
  165. {flwr-1.21.0.dist-info → flwr-1.23.0.dist-info}/METADATA +6 -16
  166. {flwr-1.21.0.dist-info → flwr-1.23.0.dist-info}/RECORD +170 -140
  167. flwr/cli/new/templates/app/code/client.pytorch_msg_api.py.tpl +0 -80
  168. flwr/cli/new/templates/app/code/server.pytorch_msg_api.py.tpl +0 -41
  169. flwr/common/auth_plugin/auth_plugin.py +0 -149
  170. flwr/serverapp/dp_fixed_clipping.py +0 -352
  171. flwr/serverapp/strategy/strategy_utils_tests.py +0 -304
  172. /flwr/cli/new/templates/app/code/{__init__.pytorch_msg_api.py.tpl → __init__.pytorch_legacy_api.py.tpl} +0 -0
  173. /flwr/{client → clientapp}/client_app.py +0 -0
  174. {flwr-1.21.0.dist-info → flwr-1.23.0.dist-info}/WHEEL +0 -0
  175. {flwr-1.21.0.dist-info → flwr-1.23.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,117 @@
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
+ """Asymmetric cryptography utilities."""
16
+
17
+
18
+ from typing import cast
19
+
20
+ from cryptography.exceptions import InvalidSignature
21
+ from cryptography.hazmat.primitives import hashes, serialization
22
+ from cryptography.hazmat.primitives.asymmetric import ec
23
+
24
+
25
+ def generate_key_pairs() -> (
26
+ tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]
27
+ ):
28
+ """Generate private and public key pairs with Cryptography."""
29
+ private_key = ec.generate_private_key(ec.SECP384R1())
30
+ public_key = private_key.public_key()
31
+ return private_key, public_key
32
+
33
+
34
+ def private_key_to_bytes(private_key: ec.EllipticCurvePrivateKey) -> bytes:
35
+ """Serialize private key to bytes."""
36
+ return private_key.private_bytes(
37
+ encoding=serialization.Encoding.PEM,
38
+ format=serialization.PrivateFormat.PKCS8,
39
+ encryption_algorithm=serialization.NoEncryption(),
40
+ )
41
+
42
+
43
+ def bytes_to_private_key(private_key_bytes: bytes) -> ec.EllipticCurvePrivateKey:
44
+ """Deserialize private key from bytes."""
45
+ return cast(
46
+ ec.EllipticCurvePrivateKey,
47
+ serialization.load_pem_private_key(data=private_key_bytes, password=None),
48
+ )
49
+
50
+
51
+ def public_key_to_bytes(public_key: ec.EllipticCurvePublicKey) -> bytes:
52
+ """Serialize public key to bytes."""
53
+ return public_key.public_bytes(
54
+ encoding=serialization.Encoding.PEM,
55
+ format=serialization.PublicFormat.SubjectPublicKeyInfo,
56
+ )
57
+
58
+
59
+ def bytes_to_public_key(public_key_bytes: bytes) -> ec.EllipticCurvePublicKey:
60
+ """Deserialize public key from bytes."""
61
+ return cast(
62
+ ec.EllipticCurvePublicKey,
63
+ serialization.load_pem_public_key(data=public_key_bytes),
64
+ )
65
+
66
+
67
+ def sign_message(private_key: ec.EllipticCurvePrivateKey, message: bytes) -> bytes:
68
+ """Sign a message using the provided EC private key.
69
+
70
+ Parameters
71
+ ----------
72
+ private_key : ec.EllipticCurvePrivateKey
73
+ The EC private key to sign the message with.
74
+ message : bytes
75
+ The message to be signed.
76
+
77
+ Returns
78
+ -------
79
+ bytes
80
+ The signature of the message.
81
+ """
82
+ signature = private_key.sign(message, ec.ECDSA(hashes.SHA256()))
83
+ return signature
84
+
85
+
86
+ def verify_signature(
87
+ public_key: ec.EllipticCurvePublicKey, message: bytes, signature: bytes
88
+ ) -> bool:
89
+ """Verify a signature against a message using the provided EC public key.
90
+
91
+ Parameters
92
+ ----------
93
+ public_key : ec.EllipticCurvePublicKey
94
+ The EC public key to verify the signature.
95
+ message : bytes
96
+ The original message.
97
+ signature : bytes
98
+ The signature to verify.
99
+
100
+ Returns
101
+ -------
102
+ bool
103
+ True if the signature is valid, False otherwise.
104
+ """
105
+ try:
106
+ public_key.verify(signature, message, ec.ECDSA(hashes.SHA256()))
107
+ return True
108
+ except InvalidSignature:
109
+ return False
110
+
111
+
112
+ def uses_nist_ec_curve(public_key: ec.EllipticCurvePublicKey) -> bool:
113
+ """Return True if the provided key uses a NIST EC curve."""
114
+ return isinstance(
115
+ public_key.curve,
116
+ (ec.SECP192R1, ec.SECP224R1, ec.SECP256R1, ec.SECP384R1, ec.SECP521R1),
117
+ )
@@ -0,0 +1,165 @@
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
+ """Ed25519-only asymmetric cryptography utilities."""
16
+
17
+ import base64
18
+
19
+ from cryptography.exceptions import InvalidSignature
20
+ from cryptography.hazmat.primitives import serialization
21
+ from cryptography.hazmat.primitives.asymmetric import ed25519
22
+
23
+
24
+ def generate_key_pair() -> tuple[ed25519.Ed25519PrivateKey, ed25519.Ed25519PublicKey]:
25
+ """Generate an Ed25519 private/public key pair.
26
+
27
+ Returns
28
+ -------
29
+ Tuple[Ed25519PrivateKey, Ed25519PublicKey]
30
+ Private and public key pair.
31
+ """
32
+ private_key = ed25519.Ed25519PrivateKey.generate()
33
+ return private_key, private_key.public_key()
34
+
35
+
36
+ def private_key_to_bytes(private_key: ed25519.Ed25519PrivateKey) -> bytes:
37
+ """Serialize an Ed25519 private key to PEM bytes.
38
+
39
+ Parameters
40
+ ----------
41
+ private_key : Ed25519PrivateKey
42
+ The private key to serialize.
43
+
44
+ Returns
45
+ -------
46
+ bytes
47
+ PEM-encoded private key.
48
+ """
49
+ return private_key.private_bytes(
50
+ encoding=serialization.Encoding.PEM,
51
+ format=serialization.PrivateFormat.PKCS8,
52
+ encryption_algorithm=serialization.NoEncryption(),
53
+ )
54
+
55
+
56
+ def bytes_to_private_key(private_key_bytes: bytes) -> ed25519.Ed25519PrivateKey:
57
+ """Deserialize an Ed25519 private key from PEM bytes.
58
+
59
+ Parameters
60
+ ----------
61
+ private_key_bytes : bytes
62
+ PEM-encoded private key.
63
+
64
+ Returns
65
+ -------
66
+ Ed25519PrivateKey
67
+ Deserialized private key.
68
+ """
69
+ return serialization.load_pem_private_key(
70
+ private_key_bytes, password=None
71
+ ) # type: ignore[return-value]
72
+
73
+
74
+ def public_key_to_bytes(public_key: ed25519.Ed25519PublicKey) -> bytes:
75
+ """Serialize an Ed25519 public key to PEM bytes.
76
+
77
+ Parameters
78
+ ----------
79
+ public_key : Ed25519PublicKey
80
+ The public key to serialize.
81
+
82
+ Returns
83
+ -------
84
+ bytes
85
+ PEM-encoded public key.
86
+ """
87
+ return public_key.public_bytes(
88
+ encoding=serialization.Encoding.PEM,
89
+ format=serialization.PublicFormat.SubjectPublicKeyInfo,
90
+ )
91
+
92
+
93
+ def bytes_to_public_key(public_key_bytes: bytes) -> ed25519.Ed25519PublicKey:
94
+ """Deserialize an Ed25519 public key from PEM bytes.
95
+
96
+ Parameters
97
+ ----------
98
+ public_key_bytes : bytes
99
+ PEM-encoded public key.
100
+
101
+ Returns
102
+ -------
103
+ Ed25519PublicKey
104
+ Deserialized public key.
105
+ """
106
+ return serialization.load_pem_public_key(public_key_bytes) # type: ignore[return-value]
107
+
108
+
109
+ def sign_message(private_key: ed25519.Ed25519PrivateKey, message: bytes) -> bytes:
110
+ """Sign a message using an Ed25519 private key.
111
+
112
+ Parameters
113
+ ----------
114
+ private_key : Ed25519PrivateKey
115
+ The private key used for signing.
116
+ message : bytes
117
+ The message to sign.
118
+
119
+ Returns
120
+ -------
121
+ bytes
122
+ The signature of the message.
123
+ """
124
+ return private_key.sign(message)
125
+
126
+
127
+ def verify_signature(
128
+ public_key: ed25519.Ed25519PublicKey, message: bytes, signature: bytes
129
+ ) -> bool:
130
+ """Verify a signature using an Ed25519 public key.
131
+
132
+ Parameters
133
+ ----------
134
+ public_key : Ed25519PublicKey
135
+ The public key used for verification.
136
+ message : bytes
137
+ The original message.
138
+ signature : bytes
139
+ The signature to verify.
140
+
141
+ Returns
142
+ -------
143
+ bool
144
+ True if the signature is valid, False otherwise.
145
+ """
146
+ try:
147
+ public_key.verify(signature, message)
148
+ return True
149
+ except InvalidSignature:
150
+ return False
151
+
152
+
153
+ def create_signed_message(fab_digest: bytes, timestamp: int) -> bytes:
154
+ """Create a canonical message:
155
+ timestamp (8 bytes big-endian) + fab_digest.
156
+ """
157
+ timestamp_bytes = timestamp.to_bytes(8, byteorder="big")
158
+ return timestamp_bytes + fab_digest
159
+
160
+
161
+ def decode_base64url(sig: str) -> bytes:
162
+ """Convert signature to b64 format."""
163
+ # add missing padding (=) to a multiple of 4
164
+ pad = (-len(sig)) % 4
165
+ return base64.urlsafe_b64decode(sig + ("=" * pad))
@@ -0,0 +1,156 @@
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
+ """Mixin providing common SQLite connection and initialization logic."""
16
+
17
+
18
+ import re
19
+ import sqlite3
20
+ from abc import ABC, abstractmethod
21
+ from collections.abc import Sequence
22
+ from logging import DEBUG, ERROR
23
+ from typing import Any, Optional, Union
24
+
25
+ from flwr.common.logger import log
26
+
27
+ DictOrTuple = Union[tuple[Any, ...], dict[str, Any]]
28
+
29
+
30
+ class SqliteMixin(ABC):
31
+ """Mixin providing common SQLite connection and initialization logic."""
32
+
33
+ def __init__(self, database_path: str) -> None:
34
+ self.database_path = database_path
35
+ self._conn: Optional[sqlite3.Connection] = None
36
+
37
+ @property
38
+ def conn(self) -> sqlite3.Connection:
39
+ """Get the SQLite connection."""
40
+ if self._conn is None:
41
+ raise AttributeError("Database not initialized. Call initialize() first.")
42
+ return self._conn
43
+
44
+ @abstractmethod
45
+ def initialize(self, log_queries: bool = False) -> list[tuple[str]]:
46
+ """Connect to the DB, enable FK support, and create tables if needed.
47
+
48
+ Parameters
49
+ ----------
50
+ log_queries : bool
51
+ Log each query which is executed.
52
+
53
+ Returns
54
+ -------
55
+ list[tuple[str]]
56
+ The list of all tables in the DB.
57
+
58
+ Examples
59
+ --------
60
+ Implement in subclass:
61
+
62
+ .. code:: python
63
+
64
+ def initialize(self, log_queries: bool = False) -> list[tuple[str]]:
65
+ return self._ensure_initialized(
66
+ SQL_CREATE_TABLE_FOO,
67
+ SQL_CREATE_TABLE_BAR,
68
+ log_queries=log_queries
69
+ )
70
+ """
71
+
72
+ def _ensure_initialized(
73
+ self,
74
+ *create_statements: str,
75
+ log_queries: bool = False,
76
+ ) -> list[tuple[str]]:
77
+ """Connect to the DB, enable FK support, and create tables if needed.
78
+
79
+ Subclasses should call this with their own CREATE TABLE/INDEX statements in
80
+ their `.initialize()` methods.
81
+
82
+ Parameters
83
+ ----------
84
+ create_statements : str
85
+ SQL statements to create tables and indexes.
86
+ log_queries : bool
87
+ Log each query which is executed.
88
+
89
+ Returns
90
+ -------
91
+ list[tuple[str]]
92
+ The list of all tables in the DB.
93
+ """
94
+ self._conn = sqlite3.connect(self.database_path)
95
+ # Enable Write-Ahead Logging (WAL) for better concurrency
96
+ self._conn.execute("PRAGMA journal_mode = WAL;")
97
+ self._conn.execute("PRAGMA synchronous = NORMAL;")
98
+ self._conn.execute("PRAGMA foreign_keys = ON;")
99
+ self._conn.row_factory = dict_factory
100
+
101
+ if log_queries:
102
+ self._conn.set_trace_callback(lambda q: log(DEBUG, q))
103
+
104
+ # Create tables and indexes
105
+ cur = self._conn.cursor()
106
+ for sql in create_statements:
107
+ cur.execute(sql)
108
+ res = cur.execute("SELECT name FROM sqlite_schema;")
109
+ return res.fetchall()
110
+
111
+ def query(
112
+ self,
113
+ query: str,
114
+ data: Optional[Union[Sequence[DictOrTuple], DictOrTuple]] = None,
115
+ ) -> list[dict[str, Any]]:
116
+ """Execute a SQL query and return the results as list of dicts."""
117
+ if self._conn is None:
118
+ raise AttributeError("LinkState is not initialized.")
119
+
120
+ if data is None:
121
+ data = []
122
+
123
+ # Clean up whitespace to make the logs nicer
124
+ query = re.sub(r"\s+", " ", query)
125
+
126
+ try:
127
+ with self._conn:
128
+ if (
129
+ len(data) > 0
130
+ and isinstance(data, (tuple, list))
131
+ and isinstance(data[0], (tuple, dict))
132
+ ):
133
+ rows = self._conn.executemany(query, data)
134
+ else:
135
+ rows = self._conn.execute(query, data)
136
+
137
+ # Extract results before committing to support
138
+ # INSERT/UPDATE ... RETURNING
139
+ # style queries
140
+ result = rows.fetchall()
141
+ except KeyError as exc:
142
+ log(ERROR, {"query": query, "data": data, "exception": exc})
143
+
144
+ return result
145
+
146
+
147
+ def dict_factory(
148
+ cursor: sqlite3.Cursor,
149
+ row: sqlite3.Row,
150
+ ) -> dict[str, Any]:
151
+ """Turn SQLite results into dicts.
152
+
153
+ Less efficent for retrival of large amounts of data but easier to use.
154
+ """
155
+ fields = [column[0] for column in cursor.description]
156
+ return dict(zip(fields, row))
@@ -17,7 +17,7 @@
17
17
 
18
18
  from abc import ABC, abstractmethod
19
19
  from collections.abc import Sequence
20
- from typing import Callable, Optional
20
+ from typing import Any, Callable, Optional
21
21
 
22
22
  from flwr.common.typing import Run
23
23
 
@@ -69,3 +69,13 @@ class ExecPlugin(ABC):
69
69
  The ID of the run associated with the token, used for tracking or
70
70
  logging purposes.
71
71
  """
72
+
73
+ # This method is optional to implement
74
+ def load_config(self, yaml_config: dict[str, Any]) -> None:
75
+ """Load configuration from a YAML dictionary.
76
+
77
+ Parameters
78
+ ----------
79
+ yaml_config : dict[str, Any]
80
+ A dictionary representing the YAML configuration.
81
+ """
@@ -17,10 +17,10 @@
17
17
 
18
18
  import time
19
19
  from logging import WARN
20
- from typing import Optional, Union
20
+ from typing import Any, Optional, Union
21
21
 
22
22
  from flwr.common.config import get_flwr_dir
23
- from flwr.common.exit import register_signal_handlers
23
+ from flwr.common.exit import ExitCode, flwr_exit, register_signal_handlers
24
24
  from flwr.common.grpc import create_channel, on_channel_state_change
25
25
  from flwr.common.logger import log
26
26
  from flwr.common.retry_invoker import _make_simple_grpc_retry_invoker, _wrap_stub
@@ -47,6 +47,7 @@ def run_superexec( # pylint: disable=R0913,R0914,R0917
47
47
  type[ClientAppIoStub], type[ServerAppIoStub], type[SimulationIoStub]
48
48
  ],
49
49
  appio_api_address: str,
50
+ plugin_config: Optional[dict[str, Any]] = None,
50
51
  flwr_dir: Optional[str] = None,
51
52
  parent_pid: Optional[int] = None,
52
53
  health_server_address: Optional[str] = None,
@@ -61,6 +62,9 @@ def run_superexec( # pylint: disable=R0913,R0914,R0917
61
62
  The gRPC stub class for the AppIO API.
62
63
  appio_api_address : str
63
64
  The address of the AppIO API.
65
+ plugin_config : Optional[dict[str, Any]] (default: None)
66
+ The configuration dictionary for the plugin. If `None`, the plugin will use
67
+ its default configuration.
64
68
  flwr_dir : Optional[str] (default: None)
65
69
  The Flower directory.
66
70
  parent_pid : Optional[int] (default: None)
@@ -113,6 +117,16 @@ def run_superexec( # pylint: disable=R0913,R0914,R0917
113
117
  get_run=get_run,
114
118
  )
115
119
 
120
+ # Load plugin configuration from file if provided
121
+ try:
122
+ if plugin_config is not None:
123
+ plugin.load_config(plugin_config)
124
+ except (KeyError, ValueError) as e:
125
+ flwr_exit(
126
+ code=ExitCode.SUPEREXEC_INVALID_PLUGIN_CONFIG,
127
+ message=f"Invalid plugin config: {e!r}",
128
+ )
129
+
116
130
  # Start the main loop
117
131
  try:
118
132
  while True:
flwr/supercore/utils.py CHANGED
@@ -30,3 +30,23 @@ def mask_string(value: str, head: int = 4, tail: int = 4) -> str:
30
30
  if len(value) <= head + tail:
31
31
  return value
32
32
  return f"{value[:head]}...{value[-tail:]}"
33
+
34
+
35
+ def uint64_to_int64(unsigned: int) -> int:
36
+ """Convert a uint64 integer to a sint64 with the same bit pattern.
37
+
38
+ For values >= 2^63, wraps around by subtracting 2^64.
39
+ """
40
+ if unsigned >= (1 << 63):
41
+ return unsigned - (1 << 64)
42
+ return unsigned
43
+
44
+
45
+ def int64_to_uint64(signed: int) -> int:
46
+ """Convert a sint64 integer to a uint64 with the same bit pattern.
47
+
48
+ For negative values, wraps around by adding 2^64.
49
+ """
50
+ if signed < 0:
51
+ return signed + (1 << 64)
52
+ return signed
@@ -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
+ """ArtifactProvider for SuperLink."""
16
+
17
+
18
+ from .artifact_provider import ArtifactProvider
19
+
20
+ __all__ = [
21
+ "ArtifactProvider",
22
+ ]
@@ -0,0 +1,37 @@
1
+ # Copyright 2025 Flower Labs GmbH. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+ """Abstract base class for ArtifactProvider."""
16
+
17
+
18
+ from abc import ABC, abstractmethod
19
+ from typing import Optional
20
+
21
+
22
+ class ArtifactProvider(ABC):
23
+ """ArtifactProvider interface for providing artifact download links."""
24
+
25
+ @abstractmethod
26
+ def get_url(self, run_id: int) -> Optional[str]:
27
+ """Return the artifact download link for the given run ID."""
28
+
29
+ @property
30
+ @abstractmethod
31
+ def output_dir(self) -> str:
32
+ """Permanent storage directory."""
33
+
34
+ @property
35
+ @abstractmethod
36
+ def tmp_dir(self) -> str:
37
+ """Temporary storage directory."""
@@ -12,15 +12,15 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
  # ==============================================================================
15
- """Auth plugin components."""
15
+ """Account auth plugin for ControlServicer."""
16
16
 
17
17
 
18
- from .auth_plugin import CliAuthPlugin as CliAuthPlugin
19
- from .auth_plugin import ControlAuthPlugin as ControlAuthPlugin
20
- from .auth_plugin import ControlAuthzPlugin as ControlAuthzPlugin
18
+ from .auth_plugin import ControlAuthnPlugin, ControlAuthzPlugin
19
+ from .noop_auth_plugin import NoOpControlAuthnPlugin, NoOpControlAuthzPlugin
21
20
 
22
21
  __all__ = [
23
- "CliAuthPlugin",
24
- "ControlAuthPlugin",
22
+ "ControlAuthnPlugin",
25
23
  "ControlAuthzPlugin",
24
+ "NoOpControlAuthnPlugin",
25
+ "NoOpControlAuthzPlugin",
26
26
  ]