flwr 1.22.0__py3-none-any.whl → 1.24.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.
- flwr/__init__.py +16 -5
- flwr/app/error.py +2 -2
- flwr/app/exception.py +3 -3
- flwr/cli/app.py +34 -1
- flwr/cli/app_cmd/__init__.py +23 -0
- flwr/cli/app_cmd/publish.py +285 -0
- flwr/cli/app_cmd/review.py +252 -0
- flwr/cli/auth_plugin/__init__.py +15 -6
- flwr/cli/auth_plugin/auth_plugin.py +94 -0
- flwr/cli/auth_plugin/noop_auth_plugin.py +101 -0
- flwr/cli/auth_plugin/oidc_cli_plugin.py +46 -32
- flwr/cli/build.py +166 -53
- flwr/cli/{cli_user_auth_interceptor.py → cli_account_auth_interceptor.py} +29 -11
- flwr/cli/config_utils.py +101 -13
- flwr/cli/federation/__init__.py +24 -0
- flwr/cli/federation/ls.py +140 -0
- flwr/cli/federation/show.py +317 -0
- flwr/cli/install.py +91 -13
- flwr/cli/log.py +54 -11
- flwr/cli/login/login.py +41 -27
- flwr/cli/ls.py +177 -133
- flwr/cli/new/new.py +175 -40
- flwr/cli/new/templates/app/code/task.pytorch.py.tpl +1 -0
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +3 -3
- flwr/cli/new/templates/app/pyproject.pytorch_legacy_api.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +2 -2
- flwr/cli/new/templates/app/pyproject.xgboost.toml.tpl +1 -1
- flwr/cli/pull.py +12 -7
- flwr/cli/run/run.py +82 -31
- flwr/cli/run_utils.py +130 -0
- flwr/cli/stop.py +27 -9
- flwr/cli/supernode/__init__.py +25 -0
- flwr/cli/supernode/ls.py +268 -0
- flwr/cli/supernode/register.py +190 -0
- flwr/cli/supernode/unregister.py +140 -0
- flwr/cli/utils.py +464 -81
- flwr/client/__init__.py +2 -1
- flwr/client/dpfedavg_numpy_client.py +4 -1
- flwr/client/grpc_adapter_client/connection.py +12 -15
- flwr/client/grpc_rere_client/connection.py +68 -41
- flwr/client/grpc_rere_client/grpc_adapter.py +34 -14
- flwr/client/grpc_rere_client/{client_interceptor.py → node_auth_client_interceptor.py} +5 -7
- flwr/client/message_handler/message_handler.py +2 -2
- flwr/client/mod/secure_aggregation/secaggplus_mod.py +10 -8
- flwr/client/numpy_client.py +1 -1
- flwr/client/rest_client/connection.py +94 -51
- flwr/client/run_info_store.py +4 -5
- flwr/client/typing.py +1 -1
- flwr/clientapp/__init__.py +1 -2
- flwr/{client → clientapp}/client_app.py +9 -10
- flwr/clientapp/mod/centraldp_mods.py +16 -17
- flwr/clientapp/mod/localdp_mod.py +8 -9
- flwr/clientapp/typing.py +1 -1
- flwr/{client/clientapp → clientapp}/utils.py +4 -4
- flwr/common/address.py +1 -2
- flwr/common/args.py +3 -4
- flwr/common/config.py +13 -16
- flwr/common/constant.py +56 -13
- flwr/common/differential_privacy.py +3 -4
- flwr/common/event_log_plugin/event_log_plugin.py +3 -4
- flwr/common/exit/exit.py +15 -2
- flwr/common/exit/exit_code.py +39 -10
- flwr/common/exit/exit_handler.py +6 -2
- flwr/common/exit/signal_handler.py +5 -5
- flwr/common/grpc.py +6 -6
- flwr/common/inflatable_protobuf_utils.py +1 -1
- flwr/common/inflatable_utils.py +48 -31
- flwr/common/logger.py +19 -19
- flwr/common/message.py +4 -4
- flwr/common/object_ref.py +7 -7
- flwr/common/record/array.py +6 -6
- flwr/common/record/arrayrecord.py +18 -21
- flwr/common/record/configrecord.py +3 -3
- flwr/common/record/recorddict.py +5 -5
- flwr/common/record/typeddict.py +9 -2
- flwr/common/recorddict_compat.py +7 -10
- flwr/common/retry_invoker.py +20 -20
- flwr/common/secure_aggregation/crypto/symmetric_encryption.py +1 -89
- flwr/common/secure_aggregation/ndarrays_arithmetic.py +3 -3
- flwr/common/serde.py +9 -6
- flwr/common/serde_utils.py +2 -2
- flwr/common/telemetry.py +9 -5
- flwr/common/typing.py +59 -43
- flwr/compat/client/app.py +39 -38
- flwr/compat/client/grpc_client/connection.py +13 -13
- flwr/compat/server/app.py +5 -6
- flwr/proto/appio_pb2.py +13 -3
- flwr/proto/appio_pb2.pyi +134 -65
- flwr/proto/appio_pb2_grpc.py +20 -0
- flwr/proto/appio_pb2_grpc.pyi +27 -0
- flwr/proto/clientappio_pb2.py +17 -7
- flwr/proto/clientappio_pb2.pyi +15 -0
- flwr/proto/clientappio_pb2_grpc.py +206 -40
- flwr/proto/clientappio_pb2_grpc.pyi +168 -53
- flwr/proto/control_pb2.py +72 -40
- flwr/proto/control_pb2.pyi +319 -87
- flwr/proto/control_pb2_grpc.py +339 -28
- flwr/proto/control_pb2_grpc.pyi +209 -37
- flwr/proto/error_pb2.py +13 -3
- flwr/proto/error_pb2.pyi +24 -6
- flwr/proto/error_pb2_grpc.py +20 -0
- flwr/proto/error_pb2_grpc.pyi +27 -0
- flwr/proto/fab_pb2.py +24 -10
- flwr/proto/fab_pb2.pyi +68 -20
- flwr/proto/fab_pb2_grpc.py +20 -0
- flwr/proto/fab_pb2_grpc.pyi +27 -0
- flwr/proto/federation_pb2.py +38 -0
- flwr/proto/federation_pb2.pyi +56 -0
- flwr/proto/federation_pb2_grpc.py +24 -0
- flwr/proto/federation_pb2_grpc.pyi +31 -0
- flwr/proto/fleet_pb2.py +45 -27
- flwr/proto/fleet_pb2.pyi +186 -70
- flwr/proto/fleet_pb2_grpc.py +277 -66
- flwr/proto/fleet_pb2_grpc.pyi +201 -55
- flwr/proto/grpcadapter_pb2.py +14 -4
- flwr/proto/grpcadapter_pb2.pyi +38 -16
- flwr/proto/grpcadapter_pb2_grpc.py +35 -4
- flwr/proto/grpcadapter_pb2_grpc.pyi +38 -7
- flwr/proto/heartbeat_pb2.py +17 -7
- flwr/proto/heartbeat_pb2.pyi +51 -22
- flwr/proto/heartbeat_pb2_grpc.py +20 -0
- flwr/proto/heartbeat_pb2_grpc.pyi +27 -0
- flwr/proto/log_pb2.py +13 -3
- flwr/proto/log_pb2.pyi +34 -11
- flwr/proto/log_pb2_grpc.py +20 -0
- flwr/proto/log_pb2_grpc.pyi +27 -0
- flwr/proto/message_pb2.py +15 -5
- flwr/proto/message_pb2.pyi +154 -86
- flwr/proto/message_pb2_grpc.py +20 -0
- flwr/proto/message_pb2_grpc.pyi +27 -0
- flwr/proto/node_pb2.py +16 -4
- flwr/proto/node_pb2.pyi +77 -4
- flwr/proto/node_pb2_grpc.py +20 -0
- flwr/proto/node_pb2_grpc.pyi +27 -0
- flwr/proto/recorddict_pb2.py +13 -3
- flwr/proto/recorddict_pb2.pyi +184 -107
- flwr/proto/recorddict_pb2_grpc.py +20 -0
- flwr/proto/recorddict_pb2_grpc.pyi +27 -0
- flwr/proto/run_pb2.py +40 -31
- flwr/proto/run_pb2.pyi +149 -84
- flwr/proto/run_pb2_grpc.py +20 -0
- flwr/proto/run_pb2_grpc.pyi +27 -0
- flwr/proto/serverappio_pb2.py +13 -3
- flwr/proto/serverappio_pb2.pyi +32 -8
- flwr/proto/serverappio_pb2_grpc.py +246 -65
- flwr/proto/serverappio_pb2_grpc.pyi +221 -85
- flwr/proto/simulationio_pb2.py +16 -8
- flwr/proto/simulationio_pb2.pyi +15 -0
- flwr/proto/simulationio_pb2_grpc.py +162 -41
- flwr/proto/simulationio_pb2_grpc.pyi +149 -55
- flwr/proto/transport_pb2.py +20 -10
- flwr/proto/transport_pb2.pyi +249 -160
- flwr/proto/transport_pb2_grpc.py +35 -4
- flwr/proto/transport_pb2_grpc.pyi +38 -8
- flwr/server/app.py +173 -127
- flwr/server/client_manager.py +4 -5
- flwr/server/client_proxy.py +10 -11
- flwr/server/compat/app.py +4 -5
- flwr/server/compat/app_utils.py +2 -1
- flwr/server/compat/grid_client_proxy.py +10 -12
- flwr/server/compat/legacy_context.py +3 -4
- flwr/server/fleet_event_log_interceptor.py +2 -1
- flwr/server/grid/grid.py +2 -3
- flwr/server/grid/grpc_grid.py +10 -8
- flwr/server/grid/inmemory_grid.py +4 -4
- flwr/server/run_serverapp.py +2 -3
- flwr/server/server.py +34 -39
- flwr/server/server_app.py +7 -8
- flwr/server/server_config.py +1 -2
- flwr/server/serverapp/app.py +34 -28
- flwr/server/serverapp_components.py +4 -5
- flwr/server/strategy/aggregate.py +9 -8
- flwr/server/strategy/bulyan.py +13 -11
- flwr/server/strategy/dp_adaptive_clipping.py +16 -20
- flwr/server/strategy/dp_fixed_clipping.py +12 -17
- flwr/server/strategy/dpfedavg_adaptive.py +3 -4
- flwr/server/strategy/dpfedavg_fixed.py +6 -10
- flwr/server/strategy/fault_tolerant_fedavg.py +14 -13
- flwr/server/strategy/fedadagrad.py +18 -14
- flwr/server/strategy/fedadam.py +16 -14
- flwr/server/strategy/fedavg.py +16 -17
- flwr/server/strategy/fedavg_android.py +15 -15
- flwr/server/strategy/fedavgm.py +21 -18
- flwr/server/strategy/fedmedian.py +2 -3
- flwr/server/strategy/fedopt.py +11 -10
- flwr/server/strategy/fedprox.py +10 -9
- flwr/server/strategy/fedtrimmedavg.py +12 -11
- flwr/server/strategy/fedxgb_bagging.py +13 -11
- flwr/server/strategy/fedxgb_cyclic.py +6 -6
- flwr/server/strategy/fedxgb_nn_avg.py +4 -4
- flwr/server/strategy/fedyogi.py +16 -14
- flwr/server/strategy/krum.py +12 -11
- flwr/server/strategy/qfedavg.py +16 -15
- flwr/server/strategy/strategy.py +6 -9
- flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +19 -8
- flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -2
- flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py +3 -4
- flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py +10 -12
- flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +1 -3
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +136 -42
- flwr/server/superlink/fleet/grpc_rere/{server_interceptor.py → node_auth_server_interceptor.py} +28 -51
- flwr/server/superlink/fleet/message_handler/message_handler.py +100 -49
- flwr/server/superlink/fleet/rest_rere/rest_api.py +54 -33
- flwr/server/superlink/fleet/vce/backend/backend.py +2 -2
- flwr/server/superlink/fleet/vce/backend/raybackend.py +6 -6
- flwr/server/superlink/fleet/vce/vce_api.py +32 -13
- flwr/server/superlink/linkstate/in_memory_linkstate.py +266 -207
- flwr/server/superlink/linkstate/linkstate.py +161 -62
- flwr/server/superlink/linkstate/linkstate_factory.py +24 -6
- flwr/server/superlink/linkstate/sqlite_linkstate.py +698 -638
- flwr/server/superlink/linkstate/utils.py +9 -60
- flwr/server/superlink/serverappio/serverappio_grpc.py +1 -2
- flwr/server/superlink/serverappio/serverappio_servicer.py +28 -23
- flwr/server/superlink/simulation/simulationio_grpc.py +1 -2
- flwr/server/superlink/simulation/simulationio_servicer.py +19 -14
- flwr/server/superlink/utils.py +4 -6
- flwr/server/typing.py +1 -1
- flwr/server/utils/tensorboard.py +15 -8
- flwr/server/utils/validator.py +2 -3
- flwr/server/workflow/default_workflows.py +5 -5
- flwr/server/workflow/secure_aggregation/secagg_workflow.py +2 -4
- flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +12 -10
- flwr/serverapp/strategy/bulyan.py +16 -15
- flwr/serverapp/strategy/dp_adaptive_clipping.py +12 -11
- flwr/serverapp/strategy/dp_fixed_clipping.py +11 -14
- flwr/serverapp/strategy/fedadagrad.py +10 -11
- flwr/serverapp/strategy/fedadam.py +10 -11
- flwr/serverapp/strategy/fedavg.py +9 -10
- flwr/serverapp/strategy/fedavgm.py +17 -16
- flwr/serverapp/strategy/fedmedian.py +2 -2
- flwr/serverapp/strategy/fedopt.py +10 -11
- flwr/serverapp/strategy/fedprox.py +7 -8
- flwr/serverapp/strategy/fedtrimmedavg.py +9 -9
- flwr/serverapp/strategy/fedxgb_bagging.py +3 -3
- flwr/serverapp/strategy/fedxgb_cyclic.py +9 -9
- flwr/serverapp/strategy/fedyogi.py +9 -11
- flwr/serverapp/strategy/krum.py +7 -7
- flwr/serverapp/strategy/multikrum.py +9 -9
- flwr/serverapp/strategy/qfedavg.py +17 -16
- flwr/serverapp/strategy/strategy.py +6 -9
- flwr/serverapp/strategy/strategy_utils.py +7 -8
- flwr/simulation/app.py +46 -42
- flwr/simulation/legacy_app.py +12 -12
- flwr/simulation/ray_transport/ray_actor.py +11 -12
- flwr/simulation/ray_transport/ray_client_proxy.py +12 -13
- flwr/simulation/run_simulation.py +44 -43
- flwr/simulation/simulationio_connection.py +4 -4
- flwr/supercore/cli/flower_superexec.py +3 -4
- flwr/supercore/constant.py +52 -0
- flwr/supercore/corestate/corestate.py +24 -3
- flwr/supercore/corestate/in_memory_corestate.py +138 -0
- flwr/supercore/corestate/sqlite_corestate.py +157 -0
- flwr/supercore/ffs/disk_ffs.py +1 -2
- flwr/supercore/ffs/ffs.py +1 -2
- flwr/supercore/ffs/ffs_factory.py +1 -2
- flwr/{common → supercore}/heartbeat.py +20 -25
- flwr/supercore/object_store/in_memory_object_store.py +1 -6
- flwr/supercore/object_store/object_store.py +1 -2
- flwr/supercore/object_store/object_store_factory.py +27 -8
- flwr/supercore/object_store/sqlite_object_store.py +253 -0
- flwr/{client/clientapp → supercore/primitives}/__init__.py +1 -1
- flwr/supercore/primitives/asymmetric.py +117 -0
- flwr/supercore/primitives/asymmetric_ed25519.py +175 -0
- flwr/supercore/sqlite_mixin.py +159 -0
- flwr/supercore/superexec/plugin/base_exec_plugin.py +1 -2
- flwr/supercore/superexec/plugin/exec_plugin.py +3 -3
- flwr/supercore/superexec/run_superexec.py +9 -13
- flwr/supercore/utils.py +20 -0
- flwr/superlink/artifact_provider/artifact_provider.py +1 -2
- flwr/{common → superlink}/auth_plugin/__init__.py +6 -6
- flwr/superlink/auth_plugin/auth_plugin.py +88 -0
- flwr/superlink/auth_plugin/noop_auth_plugin.py +84 -0
- flwr/superlink/federation/__init__.py +24 -0
- flwr/superlink/federation/federation_manager.py +64 -0
- flwr/superlink/federation/noop_federation_manager.py +71 -0
- flwr/superlink/servicer/control/{control_user_auth_interceptor.py → control_account_auth_interceptor.py} +41 -32
- flwr/superlink/servicer/control/control_event_log_interceptor.py +7 -7
- flwr/superlink/servicer/control/control_grpc.py +18 -17
- flwr/superlink/servicer/control/control_license_interceptor.py +3 -3
- flwr/superlink/servicer/control/control_servicer.py +239 -63
- flwr/supernode/cli/flower_supernode.py +74 -26
- flwr/supernode/nodestate/in_memory_nodestate.py +60 -49
- flwr/supernode/nodestate/nodestate.py +7 -8
- flwr/supernode/nodestate/nodestate_factory.py +7 -4
- flwr/supernode/runtime/run_clientapp.py +43 -24
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +40 -10
- flwr/supernode/start_client_internal.py +175 -51
- {flwr-1.22.0.dist-info → flwr-1.24.0.dist-info}/METADATA +8 -8
- flwr-1.24.0.dist-info/RECORD +454 -0
- flwr/common/auth_plugin/auth_plugin.py +0 -149
- flwr/supercore/object_store/utils.py +0 -43
- flwr-1.22.0.dist-info/RECORD +0 -428
- {flwr-1.22.0.dist-info → flwr-1.24.0.dist-info}/WHEEL +0 -0
- {flwr-1.22.0.dist-info → flwr-1.24.0.dist-info}/entry_points.txt +0 -0
|
@@ -17,12 +17,11 @@
|
|
|
17
17
|
|
|
18
18
|
import traceback
|
|
19
19
|
from logging import ERROR
|
|
20
|
-
from typing import Optional
|
|
21
20
|
|
|
22
21
|
from flwr import common
|
|
23
22
|
from flwr.client import ClientFnExt
|
|
24
|
-
from flwr.client.client_app import ClientApp
|
|
25
23
|
from flwr.client.run_info_store import DeprecatedRunInfoStore
|
|
24
|
+
from flwr.clientapp.client_app import ClientApp
|
|
26
25
|
from flwr.common import DEFAULT_TTL, Message, Metadata, RecordDict, now
|
|
27
26
|
from flwr.common.constant import (
|
|
28
27
|
NUM_PARTITIONS_KEY,
|
|
@@ -74,7 +73,7 @@ class RayActorClientProxy(ClientProxy):
|
|
|
74
73
|
},
|
|
75
74
|
)
|
|
76
75
|
|
|
77
|
-
def _submit_job(self, message: Message, timeout:
|
|
76
|
+
def _submit_job(self, message: Message, timeout: float | None) -> Message:
|
|
78
77
|
"""Sumbit a message to the ActorPool."""
|
|
79
78
|
run_id = message.metadata.run_id
|
|
80
79
|
|
|
@@ -114,8 +113,8 @@ class RayActorClientProxy(ClientProxy):
|
|
|
114
113
|
self,
|
|
115
114
|
recorddict: RecordDict,
|
|
116
115
|
message_type: str,
|
|
117
|
-
timeout:
|
|
118
|
-
group_id:
|
|
116
|
+
timeout: float | None,
|
|
117
|
+
group_id: int | None,
|
|
119
118
|
) -> Message:
|
|
120
119
|
"""Wrap a RecordDict inside a Message."""
|
|
121
120
|
return make_message(
|
|
@@ -136,8 +135,8 @@ class RayActorClientProxy(ClientProxy):
|
|
|
136
135
|
def get_properties(
|
|
137
136
|
self,
|
|
138
137
|
ins: common.GetPropertiesIns,
|
|
139
|
-
timeout:
|
|
140
|
-
group_id:
|
|
138
|
+
timeout: float | None,
|
|
139
|
+
group_id: int | None,
|
|
141
140
|
) -> common.GetPropertiesRes:
|
|
142
141
|
"""Return client's properties."""
|
|
143
142
|
recorddict = getpropertiesins_to_recorddict(ins)
|
|
@@ -155,8 +154,8 @@ class RayActorClientProxy(ClientProxy):
|
|
|
155
154
|
def get_parameters(
|
|
156
155
|
self,
|
|
157
156
|
ins: common.GetParametersIns,
|
|
158
|
-
timeout:
|
|
159
|
-
group_id:
|
|
157
|
+
timeout: float | None,
|
|
158
|
+
group_id: int | None,
|
|
160
159
|
) -> common.GetParametersRes:
|
|
161
160
|
"""Return the current local model parameters."""
|
|
162
161
|
recorddict = getparametersins_to_recorddict(ins)
|
|
@@ -172,7 +171,7 @@ class RayActorClientProxy(ClientProxy):
|
|
|
172
171
|
return recorddict_to_getparametersres(message_out.content, keep_input=False)
|
|
173
172
|
|
|
174
173
|
def fit(
|
|
175
|
-
self, ins: common.FitIns, timeout:
|
|
174
|
+
self, ins: common.FitIns, timeout: float | None, group_id: int | None
|
|
176
175
|
) -> common.FitRes:
|
|
177
176
|
"""Train model parameters on the locally held dataset."""
|
|
178
177
|
recorddict = fitins_to_recorddict(
|
|
@@ -190,7 +189,7 @@ class RayActorClientProxy(ClientProxy):
|
|
|
190
189
|
return recorddict_to_fitres(message_out.content, keep_input=False)
|
|
191
190
|
|
|
192
191
|
def evaluate(
|
|
193
|
-
self, ins: common.EvaluateIns, timeout:
|
|
192
|
+
self, ins: common.EvaluateIns, timeout: float | None, group_id: int | None
|
|
194
193
|
) -> common.EvaluateRes:
|
|
195
194
|
"""Evaluate model parameters on the locally held dataset."""
|
|
196
195
|
recorddict = evaluateins_to_recorddict(
|
|
@@ -210,8 +209,8 @@ class RayActorClientProxy(ClientProxy):
|
|
|
210
209
|
def reconnect(
|
|
211
210
|
self,
|
|
212
211
|
ins: common.ReconnectIns,
|
|
213
|
-
timeout:
|
|
214
|
-
group_id:
|
|
212
|
+
timeout: float | None,
|
|
213
|
+
group_id: int | None,
|
|
215
214
|
) -> common.DisconnectRes:
|
|
216
215
|
"""Disconnect and (optionally) reconnect later."""
|
|
217
216
|
return common.DisconnectRes(reason="") # Nothing to do here (yet)
|
|
@@ -26,11 +26,11 @@ import traceback
|
|
|
26
26
|
from logging import DEBUG, ERROR, INFO, WARNING
|
|
27
27
|
from pathlib import Path
|
|
28
28
|
from queue import Empty, Queue
|
|
29
|
-
from typing import Any,
|
|
29
|
+
from typing import Any, cast
|
|
30
30
|
|
|
31
31
|
from flwr.cli.config_utils import load_and_validate
|
|
32
32
|
from flwr.cli.utils import get_sha256_hash
|
|
33
|
-
from flwr.
|
|
33
|
+
from flwr.clientapp import ClientApp
|
|
34
34
|
from flwr.common import Context, EventType, RecordDict, event, log, now
|
|
35
35
|
from flwr.common.config import get_fused_config_from_dir, parse_config_args
|
|
36
36
|
from flwr.common.constant import RUN_ID_NUM_BYTES, Status
|
|
@@ -51,6 +51,9 @@ from flwr.server.superlink.linkstate.utils import generate_rand_int_from_bytes
|
|
|
51
51
|
from flwr.simulation.ray_transport.utils import (
|
|
52
52
|
enable_tf_gpu_growth as enable_gpu_growth,
|
|
53
53
|
)
|
|
54
|
+
from flwr.supercore.constant import FLWR_IN_MEMORY_DB_NAME, NOOP_FEDERATION
|
|
55
|
+
from flwr.supercore.object_store import ObjectStoreFactory
|
|
56
|
+
from flwr.superlink.federation import NoOpFederationManager
|
|
54
57
|
|
|
55
58
|
|
|
56
59
|
def _replace_keys(d: Any, match: str, target: str) -> Any:
|
|
@@ -98,12 +101,7 @@ def run_simulation_from_cli() -> None:
|
|
|
98
101
|
_check_ray_support(args.backend)
|
|
99
102
|
|
|
100
103
|
# Load JSON config
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if backend_config_dict:
|
|
104
|
-
# Backend config internally operates with `_` not with `-`
|
|
105
|
-
backend_config_dict = _replace_keys(backend_config_dict, match="-", target="_")
|
|
106
|
-
log(DEBUG, "backend_config_dict: %s", backend_config_dict)
|
|
104
|
+
backend_config = json.loads(args.backend_config)
|
|
107
105
|
|
|
108
106
|
run_id = (
|
|
109
107
|
generate_rand_int_from_bytes(RUN_ID_NUM_BYTES)
|
|
@@ -141,6 +139,7 @@ def run_simulation_from_cli() -> None:
|
|
|
141
139
|
|
|
142
140
|
# Create run
|
|
143
141
|
run = Run.create_empty(run_id)
|
|
142
|
+
run.federation = NOOP_FEDERATION
|
|
144
143
|
run.override_config = override_config
|
|
145
144
|
|
|
146
145
|
# Create Context
|
|
@@ -157,7 +156,7 @@ def run_simulation_from_cli() -> None:
|
|
|
157
156
|
client_app_attr=client_app_attr,
|
|
158
157
|
num_supernodes=args.num_supernodes,
|
|
159
158
|
backend_name=args.backend,
|
|
160
|
-
backend_config=
|
|
159
|
+
backend_config=backend_config,
|
|
161
160
|
app_dir=args.app,
|
|
162
161
|
run=run,
|
|
163
162
|
enable_tf_gpu_growth=args.enable_tf_gpu_growth,
|
|
@@ -175,7 +174,7 @@ def run_simulation(
|
|
|
175
174
|
client_app: ClientApp,
|
|
176
175
|
num_supernodes: int,
|
|
177
176
|
backend_name: str = "ray",
|
|
178
|
-
backend_config:
|
|
177
|
+
backend_config: BackendConfig | None = None,
|
|
179
178
|
enable_tf_gpu_growth: bool = False,
|
|
180
179
|
verbose_logging: bool = False,
|
|
181
180
|
) -> None:
|
|
@@ -248,8 +247,8 @@ def run_simulation(
|
|
|
248
247
|
|
|
249
248
|
# pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
250
249
|
def run_serverapp_th(
|
|
251
|
-
server_app_attr:
|
|
252
|
-
server_app:
|
|
250
|
+
server_app_attr: str | None,
|
|
251
|
+
server_app: ServerApp | None,
|
|
253
252
|
server_app_context: Context,
|
|
254
253
|
grid: Grid,
|
|
255
254
|
app_dir: str,
|
|
@@ -266,8 +265,8 @@ def run_serverapp_th(
|
|
|
266
265
|
exception_event: threading.Event,
|
|
267
266
|
_grid: Grid,
|
|
268
267
|
_server_app_dir: str,
|
|
269
|
-
_server_app_attr:
|
|
270
|
-
_server_app:
|
|
268
|
+
_server_app_attr: str | None,
|
|
269
|
+
_server_app: ServerApp | None,
|
|
271
270
|
_ctx_queue: "Queue[Context]",
|
|
272
271
|
) -> None:
|
|
273
272
|
"""Run SeverApp, after check if GPU memory growth has to be set.
|
|
@@ -327,16 +326,18 @@ def _main_loop(
|
|
|
327
326
|
enable_tf_gpu_growth: bool,
|
|
328
327
|
run: Run,
|
|
329
328
|
exit_event: EventType,
|
|
330
|
-
flwr_dir:
|
|
331
|
-
client_app:
|
|
332
|
-
client_app_attr:
|
|
333
|
-
server_app:
|
|
334
|
-
server_app_attr:
|
|
335
|
-
server_app_context:
|
|
329
|
+
flwr_dir: str | None = None,
|
|
330
|
+
client_app: ClientApp | None = None,
|
|
331
|
+
client_app_attr: str | None = None,
|
|
332
|
+
server_app: ServerApp | None = None,
|
|
333
|
+
server_app_attr: str | None = None,
|
|
334
|
+
server_app_context: Context | None = None,
|
|
336
335
|
) -> Context:
|
|
337
336
|
"""Start ServerApp on a separate thread, then launch Simulation Engine."""
|
|
338
337
|
# Initialize StateFactory
|
|
339
|
-
state_factory = LinkStateFactory(
|
|
338
|
+
state_factory = LinkStateFactory(
|
|
339
|
+
FLWR_IN_MEMORY_DB_NAME, NoOpFederationManager(), ObjectStoreFactory()
|
|
340
|
+
)
|
|
340
341
|
|
|
341
342
|
f_stop = threading.Event()
|
|
342
343
|
# A Threading event to indicate if an exception was raised in the ServerApp thread
|
|
@@ -427,16 +428,16 @@ def _main_loop(
|
|
|
427
428
|
def _run_simulation(
|
|
428
429
|
num_supernodes: int,
|
|
429
430
|
exit_event: EventType,
|
|
430
|
-
client_app:
|
|
431
|
-
server_app:
|
|
431
|
+
client_app: ClientApp | None = None,
|
|
432
|
+
server_app: ServerApp | None = None,
|
|
432
433
|
backend_name: str = "ray",
|
|
433
|
-
backend_config:
|
|
434
|
-
client_app_attr:
|
|
435
|
-
server_app_attr:
|
|
436
|
-
server_app_context:
|
|
434
|
+
backend_config: BackendConfig | None = None,
|
|
435
|
+
client_app_attr: str | None = None,
|
|
436
|
+
server_app_attr: str | None = None,
|
|
437
|
+
server_app_context: Context | None = None,
|
|
437
438
|
app_dir: str = "",
|
|
438
|
-
flwr_dir:
|
|
439
|
-
run:
|
|
439
|
+
flwr_dir: str | None = None,
|
|
440
|
+
run: Run | None = None,
|
|
440
441
|
enable_tf_gpu_growth: bool = False,
|
|
441
442
|
verbose_logging: bool = False,
|
|
442
443
|
is_app: bool = False,
|
|
@@ -444,29 +445,28 @@ def _run_simulation(
|
|
|
444
445
|
"""Launch the Simulation Engine."""
|
|
445
446
|
if backend_config is None:
|
|
446
447
|
backend_config = {}
|
|
448
|
+
elif backend_config:
|
|
449
|
+
# Backend config internally operates with `_` not with `-`
|
|
450
|
+
backend_config = cast(
|
|
451
|
+
BackendConfig, _replace_keys(backend_config, match="-", target="_")
|
|
452
|
+
)
|
|
453
|
+
log(DEBUG, "backend_config: %s", backend_config)
|
|
447
454
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
455
|
+
# Set default init_args if not passed
|
|
456
|
+
backend_config.setdefault("init_args", {})
|
|
451
457
|
# Set default client_resources if not passed
|
|
452
|
-
|
|
453
|
-
backend_config["client_resources"] = {"num_cpus": 2, "num_gpus": 0}
|
|
454
|
-
|
|
458
|
+
backend_config.setdefault("client_resources", {"num_cpus": 2, "num_gpus": 0})
|
|
455
459
|
# Initialization of backend config to enable GPU growth globally when set
|
|
456
|
-
|
|
457
|
-
backend_config["actor"] = {"tensorflow": 0}
|
|
460
|
+
backend_config.setdefault("actor", {"tensorflow": 0})
|
|
458
461
|
|
|
459
462
|
# Set logging level
|
|
460
463
|
logger = logging.getLogger("flwr")
|
|
461
464
|
if verbose_logging:
|
|
462
465
|
update_console_handler(level=DEBUG, timestamps=True, colored=True)
|
|
463
466
|
else:
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
)
|
|
467
|
-
backend_config["init_args"]["log_to_driver"] = backend_config["init_args"].get(
|
|
468
|
-
"log_to_driver", True
|
|
469
|
-
)
|
|
467
|
+
init_args = backend_config["init_args"]
|
|
468
|
+
init_args.setdefault("logging_level", WARNING)
|
|
469
|
+
init_args.setdefault("log_to_driver", True)
|
|
470
470
|
|
|
471
471
|
if enable_tf_gpu_growth:
|
|
472
472
|
# Check that Backend config has also enabled using GPU growth
|
|
@@ -482,6 +482,7 @@ def _run_simulation(
|
|
|
482
482
|
if run is None:
|
|
483
483
|
run_id = generate_rand_int_from_bytes(RUN_ID_NUM_BYTES)
|
|
484
484
|
run = Run.create_empty(run_id=run_id)
|
|
485
|
+
run.federation = NOOP_FEDERATION
|
|
485
486
|
|
|
486
487
|
args = (
|
|
487
488
|
num_supernodes,
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
from logging import DEBUG, WARNING
|
|
19
|
-
from typing import
|
|
19
|
+
from typing import cast
|
|
20
20
|
|
|
21
21
|
import grpc
|
|
22
22
|
|
|
@@ -43,12 +43,12 @@ class SimulationIoConnection:
|
|
|
43
43
|
def __init__( # pylint: disable=too-many-arguments
|
|
44
44
|
self,
|
|
45
45
|
simulationio_service_address: str = SIMULATIONIO_API_DEFAULT_CLIENT_ADDRESS,
|
|
46
|
-
root_certificates:
|
|
46
|
+
root_certificates: bytes | None = None,
|
|
47
47
|
) -> None:
|
|
48
48
|
self._addr = simulationio_service_address
|
|
49
49
|
self._cert = root_certificates
|
|
50
|
-
self._grpc_stub:
|
|
51
|
-
self._channel:
|
|
50
|
+
self._grpc_stub: SimulationIoStub | None = None
|
|
51
|
+
self._channel: grpc.Channel | None = None
|
|
52
52
|
self._retry_invoker = _make_simple_grpc_retry_invoker()
|
|
53
53
|
|
|
54
54
|
@property
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import argparse
|
|
19
19
|
from logging import INFO
|
|
20
|
-
from typing import Any
|
|
20
|
+
from typing import Any
|
|
21
21
|
|
|
22
22
|
import yaml
|
|
23
23
|
|
|
@@ -54,7 +54,7 @@ except ImportError:
|
|
|
54
54
|
|
|
55
55
|
def get_ee_plugin_and_stub_class( # pylint: disable=unused-argument
|
|
56
56
|
plugin_type: str,
|
|
57
|
-
) ->
|
|
57
|
+
) -> tuple[type[ExecPlugin], type[object]] | None:
|
|
58
58
|
"""Get the EE plugin class and stub class based on the plugin type."""
|
|
59
59
|
return None
|
|
60
60
|
|
|
@@ -75,7 +75,6 @@ def flower_superexec() -> None:
|
|
|
75
75
|
# Log the first message after parsing arguments in case of `--help`
|
|
76
76
|
log(INFO, "Starting Flower SuperExec")
|
|
77
77
|
|
|
78
|
-
# Trigger telemetry event
|
|
79
78
|
event(EventType.RUN_SUPEREXEC_ENTER, {"plugin_type": args.plugin_type})
|
|
80
79
|
|
|
81
80
|
# Load plugin config from YAML file if provided
|
|
@@ -83,7 +82,7 @@ def flower_superexec() -> None:
|
|
|
83
82
|
if plugin_config_path := getattr(args, "plugin_config", None):
|
|
84
83
|
try:
|
|
85
84
|
with open(plugin_config_path, encoding="utf-8") as file:
|
|
86
|
-
yaml_config:
|
|
85
|
+
yaml_config: dict[str, Any] | None = yaml.safe_load(file)
|
|
87
86
|
if yaml_config is None or EXEC_PLUGIN_SECTION not in yaml_config:
|
|
88
87
|
raise ValueError(f"Missing '{EXEC_PLUGIN_SECTION}' section.")
|
|
89
88
|
plugin_config = yaml_config[EXEC_PLUGIN_SECTION]
|
flwr/supercore/constant.py
CHANGED
|
@@ -15,5 +15,57 @@
|
|
|
15
15
|
"""Constants for Flower infrastructure."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from flwr.common.constant import FLWR_DIR
|
|
21
|
+
|
|
18
22
|
# Top-level key in YAML config for exec plugin settings
|
|
19
23
|
EXEC_PLUGIN_SECTION = "exec_plugin"
|
|
24
|
+
|
|
25
|
+
# Flower in-memory Python-based database name
|
|
26
|
+
FLWR_IN_MEMORY_DB_NAME = ":flwr-in-memory:"
|
|
27
|
+
|
|
28
|
+
# Constants for Hub
|
|
29
|
+
APP_ID_PATTERN = r"^@[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$"
|
|
30
|
+
APP_VERSION_PATTERN = r"^\d+\.\d+\.\d+$"
|
|
31
|
+
PLATFORM_API_URL = "https://api.flower.ai/v1"
|
|
32
|
+
|
|
33
|
+
# Specification for app publishing
|
|
34
|
+
APP_PUBLISH_INCLUDE_PATTERNS = (
|
|
35
|
+
"**/*.py",
|
|
36
|
+
"**/*.toml",
|
|
37
|
+
"**/*.md",
|
|
38
|
+
)
|
|
39
|
+
APP_PUBLISH_EXCLUDE_PATTERNS = FAB_EXCLUDE_PATTERNS = (
|
|
40
|
+
f"{FLWR_DIR}/**", # Exclude the .flwr directory
|
|
41
|
+
"**/__pycache__/**",
|
|
42
|
+
)
|
|
43
|
+
MAX_TOTAL_BYTES = 10 * 1024 * 1024 # 10 MB
|
|
44
|
+
MAX_FILE_BYTES = 1 * 1024 * 1024 # 1 MB
|
|
45
|
+
MAX_FILE_COUNT = 1000
|
|
46
|
+
MAX_DIR_DEPTH = 10 # relative depth (number of parts in relpath)
|
|
47
|
+
UTF8 = "utf-8"
|
|
48
|
+
MIME_MAP = {
|
|
49
|
+
".py": "text/x-python; charset=utf-8",
|
|
50
|
+
".md": "text/markdown; charset=utf-8",
|
|
51
|
+
".toml": "application/toml; charset=utf-8",
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# Constants for federations
|
|
55
|
+
NOOP_FEDERATION = "default"
|
|
56
|
+
|
|
57
|
+
# Constants for exit handling
|
|
58
|
+
FORCE_EXIT_TIMEOUT_SECONDS = 5 # Used in `flwr_exit` function
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class NodeStatus:
|
|
62
|
+
"""Event log writer types."""
|
|
63
|
+
|
|
64
|
+
REGISTERED = "registered"
|
|
65
|
+
ONLINE = "online"
|
|
66
|
+
OFFLINE = "offline"
|
|
67
|
+
UNREGISTERED = "unregistered"
|
|
68
|
+
|
|
69
|
+
def __new__(cls) -> NodeStatus:
|
|
70
|
+
"""Prevent instantiation."""
|
|
71
|
+
raise TypeError(f"{cls.__name__} cannot be instantiated.")
|
|
@@ -16,14 +16,20 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
from abc import ABC, abstractmethod
|
|
19
|
-
|
|
19
|
+
|
|
20
|
+
from ..object_store import ObjectStore
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
class CoreState(ABC):
|
|
23
24
|
"""Abstract base class for core state."""
|
|
24
25
|
|
|
26
|
+
@property
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def object_store(self) -> ObjectStore:
|
|
29
|
+
"""Return the ObjectStore instance used by this CoreState."""
|
|
30
|
+
|
|
25
31
|
@abstractmethod
|
|
26
|
-
def create_token(self, run_id: int) ->
|
|
32
|
+
def create_token(self, run_id: int) -> str | None:
|
|
27
33
|
"""Create a token for the given run ID.
|
|
28
34
|
|
|
29
35
|
Parameters
|
|
@@ -66,7 +72,7 @@ class CoreState(ABC):
|
|
|
66
72
|
"""
|
|
67
73
|
|
|
68
74
|
@abstractmethod
|
|
69
|
-
def get_run_id_by_token(self, token: str) ->
|
|
75
|
+
def get_run_id_by_token(self, token: str) -> int | None:
|
|
70
76
|
"""Get the run ID associated with a given token.
|
|
71
77
|
|
|
72
78
|
Parameters
|
|
@@ -79,3 +85,18 @@ class CoreState(ABC):
|
|
|
79
85
|
Optional[int]
|
|
80
86
|
The run ID if the token is valid, otherwise None.
|
|
81
87
|
"""
|
|
88
|
+
|
|
89
|
+
@abstractmethod
|
|
90
|
+
def acknowledge_app_heartbeat(self, token: str) -> bool:
|
|
91
|
+
"""Acknowledge an app heartbeat with the provided token.
|
|
92
|
+
|
|
93
|
+
Parameters
|
|
94
|
+
----------
|
|
95
|
+
token : str
|
|
96
|
+
The token associated with the app.
|
|
97
|
+
|
|
98
|
+
Returns
|
|
99
|
+
-------
|
|
100
|
+
bool
|
|
101
|
+
True if the heartbeat is acknowledged successfully, False otherwise.
|
|
102
|
+
"""
|
|
@@ -0,0 +1,138 @@
|
|
|
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
|
+
"""In-memory CoreState implementation."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
import secrets
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from threading import Lock
|
|
21
|
+
|
|
22
|
+
from flwr.common import now
|
|
23
|
+
from flwr.common.constant import (
|
|
24
|
+
FLWR_APP_TOKEN_LENGTH,
|
|
25
|
+
HEARTBEAT_DEFAULT_INTERVAL,
|
|
26
|
+
HEARTBEAT_PATIENCE,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
from ..object_store import ObjectStore
|
|
30
|
+
from .corestate import CoreState
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class TokenRecord:
|
|
35
|
+
"""Record containing token and heartbeat information."""
|
|
36
|
+
|
|
37
|
+
token: str
|
|
38
|
+
active_until: float
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class InMemoryCoreState(CoreState):
|
|
42
|
+
"""In-memory CoreState implementation."""
|
|
43
|
+
|
|
44
|
+
def __init__(self, object_store: ObjectStore) -> None:
|
|
45
|
+
self._object_store = object_store
|
|
46
|
+
# Store run ID to token mapping and token to run ID mapping
|
|
47
|
+
self.token_store: dict[int, TokenRecord] = {}
|
|
48
|
+
self.token_to_run_id: dict[str, int] = {}
|
|
49
|
+
self.lock_token_store = Lock()
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def object_store(self) -> ObjectStore:
|
|
53
|
+
"""Return the ObjectStore instance used by this CoreState."""
|
|
54
|
+
return self._object_store
|
|
55
|
+
|
|
56
|
+
def create_token(self, run_id: int) -> str | None:
|
|
57
|
+
"""Create a token for the given run ID."""
|
|
58
|
+
token = secrets.token_hex(FLWR_APP_TOKEN_LENGTH) # Generate a random token
|
|
59
|
+
with self.lock_token_store:
|
|
60
|
+
if run_id in self.token_store:
|
|
61
|
+
return None # Token already created for this run ID
|
|
62
|
+
|
|
63
|
+
self.token_store[run_id] = TokenRecord(
|
|
64
|
+
token=token, active_until=now().timestamp() + HEARTBEAT_DEFAULT_INTERVAL
|
|
65
|
+
)
|
|
66
|
+
self.token_to_run_id[token] = run_id
|
|
67
|
+
return token
|
|
68
|
+
|
|
69
|
+
def verify_token(self, run_id: int, token: str) -> bool:
|
|
70
|
+
"""Verify a token for the given run ID."""
|
|
71
|
+
self._cleanup_expired_tokens()
|
|
72
|
+
with self.lock_token_store:
|
|
73
|
+
record = self.token_store.get(run_id)
|
|
74
|
+
return record is not None and record.token == token
|
|
75
|
+
|
|
76
|
+
def delete_token(self, run_id: int) -> None:
|
|
77
|
+
"""Delete the token for the given run ID."""
|
|
78
|
+
with self.lock_token_store:
|
|
79
|
+
record = self.token_store.pop(run_id, None)
|
|
80
|
+
if record is not None:
|
|
81
|
+
self.token_to_run_id.pop(record.token, None)
|
|
82
|
+
|
|
83
|
+
def get_run_id_by_token(self, token: str) -> int | None:
|
|
84
|
+
"""Get the run ID associated with a given token."""
|
|
85
|
+
self._cleanup_expired_tokens()
|
|
86
|
+
with self.lock_token_store:
|
|
87
|
+
return self.token_to_run_id.get(token)
|
|
88
|
+
|
|
89
|
+
def acknowledge_app_heartbeat(self, token: str) -> bool:
|
|
90
|
+
"""Acknowledge an app heartbeat with the provided token."""
|
|
91
|
+
# Clean up expired tokens
|
|
92
|
+
self._cleanup_expired_tokens()
|
|
93
|
+
|
|
94
|
+
with self.lock_token_store:
|
|
95
|
+
# Return False if token is not found
|
|
96
|
+
if token not in self.token_to_run_id:
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
# Get the run_id and update heartbeat info
|
|
100
|
+
run_id = self.token_to_run_id[token]
|
|
101
|
+
record = self.token_store[run_id]
|
|
102
|
+
current = now().timestamp()
|
|
103
|
+
record.active_until = (
|
|
104
|
+
current + HEARTBEAT_PATIENCE * HEARTBEAT_DEFAULT_INTERVAL
|
|
105
|
+
)
|
|
106
|
+
return True
|
|
107
|
+
|
|
108
|
+
def _cleanup_expired_tokens(self) -> None:
|
|
109
|
+
"""Remove expired tokens and perform additional cleanup.
|
|
110
|
+
|
|
111
|
+
This method is called before token operations to ensure integrity.
|
|
112
|
+
Subclasses can override `_on_tokens_expired` to add custom cleanup logic.
|
|
113
|
+
"""
|
|
114
|
+
with self.lock_token_store:
|
|
115
|
+
current = now().timestamp()
|
|
116
|
+
expired_records: list[tuple[int, float]] = []
|
|
117
|
+
for run_id, record in list(self.token_store.items()):
|
|
118
|
+
if record.active_until < current:
|
|
119
|
+
expired_records.append((run_id, record.active_until))
|
|
120
|
+
# Remove from both stores
|
|
121
|
+
del self.token_store[run_id]
|
|
122
|
+
self.token_to_run_id.pop(record.token, None)
|
|
123
|
+
|
|
124
|
+
# Hook for subclasses
|
|
125
|
+
if expired_records:
|
|
126
|
+
self._on_tokens_expired(expired_records)
|
|
127
|
+
|
|
128
|
+
def _on_tokens_expired(self, expired_records: list[tuple[int, float]]) -> None:
|
|
129
|
+
"""Handle cleanup of expired tokens.
|
|
130
|
+
|
|
131
|
+
Override in subclasses to add custom cleanup logic.
|
|
132
|
+
|
|
133
|
+
Parameters
|
|
134
|
+
----------
|
|
135
|
+
expired_records : list[tuple[int, float]]
|
|
136
|
+
List of tuples containing (run_id, active_until timestamp)
|
|
137
|
+
for expired tokens.
|
|
138
|
+
"""
|